anki/ts/editable/mathjax.ts

88 lines
2.5 KiB
TypeScript
Raw Normal View History

2021-08-04 01:55:39 +02:00
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
2021-09-17 21:30:32 +02:00
/* eslint
@typescript-eslint/no-explicit-any: "off",
*/
import "mathjax/es5/tex-svg-full";
2021-08-07 05:46:52 +02:00
import { mathIcon } from "./icons";
const parser = new DOMParser();
2021-08-07 22:07:57 +02:00
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; };`;
2021-08-07 22:07:57 +02:00
}
2021-08-07 05:46:52 +02:00
2021-08-07 22:07:57 +02:00
function getStyle(css: string): HTMLStyleElement {
const style = document.createElement("style") as HTMLStyleElement;
style.appendChild(document.createTextNode(css));
return style;
}
2021-08-04 01:55:39 +02:00
2021-08-07 22:07:57 +02:00
function getEmptyIcon(style: HTMLStyleElement): [string, string] {
2021-08-07 05:46:52 +02:00
const icon = parser.parseFromString(mathIcon, "image/svg+xml");
const svg = icon.children[0];
svg.insertBefore(style, svg.children[0]);
2021-08-07 05:46:52 +02:00
2021-08-07 22:23:18 +02:00
return [svg.outerHTML, "MathJax"];
2021-08-04 01:55:39 +02:00
}
2021-08-07 05:46:52 +02:00
export function convertMathjax(
input: string,
nightMode: boolean,
fontSize: number,
): [string, string] {
input = revealClozeAnswers(input);
2021-08-07 22:07:57 +02:00
const style = getStyle(getCSS(nightMode, fontSize));
2021-08-07 05:46:52 +02:00
if (input.trim().length === 0) {
2021-08-07 22:07:57 +02:00
return getEmptyIcon(style);
2021-08-07 05:46:52 +02:00
}
let output: Element;
try {
output = globalThis.MathJax.tex2svg(input);
} catch (e) {
return ["Mathjax Error", String(e)];
}
const svg = output.children[0] as SVGElement;
2021-08-07 05:46:52 +02:00
if ((svg as any).viewBox.baseVal.height === 16) {
2021-08-07 22:07:57 +02:00
return getEmptyIcon(style);
2021-08-07 05:46:52 +02:00
}
2021-08-07 19:33:01 +02:00
let title = "";
2021-08-07 19:13:08 +02:00
if (svg.innerHTML.includes("data-mjx-error")) {
svg.querySelector("rect")?.setAttribute("fill", "yellow");
svg.querySelector("text")?.setAttribute("color", "red");
title = svg.querySelector("title")?.innerHTML ?? "";
2021-08-07 19:13:08 +02:00
} else {
2021-08-07 05:46:52 +02:00
svg.insertBefore(style, svg.children[0]);
}
2021-08-07 19:13:08 +02:00
2021-08-07 19:33:01 +02:00
return [svg.outerHTML, title];
2021-08-07 05:46:52 +02:00
}
/**
* Escape characters which are technically legal in Mathjax, but confuse HTML.
*/
export function escapeSomeEntities(value: string): string {
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
export function unescapeSomeEntities(value: string): string {
return value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
}
function revealClozeAnswers(input: string): string {
// one-line version of regex in cloze.rs
const regex = /\{\{c(\d+)::(.*?)(?:::(.*?))?\}\}/gis;
return input.replace(regex, "[$2]");
}