import type { RenderRule } from 'markdown-it/lib/renderer.mjs' import type StateBlock from 'markdown-it/lib/rules_block/state_block.mjs' import type { Markdown } from 'vuepress/markdown' import container from 'markdown-it-container' import { resolveAttrs } from '../utils/resolveAttrs.js' type RenderRuleParams = Parameters extends [...infer Args, infer _] ? Args : never export interface ContainerOptions { before?: (info: string, ...args: RenderRuleParams) => string after?: (info: string, ...args: RenderRuleParams) => string } export function createContainerPlugin( md: Markdown, type: string, { before, after }: ContainerOptions = {}, ): void { const render: RenderRule = (tokens, index, options, env): string => { const token = tokens[index] const info = token.info.trim().slice(type.length).trim() || '' if (token.nesting === 1) { return before?.(info, tokens, index, options, env) ?? `
` } else { return after?.(info, tokens, index, options, env) ?? '
' } } md.use(container, type, { render }) } /** * 创建一个自定义的容器规则,内容不会交给 markdown-it 处理。 * 需要自定义 content 的处理逻辑 * ```md * ::: type * xxxx <-- content: 这部分的内容不会交给 markdown-it 处理 * ::: * ``` * * @example * ```ts * const example = createContainerSyntaxPlugin(md, 'example', (tokens, index, options, env) => { * const { content, meta } = tokens[index] * return `
${meta.title} | ${content}
` * }) * ``` */ export function createContainerSyntaxPlugin( md: Markdown, type: string, render?: RenderRule, ): void { const maker = ':' const markerMinLen = 3 function defineContainer(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean { const start = state.bMarks[startLine] + state.tShift[startLine] const max = state.eMarks[startLine] let pos = start // check marker if (state.src[pos] !== maker) return false pos += markerMinLen for (pos = start + 1; pos <= max; pos++) { if (state.src[pos] !== maker) break } if (pos - start < markerMinLen) return false const markup = state.src.slice(start, pos) const info = state.src.slice(pos, max).trim() // ::: type if (!info.startsWith(type)) return false /* istanbul ignore if -- @preserve */ if (silent) return true let line = startLine let content = '' while (++line < endLine) { if (state.src.slice(state.bMarks[line], state.eMarks[line]).trim() === markup) { break } content += `${state.src.slice(state.bMarks[line], state.eMarks[line])}\n` } const token = state.push(`${type}_container`, '', 0) token.meta = resolveAttrs(info.slice(type.length)).attrs token.content = content token.markup = `${markup} ${type}` token.map = [startLine, line + 1] state.line = line + 1 return true } const defaultRender: RenderRule = (tokens, index) => { const { content } = tokens[index] return `
${content}
` } md.block.ruler.before('fence', `${type}_definition`, defineContainer) md.renderer.rules[`${type}_container`] = render ?? defaultRender }