diff --git a/.markdownlint.json b/.markdownlint.json index 817cb214..1378b84b 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -25,5 +25,7 @@ "no-hard-tabs": { "spaces_per_tab": 2, "ignore_code_languages": ["xml"] - } + }, + "link-image-reference-definitions": false, + "no-bare-urls": false } diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts index bfd932df..6a63dfd0 100644 --- a/docs/.vuepress/theme.ts +++ b/docs/.vuepress/theme.ts @@ -25,6 +25,7 @@ export const theme: Theme = plumeTheme({ flowchart: true, }, markdownPower: { + annotation: true, abbr: true, imageSize: 'all', pdf: true, diff --git a/plugins/plugin-md-power/src/client/components/Annotation.vue b/plugins/plugin-md-power/src/client/components/Annotation.vue new file mode 100644 index 00000000..abe5595d --- /dev/null +++ b/plugins/plugin-md-power/src/client/components/Annotation.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/plugins/plugin-md-power/src/node/inline/annotation.ts b/plugins/plugin-md-power/src/node/inline/annotation.ts new file mode 100644 index 00000000..e5023a8e --- /dev/null +++ b/plugins/plugin-md-power/src/node/inline/annotation.ts @@ -0,0 +1,161 @@ +import type { PluginSimple } from 'markdown-it' +import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs' +import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs' +import type StateBlock from 'markdown-it/lib/rules_block/state_block.mjs' +import type StateInline from 'markdown-it/lib/rules_inline/state_inline.mjs' +import type Token from 'markdown-it/lib/token.mjs' + +interface AnnotationToken extends Token { + meta: { + label: string + annotations: string[] + } +} + +interface AnnotationEnv extends Record { + annotations: Record +} + +interface AnnotationStateBlock extends StateBlock { + tokens: AnnotationToken[] + env: AnnotationEnv +} + +interface AnnotationStateInline extends StateInline { + tokens: AnnotationToken[] + env: AnnotationEnv +} + +const annotationDef: RuleBlock = ( + state: AnnotationStateBlock, + startLine: number, + endLine: number, + silent: boolean, +) => { + const start = state.bMarks[startLine] + state.tShift[startLine] + const max = state.eMarks[startLine] + + if ( + // line should be at least 5 chars - "[+x]:" + start + 4 > max + || state.src.charAt(start) !== '[' + || state.src.charAt(start + 1) !== '+' + ) { + return false + } + + let pos = start + 2 + + while (pos < max) { + if (state.src.charAt(pos) === ' ') + return false + if (state.src.charAt(pos) === ']') + break + pos++ + } + + if ( + // empty footnote label + pos === start + 2 + || pos + 1 >= max + || state.src.charAt(++pos) !== ':' + ) { + return false + } + + if (silent) + return true + + pos++ + + state.env.annotations ??= {} + + const label = state.src.slice(start + 2, pos - 2) + const annotation = state.src.slice(pos, max).trim() + + state.env.annotations[`:${label}`] ??= [] + + state.env.annotations[`:${label}`].push(annotation) + + state.line += 1 + + return true +} + +const annotationRef: RuleInline = ( + state: AnnotationStateInline, + silent: boolean, +): boolean => { + const start = state.pos + const max = state.posMax + + if ( + // should be at least 4 chars - "[+x]" + start + 3 > max + || typeof state.env.annotations === 'undefined' + || state.src.charAt(start) !== '[' + || state.src.charAt(start + 1) !== '+' + ) { + return false + } + + let pos = start + 2 + + while (pos < max) { + if (state.src.charAt(pos) === ' ' || state.src.charAt(pos) === '\n') + return false + if (state.src.charAt(pos) === ']') + break + pos++ + } + + if ( + // empty annotation labels + pos === start + 2 + || pos >= max + ) { + return false + } + + pos++ + + const label = state.src.slice(start + 2, pos - 1) + const annotations = state.env.annotations?.[`:${label}`] ?? [] + + if (annotations.length === 0) + return false + + if (!silent) { + const refToken = state.push('annotation_ref', '', 0) + + refToken.meta = { + label, + annotations, + } as AnnotationToken['meta'] + } + + state.pos = pos + state.posMax = max + + return true +} + +export const annotationPlugin: PluginSimple = (md) => { + md.renderer.rules.annotation_ref = ( + tokens: AnnotationToken[], + idx: number, + ) => { + const { label = '', annotations = [] } = tokens[idx].meta ?? {} + return ` + ${annotations.map((annotation, i) => { + return `` + }).join('\n')} + ` + } + + md.inline.ruler.before('image', 'annotation_ref', annotationRef) + + md.block.ruler.before('reference', 'annotation', annotationDef, { + alt: ['paragraph', 'reference'], + }) +} diff --git a/plugins/plugin-md-power/src/node/inline/index.ts b/plugins/plugin-md-power/src/node/inline/index.ts index e4893608..092a59ec 100644 --- a/plugins/plugin-md-power/src/node/inline/index.ts +++ b/plugins/plugin-md-power/src/node/inline/index.ts @@ -8,6 +8,7 @@ import { sup } from '@mdit/plugin-sup' import { tasklist } from '@mdit/plugin-tasklist' import { isPlainObject } from '@vuepress/helper' import { abbrPlugin } from './abbr.js' +import { annotationPlugin } from './annotation.js' import { iconsPlugin } from './icons.js' import { plotPlugin } from './plot.js' @@ -22,9 +23,21 @@ export function inlineSyntaxPlugin( md.use(footnote) md.use(tasklist) + if (options.annotation) { + /** + * xxx [+foo] xxx + * + * [+foo]: xxx + */ + md.use(annotationPlugin) + } + if (options.abbr) { - // a HTML element - // *[HTML]: A HTML element description + /** + * a HTML element + * + * [HTML]: A HTML element description + */ md.use(abbrPlugin) } diff --git a/plugins/plugin-md-power/src/node/prepareConfigFile.ts b/plugins/plugin-md-power/src/node/prepareConfigFile.ts index ea82bf64..f4ead22c 100644 --- a/plugins/plugin-md-power/src/node/prepareConfigFile.ts +++ b/plugins/plugin-md-power/src/node/prepareConfigFile.ts @@ -82,6 +82,11 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp enhances.add(`app.component('VPDemoNormal', VPDemoNormal)`) } + if (options.annotation) { + imports.add(`import Annotation from '${CLIENT_FOLDER}components/Annotation.vue'`) + enhances.add(`app.component('Annotation', Annotation)`) + } + if (options.abbr) { imports.add(`import Abbreviation from '${CLIENT_FOLDER}components/Abbreviation.vue'`) enhances.add(`app.component('Abbreviation', Abbreviation)`) diff --git a/plugins/plugin-md-power/src/shared/plugin.ts b/plugins/plugin-md-power/src/shared/plugin.ts index e839fdbb..6008a6ca 100644 --- a/plugins/plugin-md-power/src/shared/plugin.ts +++ b/plugins/plugin-md-power/src/shared/plugin.ts @@ -9,6 +9,12 @@ import type { ReplOptions } from './repl.js' export interface MarkdownPowerPluginOptions { /** + * 是否启用注释 + * @default false + */ + annotation?: boolean + + /* * 是否启用 abbr 语法 * @default false */