import { Constants } from "../constants"; import { addScript, addScriptSync } from "../util/addScript"; import { addStyle } from "../util/addStyle"; import { code160to32 } from "../util/code160to32"; import { mathRenderAdapter } from "./adapterRender"; declare const katex: { renderToString(math: string, option: { /** * If `true`, math will be rendered in display mode * (math in display style and center math on page) * * If `false`, math will be rendered in inline mode * @default false */ displayMode?: boolean | undefined; /** * Determines the markup language of the output. The valid choices are: * - `html`: Outputs KaTeX in HTML only. * - `mathml`: Outputs KaTeX in MathML only. * - `htmlAndMathml`: Outputs HTML for visual rendering * and includes MathML for accessibility. * * @default 'htmlAndMathml' */ output?: 'html' | 'mathml' | 'htmlAndMathml' | undefined; /** * If `true`, display math has \tags rendered on the left * instead of the right, like \usepackage[leqno]{amsmath} in LaTeX. * * @default false */ leqno?: boolean | undefined; /** * If `true`, display math renders flush left with a 2em left margin, * like \documentclass[fleqn] in LaTeX with the amsmath package. * * @default false */ fleqn?: boolean | undefined; /** * If `true`, KaTeX will throw a `ParseError` when * it encounters an unsupported command or invalid LaTex * * If `false`, KaTeX will render unsupported commands as * text, and render invalid LaTeX as its source code with * hover text giving the error, in color given by errorColor * @default true */ throwOnError?: boolean | undefined; /** * A Color string given in format `#XXX` or `#XXXXXX` */ errorColor?: string | undefined; /** * A collection of custom macros. * * See `src/macros.js` for its usage */ macros?: any; /** * Specifies a minimum thickness, in ems, for fraction lines, * \sqrt top lines, {array} vertical lines, \hline, \hdashline, * \underline, \overline, and the borders of \fbox, \boxed, and * \fcolorbox. */ minRuleThickness?: number | undefined; /** * If `true`, `\color` will work like LaTeX's `\textcolor` * and takes 2 arguments * * If `false`, `\color` will work like LaTeX's `\color` * and takes 1 argument * * In both cases, `\textcolor` works as in LaTeX * * @default false */ colorIsTextColor?: boolean | undefined; /** * All user-specified sizes will be caped to `maxSize` ems * * If set to Infinity, users can make elements and space * arbitrarily large * * @default Infinity */ maxSize?: number | undefined; /** * Limit the number of macro expansions to specified number * * If set to `Infinity`, marco expander will try to fully expand * as in LaTex * * @default 1000 */ maxExpand?: number | undefined; /** * If `false` or `"ignore"`, allow features that make * writing in LaTex convenient but not supported by LaTex * * If `true` or `"error"`, throw an error for such transgressions * * If `"warn"`, warn about behavior via `console.warn` * * @default "warn" */ strict?: boolean | string | undefined; /** * Place KaTeX code in the global group. * * @default false */ globalGroup?: boolean | undefined; }): string; }; declare global { interface Window { MathJax: any; } } export const mathRender = (element: HTMLElement, options?: { cdn?: string, extPath?: string, math?: IMath }) => { const mathElements = mathRenderAdapter.getElements(element); if (mathElements.length === 0) { return; } const defaultOptions = { cdn: Constants.CDN, math: { engine: "KaTeX", inlineDigit: false, macros: {}, }, }; if (options && options.math) { options.math = Object.assign({}, defaultOptions.math, options.math); } options = Object.assign({}, defaultOptions, options); if (options.math.engine === "KaTeX") { const baseUrl = options.extPath || options.cdn addStyle(`${baseUrl}/dist/js/katex/katex.min.css`, "vditorKatexStyle"); addScript(`${baseUrl}/dist/js/katex/katex.min.js`, "vditorKatexScript").then(() => { addScript(`${baseUrl}/dist/js/katex/mhchem.min.js`, "vditorKatexChemScript").then(() => { mathElements.forEach((mathElement) => { if (mathElement.parentElement.classList.contains("vditor-wysiwyg__pre") || mathElement.parentElement.classList.contains("vditor-ir__marker--pre")) { return; } if (mathElement.getAttribute("data-math")) { return; } const math = code160to32(mathRenderAdapter.getCode(mathElement)); mathElement.setAttribute("data-math", math); try { mathElement.innerHTML = katex.renderToString(math, { displayMode: mathElement.tagName === "DIV", strict: false, throwOnError: false, output: "html", }); } catch (e) { mathElement.innerHTML = e.message; mathElement.className = "language-math vditor-reset--error"; } mathElement.addEventListener("copy", (event: ClipboardEvent) => { event.stopPropagation(); event.preventDefault(); const vditorMathElement = (event.currentTarget as HTMLElement).closest(".language-math"); event.clipboardData.setData("text/html", vditorMathElement.innerHTML); event.clipboardData.setData("text/plain", vditorMathElement.getAttribute("data-math")); }); }); }); }); } else if (options.math.engine === "MathJax") { const chainAsync = (fns: any) => { if (fns.length === 0) { return; } let curr = 0; const last = fns[fns.length - 1]; const next = () => { const fn = fns[curr++]; fn === last ? fn() : fn(next); }; next(); }; if (!window.MathJax) { window.MathJax = { loader: { paths: { mathjax: `${options.cdn}/dist/js/mathjax` }, }, startup: { typeset: false, }, tex: { macros: options.math.macros, }, }; } // 循环加载会抛异常 addScriptSync(`${options.cdn}/dist/js/mathjax/tex-svg-full.js`, "protyleMathJaxScript"); const renderMath = (mathElement: Element, next?: () => void) => { const math = code160to32(mathElement.textContent).trim(); const mathOptions = window.MathJax.getMetricsFor(mathElement); mathOptions.display = mathElement.tagName === "DIV"; window.MathJax.tex2svgPromise(math, mathOptions).then((node: Element) => { mathElement.innerHTML = ""; mathElement.setAttribute("data-math", math); mathElement.append(node); window.MathJax.startup.document.clear(); window.MathJax.startup.document.updateDocument(); const errorTextElement = node.querySelector('[data-mml-node="merror"]'); if (errorTextElement && errorTextElement.textContent.trim() !== "") { mathElement.innerHTML = errorTextElement.textContent.trim(); mathElement.className = "vditor-reset--error"; } if (next) { next(); } }); }; window.MathJax.startup.promise.then(() => { const chains: any[] = []; for (let i = 0; i < mathElements.length; i++) { const mathElement = mathElements[i]; if (!mathElement.parentElement.classList.contains("vditor-wysiwyg__pre") && !mathElement.parentElement.classList.contains("vditor-ir__marker--pre") && !mathElement.getAttribute("data-math") && code160to32(mathElement.textContent).trim()) { chains.push((next: () => void) => { if (i === mathElements.length - 1) { renderMath(mathElement); } else { renderMath(mathElement, next); } }); } } chainAsync(chains); }); } };