spb/autoscholar
Public
1import katex from "katex";23/**4 * Renders $…$ / $$…$$ / \( \) / \[ \] segments of a plain-text field (abstracts, findings) to KaTeX HTML.5 * Text outside math is HTML-escaped. Used on the server only; output is trusted markup built from escaped input.6 */7export function renderMath(text: string): { html: string; hasMath: boolean } {8 const esc = (s: string) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");9 const re = /\$\$([\s\S]+?)\$\$|\\\[([\s\S]+?)\\\]|\\\(([\s\S]+?)\\\)|\$([^$\n]+?)\$/g;10 let out = "";11 let last = 0;12 let hasMath = false;13 for (const m of text.matchAll(re)) {14 const idx = m.index ?? 0;15 out += esc(text.slice(last, idx));16 const display = m[1] !== undefined || m[2] !== undefined;17 const src = (m[1] ?? m[2] ?? m[3] ?? m[4] ?? "").trim();18 try {19 const html = katex.renderToString(src, {20 displayMode: display,21 throwOnError: false,22 output: "htmlAndMathml",23 strict: "ignore",24 });25 out += display26 ? `<span class="math-display">${html}</span>`27 : `<span class="math-inline">${html}</span>`;28 hasMath = true;29 } catch {30 out += esc(m[0]);31 }32 last = idx + m[0].length;33 }34 out += esc(text.slice(last));35 return { html: out, hasMath };36}37