From f4185c38eff5a6186653a43da27d973236b4ce01 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 28 Jan 2019 14:21:49 +0800 Subject: [PATCH 1/7] remove colon as separator --- packages/@vuepress/markdown/lib/snippet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@vuepress/markdown/lib/snippet.js b/packages/@vuepress/markdown/lib/snippet.js index 834ff496a1..895e7b2d5f 100644 --- a/packages/@vuepress/markdown/lib/snippet.js +++ b/packages/@vuepress/markdown/lib/snippet.js @@ -24,7 +24,7 @@ module.exports = function snippet (md, options = {}) { const start = pos + 3 const end = state.skipSpacesBack(max, pos) const rawPath = state.src.slice(start, end).trim().replace(/^@/, root) - const filename = rawPath.split(/[{:\s]/).shift() + const filename = rawPath.split(/[{\s]/).shift() const content = fs.existsSync(filename) ? fs.readFileSync(filename).toString() : 'Not found: ' + filename const meta = rawPath.replace(filename, '') From ce5257db227981aa526a42faf0adeba733d23388 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sat, 16 Feb 2019 01:25:47 +0800 Subject: [PATCH 2/7] fix #1227: relative path in pages that use permalink --- packages/@vuepress/core/lib/prepare/Page.js | 11 +++++++++-- packages/@vuepress/markdown-loader/index.js | 9 ++++++++- packages/@vuepress/markdown/lib/link.js | 19 ++++++++++++------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/@vuepress/core/lib/prepare/Page.js b/packages/@vuepress/core/lib/prepare/Page.js index 296f3d24e1..de83e00523 100644 --- a/packages/@vuepress/core/lib/prepare/Page.js +++ b/packages/@vuepress/core/lib/prepare/Page.js @@ -85,7 +85,11 @@ module.exports = class Page { enhancers = [], preRender = {} }) { + // relative path + let relPath + if (this._filePath) { + relPath = path.relative(this._context.sourceDir, this._filePath) logger.developer(`static_route`, chalk.cyan(this.path)) this._content = await fs.readFile(this._filePath, 'utf-8') } else if (this._content) { @@ -118,7 +122,10 @@ module.exports = class Page { } if (excerpt) { - const { html } = markdown.render(excerpt) + const { html } = markdown.render(excerpt, { + frontmatter: this.frontmatter, + relPath + }) this.excerpt = html } } else if (this._filePath.endsWith('.vue')) { @@ -231,7 +238,7 @@ module.exports = class Page { /** * Execute the page enhancers. A enhancer could do following things: * - * 1. Modify page's frontmetter. + * 1. Modify page's frontmatter. * 2. Add extra field to the page. * * @api private diff --git a/packages/@vuepress/markdown-loader/index.js b/packages/@vuepress/markdown-loader/index.js index 8e53a7e283..1598b4caa1 100644 --- a/packages/@vuepress/markdown-loader/index.js +++ b/packages/@vuepress/markdown-loader/index.js @@ -66,7 +66,14 @@ module.exports = function (src) { // the render method has been augmented to allow plugins to // register data during render - const { html, data: { hoistedTags, links }, dataBlockString } = markdown.render(content) + const { + html, + data: { hoistedTags, links }, + dataBlockString + } = markdown.render(content, { + frontmatter: frontmatter.data, + relPath: path.relative(sourceDir, file) + }) // check if relative links are valid links && links.forEach(link => { diff --git a/packages/@vuepress/markdown/lib/link.js b/packages/@vuepress/markdown/lib/link.js index 4769fa80c6..900ca3603e 100644 --- a/packages/@vuepress/markdown/lib/link.js +++ b/packages/@vuepress/markdown/lib/link.js @@ -2,6 +2,8 @@ // 1. adding target="_blank" to external links // 2. converting internal links to +const url = require('url') + const indexRE = /(^|.*\/)(index|readme).md(#?.*)$/i module.exports = (md, externalAttrs) => { @@ -9,6 +11,7 @@ module.exports = (md, externalAttrs) => { let hasOpenExternalLink = false md.renderer.rules.link_open = (tokens, idx, options, env, self) => { + const { relPath } = env const token = tokens[idx] const hrefIndex = token.attrIndex('href') if (hrefIndex >= 0) { @@ -25,13 +28,13 @@ module.exports = (md, externalAttrs) => { } } else if (isSourceLink) { hasOpenRouterLink = true - tokens[idx] = toRouterLink(token, link) + tokens[idx] = toRouterLink(token, link, relPath) } } return self.renderToken(tokens, idx, options) } - function toRouterLink (token, link) { + function toRouterLink (token, link, relPath) { link[0] = 'to' let to = link[1] @@ -39,6 +42,13 @@ module.exports = (md, externalAttrs) => { const links = md.$data.links || (md.$data.links = []) links.push(to) + // relative path usage. + if (!to.startsWith('/')) { + to = relPath + ? url.resolve('/' + relPath, to) + : ensureBeginningDotSlash(to) + } + const indexMatch = to.match(indexRE) if (indexMatch) { const [, path, , hash] = indexMatch @@ -49,11 +59,6 @@ module.exports = (md, externalAttrs) => { .replace(/\.md(#.*)$/, '.html$1') } - // relative path usage. - if (!to.startsWith('/')) { - to = ensureBeginningDotSlash(to) - } - // markdown-it encodes the uri link[1] = decodeURI(to) From 91a0b7590b7d985aab50779fc7231069e34f4055 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 18 Feb 2019 02:39:07 +0800 Subject: [PATCH 3/7] toc component --- packages/@vuepress/core/lib/app/app.js | 4 ++ .../markdown/components/HeaderList.vue | 17 ++++++ .../@vuepress/markdown/components/TOC.vue | 45 ++++++++++++++ packages/@vuepress/markdown/index.js | 2 +- .../@vuepress/markdown/lib/tableOfContents.js | 61 +++++++++++++++++++ packages/@vuepress/markdown/package.json | 1 - 6 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 packages/@vuepress/markdown/components/HeaderList.vue create mode 100644 packages/@vuepress/markdown/components/TOC.vue create mode 100644 packages/@vuepress/markdown/lib/tableOfContents.js diff --git a/packages/@vuepress/core/lib/app/app.js b/packages/@vuepress/core/lib/app/app.js index ea650dbce4..ec69ffba33 100644 --- a/packages/@vuepress/core/lib/app/app.js +++ b/packages/@vuepress/core/lib/app/app.js @@ -15,6 +15,7 @@ import Content from './components/Content.js' import ContentSlotsDistributor from './components/ContentSlotsDistributor' import OutboundLink from './components/OutboundLink.vue' import ClientOnly from './components/ClientOnly' +import TOC from '@vuepress/markdown/components/TOC.vue' // suggest dev server restart on base change if (module.hot) { @@ -43,6 +44,9 @@ Vue.component('OutboundLink', OutboundLink) // component for client-only content Vue.component('ClientOnly', ClientOnly) +// table of contents +Vue.component('TOC', TOC) + // global helper for adding base path to absolute urls Vue.prototype.$withBase = function (path) { const base = this.$site.base diff --git a/packages/@vuepress/markdown/components/HeaderList.vue b/packages/@vuepress/markdown/components/HeaderList.vue new file mode 100644 index 0000000000..93a312c0e9 --- /dev/null +++ b/packages/@vuepress/markdown/components/HeaderList.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/@vuepress/markdown/components/TOC.vue b/packages/@vuepress/markdown/components/TOC.vue new file mode 100644 index 0000000000..6b703b4c48 --- /dev/null +++ b/packages/@vuepress/markdown/components/TOC.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/packages/@vuepress/markdown/index.js b/packages/@vuepress/markdown/index.js index 896f22d79c..f64bd4011d 100644 --- a/packages/@vuepress/markdown/index.js +++ b/packages/@vuepress/markdown/index.js @@ -18,7 +18,7 @@ const markdownSlotsContainersPlugin = require('./lib/markdownSlotsContainers') const snippetPlugin = require('./lib/snippet') const emojiPlugin = require('markdown-it-emoji') const anchorPlugin = require('markdown-it-anchor') -const tocPlugin = require('markdown-it-table-of-contents') +const tocPlugin = require('./lib/tableOfContents') const { parseHeaders, slugify: _slugify, logger, chalk } = require('@vuepress/shared-utils') /** diff --git a/packages/@vuepress/markdown/lib/tableOfContents.js b/packages/@vuepress/markdown/lib/tableOfContents.js new file mode 100644 index 0000000000..a7a5290027 --- /dev/null +++ b/packages/@vuepress/markdown/lib/tableOfContents.js @@ -0,0 +1,61 @@ +// reference: https://github.com/Oktavilla/markdown-it-table-of-contents + +const slugify = (s) => encodeURIComponent(String(s).trim().toLowerCase().replace(/\s+/g, '-')) +const defaults = { + includeLevel: [1, 2], + containerClass: 'table-of-contents', + slugify, + markerPattern: /^\[\[toc\]\]/im, + listType: 'ul', + format: undefined, + forceFullToc: false +} + +module.exports = (md, options) => { + options = Object.assign({}, defaults, options) + const tocRegexp = options.markerPattern + + function toc (state, silent) { + var token + var match + + // Reject if the token does not start with [ + if (state.src.charCodeAt(state.pos) !== 0x5B /* [ */) { + return false + } + // Don't run any pairs in validation mode + if (silent) { + return false + } + + // Detect TOC markdown + match = tocRegexp.exec(state.src) + match = !match ? [] : match.filter(function (m) { return m }) + if (match.length < 1) { + return false + } + + // Build content + token = state.push('toc_open', 'toc', 1) + token.markup = '[[toc]]' + token = state.push('toc_body', '', 0) + token = state.push('toc_close', 'toc', -1) + + // Update pos so the parser can continue + var newline = state.src.indexOf('\n') + if (newline !== -1) { + state.pos = state.pos + newline + } else { + state.pos = state.pos + state.posMax + 1 + } + + return true + } + + md.renderer.rules.toc_open = function (tokens, index) { + return `` + } + + // Insert TOC + md.inline.ruler.after('emphasis', 'toc', toc) +} diff --git a/packages/@vuepress/markdown/package.json b/packages/@vuepress/markdown/package.json index 1a2abf0895..5fd7bf5ccd 100644 --- a/packages/@vuepress/markdown/package.json +++ b/packages/@vuepress/markdown/package.json @@ -25,7 +25,6 @@ "markdown-it-chain": "^1.3.0", "markdown-it-container": "^2.0.0", "markdown-it-emoji": "^1.4.0", - "markdown-it-table-of-contents": "^0.4.0", "prismjs": "^1.13.0" }, "author": "Evan You", From 73f2ff818b7a75d59ceb6cb3934c7109dd5d2548 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Mon, 18 Feb 2019 11:31:28 +0800 Subject: [PATCH 4/7] support listType, includeLevel, containerHeaderHtml and containerFooterHtml --- .../markdown/components/HeaderList.vue | 14 +++- .../@vuepress/markdown/components/TOC.vue | 76 ++++++++++++------- packages/@vuepress/markdown/index.js | 8 +- .../@vuepress/markdown/lib/tableOfContents.js | 33 ++++++-- packages/docs/docs/config/README.md | 7 +- packages/docs/docs/guide/markdown.md | 10 ++- packages/docs/docs/zh/config/README.md | 7 +- packages/docs/docs/zh/guide/markdown.md | 10 ++- 8 files changed, 114 insertions(+), 51 deletions(-) diff --git a/packages/@vuepress/markdown/components/HeaderList.vue b/packages/@vuepress/markdown/components/HeaderList.vue index 93a312c0e9..ad466f4859 100644 --- a/packages/@vuepress/markdown/components/HeaderList.vue +++ b/packages/@vuepress/markdown/components/HeaderList.vue @@ -1,17 +1,23 @@ diff --git a/packages/@vuepress/markdown/components/TOC.vue b/packages/@vuepress/markdown/components/TOC.vue index 6b703b4c48..98984a6f72 100644 --- a/packages/@vuepress/markdown/components/TOC.vue +++ b/packages/@vuepress/markdown/components/TOC.vue @@ -1,45 +1,65 @@ - - diff --git a/packages/@vuepress/markdown/index.js b/packages/@vuepress/markdown/index.js index f64bd4011d..cd8b1d0740 100644 --- a/packages/@vuepress/markdown/index.js +++ b/packages/@vuepress/markdown/index.js @@ -19,7 +19,7 @@ const snippetPlugin = require('./lib/snippet') const emojiPlugin = require('markdown-it-emoji') const anchorPlugin = require('markdown-it-anchor') const tocPlugin = require('./lib/tableOfContents') -const { parseHeaders, slugify: _slugify, logger, chalk } = require('@vuepress/shared-utils') +const { slugify: _slugify, logger, chalk } = require('@vuepress/shared-utils') /** * Create markdown by config. @@ -96,11 +96,7 @@ module.exports = (markdown = {}) => { .end() .plugin(PLUGINS.TOC) - .use(tocPlugin, [Object.assign({ - slugify, - includeLevel: [2, 3], - format: parseHeaders - }, toc)]) + .use(tocPlugin, [toc]) .end() if (lineNumbers) { diff --git a/packages/@vuepress/markdown/lib/tableOfContents.js b/packages/@vuepress/markdown/lib/tableOfContents.js index a7a5290027..b400a9b7b8 100644 --- a/packages/@vuepress/markdown/lib/tableOfContents.js +++ b/packages/@vuepress/markdown/lib/tableOfContents.js @@ -1,14 +1,12 @@ // reference: https://github.com/Oktavilla/markdown-it-table-of-contents -const slugify = (s) => encodeURIComponent(String(s).trim().toLowerCase().replace(/\s+/g, '-')) const defaults = { - includeLevel: [1, 2], + includeLevel: [2, 3], containerClass: 'table-of-contents', - slugify, markerPattern: /^\[\[toc\]\]/im, listType: 'ul', - format: undefined, - forceFullToc: false + containerHeaderHtml: '', + containerFooterHtml: '' } module.exports = (md, options) => { @@ -52,10 +50,31 @@ module.exports = (md, options) => { return true } - md.renderer.rules.toc_open = function (tokens, index) { - return `` + md.renderer.rules.toc_open = function () { + return vBindEscape`` + } + + md.renderer.rules.toc_close = function () { + return `` } // Insert TOC md.inline.ruler.after('emphasis', 'toc', toc) } + +/** escape double quotes in v-bind derivatives */ +function vBindEscape (strs, ...args) { + return strs.reduce((prev, curr, index) => { + return prev + curr + (index >= args.length + ? '' + : `"${JSON.stringify(args[index]) + .replace(/"/g, "'") + .replace(/([^\\])(\\\\)*\\'/g, (_, char) => char + '\\u0022')}"`) + }, '') +} diff --git a/packages/docs/docs/config/README.md b/packages/docs/docs/config/README.md index 41b131b1ce..d06a04bfa7 100644 --- a/packages/docs/docs/config/README.md +++ b/packages/docs/docs/config/README.md @@ -222,7 +222,12 @@ The key and value pair will be added to `` tags that points to an external li - Type: `Object` - Default: `{ includeLevel: [2, 3] }` -Options for [markdown-it-table-of-contents](https://github.com/Oktavilla/markdown-it-table-of-contents). (Note: prefer `markdown.slugify` if you want to customize header ids.) +This attribute will control the behaviour of `[[TOC]]`. We design this interface as close as possible to the options of [markdown-it-table-of-contents](https://github.com/Oktavilla/markdown-it-table-of-contents). However, there are some little differences: + +1. `slugify`, `format` and `forceFullToc` are not supported at present. +2. In addition to supporting `String` type, `listType` also supports passing in an array representing the list type for each level. For example, when `listType` is set to `['ol', 'ul']`, a three-level list will show the structure of `