111 lines
3.3 KiB
TypeScript
111 lines
3.3 KiB
TypeScript
import { DocumentAST, DocumentBlock } from '../types/block';
|
|
import { cleanHeadingContent } from './autoNumber';
|
|
|
|
/**
|
|
* blockASTToMarkdown: 将 Block AST 树无损还原为整洁的标准 Markdown 文本
|
|
*/
|
|
export function blockASTToMarkdown(ast: DocumentAST): string {
|
|
const chunks: string[] = [];
|
|
|
|
function serializeBlock(block: DocumentBlock) {
|
|
switch (block.type) {
|
|
case 'heading': {
|
|
const level = Math.max(1, Math.min(6, block.level || 1));
|
|
const prefix = '#'.repeat(level);
|
|
const titleText = cleanHeadingContent(block.content);
|
|
const numPrefix = block.numbering ? `${block.numbering} ` : '';
|
|
chunks.push(`${prefix} ${numPrefix}${titleText}\n`);
|
|
break;
|
|
}
|
|
case 'paragraph': {
|
|
if (block.content) {
|
|
chunks.push(`${block.content}\n`);
|
|
}
|
|
break;
|
|
}
|
|
case 'list': {
|
|
const isOrdered = block.properties?.isOrdered ?? true;
|
|
const items =
|
|
block.properties?.listItems ||
|
|
block.content
|
|
.split('\n')
|
|
.map((line) => line.replace(/^\s*(\d+\.|[-*+])\s+/, '').trim())
|
|
.filter(Boolean);
|
|
const start = block.properties?.startNumber || 1;
|
|
|
|
items.forEach((item, idx) => {
|
|
const clean = item.trim();
|
|
if (!clean) return;
|
|
if (isOrdered) {
|
|
chunks.push(`${start + idx}. ${clean}\n`);
|
|
} else {
|
|
chunks.push(`- ${clean}\n`);
|
|
}
|
|
});
|
|
chunks.push('\n');
|
|
break;
|
|
}
|
|
case 'divider': {
|
|
chunks.push('---\n');
|
|
break;
|
|
}
|
|
case 'callout': {
|
|
const typeTag = block.properties?.calloutType === 'regulatory' ? '合规依据' : '提示';
|
|
chunks.push(`> **[${typeTag}]** ${block.content}\n`);
|
|
break;
|
|
}
|
|
case 'mermaid': {
|
|
chunks.push('```mermaid\n' + block.content.trim() + '\n```\n');
|
|
break;
|
|
}
|
|
case 'math': {
|
|
chunks.push('$$\n' + block.content.trim() + '\n$$\n');
|
|
break;
|
|
}
|
|
case 'table': {
|
|
const data = block.properties?.tableData;
|
|
if (data && data.length > 0) {
|
|
const colCount = Math.max(...data.map((r) => r.length));
|
|
const lines: string[] = [];
|
|
|
|
// 表头行
|
|
const header = data[0];
|
|
const headerCells = Array.from({ length: colCount }, (_, idx) => header[idx] || '');
|
|
lines.push('| ' + headerCells.join(' | ') + ' |');
|
|
|
|
// 分隔线
|
|
const sep = Array.from({ length: colCount }, () => '---');
|
|
lines.push('| ' + sep.join(' | ') + ' |');
|
|
|
|
// 数据行
|
|
for (let r = 1; r < data.length; r++) {
|
|
const row = data[r];
|
|
const rowCells = Array.from({ length: colCount }, (_, idx) => row[idx] || '');
|
|
lines.push('| ' + rowCells.join(' | ') + ' |');
|
|
}
|
|
|
|
chunks.push(lines.join('\n') + '\n');
|
|
}
|
|
break;
|
|
}
|
|
case 'document': {
|
|
if (block.children) {
|
|
for (const child of block.children) {
|
|
serializeBlock(child);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
if (block.content) {
|
|
chunks.push(`${block.content}\n`);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
serializeBlock(ast.rootBlock);
|
|
return chunks.join('\n').trim() + '\n';
|
|
}
|