Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby-remark-autolink-headers): Support {#custom-id} header syntax #14253

Merged
merged 6 commits into from
Jun 14, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support {#custom-id} header syntax in gatsby-remark-autolink-headers
  • Loading branch information
yunabe committed May 22, 2019
commit 7d160fe2a5f01b76587dfdf5a6acdfbc60bfc53a
1 change: 1 addition & 0 deletions packages/gatsby-remark-autolink-headers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Note: if you are using `gatsby-remark-prismjs`, make sure that it’s listed aft
- `className`: String. Set your own class for the anchor (optional)
- `maintainCase`: Boolean. Maintains the case for markdown header (optional)
- `removeAccents`: Boolean. Remove accents from generated headings IDs (optional)
- `enableCustomId`: Boolean. Enable custom header IDs with `{#id}` (optional)

```javascript
// In your gatsby-config.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,236 @@ Object {
}
`;

exports[`gatsby-remark-autolink-headers extracts custom id 1`] = `
Object {
"children": Array [
Object {
"data": Object {
"hChildren": Array [
Object {
"type": "raw",
"value": "<svg aria-hidden=\\"true\\" focusable=\\"false\\" height=\\"16\\" version=\\"1.1\\" viewBox=\\"0 0 16 16\\" width=\\"16\\"><path fill-rule=\\"evenodd\\" d=\\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\\"></path></svg>",
},
],
"hProperties": Object {
"aria-label": "cusom_h1 permalink",
"class": "anchor",
},
},
"title": null,
"type": "link",
"url": "#cusom_h1",
},
Object {
"position": Position {
"end": Object {
"column": 26,
"line": 2,
"offset": 26,
},
"indent": Array [],
"start": Object {
"column": 3,
"line": 2,
"offset": 3,
},
},
"type": "text",
"value": "Heading One",
},
],
"data": Object {
"hProperties": Object {
"id": "cusom_h1",
},
"htmlAttributes": Object {
"id": "cusom_h1",
},
"id": "cusom_h1",
},
"depth": 1,
"position": Position {
"end": Object {
"column": 26,
"line": 2,
"offset": 26,
},
"indent": Array [],
"start": Object {
"column": 1,
"line": 2,
"offset": 1,
},
},
"type": "heading",
}
`;

exports[`gatsby-remark-autolink-headers extracts custom id 2`] = `
Object {
"children": Array [
Object {
"data": Object {
"hChildren": Array [
Object {
"type": "raw",
"value": "<svg aria-hidden=\\"true\\" focusable=\\"false\\" height=\\"16\\" version=\\"1.1\\" viewBox=\\"0 0 16 16\\" width=\\"16\\"><path fill-rule=\\"evenodd\\" d=\\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\\"></path></svg>",
},
],
"hProperties": Object {
"aria-label": "custom heading two permalink",
"class": "anchor",
},
},
"title": null,
"type": "link",
"url": "#custom-heading-two",
},
Object {
"position": Position {
"end": Object {
"column": 37,
"line": 4,
"offset": 64,
},
"indent": Array [],
"start": Object {
"column": 4,
"line": 4,
"offset": 31,
},
},
"type": "text",
"value": "Heading Two",
},
],
"data": Object {
"hProperties": Object {
"id": "custom-heading-two",
},
"htmlAttributes": Object {
"id": "custom-heading-two",
},
"id": "custom-heading-two",
},
"depth": 2,
"position": Position {
"end": Object {
"column": 37,
"line": 4,
"offset": 64,
},
"indent": Array [],
"start": Object {
"column": 1,
"line": 4,
"offset": 28,
},
},
"type": "heading",
}
`;

exports[`gatsby-remark-autolink-headers extracts custom id 3`] = `
Object {
"children": Array [
Object {
"data": Object {
"hChildren": Array [
Object {
"type": "raw",
"value": "<svg aria-hidden=\\"true\\" focusable=\\"false\\" height=\\"16\\" version=\\"1.1\\" viewBox=\\"0 0 16 16\\" width=\\"16\\"><path fill-rule=\\"evenodd\\" d=\\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\\"></path></svg>",
},
],
"hProperties": Object {
"aria-label": "cusgom withbold permalink",
"class": "anchor",
},
},
"title": null,
"type": "link",
"url": "#cusgom-withbold",
},
Object {
"position": Position {
"end": Object {
"column": 8,
"line": 6,
"offset": 73,
},
"indent": Array [],
"start": Object {
"column": 3,
"line": 6,
"offset": 68,
},
},
"type": "text",
"value": "With ",
},
Object {
"children": Array [
Object {
"position": Position {
"end": Object {
"column": 13,
"line": 6,
"offset": 78,
},
"indent": Array [],
"start": Object {
"column": 9,
"line": 6,
"offset": 74,
},
},
"type": "text",
"value": "Bold",
},
],
"position": Position {
"end": Object {
"column": 14,
"line": 6,
"offset": 79,
},
"indent": Array [],
"start": Object {
"column": 8,
"line": 6,
"offset": 73,
},
},
"type": "emphasis",
},
],
"data": Object {
"hProperties": Object {
"id": "cusgom-withbold",
},
"htmlAttributes": Object {
"id": "cusgom-withbold",
},
"id": "cusgom-withbold",
},
"depth": 1,
"position": Position {
"end": Object {
"column": 33,
"line": 6,
"offset": 98,
},
"indent": Array [],
"start": Object {
"column": 1,
"line": 6,
"offset": 66,
},
},
"type": "heading",
}
`;

exports[`gatsby-remark-autolink-headers maintain case of markdown header for id 1`] = `
Object {
"children": Array [
Expand Down
19 changes: 19 additions & 0 deletions packages/gatsby-remark-autolink-headers/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,23 @@ describe(`gatsby-remark-autolink-headers`, () => {
expect(node.data.id).toEqual(expect.stringMatching(/^heading/))
})
})

it(`extracts custom id`, () => {
const markdownAST = remark.parse(`
# Heading One {#cusom_h1}

## Heading Two {#custom-heading-two}

# With *Bold* {#cusgom-withbold}
`)
const enableCustomId = true

const transformed = plugin({ markdownAST }, { enableCustomId })

visit(transformed, `heading`, node => {
expect(node.data.id).toBeDefined()

expect(node).toMatchSnapshot()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort of a nit: but generally I'd argue we way overuse snapshots and they make future failures more confusing than they need to be.

Could you rather test for a specific id, e.g. something like

const customId = `custom-id`

const markdownAST = remark.parse(`
# Heading With a Custom Id {#${customId}}
`)

// later

visit(transformed, `heading`, node => {
  expect(node.data.id).toBe(customId)
})

Obviously you can scale this however you'd like if there's value in testing for multiple instances.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I rewrote this test without using snapshots. Does it look better? If you like this, I will rewrite other tests with this style in another PR.

})
})
})
21 changes: 19 additions & 2 deletions packages/gatsby-remark-autolink-headers/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,30 @@ module.exports = (
className = `anchor`,
maintainCase = false,
removeAccents = false,
enableCustomId = false,
}
) => {
slugs.reset()

visit(markdownAST, `heading`, node => {
const slug = slugs.slug(toString(node), maintainCase)
const id = removeAccents ? deburr(slug) : slug
let id
if (enableCustomId && node.children.length > 0) {
const last = node.children[node.children.length - 1]
const match = /^(.*?)\s*\{#([\w-]+)\}$/.exec(toString(last))
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
if (match) {
id = match[2]
// Remove the custom ID from the original text.
if (match[1]) {
last.value = match[1]
} else {
node.children.pop()
}
}
}
if (!id) {
const slug = slugs.slug(toString(node), maintainCase)
id = removeAccents ? deburr(slug) : slug
}
const data = patch(node, `data`, {})

patch(data, `id`, id)
Expand Down