import { readFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import type { Integration, IntegrationContext } from "muddle/contracts"; import { renderMarkdown } from "muddle/markdown"; import katex from "katex"; export function renderLatex(source: string, displayMode = false): string { return katex.renderToString(source, { displayMode, throwOnError: false, trust: false, }); } export function renderMathMarkdown(source: string): string { const formulas: string[] = []; const body = source.replace( /\$\$([\s\S]+?)\$\$|(? { formulas.push(renderLatex(display ?? inline, display !== undefined)); return `MUDDLEMATH${formulas.length - 1}END`; }, ); return renderMarkdown(body).replace( /MUDDLEMATH(\d+)END/g, (_match, index) => formulas[Number(index)] ?? "", ); } export interface LatexIntegrationOptions { route?: string; } export function latexIntegration( options: LatexIntegrationOptions = {}, ): Integration { const route = options.route ?? "/_muddle/katex.css"; return { name: "latex", async setup(context: IntegrationContext) { const base = dirname( fileURLToPath(import.meta.resolve("katex/package.json")), ); const css = await readFile(join(base, "dist", "katex.min.css"), "utf8"); context.loader("latex-markdown", { name: "latex-markdown", extensions: [".latex.md", ".latex.markdown"], load(file) { return { data: {}, content: renderMathMarkdown(file.source) }; }, }); context.route( "GET", route, () => new Response(css, { headers: { "content-type": "text/css; charset=utf-8", "cache-control": "public, max-age=31536000, immutable", }, }), ); }, }; }