feat(plugin-md-power): add multiple lines parse for annotation syntax (#496)
* feat(plugin-md-power): add multiple lines parse for `annotation` syntax * chore: tweak
This commit is contained in:
parent
2505e7f623
commit
b879c62442
@ -7,7 +7,7 @@ permalink: /guide/markdown/annotation/
|
||||
|
||||
## 描述
|
||||
|
||||
Annotation(注释) 是 Markdown 中的一种特殊的语法,用于在文档中添加额外的信息、说明或者提示。
|
||||
==Annotation(注释)== 是 Markdown 中的一种特殊的语法,用于在文档中添加额外的信息、说明或者提示。
|
||||
|
||||
注释不会直接显示在文档中,需要用户手动点击才会显示。
|
||||
|
||||
@ -34,49 +34,120 @@ export default defineUserConfig({
|
||||
|
||||
## 语法
|
||||
|
||||
Annotation(注释) 语法有两个部分组成:
|
||||
==Annotation(注释)== 语法由两个部分组成:
|
||||
|
||||
- **行内注释:** 在行内通过 `[+label]` 语法插入注释。
|
||||
- **定义注释:** 在文档单独一行中使用 `[+label]: 内容` 语法定义注释。
|
||||
### 行内注释
|
||||
|
||||
在行内通过 `[+label]` 语法插入注释标签。
|
||||
|
||||
注释标签由 `[+` + `label` + `]` 组成。为方便与内容做区分,在 `[+label]` 的左边边缘应该有一个空格。
|
||||
|
||||
`label` 为注释的标签,可以是任意字符串。
|
||||
|
||||
::: important 符号 `+` 是必须的
|
||||
|
||||
- 行内注释语法由 `[+` + `label` + `]` 组成。
|
||||
- 定义注释语法由 `[+` + `label` + `]:` + `内容` 组成。
|
||||
:::
|
||||
|
||||
### 定义注释
|
||||
|
||||
在文档的单独区域中使用 `[+label]:` 语法开始定义注释。
|
||||
|
||||
注释定义区域由 `[+` + `label` + `]:` + `内容` 组成。
|
||||
|
||||
`label` 应该与上述的 `[+label]` 一致,用于标记注释的标签。
|
||||
|
||||
**内容** 可以跟随在 `:` 之后开始写:
|
||||
|
||||
```md
|
||||
[+label]: 这里是内容,可以使用 **Markdown** 语法。
|
||||
```
|
||||
|
||||
**内容** 也可以从下一行开始写,但需要添加缩进,多行时应该保持一致的缩进。
|
||||
|
||||
```md
|
||||
[+label]:
|
||||
这里是内容。
|
||||
缩进一致,此行也是内容。
|
||||
|
||||
即使上一行空行,但此行缩进也是一致的,也是内容。
|
||||
可以使用 **Markdown** 语法。
|
||||
|
||||
此行不再缩进,该标签的注释定义在上一行结束。
|
||||
```
|
||||
|
||||
定义注释的内容不会直接渲染在文档中,而是在 行内注释 的 `[+label]` 被点击后呈现。
|
||||
|
||||
## 示例
|
||||
|
||||
### 示例一
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
站点由 VuePress [+vuepress] 驱动。
|
||||
|
||||
[+vuepress]: VuePress 是一个 [静态站点生成器](https://en.wikipedia.org/wiki/Static_site_generator) (SSG) 。 专为构建快速、以内容为中心的站点而设计。
|
||||
[+vuepress]:
|
||||
VuePress 是一个 [静态站点生成器](https://en.wikipedia.org/wiki/Static_site_generator) (SSG) 。
|
||||
专为构建快速、以内容为中心的站点而设计。
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
站点由 VuePress [+vuepress] 驱动。
|
||||
|
||||
[+vuepress]: VuePress 是一个 [静态站点生成器](https://en.wikipedia.org/wiki/Static_site_generator) (SSG) 。 专为构建快速、以内容为中心的站点而设计。
|
||||
[+vuepress]:
|
||||
VuePress 是一个 [静态站点生成器](https://en.wikipedia.org/wiki/Static_site_generator) (SSG) 。
|
||||
专为构建快速、以内容为中心的站点而设计。
|
||||
|
||||
**还可以为 `label` 定义多个注释,多个定义将会以列表的形式渲染。**
|
||||
### 示例二
|
||||
|
||||
**同一个 `label` 定义多个注释,多个定义以列表的形式渲染。**
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
中国古代 **四大名著** [+名著] 家喻户晓。
|
||||
|
||||
[+名著]: **《三国演义》:** 以三国时期的历史为背景,描写了魏、蜀、吴三国之间的政治、军事斗争,塑造了诸葛亮、曹操、关羽、刘备等众多历史人物形象。
|
||||
[+名著]: **《西游记》:** 讲述了唐僧师徒四人(孙悟空、猪八戒、沙僧、白龙马)西天取经的故事,充满了神话色彩和奇幻冒险。
|
||||
[+名著]: **《红楼梦》:** 以贾、史、王、薛四大家族的兴衰为背景,描写了贾宝玉、林黛玉、薛宝钗等人的爱情悲剧,展现了封建社会的腐朽与没落。
|
||||
[+名著]: **《水浒传》:** 描写了北宋末年以宋江为首的108位好汉在梁山泊聚义,反抗朝廷的故事,展现了官逼民反的社会现实。
|
||||
[+名著]:
|
||||
**《三国演义》:**
|
||||
|
||||
以三国时期的历史为背景,描写了魏、蜀、吴三国之间的政治、军事斗争,塑造了诸葛亮、曹操、关羽、刘备等众多历史人物形象。
|
||||
|
||||
[+名著]:
|
||||
**《西游记》:**
|
||||
|
||||
讲述了唐僧师徒四人(孙悟空、猪八戒、沙僧、白龙马)西天取经的故事,充满了神话色彩和奇幻冒险。
|
||||
|
||||
[+名著]:
|
||||
**《红楼梦》:**
|
||||
|
||||
以贾、史、王、薛四大家族的兴衰为背景,描写了贾宝玉、林黛玉、薛宝钗等人的爱情悲剧,展现了封建社会的腐朽与没落。
|
||||
|
||||
[+名著]:
|
||||
**《水浒传》:**
|
||||
|
||||
描写了北宋末年以宋江为首的108位好汉在梁山泊聚义,反抗朝廷的故事,展现了官逼民反的社会现实。
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
中国古代 **四大名著** [+名著] 家喻户晓。
|
||||
|
||||
[+名著]: **《三国演义》:** 以三国时期的历史为背景,描写了魏、蜀、吴三国之间的政治、军事斗争,塑造了诸葛亮、曹操、关羽、刘备等众多历史人物形象。
|
||||
[+名著]: **《西游记》:** 讲述了唐僧师徒四人(孙悟空、猪八戒、沙僧、白龙马)西天取经的故事,充满了神话色彩和奇幻冒险。
|
||||
[+名著]: **《红楼梦》:** 以贾、史、王、薛四大家族的兴衰为背景,描写了贾宝玉、林黛玉、薛宝钗等人的爱情悲剧,展现了封建社会的腐朽与没落。
|
||||
[+名著]: **《水浒传》:** 描写了北宋末年以宋江为首的108位好汉在梁山泊聚义,反抗朝廷的故事,展现了官逼民反的社会现实。
|
||||
[+名著]:
|
||||
**《三国演义》:**
|
||||
|
||||
以三国时期的历史为背景,描写了魏、蜀、吴三国之间的政治、军事斗争,塑造了诸葛亮、曹操、关羽、刘备等众多历史人物形象。
|
||||
|
||||
[+名著]:
|
||||
**《西游记》:**
|
||||
|
||||
讲述了唐僧师徒四人(孙悟空、猪八戒、沙僧、白龙马)西天取经的故事,充满了神话色彩和奇幻冒险。
|
||||
|
||||
[+名著]:
|
||||
**《红楼梦》:**
|
||||
|
||||
以贾、史、王、薛四大家族的兴衰为背景,描写了贾宝玉、林黛玉、薛宝钗等人的爱情悲剧,展现了封建社会的腐朽与没落。
|
||||
|
||||
[+名著]:
|
||||
**《水浒传》:**
|
||||
|
||||
描写了北宋末年以宋江为首的108位好汉在梁山泊聚义,反抗朝廷的故事,展现了官逼民反的社会现实。
|
||||
|
||||
@ -24,32 +24,41 @@ function updatePosition() {
|
||||
return
|
||||
const { x: _x, y: _y, width: w, height: h } = button.value.getBoundingClientRect()
|
||||
const x = _x + w / 2
|
||||
const y = _y + h
|
||||
const y = _y + h / 2
|
||||
|
||||
const { width, height } = popover.value.getBoundingClientRect()
|
||||
const { clientWidth, clientHeight } = document.documentElement
|
||||
position.value.x = x + width + 16 > clientWidth ? clientWidth - x - width - 16 : 0
|
||||
position.value.y = y + height + 16 > clientHeight ? clientHeight - y - height - 16 : 0
|
||||
|
||||
if (y > clientHeight - 16) {
|
||||
active.value = false
|
||||
}
|
||||
else {
|
||||
position.value.y = y + height + 16 > clientHeight ? clientHeight - y - height - 16 : 0
|
||||
}
|
||||
}
|
||||
|
||||
watch(active, () => nextTick(updatePosition))
|
||||
useEventListener('resize', updatePosition)
|
||||
useEventListener('scroll', updatePosition, { passive: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="vp-annotation" :class="{ active, [label]: true }" :aria-label="label">
|
||||
<span class="vp-annotation ignore-header" :class="{ active, [label]: true }" :aria-label="label">
|
||||
<span ref="button" class="vpi-annotation" @click="active = !active" />
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-show="active" ref="popover"
|
||||
class="annotations-popover" :class="{ list: list.length > 1 }"
|
||||
:style="{ '--vp-annotation-x': `${position.x}px`, '--vp-annotation-y': `${position.y}px` }"
|
||||
>
|
||||
<div v-for="i in list" :key="label + i" class="annotation">
|
||||
<slot :name="`item-${i}`" />
|
||||
<ClientOnly>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-show="active" ref="popover"
|
||||
class="annotations-popover" :class="{ list: list.length > 1 }"
|
||||
:style="{ '--vp-annotation-x': `${position.x}px`, '--vp-annotation-y': `${position.y}px` }"
|
||||
>
|
||||
<div v-for="i in list" :key="label + i" class="annotation">
|
||||
<slot :name="`item-${i}`" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</ClientOnly>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@ -94,13 +103,15 @@ useEventListener('resize', updatePosition)
|
||||
max-width: min(calc(100vw - 32px), 360px);
|
||||
max-height: 360px;
|
||||
padding: 8px 12px;
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
background-color: var(--vp-c-bg);
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
transform: translateX(var(--vp-annotation-x, 0)) translateY(var(--vp-annotation-y, 0));
|
||||
transform: translateX(var(--vp-annotation-x, 0)) translateY(var(--vp-annotation-y, 0)) translateZ(0);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.annotations-popover.list {
|
||||
@ -117,4 +128,25 @@ useEventListener('resize', updatePosition)
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
.annotations-popover :deep(p) {
|
||||
margin: 12px 0;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.annotations-popover :deep(:first-child) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.annotations-popover :deep(:last-child) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.annotations-popover.list :deep(:first-child) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.annotations-popover.list :deep(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -8,12 +8,14 @@ import type Token from 'markdown-it/lib/token.mjs'
|
||||
interface AnnotationToken extends Token {
|
||||
meta: {
|
||||
label: string
|
||||
annotations: string[]
|
||||
}
|
||||
}
|
||||
|
||||
interface AnnotationEnv extends Record<string, unknown> {
|
||||
annotations: Record<string, string[]>
|
||||
annotations: Record<string, {
|
||||
sources: string[]
|
||||
rendered: string[]
|
||||
}>
|
||||
}
|
||||
|
||||
interface AnnotationStateBlock extends StateBlock {
|
||||
@ -55,7 +57,7 @@ const annotationDef: RuleBlock = (
|
||||
}
|
||||
|
||||
if (
|
||||
// empty footnote label
|
||||
// empty annotation label
|
||||
pos === start + 2
|
||||
|| pos + 1 >= max
|
||||
|| state.src.charAt(++pos) !== ':'
|
||||
@ -68,16 +70,30 @@ const annotationDef: RuleBlock = (
|
||||
|
||||
pos++
|
||||
|
||||
state.env.annotations ??= {}
|
||||
|
||||
const data = state.env.annotations ??= {}
|
||||
const label = state.src.slice(start + 2, pos - 2)
|
||||
const annotation = state.src.slice(pos, max).trim()
|
||||
|
||||
state.env.annotations[`:${label}`] ??= []
|
||||
let annotation = state.src.slice(pos, max).trim()
|
||||
|
||||
state.env.annotations[`:${label}`].push(annotation)
|
||||
// 处理多行注释
|
||||
let nextLine = startLine + 1
|
||||
while (nextLine < endLine) {
|
||||
const nextStart = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||
const nextMax = state.eMarks[nextLine]
|
||||
const source = state.src.slice(nextStart, nextMax).trim()
|
||||
|
||||
state.line += 1
|
||||
// 行不为空,且行缩进小于块缩进,则跳出
|
||||
if (state.sCount[nextLine] < state.blkIndent + 2 && source !== '')
|
||||
break
|
||||
|
||||
annotation += `\n${source}`
|
||||
nextLine++
|
||||
}
|
||||
|
||||
const current = data[`:${label}`] ??= { sources: [], rendered: [] }
|
||||
current.sources.push(annotation)
|
||||
|
||||
state.line = nextLine
|
||||
|
||||
return true
|
||||
}
|
||||
@ -120,7 +136,7 @@ const annotationRef: RuleInline = (
|
||||
pos++
|
||||
|
||||
const label = state.src.slice(start + 2, pos - 1)
|
||||
const annotations = state.env.annotations?.[`:${label}`] ?? []
|
||||
const annotations = state.env.annotations?.[`:${label}`]?.sources ?? []
|
||||
|
||||
if (annotations.length === 0)
|
||||
return false
|
||||
@ -128,10 +144,7 @@ const annotationRef: RuleInline = (
|
||||
if (!silent) {
|
||||
const refToken = state.push('annotation_ref', '', 0)
|
||||
|
||||
refToken.meta = {
|
||||
label,
|
||||
annotations,
|
||||
} as AnnotationToken['meta']
|
||||
refToken.meta = { label } as AnnotationToken['meta']
|
||||
}
|
||||
|
||||
state.pos = pos
|
||||
@ -144,13 +157,17 @@ export const annotationPlugin: PluginSimple = (md) => {
|
||||
md.renderer.rules.annotation_ref = (
|
||||
tokens: AnnotationToken[],
|
||||
idx: number,
|
||||
_,
|
||||
env: AnnotationEnv,
|
||||
) => {
|
||||
const { label = '', annotations = [] } = tokens[idx].meta ?? {}
|
||||
return `<Annotation label="${label}" :total="${annotations.length}">
|
||||
${annotations.map((annotation, i) => {
|
||||
return `<template #item-${i}>${md.renderInline(annotation)}</template>`
|
||||
}).join('\n')}
|
||||
</Annotation>`
|
||||
const label = tokens[idx].meta.label
|
||||
const data = env.annotations[`:${label}`]
|
||||
|
||||
return `<Annotation label="${label}" :total="${data.sources.length}">${
|
||||
data.sources.map((source, i) => {
|
||||
const annotation = data.rendered[i] ??= md.render(source, env)
|
||||
return `<template #item-${i}>${annotation}</template>`
|
||||
}).join('')}</Annotation>`
|
||||
}
|
||||
|
||||
md.inline.ruler.before('image', 'annotation_ref', annotationRef)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user