From 0f1ffc75322ba39d07a3fc93c3e93bf2b21f0faf Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 15 Sep 2024 02:13:49 +0800 Subject: [PATCH] perf(plugin-md-power): optimize `collapsed-lines` (#180) --- .../src/node/markdown/collapsedLinesPlugin.ts | 61 +++++++++++++++++++ .../plugin-shikiji/src/node/markdown/index.ts | 1 + .../src/node/markdown/preWrapperPlugin.ts | 10 +-- .../plugin-shikiji/src/node/shikiPlugin.ts | 9 ++- .../src/node/utils/collapsedLines.ts | 34 ++++++----- 5 files changed, 88 insertions(+), 27 deletions(-) create mode 100644 plugins/plugin-shikiji/src/node/markdown/collapsedLinesPlugin.ts diff --git a/plugins/plugin-shikiji/src/node/markdown/collapsedLinesPlugin.ts b/plugins/plugin-shikiji/src/node/markdown/collapsedLinesPlugin.ts new file mode 100644 index 00000000..468ca1b0 --- /dev/null +++ b/plugins/plugin-shikiji/src/node/markdown/collapsedLinesPlugin.ts @@ -0,0 +1,61 @@ +import type { Markdown } from 'vuepress/markdown' +import { resolveCollapsedLines } from '../utils/index.js' + +export interface MarkdownItCollapsedLinesOptions { + /** + * Whether to collapse code blocks when they exceed a certain number of lines, + * + * - If `number`, collapse starts from line `number`. + * - If `true`, collapse starts from line 15 by default. + * - If `false`, disable collapse. + * @default false + */ + collapsedLines?: boolean | number +} + +export function collapsedLinesPlugin(md: Markdown, { + collapsedLines: collapsedLinesOptions = false, +}: MarkdownItCollapsedLinesOptions = {}): void { + const rawFence = md.renderer.rules.fence! + + md.renderer.rules.fence = (...args) => { + const [tokens, index] = args + const token = tokens[index] + // get token info + const info = token.info ? md.utils.unescapeAll(token.info).trim() : '' + const code = rawFence(...args) + + // resolve collapsed-lines mark from token info + const collapsedLinesInfo + = resolveCollapsedLines(info) ?? collapsedLinesOptions + + if (collapsedLinesInfo === false) { + return code + } + + const lines + = code.slice(code.indexOf(''), code.indexOf('')).split('\n').length + + const startLines + = typeof collapsedLinesInfo === 'number' ? collapsedLinesInfo : 15 + + if (lines < startLines) { + return code + } + + const collapsedLinesCode = `
` + const styles = `--vp-collapsed-lines:${startLines};` + + const finalCode = code + .replace(/<\/div>$/, `${collapsedLinesCode}`) + .replace(/"(language-[^"]*)"/, '"$1 has-collapsed collapsed"') + .replace(/^]*>/, (match) => { + if (!match.includes('style=')) { + return `${match.slice(0, -1)} style="${styles}">` + } + return match.replace(/(style=")/, `$1${styles}`) + }) + + return finalCode + } +} diff --git a/plugins/plugin-shikiji/src/node/markdown/index.ts b/plugins/plugin-shikiji/src/node/markdown/index.ts index 18c329c3..1ce8a03d 100644 --- a/plugins/plugin-shikiji/src/node/markdown/index.ts +++ b/plugins/plugin-shikiji/src/node/markdown/index.ts @@ -1,3 +1,4 @@ +export * from './collapsedLinesPlugin.js' export * from './highlightLinesPlugin.js' export * from './lineNumberPlugin.js' export * from './preWrapperPlugin.js' diff --git a/plugins/plugin-shikiji/src/node/markdown/preWrapperPlugin.ts b/plugins/plugin-shikiji/src/node/markdown/preWrapperPlugin.ts index 089eae83..5da74fd3 100644 --- a/plugins/plugin-shikiji/src/node/markdown/preWrapperPlugin.ts +++ b/plugins/plugin-shikiji/src/node/markdown/preWrapperPlugin.ts @@ -1,12 +1,12 @@ // markdown-it plugin for generating line numbers. // v-pre block logic is in `../highlight.ts` import type { Markdown } from 'vuepress/markdown' -import { resolveAttr, resolveCollapsedLines, resolveLanguage } from '../utils/index.js' +import { resolveAttr, resolveLanguage } from '../utils/index.js' import type { PreWrapperOptions } from '../types.js' export function preWrapperPlugin( md: Markdown, - { preWrapper = true, collapsedLines = false }: PreWrapperOptions = {}, + { preWrapper = true }: PreWrapperOptions = {}, ): void { const rawFence = md.renderer.rules.fence! @@ -33,12 +33,6 @@ export function preWrapperPlugin( `data-ext="${lang}"`, `data-title="${title}"`, ] - const collapsed = resolveCollapsedLines(info, collapsedLines) - if (collapsed) { - classes.push('has-collapsed', 'collapsed') - attrs.push(`style="--vp-collapsed-lines:${collapsed}"`) - result += `
` - } return `
${result}
` } diff --git a/plugins/plugin-shikiji/src/node/shikiPlugin.ts b/plugins/plugin-shikiji/src/node/shikiPlugin.ts index 6cecdffc..6fbc7eda 100644 --- a/plugins/plugin-shikiji/src/node/shikiPlugin.ts +++ b/plugins/plugin-shikiji/src/node/shikiPlugin.ts @@ -3,6 +3,7 @@ import type { Plugin } from 'vuepress/core' import { copyCodeButtonPlugin } from './copy-code-button/index.js' import { highlight } from './highlight/index.js' import { + collapsedLinesPlugin, highlightLinesPlugin, lineNumberPlugin, preWrapperPlugin, @@ -53,14 +54,12 @@ export function shikiPlugin({ md.options.highlight = await highlight(theme, options) md.use(highlightLinesPlugin) - md.use(preWrapperPlugin, { - preWrapper, - collapsedLines, - }) + md.use(preWrapperPlugin, { preWrapper }) if (preWrapper) { copyCodeButtonPlugin(md, app, copyCode) - md.use(lineNumberPlugin, { lineNumbers }) + md.use(lineNumberPlugin, { lineNumbers }) + md.use(collapsedLinesPlugin, { collapsedLines }) } }, diff --git a/plugins/plugin-shikiji/src/node/utils/collapsedLines.ts b/plugins/plugin-shikiji/src/node/utils/collapsedLines.ts index 72d42f37..dddc8c7c 100644 --- a/plugins/plugin-shikiji/src/node/utils/collapsedLines.ts +++ b/plugins/plugin-shikiji/src/node/utils/collapsedLines.ts @@ -1,18 +1,24 @@ -export const COLLAPSED_LINES_REGEXP = /:collapsed-lines(?:=(\d+))?\b/ -export const NO_COLLAPSED_LINES_REGEXP = /:no-collapsed-lines\b/ +const COLLAPSED_LINES_REGEXP = /:collapsed-lines\b/ +const COLLAPSED_LINES_START_REGEXP = /:collapsed-lines=(\d+)\b/ +const NO_COLLAPSED_LINES_REGEXP = /:no-collapsed-lines\b/ -const DEFAULT_LINES = 15 +/** + * Resolve the `:collapsed-lines` `:collapsed-lines=num` / `:no-collapsed-lines` mark from token info + */ +export function resolveCollapsedLines(info: string): boolean | number | null { + const lines = COLLAPSED_LINES_START_REGEXP.exec(info)?.[1] -export function resolveCollapsedLines(info: string, defaultLines: boolean | number): number | false { - if (NO_COLLAPSED_LINES_REGEXP.test(info)) - return false - - const lines = defaultLines === true ? DEFAULT_LINES : defaultLines - - const match = info.match(COLLAPSED_LINES_REGEXP) - - if (match) { - return Number(match[1]) || lines || DEFAULT_LINES + if (lines) { + return Number(lines) } - return lines ?? false + + if (COLLAPSED_LINES_REGEXP.test(info)) { + return true + } + + if (NO_COLLAPSED_LINES_REGEXP.test(info)) { + return false + } + + return null }