diff --git a/docs/zh/README.md b/docs/zh/README.md index cbd4d37..928f1b7 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -22,6 +22,8 @@ features: footer: true --- +<<< config/svg-icon.md + ::: warning 请确保你的 Node.js 版本 >= 8.6。 ::: diff --git a/package.json b/package.json index 9617df3..3700df7 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "generate-robotstxt": "^8.0.3", "markdown-it-footnote": "^3.0.3", "markdown-it-imsize": "^2.0.1", + "markdown-it-include": "^2.0.0", "markdown-it-mark": "^3.0.1", "markdown-it-sub": "^1.0.0", "markdown-it-sup": "^1.0.0", diff --git a/src/commands/createConfig.js b/src/commands/createConfig.js index bc44186..0369359 100644 --- a/src/commands/createConfig.js +++ b/src/commands/createConfig.js @@ -5,13 +5,14 @@ module.exports = function createConfig(api, args, opts) { const path = require('path'); const vuepressConfig = api.vuepressConfig; - vuepressConfig.sourceDir = args._ && args._[1] || vuepressConfig.sourceDir || '.'; + const sourceDir = args._ && args._[1] || vuepressConfig.sourceDir || '.'; const root = vuepressConfig.root; + vuepressConfig.sourceDir = path.resolve(root, sourceDir); const loadConfig = require('@vuepress/core/lib/node/loadConfig'); // const vuepressDir = api.resolveWorkspace('.vuepress'); // 不能使用,vuepress内部代码太乱 - const vuepressDir = path.resolve(root, vuepressConfig.sourceDir, '.vuepress'); + const vuepressDir = path.resolve(vuepressConfig.sourceDir, '.vuepress'); const siteConfig = loadConfig(vuepressDir) || {}; const customConfig = require('../config'); diff --git a/theme/components/SidebarGroup.vue b/theme/components/SidebarGroup.vue index d1468fb..39d4c4a 100644 --- a/theme/components/SidebarGroup.vue +++ b/theme/components/SidebarGroup.vue @@ -5,3 +5,9 @@ export default { extends: ParentLayout, }; + + diff --git a/theme/index.js b/theme/index.js index 15808c9..57affb8 100644 --- a/theme/index.js +++ b/theme/index.js @@ -8,6 +8,7 @@ module.exports = (options, ctx) => { const themeConfig = ctx.themeConfig = ctx.themeConfig || {}; + const sourceDir = ctx.sourceDir; const vuepressDir = ctx.vuepressDir; const iconsDir = path.resolve(vuepressDir, 'public', 'icons'); const iconsLibDir = path.resolve(__dirname, 'icons'); @@ -115,7 +116,22 @@ module.exports = (options, ctx) => { config.plugin('imsize').use(require('markdown-it-imsize')); config.plugin('task-lists').use(require('markdown-it-task-lists'), [{ label: true, labelAfter: true }]); - // const { PLUGINS } = require('@vuepress/markdown/lib/constant.js'); + // include + config.plugin('include').use(require('markdown-it-include'), [ { + root: sourceDir, // root path + includeRe: /<< { // return options.map(opt => { // // opt.permalinkSymbol = '#$'; diff --git a/theme/plugins/markdown/snippet.js b/theme/plugins/markdown/snippet.js new file mode 100644 index 0000000..e191d57 --- /dev/null +++ b/theme/plugins/markdown/snippet.js @@ -0,0 +1,162 @@ +const { fs, logger, path } = require('@vuepress/shared-utils') + +function dedent (text) { + const wRegexp = /^([ \t]*)(.*)\n/gm + let match; let minIndentLength = null + + while ((match = wRegexp.exec(text)) !== null) { + const [indentation, content] = match.slice(1) + if (!content) continue + + const indentLength = indentation.length + if (indentLength > 0) { + minIndentLength + = minIndentLength !== null + ? Math.min(minIndentLength, indentLength) + : indentLength + } else break + } + + if (minIndentLength) { + text = text.replace( + new RegExp(`^[ \t]{${minIndentLength}}(.*)`, 'gm'), + '$1' + ) + } + + return text +} + +function testLine (line, regexp, regionName, end = false) { + const [full, tag, name] = regexp.exec(line.trim()) || [] + + return ( + full + && tag + && name === regionName + && tag.match(end ? /^[Ee]nd ?[rR]egion$/ : /^[rR]egion$/) + ) +} + +function findRegion (lines, regionName) { + const regionRegexps = [ + /^\/\/ ?#?((?:end)?region) ([\w*-]+)$/, // javascript, typescript, java + /^\/\* ?#((?:end)?region) ([\w*-]+) ?\*\/$/, // css, less, scss + /^#pragma ((?:end)?region) ([\w*-]+)$/, // C, C++ + /^$/, // HTML, markdown + /^#((?:End )Region) ([\w*-]+)$/, // Visual Basic + /^::#((?:end)region) ([\w*-]+)$/, // Bat + /^# ?((?:end)?region) ([\w*-]+)$/ // C#, PHP, Powershell, Python, perl & misc + ] + + let regexp = null + let start = -1 + + for (const [lineId, line] of lines.entries()) { + if (regexp === null) { + for (const reg of regionRegexps) { + if (testLine(line, reg, regionName)) { + start = lineId + 1 + regexp = reg + break + } + } + } else if (testLine(line, regexp, regionName, true)) { + return { start, end: lineId, regexp } + } + } + + return null +} + +module.exports = function snippet (md, options = {}) { + const fence = md.renderer.rules.fence + const root = options.root || process.cwd() + + md.renderer.rules.fence = (...args) => { + const [tokens, idx, , { loader }] = args + const token = tokens[idx] + const [src, regionName] = token.src ? token.src.split('#') : [''] + if (src) { + if (loader) { + loader.addDependency(src) + } + const isAFile = fs.lstatSync(src).isFile() + if (fs.existsSync(src) && isAFile) { + let content = fs.readFileSync(src, 'utf8') + + if (regionName) { + const lines = content.split(/\r?\n/) + const region = findRegion(lines, regionName) + + if (region) { + content = dedent( + lines + .slice(region.start, region.end) + .filter(line => !region.regexp.test(line.trim())) + .join('\n') + ) + } + } + + token.content = content + } else { + token.content = isAFile ? `Code snippet path not found: ${src}` : `Invalid code snippet option` + token.info = '' + logger.error(token.content) + } + } + return fence(...args) + } + + function parser (state, startLine, endLine, silent) { + const CH = '<'.charCodeAt(0) + const pos = state.bMarks[startLine] + state.tShift[startLine] + const max = state.eMarks[startLine] + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } + + for (let i = 0; i < 3; ++i) { + const ch = state.src.charCodeAt(pos + i) + if (ch !== CH || pos + i >= max) return false + } + + // must be `<<< /ab/b` + if (state.src.charCodeAt(pos + 3) !== ' '.charCodeAt(0)) { + return false; + } + + if (silent) { + return true + } + + const start = pos + 3 + const end = state.skipSpacesBack(max, pos) + + /** + * raw path format: "/path/to/file.extension#region {meta}" + * where #region and {meta} are optionnal + * + * captures: ['/path/to/file.extension', 'extension', '#region', '{meta}'] + */ + const rawPathRegexp = /^(.+?(?:\.([a-z]+))?)(?:(#[\w-]+))?(?: ?({\d+(?:[,-]\d+)*}))?$/ + + const rawPath = state.src.slice(start, end).trim().replace(/^@/, root).trim() + const [filename = '', extension = '', region = '', meta = ''] = (rawPathRegexp.exec(rawPath) || []).slice(1) + + state.line = startLine + 1 + + const token = state.push('fence', 'code', 0) + token.info = extension + meta + token.src = path.resolve(root, filename) + region + token.markup = '```' + token.map = [startLine, startLine + 1] + + return true + } + + md.block.ruler.before('fence', 'snippet', parser) +} diff --git a/theme/styles/code.styl b/theme/styles/code.styl index cedf059..e4e6320 100644 --- a/theme/styles/code.styl +++ b/theme/styles/code.styl @@ -8,6 +8,10 @@ div[class*="language-"] .custom-style-wrapper.window-controls padding: 10px 0 0 10px; + .highlight-lines { + padding-top: 4rem; +} + div[class*="language-"].line-numbers-mode { .line-numbers-wrapper { top: 28px; diff --git a/yarn.lock b/yarn.lock index f35e47d..5ca31b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8105,6 +8105,11 @@ markdown-it-imsize@^2.0.1: resolved "https://registry.yarnpkg.com/markdown-it-imsize/-/markdown-it-imsize-2.0.1.tgz#cca0427905d05338a247cb9ca9d968c5cddd5170" integrity sha1-zKBCeQXQUziiR8ucqdloxc3dUXA= +markdown-it-include@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-it-include/-/markdown-it-include-2.0.0.tgz#e86e3b3c68c8f0e0437e179ba919ffd28443127a" + integrity sha512-wfgIX92ZEYahYWiCk6Jx36XmHvAimeHN420csOWgfyZjpf171Y0xREqZWcm/Rwjzyd0RLYryY+cbNmrkYW2MDw== + markdown-it-mark@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz#51257db58787d78aaf46dc13418d99a9f3f0ebd3"