feat: auto-convert tables

This commit is contained in:
hypercross 2026-03-21 17:50:47 +08:00
parent 6b411c2f24
commit 6f57970711
1 changed files with 58 additions and 6 deletions

View File

@ -1,4 +1,4 @@
import { Marked } from 'marked'; import { Marked, type MarkedExtension, type Tokens } from 'marked';
import {createDirectives, presetDirectiveConfigs} from 'marked-directive'; import {createDirectives, presetDirectiveConfigs} from 'marked-directive';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import markedAlert from "marked-alert"; 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 来支持指令语法 // 使用 marked-directive 来支持指令语法
const marked = new Marked() const marked = new Marked()
.use(gfmHeadingId()) .use(gfmHeadingId())
@ -56,20 +78,20 @@ const marked = new Marked()
if (match) { if (match) {
const yamlContent = match[1]?.trim() || ''; const yamlContent = match[1]?.trim() || '';
const props = yaml.load(yamlContent) as Record<string, unknown> || {}; const props = yaml.load(yamlContent) as Record<string, unknown> || {};
// 提取 tag 名称,默认为 tag-unknown // 提取 tag 名称,默认为 tag-unknown
const tagName = (props.tag as string) || 'tag-unknown'; const tagName = (props.tag as string) || 'tag-unknown';
// 移除 tag 属性,剩下的作为 HTML 属性 // 移除 tag 属性,剩下的作为 HTML 属性
const { tag, ...rest } = props; const { tag, ...rest } = props;
// 提取 innerText 内容(如果有 body 字段) // 提取 innerText 内容(如果有 body 字段)
let content = ''; let content = '';
if ('body' in rest) { if ('body' in rest) {
content = String(rest.body || ''); content = String(rest.body || '');
delete (rest as Record<string, unknown>).body; delete (rest as Record<string, unknown>).body;
} }
// 构建属性字符串 // 构建属性字符串
const propsStr = Object.entries(rest) const propsStr = Object.entries(rest)
.map(([key, value]) => { .map(([key, value]) => {
@ -81,7 +103,7 @@ const marked = new Marked()
return `${key}="${strValue}"`; return `${key}="${strValue}"`;
}) })
.join(' '); .join(' ');
return { return {
type: 'code-block-yaml-tag', type: 'code-block-yaml-tag',
raw: match[0], 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 `<md-table>${csvData}</md-table>\n`;
}
// 默认表格渲染 - 使用 marked 默认行为
return false;
}
}
} as MarkedExtension);
export function parseMarkdown(content: string, iconPrefix?: string): string { export function parseMarkdown(content: string, iconPrefix?: string): string {
using prefix = overrideIconPrefix(iconPrefix); using prefix = overrideIconPrefix(iconPrefix);
return marked.parse(content.trimStart()) as string; return marked.parse(content.trimStart()) as string;