Skip to content

Commit a32e36a

Browse files
committed
feat: anchorAlias option to set custom header id
1 parent 9058e8b commit a32e36a

File tree

4 files changed

+52
-1
lines changed

4 files changed

+52
-1
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ marked:
2929
smartypants: true
3030
quotes: '“”‘’'
3131
modifyAnchors: 0
32+
anchorAlias: false
3233
autolink: true
3334
mangle: true
3435
sanitizeUrl: false
@@ -59,6 +60,9 @@ marked:
5960
* This is to obscure email address from _basic_ crawler used by spam bot, while still readable to web browsers.
6061
- **sanitizeUrl** - Remove URLs that start with `javascript:`, `vbscript:` and `data:`.
6162
- **headerIds** - Insert header id, e.g. `<h1 id="value">text</h1>`. Useful for inserting anchor link to each paragraph with a heading.
63+
- **anchorAlias** - Enables custom header id
64+
* Example: `## [foo](#bar)`, id will be set as "bar".
65+
* Requires **headerIds** to be enabled.
6266
- **lazyload** - Lazy loading images via `loading="lazy"` attribute.
6367
- **prependRoot** - Prepend root value to (internal) image path.
6468
* Example `_config.yml`:

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ hexo.config.marked = Object.assign({
1515
mangle: true,
1616
sanitizeUrl: false,
1717
headerIds: true,
18+
anchorAlias: false,
1819
lazyload: false,
1920
// TODO: enable prependRoot by default in v3
2021
prependRoot: false,

lib/renderer.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo
66
const MarkedRenderer = marked.Renderer;
77
const MarkedTokenizer = marked.Tokenizer;
88
const { basename, dirname, extname, join } = require('path').posix;
9+
const rATag = /<a(?:\s+?|\s+?[^<>]+\s+?)?href=["'](?:#)([^<>"']+)["'][^<>]*>/i;
910

1011
const anchorId = (str, transformOption) => {
1112
return slugize(str.trim(), {transform: transformOption});
@@ -20,7 +21,7 @@ class Renderer extends MarkedRenderer {
2021

2122
// Add id attribute to headings
2223
heading(text, level) {
23-
const { headerIds, modifyAnchors } = this.options;
24+
const { anchorAlias, headerIds, modifyAnchors } = this.options;
2425
const { _headingId } = this;
2526

2627
if (!headerIds) {
@@ -31,13 +32,25 @@ class Renderer extends MarkedRenderer {
3132
let id = anchorId(stripHTML(text), transformOption);
3233
const headingId = _headingId;
3334

35+
const anchorAliasOpt = anchorAlias && text.startsWith('<a href="#');
36+
if (anchorAliasOpt) {
37+
const customAnchor = text.match(rATag)[1];
38+
id = anchorId(stripHTML(customAnchor), transformOption);
39+
}
40+
3441
// Add a number after id if repeated
3542
if (headingId[id]) {
3643
id += `-${headingId[id]++}`;
3744
} else {
3845
headingId[id] = 1;
3946
}
4047

48+
if (anchorAliasOpt) {
49+
text = text.replace(rATag, (str, alias) => {
50+
return str.replace(alias, id);
51+
});
52+
}
53+
4154
// add headerlink
4255
return `<h${level} id="${id}"><a href="#${id}" class="headerlink" title="${stripHTML(text)}"></a>${text}</h${level}>`;
4356
}

test/index.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,39 @@ describe('Marked renderer', () => {
7474
].join(''));
7575
});
7676

77+
describe('anchorAlias', () => {
78+
beforeEach(() => { hexo.config.marked.anchorAlias = true; });
79+
80+
it('default', () => {
81+
const body = '## [foo](#alias)';
82+
83+
const result = r({text: body});
84+
result.should.eql('<h2 id="alias"><a href="#alias" class="headerlink" title="foo"></a><a href="#alias">foo</a></h2>');
85+
});
86+
87+
it('duplicate anchors', () => {
88+
const body = [
89+
'## [foo](#alias)',
90+
'## [bar](#alias)'
91+
].join('\n');
92+
93+
const result = r({text: body});
94+
result.should.eql([
95+
'<h2 id="alias"><a href="#alias" class="headerlink" title="foo"></a><a href="#alias">foo</a></h2>',
96+
'<h2 id="alias-1"><a href="#alias-1" class="headerlink" title="bar"></a><a href="#alias-1">bar</a></h2>'
97+
].join(''));
98+
});
99+
100+
it('modifyAnchors', () => {
101+
hexo.config.marked.modifyAnchors = 2;
102+
const body = '## [foo](#alias)';
103+
104+
const result = r({text: body});
105+
result.should.eql('<h2 id="ALIAS"><a href="#ALIAS" class="headerlink" title="foo"></a><a href="#ALIAS">foo</a></h2>');
106+
});
107+
});
108+
109+
77110
it('should handle duplicate headings properly', () => {
78111
const body = [
79112
'## foo',

0 commit comments

Comments
 (0)