diff --git a/docs/notes/theme/guide/markdown/timeline.md b/docs/notes/theme/guide/markdown/timeline.md index 3b6b32bd..601a2010 100644 --- a/docs/notes/theme/guide/markdown/timeline.md +++ b/docs/notes/theme/guide/markdown/timeline.md @@ -37,11 +37,13 @@ export default defineUserConfig({ ```md{1,9} title="timeline.md" ::: timeline 配置 -- 标题 配置 +- 标题 + 配置 正文内容 -- 标题 配置 +- 标题 + 配置 正文内容 ::: @@ -49,8 +51,12 @@ export default defineUserConfig({ 对于列表的每一个项: -- __第一行__: 从起始位置定义 __标题__,在标题之后跟着 `key=value` 的格式配置时间点的 __属性__。 -- __后续行__: 正文内容,==请注意添加正确的缩进=={.important}。 +- 从 __首行开始__ 到 __首个空行__,均为 __标题__ ,在标题后紧跟随的一行,用于 __配置__ 当前项的行为 + +- __首个空行之后__: 正文内容 + +:::important 请注意添加正确的缩进 +::: __一个简单的例子:__ @@ -58,15 +64,18 @@ __输入:__ ```md ::: timeline -- 节点一 time=2025-03-20 type=success +- 节点一 + time=2025-03-20 type=success 正文内容 -- 节点二 time=2025-02-21 type=warning +- 节点二 + time=2025-02-21 type=warning 正文内容 -- 节点三 time=2025-01-22 type=danger +- 节点三 + time=2025-01-22 type=danger 正文内容 ::: @@ -76,15 +85,18 @@ __输出:__ ::: timeline -- 节点一 time=2025-03-20 type=success +- 节点一 + time=2025-03-20 type=success 正文内容 -- 节点二 time=2025-02-21 type=warning +- 节点二 + time=2025-02-21 type=warning 正文内容 -- 节点三 time=2025-01-22 type=danger +- 节点三 + time=2025-01-22 type=danger 正文内容 ::: @@ -100,9 +112,17 @@ __时间线__ 支持非常灵活且灵活的配置项,配置主要分为两个 `::: timeline horizontal` 表示 渲染为 水平方向的时间线。 -- __列表项配置__: 在列表的每一个项上的配置,配置项列表项的第一行,跟随在标题之后,如: +- __列表项配置__: 列表的每一个项的配置,紧跟随在标题之后的一行,如: - `- 节点一 time=2025-03-20 type=success` 表示 时间点为 `2025-03-20`,节点类型为 `success`。 + ```md + ::: timeline + - 标题 + 也是标题 + time=2025-03-20 type=success + + 正文内容 + ::: + ``` ### 容器配置 @@ -202,13 +222,24 @@ __输入:__ ```md /horizontal/ ::: timeline horizontal -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=success + +- 节点二 + time=2025-04-20 type=success + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: ``` @@ -217,15 +248,25 @@ __输出:__ ::: timeline horizontal -- 节点一 time=2025-03-20 - 正文内容 -- 节点二 time=2025-04-20 type=success - 正文内容 -- 节点三 time=2025-01-22 type=danger - 正文内容 -- 节点四 time=2025-01-22 type=important +- 节点一 + time=2025-03-20 + 正文内容 +- 节点二 + time=2025-04-20 type=success + + 正文内容 + +- 节点三 + time=2025-01-22 type=danger + + 正文内容 + +- 节点四 + time=2025-01-22 type=important + + 正文内容 ::: ### 右对齐 @@ -236,13 +277,24 @@ __输入:__ ```md /placement="right"/ ::: timeline placement="right" -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=success + +- 节点二 + time=2025-04-20 type=success + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: ``` @@ -251,13 +303,24 @@ __输出:__ ::: timeline placement="right" -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=success + +- 节点二 + time=2025-04-20 type=success + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: @@ -271,13 +334,24 @@ __输入:__ ```md /placement="between"/ /placement=right/ ::: timeline placement="between" -- 节点一 time=2025-03-20 placement=right +- 节点一 + time=2025-03-20 placement=right + 正文内容 -- 节点二 time=2025-04-20 type=success + +- 节点二 + time=2025-04-20 type=success + 正文内容 -- 节点三 time=2025-01-22 type=danger placement=right + +- 节点三 + time=2025-01-22 type=danger placement=right + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: ``` @@ -286,31 +360,53 @@ __输出:__ ::: timeline placement="between" -- 节点一 time=2025-03-20 placement=right +- 节点一 + time=2025-03-20 placement=right + 正文内容 -- 节点二 time=2025-04-20 type=success + +- 节点二 + time=2025-04-20 type=success + 正文内容 -- 节点三 time=2025-01-22 type=danger placement=right + +- 节点三 + time=2025-01-22 type=danger placement=right + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: ### 节点类型 -在列表项首行标题之后,添加 `type=节点类型` 可以为当前节点设置节点类型。 +在列表项配置中,添加 `type=节点类型` 可以为当前节点设置节点类型。 __输入:__ ```md /type=success/ /type=warning/ /type=danger/ /type=important/ ::: timeline -- 节点一 time=2025-03-20 type=success +- 节点一 + time=2025-03-20 type=success + 正文内容 -- 节点二 time=2025-04-20 type=warning + +- 节点二 + time=2025-04-20 type=warning + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: ``` @@ -319,32 +415,54 @@ __输出:__ ::: timeline -- 节点一 time=2025-03-20 type=success +- 节点一 + time=2025-03-20 type=success + 正文内容 -- 节点二 time=2025-04-20 type=warning + +- 节点二 + time=2025-04-20 type=warning + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: ### 线条风格 - 在容器配置中添加 `line=线条风格` 可以为所有节点设置默认线条风格。 -- 在列表项首行标题之后,添加 `line=线条风格` 可以为节点设置线条风格。 +- 在列表项配置中,添加 `line=线条风格` 可以为节点设置线条风格。 __输入:__ ```md /line="dotted"/ /line=solid/ /line=dashed/ ::: timeline line="dotted" -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=success + +- 节点二 + time=2025-04-20 type=success + 正文内容 -- 节点三 time=2025-01-22 type=danger line=dashed + +- 节点三 + time=2025-01-22 type=danger line=dashed + 正文内容 -- 节点四 time=2025-01-22 type=important line=solid + +- 节点四 + time=2025-01-22 type=important line=solid + 正文内容 ::: ``` @@ -353,19 +471,30 @@ __输出:__ ::: timeline line="dotted" -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=success + +- 节点二 + time=2025-04-20 type=success + 正文内容 -- 节点三 time=2025-01-22 type=danger line=dashed + +- 节点三 + time=2025-01-22 type=danger line=dashed + 正文内容 -- 节点四 time=2025-01-22 type=important line=solid + +- 节点四 + time=2025-01-22 type=important line=solid + 正文内容 ::: ### 带图标的节点 -在列表项首行标题之后,添加 `icon=图标名称` 可以为节点添加图标。 +在列表项配置中,添加 `icon=图标名称` 可以为节点添加图标。 图标名称支持 [iconify](https://icon-sets.iconify.design/) 的图标名称。 @@ -373,13 +502,24 @@ __输入:__ ```md /icon=mdi:balloon/ /icon=mdi:bookmark/ ::: timeline placement="between" -- 节点一 time=2025-03-20 placement=right icon=mdi:balloon +- 节点一 + time=2025-03-20 placement=right icon=mdi:balloon + 正文内容 -- 节点二 time=2025-04-20 type=success icon=mdi:bookmark + +- 节点二 + time=2025-04-20 type=success icon=mdi:bookmark + 正文内容 -- 节点三 time=2025-01-22 type=danger placement=right icon=mdi:bullhorn-variant-outline + +- 节点三 + time=2025-01-22 type=danger placement=right icon=mdi:bullhorn-variant-outline + 正文内容 -- 节点四 time=2025-01-22 type=important card=true icon="mdi:cake-variant-outline" + +- 节点四 + time=2025-01-22 type=important card=true icon="mdi:cake-variant-outline" + 正文内容 ::: ``` @@ -388,13 +528,24 @@ __输出:__ ::: timeline placement="between" -- 节点一 time=2025-03-20 placement=right icon=mdi:balloon +- 节点一 + time=2025-03-20 placement=right icon=mdi:balloon + 正文内容 -- 节点二 time=2025-04-20 type=success icon=mdi:bookmark + +- 节点二 + time=2025-04-20 type=success icon=mdi:bookmark + 正文内容 -- 节点三 time=2025-01-22 type=danger placement=right icon=mdi:bullhorn-variant-outline + +- 节点三 + time=2025-01-22 type=danger placement=right icon=mdi:bullhorn-variant-outline + 正文内容 -- 节点四 time=2025-01-22 type=important card=true icon="mdi:cake-variant-outline" + +- 节点四 + time=2025-01-22 type=important card=true icon="mdi:cake-variant-outline" + 正文内容 ::: @@ -403,25 +554,36 @@ __输出:__ 卡片节点可以很灵活的进行控制: - 在 容器配置中添加 `card` 即可使每个列表项都是卡片节点。 -- 在列表项首行标题之后,添加 `card=true` 即可为节点设置为卡片节点。 -- 在列表项首行标题之后,添加 `card=false` 即可为节点设置为非卡片节点。 +- 在列表项配置中,添加 `card=true` 即可为节点设置为卡片节点。 +- 在列表项配置中,添加 `card=false` 即可为节点设置为非卡片节点。 卡片节点的样式会受到 `type` 配置的影响。 -::: tip 在列表项首行标题之后添加 `card=true` / `card=false` 可以覆盖容器节点的 `card` 配置 +::: tip 在列表项配置中添加 `card=true` / `card=false` 可以覆盖容器节点的 `card` 配置 ::: __输入:__ ```md{1} /card=false/ ::: timeline card -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=success card=false + +- 节点二 + time=2025-04-20 type=success card=false + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: ``` @@ -430,13 +592,24 @@ __输出:__ ::: timeline card -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=success card=false + +- 节点二 + time=2025-04-20 type=success card=false + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 -- 节点四 time=2025-01-22 type=important + +- 节点四 + time=2025-01-22 type=important + 正文内容 ::: @@ -480,22 +653,38 @@ __示例:__ ```md /type=your-type/ ::: timeline -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=your-type card=true + +- 节点二 + time=2025-04-20 type=your-type card=true + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 ::: ``` ::: timeline -- 节点一 time=2025-03-20 +- 节点一 + time=2025-03-20 + 正文内容 -- 节点二 time=2025-04-20 type=your-type card=true + +- 节点二 + time=2025-04-20 type=your-type card=true + 正文内容 -- 节点三 time=2025-01-22 type=danger + +- 节点三 + time=2025-01-22 type=danger + 正文内容 ::: diff --git a/plugins/plugin-md-power/__test__/__snapshots__/timelinePlugin.spec.ts.snap b/plugins/plugin-md-power/__test__/__snapshots__/timelinePlugin.spec.ts.snap index b346d401..1631ade4 100644 --- a/plugins/plugin-md-power/__test__/__snapshots__/timelinePlugin.spec.ts.snap +++ b/plugins/plugin-md-power/__test__/__snapshots__/timelinePlugin.spec.ts.snap @@ -1,16 +1,10 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`timeline > timelinePlugin() > should work 1`] = ` -" - - -这是内容 - - -这是内容 - - -这是内容 +"

这是内容

+

这是内容

+

这是内容

  • 1
  • 2
  • @@ -21,24 +15,11 @@ exports[`timeline > timelinePlugin() > should work 1`] = `
-
- - -这是内容
- - -这是内容 - - -这是内容 - - -这是内容 - - -这是内容 - - -

这是内容

+

这是内容

+

这是内容

+

这是内容

+

这是内容

+

这是内容

+

这是内容

" `; diff --git a/plugins/plugin-md-power/__test__/timelinePlugin.spec.ts b/plugins/plugin-md-power/__test__/timelinePlugin.spec.ts index f08a0cc7..3b94e13e 100644 --- a/plugins/plugin-md-power/__test__/timelinePlugin.spec.ts +++ b/plugins/plugin-md-power/__test__/timelinePlugin.spec.ts @@ -4,24 +4,24 @@ import { extractTimelineAttributes, timelinePlugin } from '../src/node/container describe('timeline > extractTimelineAttributes()', () => { it('should work', () => { - const meta = extractTimelineAttributes('这是标题 time=Q1') - expect(meta).toEqual({ title: '这是标题', time: 'Q1' }) + const meta = extractTimelineAttributes('这time=Q1') + expect(meta).toEqual({ time: 'Q1' }) }) it('should work with multi attrs', () => { - const meta = extractTimelineAttributes('这是标题 time=Q1 icon=ri:clockwise-line card=true placement=left line=dashed') - expect(meta).toEqual({ title: '这是标题', time: 'Q1', icon: 'ri:clockwise-line', card: 'true', placement: 'left', line: 'dashed' }) + const meta = extractTimelineAttributes('time=Q1 icon=ri:clockwise-line card=true placement=left line=dashed') + expect(meta).toEqual({ time: 'Q1', icon: 'ri:clockwise-line', card: 'true', placement: 'left', line: 'dashed' }) }) it('should work with title include space', () => { - const meta = extractTimelineAttributes('这是标题 这也是标题 time=Q1 icon=ri:clockwise-line card=true placement=left line=dashed') + const meta = extractTimelineAttributes('time=Q1 icon=ri:clockwise-line card=true placement=left line=dashed') - expect(meta).toEqual({ title: '这是标题 这也是标题', time: 'Q1', icon: 'ri:clockwise-line', card: 'true', placement: 'left', line: 'dashed' }) + expect(meta).toEqual({ time: 'Q1', icon: 'ri:clockwise-line', card: 'true', placement: 'left', line: 'dashed' }) }) it('should work with unknown attr', () => { - const meta = extractTimelineAttributes('这是标题 time=Q1 unknown=true card=true') - expect(meta).toEqual({ title: '这是标题 unknown=true card=true', time: 'Q1' }) + const meta = extractTimelineAttributes('time=Q1 unknown=true card=true') + expect(meta).toEqual({ time: 'Q1' }) }) }) @@ -33,14 +33,19 @@ describe('timeline > timelinePlugin()', () => { const source = `\ ::: timeline - 这是标题 + 这是内容 - 这是标题 + 这也是标题 + 这是内容 ::: ::: timeline horizontal line="dashed" card -- 这是标题 time=q1 +- 这是标题 + time=q1 + 这是内容 - 1 - 2 @@ -48,23 +53,33 @@ describe('timeline > timelinePlugin()', () => { - 1.1 - 1.2 -- 这是标题 time=q2 color=red card=false +- 这是标题 + time=q2 color=red card=false + 这是内容 ::: ::: timeline placement="right" -- 这是标题 icon=xxx card=true type=warning +- 这是标题 + icon=xxx card=true type=warning + 这是内容 -- 这是标题 type=danger line=dotted +- 这是标题 + type=danger line=dotted + 这是内容 ::: ::: timeline placement="between" -- 这是标题 card=true placement=right +- 这是标题 + card=true placement=right + 这是内容 -- 这是标题 card=true placement=left +- 这是标题 + card=true placement=left + 这是内容 - 这是标题 diff --git a/plugins/plugin-md-power/package.json b/plugins/plugin-md-power/package.json index d31c32fb..e68f1b10 100644 --- a/plugins/plugin-md-power/package.json +++ b/plugins/plugin-md-power/package.json @@ -77,6 +77,7 @@ "@mdit/plugin-sup": "catalog:prod", "@mdit/plugin-tab": "catalog:prod", "@mdit/plugin-tasklist": "catalog:prod", + "@pengzhanbo/utils": "catalog:prod", "@vuepress/helper": "catalog:vuepress", "@vueuse/core": "catalog:prod", "chokidar": "catalog:prod", diff --git a/plugins/plugin-md-power/src/node/container/timeline.ts b/plugins/plugin-md-power/src/node/container/timeline.ts index ebd4841a..17352a97 100644 --- a/plugins/plugin-md-power/src/node/container/timeline.ts +++ b/plugins/plugin-md-power/src/node/container/timeline.ts @@ -1,15 +1,21 @@ /** * ::: timeline * - * - title time="Q1" icon="ri:clockwise-line" line="dashed" type="warning" color="red" - * xxx - * - title time="Q2" icon="ri:clockwise-line" line="dashed" type="warning" color="red" + * - title + * time="Q1" icon="ri:clockwise-line" line="dashed" type="warning" color="red" + * + * content + * + * - title + * time="Q2" icon="ri:clockwise-line" line="dashed" type="warning" color="red" + * + * content * ::: */ import type Token from 'markdown-it/lib/token.mjs' import type { Markdown } from 'vuepress/markdown' +import { isEmptyObject } from '@pengzhanbo/utils' import { resolveAttrs } from '.././utils/resolveAttrs.js' -import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js' import { createContainerPlugin } from './createContainer.js' export interface TimelineAttrs { @@ -20,7 +26,6 @@ export interface TimelineAttrs { } export interface TimelineItemMeta { - title: string time?: string type?: string icon?: string @@ -54,9 +59,9 @@ export function timelinePlugin(md: Markdown) { after: () => '
', }) - md.renderer.rules.timeline_item_open = (tokens, idx, _, env) => { + md.renderer.rules.timeline_item_open = (tokens, idx) => { const token = tokens[idx] - const { title, time, type, icon, color, line, card, placement } = token.meta as TimelineItemMeta + const { time, type, icon, color, line, card, placement } = token.meta as TimelineItemMeta return ` - - ${icon ? `` : ''}` + }>${icon ? `` : ''}` } md.renderer.rules.timeline_item_close = () => '' + md.renderer.rules.timeline_item_title_open = () => '' } function parseTimeline(tokens: Token[], index: number) { @@ -101,20 +106,27 @@ function parseTimeline(tokens: Token[], index: number) { // 仅处理根级列表项(层级1) if (currentLevel === 1) { token.type = 'timeline_item_open' - const titleOpenToken = tokens[i + 1] - const titleCloseToken = tokens[i + 3] - titleOpenToken.hidden = true - titleCloseToken.hidden = true + tokens[i + 1].type = 'timeline_item_title_open' + tokens[i + 3].type = 'timeline_item_title_close' + + // - title + // attrs + // 列表项 `-` 后面包括紧跟随的后续行均在 type=inline 的 token 中, 并作为 children const inlineToken = tokens[i + 2] - const softbreakIndex = inlineToken.children!.findIndex( + // 找到最后一个 softbreak,最后一行作为 attrs 进行解析 + const softbreakIndex = inlineToken.children!.findLastIndex( token => token.type === 'softbreak', ) - inlineToken.children = softbreakIndex !== -1 - ? inlineToken.children!.slice(softbreakIndex) - : [] - - const content = inlineToken.content.replace(/\n[\s\S]*/, '') - token.meta = extractTimelineAttributes(content.trim()) + if (softbreakIndex !== -1) { + const lastToken = inlineToken.children![inlineToken.children!.length - 1] + token.meta = extractTimelineAttributes(lastToken.content.trim()) + if (!isEmptyObject(token.meta)) { + inlineToken.children = inlineToken.children!.slice(0, softbreakIndex) + } + } + else { + token.meta = {} + } } } else if (token.type === 'list_item_close') { @@ -128,27 +140,22 @@ function parseTimeline(tokens: Token[], index: number) { export function extractTimelineAttributes(rawText: string): TimelineItemMeta { const attrKeys = ['time', 'type', 'icon', 'line', 'color', 'card', 'placement'] as const - const attrs: Partial = {} + const attrs: TimelineItemMeta = {} let buffer = rawText.trim() - const titleSegments: string[] = [] while (buffer.length) { // 匹配属性键 (支持大小写) const keyMatch = buffer.match(RE_KEY) if (!keyMatch) { - titleSegments.push(buffer) break } // 提取可能的关键字 const matchedKey = keyMatch[1].toLowerCase() if (!attrKeys.includes(matchedKey as any)) { - titleSegments.push(buffer) break } const keyStart = keyMatch.index! - // 记录非属性内容为标题 - titleSegments.push(buffer.slice(0, keyStart).trim()) // 跳过已匹配的 key: const keyEnd = keyStart + keyMatch[0].length @@ -167,8 +174,5 @@ export function extractTimelineAttributes(rawText: string): TimelineItemMeta { buffer = buffer.slice(valueEnd) } - return { - title: titleSegments.join(' ').trim(), - ...attrs, - } + return attrs }