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(v1): add 'slugPreprocessor' config option to allow users customize the hash links #3124

Merged
merged 3 commits into from Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/api-site-config.md
Expand Up @@ -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.

#### `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]

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.
Expand Down Expand Up @@ -463,6 +467,9 @@ const siteConfig = {
scrollToTopOptions: {
zIndex: 100,
},
// Remove the HTML tags and HTML tags content before generating the slug
slugPreprocessor: (slugBase) =>
slugBase.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, ''),
};

module.exports = siteConfig;
Expand Down
10 changes: 10 additions & 0 deletions packages/docusaurus-1.x/lib/core/__tests__/toc.test.js
Expand Up @@ -69,6 +69,16 @@ describe('getTOC', () => {
expect(headings[0].rawContent).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', () => {
Expand Down
8 changes: 6 additions & 2 deletions packages/docusaurus-1.x/lib/core/anchors.js
Expand Up @@ -11,7 +11,7 @@ const toSlug = require('./toSlug');
/**
* The anchors plugin adds GFM-style anchors to headings.
*/
function anchors(md) {
function anchors(md, slugPreprocessor) {
const originalRender = md.renderer.rules.heading_open;

md.renderer.rules.heading_open = function (tokens, idx, options, env) {
Expand All @@ -22,7 +22,11 @@ function anchors(md) {
const textToken = tokens[idx + 1];

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>`;
}

Expand Down
14 changes: 12 additions & 2 deletions packages/docusaurus-1.x/lib/core/nav/OnPageNav.js
Expand Up @@ -37,8 +37,18 @@ class OnPageNav extends React.Component {
render() {
const customTags = siteConfig.onPageNavHeadings;
const headings = customTags
? getTOC(this.props.rawContent, customTags.topLevel, customTags.sub)
: getTOC(this.props.rawContent);
? getTOC(
this.props.rawContent,
customTags.topLevel,
customTags.sub,
siteConfig.slugPreprocessor,
)
: getTOC(
this.props.rawContent,
undefined,
undefined,
siteConfig.slugPreprocessor,
);

return <Headings headings={headings} />;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-1.x/lib/core/renderMarkdown.js
Expand Up @@ -102,7 +102,7 @@ class MarkdownRenderer {
const md = new Markdown(markdownOptions);

// Register anchors plugin
md.use(anchors);
md.use(anchors, siteConfig.slugPreprocessor);

// Linkify
md.use(linkify);
Expand Down
18 changes: 13 additions & 5 deletions packages/docusaurus-1.x/lib/core/toc.js
Expand Up @@ -19,7 +19,12 @@ const tocRegex = new RegExp('<AUTOGENERATED_TABLE_OF_CONTENTS>', 'i');
* 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 headingLevels = [].concat(headingTags).map(tagToLevel);
const subHeadingLevels = subHeadingTags
Expand All @@ -38,8 +43,11 @@ function getTOC(content, headingTags = 'h2', subHeadingTags = 'h3') {
headings.forEach((heading) => {
const rawContent = heading.content;
const rendered = md.renderInline(rawContent);

const hashLink = toSlug(rawContent, slugger);
const slugBase =
slugPreprocessor && typeof slugPreprocessor === 'function'
? slugPreprocessor(rawContent)
: rawContent;
const hashLink = toSlug(slugBase, slugger);
if (!allowedHeadingLevels.includes(heading.lvl)) {
return;
}
Expand All @@ -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
// contents inserted
function insertTOC(rawContent) {
function insertTOC(rawContent, slugPreprocessor = undefined) {
if (!rawContent || !tocRegex.test(rawContent)) {
return rawContent;
}
const filterRe = /^`[^`]*`/;
const headers = getTOC(rawContent, 'h3', null);
const headers = getTOC(rawContent, 'h3', null, slugPreprocessor);
const tableOfContents = headers
.filter((header) => filterRe.test(header.rawContent))
.map((header) => ` - [${header.rawContent}](#${header.hashLink})`)
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-1.x/lib/server/docs.js
Expand Up @@ -104,7 +104,7 @@ function mdToHtmlify(oldContent, mdToHtml, metadata, siteConfig) {

function getMarkup(rawContent, mdToHtml, metadata, siteConfig) {
// 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
content = mdToHtmlify(content, mdToHtml, metadata, siteConfig);
Expand Down