Skip to content

Commit

Permalink
feat(v2): add ability to set custom heading id
Browse files Browse the repository at this point in the history
  • Loading branch information
lex111 committed Feb 13, 2021
1 parent 11c2426 commit c8baed6
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 10 deletions.
4 changes: 2 additions & 2 deletions packages/docusaurus-mdx-loader/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ const mdx = require('@mdx-js/mdx');
const emoji = require('remark-emoji');
const matter = require('gray-matter');
const stringifyObject = require('stringify-object');
const slug = require('./remark/slug');
const headings = require('./remark/headings');
const toc = require('./remark/toc');
const transformImage = require('./remark/transformImage');
const transformLinks = require('./remark/transformLinks');

const DEFAULT_OPTIONS = {
rehypePlugins: [],
remarkPlugins: [emoji, slug, toc],
remarkPlugins: [emoji, headings, toc],
};

module.exports = async function docusaurusMdxLoader(fileString) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/

/* Based on remark-slug (https://github.com/remarkjs/remark-slug) */
/* Based on remark-slug (https://github.com/remarkjs/remark-slug) and gatsby-remark-autolink-headers (https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-autolink-headers) */

/* eslint-disable no-param-reassign */

import remark from 'remark';
import u from 'unist-builder';
import removePosition from 'unist-util-remove-position';
import toString from 'mdast-util-to-string';
import visit from 'unist-util-visit';
import slug from '../index';

function process(doc, plugins = []) {
Expand All @@ -27,7 +29,7 @@ function heading(label, id) {
);
}

describe('slug plugin', () => {
describe('headings plugin', () => {
test('should patch `id`s and `data.hProperties.id', () => {
const result = process('# Normal\n\n## Table of Contents\n\n# Baz\n');
const expected = u('root', [
Expand Down Expand Up @@ -157,7 +159,7 @@ describe('slug plugin', () => {
expect(result).toEqual(expected);
});

test('should create GitHub slugs', () => {
test('should create GitHub-style headings ids', () => {
const result = process(
[
'## I ♥ unicode',
Expand Down Expand Up @@ -225,7 +227,7 @@ describe('slug plugin', () => {
expect(result).toEqual(expected);
});

test('should generate slug from only text contents of headings if they contains HTML tags', () => {
test('should generate id from only text contents of headings if they contains HTML tags', () => {
const result = process('# <span class="normal-header">Normal</span>\n');
const expected = u('root', [
u(
Expand All @@ -244,4 +246,58 @@ describe('slug plugin', () => {

expect(result).toEqual(expected);
});

test('should create custom headings ids', () => {
const result = process(`
# Heading One {#custom_h1}
## Heading Two {#custom-heading-two}
# With *Bold* {#custom-withbold}
# Snake-cased ID {#this_is_custom_id}
# No custom ID
# {#id-only}
# {#text-after} custom ID
`);

const headers = [];
visit(result, 'heading', (node) => {
headers.push({text: toString(node), id: node.data.id});
});

expect(headers).toEqual([
{
id: 'custom_h1',
text: 'Heading One',
},
{
id: 'custom-heading-two',
text: 'Heading Two',
},
{
id: 'custom-withbold',
text: 'With Bold',
},
{
id: 'this_is_custom_id',
text: 'Snake-cased ID',
},
{
id: 'no-custom-id',
text: 'No custom I',
},
{
id: 'id-only',
text: '{#id-only}',
},
{
id: 'text-after-custom-id',
text: '{#text-after} custom ID',
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/

/* Based on remark-slug (https://github.com/remarkjs/remark-slug) */
/* Based on remark-slug (https://github.com/remarkjs/remark-slug) and gatsby-remark-autolink-headers (https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-autolink-headers) */

const visit = require('unist-util-visit');
const toString = require('mdast-util-to-string');
const slugs = require('github-slugger')();

const customHeadingIdRegex = /^(.*?)\s*\{#([\w-]+)\}$/;

function slug() {
const transformer = (ast) => {
slugs.reset();
Expand All @@ -26,11 +28,29 @@ function slug() {
const headingTextNodes = headingNode.children.filter(
({type}) => !['html', 'jsx'].includes(type),
);
const normalizedHeadingNode =
const heading = toString(
headingTextNodes.length > 0
? {children: headingTextNodes}
: headingNode;
id = slugs.slug(toString(normalizedHeadingNode));
: headingNode,
);

// Support explicit heading IDs
const customHeadingIdMatches = customHeadingIdRegex.exec(heading);

if (customHeadingIdMatches) {
id = customHeadingIdMatches[2];

// Remove the custom ID part from the text node
if (headingNode.children.length > 1) {
headingNode.children.pop();
} else {
const lastNode =
headingNode.children[headingNode.children.length - 1];
lastNode.value = customHeadingIdMatches[1] || heading;
}
} else {
id = slugs.slug(heading);
}
}

data.id = id;
Expand Down
4 changes: 4 additions & 0 deletions website/docs/guides/docs/docs-create-doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ The headers are well-spaced so that the hierarchy is clear.
- that you want your users to remember
- and you may nest them
- multiple times

### Custom id headers {#custom-id}

With `{#custom-id}` syntax you can set your own header id.
```

This will render in the browser as follows:
Expand Down
2 changes: 2 additions & 0 deletions website/src/pages/examples/markdownPageExample.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,5 @@ import MyComponentSource from '!!raw-loader!@site/src/pages/examples/\_myCompone
<CodeBlock className="language-jsx">{MyComponentSource}</CodeBlock>

</BrowserWindow>

## Custom heading id {#custom}

0 comments on commit c8baed6

Please sign in to comment.