Skip to content

Commit 560a508

Browse files
committed
1 parent a50bb47 commit 560a508

6 files changed

Lines changed: 137 additions & 74 deletions

File tree

CHANGELOG.md

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

33
## Unreleased
44

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

742
- 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

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: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
1+
from textwrap import dedent
2+
13
from django import template
4+
from django.forms.utils import flatatt
5+
from django.utils.html import format_html
6+
from django.utils.safestring import mark_safe
27

3-
from wagtail_polymath.widgets import MATHJAX_VERSION
8+
from wagtail_polymath.widgets import MATHJAX_SRI, MATHJAX_VERSION
49

510

611
register = template.Library()
712

813

914
@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}"
15+
def mathjax_script():
16+
attributes = {"crossorigin": "anonymous", "integrity": MATHJAX_SRI, "defer": True}
17+
script = format_html(
18+
'<script src="{path}"{attributes}></script>',
19+
path=f"https://cdn.jsdelivr.net/npm/mathjax@{MATHJAX_VERSION}/tex-mml-chtml.js",
20+
attributes=flatatt(attributes),
21+
)
22+
23+
safe_init = mark_safe( # noqa: S308
24+
dedent("""
25+
<script>
26+
window.MathJax = {
27+
loader: { load: ["input/asciimath"] },
28+
tex: { inlineMath: { '[+]': [['$', '$']] } },
29+
};
30+
</script>
31+
""")
32+
)
33+
34+
return script + safe_init

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>

0 commit comments

Comments
 (0)