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 a5086e79..b346d401 100644 --- a/plugins/plugin-md-power/__test__/__snapshots__/timelinePlugin.spec.ts.snap +++ b/plugins/plugin-md-power/__test__/__snapshots__/timelinePlugin.spec.ts.snap @@ -36,5 +36,9 @@ 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 9907eac8..f08a0cc7 100644 --- a/plugins/plugin-md-power/__test__/timelinePlugin.spec.ts +++ b/plugins/plugin-md-power/__test__/timelinePlugin.spec.ts @@ -4,28 +4,24 @@ import { extractTimelineAttributes, timelinePlugin } from '../src/node/container describe('timeline > extractTimelineAttributes()', () => { it('should work', () => { - const { title, attrs } = extractTimelineAttributes('这是标题 time=Q1') - expect(title).toBe('这是标题') - expect(attrs).toEqual({ time: 'Q1' }) + const meta = extractTimelineAttributes('这是标题 time=Q1') + expect(meta).toEqual({ title: '这是标题', time: 'Q1' }) }) it('should work with multi attrs', () => { - const { title, attrs } = extractTimelineAttributes('这是标题 time=Q1 icon=ri:clockwise-line card=true placement=left line=dashed') - expect(title).toBe('这是标题') - expect(attrs).toEqual({ 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' }) }) it('should work with title include space', () => { - const { title, attrs } = 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(title).toBe('这是标题 这也是标题') - expect(attrs).toEqual({ 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' }) }) it('should work with unknown attr', () => { - const { title, attrs } = extractTimelineAttributes('这是标题 time=Q1 unknown=true card=true') - expect(title).toBe('这是标题 unknown=true card=true') - expect(attrs).toEqual({ time: 'Q1' }) + const meta = extractTimelineAttributes('这是标题 time=Q1 unknown=true card=true') + expect(meta).toEqual({ title: '这是标题 unknown=true card=true', time: 'Q1' }) }) }) @@ -70,6 +66,10 @@ describe('timeline > timelinePlugin()', () => { - 这是标题 card=true placement=left 这是内容 + +- 这是标题 + + 这是内容 ::: ` expect(md.render(source)).toMatchSnapshot() diff --git a/plugins/plugin-md-power/src/node/container/timeline.ts b/plugins/plugin-md-power/src/node/container/timeline.ts index d70b4062..ebd4841a 100644 --- a/plugins/plugin-md-power/src/node/container/timeline.ts +++ b/plugins/plugin-md-power/src/node/container/timeline.ts @@ -6,6 +6,7 @@ * - title time="Q2" icon="ri:clockwise-line" line="dashed" type="warning" color="red" * ::: */ +import type Token from 'markdown-it/lib/token.mjs' import type { Markdown } from 'vuepress/markdown' import { resolveAttrs } from '.././utils/resolveAttrs.js' import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js' @@ -18,7 +19,8 @@ export interface TimelineAttrs { line?: string } -export interface TimelineItemAttrs { +export interface TimelineItemMeta { + title: string time?: string type?: string icon?: string @@ -28,10 +30,6 @@ export interface TimelineItemAttrs { placement?: string } -export interface TimelineItemMeta extends TimelineItemAttrs { - title: string -} - const RE_KEY = /(\w+)=\s*/ const RE_SEARCH_KEY = /\s+\w+=\s*|$/ const RE_CLEAN_VALUE = /(?["'])(.*?)(\k)/ @@ -39,50 +37,8 @@ const RE_CLEAN_VALUE = /(?["'])(.*?)(\k)/ export function timelinePlugin(md: Markdown) { createContainerPlugin(md, 'timeline', { before(info, tokens, index) { - const listStack: number[] = [] // 记录列表嵌套深度 + parseTimeline(tokens, index) - for (let i = index + 1; i < tokens.length; i++) { - const token = tokens[i] - if (token.type === 'container_timeline_close') { - break - } - // 列表层级追踪 - if (token.type === 'bullet_list_open') { - listStack.push(0) // 每个新列表初始层级为0 - if (listStack.length === 1) - token.hidden = true - } - else if (token.type === 'bullet_list_close') { - listStack.pop() - if (listStack.length === 0) - token.hidden = true - } - else if (token.type === 'list_item_open') { - const currentLevel = listStack.length - // 仅处理根级列表项(层级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 - const inlineToken = tokens[i + 2] - const firstChildToken = inlineToken.children?.shift() - const { title, attrs } = extractTimelineAttributes(firstChildToken!.content.trim()) - - token.meta = { - title, - ...attrs, - } as TimelineItemMeta - } - } - else if (token.type === 'list_item_close') { - const currentLevel = listStack.length - if (currentLevel === 1) { - token.type = 'timeline_item_close' - } - } - } const { attrs } = resolveAttrs(info) const { horizontal, card, placement, line } = attrs return ` '' } -// 核心属性扫描器 -export function extractTimelineAttributes(rawText: string): { - title: string - attrs: TimelineItemAttrs -} { +function parseTimeline(tokens: Token[], index: number) { + const listStack: number[] = [] // 记录列表嵌套深度 + + for (let i = index + 1; i < tokens.length; i++) { + const token = tokens[i] + if (token.type === 'container_timeline_close') { + break + } + // 列表层级追踪 + if (token.type === 'bullet_list_open') { + listStack.push(0) // 每个新列表初始层级为0 + if (listStack.length === 1) + token.hidden = true + } + else if (token.type === 'bullet_list_close') { + listStack.pop() + if (listStack.length === 0) + token.hidden = true + } + else if (token.type === 'list_item_open') { + const currentLevel = listStack.length + // 仅处理根级列表项(层级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 + const inlineToken = tokens[i + 2] + const softbreakIndex = inlineToken.children!.findIndex( + 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()) + } + } + else if (token.type === 'list_item_close') { + const currentLevel = listStack.length + if (currentLevel === 1) { + token.type = 'timeline_item_close' + } + } + } +} + +export function extractTimelineAttributes(rawText: string): TimelineItemMeta { const attrKeys = ['time', 'type', 'icon', 'line', 'color', 'card', 'placement'] as const - const attrs: Partial = {} + const attrs: Partial = {} let buffer = rawText.trim() const titleSegments: string[] = [] @@ -160,7 +161,7 @@ export function extractTimelineAttributes(rawText: string): { valueEnd = buffer.length const value = buffer.slice(0, valueEnd).trim() // 存储属性 - attrs[matchedKey as keyof TimelineItemAttrs] = value.replace(RE_CLEAN_VALUE, '$2') + attrs[matchedKey as keyof TimelineItemMeta] = value.replace(RE_CLEAN_VALUE, '$2') // 跳过已处理的值 buffer = buffer.slice(valueEnd) @@ -168,6 +169,6 @@ export function extractTimelineAttributes(rawText: string): { return { title: titleSegments.join(' ').trim(), - attrs, + ...attrs, } }