Skip to content

Commit 0a6cefc

Browse files
authored
Upgrade to MathJax 4.1.2 (#21)
1 parent 5def51e commit 0a6cefc

9 files changed

Lines changed: 130 additions & 77 deletions

File tree

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,42 @@
22

33
## Unreleased
44

5+
- Dropped support for Django < 5.2
6+
- Upgraded to MathJax 4.1.2, using [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity)
7+
for the CDN script. The template tag has also changed to `mathjax_script`. See upgrade considerations
8+
9+
### Upgrade considerations
10+
11+
#### The project namespace changed to `wagtail_polymath`
12+
13+
```diff
14+
# Old
15+
- from wagtailmath.blocks import MathBlock
16+
# New
17+
+ from wagtail_polymath.blocks import MathBlock
18+
```
19+
20+
and
21+
22+
```diff
23+
# Old
24+
- {% load wagtailmath %}
25+
# New
26+
+ {% load wagtail_polymath %}
27+
```
28+
29+
#### The template tag has changed
30+
The `mathjax` template tag has changed to `mathjax_script` and should no longer be wrapped in `<script></script>`
31+
32+
```diff
33+
# Old
34+
- {% load wagtailmath %}
35+
- <script src="{% mathjax %}"></script>
36+
# New
37+
+ {% load wagtail_polymath %}
38+
+ {% mathjax_script %}
39+
```
40+
541
## 2.0.0.dev1 (2026-06-18)
642

743
- The project namespace has changed from wagtailmath to wagtail_polymath.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@ class MyPage(Page):
7171
])
7272
```
7373

74-
Use the `mathjax` template tag in your front end template to load the
74+
Use the `mathjax_script` template tag in your front end template to load the
7575
MathJax library:
7676

7777
```django+html
7878
{% load wagtail_polymath %}
7979
...
8080
81-
<script src="{% mathjax %}"></script>
81+
{% mathjax_script %}
8282
```
8383

8484

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ classifiers = [
3131
requires-python = ">=3.10"
3232
dynamic = ["version"]
3333
dependencies = [
34-
"Django>=4.2",
34+
"Django>=5.2",
3535
"Wagtail>=7.0"
3636
]
3737
[project.optional-dependencies]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
window.MathJax = {
2+
loader: { load: ["input/asciimath"] },
3+
tex: { inlineMath: { '[+]': [['$', '$']] } },
4+
};
Lines changed: 62 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
1-
// Update the preview area on input. Lifted directly from mathjax website examples
21
class Preview {
3-
4-
// Get the preview and buffer DIV's
5-
//
62
constructor(previewId, bufferId, inputId) {
73
this.preview = document.getElementById(previewId);
84
this.buffer = document.getElementById(bufferId);
95
this.input = document.getElementById(inputId);
106
this.delay = 150; // delay after keystroke before updating
11-
this.timeout = null; // store setTimout id
7+
this.timeout = null; // store setTimeout id
128
this.mjRunning = false; // true when MathJax is processing
139
this.mjPending = false; // true when a typeset has been queued
1410
this.oldText = null; // used to check if an update is needed
1511
}
1612

17-
//
18-
// Switch the buffer and preview, and display the right one.
19-
// (We use visibility:hidden rather than display:none since
20-
// the results of running MathJax are more accurate that way.)
21-
//
22-
SwapBuffers() {
23-
var buffer = this.preview, preview = this.buffer;
13+
// Swap buffer/preview so the freshly typeset one is shown.
14+
swapBuffers() {
15+
let buffer = this.preview, preview = this.buffer;
2416
this.buffer = buffer;
2517
this.preview = preview;
2618
buffer.style.visibility = "hidden";
@@ -29,76 +21,80 @@ class Preview {
2921
preview.style.visibility = "";
3022
}
3123

32-
//
33-
// This gets called when a key is pressed in the textarea.
34-
// We check if there is already a pending update and clear it if so.
35-
// Then set up an update to occur after a small delay (so if more keys
36-
// are pressed, the update won't occur until after there has been
37-
// a pause in the typing).
38-
// The callback function is set up below, after the Preview object is set up.
39-
//
40-
Update() {
24+
// Debounce: typeset only after a pause in typing.
25+
update() {
4126
if (this.timeout) {
4227
clearTimeout(this.timeout);
4328
}
44-
this.timeout = setTimeout(this.callback, this.delay);
29+
30+
this.timeout = setTimeout(() => this.createPreview(), this.delay);
4531
}
4632

47-
//
48-
// Creates the preview and runs MathJax on it.
49-
// If MathJax is already trying to render the code, return
50-
// If the text hasn't changed, return
51-
// Otherwise, indicate that MathJax is running, and start the
52-
// typesetting. After it is done, call PreviewDone.
53-
//
54-
CreatePreview() {
33+
// Render input into the hidden buffer, then typeset it.
34+
createPreview() {
5535
this.timeout = null;
56-
if (this.mjPending) return;
57-
var text = this.input.value;
58-
if (text === this.oldtext) return;
59-
if (this.mjRunning) {
60-
this.mjPending = true;
61-
MathJax.Hub.Queue(["CreatePreview", this]);
62-
} else {
63-
this.buffer.innerHTML = this.oldtext = text;
64-
this.mjRunning = true;
65-
MathJax.Hub.Queue(
66-
["Typeset", MathJax.Hub, this.buffer],
67-
["PreviewDone", this]
68-
);
69-
}
36+
this.mjRunning = true;
37+
this.mjPending = false;
38+
39+
const text = this.input.value;
40+
if (text === this.oldText) return;
41+
this.oldText = text;
42+
43+
let buffer = this.buffer; // capture: SwapBuffers may run before the promise settles
44+
45+
46+
MathJax.typesetClear([buffer]); // drop metadata from the previous render
47+
48+
buffer.innerHTML = text;
49+
50+
MathJax.typesetPromise([buffer])
51+
.then(() => {
52+
this.mjRunning = false;
53+
if (this.mjPending) {
54+
this.update();
55+
}
56+
else {
57+
this.previewDone();
58+
}
59+
})
60+
.catch((err) => console.error("MathJax typeset failed:", err));
7061
}
7162

72-
//
73-
// Indicate that MathJax is no longer running,
74-
// and swap the buffers to show the results.
75-
//
76-
PreviewDone() {
63+
previewDone() {
7764
this.mjRunning = this.mjPending = false;
78-
this.SwapBuffers();
65+
this.swapBuffers();
7966
}
8067
}
8168

8269

8370
function initMathJaxPreview(id) {
84-
window.wagtailMathPreviews = window.wagtailMathPreviews || {};
71+
const target = document.getElementById(id);
72+
if (!target) {
73+
return;
74+
}
8575

86-
window.wagtailMathPreviews[id] = new Preview(
87-
"MathPreview-" + id,
88-
"MathBuffer-" + id,
89-
id
90-
);
76+
const preview = new Preview("MathPreview-" + id, "MathBuffer-" + id, id);
9177

92-
// Cache a callback to the CreatePreview action
93-
window.wagtailMathPreviews[id].callback = MathJax.Callback(["CreatePreview", window.wagtailMathPreviews[id]]);
94-
window.wagtailMathPreviews[id].callback.autoReset = true; // make sure it can run more than once
95-
window.wagtailMathPreviews[id].Update();
78+
window.wagtailMathPreviews = window.wagtailMathPreviews || {};
79+
window.wagtailMathPreviews[id] = preview;
9680

97-
// attach a keyup event listener so we update the preview
98-
const target = document.getElementById(id);
99-
if (target) {
100-
target.addEventListener("keyup", function() {
101-
window.wagtailMathPreviews[id].Update();
102-
})
81+
if (target.value) {
82+
preview.update();
10383
}
84+
85+
target.addEventListener("keyup", () => {
86+
if (this.mjRunning) {
87+
this.mjPending = true;
88+
}
89+
else {
90+
preview.update()
91+
}
92+
});
10493
}
94+
95+
window.MathJax = {
96+
loader: { load: ["input/asciimath"] }, // "AM" isn't in tex-mml-chtml; add it explicitly
97+
tex: {
98+
inlineMath: { '[+]': [['$', '$']] }
99+
},
100+
};
Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
from django import template
2+
from django.forms.utils import flatatt
3+
from django.utils.html import format_html
4+
from wagtail.admin.staticfiles import versioned_static
25

3-
from wagtail_polymath.widgets import MATHJAX_VERSION
6+
from wagtail_polymath.widgets import MATHJAX_SRI, MATHJAX_VERSION
47

58

69
register = template.Library()
710

811

912
@register.simple_tag
10-
def mathjax(config="TeX-MML-AM_CHTML"):
11-
return f"https://cdnjs.cloudflare.com/ajax/libs/mathjax/{MATHJAX_VERSION}/MathJax.js?config={config}"
13+
def mathjax_script():
14+
attributes = {"crossorigin": "anonymous", "integrity": MATHJAX_SRI, "defer": True}
15+
return format_html(
16+
'<script src="{init_path}"></script><script src="{path}"{attributes}></script>',
17+
init_path=versioned_static("wagtail_polymath/js/mathjax_init.js"),
18+
path=f"https://cdn.jsdelivr.net/npm/mathjax@{MATHJAX_VERSION}/tex-mml-chtml.js",
19+
attributes=flatatt(attributes),
20+
)

src/wagtail_polymath/widgets.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from django import forms
2+
from django.forms import Script
23
from wagtail.admin.staticfiles import versioned_static
34

45

5-
MATHJAX_VERSION = "2.7.9"
6+
MATHJAX_VERSION = "4.1.2"
7+
MATHJAX_SRI = "sha256-dPV35kaoLq1rg+JbYf8p1kTrZamwMY+XIwaWUPwqtpU="
68

79

810
class MathJaxWidget(forms.Textarea):
@@ -18,7 +20,14 @@ def build_attrs(self, *args, **kwargs):
1820
def media(self):
1921
return forms.Media(
2022
js=(
21-
f"https://cdnjs.cloudflare.com/ajax/libs/mathjax/{MATHJAX_VERSION}/MathJax.js?config=TeX-MML-AM_HTMLorMML",
23+
Script(
24+
f"https://cdn.jsdelivr.net/npm/mathjax@{MATHJAX_VERSION}/tex-mml-chtml.js",
25+
**{
26+
"crossorigin": "anonymous",
27+
"integrity": MATHJAX_SRI,
28+
"defer": True,
29+
},
30+
),
2231
versioned_static("wagtail_polymath/js/wagtail_polymath.js"),
2332
versioned_static(
2433
"wagtail_polymath/js/wagtail_polymath-mathjax-controller.js"

tests/testapp/templates/testapp/math_page.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% load wagtail_polymath %}
22
<html>
33
<head>
4-
<script src="{% mathjax %}"></script>
4+
{% mathjax_script %}
55
</head>
66
<body>
77
<h1>{{ page.title }}</h1>

tox.ini

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
min_version = 4.30
33

44
envlist =
5-
python{3.10}-django{4.2}-wagtail{7.0}-{sqlite}
5+
python{3.10}-django{5.2}-wagtail{7.0}-{sqlite}
66
python{3.11,3.12}-django{5.2}-wagtail{7.0,7.4}-{sqlite}
77
python{3.13,3.14}-django{6.0}-wagtail{7.4}-{sqlite}
88
python{3.14}-django{6.0}-wagtail{main}-{sqlite}
@@ -35,7 +35,6 @@ setenv =
3535
deps =
3636
coverage>=7.0,<8.0
3737

38-
django4.2: Django>=4.2,<4.3
3938
django5.2: Django>=5.2,<5.3
4039
django6.0: Django>=6.0,<6.1
4140

0 commit comments

Comments
 (0)