Skip to content

Commit 6386ee2

Browse files
committed
Custom markdown-it-anchors renderer
This applies an accessible label to the inserted hash. Most of the new code is concerned with building a plain-text representation of the header’s content.
1 parent e35d195 commit 6386ee2

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

docs/.vuepress/config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const markdownHelpers = require('./theme/util/markdown');
2+
13
module.exports = {
24
theme: "craftdocs",
35
base: "/docs/",
@@ -73,7 +75,9 @@ module.exports = {
7375
markdown: {
7476
extractHeaders: ['h2', 'h3', 'h4'],
7577
anchor: {
76-
level: [2, 3, 4]
78+
level: [2, 3, 4],
79+
permalinkSymbol: '#',
80+
renderPermalink: markdownHelpers.renderPermalink,
7781
},
7882
toc: {
7983
format(content) {
@@ -92,6 +96,6 @@ module.exports = {
9296
}
9397
},
9498
postcss: {
95-
plugins: require("../../postcss.config.js").plugins
99+
plugins: require("../../postcss.config.js").plugins,
96100
}
97101
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
function getHeaderText(token) {
2+
// Get the full text of the token (including any children, if it had formatting), and do a crude entity conversion:
3+
let text = decodeEntities(token.content);
4+
5+
// Does it look like there might be HTML (say, from an in-line Vue component)?
6+
if (text.includes('<')) {
7+
// Taken from `npm-strip-html` package!
8+
text = text.replace(/<[^>]+>([^<]+)?/g, '$1').trim();
9+
}
10+
11+
return text;
12+
}
13+
14+
function decodeEntities(str) {
15+
const entityMap = {
16+
'&lt;': '<',
17+
'&gt;': '>',
18+
'&quot;': '"',
19+
};
20+
const expr = new RegExp(Object.keys(entityMap).join('|'));
21+
22+
return str.replace(expr, function(r) {
23+
return entityMap[r];
24+
});
25+
}
26+
27+
function renderPermalink(slug, opts, state, idx) {
28+
// Get text of current header:
29+
const headerText = getHeaderText(state.tokens[idx + 1]);
30+
31+
const linkTokens = [
32+
// Opening anchor tag:
33+
Object.assign(new state.Token('link_open', 'a', 1), {
34+
attrs: [
35+
['class', opts.permalinkClass],
36+
['href', opts.permalinkHref(slug, state)],
37+
['aria-label', `Permalink to “${headerText}”`],
38+
...Object.entries(opts.permalinkAttrs(slug, state)),
39+
],
40+
}),
41+
// Symbol
42+
Object.assign(new state.Token('html_block', '', 0), { content: opts.permalinkSymbol }),
43+
// Closing anchor tag:
44+
new state.Token('link_close', 'a', -1),
45+
];
46+
47+
// Place at the beginning of the heading tag:
48+
state.tokens[idx + 1].children.unshift(...linkTokens);
49+
}
50+
51+
export {
52+
getHeaderText,
53+
renderPermalink,
54+
};

0 commit comments

Comments
 (0)