From e6c9a208219a164c924adc27ca1a65f68fd45b65 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 18 Oct 2023 20:09:33 +0530 Subject: [PATCH 1/8] feat: mathjax upgrade v2 to v3 fix: use asciimath test: fix mathjax related tests fix: lint issues fix: mathjax_delay_renderer test: fix mathjax related tests --- cms/djangoapps/pipeline_js/js/xmodule.js | 61 +- cms/static/cms/js/require-config.js | 62 +- cms/static/cms/js/spec/main.js | 42 +- cms/static/cms/js/spec/main_squire.js | 43 +- cms/static/sass/studio-main-v1.scss | 9 + .../common/js/discussion/mathjax_include.js | 62 +- common/static/common/js/discussion/utils.js | 5 +- .../spec/formula_equation_preview_spec.js | 123 +- .../js/capa/src/formula_equation_preview.js | 36 +- common/templates/mathjax_include.html | 133 +- common/templates/xblock_v2/xblock_iframe.html | 78 +- lms/djangoapps/courseware/tests/test_views.py | 8 +- lms/static/js/customwmd.js | 3 - lms/static/js/mathjax_delay_renderer.js | 33 +- lms/static/lms/js/spec/main.js | 43 +- lms/static/sass/lms-course.scss | 8 + .../courseware/courseware-chromeless.html | 2 +- lms/templates/courseware/static_tab.html | 2 +- lms/templates/courseware/tab-view.html | 2 +- .../discussion/_js_body_dependencies.html | 6 +- xmodule/assets/capa/_display.scss | 1746 +++++++++++++++++ xmodule/js/spec/capa/display_spec.js | 28 +- xmodule/js/src/capa/display.js | 47 +- xmodule/js/src/html/display.js | 3 +- 24 files changed, 2180 insertions(+), 405 deletions(-) create mode 100644 xmodule/assets/capa/_display.scss diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index 8a19355c1a93..558c8824fd66 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -16,36 +16,42 @@ 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@3.2.1/es5/tex-mml-svg.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 +66,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..b9f63b17f9f8 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -47,19 +47,34 @@ } }); - // 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 +152,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@3.2.1/es5/tex-mml-svg.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 +264,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 e56b95ef2826..32eca42d6ab5 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -19,6 +19,37 @@ 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 +100,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@3.2.1/es5/tex-mml-svg.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 +203,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 8feb05692273..6c782cb5e17b 100644 --- a/cms/static/cms/js/spec/main_squire.js +++ b/cms/static/cms/js/spec/main_squire.js @@ -5,6 +5,38 @@ '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 +80,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@3.2.1/es5/tex-mml-svg.js?noext', 'youtube': '//www.youtube.com/player_api?noext', 'js/src/ajax_prefix': 'xmodule_js/common_static/js/src/ajax_prefix' }, @@ -142,15 +174,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..141f5b8f5e86 100644 --- a/cms/static/sass/studio-main-v1.scss +++ b/cms/static/sass/studio-main-v1.scss @@ -20,3 +20,12 @@ @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'; + + +.MathJax>svg { + max-width: 100%, +} + +table>tbody>tr>td>.MathJax>svg { + max-width: inherit +} diff --git a/common/static/common/js/discussion/mathjax_include.js b/common/static/common/js/discussion/mathjax_include.js index 074354566e1b..0cf5f5e8f657 100644 --- a/common/static/common/js/discussion/mathjax_include.js +++ b/common/static/common/js/discussion/mathjax_include.js @@ -1,21 +1,11 @@ -// See common/templates/mathjax_include.html for info on Fast Preview mode. -var disableFastPreview = true, - vendorScript; +var 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: { + window.MathJax = { + tex: { inlineMath: [ ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] @@ -23,34 +13,28 @@ if (typeof MathJax === 'undefined') { displayMath: [ ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] - ] - } - }); - if (disableFastPreview) { - MathJax.Hub.processSectionDelay = 0; - } - MathJax.Hub.signal.Interest(function(message) { - if (message[0] === 'End Math') { - setMathJaxDisplayDivSettings(); + ], + 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'] } - }); - 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 - } - }; - vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_HTMLorMML'; + vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-mml-svg.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/static/js/capa/spec/formula_equation_preview_spec.js b/common/static/js/capa/spec/formula_equation_preview_spec.js index dc4e1ff9d594..7c9acda677d7 100644 --- a/common/static/js/capa/spec/formula_equation_preview_spec.js +++ b/common/static/js/capa/spec/formula_equation_preview_spec.js @@ -84,20 +84,25 @@ describe('Formula Equation Preview', function() { this.jax = 'OUTPUT_JAX'; this.oldMathJax = window.MathJax; - window.MathJax = {Hub: {}}; - window.MathJax.Hub.getAllJax = jasmine.createSpy('MathJax.Hub.getAllJax') - .and.returnValue([this.jax]); - window.MathJax.Hub.Queue = function(callback) { - if (typeof callback === 'function') { - callback(); - } + window.MathJax = { + startup: { + document: { + getMathItemsWithin: {} + }, + promise: { + then: {} + } + }, + typesetPromise: {}, + typesetClear: jasmine.createSpy('MathJax.typesetClear'), }; - spyOn(window.MathJax.Hub, 'Queue').and.callThrough(); - window.MathJax.Hub.Startup = jasmine.createSpy('MathJax.Hub.Startup'); - window.MathJax.Hub.Startup.signal = jasmine.createSpy('MathJax.Hub.Startup.signal'); - window.MathJax.Hub.Startup.signal.Interest = function(callback) { - callback('End'); + var returnMock = { + then: (callback) => callback(), }; + spyOn(window.MathJax, 'typesetPromise').and.returnValue(returnMock); + window.MathJax.startup.promise = returnMock; + spyOn(window.MathJax.startup.promise, 'then').and.callThrough(); + spyOn(window.MathJax.startup.document, 'getMathItemsWithin').and.returnValue([this.jax]); }); it('(the test) is able to swap out the behavior of $', function() { @@ -184,7 +189,7 @@ describe('Formula Equation Preview', function() { jasmine.waitUntil(function() { // (Short circuit if `inputAjax` is indeed called) return window.Problem.inputAjax.calls.count() > 0 - || window.MathJax.Hub.Queue.calls.count() > 0; + || window.MathJax.typesetPromise.calls.count() > 0; }).then(function() { // Expect the request not to have been called. expect(window.Problem.inputAjax).not.toHaveBeenCalled(); @@ -257,6 +262,7 @@ describe('Formula Equation Preview', function() { formulaEquationPreview.enable(); var jax = this.jax; + var $img = $('img.loading'); jasmine.waitUntil(function() { return window.Problem.inputAjax.calls.count() > 0; @@ -269,49 +275,16 @@ describe('Formula Equation Preview', function() { }); // The only request returned--it should hide the loading icon. - expect($('img.loading').css('visibility')).toEqual('hidden'); + expect($img.css('visibility')).toEqual('hidden'); // We should look in the preview div for the MathJax. var previewDiv = $('#input_THE_ID_preview')[0]; - expect(window.MathJax.Hub.getAllJax).toHaveBeenCalledWith(previewDiv); - - // Refresh the MathJax. - expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith( - ['Text', jax, 'THE_FORMULA'] - ); - }).always(done); - }); - - it('finds alternatives if MathJax hasn\'t finished loading', function(done) { - formulaEquationPreview.enable(); - $('#input_THE_ID').val('user_input').trigger('input'); - - jasmine.waitUntil(function() { - return window.Problem.inputAjax.calls.count() > 0; - }).then(function() { - var args = window.Problem.inputAjax.calls.mostRecent().args; - var callback = args[4]; - - // Cannot find MathJax. - window.MathJax.Hub.getAllJax.and.returnValue([]); - spyOn(console, 'log'); - - callback({ - preview: 'THE_FORMULA', - request_start: args[3].request_start - }); - - // Tests. - expect(console.log).toHaveBeenCalled(); - - // We should look in the preview div for the MathJax. - var previewElement = $('#input_THE_ID_preview')[0]; - expect(previewElement.firstChild.data).toEqual('\\(THE_FORMULA\\)'); // Refresh the MathJax. - expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith( - ['Typeset', jasmine.any(Object), jasmine.any(Element)] - ); + expect(window.MathJax.typesetClear).toHaveBeenCalledWith([previewDiv]); + expect(previewDiv.innerHTML).toEqual('`THE_FORMULA`'); + expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([previewDiv]); + expect(window.MathJax.startup.document.getMathItemsWithin).toHaveBeenCalledWith(previewDiv); }).always(done); }); @@ -329,16 +302,17 @@ describe('Formula Equation Preview', function() { error: 'OOPSIE', request_start: args[3].request_start }); - expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled(); + expect(window.MathJax.typesetPromise).not.toHaveBeenCalled(); expect($img.css('visibility')).toEqual('visible'); }).then(function() { jasmine.waitUntil(function() { - return window.MathJax.Hub.Queue.calls.count() > 0; + return window.MathJax.typesetPromise.calls.count() > 0; }).then(function() { // Refresh the MathJax. - expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith( - ['Text', jax, '\\text{OOPSIE}'] - ); + var previewDiv = $('#input_THE_ID_preview')[0]; + expect(window.MathJax.typesetClear).toHaveBeenCalledWith([previewDiv]); + expect(previewDiv.innerHTML).toEqual('`\\text{OOPSIE}`'); + expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([previewDiv]); expect($img.css('visibility')).toEqual('hidden'); }).then(done); }); @@ -380,15 +354,16 @@ describe('Formula Equation Preview', function() { expect($img.css('visibility')).toEqual('visible'); this.callbacks[0](this.responses[0]); - expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith( - ['Text', this.jax, 'THE_FORMULA_0'] - ); + var previewDiv = $('#input_THE_ID_preview')[0]; + expect(window.MathJax.typesetClear).toHaveBeenCalledWith([previewDiv]); + expect(previewDiv.innerHTML).toEqual('`THE_FORMULA_0`'); + expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([previewDiv]); expect($img.css('visibility')).toEqual('visible'); this.callbacks[1](this.responses[1]); - expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith( - ['Text', this.jax, 'THE_FORMULA_1'] - ); + expect(window.MathJax.typesetClear).toHaveBeenCalledWith([previewDiv]); + expect(previewDiv.innerHTML).toEqual('`THE_FORMULA_1`'); + expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([previewDiv]); expect($img.css('visibility')).toEqual('hidden'); }); @@ -399,14 +374,15 @@ describe('Formula Equation Preview', function() { // Switch the order (1 returns before 0) this.callbacks[1](this.responses[1]); - expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith( - ['Text', this.jax, 'THE_FORMULA_1'] - ); + var previewDiv = $('#input_THE_ID_preview')[0]; + expect(window.MathJax.typesetClear).toHaveBeenCalledWith([previewDiv]); + expect(previewDiv.innerHTML).toEqual('`THE_FORMULA_1`'); + expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([previewDiv]); expect($img.css('visibility')).toEqual('hidden'); - window.MathJax.Hub.Queue.calls.reset(); + window.MathJax.typesetPromise.calls.reset(); this.callbacks[0](this.responses[0]); - expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled(); + expect(window.MathJax.typesetPromise).not.toHaveBeenCalled(); expect($img.css('visibility')).toEqual('hidden'); }); @@ -415,20 +391,21 @@ describe('Formula Equation Preview', function() { error: 'OOPSIE', request_start: this.responses[0].request_start }); - expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled(); + expect(window.MathJax.typesetPromise).not.toHaveBeenCalled(); // Error message waiting to be displayed this.callbacks[1](this.responses[1]); - expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith( - ['Text', this.jax, 'THE_FORMULA_1'] - ); + var previewDiv = $('#input_THE_ID_preview')[0]; + expect(window.MathJax.typesetClear).toHaveBeenCalledWith([previewDiv]); + expect(previewDiv.innerHTML).toEqual('`THE_FORMULA_1`'); + expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([previewDiv]); // Make sure that it doesn't indeed show up later - window.MathJax.Hub.Queue.calls.reset(); + window.MathJax.typesetPromise.calls.reset(); jasmine.waitUntil(function() { return formulaEquationPreview.errorDelay * 1.1; }).then(function() { - expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled(); + expect(window.MathJax.typesetPromise).not.toHaveBeenCalled(); }).then(done); }); }); diff --git a/common/static/js/capa/src/formula_equation_preview.js b/common/static/js/capa/src/formula_equation_preview.js index ba0f7fc42e87..1cb644b0ea8a 100644 --- a/common/static/js/capa/src/formula_equation_preview.js +++ b/common/static/js/capa/src/formula_equation_preview.js @@ -169,30 +169,22 @@ formulaEquationPreview.enable = function() { } function display(latex) { - MathJax.Hub.Startup.signal.Interest(function(message) { - if (message === 'End') { + // Check if MathJax is loaded + if (typeof MathJax !== 'undefined' && MathJax !== null) { + MathJax.startup.promise.then(() => { var previewElement = inputData.$preview[0]; - MathJax.Hub.Queue(function() { - inputData.jax = MathJax.Hub.getAllJax(previewElement)[0]; - }); - - MathJax.Hub.Queue(function() { - // Check if MathJax is loaded - if (inputData.jax) { - // Set the text as the latex code, and then update the MathJax. - MathJax.Hub.Queue( - ['Text', inputData.jax, latex] - ); - } else if (latex) { - console.log('[FormulaEquationInput] Oops no mathjax for ', latex); - // Fall back to modifying the actual element. - var textNode = previewElement.childNodes[0]; - textNode.data = '\\(' + latex + '\\)'; - MathJax.Hub.Queue(['Typeset', MathJax.Hub, previewElement]); + // Set the text as the latex code, and then update the MathJax. + MathJax.typesetClear([previewElement]); + // Surround eqn with backticks "`", so that mathjax can process it as asciimath + previewElement.innerHTML = `\`${latex}\``; + MathJax.startup.promise.then(() => MathJax.typesetPromise([previewElement]).then(() => { + var allJax = MathJax.startup.document.getMathItemsWithin(previewElement); + if (allJax.length > 0) { + inputData.jax = allJax[0]; } - }); - } - }); + })); + }); + } } if (response.error) { diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html index ead5a935ee94..27690fbd83bc 100644 --- a/common/templates/mathjax_include.html +++ b/common/templates/mathjax_include.html @@ -5,92 +5,76 @@ ## 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 9bf0ab2df94f..60cf422c37d6 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -122,22 +122,38 @@ - - - - + {{ fragment.head_html | safe }} @@ -405,13 +395,13 @@ // // Here that is an issue because we show a confirmation modal when clicking cancel, // if the user stays to edit all TinyMCE editors are no longer there. - // + // // We uncouple the listener to avoid remove the TinyMCE editors const extraCancelSelector = '.cancel-button'; const elements = $(extraCancelSelector).first(); if (elements.length) { elements.first().unbind("click"); - elements.on('click', function() { + elements.on('click', function() { event.preventDefault(); runtime.notify('cancel', {}); }); diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 2c3ece3133a5..fe5936fcb03f 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -2838,7 +2838,7 @@ 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 # Check the one without Math... url = reverse("render_xblock", kwargs={ @@ -2846,7 +2846,7 @@ 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 # The containing vertical should still return MathJax (for now) url = reverse("render_xblock", kwargs={ @@ -2854,7 +2854,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): @@ -2865,7 +2865,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/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..4b1c83babce9 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; @@ -60,7 +58,10 @@ text = preprocessor(text); } $(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) { + return MathJax.startup.promise + .then(() => MathJax.typesetPromise([$(elem).attr("id")])); + } } else { if (this.mathjaxTimeout) { window.clearTimeout(this.mathjaxTimeout); @@ -70,9 +71,6 @@ renderer = (function(_this) { return function() { var curTime, prevTime; - if (_this.mathjaxRunning) { - return; - } prevTime = getTime(); if (preprocessor != null) { text = preprocessor(text); @@ -82,17 +80,18 @@ _this.elapsedTime = curTime - prevTime; if (typeof MathJax !== "undefined" && MathJax !== null) { 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; } diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index d8bc1417e86e..374f368ba85d 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -5,6 +5,38 @@ '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 +88,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@3.2.1/es5/tex-mml-svg.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 +266,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..c2d839964e9d 100644 --- a/lms/static/sass/lms-course.scss +++ b/lms/static/sass/lms-course.scss @@ -34,3 +34,11 @@ width: 54px !important; } } + +.MathJax>svg { + max-width: 100%, +} + +table>tbody>tr>td>.MathJax>svg { + max-width: inherit +} 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 name="footer_extra"> -<%include file="/mathjax_include.html" args="disable_fast_preview=True"/> +<%include file="/mathjax_include.html" /> ${HTML(fragment.foot_html())} 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 name="footer_extra"> -<%include file="/mathjax_include.html" args="disable_fast_preview=True"/> +<%include file="/mathjax_include.html" /> ${HTML(fragment.foot_html())} 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/assets/capa/_display.scss b/xmodule/assets/capa/_display.scss new file mode 100644 index 000000000000..7ec25ab4350c --- /dev/null +++ b/xmodule/assets/capa/_display.scss @@ -0,0 +1,1746 @@ +// capa - styling +// ==================== + +// Table of Contents +// * +Variables - Capa +// * +Extends - Capa +// * +Mixins - Status Icon - Capa +// * +Resets - Deprecate Please +// * +Problem - Base +// * +Problem - Choice Group +// * +Problem - Misc, Unclassified Mess +// * +Problem - Text Input, Numerical Input +// * +Problem - Option Input (Dropdown) +// * +Problem - CodeMirror +// * +Problem - Misc, Unclassified Mess Part 2 +// * +Problem - Rubric +// * +Problem - Annotation +// * +Problem - Choice Text Group +// * +Problem - Image Input Overrides +// * +Problem - Annotation Problem Overrides +@import 'vendor/bi-app/bi-app-ltr'; +@import 'bourbon/bourbon'; +@import 'lms/theme/variables'; +@import 'bootstrap/scss/variables'; +@import 'lms/theme/variables-v1'; + +// +Variables - Capa +// ==================== +$annotation-yellow: rgba(255, 255, 10, 0.3); +$color-copy-tip: rgb(100, 100, 100); + +// FontAwesome Icon code +// ==================== +$checkmark-icon: '\f00c'; // .fa-check +$cross-icon: '\f00d'; // .fa-close +$asterisk-icon: '\f069'; // .fa-asterisk + + +@import '../../../../../static/sass/edx-pattern-library-shims/base/variables'; + +// +Extends - Capa +// ==================== +// Duplicated from _mixins.scss due to xmodule compilation, inheritance issues +%use-font-awesome { + font-family: FontAwesome; + -webkit-font-smoothing: antialiased; + display: inline-block; + speak: none; +} + +// +Mixins - Status Icon - Capa +// ==================== +@mixin status-icon($color: $gray, $fontAwesomeIcon: "\f00d") { + .status-icon { + &::after { + @extend %use-font-awesome; + + color: $color; + font-size: 1.2em; + content: $fontAwesomeIcon; + } + } +} + +// +Resets - Deprecate Please +// ==================== +h2 { + margin-top: 0; + margin-bottom: ($baseline*0.75); + + &.problem-header { + display: inline-block; + + section.staff { + margin-top: ($baseline*1.5); + font-size: 80%; + } + } + + @media print { + display: block; + width: auto; + border-right: 0; + } +} + +.explanation-title { + font-weight: bold; +} + +%feedback-hint { + margin-top: ($baseline / 4); + + .icon { + @include margin-right($baseline / 4); + } +} + +.feedback-hint-incorrect { + @extend %feedback-hint; + + .icon { + color: $incorrect; + } +} + +.feedback-hint-partially-correct, +.feedback-hint-correct { + @extend %feedback-hint; + + .icon { + color: $correct; + } +} + +.feedback-hint-text { + color: $color-copy-tip; +} + +.problem-hint { + margin-bottom: 20px; + width: 100%; +} + +.hint-label { + display: inline-block; + padding-right: 0.5em; +} + +.hint-text { + display: inline-block; +} + +.feedback-hint-multi .hint-text { + display: block; +} + +iframe[seamless] { + overflow: hidden; + padding: 0; + border: 0 none transparent; + background-color: transparent; +} + +.inline-error { + color: darken($error-color, 11%); +} + +div.problem-progress { + display: inline-block; + color: $gray-d1; + font-size: em(14); +} + +// +Problem - Base +// ==================== +div.problem { + padding-top: $baseline; + + @media print { + display: block; + padding: 0; + width: auto; + + canvas, + img { + page-break-inside: avoid; + } + } + + input.math { + direction: ltr; // Equations are always English + } + + .inline { + display: inline; + + + p { + margin-top: $baseline; + } + } + + .question-description { + color: $gray-d1; + font-size: $small-font-size; + } + + form > label, .problem-group-label { + display: block; + margin-bottom: $baseline; + font: inherit; + color: inherit; + -webkit-font-smoothing: initial; + } + + .problem-group-label + .question-description { + margin-top: -$baseline; + } + +} + +// CAPA gap spacing between problem parts +// can not use the & + & since .problem is nested deeply in .xmodule_display.xmodule_CapaModule +.wrapper-problem-response + .wrapper-problem-response, +.wrapper-problem-response + p { + margin-top: ($baseline * 1.5); +} + +// Choice Group - silent class +%choicegroup-base { + @include clearfix(); + + min-width: 100px; + width: auto !important; + width: 100px; + + label { + box-sizing: border-box; + + display: inline-block; + clear: both; + margin-bottom: ($baseline/2); + border: 2px solid $gray-l4; + border-radius: 3px; + padding: ($baseline/2); + width: 100%; + + &::after { + @include margin-left($baseline*0.75); + } + } + + .indicator-container { + min-height: 1px; + width: 25px; + display: inline-block; + } + + fieldset { + box-sizing: border-box; + } + + input[type="radio"], + input[type="checkbox"] { + @include margin($baseline/4); + @include margin-right($baseline/2); + } + + input { + &:focus, + &:hover { + & + label { + border: 2px solid $blue; + } + } + + &, + &:focus, + &:hover { + & + label.choicegroup_correct { + @include status-icon($correct, $checkmark-icon); + + border: 2px solid $correct; + } + + & + label.choicegroup_partially-correct { + @include status-icon($partially-correct, $asterisk-icon); + + border: 2px solid $partially-correct; + } + + & + label.choicegroup_incorrect { + @include status-icon($incorrect, $cross-icon); + + border: 2px solid $incorrect; + } + + & + label.choicegroup_submitted { + border: 2px solid $submitted; + } + } + } +} + +// +Problem - Choice Group +// ==================== +div.problem { + .choicegroup { + @extend %choicegroup-base; + + .field { + position: relative; + } + + label { + @include padding($baseline/2); + @include padding-left($baseline*2.3); + + position: relative; + font-size: $base-font-size; + line-height: normal; + cursor: pointer; + } + + input[type="radio"], + input[type="checkbox"] { + @include left(em(9)); + + position: absolute; + top: 0.35em; + width: $baseline*1.1; + height: $baseline*1.1; + z-index: 1; + } + + legend { + margin-bottom: $baseline; + max-width: 100%; + white-space: normal; + } + + legend + .question-description { + margin-top: -$baseline; + max-width: 100%; + white-space: normal; + } + } +} + +// +Problem - Status Indicators +// ==================== +// Summary status indicators shown after the input area +div.problem { + .indicator-container { + @include margin-left($baseline*0.75); + + .status { + width: $baseline; + + // CASE: correct answer + &.correct { + @include status-icon($correct, $checkmark-icon); + } + + // CASE: partially correct answer + &.partially-correct { + @include status-icon($partially-correct, $asterisk-icon); + } + + // CASE: incorrect answer + &.incorrect { + @include status-icon($incorrect, $cross-icon); + } + + &.submitted, + &.unsubmitted, + &.unanswered { + .status-icon { + content: ''; + } + } + } + } +} + +// +Problem - Misc, Unclassified Mess +// ==================== +div.problem { + ol.enumerate { + li { + &::before { + display: block; + visibility: hidden; + height: 0; + content: " "; + } + } + } + + .solution-span { + > span { + margin: $baseline 0; + display: block; + position: relative; + + &:empty { + display: none; + } + } + } + + .targeted-feedback-span { + > span { + display: block; + position: relative; + + &:empty { + display: none; + } + } + } + + // known classes using this div: .indicator-container, moved to section above + div { + + // TO-DO: Styling used by advanced capa problem types. Should be synced up to use .status class + p { + &.answer { + margin-top: -2px; + } + + span.clarification i { + font-style: normal; + + &:hover { + color: $blue; + } + } + } + + &.correct, &.ui-icon-check { + input { + border-color: $correct; + } + } + + &.partially-correct, &.ui-icon-check { + input { + border-color: $partially-correct; + } + } + + &.processing { + input { + border-color: #aaa; + } + } + + &.ui-icon-close { + input { + border-color: $incorrect; + } + } + + &.incorrect, &.incomplete { + input { + border-color: $incorrect; + } + } + + &.submitted, &.ui-icon-check { + input { + border-color: $submitted; + } + } + + p.answer { + display: inline-block; + margin-top: ($baseline / 2); + margin-bottom: 0; + + &::before { + @extend %t-strong; + + display: inline; + content: "Answer: "; + + } + + &:empty { + &::before { + display: none; + } + } + } + + div.equation { + clear: both; + margin-top: 3px; + + mjx-container { + display: inline-block; + width: auto; + padding: 6px; + padding-right: 14px; + min-width: 30px; + border: 1px solid #e3e3e3; + border-radius: 4px; + background: #f1f1f1; + } + + img.loading { + @include padding-left($baseline/2); + + display: inline-block; + } + + span { + margin-bottom: 0; + display: inline-block; + } + } + + // Hides equation previews in symbolic response problems when printing + [id^='display'].equation { + @media print { + display: none; + } + } + + //TO-DO: review and deprecate all these styles within span {} + span { + &.ui-icon-bullet { + display: inline-block; + position: relative; + top: 4px; + width: 14px; + height: 14px; + background: url('#{$static-path}/images/unanswered-icon.png') center center no-repeat; + } + + &.processing, &.ui-icon-processing { + display: inline-block; + position: relative; + top: 6px; + width: 25px; + height: 20px; + background: url('#{$static-path}/images/spinner.gif') center center no-repeat; + } + + &.ui-icon-check { + display: inline-block; + position: relative; + top: 3px; + width: 25px; + height: 20px; + background: url('#{$static-path}/images/correct-icon.png') center center no-repeat; + } + + &.incomplete, &.ui-icon-close { + display: inline-block; + position: relative; + top: 3px; + width: 20px; + height: 20px; + background: url('#{$static-path}/images/incorrect-icon.png') center center no-repeat; + } + } + + .reload { + @include float(right); + + margin: ($baseline/2); + } + + + .grader-status { + @include clearfix(); + + margin: $baseline/2 0; + padding: $baseline/2; + border-radius: 5px; + background: $gray-l6; + + span { + display: block; + float: left; + overflow: hidden; + margin: -7px 7px 0 0; + text-indent: -9999px; + } + + .grading { + margin: 0px 7px 0 0; + padding-left: 25px; + background: url('#{$static-path}/images/info-icon.png') left center no-repeat; + text-indent: 0px; + } + + p { + float: left; + margin-bottom: 0; + text-transform: capitalize; + line-height: 20px; + } + + &.file { + margin-top: $baseline; + padding: $baseline 0 0 0; + border: 0; + border-top: 1px solid #eee; + background: $white; + + p.debug { + display: none; + } + + input { + float: left; + } + } + + } + + .evaluation { + p { + margin-bottom: ($baseline/5); + } + } + + + .feedback-on-feedback { + margin-right: $baseline; + height: 100px; + } + + .evaluation-response { + header { + text-align: right; + + a { + font-size: .85em; + } + } + } + + .evaluation-scoring { + .scoring-list { + margin-left: 3px; + list-style-type: none; + + li { + display:inline; + margin-left: 50px; + + &:first-child { + margin-left: 0; + } + + label { + font-size: .9em; + } + } + } + } + + .submit-message-container { + margin: $baseline 0px ; + } + } + + div.inline { + > span { + display: inline; + } + } + + ul { + margin-bottom: lh(); + margin-left: .75em; + margin-left: .75rem; + list-style: disc outside none; + } + + ol { + margin-bottom: lh(); + margin-left: .75em; + margin-left: .75rem; + list-style: decimal outside none; + } + + dl { + line-height: 1.4em; + } + + dl dt { + @extend %t-strong; + + } + + dl dd { + margin-bottom: 0; + } + + dd { + margin-left: .5em; + margin-left: .5rem; + } + + li { + margin-bottom: lh(0.5); + line-height: 1.4em; + + &:last-child { + margin-bottom: 0; + } + } + + p { + margin-bottom: lh(); + } + + table { + margin: lh() 0; + border-collapse: collapse; + table-layout: auto; + + td, th { + &.cont-justified-left { + text-align: left !important; // nasty, but needed to override the bad specificity of the xmodule css selectors + } + + &.cont-justified-right { + text-align: right !important; // nasty, but needed to override the bad specificity of the xmodule css selectors + } + + &.cont-justified-center { + text-align: center !important; // nasty, but needed to override the bad specificity of the xmodule css selectorsstyles + } + } + + th { + @extend %t-strong; + + text-align: left; + } + + td { + text-align: left; + } + + caption, th, td { + padding: .25em .75em .25em 0; + padding: .25rem .75rem .25rem 0; + } + + caption { + margin-bottom: .75em; + margin-bottom: .75rem; + padding: .75em 0; + padding: .75rem 0; + background: #f1f1f1; + } + + tr, td, th { + vertical-align: middle; + } + } + + code { + margin: 0 2px; + padding: 0px 5px; + border: 1px solid #eaeaea; + border-radius: 3px; + background-color: $gray-l6; + white-space: nowrap; + font-size: .9em; + } + + pre { + overflow: auto; + padding: 6px $baseline/2; + border: 1px solid $gray-l3; + border-radius: 3px; + background-color: $gray-l6; + font-size: .9em; + line-height: 1.4; + + > code { + margin: 0; + padding: 0; + border: none; + background: transparent; + white-space: pre; + } + } +} + +// +Problem - Text Input, Numerical Input +// ==================== +.problem { + .capa_inputtype.textline, .inputtype.formulaequationinput { + input { + box-sizing: border-box; + + border: 2px solid $gray-l4; + border-radius: 3px; + min-width: 160px; + height: 46px; + } + + .status { + display: inline-block; + margin-top: ($baseline/2); + background: none; + } + + // CASE: incorrect answer + > .incorrect { + input { + border: 2px solid $incorrect; + } + + .status { + @include status-icon($incorrect, $cross-icon); + } + } + + // CASE: partially correct answer + > .partially-correct { + input { + border: 2px solid $partially-correct; + } + + .status { + @include status-icon($partially-correct, $asterisk-icon); + } + } + + // CASE: correct answer + > .correct { + input { + border: 2px solid $correct; + } + + .status { + @include status-icon($correct, $checkmark-icon); + } + } + + // CASE: submitted, correctness withheld + > .submitted { + input { + border: 2px solid $submitted; + } + + .status { + content: ''; + } + } + + // CASE: unanswered and unsubmitted + > .unanswered, > .unsubmitted { + input { + border: 2px solid $gray-l4; + } + + .status { + .status-icon { + &::after { + content: ''; + display: inline-block; + } + } + } + } + + } + + .inputtype.formulaequationinput { + > div { + input { + direction: ltr; + @include text-align(left); + } + } + } + + .trailing_text { + @include margin-right($baseline/2); + + display: inline-block; + } +} + + +// +Problem - Option Input (Dropdown) +// ==================== +.problem { + .inputtype.option-input { + margin: 0 0 0 0 !important; + + .indicator-container { + display: inline-block; + + .status.correct::after, + .status.partially-correct::after, + .status.incorrect::after, + .status.submitted::after, + .status.unanswered::after { + @include margin-left(0); + } + } + } +} + +// +Problem - CodeMirror +// ==================== +div.problem { + .CodeMirror { + border: 1px solid black; + font-size: 14px; + line-height: 18px; + resize: none; + + .cm-tab { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=); + background-position: right; + background-repeat: no-repeat; + } + + pre { + overflow: hidden; + margin: 0; + padding: 0; + border-width: 0; + border-radius: 0; + background: transparent; + white-space: pre; + word-wrap: normal; + font-size: inherit; + font-family: inherit; + resize: none; + + &.CodeMirror-cursor { + @extend %ui-depth1; + + position: absolute; + visibility: hidden; + width: 0; + border-right: none; + border-left: 1px solid $black; + } + } + } + + .CodeMirror-focused pre.CodeMirror-cursor { + visibility: visible; + } + + .CodeMirror-code pre { + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + } + + .CodeMirror-scroll { + margin-right: 0px; + } +} + +.capa-message { + display: inline-block; + color: $gray-d1; + -webkit-font-smoothing: antialiased; +} + +// +Problem - Actions +// ==================== +div.problem .action { + min-height: $baseline; + width: 100%; + display: flex; + display: -ms-flexbox; + -ms-flex-align: start; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + + .problem-action-buttons-wrapper { + display: inline-flex; + justify-content: flex-end; + width: 100%; + padding-bottom: $baseline; + } + + .problem-action-button-wrapper { + @include border-right(1px solid $gray-300); + @include padding(0, 13px); // to create a 26px gap, which is an a11y recommendation + + display: inline-block; + + &:last-child { + border: none; + padding-right: 0; + } + } + + .problem-action-btn { + border: none; + max-width: 110px; + + &:hover, + &:focus, + &:active { + color: $primary !important; + } + + .icon { + margin-bottom: $baseline / 10; + display: block; + } + + @media print { + display: none; + } + } + + .submit-attempt-container { + padding-bottom: $baseline; + flex-grow: 1; + display: flex; + align-items: center; + + @media (max-width: $bp-screen-lg) { + max-width: 100%; + padding-bottom: $baseline; + } + + .submit { + @include margin-right($baseline / 2); + @include float(left); + + white-space: nowrap; + } + + .submit-cta-description { + color: $primary; + font-size: small; + padding-right: $baseline / 2; + } + .submit-cta-link-button { + color: $primary; + padding-right: $baseline / 4; + } + } + + .submission-feedback { + @include margin-right($baseline / 2); + + margin-top: $baseline / 2; + display: inline-block; + color: $gray-d1; + font-size: $medium-font-size; + -webkit-font-smoothing: antialiased; + vertical-align: middle; + + &.cta-enabled { + margin-top: 0; + } + } +} + + +// +Problem - Misc, Unclassified Mess Part 2 +// ==================== +div.problem { + hr { + float: none; + clear: both; + margin: 0 0 .75rem; + width: 100%; + height: 1px; + border: none; + background: #ddd; + color: #ddd; + } + + .hidden { + display: none; + visibility: hidden; + } + + #{$all-text-inputs} { + display: inline; + width: auto; + } + + // this supports a deprecated element and should be removed once the center tag is removed + center { + display: block; + margin: lh() 0; + padding: lh(); + border: 1px solid $gray-l3; + } + + .message { + font-size: inherit; + } + + .detailed-solution > p { + margin: 0; + + &:first-child { + @extend %t-strong; + + margin-bottom: 0; + } + + } + + .detailed-targeted-feedback, + .detailed-targeted-feedback-partially-correct, + .detailed-targeted-feedback-correct { + > p { + margin: 0; + font-weight: normal; + + &:first-child { + @extend %t-strong; + } + } + } + + div.capa_alert { + margin-top: $baseline; + padding: 8px 12px; + border: 1px solid $warning-color; + border-radius: 3px; + background: $warning-color-accent; + font-size: 0.9em; + } + + .notification { + @include float(left); + + margin-top: $baseline / 2; + padding: ($baseline / 2.5) ($baseline / 2) ($baseline / 5) ($baseline / 2); + line-height: $base-line-height; + + &.success { + @include notification-by-type($success); + } + + &.error { + @include notification-by-type($danger); + } + + &.warning { + @include notification-by-type($warning); + } + + &.general { + @include notification-by-type($general-color-accent); + } + + &.problem-hint { + border: 1px solid $uxpl-gray-background; + border-radius: 6px; + + .icon { + @include margin-right(3 * $baseline / 4); + + color: $uxpl-gray-dark; + } + + li { + color: $uxpl-gray-base; + + strong { + color: $uxpl-gray-dark; + } + } + } + + .icon { + @include float(left); + + position: relative; + top: $baseline / 5; + } + + .notification-message { + display: inline-block; + width: flex-grid(7,10); + // Make notification tall enough that when the "Review" button is displayed, + // the notification does not grow in height. + margin-bottom: 8px; + + ol { + list-style: none outside none; + padding: 0; + margin: 0; + + li:not(:last-child) { + margin-bottom: $baseline / 4; + } + } + } + + .notification-btn-wrapper { + @include float(right); + } + + } + + .notification-btn { + @include float(right); + + padding: ($baseline / 10) ($baseline / 4); + min-width: ($baseline * 3); + display: block; + clear: both; + + &:first-child { + margin-bottom: $baseline / 4; + } + } + + // override default button hover + button { + &:hover { + background-image: none; + box-shadow: none; + } + + &:focus { + box-shadow: none; + } + + &.btn-default { + background-color: transparent; + } + + &.btn-brand { + &:hover { + background-color: $btn-brand-focus-background; + } + } + } + + .review-btn { + color: $blue; // notification type has other colors + &.sr { + color: $blue; + } + } + + div.capa_reset { + padding: 25px; + border: 1px solid $error-color; + background-color: lighten($error-color, 25%); + border-radius: 3px; + font-size: 1em; + margin-top: $baseline/2; + margin-bottom: $baseline/2; + } + + .capa_reset>h2 { + color: #a00; + } + + .capa_reset li { + font-size: 0.9em; + } + + .hints { + border: 1px solid $gray-l3; + + h3 { + @extend %t-strong; + + padding: 9px; + border-bottom: 1px solid #e3e3e3; + background: #eee; + text-shadow: 0 1px 0 $white; + font-size: em(16); + } + + div { + border-bottom: 1px solid #ddd; + + &:last-child { + border-bottom: none; + } + + p { + margin-bottom: 0; + } + + header { + a { + display: block; + padding: 9px; + background: $gray-l6; + box-shadow: inset 0 0 0 1px $white; + } + } + + > section { + padding: 9px; + } + } + } + + .test { + padding-top: 18px; + + header { + margin-bottom: 12px; + + h3 { + @extend %t-strong; + + color: #aaa; + font-style: normal; + font-size: 0.9em; + } + } + + > section { + position: relative; + margin-bottom: ($baseline/2); + padding: 9px 9px $baseline; + border: 1px solid #ddd; + border-radius: 3px; + background: $white; + box-shadow: inset 0 0 0 1px #eee; + + p:last-of-type { + margin-bottom: 0; + } + + .shortform { + margin-bottom: .6em; + } + + a.full { + @include position(absolute, 0 0 1px 0); + box-sizing: border-box; + + display: block; + padding: ($baseline/5); + background: $gray-l4; + text-align: right; + font-size: 1em; + + &.full-top { + @include position(absolute, 1px 0 auto 0); + } + + &.full-bottom { + @include position(absolute, auto 0 1px 0); + } + } + } + } + + .external-grader-message { + section { + padding-top: ($baseline*1.5); + padding-left: $baseline; + background-color: #fafafa; + color: #2c2c2c; + font-size: 1em; + font-family: monospace; + + header { + font-size: 1.4em; + } + + .shortform { + @extend %t-strong; + } + + .longform { + margin: 0; + padding: 0; + + .result-errors { + margin: ($baseline/4); + padding: ($baseline/2) ($baseline/2) ($baseline/2) ($baseline*2); + background: url('#{$static-path}/images/incorrect-icon.png') center left no-repeat; + + li { + color: #b00; + } + } + + .result-output { + margin: $baseline/4; + padding: $baseline 0 ($baseline*0.75) 50px; + border-top: 1px solid #ddd; + border-left: $baseline solid #fafafa; + + h4 { + font-size: 1em; + font-family: monospace; + } + + dl { + margin: 0; + } + + dt { + margin-top: $baseline; + } + + dd { + margin-left: 24pt; + } + } + + .result-correct { + background: url('#{$static-path}/images/correct-icon.png') left 20px no-repeat; + + .result-actual-output { + color: #090; + } + } + + .result-partially-correct { + background: url('#{$static-path}/images/partially-correct-icon.png') left 20px no-repeat; + + .result-actual-output { + color: #090; + } + } + + .result-incorrect { + background: url('#{$static-path}/images/incorrect-icon.png') left 20px no-repeat; + + .result-actual-output { + color: #b00; + } + } + + .markup-text{ + margin: ($baseline/4); + padding: $baseline 0 15px 50px; + border-top: 1px solid #ddd; + border-left: 20px solid #fafafa; + + bs { + color: #b00; + } + + bg { + color: #bda046; + } + } + } + } + } +} + + +// +Problem - Rubric +// ==================== +div.problem { + .rubric { + tr { + margin: ($baseline/2) 0; + height: 100%; + } + + td { + margin: ($baseline/2) 0; + padding: $baseline 0; + height: 100%; + } + + th { + margin: ($baseline/4); + padding: ($baseline/4); + } + + label, + .view-only { + position: relative; + display: inline-block; + margin: 3px; + padding: ($baseline*0.75); + min-width: 50px; + min-height: 50px; + width: 150px; + height: 100%; + background-color: $gray-l3; + font-size: .9em; + } + + .grade { + position: absolute; + right: 0; + bottom: 0; + margin: ($baseline/2); + } + + .selected-grade { + background: #666; + color: white; + } + + input[type=radio]:checked + label { + background: #666; + color: white; + } + + input[class='score-selection'] { + display: none; + } + } +} + +// +Problem - Annotation +// ==================== +div.problem { + .annotation-input { + margin: 0 0 1em 0; + border: 1px solid $gray-l3; + border-radius: 1em; + + .annotation-header { + @extend %t-strong; + + padding: .5em 1em; + border-bottom: 1px solid $gray-l3; + } + + .annotation-body { padding: .5em 1em; } + + a.annotation-return { + float: right; + font: inherit; + font-weight: normal; + } + + a.annotation-return::after { content: " \2191" } + + .block, ul.tags { + margin: .5em 0; + padding: 0; + } + + .block-highlight { + padding: .5em; + border: 1px solid darken($annotation-yellow, 10%); + background-color: $annotation-yellow; + color: #333; + font-style: normal; + } + + .block-comment { font-style: italic; } + + ul.tags { + display: block; + margin-left: 1em; + list-style-type: none; + + li { + position: relative; + display: block; + margin: 1em 0 0 0; + + .tag { + @extend %ui-fake-link; + + display: inline-block; + margin-left: ($baseline*2); + border: 1px solid rgb(102,102,102); + + &.selected { + background-color: $annotation-yellow; + } + } + + .tag-status { + position: absolute; + left: 0; + } + .tag-status, .tag { padding: .25em .5em; } + } + } + + textarea.comment { + $num-lines-to-show: 5; + $line-height: 1.4em; + $padding: .2em; + + padding: $padding (2 * $padding); + width: 100%; + height: ($num-lines-to-show * $line-height) + (2*$padding) - (($line-height - 1)/2); + line-height: $line-height; + } + + .answer-annotation { display: block; margin: 0; } + + /* for debugging the input value field. enable the debug flag on the inputtype */ + .debug-value { + margin: 1em 0; + padding: 1em; + border: 1px solid $black; + background-color: #999; + color: $white; + + input[type="text"] { width: 100%; } + + pre { background-color: $gray-l3; color: $black; } + + &::before { + @extend %t-strong; + + display: block; + content: "debug input value"; + font-size: 1.5em; + } + } + } +} + +// +Problem - Choice Text Group +// ==================== +div.problem { + .choicetextgroup { + @extend %choicegroup-base; + + input[type="text"]{ + margin-bottom: 0.5em; + } + + label.choicetextgroup_correct, section.choicetextgroup_correct { + @extend label.choicegroup_correct; + + input[type="text"] { + border-color: $correct; + } + } + + label.choicetextgroup_partially-correct, section.choicetextgroup_partially-correct { + @extend label.choicegroup_partially-correct; + + input[type="text"] { + border-color: $partially-correct; + } + } + + label.choicetextgroup_incorrect, section.choicetextgroup_incorrect { + @extend label.choicegroup_incorrect; + } + + label.choicetextgroup_submitted, section.choicetextgroup_submitted { + @extend label.choicegroup_submitted; + } + + label.choicetextgroup_show_correct, section.choicetextgroup_show_correct { + &::after { + @include margin-left($baseline*0.75); + + content: url('#{$static-path}/images/correct-icon.png'); + } + } + + span.mock_label { + cursor : default; + } + } +} + +// +Problem - Image Input Overrides +// ==================== + +// NOTE: temporary override until image inputs use same base html structure as other common capa input types. +div.problem .imageinput.capa_inputtype { + .status { + display: inline-block; + position: relative; + top: 3px; + width: 25px; + height: 20px; + + &.unsubmitted, + &.unanswered { + .status-icon { + content: ''; + } + + .status-message { + display: none; + } + } + } + + .correct { + @include status-icon($correct, $checkmark-icon); + } + + .incorrect { + @include status-icon($incorrect, $cross-icon); + } + + .partially-correct { + @include status-icon($partially-correct, $asterisk-icon); + } + + .submitted { + content: ''; + } +} + +// +Problem - Annotation Problem Overrides +// ==================== + +// NOTE: temporary override until annotation problem inputs use same base html structure as other common capa input types. +div.problem .annotation-input { + .tag-status { + display: inline-block; + position: relative; + top: 3px; + width: 25px; + height: 20px; + + &.unsubmitted, + &.unanswered { + .status-icon { + content: ''; + } + + .status-message { + display: none; + } + } + } + + .correct { + @include status-icon($correct, $checkmark-icon); + } + + .incorrect { + @include status-icon($incorrect, $cross-icon); + } + + .partially-correct { + @include status-icon($partially-correct, $asterisk-icon); + } + + .submitted { + content: ''; + } +} + +// Loading Spinner +// ==================== +.problems-wrapper .loading-spinner { + text-align: center; + color: $gray-d1; +} diff --git a/xmodule/js/spec/capa/display_spec.js b/xmodule/js/spec/capa/display_spec.js index 385bd2d24333..303be1260712 100644 --- a/xmodule/js/spec/capa/display_spec.js +++ b/xmodule/js/spec/capa/display_spec.js @@ -10,11 +10,14 @@ 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']), + }, + toMML: jasmine.createSpyObj('MathJax.startup', ['toMML']), + typesetPromise: jasmine.createSpyObj('MathJax.typesetPromise'), }; - this.stubbedJax = {root: jasmine.createSpyObj('jax.root', ['toMathML'])}; - MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]); + spyOn(MathJax.typesetPromise).and.returnValue(Promise.resolve()); + this.stubbedJax = jasmine.createSpyObj('jax', ['root']); window.update_schematics = function() {}; spyOn(SR, 'readText'); spyOn(SR, 'readTexts'); @@ -59,11 +62,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()); @@ -95,7 +98,7 @@ data-url='/problem/quiz/'> \ 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')); }); @@ -929,15 +932,15 @@ data-url='/problem/quiz/'> \ }); 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)]); + expect(MathJax.typesetClear).toHaveBeenCalledWith([this.stubbedJax]); + expect(MathJax.typesetPromise).toHaveBeenCalledWith([this.stubbedJax]); }); }); 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() { @@ -952,12 +955,13 @@ 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); 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).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 9bc5591acd4f..c6511e32f26b 100644 --- a/xmodule/js/src/capa/display.js +++ b/xmodule/js/src/capa/display.js @@ -153,7 +153,8 @@ that = this; if (typeof MathJax !== 'undefined' && MathJax !== null) { 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')) { @@ -205,7 +206,8 @@ 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]); + return MathJax.startup.promise + .then(() => that.refreshMath(null, element)); }); } }; @@ -779,7 +781,8 @@ }); if (typeof MathJax !== 'undefined' && MathJax !== null) { 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'); @@ -841,30 +844,39 @@ element = event.target; // eslint-disable-line no-param-reassign } elid = element.id.replace(/^input_/, ''); - target = 'display_' + elid; + 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 (typeof MathJax !== 'undefined' && MathJax !== null) { + const math = document.querySelector(target); eqn = $(element).val(); if (mathjaxPreprocessor) { eqn = mathjaxPreprocessor(eqn); } - MathJax.Hub.Queue(['Text', jax, eqn], [this.updateMathML, jax, element]); + MathJax.typesetClear([math]); + // Surround eqn with backticks "`", so that mathjax can process it as asciimath + math.innerHTML = `\`${eqn}\``; + MathJax.typesetPromise([math]).then(() => { + jax = MathJax.startup.document.getMathItemsWithin(math)[0]; + if (jax) { + this.updateMathML(jax, element); + } + }); } }; 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 (typeof MathJax !== 'undefined' && MathJax !== null) { + try { + $('#' + element.id + '_dynamath').val(MathJax.startup.toMML(jax.root)); + } catch (exception) { + if (!exception.restart) { + throw exception; + } + MathJax.startup.promise + .then(() => this.refreshMath(null, element)); } } }; @@ -1339,7 +1351,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 (typeof MathJax !== 'undefined' && MathJax !== null) { + 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..d1d629142a39 100644 --- a/xmodule/js/src/html/display.js +++ b/xmodule/js/src/html/display.js @@ -8,7 +8,8 @@ JavascriptLoader.executeModuleScripts(this.el); Collapsible.setCollapsibles(this.el); if (typeof MathJax !== "undefined" && MathJax !== null) { - MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.el[0]]); + MathJax.startup.promise + .then(() => MathJax.typesetPromise([this.el[0]])); } if (typeof setupFullScreenModal !== "undefined" && setupFullScreenModal !== null) { setupFullScreenModal(); From c65f945d182acd12d90681229cb9b1bf65faf5de Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 16 Jun 2026 10:56:46 +0530 Subject: [PATCH 2/8] fix(mathjax): complete v4 upgrade fixes - Pin CDN URLs to mathjax@4.1.2 - Fix mathjax_delay_renderer.js immediate path to pass DOM node - Add null guard in capa/display.js for missing math element - Fix CSS syntax: comma to semicolon in MathJax SVG rules - Pass textContent instead of innerHTML for security --- cms/djangoapps/pipeline_js/js/xmodule.js | 2 +- cms/static/cms/js/require-config.js | 2 +- cms/static/cms/js/spec/main.js | 2 +- cms/static/cms/js/spec/main_squire.js | 2 +- cms/static/sass/studio-main-v1.scss | 2 +- .../common/js/discussion/mathjax_include.js | 61 +++++++++---------- common/templates/mathjax_include.html | 2 +- common/templates/xblock_v2/xblock_iframe.html | 2 +- lms/static/js/mathjax_delay_renderer.js | 4 +- lms/static/lms/js/spec/main.js | 2 +- lms/static/sass/lms-course.scss | 2 +- xmodule/js/src/capa/display.js | 5 +- 12 files changed, 43 insertions(+), 45 deletions(-) diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index 558c8824fd66..88d5926a48db 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -49,7 +49,7 @@ define( }; $script( - 'https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-mml-svg.js', + 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.js', 'mathjax', function() { window.addEventListener('resize', MJrenderer); diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js index b9f63b17f9f8..be2ef4568deb 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -152,7 +152,7 @@ 'jquery_extend_patch': 'js/src/jquery_extend_patch', // externally hosted files - mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-mml-svg.js?noext', + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.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, diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index 389dcb55371b..0686bb0e6a01 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -100,7 +100,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@3.2.1/es5/tex-mml-svg.js?noext', + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.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' diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js index d0fd8144bd5b..b0ab0e8ec185 100644 --- a/cms/static/cms/js/spec/main_squire.js +++ b/cms/static/cms/js/spec/main_squire.js @@ -80,7 +80,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@3.2.1/es5/tex-mml-svg.js?noext', + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.js?noext', 'youtube': '//www.youtube.com/player_api?noext', 'js/src/ajax_prefix': 'xmodule_js/common_static/js/src/ajax_prefix' }, diff --git a/cms/static/sass/studio-main-v1.scss b/cms/static/sass/studio-main-v1.scss index 141f5b8f5e86..fbeaf7d9befd 100644 --- a/cms/static/sass/studio-main-v1.scss +++ b/cms/static/sass/studio-main-v1.scss @@ -23,7 +23,7 @@ .MathJax>svg { - max-width: 100%, + max-width: 100%; } table>tbody>tr>td>.MathJax>svg { diff --git a/common/static/common/js/discussion/mathjax_include.js b/common/static/common/js/discussion/mathjax_include.js index 0cf5f5e8f657..ebb318cd5bfe 100644 --- a/common/static/common/js/discussion/mathjax_include.js +++ b/common/static/common/js/discussion/mathjax_include.js @@ -1,40 +1,35 @@ -var vendorScript; if (typeof MathJax === 'undefined') { - vendorScript = document.createElement('script'); - vendorScript.onload = function() { - 'use strict'; - - window.MathJax = { - tex: { - inlineMath: [ - ['\\(', '\\)'], - ['[mathjaxinline]', '[/mathjaxinline]'] - ], - displayMath: [ - ['\\[', '\\]'], - ['[mathjax]', '[/mathjax]'] - ], - autoload: { - color: [], - colorv2: ['color'] - }, - packages: {'[+]': ['noerrors']} + window.MathJax = { + tex: { + inlineMath: [ + ['\\(', '\\)'], + ['[mathjaxinline]', '[/mathjaxinline]'] + ], + displayMath: [ + ['\\[', '\\]'], + ['[mathjax]', '[/mathjax]'] + ], + autoload: { + color: [], + colorv2: ['color'] }, - options: { - ignoreHtmlClass: 'tex2jax_ignore', - processHtmlClass: 'tex2jax_process', - menuOptions: { - settings: { - collapsible: true, - explorer: true - }, + packages: {'[+]': ['noerrors']} + }, + options: { + ignoreHtmlClass: 'tex2jax_ignore', + processHtmlClass: 'tex2jax_process', + menuOptions: { + settings: { + collapsible: true, + explorer: true }, }, - loader: { - load: ['input/asciimath', '[tex]/noerrors'] - } - }; + }, + loader: { + load: ['input/asciimath', '[tex]/noerrors'] + } }; - vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-mml-svg.js'; + var vendorScript = document.createElement('script'); + vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.js'; document.body.appendChild(vendorScript); } diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html index 27690fbd83bc..10a9c19ad17c 100644 --- a/common/templates/mathjax_include.html +++ b/common/templates/mathjax_include.html @@ -99,5 +99,5 @@ - + %endif diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index 60426aa27bac..1f6509f3c86f 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -177,7 +177,7 @@ - + {{ fragment.head_html | safe }} diff --git a/lms/static/js/mathjax_delay_renderer.js b/lms/static/js/mathjax_delay_renderer.js index 4b1c83babce9..bfb303d5539b 100644 --- a/lms/static/js/mathjax_delay_renderer.js +++ b/lms/static/js/mathjax_delay_renderer.js @@ -58,9 +58,9 @@ text = preprocessor(text); } $(elem).html(text); // xss-lint: disable=javascript-jquery-html - if (typeof MathJax !== 'undefined' && MathJax !== null) { + if (typeof MathJax !== 'undefined' && MathJax !== null && MathJax.startup && MathJax.startup.promise) { return MathJax.startup.promise - .then(() => MathJax.typesetPromise([$(elem).attr("id")])); + .then(() => MathJax.typesetPromise([$(elem)[0]])); } } else { if (this.mathjaxTimeout) { diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 374f368ba85d..97f8eab5dd86 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -88,7 +88,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@3.2.1/es5/tex-mml-svg.js?noext', + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.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', diff --git a/lms/static/sass/lms-course.scss b/lms/static/sass/lms-course.scss index c2d839964e9d..817031833154 100644 --- a/lms/static/sass/lms-course.scss +++ b/lms/static/sass/lms-course.scss @@ -36,7 +36,7 @@ } .MathJax>svg { - max-width: 100%, + max-width: 100%; } table>tbody>tr>td>.MathJax>svg { diff --git a/xmodule/js/src/capa/display.js b/xmodule/js/src/capa/display.js index 3d6e506a5bd7..c2aa67c587af 100644 --- a/xmodule/js/src/capa/display.js +++ b/xmodule/js/src/capa/display.js @@ -888,13 +888,16 @@ mathjaxPreprocessor = this.inputtypeDisplays[preprocessorTag]; if (typeof MathJax !== "undefined" && MathJax !== null) { var math = document.querySelector(target); + if (!math) { + return; + } eqn = $(element).val(); if (mathjaxPreprocessor) { eqn = mathjaxPreprocessor(eqn); } MathJax.typesetClear([math]); // Surround eqn with backticks so MathJax processes it as AsciiMath - math.innerHTML = "`" + eqn + "`"; + math.textContent = "`" + eqn + "`"; MathJax.typesetPromise([math]).then(function () { jax = MathJax.startup.document.getMathItemsWithin(math)[0]; if (jax) { From 6dd7f9b2cff08e805e84ff24efe31524958a5702 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 16 Jun 2026 11:04:35 +0530 Subject: [PATCH 3/8] fix(mathjax): use chtml output --- cms/djangoapps/pipeline_js/js/xmodule.js | 2 +- cms/static/cms/js/require-config.js | 2 +- cms/static/cms/js/spec/main.js | 2 +- cms/static/cms/js/spec/main_squire.js | 2 +- cms/static/sass/studio-main-v1.scss | 10 +++++++--- common/static/common/js/discussion/mathjax_include.js | 2 +- common/templates/mathjax_include.html | 2 +- common/templates/xblock_v2/xblock_iframe.html | 2 +- lms/static/lms/js/spec/main.js | 2 +- lms/static/sass/lms-course.scss | 10 +++++++--- 10 files changed, 22 insertions(+), 14 deletions(-) diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index 88d5926a48db..643b640670c8 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -49,7 +49,7 @@ define( }; $script( - 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.js', + 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js', 'mathjax', function() { window.addEventListener('resize', MJrenderer); diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js index be2ef4568deb..a8edb19c9f2b 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -152,7 +152,7 @@ 'jquery_extend_patch': 'js/src/jquery_extend_patch', // externally hosted files - mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.js?noext', + 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, diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index 0686bb0e6a01..f04652939b45 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -100,7 +100,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@4.1.2/tex-mml-svg.js?noext', + 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' diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js index b0ab0e8ec185..e3545f2202a9 100644 --- a/cms/static/cms/js/spec/main_squire.js +++ b/cms/static/cms/js/spec/main_squire.js @@ -80,7 +80,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@4.1.2/tex-mml-svg.js?noext', + 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' }, diff --git a/cms/static/sass/studio-main-v1.scss b/cms/static/sass/studio-main-v1.scss index fbeaf7d9befd..9c5b4cbf4818 100644 --- a/cms/static/sass/studio-main-v1.scss +++ b/cms/static/sass/studio-main-v1.scss @@ -22,10 +22,14 @@ @import '_builtin-block-variables'; -.MathJax>svg { +mjx-container { max-width: 100%; + overflow-x: auto; + overflow-y: hidden; } -table>tbody>tr>td>.MathJax>svg { - max-width: inherit +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 ebb318cd5bfe..468ad7046ade 100644 --- a/common/static/common/js/discussion/mathjax_include.js +++ b/common/static/common/js/discussion/mathjax_include.js @@ -30,6 +30,6 @@ if (typeof MathJax === 'undefined') { } }; var vendorScript = document.createElement('script'); - vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-svg.js'; + vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js'; document.body.appendChild(vendorScript); } diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html index 10a9c19ad17c..bbcacc90749a 100644 --- a/common/templates/mathjax_include.html +++ b/common/templates/mathjax_include.html @@ -99,5 +99,5 @@ - + %endif diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index 1f6509f3c86f..b2d5d6509119 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -177,7 +177,7 @@ - + {{ fragment.head_html | safe }} diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 97f8eab5dd86..61e58e794f43 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -88,7 +88,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@4.1.2/tex-mml-svg.js?noext', + 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', diff --git a/lms/static/sass/lms-course.scss b/lms/static/sass/lms-course.scss index 817031833154..1923d8a321b1 100644 --- a/lms/static/sass/lms-course.scss +++ b/lms/static/sass/lms-course.scss @@ -35,10 +35,14 @@ } } -.MathJax>svg { +mjx-container { max-width: 100%; + overflow-x: auto; + overflow-y: hidden; } -table>tbody>tr>td>.MathJax>svg { - max-width: inherit +table>tbody>tr>td>mjx-container { + max-width: inherit; + overflow-x: auto; + overflow-y: hidden; } From 40d3ae7ad3ae448b6ffa7b866ef41c24a700e0fa Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 17 Jun 2026 19:35:45 +0530 Subject: [PATCH 4/8] fix(mathjax): support v4 formula previews --- lms/static/js/mathjax_delay_renderer.js | 13 ++++++-- xmodule/js/src/capa/display.js | 44 ++++++++++++++++++++----- xmodule/js/src/html/display.js | 3 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/lms/static/js/mathjax_delay_renderer.js b/lms/static/js/mathjax_delay_renderer.js index bfb303d5539b..0adfe5c484c7 100644 --- a/lms/static/js/mathjax_delay_renderer.js +++ b/lms/static/js/mathjax_delay_renderer.js @@ -58,7 +58,8 @@ text = preprocessor(text); } $(elem).html(text); // xss-lint: disable=javascript-jquery-html - if (typeof MathJax !== 'undefined' && MathJax !== null && MathJax.startup && MathJax.startup.promise) { + if (typeof MathJax !== 'undefined' && MathJax !== null && MathJax.startup && + MathJax.startup.promise && typeof MathJax.typesetPromise === "function") { return MathJax.startup.promise .then(() => MathJax.typesetPromise([$(elem)[0]])); } @@ -78,7 +79,8 @@ _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(); return MathJax.startup.promise .then( @@ -93,7 +95,12 @@ }) ); } 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/xmodule/js/src/capa/display.js b/xmodule/js/src/capa/display.js index c2aa67c587af..6c6abc92d227 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,7 +189,7 @@ 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.startup.promise.then(() => MathJax.typesetPromise([element])); }); @@ -215,9 +240,9 @@ 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) { + 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,7 +845,7 @@ } return results; }); - if (typeof MathJax !== "undefined" && MathJax !== null) { + if (isMathJaxTypesetReady()) { that.el.find(".problem > div").each(function (index, element) { return MathJax.startup.promise.then(() => MathJax.typesetPromise([element])); }); @@ -886,8 +911,11 @@ // MathJax preprocessor is loaded by 'setupInputTypes' preprocessorTag = "inputtype_" + elid; mathjaxPreprocessor = this.inputtypeDisplays[preprocessorTag]; - if (typeof MathJax !== "undefined" && MathJax !== null) { + if (isMathJaxRefreshReady()) { var math = document.querySelector(target); + if (!math) { + math = document.getElementById(element.id + "_preview"); + } if (!math) { return; } @@ -908,7 +936,7 @@ }; Problem.prototype.updateMathML = function (jax, element) { - if (typeof MathJax !== "undefined" && MathJax !== null) { + if (isMathJaxMathMLReady()) { try { $("#" + element.id + "_dynamath").val(MathJax.startup.toMML(jax.root)); } catch (exception) { @@ -1405,7 +1433,7 @@ hintMsgContainer = that.$(".problem-hint .notification-message"); hintContainer.attr("hint_index", response.hint_index); edx.HtmlUtils.setHtml(hintMsgContainer, edx.HtmlUtils.HTML(response.msg)); - if (typeof MathJax !== "undefined" && MathJax !== null) { + if (isMathJaxTypesetReady()) { MathJax.startup.promise .then(() => MathJax.typesetPromise([hintContainer[0]])); } diff --git a/xmodule/js/src/html/display.js b/xmodule/js/src/html/display.js index d1d629142a39..b94d86058788 100644 --- a/xmodule/js/src/html/display.js +++ b/xmodule/js/src/html/display.js @@ -7,7 +7,8 @@ this.el = $(this.element); JavascriptLoader.executeModuleScripts(this.el); Collapsible.setCollapsibles(this.el); - if (typeof MathJax !== "undefined" && MathJax !== null) { + if (typeof MathJax !== "undefined" && MathJax !== null && MathJax.startup && + MathJax.startup.promise && typeof MathJax.typesetPromise === "function") { MathJax.startup.promise .then(() => MathJax.typesetPromise([this.el[0]])); } From 1fd78e482c3075debfe6a7ecce2d0cebebf0a24f Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 17 Jun 2026 21:58:33 +0530 Subject: [PATCH 5/8] fix(mathjax): render formula previews with v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mathjax_delay_renderer.js: add typesetClear before .html() in both immediate and delayed paths, so MathJax v3/v4 re-renders replaced content - capa/display.js refreshMath: replace v2 Hub.Queue/getAllJax with v3/v4 typesetClear/typesetPromise; add typesetClear before setting preview textContent; unify rendering to AsciiMath/backtick for all non-tex-delimited input (fixes formula preview for formulaequationinput where * → multiplication and / → fraction instead of literal chars) - capa/display_spec.js: add tests for calculator syntax backtick wrapping, TeX delimiter preservation, and empty-input early return --- lms/static/js/mathjax_delay_renderer.js | 6 ++ xmodule/js/spec/capa/display_spec.js | 75 +++++++++++++++++++++++++ xmodule/js/src/capa/display.js | 30 +++++++++- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/lms/static/js/mathjax_delay_renderer.js b/lms/static/js/mathjax_delay_renderer.js index 0adfe5c484c7..6517dff14a9c 100644 --- a/lms/static/js/mathjax_delay_renderer.js +++ b/lms/static/js/mathjax_delay_renderer.js @@ -57,6 +57,9 @@ 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 if (typeof MathJax !== 'undefined' && MathJax !== null && MathJax.startup && MathJax.startup.promise && typeof MathJax.typesetPromise === "function") { @@ -76,6 +79,9 @@ 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; diff --git a/xmodule/js/spec/capa/display_spec.js b/xmodule/js/spec/capa/display_spec.js index 4f7bf319781f..5cc9985c5467 100644 --- a/xmodule/js/spec/capa/display_spec.js +++ b/xmodule/js/spec/capa/display_spec.js @@ -105,6 +105,16 @@ 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 () { @@ -1024,6 +1034,71 @@ data-url='/problem/quiz/'> \ 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 () { diff --git a/xmodule/js/src/capa/display.js b/xmodule/js/src/capa/display.js index 6c6abc92d227..0ab45f6cd7ff 100644 --- a/xmodule/js/src/capa/display.js +++ b/xmodule/js/src/capa/display.js @@ -901,7 +901,13 @@ }; Problem.prototype.refreshMath = function (event, element) { - var elid, eqn, jax, mathjaxPreprocessor, preprocessorTag, target; + var elid, + eqn, + jax, + mathjaxPreprocessor, + preprocessorTag, + target, + isTexDelimited; if (!element) { element = event.target; // eslint-disable-line no-param-reassign } @@ -924,8 +930,26 @@ eqn = mathjaxPreprocessor(eqn); } MathJax.typesetClear([math]); - // Surround eqn with backticks so MathJax processes it as AsciiMath - math.textContent = "`" + eqn + "`"; + if (!eqn) { + math.textContent = ""; + $("#" + element.id + "_dynamath").val(""); + 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) { From c9945ba11786db4db1eb5b43b0486dd9bbded06f Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Thu, 25 Jun 2026 22:05:25 +0530 Subject: [PATCH 6/8] temp: fix --- common/static/js/capa/src/formula_equation_preview.js | 7 +++++++ openedx/envs/common.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 common/static/js/capa/src/formula_equation_preview.js diff --git a/common/static/js/capa/src/formula_equation_preview.js b/common/static/js/capa/src/formula_equation_preview.js new file mode 100644 index 000000000000..38c0007db3f6 --- /dev/null +++ b/common/static/js/capa/src/formula_equation_preview.js @@ -0,0 +1,7 @@ +// ponytail: no-op override for the xblocks-contrib formula_equation_preview.js, +// which uses MathJax v2 Hub.Queue/getAllJax/Callback APIs that silently fail +// in MathJax v4. The built-in ProblemBlockDisplay.js (activated when +// USE_EXTRACTED_PROBLEM_BLOCK=False) already provides a working v4-compatible +// preview via typesetPromise. Remove this file when xblocks-contrib ships +// MathJax v4-compatible preview JS or when USE_EXTRACTED_PROBLEM_BLOCK is +// permanently removed. diff --git a/openedx/envs/common.py b/openedx/envs/common.py index 31b0015d34d9..f8705e2a8cd3 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -2102,7 +2102,10 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_use_cases: temporary # .. toggle_creation_date: 2024-11-10 # .. toggle_target_removal_date: 2026-04-10 -USE_EXTRACTED_HTML_BLOCK = True +# ponytail: set False because xblocks-contrib 0.17.0 ships no MathJax-v4-compatible +# HtmlBlockDisplay.js, so dynamically-rendered HTML math never gets typeset. +# Switch back to True when xblocks-contrib supports MathJax v4. +USE_EXTRACTED_HTML_BLOCK = False # .. toggle_name: USE_EXTRACTED_DISCUSSION_BLOCK # .. toggle_default: False @@ -2122,7 +2125,10 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_warning: Not production-ready until relevant subtask https://github.com/openedx/edx-platform/issues/34827 is done. # .. toggle_creation_date: 2024-11-10 # .. toggle_target_removal_date: 2026-04-10 -USE_EXTRACTED_PROBLEM_BLOCK = True +# ponytail: set False because xblocks-contrib 0.17.0 ships MathJax-v2-only display JS +# (Hub.Queue, getAllJax, Callback) that crashes in MathJax v4. +# Switch back to True when xblocks-contrib supports MathJax v4. +USE_EXTRACTED_PROBLEM_BLOCK = False # .. toggle_name: USE_EXTRACTED_VIDEO_BLOCK # .. toggle_default: True From b960c53997efbde2f1d20b899d06d532e0de86e3 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 26 Jun 2026 22:38:27 +0530 Subject: [PATCH 7/8] fix(mathjax): enable extracted xblocks for v4 --- common/static/js/capa/src/formula_equation_preview.js | 7 ------- openedx/envs/common.py | 10 ++-------- 2 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 common/static/js/capa/src/formula_equation_preview.js diff --git a/common/static/js/capa/src/formula_equation_preview.js b/common/static/js/capa/src/formula_equation_preview.js deleted file mode 100644 index 38c0007db3f6..000000000000 --- a/common/static/js/capa/src/formula_equation_preview.js +++ /dev/null @@ -1,7 +0,0 @@ -// ponytail: no-op override for the xblocks-contrib formula_equation_preview.js, -// which uses MathJax v2 Hub.Queue/getAllJax/Callback APIs that silently fail -// in MathJax v4. The built-in ProblemBlockDisplay.js (activated when -// USE_EXTRACTED_PROBLEM_BLOCK=False) already provides a working v4-compatible -// preview via typesetPromise. Remove this file when xblocks-contrib ships -// MathJax v4-compatible preview JS or when USE_EXTRACTED_PROBLEM_BLOCK is -// permanently removed. diff --git a/openedx/envs/common.py b/openedx/envs/common.py index f8705e2a8cd3..31b0015d34d9 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -2102,10 +2102,7 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_use_cases: temporary # .. toggle_creation_date: 2024-11-10 # .. toggle_target_removal_date: 2026-04-10 -# ponytail: set False because xblocks-contrib 0.17.0 ships no MathJax-v4-compatible -# HtmlBlockDisplay.js, so dynamically-rendered HTML math never gets typeset. -# Switch back to True when xblocks-contrib supports MathJax v4. -USE_EXTRACTED_HTML_BLOCK = False +USE_EXTRACTED_HTML_BLOCK = True # .. toggle_name: USE_EXTRACTED_DISCUSSION_BLOCK # .. toggle_default: False @@ -2125,10 +2122,7 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_warning: Not production-ready until relevant subtask https://github.com/openedx/edx-platform/issues/34827 is done. # .. toggle_creation_date: 2024-11-10 # .. toggle_target_removal_date: 2026-04-10 -# ponytail: set False because xblocks-contrib 0.17.0 ships MathJax-v2-only display JS -# (Hub.Queue, getAllJax, Callback) that crashes in MathJax v4. -# Switch back to True when xblocks-contrib supports MathJax v4. -USE_EXTRACTED_PROBLEM_BLOCK = False +USE_EXTRACTED_PROBLEM_BLOCK = True # .. toggle_name: USE_EXTRACTED_VIDEO_BLOCK # .. toggle_default: True From 7e234bd27aa9fc942cce2d6a3e29d6e9c8ff3951 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 29 Jun 2026 11:13:32 +0530 Subject: [PATCH 8/8] fix(mathjax): stabilize v4 xblock rendering --- common/templates/mathjax_include.html | 8 +- common/templates/xblock_v2/xblock_iframe.html | 8 +- xmodule/assets/capa/_display.scss | 1746 ----------------- xmodule/js/src/capa/display.js | 16 +- 4 files changed, 21 insertions(+), 1757 deletions(-) delete mode 100644 xmodule/assets/capa/_display.scss diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html index bbcacc90749a..fa9a7d4a0192 100644 --- a/common/templates/mathjax_include.html +++ b/common/templates/mathjax_include.html @@ -88,8 +88,12 @@ if (oldWidth !== document.documentElement.scrollWidth) { t = window.setTimeout(function() { oldWidth = document.documentElement.scrollWidth; - MathJax.typesetClear(); - MathJax.typesetPromise(); + if (typeof MathJax !== 'undefined' && MathJax !== null && + typeof MathJax.typesetClear === 'function' && + typeof MathJax.typesetPromise === 'function') { + MathJax.typesetClear(); + MathJax.typesetPromise(); + } t = -1; }, delay); } diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index b2d5d6509119..d2452aae9c1f 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -167,8 +167,12 @@ if (oldWidth !== document.documentElement.scrollWidth) { t = window.setTimeout(function() { oldWidth = document.documentElement.scrollWidth; - MathJax.typesetClear(); - MathJax.typesetPromise(); + if (typeof MathJax !== 'undefined' && MathJax !== null && + typeof MathJax.typesetClear === 'function' && + typeof MathJax.typesetPromise === 'function') { + MathJax.typesetClear(); + MathJax.typesetPromise(); + } t = -1; }, delay); } diff --git a/xmodule/assets/capa/_display.scss b/xmodule/assets/capa/_display.scss deleted file mode 100644 index 7ec25ab4350c..000000000000 --- a/xmodule/assets/capa/_display.scss +++ /dev/null @@ -1,1746 +0,0 @@ -// capa - styling -// ==================== - -// Table of Contents -// * +Variables - Capa -// * +Extends - Capa -// * +Mixins - Status Icon - Capa -// * +Resets - Deprecate Please -// * +Problem - Base -// * +Problem - Choice Group -// * +Problem - Misc, Unclassified Mess -// * +Problem - Text Input, Numerical Input -// * +Problem - Option Input (Dropdown) -// * +Problem - CodeMirror -// * +Problem - Misc, Unclassified Mess Part 2 -// * +Problem - Rubric -// * +Problem - Annotation -// * +Problem - Choice Text Group -// * +Problem - Image Input Overrides -// * +Problem - Annotation Problem Overrides -@import 'vendor/bi-app/bi-app-ltr'; -@import 'bourbon/bourbon'; -@import 'lms/theme/variables'; -@import 'bootstrap/scss/variables'; -@import 'lms/theme/variables-v1'; - -// +Variables - Capa -// ==================== -$annotation-yellow: rgba(255, 255, 10, 0.3); -$color-copy-tip: rgb(100, 100, 100); - -// FontAwesome Icon code -// ==================== -$checkmark-icon: '\f00c'; // .fa-check -$cross-icon: '\f00d'; // .fa-close -$asterisk-icon: '\f069'; // .fa-asterisk - - -@import '../../../../../static/sass/edx-pattern-library-shims/base/variables'; - -// +Extends - Capa -// ==================== -// Duplicated from _mixins.scss due to xmodule compilation, inheritance issues -%use-font-awesome { - font-family: FontAwesome; - -webkit-font-smoothing: antialiased; - display: inline-block; - speak: none; -} - -// +Mixins - Status Icon - Capa -// ==================== -@mixin status-icon($color: $gray, $fontAwesomeIcon: "\f00d") { - .status-icon { - &::after { - @extend %use-font-awesome; - - color: $color; - font-size: 1.2em; - content: $fontAwesomeIcon; - } - } -} - -// +Resets - Deprecate Please -// ==================== -h2 { - margin-top: 0; - margin-bottom: ($baseline*0.75); - - &.problem-header { - display: inline-block; - - section.staff { - margin-top: ($baseline*1.5); - font-size: 80%; - } - } - - @media print { - display: block; - width: auto; - border-right: 0; - } -} - -.explanation-title { - font-weight: bold; -} - -%feedback-hint { - margin-top: ($baseline / 4); - - .icon { - @include margin-right($baseline / 4); - } -} - -.feedback-hint-incorrect { - @extend %feedback-hint; - - .icon { - color: $incorrect; - } -} - -.feedback-hint-partially-correct, -.feedback-hint-correct { - @extend %feedback-hint; - - .icon { - color: $correct; - } -} - -.feedback-hint-text { - color: $color-copy-tip; -} - -.problem-hint { - margin-bottom: 20px; - width: 100%; -} - -.hint-label { - display: inline-block; - padding-right: 0.5em; -} - -.hint-text { - display: inline-block; -} - -.feedback-hint-multi .hint-text { - display: block; -} - -iframe[seamless] { - overflow: hidden; - padding: 0; - border: 0 none transparent; - background-color: transparent; -} - -.inline-error { - color: darken($error-color, 11%); -} - -div.problem-progress { - display: inline-block; - color: $gray-d1; - font-size: em(14); -} - -// +Problem - Base -// ==================== -div.problem { - padding-top: $baseline; - - @media print { - display: block; - padding: 0; - width: auto; - - canvas, - img { - page-break-inside: avoid; - } - } - - input.math { - direction: ltr; // Equations are always English - } - - .inline { - display: inline; - - + p { - margin-top: $baseline; - } - } - - .question-description { - color: $gray-d1; - font-size: $small-font-size; - } - - form > label, .problem-group-label { - display: block; - margin-bottom: $baseline; - font: inherit; - color: inherit; - -webkit-font-smoothing: initial; - } - - .problem-group-label + .question-description { - margin-top: -$baseline; - } - -} - -// CAPA gap spacing between problem parts -// can not use the & + & since .problem is nested deeply in .xmodule_display.xmodule_CapaModule -.wrapper-problem-response + .wrapper-problem-response, -.wrapper-problem-response + p { - margin-top: ($baseline * 1.5); -} - -// Choice Group - silent class -%choicegroup-base { - @include clearfix(); - - min-width: 100px; - width: auto !important; - width: 100px; - - label { - box-sizing: border-box; - - display: inline-block; - clear: both; - margin-bottom: ($baseline/2); - border: 2px solid $gray-l4; - border-radius: 3px; - padding: ($baseline/2); - width: 100%; - - &::after { - @include margin-left($baseline*0.75); - } - } - - .indicator-container { - min-height: 1px; - width: 25px; - display: inline-block; - } - - fieldset { - box-sizing: border-box; - } - - input[type="radio"], - input[type="checkbox"] { - @include margin($baseline/4); - @include margin-right($baseline/2); - } - - input { - &:focus, - &:hover { - & + label { - border: 2px solid $blue; - } - } - - &, - &:focus, - &:hover { - & + label.choicegroup_correct { - @include status-icon($correct, $checkmark-icon); - - border: 2px solid $correct; - } - - & + label.choicegroup_partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - - border: 2px solid $partially-correct; - } - - & + label.choicegroup_incorrect { - @include status-icon($incorrect, $cross-icon); - - border: 2px solid $incorrect; - } - - & + label.choicegroup_submitted { - border: 2px solid $submitted; - } - } - } -} - -// +Problem - Choice Group -// ==================== -div.problem { - .choicegroup { - @extend %choicegroup-base; - - .field { - position: relative; - } - - label { - @include padding($baseline/2); - @include padding-left($baseline*2.3); - - position: relative; - font-size: $base-font-size; - line-height: normal; - cursor: pointer; - } - - input[type="radio"], - input[type="checkbox"] { - @include left(em(9)); - - position: absolute; - top: 0.35em; - width: $baseline*1.1; - height: $baseline*1.1; - z-index: 1; - } - - legend { - margin-bottom: $baseline; - max-width: 100%; - white-space: normal; - } - - legend + .question-description { - margin-top: -$baseline; - max-width: 100%; - white-space: normal; - } - } -} - -// +Problem - Status Indicators -// ==================== -// Summary status indicators shown after the input area -div.problem { - .indicator-container { - @include margin-left($baseline*0.75); - - .status { - width: $baseline; - - // CASE: correct answer - &.correct { - @include status-icon($correct, $checkmark-icon); - } - - // CASE: partially correct answer - &.partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - } - - // CASE: incorrect answer - &.incorrect { - @include status-icon($incorrect, $cross-icon); - } - - &.submitted, - &.unsubmitted, - &.unanswered { - .status-icon { - content: ''; - } - } - } - } -} - -// +Problem - Misc, Unclassified Mess -// ==================== -div.problem { - ol.enumerate { - li { - &::before { - display: block; - visibility: hidden; - height: 0; - content: " "; - } - } - } - - .solution-span { - > span { - margin: $baseline 0; - display: block; - position: relative; - - &:empty { - display: none; - } - } - } - - .targeted-feedback-span { - > span { - display: block; - position: relative; - - &:empty { - display: none; - } - } - } - - // known classes using this div: .indicator-container, moved to section above - div { - - // TO-DO: Styling used by advanced capa problem types. Should be synced up to use .status class - p { - &.answer { - margin-top: -2px; - } - - span.clarification i { - font-style: normal; - - &:hover { - color: $blue; - } - } - } - - &.correct, &.ui-icon-check { - input { - border-color: $correct; - } - } - - &.partially-correct, &.ui-icon-check { - input { - border-color: $partially-correct; - } - } - - &.processing { - input { - border-color: #aaa; - } - } - - &.ui-icon-close { - input { - border-color: $incorrect; - } - } - - &.incorrect, &.incomplete { - input { - border-color: $incorrect; - } - } - - &.submitted, &.ui-icon-check { - input { - border-color: $submitted; - } - } - - p.answer { - display: inline-block; - margin-top: ($baseline / 2); - margin-bottom: 0; - - &::before { - @extend %t-strong; - - display: inline; - content: "Answer: "; - - } - - &:empty { - &::before { - display: none; - } - } - } - - div.equation { - clear: both; - margin-top: 3px; - - mjx-container { - display: inline-block; - width: auto; - padding: 6px; - padding-right: 14px; - min-width: 30px; - border: 1px solid #e3e3e3; - border-radius: 4px; - background: #f1f1f1; - } - - img.loading { - @include padding-left($baseline/2); - - display: inline-block; - } - - span { - margin-bottom: 0; - display: inline-block; - } - } - - // Hides equation previews in symbolic response problems when printing - [id^='display'].equation { - @media print { - display: none; - } - } - - //TO-DO: review and deprecate all these styles within span {} - span { - &.ui-icon-bullet { - display: inline-block; - position: relative; - top: 4px; - width: 14px; - height: 14px; - background: url('#{$static-path}/images/unanswered-icon.png') center center no-repeat; - } - - &.processing, &.ui-icon-processing { - display: inline-block; - position: relative; - top: 6px; - width: 25px; - height: 20px; - background: url('#{$static-path}/images/spinner.gif') center center no-repeat; - } - - &.ui-icon-check { - display: inline-block; - position: relative; - top: 3px; - width: 25px; - height: 20px; - background: url('#{$static-path}/images/correct-icon.png') center center no-repeat; - } - - &.incomplete, &.ui-icon-close { - display: inline-block; - position: relative; - top: 3px; - width: 20px; - height: 20px; - background: url('#{$static-path}/images/incorrect-icon.png') center center no-repeat; - } - } - - .reload { - @include float(right); - - margin: ($baseline/2); - } - - - .grader-status { - @include clearfix(); - - margin: $baseline/2 0; - padding: $baseline/2; - border-radius: 5px; - background: $gray-l6; - - span { - display: block; - float: left; - overflow: hidden; - margin: -7px 7px 0 0; - text-indent: -9999px; - } - - .grading { - margin: 0px 7px 0 0; - padding-left: 25px; - background: url('#{$static-path}/images/info-icon.png') left center no-repeat; - text-indent: 0px; - } - - p { - float: left; - margin-bottom: 0; - text-transform: capitalize; - line-height: 20px; - } - - &.file { - margin-top: $baseline; - padding: $baseline 0 0 0; - border: 0; - border-top: 1px solid #eee; - background: $white; - - p.debug { - display: none; - } - - input { - float: left; - } - } - - } - - .evaluation { - p { - margin-bottom: ($baseline/5); - } - } - - - .feedback-on-feedback { - margin-right: $baseline; - height: 100px; - } - - .evaluation-response { - header { - text-align: right; - - a { - font-size: .85em; - } - } - } - - .evaluation-scoring { - .scoring-list { - margin-left: 3px; - list-style-type: none; - - li { - display:inline; - margin-left: 50px; - - &:first-child { - margin-left: 0; - } - - label { - font-size: .9em; - } - } - } - } - - .submit-message-container { - margin: $baseline 0px ; - } - } - - div.inline { - > span { - display: inline; - } - } - - ul { - margin-bottom: lh(); - margin-left: .75em; - margin-left: .75rem; - list-style: disc outside none; - } - - ol { - margin-bottom: lh(); - margin-left: .75em; - margin-left: .75rem; - list-style: decimal outside none; - } - - dl { - line-height: 1.4em; - } - - dl dt { - @extend %t-strong; - - } - - dl dd { - margin-bottom: 0; - } - - dd { - margin-left: .5em; - margin-left: .5rem; - } - - li { - margin-bottom: lh(0.5); - line-height: 1.4em; - - &:last-child { - margin-bottom: 0; - } - } - - p { - margin-bottom: lh(); - } - - table { - margin: lh() 0; - border-collapse: collapse; - table-layout: auto; - - td, th { - &.cont-justified-left { - text-align: left !important; // nasty, but needed to override the bad specificity of the xmodule css selectors - } - - &.cont-justified-right { - text-align: right !important; // nasty, but needed to override the bad specificity of the xmodule css selectors - } - - &.cont-justified-center { - text-align: center !important; // nasty, but needed to override the bad specificity of the xmodule css selectorsstyles - } - } - - th { - @extend %t-strong; - - text-align: left; - } - - td { - text-align: left; - } - - caption, th, td { - padding: .25em .75em .25em 0; - padding: .25rem .75rem .25rem 0; - } - - caption { - margin-bottom: .75em; - margin-bottom: .75rem; - padding: .75em 0; - padding: .75rem 0; - background: #f1f1f1; - } - - tr, td, th { - vertical-align: middle; - } - } - - code { - margin: 0 2px; - padding: 0px 5px; - border: 1px solid #eaeaea; - border-radius: 3px; - background-color: $gray-l6; - white-space: nowrap; - font-size: .9em; - } - - pre { - overflow: auto; - padding: 6px $baseline/2; - border: 1px solid $gray-l3; - border-radius: 3px; - background-color: $gray-l6; - font-size: .9em; - line-height: 1.4; - - > code { - margin: 0; - padding: 0; - border: none; - background: transparent; - white-space: pre; - } - } -} - -// +Problem - Text Input, Numerical Input -// ==================== -.problem { - .capa_inputtype.textline, .inputtype.formulaequationinput { - input { - box-sizing: border-box; - - border: 2px solid $gray-l4; - border-radius: 3px; - min-width: 160px; - height: 46px; - } - - .status { - display: inline-block; - margin-top: ($baseline/2); - background: none; - } - - // CASE: incorrect answer - > .incorrect { - input { - border: 2px solid $incorrect; - } - - .status { - @include status-icon($incorrect, $cross-icon); - } - } - - // CASE: partially correct answer - > .partially-correct { - input { - border: 2px solid $partially-correct; - } - - .status { - @include status-icon($partially-correct, $asterisk-icon); - } - } - - // CASE: correct answer - > .correct { - input { - border: 2px solid $correct; - } - - .status { - @include status-icon($correct, $checkmark-icon); - } - } - - // CASE: submitted, correctness withheld - > .submitted { - input { - border: 2px solid $submitted; - } - - .status { - content: ''; - } - } - - // CASE: unanswered and unsubmitted - > .unanswered, > .unsubmitted { - input { - border: 2px solid $gray-l4; - } - - .status { - .status-icon { - &::after { - content: ''; - display: inline-block; - } - } - } - } - - } - - .inputtype.formulaequationinput { - > div { - input { - direction: ltr; - @include text-align(left); - } - } - } - - .trailing_text { - @include margin-right($baseline/2); - - display: inline-block; - } -} - - -// +Problem - Option Input (Dropdown) -// ==================== -.problem { - .inputtype.option-input { - margin: 0 0 0 0 !important; - - .indicator-container { - display: inline-block; - - .status.correct::after, - .status.partially-correct::after, - .status.incorrect::after, - .status.submitted::after, - .status.unanswered::after { - @include margin-left(0); - } - } - } -} - -// +Problem - CodeMirror -// ==================== -div.problem { - .CodeMirror { - border: 1px solid black; - font-size: 14px; - line-height: 18px; - resize: none; - - .cm-tab { - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=); - background-position: right; - background-repeat: no-repeat; - } - - pre { - overflow: hidden; - margin: 0; - padding: 0; - border-width: 0; - border-radius: 0; - background: transparent; - white-space: pre; - word-wrap: normal; - font-size: inherit; - font-family: inherit; - resize: none; - - &.CodeMirror-cursor { - @extend %ui-depth1; - - position: absolute; - visibility: hidden; - width: 0; - border-right: none; - border-left: 1px solid $black; - } - } - } - - .CodeMirror-focused pre.CodeMirror-cursor { - visibility: visible; - } - - .CodeMirror-code pre { - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; - } - - .CodeMirror-scroll { - margin-right: 0px; - } -} - -.capa-message { - display: inline-block; - color: $gray-d1; - -webkit-font-smoothing: antialiased; -} - -// +Problem - Actions -// ==================== -div.problem .action { - min-height: $baseline; - width: 100%; - display: flex; - display: -ms-flexbox; - -ms-flex-align: start; - flex-direction: row; - align-items: center; - flex-wrap: wrap; - - .problem-action-buttons-wrapper { - display: inline-flex; - justify-content: flex-end; - width: 100%; - padding-bottom: $baseline; - } - - .problem-action-button-wrapper { - @include border-right(1px solid $gray-300); - @include padding(0, 13px); // to create a 26px gap, which is an a11y recommendation - - display: inline-block; - - &:last-child { - border: none; - padding-right: 0; - } - } - - .problem-action-btn { - border: none; - max-width: 110px; - - &:hover, - &:focus, - &:active { - color: $primary !important; - } - - .icon { - margin-bottom: $baseline / 10; - display: block; - } - - @media print { - display: none; - } - } - - .submit-attempt-container { - padding-bottom: $baseline; - flex-grow: 1; - display: flex; - align-items: center; - - @media (max-width: $bp-screen-lg) { - max-width: 100%; - padding-bottom: $baseline; - } - - .submit { - @include margin-right($baseline / 2); - @include float(left); - - white-space: nowrap; - } - - .submit-cta-description { - color: $primary; - font-size: small; - padding-right: $baseline / 2; - } - .submit-cta-link-button { - color: $primary; - padding-right: $baseline / 4; - } - } - - .submission-feedback { - @include margin-right($baseline / 2); - - margin-top: $baseline / 2; - display: inline-block; - color: $gray-d1; - font-size: $medium-font-size; - -webkit-font-smoothing: antialiased; - vertical-align: middle; - - &.cta-enabled { - margin-top: 0; - } - } -} - - -// +Problem - Misc, Unclassified Mess Part 2 -// ==================== -div.problem { - hr { - float: none; - clear: both; - margin: 0 0 .75rem; - width: 100%; - height: 1px; - border: none; - background: #ddd; - color: #ddd; - } - - .hidden { - display: none; - visibility: hidden; - } - - #{$all-text-inputs} { - display: inline; - width: auto; - } - - // this supports a deprecated element and should be removed once the center tag is removed - center { - display: block; - margin: lh() 0; - padding: lh(); - border: 1px solid $gray-l3; - } - - .message { - font-size: inherit; - } - - .detailed-solution > p { - margin: 0; - - &:first-child { - @extend %t-strong; - - margin-bottom: 0; - } - - } - - .detailed-targeted-feedback, - .detailed-targeted-feedback-partially-correct, - .detailed-targeted-feedback-correct { - > p { - margin: 0; - font-weight: normal; - - &:first-child { - @extend %t-strong; - } - } - } - - div.capa_alert { - margin-top: $baseline; - padding: 8px 12px; - border: 1px solid $warning-color; - border-radius: 3px; - background: $warning-color-accent; - font-size: 0.9em; - } - - .notification { - @include float(left); - - margin-top: $baseline / 2; - padding: ($baseline / 2.5) ($baseline / 2) ($baseline / 5) ($baseline / 2); - line-height: $base-line-height; - - &.success { - @include notification-by-type($success); - } - - &.error { - @include notification-by-type($danger); - } - - &.warning { - @include notification-by-type($warning); - } - - &.general { - @include notification-by-type($general-color-accent); - } - - &.problem-hint { - border: 1px solid $uxpl-gray-background; - border-radius: 6px; - - .icon { - @include margin-right(3 * $baseline / 4); - - color: $uxpl-gray-dark; - } - - li { - color: $uxpl-gray-base; - - strong { - color: $uxpl-gray-dark; - } - } - } - - .icon { - @include float(left); - - position: relative; - top: $baseline / 5; - } - - .notification-message { - display: inline-block; - width: flex-grid(7,10); - // Make notification tall enough that when the "Review" button is displayed, - // the notification does not grow in height. - margin-bottom: 8px; - - ol { - list-style: none outside none; - padding: 0; - margin: 0; - - li:not(:last-child) { - margin-bottom: $baseline / 4; - } - } - } - - .notification-btn-wrapper { - @include float(right); - } - - } - - .notification-btn { - @include float(right); - - padding: ($baseline / 10) ($baseline / 4); - min-width: ($baseline * 3); - display: block; - clear: both; - - &:first-child { - margin-bottom: $baseline / 4; - } - } - - // override default button hover - button { - &:hover { - background-image: none; - box-shadow: none; - } - - &:focus { - box-shadow: none; - } - - &.btn-default { - background-color: transparent; - } - - &.btn-brand { - &:hover { - background-color: $btn-brand-focus-background; - } - } - } - - .review-btn { - color: $blue; // notification type has other colors - &.sr { - color: $blue; - } - } - - div.capa_reset { - padding: 25px; - border: 1px solid $error-color; - background-color: lighten($error-color, 25%); - border-radius: 3px; - font-size: 1em; - margin-top: $baseline/2; - margin-bottom: $baseline/2; - } - - .capa_reset>h2 { - color: #a00; - } - - .capa_reset li { - font-size: 0.9em; - } - - .hints { - border: 1px solid $gray-l3; - - h3 { - @extend %t-strong; - - padding: 9px; - border-bottom: 1px solid #e3e3e3; - background: #eee; - text-shadow: 0 1px 0 $white; - font-size: em(16); - } - - div { - border-bottom: 1px solid #ddd; - - &:last-child { - border-bottom: none; - } - - p { - margin-bottom: 0; - } - - header { - a { - display: block; - padding: 9px; - background: $gray-l6; - box-shadow: inset 0 0 0 1px $white; - } - } - - > section { - padding: 9px; - } - } - } - - .test { - padding-top: 18px; - - header { - margin-bottom: 12px; - - h3 { - @extend %t-strong; - - color: #aaa; - font-style: normal; - font-size: 0.9em; - } - } - - > section { - position: relative; - margin-bottom: ($baseline/2); - padding: 9px 9px $baseline; - border: 1px solid #ddd; - border-radius: 3px; - background: $white; - box-shadow: inset 0 0 0 1px #eee; - - p:last-of-type { - margin-bottom: 0; - } - - .shortform { - margin-bottom: .6em; - } - - a.full { - @include position(absolute, 0 0 1px 0); - box-sizing: border-box; - - display: block; - padding: ($baseline/5); - background: $gray-l4; - text-align: right; - font-size: 1em; - - &.full-top { - @include position(absolute, 1px 0 auto 0); - } - - &.full-bottom { - @include position(absolute, auto 0 1px 0); - } - } - } - } - - .external-grader-message { - section { - padding-top: ($baseline*1.5); - padding-left: $baseline; - background-color: #fafafa; - color: #2c2c2c; - font-size: 1em; - font-family: monospace; - - header { - font-size: 1.4em; - } - - .shortform { - @extend %t-strong; - } - - .longform { - margin: 0; - padding: 0; - - .result-errors { - margin: ($baseline/4); - padding: ($baseline/2) ($baseline/2) ($baseline/2) ($baseline*2); - background: url('#{$static-path}/images/incorrect-icon.png') center left no-repeat; - - li { - color: #b00; - } - } - - .result-output { - margin: $baseline/4; - padding: $baseline 0 ($baseline*0.75) 50px; - border-top: 1px solid #ddd; - border-left: $baseline solid #fafafa; - - h4 { - font-size: 1em; - font-family: monospace; - } - - dl { - margin: 0; - } - - dt { - margin-top: $baseline; - } - - dd { - margin-left: 24pt; - } - } - - .result-correct { - background: url('#{$static-path}/images/correct-icon.png') left 20px no-repeat; - - .result-actual-output { - color: #090; - } - } - - .result-partially-correct { - background: url('#{$static-path}/images/partially-correct-icon.png') left 20px no-repeat; - - .result-actual-output { - color: #090; - } - } - - .result-incorrect { - background: url('#{$static-path}/images/incorrect-icon.png') left 20px no-repeat; - - .result-actual-output { - color: #b00; - } - } - - .markup-text{ - margin: ($baseline/4); - padding: $baseline 0 15px 50px; - border-top: 1px solid #ddd; - border-left: 20px solid #fafafa; - - bs { - color: #b00; - } - - bg { - color: #bda046; - } - } - } - } - } -} - - -// +Problem - Rubric -// ==================== -div.problem { - .rubric { - tr { - margin: ($baseline/2) 0; - height: 100%; - } - - td { - margin: ($baseline/2) 0; - padding: $baseline 0; - height: 100%; - } - - th { - margin: ($baseline/4); - padding: ($baseline/4); - } - - label, - .view-only { - position: relative; - display: inline-block; - margin: 3px; - padding: ($baseline*0.75); - min-width: 50px; - min-height: 50px; - width: 150px; - height: 100%; - background-color: $gray-l3; - font-size: .9em; - } - - .grade { - position: absolute; - right: 0; - bottom: 0; - margin: ($baseline/2); - } - - .selected-grade { - background: #666; - color: white; - } - - input[type=radio]:checked + label { - background: #666; - color: white; - } - - input[class='score-selection'] { - display: none; - } - } -} - -// +Problem - Annotation -// ==================== -div.problem { - .annotation-input { - margin: 0 0 1em 0; - border: 1px solid $gray-l3; - border-radius: 1em; - - .annotation-header { - @extend %t-strong; - - padding: .5em 1em; - border-bottom: 1px solid $gray-l3; - } - - .annotation-body { padding: .5em 1em; } - - a.annotation-return { - float: right; - font: inherit; - font-weight: normal; - } - - a.annotation-return::after { content: " \2191" } - - .block, ul.tags { - margin: .5em 0; - padding: 0; - } - - .block-highlight { - padding: .5em; - border: 1px solid darken($annotation-yellow, 10%); - background-color: $annotation-yellow; - color: #333; - font-style: normal; - } - - .block-comment { font-style: italic; } - - ul.tags { - display: block; - margin-left: 1em; - list-style-type: none; - - li { - position: relative; - display: block; - margin: 1em 0 0 0; - - .tag { - @extend %ui-fake-link; - - display: inline-block; - margin-left: ($baseline*2); - border: 1px solid rgb(102,102,102); - - &.selected { - background-color: $annotation-yellow; - } - } - - .tag-status { - position: absolute; - left: 0; - } - .tag-status, .tag { padding: .25em .5em; } - } - } - - textarea.comment { - $num-lines-to-show: 5; - $line-height: 1.4em; - $padding: .2em; - - padding: $padding (2 * $padding); - width: 100%; - height: ($num-lines-to-show * $line-height) + (2*$padding) - (($line-height - 1)/2); - line-height: $line-height; - } - - .answer-annotation { display: block; margin: 0; } - - /* for debugging the input value field. enable the debug flag on the inputtype */ - .debug-value { - margin: 1em 0; - padding: 1em; - border: 1px solid $black; - background-color: #999; - color: $white; - - input[type="text"] { width: 100%; } - - pre { background-color: $gray-l3; color: $black; } - - &::before { - @extend %t-strong; - - display: block; - content: "debug input value"; - font-size: 1.5em; - } - } - } -} - -// +Problem - Choice Text Group -// ==================== -div.problem { - .choicetextgroup { - @extend %choicegroup-base; - - input[type="text"]{ - margin-bottom: 0.5em; - } - - label.choicetextgroup_correct, section.choicetextgroup_correct { - @extend label.choicegroup_correct; - - input[type="text"] { - border-color: $correct; - } - } - - label.choicetextgroup_partially-correct, section.choicetextgroup_partially-correct { - @extend label.choicegroup_partially-correct; - - input[type="text"] { - border-color: $partially-correct; - } - } - - label.choicetextgroup_incorrect, section.choicetextgroup_incorrect { - @extend label.choicegroup_incorrect; - } - - label.choicetextgroup_submitted, section.choicetextgroup_submitted { - @extend label.choicegroup_submitted; - } - - label.choicetextgroup_show_correct, section.choicetextgroup_show_correct { - &::after { - @include margin-left($baseline*0.75); - - content: url('#{$static-path}/images/correct-icon.png'); - } - } - - span.mock_label { - cursor : default; - } - } -} - -// +Problem - Image Input Overrides -// ==================== - -// NOTE: temporary override until image inputs use same base html structure as other common capa input types. -div.problem .imageinput.capa_inputtype { - .status { - display: inline-block; - position: relative; - top: 3px; - width: 25px; - height: 20px; - - &.unsubmitted, - &.unanswered { - .status-icon { - content: ''; - } - - .status-message { - display: none; - } - } - } - - .correct { - @include status-icon($correct, $checkmark-icon); - } - - .incorrect { - @include status-icon($incorrect, $cross-icon); - } - - .partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - } - - .submitted { - content: ''; - } -} - -// +Problem - Annotation Problem Overrides -// ==================== - -// NOTE: temporary override until annotation problem inputs use same base html structure as other common capa input types. -div.problem .annotation-input { - .tag-status { - display: inline-block; - position: relative; - top: 3px; - width: 25px; - height: 20px; - - &.unsubmitted, - &.unanswered { - .status-icon { - content: ''; - } - - .status-message { - display: none; - } - } - } - - .correct { - @include status-icon($correct, $checkmark-icon); - } - - .incorrect { - @include status-icon($incorrect, $cross-icon); - } - - .partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - } - - .submitted { - content: ''; - } -} - -// Loading Spinner -// ==================== -.problems-wrapper .loading-spinner { - text-align: center; - color: $gray-d1; -} diff --git a/xmodule/js/src/capa/display.js b/xmodule/js/src/capa/display.js index 0ab45f6cd7ff..756a771e4dfe 100644 --- a/xmodule/js/src/capa/display.js +++ b/xmodule/js/src/capa/display.js @@ -906,19 +906,16 @@ jax, mathjaxPreprocessor, preprocessorTag, - target, 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 (isMathJaxRefreshReady()) { - var math = document.querySelector(target); + var math = document.getElementById("display_" + elid); if (!math) { math = document.getElementById(element.id + "_preview"); } @@ -932,7 +929,8 @@ MathJax.typesetClear([math]); if (!eqn) { math.textContent = ""; - $("#" + element.id + "_dynamath").val(""); + var dynInput = document.getElementById(element.id + "_dynamath"); + if (dynInput) { dynInput.value = ""; } return; } // Determine rendering mode: @@ -955,14 +953,18 @@ if (jax) { this.updateMathML(jax, element); } - }.bind(this)); + }.bind(this)).catch(function () { + var dynInput = document.getElementById(element.id + "_dynamath"); + if (dynInput) { dynInput.value = ""; } + }); } }; Problem.prototype.updateMathML = function (jax, element) { if (isMathJaxMathMLReady()) { try { - $("#" + element.id + "_dynamath").val(MathJax.startup.toMML(jax.root)); + var dynInput = document.getElementById(element.id + "_dynamath"); + if (dynInput) { dynInput.value = MathJax.startup.toMML(jax.root); } } catch (exception) { if (!exception.restart) { throw exception;