From 6f57970711227281bd29fbd78d8bb86ed4fa5ddc Mon Sep 17 00:00:00 2001 From: hypercross Date: Sat, 21 Mar 2026 17:50:47 +0800 Subject: [PATCH] feat: auto-convert tables --- src/markdown/index.ts | 64 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/src/markdown/index.ts b/src/markdown/index.ts index 53c2398..8072063 100644 --- a/src/markdown/index.ts +++ b/src/markdown/index.ts @@ -1,4 +1,4 @@ -import { Marked } from 'marked'; +import { Marked, type MarkedExtension, type Tokens } from 'marked'; import {createDirectives, presetDirectiveConfigs} from 'marked-directive'; import yaml from 'js-yaml'; import markedAlert from "marked-alert"; @@ -14,6 +14,28 @@ function overrideIconPrefix(path?: string){ } } } + +/** + * 将表格数据转换为 CSV 格式字符串 + * @param headers 表头数组 + * @param rows 表格数据行 + * @returns CSV 格式字符串 + */ +function tableToCSV(headers: string[], rows: string[][]): string { + const escapeCell = (cell: string) => { + // 如果单元格包含逗号、换行或引号,需要转义 + if (cell.includes(',') || cell.includes('\n') || cell.includes('"')) { + return `"${cell.replace(/"/g, '""')}"`; + } + return cell; + }; + + const headerLine = headers.map(escapeCell).join(','); + const dataLines = rows.map(row => row.map(escapeCell).join(',')); + + return [headerLine, ...dataLines].join('\n'); +} + // 使用 marked-directive 来支持指令语法 const marked = new Marked() .use(gfmHeadingId()) @@ -56,20 +78,20 @@ const marked = new Marked() if (match) { const yamlContent = match[1]?.trim() || ''; const props = yaml.load(yamlContent) as Record || {}; - + // 提取 tag 名称,默认为 tag-unknown const tagName = (props.tag as string) || 'tag-unknown'; - + // 移除 tag 属性,剩下的作为 HTML 属性 const { tag, ...rest } = props; - + // 提取 innerText 内容(如果有 body 字段) let content = ''; if ('body' in rest) { content = String(rest.body || ''); delete (rest as Record).body; } - + // 构建属性字符串 const propsStr = Object.entries(rest) .map(([key, value]) => { @@ -81,7 +103,7 @@ const marked = new Marked() return `${key}="${strValue}"`; }) .join(' '); - + return { type: 'code-block-yaml-tag', raw: match[0], @@ -99,6 +121,36 @@ const marked = new Marked() }] }); +// 覆盖默认的 table renderer 以支持自动转换 +marked.use({ + renderer: { + table(token: Tokens.Table) { + // 检查表头是否包含 md-table-label + const header = token.header; + const labelIndex = header.findIndex(cell => cell.text === 'md-table-label'); + + if (labelIndex !== -1) { + // 将 md-table-label 列转换为 label + const headers = header.map(cell => cell.text === 'md-table-label' ? 'label' : cell.text); + + // 转换所有行 + const rows = token.rows.map(row => + row.map(cell => cell.text) + ); + + // 生成 CSV 数据 + const csvData = tableToCSV(headers, rows); + + // 渲染为 md-table 组件,内联 CSV 数据 + return `${csvData}\n`; + } + + // 默认表格渲染 - 使用 marked 默认行为 + return false; + } + } +} as MarkedExtension); + export function parseMarkdown(content: string, iconPrefix?: string): string { using prefix = overrideIconPrefix(iconPrefix); return marked.parse(content.trimStart()) as string;