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 `${md.renderInline(annotation)}`
+ }).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
*/