Skip to content

Commit 98af277

Browse files
committed
feat: add MathJax v4 and remove v2
1 parent fbe437c commit 98af277

5 files changed

Lines changed: 73 additions & 83 deletions

File tree

ts/src/mathjax/base.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment */
1+
/* eslint-disable */
22

33
export abstract class BaseMathJaxHelper {
44
protected abstract cdnUrl: string;
55
protected mathjax: any;
6-
7-
private renderQueue: Promise<void> = Promise.resolve();
6+
private loadPromise: Promise<void> | null = null;
87

98
/**
109
* MathJax helper.
@@ -16,12 +15,16 @@ export abstract class BaseMathJaxHelper {
1615
}
1716

1817
/**
19-
* Loads MathJax from a CDN.
18+
* Loads MathJax from a CDN and initializes it.
2019
*
2120
* @protected
2221
*/
2322
protected loadFromCdn(): Promise<void> {
24-
return new Promise((resolve, reject) => {
23+
if (this.loadPromise) {
24+
return this.loadPromise;
25+
}
26+
27+
return this.loadPromise = new Promise((resolve, reject) => {
2528
const script = document.createElement("script");
2629
script.type = "text/javascript";
2730
script.src = this.cdnUrl;
@@ -30,7 +33,9 @@ export abstract class BaseMathJaxHelper {
3033
script.onload = () => {
3134
// @ts-expect-error After loading the script, window.MathJax will exist.
3235
this.mathjax = window.MathJax;
33-
resolve();
36+
37+
// We return the startup promise to ensure that MathJax is fully initialized before using it.
38+
resolve(this.mathjax.startup.promise);
3439
};
3540
script.onerror = () => reject(new Error(`Failed to load ${this.cdnUrl}.`));
3641

@@ -49,13 +54,9 @@ export abstract class BaseMathJaxHelper {
4954
* Renders LaTeX inside an element. Loads MathJax from a CDN if necessary.
5055
*/
5156
render(element: Element, inline: boolean): Promise<void> {
52-
// We do this to chain every render call. This also ensures that we only load once from the CDN:
53-
// https://docs.mathjax.org/en/v3.2-latest/web/typeset.html#handling-asynchronous-typesetting
54-
return (this.renderQueue = this.renderQueue.then(() => {
55-
if (this.mathjax === undefined) {
56-
return this.loadFromCdn().then(() => this._render(element, inline));
57-
}
58-
return this._render(element, inline);
59-
}));
57+
if (this.mathjax === undefined) {
58+
return this.loadFromCdn().then(() => this._render(element, inline));
59+
}
60+
return this._render(element, inline);
6061
}
6162
}

ts/src/mathjax/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
/* eslint-disable */
22

33
import { BaseMathJaxHelper } from "./base";
4-
import { MathJax2Helper } from "./v2";
54
import { MathJax3Helper } from "./v3";
5+
import { MathJax4Helper } from "./v4";
66

77
let mathJaxHelper: BaseMathJaxHelper | null = null;
88

99
/**
1010
* Uses MathJax to render LaTeX inside the given element.
1111
*
12-
* If MathJax is not available it will be loaded from a CDN.
12+
* If MathJax is not available, it will be loaded from a CDN.
1313
*/
1414
export function renderLaTeX(element: Element, inline: boolean = true): Promise<void> {
1515
if (mathJaxHelper === null) {
1616
// @ts-expect-error We need to check for the existence of MathJax.
1717
const mathjax: any = window.MathJax;
1818
if (typeof mathjax === "object") {
19-
if (mathjax.version.startsWith("2.")) {
20-
mathJaxHelper = new MathJax2Helper(mathjax);
21-
} else if (mathjax.version.startsWith("3.")) {
19+
if (mathjax.version.startsWith("3.")) {
2220
mathJaxHelper = new MathJax3Helper(mathjax);
21+
} else if (mathjax.version.startsWith("4.")) {
22+
mathJaxHelper = new MathJax4Helper(mathjax);
2323
} else {
24-
return Promise.reject(new Error("Only MathJax 2.x and 3.x are supported."));
24+
return Promise.reject(new Error("Only MathJax 3.x and 4.x are supported."));
2525
}
2626
} else {
27+
// We should in theory switch to MathJax 4 as the default, but we might need to change our UI.
2728
mathJaxHelper = new MathJax3Helper();
2829
}
2930
}

ts/src/mathjax/v2.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.

ts/src/mathjax/v3.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { BaseMathJaxHelper } from "./base";
44
/**
55
* Uses MathJax 3 to render LaTeX.
66
*
7-
* Docs: https://docs.mathjax.org/en/v3.2-latest/
7+
* Docs: https://docs.mathjax.org/en/v3.2/
88
*/
99
export class MathJax3Helper extends BaseMathJaxHelper {
1010
protected cdnUrl = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
11+
private queue: Promise<void> = Promise.resolve();
1112

1213
protected loadFromCdn() {
1314
// Configure MathJax.
@@ -34,8 +35,9 @@ export class MathJax3Helper extends BaseMathJaxHelper {
3435
}
3536

3637
protected _render(element: Element, inline: boolean): Promise<void> {
37-
// Ensure that MathJax is fully initialized.
38-
return this.mathjax.startup.promise.then(() => {
38+
// We need to manually chain every render call.
39+
// https://docs.mathjax.org/en/v3.2/web/typeset.html#handling-asynchronous-typesetting
40+
return this.queue = this.queue.then(() => {
3941
// Get the delimiters.
4042
const inlineDelimiters = this.mathjax.config.tex?.inlineMath?.[0] ?? ["\\(", "\\)"];
4143
const displayDelimiters = this.mathjax.config.tex?.displayMath?.[0] ?? ["\\[", "\\]"];

ts/src/mathjax/v4.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* eslint-disable */
2+
import { BaseMathJaxHelper } from "./base";
3+
4+
/**
5+
* Uses MathJax 4 to render LaTeX.
6+
*
7+
* Docs: https://docs.mathjax.org/en/v4.0/
8+
*/
9+
export class MathJax4Helper extends BaseMathJaxHelper {
10+
protected cdnUrl = "https://cdn.jsdelivr.net/npm/mathjax@4/tex-chtml.js";
11+
12+
protected loadFromCdn(): Promise<void> {
13+
// Configure MathJax.
14+
// @ts-ignore
15+
window.MathJax = {
16+
tex: {
17+
inlineMath: [["\\\\(", "\\\\)"]],
18+
displayMath: [["\\\\[", "\\\\]"]],
19+
processEscapes: true,
20+
packages: { "[+]": ["noerrors"] },
21+
},
22+
startup: {
23+
typeset: false,
24+
},
25+
options: {
26+
ignoreHtmlClass: "tex2jax_ignore",
27+
processHtmlClass: "tex2jax_process",
28+
},
29+
loader: {
30+
load: ["[tex]/noerrors", "ui/safe"],
31+
},
32+
};
33+
return super.loadFromCdn();
34+
}
35+
36+
protected _render(element: Element, inline: boolean): Promise<void> {
37+
const inlineDelimiters = this.mathjax.config.tex?.inlineMath?.[0] ?? ["\\(", "\\)"];
38+
const displayDelimiters = this.mathjax.config.tex?.displayMath?.[0] ?? ["\\[", "\\]"];
39+
const [openingDelimiter, closingDelimiter] = inline ? inlineDelimiters : displayDelimiters;
40+
41+
// Add the delimiters.
42+
element.textContent = `${openingDelimiter} ${element.textContent} ${closingDelimiter}`;
43+
44+
return this.mathjax.typesetPromise([element])
45+
}
46+
}

0 commit comments

Comments
 (0)