ae18ba2a05
Re: https://forums.ankiweb.net/t/anki-2-1-53-release-candidate/20122/2 Autoloads in MathJax are asynchronous, and the caller is expected to use asynchronous APIs when they are in use [1]. The editor uses the synchronous tex2svg(), which throws a "MathJax retry" error when an autoload package has not yet loaded. Attempting to use the package before it has loaded appears to break future invocations as well, so the package fails to work at all until a new webview is created. The following HTML will reproduce the issue when added to a single card in a new profile: ``` <strong>6 </strong>Every combination of <anki-mathjax>\boldsymbol{v}=(1,-2,1)</anki-mathjax> and <anki-mathjax>\boldsymbol{w}=(0,1,-1)</anki-mathjax> has components that add to _____.<br> ``` Ideally we'd switch the MathJax rendering to be asynchronous, but that didn't work well when I tried it in #1862. For now I've just switched to the full package, which adds about 130KB to the final minified JS (2.76MB), and likely slows down editor loading somewhat. [1] https://github.com/mathjax/MathJax/issues/2557#issuecomment-727655089
88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
/* eslint
|
|
@typescript-eslint/no-explicit-any: "off",
|
|
*/
|
|
|
|
import "mathjax/es5/tex-svg-full";
|
|
|
|
import { mathIcon } from "./icons";
|
|
|
|
const parser = new DOMParser();
|
|
|
|
function getCSS(nightMode: boolean, fontSize: number): string {
|
|
const color = nightMode ? "white" : "black";
|
|
/* color is set for Maths, fill for the empty icon */
|
|
return `svg { color: ${color}; fill: ${color}; font-size: ${fontSize}px; };`;
|
|
}
|
|
|
|
function getStyle(css: string): HTMLStyleElement {
|
|
const style = document.createElement("style") as HTMLStyleElement;
|
|
style.appendChild(document.createTextNode(css));
|
|
return style;
|
|
}
|
|
|
|
function getEmptyIcon(style: HTMLStyleElement): [string, string] {
|
|
const icon = parser.parseFromString(mathIcon, "image/svg+xml");
|
|
const svg = icon.children[0];
|
|
svg.insertBefore(style, svg.children[0]);
|
|
|
|
return [svg.outerHTML, "MathJax"];
|
|
}
|
|
|
|
export function convertMathjax(
|
|
input: string,
|
|
nightMode: boolean,
|
|
fontSize: number,
|
|
): [string, string] {
|
|
input = revealClozeAnswers(input);
|
|
const style = getStyle(getCSS(nightMode, fontSize));
|
|
|
|
if (input.trim().length === 0) {
|
|
return getEmptyIcon(style);
|
|
}
|
|
|
|
let output: Element;
|
|
try {
|
|
output = globalThis.MathJax.tex2svg(input);
|
|
} catch (e) {
|
|
return ["Mathjax Error", String(e)];
|
|
}
|
|
|
|
const svg = output.children[0] as SVGElement;
|
|
|
|
if ((svg as any).viewBox.baseVal.height === 16) {
|
|
return getEmptyIcon(style);
|
|
}
|
|
|
|
let title = "";
|
|
|
|
if (svg.innerHTML.includes("data-mjx-error")) {
|
|
svg.querySelector("rect")?.setAttribute("fill", "yellow");
|
|
svg.querySelector("text")?.setAttribute("color", "red");
|
|
title = svg.querySelector("title")?.innerHTML ?? "";
|
|
} else {
|
|
svg.insertBefore(style, svg.children[0]);
|
|
}
|
|
|
|
return [svg.outerHTML, title];
|
|
}
|
|
|
|
/**
|
|
* Escape characters which are technically legal in Mathjax, but confuse HTML.
|
|
*/
|
|
export function escapeSomeEntities(value: string): string {
|
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
}
|
|
|
|
export function unescapeSomeEntities(value: string): string {
|
|
return value.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
}
|
|
|
|
function revealClozeAnswers(input: string): string {
|
|
// one-line version of regex in cloze.rs
|
|
const regex = /\{\{c(\d+)::(.*?)(?:::(.*?))?\}\}/gis;
|
|
return input.replace(regex, "[$2]");
|
|
}
|