import { type ReactNode } from "react";
// 轻量 Markdown 渲染 —— 零依赖、行级解析,覆盖报告正文用到的子集:
// # / ## / ### 标题、**粗** *斜* `码`、[[双链]]、- 与 1. 列表、> 引用、--- 分隔、段落。
// 流式安全:每个 token 重渲染,残缺语法也能容忍。onLink 非空时 [[名称]] 可点(Obsidian 式)。
// 行内:把一段文本切成 [[双链]] / **粗** / *斜* / `码` / 纯文本节点。
function inline(text: string, keyPrefix: string, onLink?: (name: string) => void): ReactNode[] {
const out: ReactNode[] = [];
const re = /(\[\[([^\]]+)\]\]|\*\*([^*]+)\*\*|`([^`]+)`|\*([^*]+)\*)/g;
let last = 0;
let m: RegExpExecArray | null;
let i = 0;
while ((m = re.exec(text)) !== null) {
if (m.index > last) out.push(text.slice(last, m.index));
const key = `${keyPrefix}-${i++}`;
if (m[2] !== undefined) {
const name = m[2].split("|")[0].trim(); // 支持 [[名称|别名]]
const label = m[2].includes("|") ? m[2].split("|")[1].trim() : m[2];
out.push(
,
);
} else if (m[3] !== undefined) out.push({m[3]});
else if (m[4] !== undefined) out.push({m[4]});
else if (m[5] !== undefined) out.push({m[5]});
last = re.lastIndex;
}
if (last < text.length) out.push(text.slice(last));
return out;
}
export function Markdown({ text, className, onLink }: { text: string; className?: string; onLink?: (name: string) => void }) {
const il = (t: string, k: string) => inline(t, k, onLink);
const lines = text.replace(/\r\n/g, "\n").split("\n");
const blocks: ReactNode[] = [];
let para: string[] = [];
let list: { ordered: boolean; items: string[] } | null = null;
let tableBuf: string[] = []; // 连续的 | … | 行,flush 时判定是否真表格
let code: { lang: string; lines: string[] } | null = null; // ``` 围栏代码块
let k = 0;
const cells = (r: string) => r.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((c) => c.trim());
const isSep = (s: string) => s.includes("-") && /^\s*\|?[\s:|-]+\|?\s*$/.test(s);
const flushTable = () => {
if (!tableBuf.length) return;
if (tableBuf.length >= 2 && isSep(tableBuf[1])) {
const head = cells(tableBuf[0]);
const rows = tableBuf.slice(2).map(cells);
blocks.push(
| {il(c, `th${k}-${i}`)} | )}
|---|
| {il(c, `td${k}-${ri}-${ci}`)} | )}
{il(para.join(" "), `p${k}`)}
, ); para = []; } }; const flushList = () => { if (list) { const items = list.items.map((it, idx) => ({code.lines.join("\n")});
code = null;
} else code.lines.push(raw);
continue;
}
if (line.trim().startsWith("```")) {
flushAll();
code = { lang: line.trim().slice(3), lines: [] };
continue;
}
// 表格行:连续的 | … | 缓冲,flush 时判定真伪。
if (line.trim().startsWith("|")) {
flushPara();
flushList();
tableBuf.push(line);
continue;
}
const h = /^(#{1,3})\s+(.*)$/.exec(line);
const ol = /^\d+\.\s+(.*)$/.exec(line);
const ul = /^[-*]\s+(.*)$/.exec(line);
if (line.trim() === "") {
flushAll();
} else if (/^(---|\*\*\*|___)\s*$/.test(line)) {
flushAll();
blocks.push({il(line.replace(/^>\s?/, ""), `q${k}`)}, ); } else if (ol) { flushTable(); flushPara(); if (!list || !list.ordered) { flushList(); list = { ordered: true, items: [] }; } list.items.push(ol[1]); } else if (ul) { flushTable(); flushPara(); if (!list || list.ordered) { flushList(); list = { ordered: false, items: [] }; } list.items.push(ul[1]); } else { flushTable(); flushList(); para.push(line); } } if (code) blocks.push(
{code.lines.join("\n")});
flushAll();
return