diff --git a/docs/helpers.md b/docs/helpers.md index 6bd8a069d..910846345 100644 --- a/docs/helpers.md +++ b/docs/helpers.md @@ -127,6 +127,12 @@ Only when you set both the `routerMode: 'history'` and `externalLinkTarget: '_se ### Hello, world! :id=hello-world ``` +## Customise the title for the sidebar items :sidebar="Customise title for sidebar" + +```md +### How would I write a "hello, world" example? :sidebar="Hello, world?" +``` + ## Markdown in html tag You need to insert a space between the html and markdown content. diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 255c49642..c59f5a1c9 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -207,34 +207,32 @@ export class Compiler { */ origin.heading = renderer.heading = function (text, level) { let { str, config } = getAndRemoveConfig(text); - const nextToc = { level, title: removeAtag(str) }; + const nextToc = { level }; if (//g.test(str)) { str = str.replace('', ''); - nextToc.title = removeAtag(str); nextToc.ignoreSubHeading = true; } if (/{docsify-ignore}/g.test(str)) { str = str.replace('{docsify-ignore}', ''); - nextToc.title = removeAtag(str); nextToc.ignoreSubHeading = true; } if (//g.test(str)) { str = str.replace('', ''); - nextToc.title = removeAtag(str); nextToc.ignoreAllSubs = true; } if (/{docsify-ignore-all}/g.test(str)) { str = str.replace('{docsify-ignore-all}', ''); - nextToc.title = removeAtag(str); nextToc.ignoreAllSubs = true; } const slug = slugify(config.id || str); const url = router.toURL(router.getCurrentPath(), { id: slug }); + nextToc.title = removeAtag(str); + nextToc.text = config.sidebar || nextToc.title; nextToc.slug = url; _self.toc.push(nextToc); diff --git a/src/core/render/compiler/headline.js b/src/core/render/compiler/headline.js index 61e4b3fb9..bbe9be19a 100644 --- a/src/core/render/compiler/headline.js +++ b/src/core/render/compiler/headline.js @@ -4,34 +4,32 @@ import { slugify } from './slugify'; export const headingCompiler = ({ renderer, router, _self }) => (renderer.code = (text, level) => { let { str, config } = getAndRemoveConfig(text); - const nextToc = { level, title: removeAtag(str) }; + const nextToc = { level }; if (//g.test(str)) { str = str.replace('', ''); - nextToc.title = removeAtag(str); nextToc.ignoreSubHeading = true; } if (/{docsify-ignore}/g.test(str)) { str = str.replace('{docsify-ignore}', ''); - nextToc.title = removeAtag(str); nextToc.ignoreSubHeading = true; } if (//g.test(str)) { str = str.replace('', ''); - nextToc.title = removeAtag(str); nextToc.ignoreAllSubs = true; } if (/{docsify-ignore-all}/g.test(str)) { str = str.replace('{docsify-ignore-all}', ''); - nextToc.title = removeAtag(str); nextToc.ignoreAllSubs = true; } const slug = slugify(config.id || str); const url = router.toURL(router.getCurrentPath(), { id: slug }); + nextToc.title = removeAtag(str); + nextToc.text = config.sidebar || nextToc.title; nextToc.slug = url; _self.toc.push(nextToc); diff --git a/src/core/render/tpl.js b/src/core/render/tpl.js index 47ceeab74..ff5668179 100644 --- a/src/core/render/tpl.js +++ b/src/core/render/tpl.js @@ -92,7 +92,7 @@ export function tree(toc, tpl = '') { let innerHTML = ''; toc.forEach(node => { const title = node.title.replace(/(<([^>]+)>)/g, ''); - innerHTML += `
  • ${node.title}
  • `; + innerHTML += `
  • ${node.text}
  • `; if (node.children) { innerHTML += tree(node.children, tpl); } diff --git a/src/core/render/utils.js b/src/core/render/utils.js index 42fbfa078..beedeea54 100644 --- a/src/core/render/utils.js +++ b/src/core/render/utils.js @@ -23,16 +23,18 @@ export function getAndRemoveConfig(str = '') { if (str) { str = str - .replace(/^('|")/, '') - .replace(/('|")$/, '') - .replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g, (m, key, value) => { - if (key.indexOf(':') === -1) { - config[key] = (value && value.replace(/"/g, '')) || true; - return ''; - } + .replace( + /(?:^|\s):([\w-]+:?)=?([\w-%]+|"((?!").)*"|[“”][^“”]*[“”])?/g, // Note: because the provided `str` argument has been html-escaped, with backslashes stripped, we cannot support escaped characters in quoted strings :-( + function (m, key, value) { + if (key.indexOf(':') === -1) { + config[key] = (value && value.replace(/"|[“”]/g, '')) || true; + return ''; + } - return m; - }) + return m; + } + ) + .replace(/^('|")|('|")$/g, '') .trim(); } diff --git a/test/unit/render-util.test.js b/test/unit/render-util.test.js index 574e0a8ef..f4f1fc405 100644 --- a/test/unit/render-util.test.js +++ b/test/unit/render-util.test.js @@ -58,6 +58,19 @@ describe('core/render/utils', () => { str: `[filename](_media/example.md ":include")`, }); }); + + test('parse config with quoted string arguments', () => { + const result = getAndRemoveConfig( + `[filename](_media/example.md ':include :foo="bar :baz test"')` + ); + + expect(result).toMatchObject({ + config: { + foo: 'bar :baz test', + }, + str: `[filename](_media/example.md ':include')`, + }); + }); }); }); @@ -68,22 +81,31 @@ describe('core/render/tpl', () => { level: 2, slug: '#/cover?id=basic-usage', title: 'Basic usage', + text: 'Basic usage', }, { level: 2, slug: '#/cover?id=custom-background', title: 'Custom background', + text: 'Custom background', }, { level: 2, slug: '#/cover?id=test', title: 'icoTest', + text: 'icoTest', + }, + { + level: 2, + slug: '#/cover?id=different-title-and-label', + title: 'Long title string', + text: 'Short string', }, ]); expect(result).toBe( - `` + `` ); }); });