2026-03-21 18:30:40 +08:00
|
|
|
|
import type { MarkedExtension, Tokens } from "marked";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将表格数据转换为 CSV 格式字符串
|
|
|
|
|
|
* @param headers 表头数组
|
|
|
|
|
|
* @param rows 表格数据行
|
|
|
|
|
|
* @returns CSV 格式字符串
|
|
|
|
|
|
*/
|
|
|
|
|
|
function tableToCSV(headers: string[], rows: string[][]): string {
|
|
|
|
|
|
const escapeCell = (cell: string) => {
|
|
|
|
|
|
// 如果单元格包含逗号、换行或引号,需要转义
|
2026-03-22 00:27:59 +08:00
|
|
|
|
if (cell.includes(',') || cell.includes('\n') || cell.includes('"') || cell.includes("#")) {
|
2026-03-21 18:30:40 +08:00
|
|
|
|
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) {
|
|
|
|
|
|
// 检查表头是否包含 md-table-label
|
|
|
|
|
|
const header = token.header;
|
2026-03-21 18:58:42 +08:00
|
|
|
|
let roll = '';
|
|
|
|
|
|
const labelIndex = header.findIndex(cell => {
|
2026-03-23 18:57:55 +08:00
|
|
|
|
if(cell.text === 'md-roll-label' || cell.text.match(/(\d+)?d\d+/)){
|
2026-03-21 18:58:42 +08:00
|
|
|
|
roll = ' roll=true';
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}else if(cell.text === 'md-remix-label'){
|
|
|
|
|
|
roll = ' roll=true remix=true';
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-23 18:57:55 +08:00
|
|
|
|
return cell.text === 'md-table-label' || cell.text === 'label';
|
2026-03-21 18:58:42 +08:00
|
|
|
|
});
|
2026-03-21 18:30:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 默认表格渲染 - 使用 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 列的表头
|
2026-03-21 19:09:54 +08:00
|
|
|
|
const bodyColumns = headers
|
|
|
|
|
|
.filter(cell => cell !== 'label');
|
2026-03-21 18:30:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 构建 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 数据
|
2026-03-21 18:58:42 +08:00
|
|
|
|
return `<md-table ${roll}>${csvData}</md-table>\n`;
|
2026-03-21 18:30:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|