Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions ts/src/mathjax/base.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment */
/* eslint-disable */

export abstract class BaseMathJaxHelper {
protected abstract cdnUrl: string;
protected mathjax: any;

private renderQueue: Promise<void> = Promise.resolve();
private loadPromise: Promise<void> | null = null;

/**
* MathJax helper.
Expand All @@ -16,12 +15,16 @@ export abstract class BaseMathJaxHelper {
}

/**
* Loads MathJax from a CDN.
* Loads MathJax from a CDN and initializes it.
*
* @protected
*/
protected loadFromCdn(): Promise<void> {
return new Promise((resolve, reject) => {
if (this.loadPromise) {
return this.loadPromise;
}

return this.loadPromise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = this.cdnUrl;
Expand All @@ -30,7 +33,9 @@ export abstract class BaseMathJaxHelper {
script.onload = () => {
// @ts-expect-error After loading the script, window.MathJax will exist.
this.mathjax = window.MathJax;
resolve();

// We return the startup promise to ensure that MathJax is fully initialized before using it.
resolve(this.mathjax.startup.promise);
};
script.onerror = () => reject(new Error(`Failed to load ${this.cdnUrl}.`));

Expand All @@ -49,13 +54,9 @@ export abstract class BaseMathJaxHelper {
* Renders LaTeX inside an element. Loads MathJax from a CDN if necessary.
*/
render(element: Element, inline: boolean): Promise<void> {
// We do this to chain every render call. This also ensures that we only load once from the CDN:
// https://docs.mathjax.org/en/v3.2-latest/web/typeset.html#handling-asynchronous-typesetting
return (this.renderQueue = this.renderQueue.then(() => {
if (this.mathjax === undefined) {
return this.loadFromCdn().then(() => this._render(element, inline));
}
return this._render(element, inline);
}));
if (this.mathjax === undefined) {
return this.loadFromCdn().then(() => this._render(element, inline));
}
return this._render(element, inline);
}
}
13 changes: 7 additions & 6 deletions ts/src/mathjax/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
/* eslint-disable */

import { BaseMathJaxHelper } from "./base";
import { MathJax2Helper } from "./v2";
import { MathJax3Helper } from "./v3";
import { MathJax4Helper } from "./v4";

let mathJaxHelper: BaseMathJaxHelper | null = null;

/**
* Uses MathJax to render LaTeX inside the given element.
*
* If MathJax is not available it will be loaded from a CDN.
* If MathJax is not available, it will be loaded from a CDN.
*/
export function renderLaTeX(element: Element, inline: boolean = true): Promise<void> {
if (mathJaxHelper === null) {
// @ts-expect-error We need to check for the existence of MathJax.
const mathjax: any = window.MathJax;
if (typeof mathjax === "object") {
if (mathjax.version.startsWith("2.")) {
mathJaxHelper = new MathJax2Helper(mathjax);
} else if (mathjax.version.startsWith("3.")) {
if (mathjax.version.startsWith("3.")) {
mathJaxHelper = new MathJax3Helper(mathjax);
} else if (mathjax.version.startsWith("4.")) {
mathJaxHelper = new MathJax4Helper(mathjax);
} else {
return Promise.reject(new Error("Only MathJax 2.x and 3.x are supported."));
return Promise.reject(new Error("Only MathJax 3.x and 4.x are supported."));
}
} else {
// We should in theory switch to MathJax 4 as the default, but we might need to change our UI.
mathJaxHelper = new MathJax3Helper();
}
}
Expand Down
60 changes: 0 additions & 60 deletions ts/src/mathjax/v2.ts

This file was deleted.

8 changes: 5 additions & 3 deletions ts/src/mathjax/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { BaseMathJaxHelper } from "./base";
/**
* Uses MathJax 3 to render LaTeX.
*
* Docs: https://docs.mathjax.org/en/v3.2-latest/
* Docs: https://docs.mathjax.org/en/v3.2/
*/
export class MathJax3Helper extends BaseMathJaxHelper {
protected cdnUrl = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
private queue: Promise<void> = Promise.resolve();

protected loadFromCdn() {
// Configure MathJax.
Expand All @@ -34,8 +35,9 @@ export class MathJax3Helper extends BaseMathJaxHelper {
}

protected _render(element: Element, inline: boolean): Promise<void> {
// Ensure that MathJax is fully initialized.
return this.mathjax.startup.promise.then(() => {
// We need to manually chain every render call.
// https://docs.mathjax.org/en/v3.2/web/typeset.html#handling-asynchronous-typesetting
return this.queue = this.queue.then(() => {
// Get the delimiters.
const inlineDelimiters = this.mathjax.config.tex?.inlineMath?.[0] ?? ["\\(", "\\)"];
const displayDelimiters = this.mathjax.config.tex?.displayMath?.[0] ?? ["\\[", "\\]"];
Expand Down
46 changes: 46 additions & 0 deletions ts/src/mathjax/v4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* eslint-disable */
import { BaseMathJaxHelper } from "./base";

/**
* Uses MathJax 4 to render LaTeX.
*
* Docs: https://docs.mathjax.org/en/v4.0/
*/
export class MathJax4Helper extends BaseMathJaxHelper {
protected cdnUrl = "https://cdn.jsdelivr.net/npm/mathjax@4/tex-chtml.js";

protected loadFromCdn(): Promise<void> {
// Configure MathJax.
// @ts-ignore
window.MathJax = {
tex: {
inlineMath: [["\\\\(", "\\\\)"]],
displayMath: [["\\\\[", "\\\\]"]],
processEscapes: true,
packages: { "[+]": ["noerrors"] },
},
startup: {
typeset: false,
},
options: {
ignoreHtmlClass: "tex2jax_ignore",
processHtmlClass: "tex2jax_process",
},
loader: {
load: ["[tex]/noerrors", "ui/safe"],
},
};
return super.loadFromCdn();
}

protected _render(element: Element, inline: boolean): Promise<void> {
const inlineDelimiters = this.mathjax.config.tex?.inlineMath?.[0] ?? ["\\(", "\\)"];
const displayDelimiters = this.mathjax.config.tex?.displayMath?.[0] ?? ["\\[", "\\]"];
const [openingDelimiter, closingDelimiter] = inline ? inlineDelimiters : displayDelimiters;

// Add the delimiters.
element.textContent = `${openingDelimiter} ${element.textContent} ${closingDelimiter}`;

return this.mathjax.typesetPromise([element])
}
}