feat(plugin-md-power): add timeline syntax support (#529)
This commit is contained in:
parent
de69b22dcc
commit
5173e7f2ed
@ -43,6 +43,7 @@ export const themeGuide = defineNoteConfig({
|
||||
'steps',
|
||||
'file-tree',
|
||||
'tabs',
|
||||
'timeline',
|
||||
'demo-wrapper',
|
||||
'npm-to',
|
||||
'caniuse',
|
||||
|
||||
@ -28,6 +28,7 @@ export const theme: Theme = plumeTheme({
|
||||
|
||||
annotation: true,
|
||||
abbr: true,
|
||||
timeline: true,
|
||||
imageSize: 'all',
|
||||
pdf: true,
|
||||
caniuse: true,
|
||||
|
||||
@ -155,6 +155,12 @@ export default defineUserConfig({
|
||||
- **默认值**: `true`
|
||||
- **详情**: 是否启用文件树容器语法
|
||||
|
||||
### timeline
|
||||
|
||||
- **类型**: `boolean`
|
||||
- **默认值**: `false`
|
||||
- **详情**: 是否启用时间线容器语法
|
||||
|
||||
### demo
|
||||
|
||||
- **类型**: `boolean`
|
||||
|
||||
508
docs/notes/theme/guide/markdown/timeline.md
Normal file
508
docs/notes/theme/guide/markdown/timeline.md
Normal file
@ -0,0 +1,508 @@
|
||||
---
|
||||
title: 时间线
|
||||
icon: mdi:timeline-text-outline
|
||||
createTime: 2025/03/20 18:05:29
|
||||
permalink: /guide/markdown/timeline/
|
||||
badge:
|
||||
text: 1.0.0-rc.137 +
|
||||
type: tip
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
在 markdown 中,使用 `::: timeline` 容器,包含 markdown 无序列表语法,即可实现 ==时间线== 的 渲染效果。
|
||||
|
||||
- 支持 ==水平方向== 和 ==垂直方向==
|
||||
- 垂直方向支持 __左对齐__,__右对齐__ 和 __两端对齐__
|
||||
- 支持 __图标__ 和 __线条样式__
|
||||
- 支持 通过预设 __类型__ 设置 __颜色__,支持自定义颜色
|
||||
|
||||
## 启用
|
||||
|
||||
该功能默认不启用,你需要在 `theme` 配置中启用。
|
||||
|
||||
```ts title=".vuepress/config.ts"
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
markdown: {
|
||||
timeline: true, // [!code ++]
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
在 `::: timeline` 容器中,使用 markdown 无序列表语法,列表的每一个项即 时间线上的每一个点。
|
||||
|
||||
```md{1,9} title="timeline.md"
|
||||
::: timeline 配置
|
||||
- 标题 配置
|
||||
|
||||
正文内容
|
||||
|
||||
- 标题 配置
|
||||
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
对于列表的每一个项:
|
||||
|
||||
- __第一行__: 从起始位置定义 __标题__,在标题之后跟着 `key=value` 的格式配置时间点的 __属性__。
|
||||
- __后续行__: 正文内容,==请注意添加正确的缩进=={.important}。
|
||||
|
||||
__一个简单的例子:__
|
||||
|
||||
__输入:__
|
||||
|
||||
```md
|
||||
::: timeline
|
||||
- 节点一 time=2025-03-20 type=success
|
||||
|
||||
正文内容
|
||||
|
||||
- 节点二 time=2025-02-21 type=warning
|
||||
|
||||
正文内容
|
||||
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline
|
||||
|
||||
- 节点一 time=2025-03-20 type=success
|
||||
|
||||
正文内容
|
||||
|
||||
- 节点二 time=2025-02-21 type=warning
|
||||
|
||||
正文内容
|
||||
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
|
||||
正文内容
|
||||
:::
|
||||
|
||||
::: important 时间线默认为垂直方向
|
||||
:::
|
||||
|
||||
## 配置
|
||||
|
||||
__时间线__ 支持非常灵活且灵活的配置项,配置主要分为两个部分:
|
||||
|
||||
- __容器配置__: 在 `::: timeline` 容器上的配置,配置项跟随在 `::: timeline` 之后,如:
|
||||
|
||||
`::: timeline horizontal` 表示 渲染为 水平方向的时间线。
|
||||
|
||||
- __列表项配置__: 在列表的每一个项上的配置,配置项列表项的第一行,跟随在标题之后,如:
|
||||
|
||||
`- 节点一 time=2025-03-20 type=success` 表示 时间点为 `2025-03-20`,节点类型为 `success`。
|
||||
|
||||
### 容器配置
|
||||
|
||||
#### horizontal
|
||||
|
||||
- __类型:__ `boolean`
|
||||
- __默认值:__ `false`
|
||||
|
||||
渲染为 水平方向的时间线。
|
||||
|
||||
#### card
|
||||
|
||||
- __类型:__ `boolean`
|
||||
- __默认值:__ `false`
|
||||
|
||||
每个时间节点默认渲染为卡片样式(可在列表项配置中覆盖)。
|
||||
|
||||
#### placement
|
||||
|
||||
- __类型:__ `'left' | 'right' | 'between'`
|
||||
- __默认值:__ `'left'`
|
||||
|
||||
时间节点的对齐方式。==仅在垂直方向时生效=={.warning}
|
||||
|
||||
- `left` : 时间轴左侧对齐
|
||||
- `right` : 时间轴右侧对齐
|
||||
- `between` : 时间轴两端对齐 (通过列表项配置中的 `placement` 定义位置,默认为 `left`)
|
||||
|
||||
#### line
|
||||
|
||||
- __类型:__ `'solid' | 'dashed' | 'dotted'`
|
||||
- __默认值:__ `'solid'`
|
||||
|
||||
线条样式(可在列表项配置中覆盖)
|
||||
|
||||
### 列表项配置
|
||||
|
||||
#### time
|
||||
|
||||
- __类型:__ `string`
|
||||
- __默认值:__ `''`
|
||||
|
||||
时间点,可以是任何字符串,比如 `2025-03-20`, `Q1` 等。
|
||||
|
||||
#### type
|
||||
|
||||
- __类型:__ `'info' | 'tip' | 'success' | 'warning' | 'danger' | 'caution' | 'important'`
|
||||
- __默认值:__ `'info'`
|
||||
|
||||
时间节点的类型。
|
||||
|
||||
#### card
|
||||
|
||||
- __类型:__ `boolean`
|
||||
- __默认值:__ `false` 从 容器配置 `card` 中继承
|
||||
|
||||
当前 时间节点渲染为卡片样式。
|
||||
|
||||
#### line
|
||||
|
||||
- __类型:__ `'solid' | 'dashed' | 'dotted'`
|
||||
- __默认值:__ `'solid'` 从 容器配置 `line` 中继承
|
||||
|
||||
线条样式
|
||||
|
||||
#### icon
|
||||
|
||||
- __类型:__ `string`
|
||||
- __默认值:__ `''`
|
||||
|
||||
时间节点的图标,支持所有的 [iconify](https://icon-sets.iconify.design/) 图标。
|
||||
|
||||
#### placement
|
||||
|
||||
- __类型:__ `'left' | 'right'`
|
||||
- __默认值:__ `'left'`
|
||||
|
||||
当 容器配置为 `between` 时,定义当前时间节点的位置。
|
||||
|
||||
- `left` : 在时间轴左侧
|
||||
- `right` : 在时间轴右侧
|
||||
|
||||
#### color
|
||||
|
||||
- __类型:__ `string`
|
||||
- __默认值:__ `''`
|
||||
|
||||
时间节点线条颜色,可以是任何有效的颜色值。
|
||||
|
||||
## 示例
|
||||
|
||||
### 水平方向
|
||||
|
||||
在 `:::timeline` 后跟随声明 `horizontal` , 即可将时间线渲染为 水平方向。
|
||||
|
||||
__输入:__
|
||||
|
||||
```md /horizontal/
|
||||
::: timeline horizontal
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline horizontal
|
||||
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
|
||||
:::
|
||||
|
||||
### 右对齐
|
||||
|
||||
在 `:::timeline` 后跟随声明 `placement="right"` , 即可将时间线渲染为 右对齐。
|
||||
|
||||
__输入:__
|
||||
|
||||
```md /placement="right"/
|
||||
::: timeline placement="right"
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline placement="right"
|
||||
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
|
||||
### 两端对齐
|
||||
|
||||
在 `:::timeline` 后跟随声明 `placement="between"` , 即可将时间线渲染为 两端对齐。
|
||||
|
||||
列表项默认位于时间线的左侧,可以通过 `placement="right"` 为列表项设置右侧位置。
|
||||
|
||||
__输入:__
|
||||
|
||||
```md /placement="between"/ /placement=right/
|
||||
::: timeline placement="between"
|
||||
- 节点一 time=2025-03-20 placement=right
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger placement=right
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline placement="between"
|
||||
|
||||
- 节点一 time=2025-03-20 placement=right
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger placement=right
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
|
||||
### 节点类型
|
||||
|
||||
在列表项首行标题之后,添加 `type=节点类型` 可以为当前节点设置节点类型。
|
||||
|
||||
__输入:__
|
||||
|
||||
```md /type=success/ /type=warning/ /type=danger/ /type=important/
|
||||
::: timeline
|
||||
- 节点一 time=2025-03-20 type=success
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=warning
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline
|
||||
|
||||
- 节点一 time=2025-03-20 type=success
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=warning
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
|
||||
### 线条风格
|
||||
|
||||
- 在容器配置中添加 `line=线条风格` 可以为所有节点设置默认线条风格。
|
||||
- 在列表项首行标题之后,添加 `line=线条风格` 可以为节点设置线条风格。
|
||||
|
||||
__输入:__
|
||||
|
||||
```md /line="dotted"/ /line=solid/ /line=dashed/
|
||||
::: timeline line="dotted"
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger line=dashed
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important line=solid
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline line="dotted"
|
||||
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger line=dashed
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important line=solid
|
||||
正文内容
|
||||
:::
|
||||
|
||||
### 带图标的节点
|
||||
|
||||
在列表项首行标题之后,添加 `icon=图标名称` 可以为节点添加图标。
|
||||
|
||||
图标名称支持 [iconify](https://icon-sets.iconify.design/) 的图标名称。
|
||||
|
||||
__输入:__
|
||||
|
||||
```md /icon=mdi:balloon/ /icon=mdi:bookmark/
|
||||
::: timeline placement="between"
|
||||
- 节点一 time=2025-03-20 placement=right icon=mdi:balloon
|
||||
正文内容
|
||||
- 节点二 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=important card=true icon="mdi:cake-variant-outline"
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline placement="between"
|
||||
|
||||
- 节点一 time=2025-03-20 placement=right icon=mdi:balloon
|
||||
正文内容
|
||||
- 节点二 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=important card=true icon="mdi:cake-variant-outline"
|
||||
正文内容
|
||||
:::
|
||||
|
||||
### 卡片节点
|
||||
|
||||
卡片节点可以很灵活的进行控制:
|
||||
|
||||
- 在 容器配置中添加 `card` 即可使每个列表项都是卡片节点。
|
||||
- 在列表项首行标题之后,添加 `card=true` 即可为节点设置为卡片节点。
|
||||
- 在列表项首行标题之后,添加 `card=false` 即可为节点设置为非卡片节点。
|
||||
|
||||
卡片节点的样式会受到 `type` 配置的影响。
|
||||
|
||||
::: tip 在列表项首行标题之后添加 `card=true` / `card=false` 可以覆盖容器节点的 `card` 配置
|
||||
:::
|
||||
|
||||
__输入:__
|
||||
|
||||
```md{1} /card=false/
|
||||
::: timeline card
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success card=false
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
__输出:__
|
||||
|
||||
::: timeline card
|
||||
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=success card=false
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
- 节点四 time=2025-01-22 type=important
|
||||
正文内容
|
||||
:::
|
||||
|
||||
## 自定义节点类型
|
||||
|
||||
时间轴的节点类型是通过 CSS Variables 控制的,主题提供了以下的 CSS 变量:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--vp-timeline-c-line: var(--vp-c-border); /* 线条颜色 */
|
||||
--vp-timeline-c-point: var(--vp-c-border); /* 点颜色 */
|
||||
--vp-timeline-c-title: var(--vp-c-text-1); /* 标题文本颜色 */
|
||||
--vp-timeline-c-text: var(--vp-c-text-1); /* 正文文本颜色 */
|
||||
--vp-timeline-c-time: var(--vp-c-text-3); /* 时间文本颜色 */
|
||||
--vp-timeline-c-icon: var(--vp-c-bg); /* 图标颜色 */
|
||||
--vp-timeline-bg-card: var(--vp-c-bg-soft); /* 卡片节点的背景颜色 */
|
||||
}
|
||||
```
|
||||
|
||||
比如主题内置的节点类型 `tip`:
|
||||
|
||||
```css /.tip/
|
||||
.vp-timeline-item.tip {
|
||||
--vp-timeline-c-line: var(--vp-c-tip-1);
|
||||
--vp-timeline-c-point: var(--vp-c-tip-1);
|
||||
--vp-timeline-bg-card: var(--vp-c-tip-soft);
|
||||
}
|
||||
```
|
||||
|
||||
可以在 [自定义样式](../custom/style.md) 中,覆盖内置的类型,或者添加新的类型。
|
||||
|
||||
__示例:__
|
||||
|
||||
```css title=".vuepress/styles/index.css"
|
||||
.vp-timeline-item.your-type {
|
||||
--vp-timeline-c-line: #3cf;
|
||||
--vp-timeline-c-point: #3cf;
|
||||
--vp-timeline-bg-card: rgba(60, 252, 255, 0.314);
|
||||
}
|
||||
```
|
||||
|
||||
```md /type=your-type/
|
||||
::: timeline
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=your-type card=true
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
:::
|
||||
```
|
||||
|
||||
::: timeline
|
||||
|
||||
- 节点一 time=2025-03-20
|
||||
正文内容
|
||||
- 节点二 time=2025-04-20 type=your-type card=true
|
||||
正文内容
|
||||
- 节点三 time=2025-01-22 type=danger
|
||||
正文内容
|
||||
:::
|
||||
|
||||
<style>
|
||||
.vp-timeline-item.your-type {
|
||||
--vp-timeline-c-line: #3cf;
|
||||
--vp-timeline-c-point: #3cf;
|
||||
--vp-timeline-bg-card: rgba(60, 252, 255, 0.314);
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,40 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`timeline > timelinePlugin() > should work 1`] = `
|
||||
"<VPTimeline :card="undefined"><VPTimelineItem :card="undefined">
|
||||
<template #title>这是标题</template>
|
||||
|
||||
这是内容</VPTimelineItem><VPTimelineItem :card="undefined">
|
||||
<template #title>这是标题</template>
|
||||
|
||||
这是内容</VPTimelineItem></VPTimeline><VPTimeline horizontal card line="dashed"><VPTimelineItem time="q1" :card="undefined">
|
||||
<template #title>这是标题</template>
|
||||
|
||||
这是内容
|
||||
<ul>
|
||||
<li>1</li>
|
||||
<li>2</li>
|
||||
<li>3
|
||||
<ul>
|
||||
<li>1.1</li>
|
||||
<li>1.2</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</VPTimelineItem><VPTimelineItem time="q2" color="red">
|
||||
<template #title>这是标题</template>
|
||||
|
||||
这是内容</VPTimelineItem></VPTimeline><VPTimeline :card="undefined" placement="right"><VPTimelineItem type="warning" icon="xxx" card>
|
||||
<template #title>这是标题</template>
|
||||
<template #icon><VPIcon name="xxx"/></template>
|
||||
这是内容</VPTimelineItem><VPTimelineItem type="danger" line="dotted" :card="undefined">
|
||||
<template #title>这是标题</template>
|
||||
|
||||
这是内容</VPTimelineItem></VPTimeline><VPTimeline :card="undefined" placement="between"><VPTimelineItem card placement="right">
|
||||
<template #title>这是标题</template>
|
||||
|
||||
这是内容</VPTimelineItem><VPTimelineItem card placement="left">
|
||||
<template #title>这是标题</template>
|
||||
|
||||
这是内容</VPTimelineItem></VPTimeline>"
|
||||
`;
|
||||
77
plugins/plugin-md-power/__test__/timelinePlugin.spec.ts
Normal file
77
plugins/plugin-md-power/__test__/timelinePlugin.spec.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { extractTimelineAttributes, timelinePlugin } from '../src/node/container/timeline.js'
|
||||
|
||||
describe('timeline > extractTimelineAttributes()', () => {
|
||||
it('should work', () => {
|
||||
const { title, attrs } = extractTimelineAttributes('这是标题 time=Q1')
|
||||
expect(title).toBe('这是标题')
|
||||
expect(attrs).toEqual({ 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' })
|
||||
})
|
||||
|
||||
it('should work with title include space', () => {
|
||||
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' })
|
||||
})
|
||||
|
||||
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' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('timeline > timelinePlugin()', () => {
|
||||
const md = new MarkdownIt()
|
||||
timelinePlugin(md)
|
||||
|
||||
it('should work', () => {
|
||||
const source = `\
|
||||
::: timeline
|
||||
- 这是标题
|
||||
这是内容
|
||||
|
||||
- 这是标题
|
||||
这是内容
|
||||
:::
|
||||
|
||||
::: timeline horizontal line="dashed" card
|
||||
- 这是标题 time=q1
|
||||
这是内容
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 1.1
|
||||
- 1.2
|
||||
|
||||
- 这是标题 time=q2 color=red card=false
|
||||
这是内容
|
||||
:::
|
||||
|
||||
::: timeline placement="right"
|
||||
- 这是标题 icon=xxx card=true type=warning
|
||||
这是内容
|
||||
|
||||
- 这是标题 type=danger line=dotted
|
||||
这是内容
|
||||
:::
|
||||
|
||||
::: timeline placement="between"
|
||||
- 这是标题 card=true placement=right
|
||||
这是内容
|
||||
|
||||
- 这是标题 card=true placement=left
|
||||
这是内容
|
||||
:::
|
||||
`
|
||||
expect(md.render(source)).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
52
plugins/plugin-md-power/src/client/components/VPTimeline.vue
Normal file
52
plugins/plugin-md-power/src/client/components/VPTimeline.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, provide } from 'vue'
|
||||
import { INJECT_TIMELINE_KEY } from '../options.js'
|
||||
|
||||
const props = defineProps<{
|
||||
horizontal?: boolean
|
||||
card?: boolean
|
||||
placement?: 'left' | 'right' | 'between'
|
||||
line?: 'solid' | 'dashed' | 'dotted'
|
||||
}>()
|
||||
|
||||
provide(INJECT_TIMELINE_KEY, computed(() => ({
|
||||
line: props.line || 'solid',
|
||||
card: props.card ?? false,
|
||||
horizontal: props.horizontal ?? false,
|
||||
placement: props.placement || 'left',
|
||||
})))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-timeline" :class="{ horizontal }">
|
||||
<div class="vp-timeline-box">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.vp-timeline {
|
||||
position: relative;
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.vp-timeline.horizontal {
|
||||
padding-bottom: 7px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.vp-timeline-box {
|
||||
display: flex;
|
||||
gap: 24px 36px;
|
||||
}
|
||||
|
||||
.vp-timeline:not(.horizontal) .vp-timeline-box {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.vp-timeline.horizontal .vp-timeline-box {
|
||||
flex-direction: row;
|
||||
width: max-content;
|
||||
}
|
||||
</style>
|
||||
330
plugins/plugin-md-power/src/client/components/VPTimelineItem.vue
Normal file
330
plugins/plugin-md-power/src/client/components/VPTimelineItem.vue
Normal file
@ -0,0 +1,330 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import { computed, inject } from 'vue'
|
||||
import { INJECT_TIMELINE_KEY } from '../options.js'
|
||||
|
||||
const props = defineProps<{
|
||||
time?: string
|
||||
type?: 'info' | 'tip' | 'success' | 'warning' | 'danger' | 'caution' | 'important' | (string & {})
|
||||
card?: boolean
|
||||
line?: 'solid' | 'dashed' | 'dotted'
|
||||
icon?: string
|
||||
color?: string
|
||||
placement?: 'left' | 'right'
|
||||
}>()
|
||||
|
||||
const is639 = useMediaQuery('(max-width: 639px)')
|
||||
|
||||
const defaultOptions = inject<ComputedRef<{
|
||||
line?: 'solid' | 'dashed' | 'dotted'
|
||||
card?: boolean
|
||||
horizontal?: boolean
|
||||
placement?: 'left' | 'right' | 'between'
|
||||
}>>(INJECT_TIMELINE_KEY)
|
||||
|
||||
const timeline = computed(() => {
|
||||
const between = defaultOptions?.value.placement === 'between' && !is639.value
|
||||
const placement = defaultOptions?.value.placement === 'between' ? 'left' : defaultOptions?.value.placement
|
||||
return {
|
||||
time: props.time,
|
||||
type: props.type || 'info',
|
||||
line: props.line || defaultOptions?.value.line || 'solid',
|
||||
icon: props.icon,
|
||||
color: props.color,
|
||||
horizontal: defaultOptions?.value.horizontal ?? false,
|
||||
between: between ? props.placement || 'left' : false,
|
||||
placement: between ? '' : (placement || 'left'),
|
||||
card: props.card ?? defaultOptions?.value.card ?? false,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vp-timeline-item" :class="{
|
||||
card: timeline.card,
|
||||
horizontal: timeline.horizontal,
|
||||
[timeline.type]: true,
|
||||
[`line-${timeline.line}`]: true,
|
||||
[`placement-${timeline.placement}`]: !timeline.horizontal && timeline.placement,
|
||||
between: timeline.between,
|
||||
[`between-${timeline.between}`]: timeline.between,
|
||||
}"
|
||||
:style="timeline.color ? {
|
||||
'--vp-timeline-c-line': timeline.color,
|
||||
'--vp-timeline-c-point': timeline.color,
|
||||
} : null"
|
||||
>
|
||||
<div class="vp-timeline-line" :class="{ 'has-icon': timeline.icon }">
|
||||
<span class="vp-timeline-point">
|
||||
<slot name="icon">
|
||||
<VPIcon v-if="timeline.icon" :name="timeline.icon" />
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="vp-timeline-container">
|
||||
<div class="vp-timeline-content">
|
||||
<p class="vp-timeline-title">
|
||||
<slot name="title" />
|
||||
</p>
|
||||
<slot />
|
||||
</div>
|
||||
<p v-if="timeline.time" class="vp-timeline-time">
|
||||
{{ timeline.time }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root,
|
||||
.vp-timeline-item.info {
|
||||
--vp-timeline-c-line: var(--vp-c-border);
|
||||
--vp-timeline-c-point: var(--vp-c-border);
|
||||
--vp-timeline-c-title: var(--vp-c-text-1);
|
||||
--vp-timeline-c-text: var(--vp-c-text-1);
|
||||
--vp-timeline-c-time: var(--vp-c-text-3);
|
||||
--vp-timeline-c-icon: var(--vp-c-bg);
|
||||
--vp-timeline-bg-card: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.vp-timeline-item.tip {
|
||||
--vp-timeline-c-line: var(--vp-c-tip-1);
|
||||
--vp-timeline-c-point: var(--vp-c-tip-1);
|
||||
--vp-timeline-bg-card: var(--vp-c-tip-soft);
|
||||
}
|
||||
|
||||
.vp-timeline-item.success {
|
||||
--vp-timeline-c-line: var(--vp-c-success-3);
|
||||
--vp-timeline-c-point: var(--vp-c-success-3);
|
||||
--vp-timeline-bg-card: var(--vp-c-success-soft);
|
||||
}
|
||||
|
||||
.vp-timeline-item.warning {
|
||||
--vp-timeline-c-line: var(--vp-c-warning-3);
|
||||
--vp-timeline-c-point: var(--vp-c-warning-3);
|
||||
--vp-timeline-bg-card: var(--vp-c-warning-soft);
|
||||
}
|
||||
|
||||
.vp-timeline-item.danger {
|
||||
--vp-timeline-c-line: var(--vp-c-danger-3);
|
||||
--vp-timeline-c-point: var(--vp-c-danger-3);
|
||||
--vp-timeline-bg-card: var(--vp-c-danger-soft);
|
||||
}
|
||||
|
||||
.vp-timeline-item.caution {
|
||||
--vp-timeline-c-line: var(--vp-c-caution-3);
|
||||
--vp-timeline-c-point: var(--vp-c-caution-3);
|
||||
--vp-timeline-bg-card: var(--vp-c-caution-soft);
|
||||
}
|
||||
|
||||
.vp-timeline-item.important {
|
||||
--vp-timeline-c-line: var(--vp-c-important-3);
|
||||
--vp-timeline-c-point: var(--vp-c-important-3);
|
||||
--vp-timeline-bg-card: var(--vp-c-important-soft);
|
||||
}
|
||||
|
||||
.vp-timeline-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).between {
|
||||
width: calc(50% - 18px);
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal {
|
||||
padding-top: 36px;
|
||||
}
|
||||
|
||||
.vp-timeline-item > .vp-timeline-line {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).placement-left {
|
||||
justify-content: flex-start;
|
||||
padding-left: 36px;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).placement-right,
|
||||
.vp-timeline-item:not(.horizontal).between {
|
||||
justify-content: flex-end;
|
||||
padding-right: 36px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal) > .vp-timeline-line {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal > .vp-timeline-line {
|
||||
top: 12px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).card > .vp-timeline-line {
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).placement-left > .vp-timeline-line {
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).placement-right > .vp-timeline-line,
|
||||
.vp-timeline-item:not(.horizontal).between > .vp-timeline-line {
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.vp-timeline-item > .vp-timeline-line::before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: "";
|
||||
border: none;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal) > .vp-timeline-line::before {
|
||||
top: 10px;
|
||||
bottom: -48px;
|
||||
border-left: 2px solid var(--vp-timeline-c-line);
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal > .vp-timeline-line::before {
|
||||
right: -46px;
|
||||
left: 8px;
|
||||
border-top: 2px solid var(--vp-timeline-c-line);
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal):last-of-type > .vp-timeline-line::before {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal:last-of-type > .vp-timeline-line::before {
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).line-dashed > .vp-timeline-line::before {
|
||||
border-left-style: dashed;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).line-dotted > .vp-timeline-line::before {
|
||||
border-left-style: dotted;
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal.line-dashed > .vp-timeline-line::before {
|
||||
border-top-style: dashed;
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal.line-dotted > .vp-timeline-line::before {
|
||||
border-top-style: dotted;
|
||||
}
|
||||
|
||||
.vp-timeline-item > .vp-timeline-line .vp-timeline-point {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: var(--vp-timeline-c-point);
|
||||
border-radius: 50%;
|
||||
transition: background-color var(--vp-t-color);
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal) > .vp-timeline-line .vp-timeline-point {
|
||||
top: 4px;
|
||||
left: -7px;
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal > .vp-timeline-line .vp-timeline-point {
|
||||
top: -7px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.vp-timeline-item > .vp-timeline-line.has-icon .vp-timeline-point {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal) > .vp-timeline-line.has-icon .vp-timeline-point {
|
||||
top: -1px;
|
||||
left: -11px;
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal > .vp-timeline-line.has-icon .vp-timeline-point {
|
||||
top: -11px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.vp-timeline-item > .vp-timeline-line.has-icon .vp-timeline-point .vp-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
color: var(--vp-timeline-c-icon);
|
||||
}
|
||||
|
||||
.vp-timeline-item .vp-timeline-container {
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--vp-timeline-c-text);
|
||||
transition: color var(--vp-t-color);
|
||||
}
|
||||
|
||||
.vp-timeline-item.horizontal .vp-timeline-container {
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.vp-timeline-item:not(.horizontal).between-right .vp-timeline-container {
|
||||
text-align: left;
|
||||
transform: translateX(calc(100% + 48px));
|
||||
}
|
||||
|
||||
.vp-timeline-item.card .vp-timeline-content {
|
||||
padding: 16px;
|
||||
background-color: var(--vp-timeline-bg-card);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.vp-timeline-item .vp-timeline-content :where(p, ul, ol) {
|
||||
margin: 8px 0;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.vp-doc .vp-timeline-item .vp-timeline-content div[class*="language-"] {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.vp-timeline-item .vp-timeline-content li + li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.vp-timeline-item .vp-timeline-content .vp-timeline-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
color: var(--vp-timeline-c-title);
|
||||
transition: color var(--vp-t-color);
|
||||
}
|
||||
|
||||
.vp-timeline-item .vp-timeline-content > .vp-timeline-title + * {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.vp-timeline-item .vp-timeline-content > :last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.vp-timeline-item .vp-timeline-time {
|
||||
margin: 4px 0 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-timeline-c-time);
|
||||
transition: color var(--vp-t-color);
|
||||
}
|
||||
</style>
|
||||
@ -26,3 +26,7 @@ if (installed.hlsjs) {
|
||||
if (installed.mpegtsjs) {
|
||||
ART_PLAYER_SUPPORTED_VIDEO_TYPES.push('ts', 'flv')
|
||||
}
|
||||
|
||||
export const INJECT_TIMELINE_KEY = Symbol(
|
||||
__VUEPRESS_DEV__ ? 'timeline' : '',
|
||||
)
|
||||
|
||||
@ -11,6 +11,7 @@ import { langReplPlugin } from './langRepl.js'
|
||||
import { npmToPlugins } from './npmTo.js'
|
||||
import { stepsPlugin } from './steps.js'
|
||||
import { tabs } from './tabs.js'
|
||||
import { timelinePlugin } from './timeline.js'
|
||||
|
||||
export async function containerPlugin(
|
||||
app: App,
|
||||
@ -46,4 +47,7 @@ export async function containerPlugin(
|
||||
// ::: file-tree
|
||||
fileTreePlugin(md, isPlainObject(options.fileTree) ? options.fileTree : {})
|
||||
}
|
||||
|
||||
if (options.timeline)
|
||||
timelinePlugin(md)
|
||||
}
|
||||
|
||||
173
plugins/plugin-md-power/src/node/container/timeline.ts
Normal file
173
plugins/plugin-md-power/src/node/container/timeline.ts
Normal file
@ -0,0 +1,173 @@
|
||||
/**
|
||||
* ::: 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"
|
||||
* :::
|
||||
*/
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
import { resolveAttrs } from '.././utils/resolveAttrs.js'
|
||||
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
export interface TimelineAttrs {
|
||||
horizontal?: boolean
|
||||
card?: boolean
|
||||
placement?: string
|
||||
line?: string
|
||||
}
|
||||
|
||||
export interface TimelineItemAttrs {
|
||||
time?: string
|
||||
type?: string
|
||||
icon?: string
|
||||
color?: string
|
||||
line?: string
|
||||
card?: string
|
||||
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 = /(?<quote>["'])(.*?)(\k<quote>)/
|
||||
|
||||
export function timelinePlugin(md: Markdown) {
|
||||
createContainerPlugin(md, 'timeline', {
|
||||
before(info, tokens, index) {
|
||||
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 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<TimelineAttrs>(info)
|
||||
const { horizontal, card, placement, line } = attrs
|
||||
return `<VPTimeline${
|
||||
horizontal ? ' horizontal' : ''
|
||||
}${
|
||||
card ? ' card' : ' :card="undefined"'
|
||||
}${
|
||||
placement ? ` placement="${placement}"` : ''
|
||||
}${
|
||||
line ? ` line="${line}"` : ''
|
||||
}>`
|
||||
},
|
||||
after: () => '</VPTimeline>',
|
||||
})
|
||||
|
||||
md.renderer.rules.timeline_item_open = (tokens, idx, _, env) => {
|
||||
const token = tokens[idx]
|
||||
const { title, time, type, icon, color, line, card, placement } = token.meta as TimelineItemMeta
|
||||
return `<VPTimelineItem${
|
||||
time ? ` time="${time}"` : ''
|
||||
}${
|
||||
type ? ` type="${type}"` : ''
|
||||
}${
|
||||
color ? ` color="${color}"` : ''
|
||||
}${
|
||||
line ? ` line="${line}"` : ''
|
||||
}${icon ? ` icon="${icon}"` : ''}${
|
||||
card === 'true' ? ' card' : card === 'false' ? '' : ' :card="undefined"'
|
||||
}${
|
||||
placement ? ` placement="${placement}"` : ''
|
||||
}>
|
||||
<template #title>${md.renderInline(title, cleanMarkdownEnv(env))}</template>
|
||||
${icon ? `<template #icon><VPIcon name="${icon}"/></template>` : ''}`
|
||||
}
|
||||
|
||||
md.renderer.rules.timeline_item_close = () => '</VPTimelineItem>'
|
||||
}
|
||||
|
||||
// 核心属性扫描器
|
||||
export function extractTimelineAttributes(rawText: string): {
|
||||
title: string
|
||||
attrs: TimelineItemAttrs
|
||||
} {
|
||||
const attrKeys = ['time', 'type', 'icon', 'line', 'color', 'card', 'placement'] as const
|
||||
const attrs: Partial<TimelineItemAttrs> = {}
|
||||
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
|
||||
buffer = buffer.slice(keyEnd)
|
||||
|
||||
// 提取属性值 (到下一个属性或行尾)
|
||||
let valueEnd = buffer.search(RE_SEARCH_KEY)
|
||||
/* istanbul ignore if -- @preserve */
|
||||
if (valueEnd === -1)
|
||||
valueEnd = buffer.length
|
||||
const value = buffer.slice(0, valueEnd).trim()
|
||||
// 存储属性
|
||||
attrs[matchedKey as keyof TimelineItemAttrs] = value.replace(RE_CLEAN_VALUE, '$2')
|
||||
|
||||
// 跳过已处理的值
|
||||
buffer = buffer.slice(valueEnd)
|
||||
}
|
||||
|
||||
return {
|
||||
title: titleSegments.join(' ').trim(),
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
@ -102,6 +102,13 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp
|
||||
enhances.add(`app.component('Abbreviation', Abbreviation)`)
|
||||
}
|
||||
|
||||
if (options.timeline) {
|
||||
imports.add(`import VPTimeline from '${CLIENT_FOLDER}components/VPTimeline.vue'`)
|
||||
imports.add(`import VPTimelineItem from '${CLIENT_FOLDER}components/VPTimelineItem.vue'`)
|
||||
enhances.add(`app.component('VPTimeline', VPTimeline)`)
|
||||
enhances.add(`app.component('VPTimelineItem', VPTimelineItem)`)
|
||||
}
|
||||
|
||||
return app.writeTemp(
|
||||
'md-power/config.js',
|
||||
`\
|
||||
|
||||
@ -56,6 +56,20 @@ export interface MarkdownPowerPluginOptions {
|
||||
*/
|
||||
plot?: boolean | PlotOptions
|
||||
|
||||
/**
|
||||
* 是否启用 timeline 语法
|
||||
*
|
||||
* ```md
|
||||
* ::: timeline
|
||||
* - title time="Q1" icon="ri:clockwise-line" line="dashed" type="warning" color="red"
|
||||
* xxx
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
timeline?: boolean
|
||||
|
||||
// video embed
|
||||
/**
|
||||
* 是否启用 bilibili 视频嵌入
|
||||
|
||||
@ -57,6 +57,7 @@ export const MARKDOWN_POWER_FIELDS: (keyof MarkdownPowerPluginOptions)[] = [
|
||||
'plot',
|
||||
'repl',
|
||||
'replit',
|
||||
'timeline',
|
||||
'youtube',
|
||||
]
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user