mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-20 20:46:58 +02:00
feat(v1): add 'slugPreprocessor' config option to allow users customize the hash links (#3124)
* fix(v1): remove HTML content from hash links * fix one more test * rewrite changes as new feature to prevent breaking changes
This commit is contained in:
parent
d1a27efe8c
commit
aa7430e168
7 changed files with 50 additions and 11 deletions
|
@ -318,6 +318,10 @@ Set this to `true` if you want to enable the scroll to top button at the bottom
|
||||||
|
|
||||||
Optional options configuration for the scroll to top button. You do not need to use this, even if you set `scrollToTop` to `true`; it just provides you more configuration control of the button. You can find more options [here](https://github.com/vfeskov/vanilla-back-to-top/blob/v7.1.14/OPTIONS.md). By default, we set the zIndex option to 100.
|
Optional options configuration for the scroll to top button. You do not need to use this, even if you set `scrollToTop` to `true`; it just provides you more configuration control of the button. You can find more options [here](https://github.com/vfeskov/vanilla-back-to-top/blob/v7.1.14/OPTIONS.md). By default, we set the zIndex option to 100.
|
||||||
|
|
||||||
|
#### `slugPreprocessor` [function]
|
||||||
|
|
||||||
|
Define the slug preprocessor function if you want to customize the text used for generating the hash links. Function provides the base string as the first argument and must always return a string.
|
||||||
|
|
||||||
#### `stylesheets` [array]
|
#### `stylesheets` [array]
|
||||||
|
|
||||||
An array of CSS sources to load. The values can be either strings or plain objects of attribute-value maps. The link tag will be inserted in the HTML head.
|
An array of CSS sources to load. The values can be either strings or plain objects of attribute-value maps. The link tag will be inserted in the HTML head.
|
||||||
|
@ -463,6 +467,9 @@ const siteConfig = {
|
||||||
scrollToTopOptions: {
|
scrollToTopOptions: {
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
},
|
},
|
||||||
|
// Remove the HTML tags and HTML tags content before generating the slug
|
||||||
|
slugPreprocessor: (slugBase) =>
|
||||||
|
slugBase.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, ''),
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = siteConfig;
|
module.exports = siteConfig;
|
||||||
|
|
|
@ -69,6 +69,16 @@ describe('getTOC', () => {
|
||||||
expect(headings[0].rawContent).toEqual(`function1 [array<string>]`);
|
expect(headings[0].rawContent).toEqual(`function1 [array<string>]`);
|
||||||
expect(headings[0].content).toEqual(`function1 [array<string>]`);
|
expect(headings[0].content).toEqual(`function1 [array<string>]`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('test slugPreprocessor', () => {
|
||||||
|
const headings = getTOC(`## <a name="foo"></a> Foo`, 'h2', [], (s) =>
|
||||||
|
s.replace(/foo/gi, 'bar'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(headings[0].hashLink).toEqual('a-namebara-bar');
|
||||||
|
expect(headings[0].rawContent).toEqual(`<a name="foo"></a> Foo`);
|
||||||
|
expect(headings[0].content).toEqual(`<a name="foo"></a> Foo`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('insertTOC', () => {
|
describe('insertTOC', () => {
|
||||||
|
|
|
@ -11,7 +11,7 @@ const toSlug = require('./toSlug');
|
||||||
/**
|
/**
|
||||||
* The anchors plugin adds GFM-style anchors to headings.
|
* The anchors plugin adds GFM-style anchors to headings.
|
||||||
*/
|
*/
|
||||||
function anchors(md) {
|
function anchors(md, slugPreprocessor) {
|
||||||
const originalRender = md.renderer.rules.heading_open;
|
const originalRender = md.renderer.rules.heading_open;
|
||||||
|
|
||||||
md.renderer.rules.heading_open = function (tokens, idx, options, env) {
|
md.renderer.rules.heading_open = function (tokens, idx, options, env) {
|
||||||
|
@ -22,7 +22,11 @@ function anchors(md) {
|
||||||
const textToken = tokens[idx + 1];
|
const textToken = tokens[idx + 1];
|
||||||
|
|
||||||
if (textToken.content) {
|
if (textToken.content) {
|
||||||
const anchor = toSlug(textToken.content, slugger);
|
const slugBase =
|
||||||
|
slugPreprocessor && typeof slugPreprocessor === 'function'
|
||||||
|
? slugPreprocessor(textToken.content)
|
||||||
|
: textToken.content;
|
||||||
|
const anchor = toSlug(slugBase, slugger);
|
||||||
return `<h${tokens[idx].hLevel}><a class="anchor" aria-hidden="true" id="${anchor}"></a><a href="#${anchor}" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" 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>`;
|
return `<h${tokens[idx].hLevel}><a class="anchor" aria-hidden="true" id="${anchor}"></a><a href="#${anchor}" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" 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>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,18 @@ class OnPageNav extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const customTags = siteConfig.onPageNavHeadings;
|
const customTags = siteConfig.onPageNavHeadings;
|
||||||
const headings = customTags
|
const headings = customTags
|
||||||
? getTOC(this.props.rawContent, customTags.topLevel, customTags.sub)
|
? getTOC(
|
||||||
: getTOC(this.props.rawContent);
|
this.props.rawContent,
|
||||||
|
customTags.topLevel,
|
||||||
|
customTags.sub,
|
||||||
|
siteConfig.slugPreprocessor,
|
||||||
|
)
|
||||||
|
: getTOC(
|
||||||
|
this.props.rawContent,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
siteConfig.slugPreprocessor,
|
||||||
|
);
|
||||||
|
|
||||||
return <Headings headings={headings} />;
|
return <Headings headings={headings} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ class MarkdownRenderer {
|
||||||
const md = new Markdown(markdownOptions);
|
const md = new Markdown(markdownOptions);
|
||||||
|
|
||||||
// Register anchors plugin
|
// Register anchors plugin
|
||||||
md.use(anchors);
|
md.use(anchors, siteConfig.slugPreprocessor);
|
||||||
|
|
||||||
// Linkify
|
// Linkify
|
||||||
md.use(linkify);
|
md.use(linkify);
|
||||||
|
|
|
@ -19,7 +19,12 @@ const tocRegex = new RegExp('<AUTOGENERATED_TABLE_OF_CONTENTS>', 'i');
|
||||||
* Array of heading objects with `hashLink`, `content` and `children` fields
|
* Array of heading objects with `hashLink`, `content` and `children` fields
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function getTOC(content, headingTags = 'h2', subHeadingTags = 'h3') {
|
function getTOC(
|
||||||
|
content,
|
||||||
|
headingTags = 'h2',
|
||||||
|
subHeadingTags = 'h3',
|
||||||
|
slugPreprocessor = undefined,
|
||||||
|
) {
|
||||||
const tagToLevel = (tag) => Number(tag.slice(1));
|
const tagToLevel = (tag) => Number(tag.slice(1));
|
||||||
const headingLevels = [].concat(headingTags).map(tagToLevel);
|
const headingLevels = [].concat(headingTags).map(tagToLevel);
|
||||||
const subHeadingLevels = subHeadingTags
|
const subHeadingLevels = subHeadingTags
|
||||||
|
@ -38,8 +43,11 @@ function getTOC(content, headingTags = 'h2', subHeadingTags = 'h3') {
|
||||||
headings.forEach((heading) => {
|
headings.forEach((heading) => {
|
||||||
const rawContent = heading.content;
|
const rawContent = heading.content;
|
||||||
const rendered = md.renderInline(rawContent);
|
const rendered = md.renderInline(rawContent);
|
||||||
|
const slugBase =
|
||||||
const hashLink = toSlug(rawContent, slugger);
|
slugPreprocessor && typeof slugPreprocessor === 'function'
|
||||||
|
? slugPreprocessor(rawContent)
|
||||||
|
: rawContent;
|
||||||
|
const hashLink = toSlug(slugBase, slugger);
|
||||||
if (!allowedHeadingLevels.includes(heading.lvl)) {
|
if (!allowedHeadingLevels.includes(heading.lvl)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -61,12 +69,12 @@ function getTOC(content, headingTags = 'h2', subHeadingTags = 'h3') {
|
||||||
|
|
||||||
// takes the content of a doc article and returns the content with a table of
|
// takes the content of a doc article and returns the content with a table of
|
||||||
// contents inserted
|
// contents inserted
|
||||||
function insertTOC(rawContent) {
|
function insertTOC(rawContent, slugPreprocessor = undefined) {
|
||||||
if (!rawContent || !tocRegex.test(rawContent)) {
|
if (!rawContent || !tocRegex.test(rawContent)) {
|
||||||
return rawContent;
|
return rawContent;
|
||||||
}
|
}
|
||||||
const filterRe = /^`[^`]*`/;
|
const filterRe = /^`[^`]*`/;
|
||||||
const headers = getTOC(rawContent, 'h3', null);
|
const headers = getTOC(rawContent, 'h3', null, slugPreprocessor);
|
||||||
const tableOfContents = headers
|
const tableOfContents = headers
|
||||||
.filter((header) => filterRe.test(header.rawContent))
|
.filter((header) => filterRe.test(header.rawContent))
|
||||||
.map((header) => ` - [${header.rawContent}](#${header.hashLink})`)
|
.map((header) => ` - [${header.rawContent}](#${header.hashLink})`)
|
||||||
|
|
|
@ -104,7 +104,7 @@ function mdToHtmlify(oldContent, mdToHtml, metadata, siteConfig) {
|
||||||
|
|
||||||
function getMarkup(rawContent, mdToHtml, metadata, siteConfig) {
|
function getMarkup(rawContent, mdToHtml, metadata, siteConfig) {
|
||||||
// generate table of contents
|
// generate table of contents
|
||||||
let content = insertTOC(rawContent);
|
let content = insertTOC(rawContent, siteConfig.slugPreprocessor);
|
||||||
|
|
||||||
// replace any links to markdown files to their website html links
|
// replace any links to markdown files to their website html links
|
||||||
content = mdToHtmlify(content, mdToHtml, metadata, siteConfig);
|
content = mdToHtmlify(content, mdToHtml, metadata, siteConfig);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue