diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js
index 8a19355c1a93..be080b06d13d 100644
--- a/cms/djangoapps/pipeline_js/js/xmodule.js
+++ b/cms/djangoapps/pipeline_js/js/xmodule.js
@@ -16,36 +16,44 @@ define(
window.$ = $;
window._ = _;
+ window.MathJax = {
+ tex: {
+ inlineMath: [
+ ['$', '$'],
+ ['\\(', '\\)'],
+ ['[mathjaxinline]', '[/mathjaxinline]']
+ ],
+ displayMath: [
+ ['$$', '$$'],
+ ['\\[', '\\]'],
+ ['[mathjax]', '[/mathjax]']
+ ],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ // autocollapse: false, // Not found in v3
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
+ }
+ };
$script(
- 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js'
- + '?config=TeX-MML-AM_SVG&delayStartupUntil=configured',
+ 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js',
'mathjax',
function() {
- window.MathJax.Hub.Config({
- tex2jax: {
- inlineMath: [
- ['\\(', '\\)'],
- ['[mathjaxinline]', '[/mathjaxinline]']
- ],
- displayMath: [
- ['\\[', '\\]'],
- ['[mathjax]', '[/mathjax]']
- ]
- },
- CommonHTML: {linebreaks: {automatic: true}},
- SVG: {linebreaks: {automatic: true}},
- 'HTML-CSS': {linebreaks: {automatic: true}},
- });
-
- // In order to eliminate all flashing during interactive
- // preview, it is necessary to set processSectionDelay to 0
- // (remove delay between input and output phases). This
- // effectively disables fast preview, regardless of
- // the fast preview setting as shown in the context menu.
- window.MathJax.Hub.processSectionDelay = 0;
- window.MathJax.Hub.Configured();
-
window.addEventListener('resize', MJrenderer);
let t = -1;
@@ -60,7 +68,8 @@ define(
if (oldWidth !== document.documentElement.scrollWidth) {
t = window.setTimeout(function() {
oldWidth = document.documentElement.scrollWidth;
- MathJax.Hub.Queue(['Rerender', MathJax.Hub]);
+ MathJax.typesetClear();
+ MathJax.typesetPromise();
t = -1;
}, delay);
}
diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js
index d2bc6341ebba..0d3df4487e84 100644
--- a/cms/static/cms/js/require-config.js
+++ b/cms/static/cms/js/require-config.js
@@ -47,19 +47,36 @@
}
});
- // MathJax Fast Preview was introduced in 2.5. However, it
- // causes undesirable flashing/font size changes when
- // MathJax is used for interactive preview (equation editor).
- // Setting processSectionDelay to 0 (see below) fully eliminates
- // fast preview, but to reduce confusion, we are also setting
- // the option as displayed in the context menu to false.
- // When upgrading to 2.6, check if this variable name changed.
window.MathJax = {
- menuSettings: {
- CHTMLpreview: false,
- collapsible: true,
- autocollapse: false,
- explorer: true
+ tex: {
+ inlineMath: [
+ ['$', '$'],
+ ['\\(', '\\)'],
+ ['[mathjaxinline]', '[/mathjaxinline]']
+ ],
+ displayMath: [
+ ['$$', '$$'],
+ ['\\[', '\\]'],
+ ['[mathjax]', '[/mathjax]']
+ ],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
}
};
};
@@ -137,7 +154,7 @@
'jquery_extend_patch': 'js/src/jquery_extend_patch',
// externally hosted files
- mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len
+ mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js?noext',
'youtube': [
// youtube URL does not end in '.js'. We add '?noext' to the path so
// that require.js adds the '.js' to the query component of the URL,
@@ -249,27 +266,6 @@
},
'mathjax': {
exports: 'MathJax',
- init: function() {
- window.MathJax.Hub.Config({
- tex2jax: {
- inlineMath: [
- ['\\(', '\\)'],
- ['[mathjaxinline]', '[/mathjaxinline]']
- ],
- displayMath: [
- ['\\[', '\\]'],
- ['[mathjax]', '[/mathjax]']
- ]
- }
- });
- // In order to eliminate all flashing during interactive
- // preview, it is necessary to set processSectionDelay to 0
- // (remove delay between input and output phases). This
- // effectively disables fast preview, regardless of
- // the fast preview setting as shown in the context menu.
- window.MathJax.Hub.processSectionDelay = 0;
- window.MathJax.Hub.Configured();
- }
},
'URI': {
exports: 'URI'
diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js
index 019fb2b67887..ba8a45d18c21 100644
--- a/cms/static/cms/js/spec/main.js
+++ b/cms/static/cms/js/spec/main.js
@@ -19,6 +19,39 @@
window.edx.HtmlUtils = HtmlUtils;
window.edx.StringUtils = StringUtils;
});
+
+ window.MathJax = {
+ tex: {
+ inlineMath: [
+ ['$', '$'],
+ ['\\(', '\\)'],
+ ['[mathjaxinline]', '[/mathjaxinline]']
+ ],
+ displayMath: [
+ ['$$', '$$'],
+ ['\\[', '\\]'],
+ ['[mathjax]', '[/mathjax]']
+ ],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
+ }
+ };
}
requirejs.config({
@@ -69,7 +102,7 @@
'domReady': 'xmodule_js/common_static/js/vendor/domReady',
'URI': 'xmodule_js/common_static/js/vendor/URI.min',
'mock-ajax': 'xmodule_js/common_static/js/vendor/mock-ajax',
- mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len
+ mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js?noext',
'youtube': '//www.youtube.com/player_api?noext',
'js/src/ajax_prefix': 'xmodule_js/common_static/js/src/ajax_prefix',
'js/spec/test_utils': 'js/spec/test_utils'
@@ -172,15 +205,6 @@
},
'mathjax': {
exports: 'MathJax',
- init: function() {
- window.MathJax.Hub.Config({
- tex2jax: {
- inlineMath: [['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]']],
- displayMath: [['\\[', '\\]'], ['[mathjax]', '[/mathjax]']]
- }
- });
- return window.MathJax.Hub.Configured();
- }
},
'accessibility': {
exports: 'accessibility',
diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js
index ace0f36b1f2d..e2863ef72438 100644
--- a/cms/static/cms/js/spec/main_squire.js
+++ b/cms/static/cms/js/spec/main_squire.js
@@ -5,6 +5,40 @@
'use strict';
var i, specHelpers, testFiles;
+ if (window) {
+ window.MathJax = {
+ tex: {
+ inlineMath: [
+ ['$', '$'],
+ ['\\(', '\\)'],
+ ['[mathjaxinline]', '[/mathjaxinline]']
+ ],
+ displayMath: [
+ ['$$', '$$'],
+ ['\\[', '\\]'],
+ ['[mathjax]', '[/mathjax]']
+ ],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
+ }
+ };
+ }
requirejs.config({
baseUrl: '/base/',
@@ -48,7 +82,7 @@
'draggabilly': 'xmodule_js/common_static/js/vendor/draggabilly',
'domReady': 'xmodule_js/common_static/js/vendor/domReady',
'URI': 'xmodule_js/common_static/js/vendor/URI.min',
- mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG&delayStartupUntil=configured', // eslint-disable-line max-len
+ mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js?noext',
'youtube': '//www.youtube.com/player_api?noext',
'js/src/ajax_prefix': 'xmodule_js/common_static/js/src/ajax_prefix'
},
@@ -142,15 +176,6 @@
},
'mathjax': {
exports: 'MathJax',
- init: function() {
- window.MathJax.Hub.Config({
- tex2jax: {
- inlineMath: [['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]']],
- displayMath: [['\\[', '\\]'], ['[mathjax]', '[/mathjax]']]
- }
- });
- window.MathJax.Hub.Configured();
- }
},
'URI': {
exports: 'URI'
diff --git a/cms/static/sass/studio-main-v1.scss b/cms/static/sass/studio-main-v1.scss
index a325010a7c61..9c5b4cbf4818 100644
--- a/cms/static/sass/studio-main-v1.scss
+++ b/cms/static/sass/studio-main-v1.scss
@@ -20,3 +20,16 @@
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
@import 'build-v1'; // shared app style assets/rendering
@import '_builtin-block-variables';
+
+
+mjx-container {
+ max-width: 100%;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+table>tbody>tr>td>mjx-container {
+ max-width: inherit;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
diff --git a/common/static/common/js/discussion/mathjax_include.js b/common/static/common/js/discussion/mathjax_include.js
index 074354566e1b..17a0d7817e8e 100644
--- a/common/static/common/js/discussion/mathjax_include.js
+++ b/common/static/common/js/discussion/mathjax_include.js
@@ -1,56 +1,37 @@
-// See common/templates/mathjax_include.html for info on Fast Preview mode.
-var disableFastPreview = true,
- vendorScript;
if (typeof MathJax === 'undefined') {
- if (disableFastPreview) {
- window.MathJax = {
- menuSettings: {CHTMLpreview: false}
- };
- }
-
- vendorScript = document.createElement('script');
- vendorScript.onload = function() {
- 'use strict';
-
- var MathJax = window.MathJax,
- setMathJaxDisplayDivSettings;
- MathJax.Hub.Config({
- tex2jax: {
- inlineMath: [
- ['\\(', '\\)'],
- ['[mathjaxinline]', '[/mathjaxinline]']
- ],
- displayMath: [
- ['\\[', '\\]'],
- ['[mathjax]', '[/mathjax]']
- ]
- }
- });
- if (disableFastPreview) {
- MathJax.Hub.processSectionDelay = 0;
- }
- MathJax.Hub.signal.Interest(function(message) {
- if (message[0] === 'End Math') {
- setMathJaxDisplayDivSettings();
- }
- });
- setMathJaxDisplayDivSettings = function() {
- $('.MathJax_Display').each(function() {
- this.setAttribute('tabindex', '0');
- this.setAttribute('aria-live', 'off');
- this.removeAttribute('role');
- this.removeAttribute('aria-readonly');
- });
- };
- };
- // Automatic loading of Mathjax accessibility files
window.MathJax = {
- menuSettings: {
- collapsible: true,
- autocollapse: false,
- explorer: true
+ tex: {
+ inlineMath: [
+ ['$', '$'],
+ ['\\(', '\\)'],
+ ['[mathjaxinline]', '[/mathjaxinline]']
+ ],
+ displayMath: [
+ ['$$', '$$'],
+ ['\\[', '\\]'],
+ ['[mathjax]', '[/mathjax]']
+ ],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
}
};
- vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_HTMLorMML';
+ var vendorScript = document.createElement('script');
+ vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js';
document.body.appendChild(vendorScript);
}
diff --git a/common/static/common/js/discussion/utils.js b/common/static/common/js/discussion/utils.js
index 3f0a7a448438..23ff811648f1 100644
--- a/common/static/common/js/discussion/utils.js
+++ b/common/static/common/js/discussion/utils.js
@@ -493,8 +493,9 @@
};
DiscussionUtil.typesetMathJax = function(element) {
- if (typeof MathJax !== 'undefined' && MathJax !== null && typeof MathJax.Hub !== 'undefined') {
- MathJax.Hub.Queue(['Typeset', MathJax.Hub, element[0]]);
+ if (typeof MathJax !== 'undefined' && MathJax !== null && typeof MathJax.startup !== 'undefined') {
+ MathJax.startup.promise
+ .then(() => MathJax.typesetPromise([element[0]]));
}
};
diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html
index ead5a935ee94..262a3a5d4661 100644
--- a/common/templates/mathjax_include.html
+++ b/common/templates/mathjax_include.html
@@ -5,92 +5,78 @@
## MathJax configuration for Studio lives in
## cms/js/require-config.js.
-<%page args="disable_fast_preview=True" expression_filter="h"/>
-
## Avoid loading mathjax if already loaded on the page
+<%page expression_filter="h"/>
%if context.get('load_mathjax', True):
-%if disable_fast_preview:
+%if mathjax_mode is not Undefined and mathjax_mode == 'wiki':
-%endif
+ window.MathJax = {
+ tex: {
+ inlineMath: [ ['$','$'], ["\\(","\\)"]],
+ displayMath: [ ['$$','$$'], ["\\[","\\]"]],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
+ }
+ };
-%if mathjax_mode is not Undefined and mathjax_mode == 'wiki':
-
%else:
-
%endif
-
+
%endif
diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html
index 358765c85eb4..a129146d18ed 100644
--- a/common/templates/xblock_v2/xblock_iframe.html
+++ b/common/templates/xblock_v2/xblock_iframe.html
@@ -122,22 +122,40 @@
-
-
-
-
+
{{ fragment.head_html | safe }}
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 85a6d21d3cbd..045dc1d3ebae 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -2811,6 +2811,8 @@ def setUp(self):
display_name='Vertical with No Mathjax HTML',
)
MATHJAX_TAG_PAIRS = [
+ ("$", "$"),
+ ("$$", "$$"),
(r"\(", r"\)"),
(r"\[", r"\]"),
("[mathjaxinline]", "[/mathjaxinline]"),
@@ -2831,6 +2833,12 @@ def setUp(self):
display_name="HTML Without Mathjax",
data="
I talk about mathjax, but I have no actual Math!
",
)
+ self.html_with_lone_dollar = BlockFactory.create(
+ category='html',
+ parent_location=self.no_math_vertical.location,
+ display_name="HTML With Lone Dollar",
+ data="Price: $5
",
+ )
self.course_key = self.course.id
self.user = UserFactory(username='staff_user', profile__country='AX', is_staff=True)
@@ -2848,7 +2856,9 @@ def test_mathjax_detection(self):
url = reverse("render_xblock", kwargs={'usage_key_string': str(usage_key)})
response = self.client.get(url)
assert response.status_code == 200
- assert b"MathJax.Hub.Config" in response.content
+ assert b"window.MathJax" in response.content
+ assert b"['$', '$']" in response.content
+ assert b"['$$', '$$']" in response.content
# Check the one without Math...
url = reverse("render_xblock", kwargs={
@@ -2856,7 +2866,15 @@ def test_mathjax_detection(self):
})
response = self.client.get(url)
assert response.status_code == 200
- assert b"MathJax.Hub.Config" not in response.content
+ assert b"window.MathJax" not in response.content
+
+ # Check lone dollar sign isn't mistaken for MathJax
+ url = reverse("render_xblock", kwargs={
+ 'usage_key_string': str(self.html_with_lone_dollar.location)
+ })
+ response = self.client.get(url)
+ assert response.status_code == 200
+ assert b"window.MathJax" not in response.content
# The containing vertical should still return MathJax (for now)
url = reverse("render_xblock", kwargs={
@@ -2864,7 +2882,7 @@ def test_mathjax_detection(self):
})
response = self.client.get(url)
assert response.status_code == 200
- assert b"MathJax.Hub.Config" in response.content
+ assert b"window.MathJax" in response.content
@override_waffle_flag(COURSEWARE_OPTIMIZED_RENDER_XBLOCK, False)
def test_mathjax_detection_disabled(self):
@@ -2875,7 +2893,7 @@ def test_mathjax_detection_disabled(self):
})
response = self.client.get(url)
assert response.status_code == 200
- assert b"MathJax.Hub.Config" in response.content
+ assert b"window.MathJax" in response.content
@ddt.ddt
diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py
index 8ae58850920d..4aaa5f57aded 100644
--- a/lms/djangoapps/courseware/views/views.py
+++ b/lms/djangoapps/courseware/views/views.py
@@ -1802,6 +1802,8 @@ def has_mathjax_content(self):
# The following pairs are used to mark Mathjax syntax in XBlocks. There
# are other options for the wiki, but we don't worry about those here.
MATHJAX_TAG_PAIRS = [
+ ("$", "$"),
+ ("$$", "$$"),
(r"\(", r"\)"),
(r"\[", r"\]"),
("[mathjaxinline]", "[/mathjaxinline]"),
@@ -1810,7 +1812,8 @@ def has_mathjax_content(self):
content = self.fragment.body_html()
for (start_tag, end_tag) in MATHJAX_TAG_PAIRS:
if start_tag in content and end_tag in content:
- return True
+ if start_tag != end_tag or content.count(start_tag) >= 2:
+ return True
return False
diff --git a/lms/static/js/customwmd.js b/lms/static/js/customwmd.js
index 0f4cfccaeecb..8027cc15fb30 100644
--- a/lms/static/js/customwmd.js
+++ b/lms/static/js/customwmd.js
@@ -42,9 +42,6 @@ Mostly adapted from math.stackexchange.com: http://cdn.sstatic.net/js/mathjax-ed
MathJaxProcessor.prototype.processMath = function(start, last, preProcess) {
var block, i, j, ref, ref1;
block = this.blocks.slice(start, last + 1).join("").replace(/&/g, "&").replace(//g, ">");
- if (MathJax.Hub.Browser.isMSIE) {
- block = block.replace(/(%[^\n]*)\n/g, "$1 \n");
- }
for (i = j = ref = start + 1, ref1 = last; ref <= ref1 ? j <= ref1 : j >= ref1; i = ref <= ref1 ? ++j : --j) {
this.blocks[i] = "";
}
diff --git a/lms/static/js/mathjax_delay_renderer.js b/lms/static/js/mathjax_delay_renderer.js
index e7b10b4073e8..6517dff14a9c 100644
--- a/lms/static/js/mathjax_delay_renderer.js
+++ b/lms/static/js/mathjax_delay_renderer.js
@@ -13,8 +13,6 @@
MathJaxDelayRenderer.prototype.maxDelay = 3000;
- MathJaxDelayRenderer.prototype.mathjaxRunning = false;
-
MathJaxDelayRenderer.prototype.elapsedTime = 0;
MathJaxDelayRenderer.prototype.mathjaxDelay = 0;
@@ -59,8 +57,15 @@
if (preprocessor != null) {
text = preprocessor(text);
}
+ if (typeof MathJax !== 'undefined' && MathJax !== null && typeof MathJax.typesetClear === 'function') {
+ MathJax.typesetClear([$(elem)[0]]);
+ }
$(elem).html(text); // xss-lint: disable=javascript-jquery-html
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, $(elem).attr("id")]);
+ if (typeof MathJax !== 'undefined' && MathJax !== null && MathJax.startup &&
+ MathJax.startup.promise && typeof MathJax.typesetPromise === "function") {
+ return MathJax.startup.promise
+ .then(() => MathJax.typesetPromise([$(elem)[0]]));
+ }
} else {
if (this.mathjaxTimeout) {
window.clearTimeout(this.mathjaxTimeout);
@@ -70,31 +75,38 @@
renderer = (function(_this) {
return function() {
var curTime, prevTime;
- if (_this.mathjaxRunning) {
- return;
- }
prevTime = getTime();
if (preprocessor != null) {
text = preprocessor(text);
}
+ if (typeof MathJax !== 'undefined' && MathJax !== null && typeof MathJax.typesetClear === 'function') {
+ MathJax.typesetClear([_this.$buffer[0]]);
+ }
_this.$buffer.html(text); // xss-lint: disable=javascript-jquery-html
curTime = getTime();
_this.elapsedTime = curTime - prevTime;
- if (typeof MathJax !== "undefined" && MathJax !== null) {
+ if (typeof MathJax !== "undefined" && MathJax !== null && MathJax.startup &&
+ MathJax.startup.promise && typeof MathJax.typesetPromise === "function") {
prevTime = getTime();
- _this.mathjaxRunning = true;
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, _this.$buffer.attr("id")], function() {
- _this.mathjaxRunning = false;
- curTime = getTime();
- _this.mathjaxDelay = curTime - prevTime;
- if (previewSetter) {
- return previewSetter($(_this.$buffer).html());
- } else {
- return $(elem).html($(_this.$buffer).html()); // xss-lint: disable=javascript-jquery-html
- }
- });
+ return MathJax.startup.promise
+ .then(
+ () => MathJax.typesetPromise([_this.$buffer[0]]).then(() => {
+ curTime = getTime();
+ _this.mathjaxDelay = curTime - prevTime;
+ if (previewSetter) {
+ return previewSetter($(_this.$buffer).html());
+ } else {
+ return $(elem).html($(_this.$buffer).html()); // xss-lint: disable=javascript-jquery-html
+ }
+ })
+ );
} else {
- return _this.mathjaxDelay = 0;
+ _this.mathjaxDelay = 0;
+ if (previewSetter) {
+ return previewSetter($(_this.$buffer).html());
+ } else {
+ return $(elem).html($(_this.$buffer).html()); // xss-lint: disable=javascript-jquery-html
+ }
}
};
})(this);
diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js
index d8bc1417e86e..d46c45f57d00 100644
--- a/lms/static/lms/js/spec/main.js
+++ b/lms/static/lms/js/spec/main.js
@@ -5,6 +5,40 @@
'use strict';
var i, specHelpers, testFiles;
+ if (window) {
+ window.MathJax = {
+ tex: {
+ inlineMath: [
+ ['$', '$'],
+ ['\\(', '\\)'],
+ ['[mathjaxinline]', '[/mathjaxinline]']
+ ],
+ displayMath: [
+ ['$$', '$$'],
+ ['\\[', '\\]'],
+ ['[mathjax]', '[/mathjax]']
+ ],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
+ }
+ };
+ }
// TODO: how can we share the vast majority of this config that is in common with CMS?
requirejs.config({
@@ -56,7 +90,7 @@
'squire': 'common/js/vendor/Squire',
'jasmine-imagediff': 'xmodule_js/common_static/js/vendor/jasmine-imagediff',
'domReady': 'xmodule_js/common_static/js/vendor/domReady',
- mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_HTMLorMML&delayStartupUntil=configured', // eslint-disable-line max-len
+ mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js?noext',
'youtube': '//www.youtube.com/player_api?noext',
'js/src/ajax_prefix': 'xmodule_js/common_static/js/src/ajax_prefix',
'js/instructor_dashboard/student_admin': 'js/instructor_dashboard/student_admin',
@@ -234,15 +268,6 @@
},
'mathjax': {
exports: 'MathJax',
- init: function() {
- MathJax.Hub.Config({
- tex2jax: {
- inlineMath: [['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]']],
- displayMath: [['\\[', '\\]'], ['[mathjax]', '[/mathjax]']]
- }
- });
- return MathJax.Hub.Configured();
- }
},
'URI': {
exports: 'URI'
diff --git a/lms/static/sass/lms-course.scss b/lms/static/sass/lms-course.scss
index 738d892257dc..1923d8a321b1 100644
--- a/lms/static/sass/lms-course.scss
+++ b/lms/static/sass/lms-course.scss
@@ -34,3 +34,15 @@
width: 54px !important;
}
}
+
+mjx-container {
+ max-width: 100%;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+table>tbody>tr>td>mjx-container {
+ max-width: inherit;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
diff --git a/lms/templates/courseware/courseware-chromeless.html b/lms/templates/courseware/courseware-chromeless.html
index deeda26c431d..e45b586f4dcb 100644
--- a/lms/templates/courseware/courseware-chromeless.html
+++ b/lms/templates/courseware/courseware-chromeless.html
@@ -68,7 +68,7 @@
<%static:js group='courseware'/>
% if enable_mathjax:
- <%include file="/mathjax_include.html" args="disable_fast_preview=True"/>
+ <%include file="/mathjax_include.html" />
% endif
% if staff_access:
<%include file="xqa_interface.html"/>
diff --git a/lms/templates/courseware/static_tab.html b/lms/templates/courseware/static_tab.html
index cf79fb8948f7..cbce51b62223 100644
--- a/lms/templates/courseware/static_tab.html
+++ b/lms/templates/courseware/static_tab.html
@@ -18,7 +18,7 @@
%block>
<%block name="footer_extra">
-<%include file="/mathjax_include.html" args="disable_fast_preview=True"/>
+<%include file="/mathjax_include.html" />
${HTML(fragment.foot_html())}
%block>
diff --git a/lms/templates/courseware/tab-view.html b/lms/templates/courseware/tab-view.html
index 319861c8ffb6..07d982a25359 100644
--- a/lms/templates/courseware/tab-view.html
+++ b/lms/templates/courseware/tab-view.html
@@ -26,7 +26,7 @@
%block>
<%block name="footer_extra">
-<%include file="/mathjax_include.html" args="disable_fast_preview=True"/>
+<%include file="/mathjax_include.html" />
${HTML(fragment.foot_html())}
%block>
diff --git a/lms/templates/discussion/_js_body_dependencies.html b/lms/templates/discussion/_js_body_dependencies.html
index d10b6918b62d..bd91f8cc9fd4 100644
--- a/lms/templates/discussion/_js_body_dependencies.html
+++ b/lms/templates/discussion/_js_body_dependencies.html
@@ -1,14 +1,14 @@
## mako
-<%namespace name='static' file='/static_content.html'/>
+<%page expression_filter="h"/>
-<%page args="disable_fast_preview=True" expression_filter="h"/>
+<%namespace name='static' file='/static_content.html'/>
<%!
from openedx.core.djangolib.js_utils import js_escaped_string
%>
-<%include file="/mathjax_include.html" args="disable_fast_preview=disable_fast_preview"/>
+<%include file="/mathjax_include.html"/>
<%static:js group='discussion'/>
diff --git a/xmodule/js/spec/capa/display_spec.js b/xmodule/js/spec/capa/display_spec.js
index f44d3c185168..5cc9985c5467 100644
--- a/xmodule/js/spec/capa/display_spec.js
+++ b/xmodule/js/spec/capa/display_spec.js
@@ -10,11 +10,28 @@ describe("Problem", function () {
beforeEach(function () {
// Stub MathJax
window.MathJax = {
- Hub: jasmine.createSpyObj("MathJax.Hub", ["getAllJax", "Queue"]),
- Callback: jasmine.createSpyObj("MathJax.Callback", ["After"]),
+ startup: {
+ document: jasmine.createSpyObj("MathJax.startup.document", ["getMathItemsWithin"]),
+ promise: jasmine.createSpyObj("MathJax.startup.promise", ["then"]),
+ toMML: jasmine.createSpy("MathJax.startup.toMML"),
+ },
+ typesetClear: jasmine.createSpy("MathJax.typesetClear"),
+ typesetPromise: jasmine.createSpy("MathJax.typesetPromise"),
};
- this.stubbedJax = { root: jasmine.createSpyObj("jax.root", ["toMathML"]) };
- MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
+ MathJax.startup.promise.then.and.callFake(function (callback) {
+ callback();
+ return Promise.resolve();
+ });
+ MathJax.typesetPromise.and.callFake(function () {
+ return {
+ then(callback) {
+ callback();
+ return Promise.resolve();
+ },
+ };
+ });
+ this.stubbedJax = { root: jasmine.createSpy("jax.root") };
+ MathJax.startup.document.getMathItemsWithin.and.returnValue([this.stubbedJax]);
window.update_schematics = function () {};
spyOn(SR, "readText");
spyOn(SR, "readTexts");
@@ -57,11 +74,11 @@ data-url='/problem/quiz/'> \
describe("bind", function () {
beforeEach(function () {
spyOn(window, "update_schematics");
- MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
+ MathJax.startup.document.getMathItemsWithin.and.returnValue([this.stubbedJax]);
this.problem = new Problem($(".xblock-student_view"));
});
- it("set mathjax typeset", () => expect(MathJax.Hub.Queue).toHaveBeenCalled());
+ it("set mathjax typeset", () => expect(MathJax.typesetPromise).toHaveBeenCalled());
it("update schematics", () => expect(window.update_schematics).toHaveBeenCalled());
@@ -88,12 +105,22 @@ data-url='/problem/quiz/'> \
it("bind the math input", function () {
expect($("input.math")).toHandleWith("keyup", this.problem.refreshMath);
});
+
+ it("bind the formulaequationinput", function () {
+ this.problem.el.find(".problem").prepend(
+ '' +
+ ' ' +
+ '
'
+ );
+ this.problem.bind();
+ expect($("#input_fe_bind")).toHandleWith("keyup", this.problem.refreshMath);
+ });
});
describe("bind_with_custom_input_id", function () {
beforeEach(function () {
spyOn(window, "update_schematics");
- MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
+ MathJax.startup.document.getMathItemsWithin.and.returnValue([this.stubbedJax]);
this.problem = new Problem($(".xblock-student_view"));
return $(this).html(readFixtures("problem_content_1240.html"));
});
@@ -994,31 +1021,90 @@ data-url='/problem/quiz/'> \
describe("refreshMath", function () {
beforeEach(function () {
this.problem = new Problem($(".xblock-student_view"));
- // Reset Queue spy so that bind()'s Queue call ([fn, null, domEl]) is not
- // included when toHaveBeenCalledWith scans recorded calls. In Jasmine 2.99,
- // toHaveBeenCalledWith iterates ALL recorded calls' args element-by-element
- // (even mismatched ones, for diff output). jasmine-jquery's custom equality
- // tester calls $(domEl).is(anyString) when comparing a DOM node against a
- // string — which throws a Sizzle syntax error if the string isn't a valid
- // CSS selector (e.g. "E=mc^2"). Resetting here isolates this describe to
- // testing only what refreshMath itself queues.
- MathJax.Hub.Queue.calls.reset();
+ MathJax.typesetClear.calls.reset();
+ MathJax.typesetPromise.calls.reset();
+ MathJax.startup.document.getMathItemsWithin.calls.reset();
+ MathJax.startup.document.getMathItemsWithin.and.returnValue([this.stubbedJax]);
$("#input_example_1").val("E=mc^2");
this.problem.refreshMath({ target: $("#input_example_1").get(0) });
});
- it("should queue the conversion and MathML element update", function () {
- expect(MathJax.Hub.Queue).toHaveBeenCalledWith(
- ["Text", this.stubbedJax, "E=mc^2"],
- [this.problem.updateMathML, this.stubbedJax, $("#input_example_1").get(0)],
- );
+ it("should clear and typeset the math element", function () {
+ var math = document.querySelector("#display_example_1");
+ expect(MathJax.typesetClear).toHaveBeenCalledWith([math]);
+ expect(MathJax.typesetPromise).toHaveBeenCalledWith([math]);
+ });
+
+ it("wraps calculator input in backticks for input.math", function () {
+ var math = document.querySelector("#display_example_1");
+ expect(math.textContent).toBe("`E=mc^2`");
+ });
+ });
+
+ describe("refreshMath formula and delimiter handling", function () {
+ var formulaHTML = '' +
+ '';
+
+ beforeEach(function () {
+ this.problem = new Problem($(".xblock-student_view"));
+ MathJax.typesetClear.calls.reset();
+ MathJax.typesetPromise.calls.reset();
+ MathJax.startup.document.getMathItemsWithin.calls.reset();
+ MathJax.startup.document.getMathItemsWithin.and.returnValue([this.stubbedJax]);
+ this.problem.el.find(".problem").prepend(formulaHTML);
+ });
+
+ it("wraps calculator syntax in backticks for formulaequationinput", function () {
+ $("#input_test_fe").val("R_1*R_2/R_3");
+ this.problem.refreshMath({ target: $("#input_test_fe").get(0) });
+ var math = document.querySelector("#input_test_fe_preview");
+ expect(math.textContent).toBe("`R_1*R_2/R_3`");
+ });
+
+ it("preserves explicit TeX delimiters \\(...\\)", function () {
+ $("#input_test_fe").val("\\\(E=mc^2\\\)");
+ this.problem.refreshMath({ target: $("#input_test_fe").get(0) });
+ var math = document.querySelector("#input_test_fe_preview");
+ expect(math.textContent).toBe("\\\(E=mc^2\\\)");
+ });
+
+ it("preserves display math TeX delimiters \\[\\]", function () {
+ $("#input_test_fe").val("\\\[E=mc^2\\\]");
+ this.problem.refreshMath({ target: $("#input_test_fe").get(0) });
+ var math = document.querySelector("#input_test_fe_preview");
+ expect(math.textContent).toBe("\\\[E=mc^2\\\]");
+ });
+
+ it("preserves double-dollar delimiters", function () {
+ $("#input_test_fe").val("$$E=mc^2$$").trigger("keyup");
+ this.problem.refreshMath({ target: $("#input_test_fe").get(0) });
+ var math = document.querySelector("#input_test_fe_preview");
+ expect(math.textContent).toBe("$$E=mc^2$$");
+ });
+
+ it("preserves single-dollar delimiters", function () {
+ $("#input_test_fe").val("$E=mc^2$").trigger("keyup");
+ this.problem.refreshMath({ target: $("#input_test_fe").get(0) });
+ var math = document.querySelector("#input_test_fe_preview");
+ expect(math.textContent).toBe("$E=mc^2$");
+ });
+
+ it("clears preview and skips typesetPromise on empty input", function () {
+ $("#input_test_fe").val("");
+ this.problem.refreshMath({ target: $("#input_test_fe").get(0) });
+ var math = document.querySelector("#input_test_fe_preview");
+ expect(math.textContent).toBe("");
+ expect(MathJax.typesetPromise).not.toHaveBeenCalled();
});
});
describe("updateMathML", function () {
beforeEach(function () {
this.problem = new Problem($(".xblock-student_view"));
- this.stubbedJax.root.toMathML.and.returnValue("");
+ MathJax.startup.toMML.and.returnValue("");
});
describe("when there is no exception", function () {
@@ -1033,12 +1119,14 @@ data-url='/problem/quiz/'> \
beforeEach(function () {
const error = new Error();
error.restart = true;
- this.stubbedJax.root.toMathML.and.throwError(error);
+ MathJax.startup.toMML.and.throwError(error);
+ spyOn(this.problem, "refreshMath");
this.problem.updateMathML(this.stubbedJax, $("#input_example_1").get(0));
});
it("should queue up the exception", function () {
- expect(MathJax.Callback.After).toHaveBeenCalledWith([this.problem.refreshMath, this.stubbedJax], true);
+ expect(MathJax.startup.promise.then).toHaveBeenCalled();
+ expect(this.problem.refreshMath).toHaveBeenCalledWith(null, $("#input_example_1").get(0));
});
});
});
diff --git a/xmodule/js/src/capa/display.js b/xmodule/js/src/capa/display.js
index fea25a654049..756a771e4dfe 100644
--- a/xmodule/js/src/capa/display.js
+++ b/xmodule/js/src/capa/display.js
@@ -20,6 +20,31 @@
return -1;
};
+ /**
+ * Check whether MathJax has been fully loaded and initialized (not just the config object).
+ * In v3/v4, startup.promise is only set after the MathJax component loader completes.
+ */
+ var isMathJaxTypesetReady = function () {
+ return typeof MathJax !== "undefined" && MathJax !== null &&
+ typeof MathJax.startup !== "undefined" && MathJax.startup !== null &&
+ typeof MathJax.startup.promise !== "undefined" &&
+ typeof MathJax.typesetPromise === "function";
+ };
+
+ var isMathJaxRefreshReady = function () {
+ return isMathJaxTypesetReady() &&
+ typeof MathJax.typesetClear === "function" &&
+ typeof MathJax.startup.document !== "undefined" && MathJax.startup.document !== null &&
+ typeof MathJax.startup.document.getMathItemsWithin === "function";
+ };
+
+ var isMathJaxMathMLReady = function () {
+ return typeof MathJax !== "undefined" && MathJax !== null &&
+ typeof MathJax.startup !== "undefined" && MathJax.startup !== null &&
+ typeof MathJax.startup.promise !== "undefined" &&
+ typeof MathJax.startup.toMML === "function";
+ };
+
this.Problem = function () {
function Problem(element) {
var that = this;
@@ -164,9 +189,9 @@
Problem.prototype.bind = function () {
var problemPrefix,
that = this;
- if (typeof MathJax !== "undefined" && MathJax !== null) {
+ if (isMathJaxTypesetReady()) {
this.el.find(".problem > div").each(function (index, element) {
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
+ return MathJax.startup.promise.then(() => MathJax.typesetPromise([element]));
});
}
if (window.hasOwnProperty("update_schematics")) {
@@ -215,10 +240,10 @@
this.submitAnswersAndSubmitButton(true);
}
Collapsible.setCollapsibles(this.el);
- this.$("input.math").keyup(this.refreshMath);
- if (typeof MathJax !== "undefined" && MathJax !== null) {
- this.$("input.math").each(function (index, element) {
- return MathJax.Hub.Queue([that.refreshMath, null, element]);
+ this.$("input.math, .formulaequationinput input").keyup(this.refreshMath);
+ if (isMathJaxTypesetReady()) {
+ this.$("input.math, .formulaequationinput input").each(function (index, element) {
+ return MathJax.startup.promise.then(() => that.refreshMath(null, element));
});
}
};
@@ -820,9 +845,9 @@
}
return results;
});
- if (typeof MathJax !== "undefined" && MathJax !== null) {
+ if (isMathJaxTypesetReady()) {
that.el.find(".problem > div").each(function (index, element) {
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
+ return MathJax.startup.promise.then(() => MathJax.typesetPromise([element]));
});
}
that.el.find(".show").attr("disabled", "disabled");
@@ -876,35 +901,78 @@
};
Problem.prototype.refreshMath = function (event, element) {
- var elid, eqn, jax, mathjaxPreprocessor, preprocessorTag, target;
+ var elid,
+ eqn,
+ jax,
+ mathjaxPreprocessor,
+ preprocessorTag,
+ isTexDelimited;
if (!element) {
element = event.target; // eslint-disable-line no-param-reassign
}
elid = element.id.replace(/^input_/, "");
- target = "display_" + elid;
-
// MathJax preprocessor is loaded by 'setupInputTypes'
preprocessorTag = "inputtype_" + elid;
mathjaxPreprocessor = this.inputtypeDisplays[preprocessorTag];
- if (typeof MathJax !== "undefined" && MathJax !== null && MathJax.Hub.getAllJax(target)[0]) {
- jax = MathJax.Hub.getAllJax(target)[0];
+ if (isMathJaxRefreshReady()) {
+ var math = document.getElementById("display_" + elid);
+ if (!math) {
+ math = document.getElementById(element.id + "_preview");
+ }
+ if (!math) {
+ return;
+ }
eqn = $(element).val();
if (mathjaxPreprocessor) {
eqn = mathjaxPreprocessor(eqn);
}
- MathJax.Hub.Queue(["Text", jax, eqn], [this.updateMathML, jax, element]);
+ MathJax.typesetClear([math]);
+ if (!eqn) {
+ math.textContent = "";
+ var dynInput = document.getElementById(element.id + "_dynamath");
+ if (dynInput) { dynInput.value = ""; }
+ return;
+ }
+ // Determine rendering mode:
+ // isTexDelimited -> preserve user-written TeX delimiters as-is
+ // else -> calculator/AsciiMath syntax via backtick delimiters
+ isTexDelimited = (
+ (/^\s*\\\(/.test(eqn) && /\\\)\s*$/.test(eqn)) ||
+ (/^\s*\\\[/.test(eqn) && /\\\]\s*$/.test(eqn)) ||
+ (/^\s*\$\$/.test(eqn) && /\$\$\s*$/.test(eqn)) ||
+ (/^\s*\$[^$]/.test(eqn) && /[^$]\$\s*$/.test(eqn))
+ );
+ if (isTexDelimited) {
+ math.textContent = eqn;
+ } else {
+ // Calculator/AsciiMath syntax: backtick-delimited for AsciiMath input jax
+ math.textContent = "`" + eqn + "`";
+ }
+ MathJax.typesetPromise([math]).then(function () {
+ jax = MathJax.startup.document.getMathItemsWithin(math)[0];
+ if (jax) {
+ this.updateMathML(jax, element);
+ }
+ }.bind(this)).catch(function () {
+ var dynInput = document.getElementById(element.id + "_dynamath");
+ if (dynInput) { dynInput.value = ""; }
+ });
}
};
Problem.prototype.updateMathML = function (jax, element) {
- try {
- $("#" + element.id + "_dynamath").val(jax.root.toMathML(""));
- } catch (exception) {
- if (!exception.restart) {
- throw exception;
- }
- if (typeof MathJax !== "undefined" && MathJax !== null) {
- MathJax.Callback.After([this.refreshMath, jax], exception.restart);
+ if (isMathJaxMathMLReady()) {
+ try {
+ var dynInput = document.getElementById(element.id + "_dynamath");
+ if (dynInput) { dynInput.value = MathJax.startup.toMML(jax.root); }
+ } catch (exception) {
+ if (!exception.restart) {
+ throw exception;
+ }
+ MathJax.startup.promise
+ .then(function () {
+ this.refreshMath(null, element);
+ }.bind(this));
}
}
};
@@ -1391,7 +1459,10 @@
hintMsgContainer = that.$(".problem-hint .notification-message");
hintContainer.attr("hint_index", response.hint_index);
edx.HtmlUtils.setHtml(hintMsgContainer, edx.HtmlUtils.HTML(response.msg));
- MathJax.Hub.Queue(["Typeset", MathJax.Hub, hintContainer[0]]);
+ if (isMathJaxTypesetReady()) {
+ MathJax.startup.promise
+ .then(() => MathJax.typesetPromise([hintContainer[0]]));
+ }
if (response.should_enable_next_hint) {
that.hintButton.removeAttr("disabled");
} else {
diff --git a/xmodule/js/src/html/display.js b/xmodule/js/src/html/display.js
index ec3e0f3097fb..b94d86058788 100644
--- a/xmodule/js/src/html/display.js
+++ b/xmodule/js/src/html/display.js
@@ -7,8 +7,10 @@
this.el = $(this.element);
JavascriptLoader.executeModuleScripts(this.el);
Collapsible.setCollapsibles(this.el);
- if (typeof MathJax !== "undefined" && MathJax !== null) {
- MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.el[0]]);
+ if (typeof MathJax !== "undefined" && MathJax !== null && MathJax.startup &&
+ MathJax.startup.promise && typeof MathJax.typesetPromise === "function") {
+ MathJax.startup.promise
+ .then(() => MathJax.typesetPromise([this.el[0]]));
}
if (typeof setupFullScreenModal !== "undefined" && setupFullScreenModal !== null) {
setupFullScreenModal();