mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
parent
b120633453
commit
371834640b
@ -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)
|
||||
|
||||
@ -35,6 +35,7 @@ export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
items: [
|
||||
'basic',
|
||||
'extensions',
|
||||
'table',
|
||||
'icons',
|
||||
'mark',
|
||||
'plot',
|
||||
|
||||
@ -27,6 +27,7 @@ export const theme: Theme = plumeTheme({
|
||||
|
||||
annotation: true,
|
||||
abbr: true,
|
||||
table: true,
|
||||
timeline: true,
|
||||
collapse: true,
|
||||
chat: true,
|
||||
|
||||
151
docs/notes/theme/guide/markdown/table.md
Normal file
151
docs/notes/theme/guide/markdown/table.md
Normal file
@ -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 |
|
||||
|
||||
:::
|
||||
146
plugins/plugin-md-power/src/client/components/VPTable.vue
Normal file
146
plugins/plugin-md-power/src/client/components/VPTable.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<script setup lang="ts">
|
||||
import { decodeData } from '@vuepress/helper/client'
|
||||
import { useClipboard, useToggle } from '@vueuse/core'
|
||||
import { computed, useTemplateRef } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
/** 表格标题 */
|
||||
title?: string
|
||||
/** 对其方式 */
|
||||
align?: 'left' | 'center' | 'right'
|
||||
/** 复制为 html/markdown */
|
||||
copy?: false | 'all' | 'html' | 'md'
|
||||
/** 最大化内容 */
|
||||
maxContent?: boolean
|
||||
/** @internal */
|
||||
markdown?: string
|
||||
}>()
|
||||
|
||||
const tableEl = useTemplateRef('table')
|
||||
const rawContent = computed(() => props.markdown ? decodeData(props.markdown) : '')
|
||||
|
||||
const [isHTMLCopied, toggleHTMLCopy] = useToggle()
|
||||
const [isMDCopied, toggleMDCopy] = useToggle()
|
||||
const { copy: copyTable } = useClipboard()
|
||||
|
||||
function onCopy(type: 'html' | 'md') {
|
||||
copyTable(type === 'md' ? rawContent.value : tableEl.value?.innerHTML || '')
|
||||
type === 'html' ? toggleHTMLCopy(true) : toggleMDCopy(true)
|
||||
setTimeout(() => {
|
||||
type === 'html' ? toggleHTMLCopy(false) : toggleMDCopy(false)
|
||||
}, 1500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-table" :class="{ [align || 'left']: true }">
|
||||
<div class="table-container">
|
||||
<div class="table-content">
|
||||
<div v-if="copy" class="table-toolbar">
|
||||
<button
|
||||
v-if="copy === 'all' || copy === 'html'"
|
||||
type="button"
|
||||
aria-label="Copy Table as HTML"
|
||||
@click="onCopy('html')"
|
||||
>
|
||||
<span :class="isHTMLCopied ? 'vpi-table-copied' : 'vpi-table-copy'" />
|
||||
<span>HTML</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="copy === 'all' || copy === 'md'"
|
||||
type="button"
|
||||
aria-label="Copy Table as Markdown"
|
||||
@click="onCopy('md')"
|
||||
>
|
||||
<span :class="isMDCopied ? 'vpi-table-copied' : 'vpi-table-copy'" />
|
||||
<span>Markdown</span>
|
||||
</button>
|
||||
</div>
|
||||
<div ref="table" :class="{ 'max-content': maxContent }">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="title" class="table-title">
|
||||
{{ title }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.vp-table {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.vp-table.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.vp-table.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.vp-table.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.vp-table .table-container,
|
||||
.vp-table .table-content {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.vp-table .table-content {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.vp-table .table-title {
|
||||
margin: 8px auto;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vp-table .table-container table {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vp-table .table-toolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.vp-table .table-toolbar button {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-3);
|
||||
cursor: pointer;
|
||||
transition: var(--vp-t-color);
|
||||
transition-property: color;
|
||||
}
|
||||
|
||||
.vp-table .table-toolbar button:hover {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.vpi-table-copy {
|
||||
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M20.829 12.861c.171-.413.171-.938.171-1.986s0-1.573-.171-1.986a2.25 2.25 0 0 0-1.218-1.218c-.413-.171-.938-.171-1.986-.171H11.1c-1.26 0-1.89 0-2.371.245a2.25 2.25 0 0 0-.984.984C7.5 9.209 7.5 9.839 7.5 11.1v6.525c0 1.048 0 1.573.171 1.986c.229.551.667.99 1.218 1.218c.413.171.938.171 1.986.171s1.573 0 1.986-.171m7.968-7.968a2.25 2.25 0 0 1-1.218 1.218c-.413.171-.938.171-1.986.171s-1.573 0-1.986.171a2.25 2.25 0 0 0-1.218 1.218c-.171.413-.171.938-.171 1.986s0 1.573-.171 1.986a2.25 2.25 0 0 1-1.218 1.218m7.968-7.968a11.68 11.68 0 0 1-7.75 7.9l-.218.068M16.5 7.5v-.9c0-1.26 0-1.89-.245-2.371a2.25 2.25 0 0 0-.983-.984C14.79 3 14.16 3 12.9 3H6.6c-1.26 0-1.89 0-2.371.245a2.25 2.25 0 0 0-.984.984C3 4.709 3 5.339 3 6.6v6.3c0 1.26 0 1.89.245 2.371c.216.424.56.768.984.984c.48.245 1.111.245 2.372.245H7.5'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.vpi-table-copied {
|
||||
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='m9 20.42l-6.21-6.21l2.83-2.83L9 14.77l9.88-9.89l2.83 2.83z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.vp-table .table-content .max-content {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.vp-table .table-content .max-content table {
|
||||
width: max-content;
|
||||
}
|
||||
</style>
|
||||
@ -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 : {})
|
||||
}
|
||||
|
||||
29
plugins/plugin-md-power/src/node/container/table.ts
Normal file
29
plugins/plugin-md-power/src/node/container/table.ts
Normal file
@ -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 `<VPTable ${stringifyAttrs(meta)}>${md.render(content, env)}</VPTable>`
|
||||
})
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
/**
|
||||
* 是否启用 自动填充 图片宽高属性
|
||||
|
||||
31
plugins/plugin-md-power/src/shared/table.ts
Normal file
31
plugins/plugin-md-power/src/shared/table.ts
Normal file
@ -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
|
||||
}
|
||||
@ -57,6 +57,7 @@ export const MARKDOWN_POWER_FIELDS: (keyof MarkdownPowerPluginOptions)[] = [
|
||||
'plot',
|
||||
'repl',
|
||||
'replit',
|
||||
'table',
|
||||
'timeline',
|
||||
'collapse',
|
||||
'chat',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user