From 371834640b8be4747116fe05dfae6c29f81be19f Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sat, 26 Jul 2025 09:11:51 +0800 Subject: [PATCH] feat(plugin-md-power): add `table` container, close #652 (#655) --- cli/templates/.vuepress/config.ts.handlebars | 1 + docs/.vuepress/notes/zh/theme-guide.ts | 1 + docs/.vuepress/theme.ts | 1 + docs/notes/theme/guide/markdown/table.md | 151 ++++++++++++++++++ .../src/client/components/VPTable.vue | 146 +++++++++++++++++ .../src/node/container/index.ts | 4 + .../src/node/container/table.ts | 29 ++++ .../src/node/prepareConfigFile.ts | 5 + plugins/plugin-md-power/src/shared/plugin.ts | 10 ++ plugins/plugin-md-power/src/shared/table.ts | 31 ++++ theme/src/node/detector/fields.ts | 1 + 11 files changed, 380 insertions(+) create mode 100644 docs/notes/theme/guide/markdown/table.md create mode 100644 plugins/plugin-md-power/src/client/components/VPTable.vue create mode 100644 plugins/plugin-md-power/src/node/container/table.ts create mode 100644 plugins/plugin-md-power/src/shared/table.ts diff --git a/cli/templates/.vuepress/config.ts.handlebars b/cli/templates/.vuepress/config.ts.handlebars index 10dc23de..d7946e49 100644 --- a/cli/templates/.vuepress/config.ts.handlebars +++ b/cli/templates/.vuepress/config.ts.handlebars @@ -130,6 +130,7 @@ export default defineUserConfig({ // artPlayer: true, // 启用嵌入 artPlayer 本地视频 语法 @[artPlayer](url) // audioReader: true, // 启用嵌入音频朗读功能 语法 @[audioReader](url) // icon: { provider: 'iconify' }, // 启用内置图标语法 ::icon-name:: + // table: true, // 启用表格增强容器语法 ::: table // codepen: true, // 启用嵌入 codepen 语法 @[codepen](user/slash) // replit: true, // 启用嵌入 replit 语法 @[replit](user/repl-name) // codeSandbox: true, // 启用嵌入 codeSandbox 语法 @[codeSandbox](id) diff --git a/docs/.vuepress/notes/zh/theme-guide.ts b/docs/.vuepress/notes/zh/theme-guide.ts index 55e9d0d8..49ecbe52 100644 --- a/docs/.vuepress/notes/zh/theme-guide.ts +++ b/docs/.vuepress/notes/zh/theme-guide.ts @@ -35,6 +35,7 @@ export const themeGuide: ThemeNote = defineNoteConfig({ items: [ 'basic', 'extensions', + 'table', 'icons', 'mark', 'plot', diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts index 4aef1ed2..9814e998 100644 --- a/docs/.vuepress/theme.ts +++ b/docs/.vuepress/theme.ts @@ -27,6 +27,7 @@ export const theme: Theme = plumeTheme({ annotation: true, abbr: true, + table: true, timeline: true, collapse: true, chat: true, diff --git a/docs/notes/theme/guide/markdown/table.md b/docs/notes/theme/guide/markdown/table.md new file mode 100644 index 00000000..a0ec9aa3 --- /dev/null +++ b/docs/notes/theme/guide/markdown/table.md @@ -0,0 +1,151 @@ +--- +title: table 增强 +icon: mdi:table-plus +createTime: 2025/07/25 16:57:42 +permalink: /guide/markdown/table/ +badge: 新 +--- + +## 概述 + +markdown 默认的表格功能相对比较简单,但在实际使用场景中,常常需要在表格中添加一些额外的信息,比如表格的标题; +或者额外的功能,如复制表格的内容等。 + +在不破坏表格语法的前提下,主题提供了 `::: table` 容器,可以方便的对表格进行扩展。 + +::: tip 表格增强容器在持续开发中,如果有其他的功能建议请在 [Issue](https://github.com/pengzhanbo/vuepress-theme-plume/issues) 中反馈。 +::: + +## 配置 + +该功能默认不启用,您需要在 `theme` 配置中启用它。 + +```ts title=".vuepress/config.ts" +export default defineUserConfig({ + theme: plumeTheme({ + markdown: { + // table: true, // 启用默认功能 + table: { + // 表格默认对齐方式 'left' | 'center' | 'right' + align: 'left', + // 表格宽度是否为最大内容宽度 + // 行内元素不再自动换行,超出容器宽度时表格显示滚动条 + maxContent: false, + /** + * 复制为 html/markdown + * true 相当于 `all`,相当于同时启用 html 和 markdown + */ + copy: true, // true | 'all' | 'html' | 'md' + } + }, + }) +}) +``` + +## 语法 + +直接将 表格 包裹在 `:::table` 中即可。 + +```md +:::table title="标题" align="center" max-content copy="all" +| xx | xx | xx | +| -- | -- | -- | +| xx | xx | xx | +::: +``` + +### Props + +:::: field-group + +::: field name="title" type="string" optional +表格标题,显示在表格的下方 +::: + +::: field name="align" type="'left' | 'center' | 'right'" optional default="'left'" +表格对齐方式 +::: + +::: field name="copy" type="boolean | 'all' | 'html' | 'md'" optional default="true" +在表格的右上角显示复制按钮,可以复制为 html / markdown + +- `true` 等同于 `all` +- `false` 不显示复制按钮 +- `all` 同时启用 `html` 和 `md` +- `html` 启用复制为 html +- `md` 启用复制为 markdown +::: + +::: field name="maxContent" type="boolean" optional default="false" +行内元素不再自动换行,超出容器宽度时表格显示滚动条 +::: +:::: + +## 示例 + +**输入:** + +```md +::: table title="这是表格标题" +| Header 1 | Header 2 | Header 3 | +|----------|----------|----------| +| Cell 1 | Cell 2 | Cell 3 | +| Row 2 | Data | Info | +::: +``` + +**输出:** + +::: table title="这是表格标题" + +| Header 1 | Header 2 | Header 3 | +|----------|----------|----------| +| Cell 1 | Cell 2 | Cell 3 | +| Row 2 | Data | Info | + +::: + +**输入:** + +```md +::: table title="这是表格标题" align="center" +| Header 1 | Header 2 | Header 3 | +|----------|----------|----------| +| Cell 1 | Cell 2 | Cell 3 | +| Row 2 | Data | Info | +::: +``` + +**输出:** + +::: table title="这是表格标题" align="center" + +| Header 1 | Header 2 | Header 3 | +|----------|----------|----------| +| Cell 1 | Cell 2 | Cell 3 | +| Row 2 | Data | Info | + +::: + +**输入:** + +```md +:::table title="这是表格标题" max-content + +| ID | Description | Status | +|----|-----------------------------------------------------------------------------|--------------| +| 1 | This is an extremely long description that should trigger text wrapping in most table implementations. | In Progress | +| 2 | Short text | ✅ Completed | +::: +``` + +**输出:** + +:::table title="这是表格标题" max-content + +| ID | Description | Status | +|----|-----------------------------------------------------------------------------|--------------| +| 1 | This is an extremely long description that should trigger text wrapping in most table implementations. | In Progress | +| 2 | Short text | ✅ Completed | + +::: diff --git a/plugins/plugin-md-power/src/client/components/VPTable.vue b/plugins/plugin-md-power/src/client/components/VPTable.vue new file mode 100644 index 00000000..714dada6 --- /dev/null +++ b/plugins/plugin-md-power/src/client/components/VPTable.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/plugins/plugin-md-power/src/node/container/index.ts b/plugins/plugin-md-power/src/node/container/index.ts index 51b175d1..86f90f1d 100644 --- a/plugins/plugin-md-power/src/node/container/index.ts +++ b/plugins/plugin-md-power/src/node/container/index.ts @@ -14,6 +14,7 @@ import { fileTreePlugin } from './fileTree.js' import { langReplPlugin } from './langRepl.js' import { npmToPlugins } from './npmTo.js' import { stepsPlugin } from './steps.js' +import { tablePlugin } from './table.js' import { tabs } from './tabs.js' import { timelinePlugin } from './timeline.js' @@ -67,4 +68,7 @@ export async function containerPlugin( if (options.field) fieldPlugin(md) + + if (options.table) + tablePlugin(md, isPlainObject(options.table) ? options.table : {}) } diff --git a/plugins/plugin-md-power/src/node/container/table.ts b/plugins/plugin-md-power/src/node/container/table.ts new file mode 100644 index 00000000..4fd93586 --- /dev/null +++ b/plugins/plugin-md-power/src/node/container/table.ts @@ -0,0 +1,29 @@ +import type { Markdown } from 'vuepress/markdown' +import type { TableContainerOptions } from '../../shared/table.js' +import { encodeData } from '@vuepress/helper' +import { stringifyAttrs } from '../utils/stringifyAttrs.js' +import { createContainerSyntaxPlugin } from './createContainer.js' + +export interface TableContainerAttrs extends TableContainerOptions { + title?: string +} + +/** + * 在不破坏表格语法的前提下,通过容器语法将表格包裹起来,为表格提供增强功能 + */ +export function tablePlugin(md: Markdown, options: TableContainerOptions = {}): void { + createContainerSyntaxPlugin(md, 'table', (tokens, index, _, env) => { + const meta = { copy: true, maxContent: false, ...options, ...tokens[index].meta } as TableContainerAttrs & { markdown?: string } + const content = tokens[index].content + + if (meta.copy) { + meta.copy = meta.copy === true ? 'all' : meta.copy + + if (meta.copy === 'all' || meta.copy === 'md') { + meta.markdown = encodeData(content.trim()) + } + } + + return `${md.render(content, env)}` + }) +} diff --git a/plugins/plugin-md-power/src/node/prepareConfigFile.ts b/plugins/plugin-md-power/src/node/prepareConfigFile.ts index 7e958c1b..b31b9367 100644 --- a/plugins/plugin-md-power/src/node/prepareConfigFile.ts +++ b/plugins/plugin-md-power/src/node/prepareConfigFile.ts @@ -126,6 +126,11 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp enhances.add(`app.component('VPField', VPField)`) } + if (options.table) { + imports.add(`import VPTable from '${CLIENT_FOLDER}components/VPTable.vue'`) + enhances.add(`app.component('VPTable', VPTable)`) + } + const setupIcon = prepareIcon(imports, options.icon) return app.writeTemp( diff --git a/plugins/plugin-md-power/src/shared/plugin.ts b/plugins/plugin-md-power/src/shared/plugin.ts index 0fb76000..56c9fbf1 100644 --- a/plugins/plugin-md-power/src/shared/plugin.ts +++ b/plugins/plugin-md-power/src/shared/plugin.ts @@ -7,6 +7,7 @@ import type { NpmToOptions } from './npmTo.js' import type { PDFOptions } from './pdf.js' import type { PlotOptions } from './plot.js' import type { ReplOptions } from './repl.js' +import type { TableContainerOptions } from './table.js' export interface MarkdownPowerPluginOptions { /** @@ -240,6 +241,15 @@ export interface MarkdownPowerPluginOptions { */ caniuse?: boolean | CanIUseOptions + /** + * 是否启用 table 容器语法,为表格提供增强功能 + * + * - `copy`: 是否启用复制功能,支持复制为 html 格式 和 markdown 格式 + * + * @default false + */ + table?: boolean | TableContainerOptions + // enhance /** * 是否启用 自动填充 图片宽高属性 diff --git a/plugins/plugin-md-power/src/shared/table.ts b/plugins/plugin-md-power/src/shared/table.ts new file mode 100644 index 00000000..5178f9d7 --- /dev/null +++ b/plugins/plugin-md-power/src/shared/table.ts @@ -0,0 +1,31 @@ +export interface TableContainerOptions { + /** + * 表格对齐方式 + * - 'left': 左对齐 + * - 'center': 居中对齐 + * - 'right': 右对齐 + * + * @default 'left' + */ + align?: 'left' | 'center' | 'right' + /** + * 表格复制 + * - true: 等同于 `all`,支持复制为 html 和 markdown 格式 + * - 'all': 支持复制为 html 和 markdown 格式 + * - 'html': 只支持复制为 html 格式 + * - 'md': 只支持复制为 markdown 格式 + * - `false`: 禁用复制 + * + * @default true + */ + copy?: boolean | 'all' | 'html' | 'md' + + /** + * 表格宽度是否为最大内容宽度 + * + * 最大内容宽度时,行内元素不再自动换行,超出容器宽度时表格显示滚动条 + * + * @default false + */ + maxContent?: boolean +} diff --git a/theme/src/node/detector/fields.ts b/theme/src/node/detector/fields.ts index 2940b929..2ad7c97c 100644 --- a/theme/src/node/detector/fields.ts +++ b/theme/src/node/detector/fields.ts @@ -57,6 +57,7 @@ export const MARKDOWN_POWER_FIELDS: (keyof MarkdownPowerPluginOptions)[] = [ 'plot', 'repl', 'replit', + 'table', 'timeline', 'collapse', 'chat',