Files
ttrpg-tools/src/markdown/table.ts
T
hyper 750af4a50c refactor(markdown): Simplify table header detection logic
Remove unused `isSparkTableHeader` import and simplify condition
since spark tables are handled upstream by `coerceSparkTables`.
2026-08-07 13:21:11 +08:00

85 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { MarkedExtension, Tokens } from "marked";
/**
* 将表格数据转换为 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('"') ||
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");
}
export default function markedTable(): MarkedExtension {
return {
renderer: {
table(token: Tokens.Table) {
const header = token.header;
let roll = "";
let remix = "";
// Spark tables (dice-formula first column) are handled upstream by
// `coerceSparkTables` in the content registry — they're rewritten to
// `:md-table` directives before rendering. This renderer only handles
// label-based tables (md-table-label / md-roll-label / md-remix-label).
const labelIndex = header.findIndex((cell) => {
if (cell.text === "md-roll-label") {
roll = " roll=true";
return true;
} else if (cell.text === "md-remix-label") {
roll = " roll=true remix=true";
return true;
}
return cell.text === "md-table-label" || cell.text === "label";
});
// 默认表格渲染 - 使用 marked 默认行为
if (labelIndex === -1) return false;
const headers = token.header.map((cell) => cell.text);
headers[labelIndex] = "label";
const rows = token.rows.map((row) => row.map((cell) => cell.text));
if (header.findIndex((header) => header.text === "body") < 0) {
// 收集所有非 label 列的表头
const bodyColumns = headers.filter((cell) => cell !== "label");
// 构建 body 列的模板:**列名**{{列名}}\n\n
const bodyTemplate = bodyColumns
.map((col) => `**${col}**{{${col}}}`)
.join("\n\n");
headers.push("body");
rows.forEach((row) => {
row.push(bodyTemplate);
});
}
// 生成 CSV 数据
const csvData = tableToCSV(headers, rows);
// 渲染为 md-table 组件,内联 CSV 数据
// data-spark attribute is injected by the CLI directive scanner,
// not computed here.
return `<md-table ${roll}${remix}>${csvData}</md-table>\n`;
},
},
};
}