mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 07:37:19 +02:00
Ensure anchor links are unique per document (#574)
This commit is contained in:
parent
2a83959ac1
commit
9c98142fea
12 changed files with 440 additions and 43 deletions
20
lib/core/__tests__/__fixtures__/getTOC.md
Normal file
20
lib/core/__tests__/__fixtures__/getTOC.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
## foo
|
||||
### foo
|
||||
### foo 1
|
||||
## foo 1
|
||||
## foo 2
|
||||
### foo
|
||||
#### 4th level headings
|
||||
All 4th level headings should not be shown by default
|
||||
|
||||
## bar
|
||||
### bar
|
||||
#### bar
|
||||
4th level heading should be ignored by default, but is should be always taken
|
||||
into account, when generating slugs
|
||||
### `bar`
|
||||
#### `bar`
|
||||
## bar
|
||||
### bar
|
||||
#### bar
|
||||
## bar
|
5
lib/core/__tests__/__snapshots__/anchors.tests.js.snap
Normal file
5
lib/core/__tests__/__snapshots__/anchors.tests.js.snap
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Anchors rendering 1`] = `"<h1><a class=\\"anchor\\" aria-hidden=\\"true\\" id=\\"hello-world\\"></a><a href=\\"#hello-world\\" aria-hidden=\\"true\\" class=\\"hash-link\\" ><svg aria-hidden=\\"true\\" 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></a>"`;
|
||||
|
||||
exports[`Anchors rendering 2`] = `"<h2><a class=\\"anchor\\" aria-hidden=\\"true\\" id=\\"hello-small-world\\"></a><a href=\\"#hello-small-world\\" aria-hidden=\\"true\\" class=\\"hash-link\\" ><svg aria-hidden=\\"true\\" 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></a>"`;
|
187
lib/core/__tests__/__snapshots__/getTOC.tests.js.snap
Normal file
187
lib/core/__tests__/__snapshots__/getTOC.tests.js.snap
Normal file
|
@ -0,0 +1,187 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`with custom heading levels 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-1",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-1",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
],
|
||||
"content": "foo",
|
||||
"hashLink": "foo",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-2",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-3",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "4th level headings",
|
||||
"hashLink": "4th-level-headings",
|
||||
"rawContent": "4th level headings",
|
||||
},
|
||||
],
|
||||
"content": "foo 2",
|
||||
"hashLink": "foo-2",
|
||||
"rawContent": "foo 2",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-1",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-2",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "<code>bar</code>",
|
||||
"hashLink": "bar-3",
|
||||
"rawContent": "\`bar\`",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "<code>bar</code>",
|
||||
"hashLink": "bar-4",
|
||||
"rawContent": "\`bar\`",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-6",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-7",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-5",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-8",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`with defaults 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-1",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-1",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
],
|
||||
"content": "foo",
|
||||
"hashLink": "foo",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-2",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-3",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
],
|
||||
"content": "foo 2",
|
||||
"hashLink": "foo-2",
|
||||
"rawContent": "foo 2",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-1",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "<code>bar</code>",
|
||||
"hashLink": "bar-3",
|
||||
"rawContent": "\`bar\`",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-6",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-5",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-8",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
]
|
||||
`;
|
104
lib/core/__tests__/anchors.tests.js
Normal file
104
lib/core/__tests__/anchors.tests.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
const anchors = require('../anchors');
|
||||
|
||||
const md = {
|
||||
renderer: {
|
||||
rules: {},
|
||||
},
|
||||
};
|
||||
|
||||
anchors(md);
|
||||
|
||||
const render = md.renderer.rules.heading_open;
|
||||
|
||||
test('Anchors rendering', () => {
|
||||
expect(
|
||||
render([{hLevel: 1}, {content: 'Hello world'}], 0, {}, {})
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
render([{hLevel: 2}, {content: 'Hello small world'}], 0, {}, {})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Each anchor is unique across rendered document', () => {
|
||||
const tokens = [
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading 1'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading 1'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading 2'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
];
|
||||
const options = {};
|
||||
const env = {};
|
||||
|
||||
expect(render(tokens, 0, options, env)).toContain(
|
||||
'id="almost-unique-heading"'
|
||||
);
|
||||
expect(render(tokens, 2, options, env)).toContain(
|
||||
'id="almost-unique-heading-1"'
|
||||
);
|
||||
expect(render(tokens, 4, options, env)).toContain(
|
||||
'id="almost-unique-heading-1-1"'
|
||||
);
|
||||
expect(render(tokens, 6, options, env)).toContain(
|
||||
'id="almost-unique-heading-1-2"'
|
||||
);
|
||||
expect(render(tokens, 8, options, env)).toContain(
|
||||
'id="almost-unique-heading-2"'
|
||||
);
|
||||
expect(render(tokens, 10, options, env)).toContain(
|
||||
'id="almost-unique-heading-3"'
|
||||
);
|
||||
});
|
||||
|
||||
test('Each anchor is unique across rendered document. Case 2', () => {
|
||||
const tokens = [
|
||||
{hLevel: 1},
|
||||
{content: 'foo'},
|
||||
{hLevel: 1},
|
||||
{content: 'foo 1'},
|
||||
{hLevel: 1},
|
||||
{content: 'foo'},
|
||||
{hLevel: 1},
|
||||
{content: 'foo 1'},
|
||||
];
|
||||
const options = {};
|
||||
const env = {};
|
||||
|
||||
expect(render(tokens, 0, options, env)).toContain('id="foo"');
|
||||
expect(render(tokens, 2, options, env)).toContain('id="foo-1"');
|
||||
expect(render(tokens, 4, options, env)).toContain('id="foo-2"');
|
||||
expect(render(tokens, 6, options, env)).toContain('id="foo-1-1"');
|
||||
});
|
||||
|
||||
test('Anchor index resets on each render', () => {
|
||||
const tokens = [
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
];
|
||||
const options = {};
|
||||
const env = {};
|
||||
const env2 = {};
|
||||
|
||||
expect(render(tokens, 0, options, env)).toContain(
|
||||
'id="almost-unique-heading"'
|
||||
);
|
||||
expect(render(tokens, 2, options, env)).toContain(
|
||||
'id="almost-unique-heading-1"'
|
||||
);
|
||||
|
||||
expect(render(tokens, 0, options, env2)).toContain(
|
||||
'id="almost-unique-heading"'
|
||||
);
|
||||
expect(render(tokens, 2, options, env2)).toContain(
|
||||
'id="almost-unique-heading-1"'
|
||||
);
|
||||
});
|
26
lib/core/__tests__/getTOC.tests.js
Normal file
26
lib/core/__tests__/getTOC.tests.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const path = require('path');
|
||||
const readFileSync = require('fs').readFileSync;
|
||||
const getTOC = require('../getTOC');
|
||||
|
||||
const mdContents = readFileSync(
|
||||
path.join(__dirname, '__fixtures__', 'getTOC.md'),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
test('with defaults', () => {
|
||||
const headings = getTOC(mdContents);
|
||||
const headingsJson = JSON.stringify(headings);
|
||||
|
||||
expect(headings).toMatchSnapshot();
|
||||
expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8
|
||||
expect(headingsJson).not.toContain('4th level headings');
|
||||
});
|
||||
|
||||
test('with custom heading levels', () => {
|
||||
const headings = getTOC(mdContents, 'h2', ['h3', 'h4']);
|
||||
const headingsJson = JSON.stringify(headings);
|
||||
|
||||
expect(headings).toMatchSnapshot();
|
||||
expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8
|
||||
expect(headingsJson).toContain('4th level headings');
|
||||
});
|
|
@ -12,3 +12,18 @@ const toSlug = require('../toSlug');
|
|||
expect(toSlug(input)).toBe(output);
|
||||
});
|
||||
});
|
||||
|
||||
test('unique slugs if `context` argument passed', () => {
|
||||
[
|
||||
['foo', 'foo'],
|
||||
['foo', 'foo-1'],
|
||||
['foo 1', 'foo-1-1'],
|
||||
['foo 1', 'foo-1-2'],
|
||||
['foo 2', 'foo-2'],
|
||||
['foo', 'foo-3'],
|
||||
].reduce((context, [input, output]) => {
|
||||
expect(toSlug(input, context)).toBe(output);
|
||||
|
||||
return context;
|
||||
}, {});
|
||||
});
|
||||
|
|
29
lib/core/anchors.js
Normal file
29
lib/core/anchors.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
const toSlug = require('./toSlug.js');
|
||||
|
||||
/**
|
||||
* The anchors plugin adds GFM-style anchors to headings.
|
||||
*/
|
||||
function anchors(md) {
|
||||
md.renderer.rules.heading_open = function(tokens, idx, options, env) {
|
||||
const textToken = tokens[idx + 1];
|
||||
const anchor = toSlug(textToken.content, env);
|
||||
|
||||
return (
|
||||
'<h' +
|
||||
tokens[idx].hLevel +
|
||||
'><a class="anchor" aria-hidden="true" id="' +
|
||||
anchor +
|
||||
'"></a><a href="#' +
|
||||
anchor +
|
||||
'" aria-hidden="true" class="hash-link" ><svg aria-hidden="true" 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></a>'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = anchors;
|
|
@ -23,20 +23,29 @@ module.exports = (content, headingTags = 'h2', subHeadingTags = 'h3') => {
|
|||
const subHeadingLevels = subHeadingTags
|
||||
? [].concat(subHeadingTags).map(tagToLevel)
|
||||
: [];
|
||||
const allowedHeadingLevels = headingLevels.concat(subHeadingLevels);
|
||||
|
||||
const md = new Remarkable();
|
||||
const headings = mdToc(content, {
|
||||
filter: function(str, ele) {
|
||||
return headingLevels.concat(subHeadingLevels).includes(ele.lvl);
|
||||
},
|
||||
}).json;
|
||||
const headings = mdToc(content).json;
|
||||
|
||||
const toc = [];
|
||||
const context = {};
|
||||
let current;
|
||||
|
||||
headings.forEach(heading => {
|
||||
// we need always generate slugs to ensure, that we will have consistent
|
||||
// slug indexes for headings with the same names
|
||||
const hashLink = toSlug(heading.content, context);
|
||||
|
||||
if (!allowedHeadingLevels.includes(heading.lvl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawContent = mdToc.titleize(heading.content);
|
||||
const entry = {
|
||||
hashLink: toSlug(heading.content),
|
||||
content: md.renderInline(mdToc.titleize(heading.content)),
|
||||
hashLink,
|
||||
rawContent,
|
||||
content: md.renderInline(rawContent),
|
||||
children: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -7,28 +7,10 @@
|
|||
|
||||
const hljs = require('highlight.js');
|
||||
const Markdown = require('remarkable');
|
||||
const toSlug = require('./toSlug.js');
|
||||
const anchors = require('./anchors.js');
|
||||
|
||||
const CWD = process.cwd();
|
||||
|
||||
/**
|
||||
* The anchors plugin adds GFM-style anchors to headings.
|
||||
*/
|
||||
function anchors(md) {
|
||||
md.renderer.rules.heading_open = function(tokens, idx /*, options, env */) {
|
||||
const textToken = tokens[idx + 1];
|
||||
return (
|
||||
'<h' +
|
||||
tokens[idx].hLevel +
|
||||
'><a class="anchor" aria-hidden="true" id="' +
|
||||
toSlug(textToken.content) +
|
||||
'"></a><a href="#' +
|
||||
toSlug(textToken.content) +
|
||||
'" aria-hidden="true" class="hash-link" ><svg aria-hidden="true" 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></a>'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
class MarkdownRenderer {
|
||||
constructor() {
|
||||
const siteConfig = require(CWD + '/siteConfig.js');
|
||||
|
|
|
@ -18,7 +18,16 @@ const exceptAlphanum = new RegExp(
|
|||
'g'
|
||||
);
|
||||
|
||||
module.exports = string => {
|
||||
/**
|
||||
* Converts a string to a slug, that can be used in heading anchors
|
||||
*
|
||||
* @param {string} string
|
||||
* @param {Object} [context={}] - an optional context to track used slugs and
|
||||
* ensure that new slug will be unique
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
module.exports = (string, context = {}) => {
|
||||
// var accents = "àáäâèéëêìíïîòóöôùúüûñç";
|
||||
const accents =
|
||||
'\u00e0\u00e1\u00e4\u00e2\u00e8' +
|
||||
|
@ -50,5 +59,22 @@ module.exports = string => {
|
|||
slug += '-';
|
||||
}
|
||||
|
||||
if (!context.slugStats) {
|
||||
context.slugStats = {};
|
||||
}
|
||||
|
||||
if (typeof context.slugStats[slug] === 'number') {
|
||||
// search for an index, that will not clash with an existing headings
|
||||
while (
|
||||
typeof context.slugStats[slug + '-' + ++context.slugStats[slug]] ===
|
||||
'number'
|
||||
);
|
||||
slug += '-' + context.slugStats[slug];
|
||||
}
|
||||
|
||||
// we are tracking both original anchors and suffixed to avoid future name
|
||||
// clashing with headings with numbers e.g. `#Foo 1` may clash with the second `#Foo`
|
||||
context.slugStats[slug] = 0;
|
||||
|
||||
return slug;
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ async function execute() {
|
|||
const readMetadata = require('./readMetadata.js');
|
||||
const path = require('path');
|
||||
const color = require('color');
|
||||
const toSlug = require('../core/toSlug.js');
|
||||
const getTOC = require('../core/getTOC.js');
|
||||
const React = require('react');
|
||||
const mkdirp = require('mkdirp');
|
||||
const glob = require('glob');
|
||||
|
@ -42,15 +42,12 @@ async function execute() {
|
|||
// takes the content of a doc article and returns the content with a table of
|
||||
// contents inserted
|
||||
const insertTableOfContents = rawContent => {
|
||||
const regexp = /\n###\s+(`.*`.*)\n/g;
|
||||
let match;
|
||||
const headers = [];
|
||||
while ((match = regexp.exec(rawContent))) {
|
||||
headers.push(match[1]);
|
||||
}
|
||||
const filterRe = /^`[^`]*`/;
|
||||
const headers = getTOC(rawContent, 'h3', null);
|
||||
|
||||
const tableOfContents = headers
|
||||
.map(header => ` - [${header}](#${toSlug(header)})`)
|
||||
.filter(header => filterRe.test(header.rawContent))
|
||||
.map(header => ` - [${header.rawContent}](#${header.hashLink})`)
|
||||
.join('\n');
|
||||
|
||||
return rawContent.replace(TABLE_OF_CONTENTS_TOKEN, tableOfContents);
|
||||
|
|
|
@ -17,7 +17,7 @@ function execute(port) {
|
|||
const os = require('os');
|
||||
const path = require('path');
|
||||
const color = require('color');
|
||||
const toSlug = require('../core/toSlug');
|
||||
const getTOC = require('../core/getTOC');
|
||||
const mkdirp = require('mkdirp');
|
||||
const glob = require('glob');
|
||||
const chalk = require('chalk');
|
||||
|
@ -91,15 +91,12 @@ function execute(port) {
|
|||
const TABLE_OF_CONTENTS_TOKEN = '<AUTOGENERATED_TABLE_OF_CONTENTS>';
|
||||
|
||||
const insertTableOfContents = rawContent => {
|
||||
const regexp = /\n###\s+(`.*`.*)\n/g;
|
||||
let match;
|
||||
const headers = [];
|
||||
while ((match = regexp.exec(rawContent))) {
|
||||
headers.push(match[1]);
|
||||
}
|
||||
const filterRe = /^`[^`]*`/;
|
||||
const headers = getTOC(rawContent, 'h3', null);
|
||||
|
||||
const tableOfContents = headers
|
||||
.map(header => ` - [${header}](#${toSlug(header)})`)
|
||||
.filter(header => filterRe.test(header.rawContent))
|
||||
.map(header => ` - [${header.rawContent}](#${header.hashLink})`)
|
||||
.join('\n');
|
||||
|
||||
return rawContent.replace(TABLE_OF_CONTENTS_TOKEN, tableOfContents);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue