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(
{head.map((c, i) => )} {rows.map((r, ri) => ( {r.map((c, ci) => )} ))}
{il(c, `th${k}-${i}`)}
{il(c, `td${k}-${ri}-${ci}`)}
, ); } else { para.push(...tableBuf); // 不是合法表格 → 当普通段落 } tableBuf = []; }; const flushPara = () => { if (para.length) { blocks.push(

{il(para.join(" "), `p${k}`)}

, ); para = []; } }; const flushList = () => { if (list) { const items = list.items.map((it, idx) => (
  • {il(it, `li${k}-${idx}`)}
  • )); blocks.push( list.ordered ? (
      {items}
    ) : ( ), ); list = null; } }; const flushAll = () => { flushTable(); flushPara(); flushList(); }; for (const raw of lines) { const line = raw.trimEnd(); // 围栏代码块 ```lang … ```(含未被图表分流的普通代码)。 if (code) { if (line.trim().startsWith("```")) { blocks.push(
    {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(
    ); } else if (h) { flushTable(); flushPara(); flushList(); const lvl = h[1].length; const cls = lvl === 1 ? "mt-1 mb-3 text-xl font-bold text-slate-100" : lvl === 2 ? "mt-4 mb-2 text-base font-semibold text-slate-100" : "mt-3 mb-1 text-sm font-semibold text-slate-200"; const content = il(h[2], `h${k}`); blocks.push(lvl === 1 ?

    {content}

    : lvl === 2 ?

    {content}

    :

    {content}

    ); } else if (line.startsWith(">")) { 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
    {blocks}
    ; }