Skip to content

Commit 63dd87b

Browse files
yunabewardpeet
authored andcommitted
feat(gatsby-remark-autolink-headers): Support {#custom-id} header syntax (#14253)
- add enableCustomId option - enable usage of custom-id ``{#custom-id}` when enableCustomId is set to true
1 parent 91bafd6 commit 63dd87b

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

packages/gatsby-remark-autolink-headers/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Note: if you are using `gatsby-remark-prismjs`, make sure that it’s listed aft
5757
- `className`: String. Set your own class for the anchor (optional)
5858
- `maintainCase`: Boolean. Maintains the case for markdown header (optional)
5959
- `removeAccents`: Boolean. Remove accents from generated headings IDs (optional)
60+
- `enableCustomId`: Boolean. Enable custom header IDs with `{#id}` (optional)
6061

6162
```javascript
6263
// In your gatsby-config.js

packages/gatsby-remark-autolink-headers/src/__tests__/index.js

+57
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const Remark = require(`remark`)
2+
const toString = require(`mdast-util-to-string`)
23
const visit = require(`unist-util-visit`)
34

45
const plugin = require(`../`)
@@ -182,4 +183,60 @@ describe(`gatsby-remark-autolink-headers`, () => {
182183
expect(node.data.id).toEqual(expect.stringMatching(/^heading/))
183184
})
184185
})
186+
187+
it(`extracts custom id`, () => {
188+
const markdownAST = remark.parse(`
189+
# Heading One {#custom_h1}
190+
191+
## Heading Two {#custom-heading-two}
192+
193+
# With *Bold* {#custom-withbold}
194+
195+
# Invalid {#this_is_italic}
196+
197+
# No custom ID
198+
199+
# {#id-only}
200+
201+
# {#text-after} custom ID
202+
`)
203+
const enableCustomId = true
204+
205+
const transformed = plugin({ markdownAST }, { enableCustomId })
206+
207+
const headers = []
208+
visit(transformed, `heading`, node => {
209+
headers.push({ text: toString(node), id: node.data.id })
210+
})
211+
expect(headers).toEqual([
212+
{
213+
id: `custom_h1`,
214+
text: `Heading One`,
215+
},
216+
{
217+
id: `custom-heading-two`,
218+
text: `Heading Two`,
219+
},
220+
{
221+
id: `custom-withbold`,
222+
text: `With Bold`,
223+
},
224+
{
225+
id: `invalid-thisisitalic`,
226+
text: `Invalid {#thisisitalic}`,
227+
},
228+
{
229+
id: `no-custom-id`,
230+
text: `No custom ID`,
231+
},
232+
{
233+
id: `id-only`,
234+
text: `{#id-only}`,
235+
},
236+
{
237+
id: `text-after-custom-id`,
238+
text: `{#text-after} custom ID`,
239+
},
240+
])
241+
})
185242
})

packages/gatsby-remark-autolink-headers/src/index.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,32 @@ module.exports = (
2020
className = `anchor`,
2121
maintainCase = false,
2222
removeAccents = false,
23+
enableCustomId = false,
2324
}
2425
) => {
2526
slugs.reset()
2627

2728
visit(markdownAST, `heading`, node => {
28-
const slug = slugs.slug(toString(node), maintainCase)
29-
const id = removeAccents ? deburr(slug) : slug
29+
let id
30+
if (enableCustomId && node.children.length > 0) {
31+
const last = node.children[node.children.length - 1]
32+
// This regex matches to preceding spaces and {#custom-id} at the end of a string.
33+
// Also, checks the text of node won't be empty after the removal of {#custom-id}.
34+
const match = /^(.*?)\s*\{#([\w-]+)\}$/.exec(toString(last))
35+
if (match && (match[1] || node.children.length > 1)) {
36+
id = match[2]
37+
// Remove the custom ID from the original text.
38+
if (match[1]) {
39+
last.value = match[1]
40+
} else {
41+
node.children.pop()
42+
}
43+
}
44+
}
45+
if (!id) {
46+
const slug = slugs.slug(toString(node), maintainCase)
47+
id = removeAccents ? deburr(slug) : slug
48+
}
3049
const data = patch(node, `data`, {})
3150

3251
patch(data, `id`, id)

0 commit comments

Comments
 (0)