From e6c9a208219a164c924adc27ca1a65f68fd45b65 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 18 Oct 2023 20:09:33 +0530 Subject: [PATCH 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 07/12] 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 08/12] 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; From 65cffcf6c229868ce521e7882cb5674a16c6e3e7 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 21 Jul 2026 22:15:39 +0530 Subject: [PATCH 09/12] fix(mathjax): restore dollar delimiters --- 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 ++ .../common/js/discussion/mathjax_include.js | 2 ++ common/templates/mathjax_include.html | 2 ++ common/templates/xblock_v2/xblock_iframe.html | 2 ++ lms/djangoapps/courseware/tests/test_views.py | 18 ++++++++++++++++++ lms/djangoapps/courseware/views/views.py | 5 ++++- lms/static/lms/js/spec/main.js | 2 ++ 10 files changed, 38 insertions(+), 1 deletion(-) diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index 643b640670c8..be080b06d13d 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -19,10 +19,12 @@ define( window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js index a8edb19c9f2b..0d3df4487e84 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -50,10 +50,12 @@ window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index 764248f5fe56..ba8a45d18c21 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -23,10 +23,12 @@ window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js index e3545f2202a9..e2863ef72438 100644 --- a/cms/static/cms/js/spec/main_squire.js +++ b/cms/static/cms/js/spec/main_squire.js @@ -9,10 +9,12 @@ window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/common/static/common/js/discussion/mathjax_include.js b/common/static/common/js/discussion/mathjax_include.js index 468ad7046ade..17a0d7817e8e 100644 --- a/common/static/common/js/discussion/mathjax_include.js +++ b/common/static/common/js/discussion/mathjax_include.js @@ -2,10 +2,12 @@ if (typeof MathJax === 'undefined') { window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html index fa9a7d4a0192..262a3a5d4661 100644 --- a/common/templates/mathjax_include.html +++ b/common/templates/mathjax_include.html @@ -43,10 +43,12 @@ window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ["\\(","\\)"], ['[mathjaxinline]','[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ["\\[","\\]"], ['[mathjax]','[/mathjax]'] ], diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index d2452aae9c1f..a129146d18ed 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -126,10 +126,12 @@ window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ["\\(","\\)"], ['[mathjaxinline]','[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ["\\[","\\]"], ['[mathjax]','[/mathjax]'] ], diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index b2968d03672b..045dc1d3ebae 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -2811,6 +2811,8 @@ def setUp(self): display_name='Vertical with No Mathjax HTML', ) MATHJAX_TAG_PAIRS = [ + ("$", "$"), + ("$$", "$$"), (r"\(", r"\)"), (r"\[", r"\]"), ("[mathjaxinline]", "[/mathjaxinline]"), @@ -2831,6 +2833,12 @@ def setUp(self): display_name="HTML Without Mathjax", data="

I talk about mathjax, but I have no actual Math!

", ) + self.html_with_lone_dollar = BlockFactory.create( + category='html', + parent_location=self.no_math_vertical.location, + display_name="HTML With Lone Dollar", + data="

Price: $5

", + ) self.course_key = self.course.id self.user = UserFactory(username='staff_user', profile__country='AX', is_staff=True) @@ -2849,6 +2857,8 @@ def test_mathjax_detection(self): response = self.client.get(url) assert response.status_code == 200 assert b"window.MathJax" in response.content + assert b"['$', '$']" in response.content + assert b"['$$', '$$']" in response.content # Check the one without Math... url = reverse("render_xblock", kwargs={ @@ -2858,6 +2868,14 @@ def test_mathjax_detection(self): assert response.status_code == 200 assert b"window.MathJax" not in response.content + # Check lone dollar sign isn't mistaken for MathJax + url = reverse("render_xblock", kwargs={ + 'usage_key_string': str(self.html_with_lone_dollar.location) + }) + response = self.client.get(url) + assert response.status_code == 200 + assert b"window.MathJax" not in response.content + # The containing vertical should still return MathJax (for now) url = reverse("render_xblock", kwargs={ 'usage_key_string': str(self.no_math_vertical.location) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 027f0d4f6591..81381a1ee393 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -1802,6 +1802,8 @@ def has_mathjax_content(self): # The following pairs are used to mark Mathjax syntax in XBlocks. There # are other options for the wiki, but we don't worry about those here. MATHJAX_TAG_PAIRS = [ + ("$", "$"), + ("$$", "$$"), (r"\(", r"\)"), (r"\[", r"\]"), ("[mathjaxinline]", "[/mathjaxinline]"), @@ -1810,7 +1812,8 @@ def has_mathjax_content(self): content = self.fragment.body_html() for (start_tag, end_tag) in MATHJAX_TAG_PAIRS: if start_tag in content and end_tag in content: - return True + if start_tag != end_tag or content.count(start_tag) >= 2: + return True return False diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 61e58e794f43..d46c45f57d00 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -9,10 +9,12 @@ window.MathJax = { tex: { inlineMath: [ + ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ + ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], From 8d1596604cb6f3878f1242b567e13a8a9ce1d280 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 28 Jul 2026 10:44:31 +0530 Subject: [PATCH 10/12] fix(mathjax): remove redundant dollar delimiter config and detection Dollar delimiters ($...$, $$...$$) are MathJax 3 defaults, so explicit config is redundant. Remove them from config, inline detection, and tests, and simplify the tag-detection logic (no remaining self-matching pairs). --- 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 -- .../common/js/discussion/mathjax_include.js | 2 -- common/templates/mathjax_include.html | 2 -- common/templates/xblock_v2/xblock_iframe.html | 2 -- lms/djangoapps/courseware/tests/test_views.py | 18 ------------------ lms/djangoapps/courseware/views/views.py | 5 +---- lms/static/lms/js/spec/main.js | 2 -- 10 files changed, 1 insertion(+), 38 deletions(-) diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index be080b06d13d..643b640670c8 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -19,12 +19,10 @@ define( window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js index 0d3df4487e84..a8edb19c9f2b 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -50,12 +50,10 @@ window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index ba8a45d18c21..764248f5fe56 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -23,12 +23,10 @@ window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js index e2863ef72438..e3545f2202a9 100644 --- a/cms/static/cms/js/spec/main_squire.js +++ b/cms/static/cms/js/spec/main_squire.js @@ -9,12 +9,10 @@ window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/common/static/common/js/discussion/mathjax_include.js b/common/static/common/js/discussion/mathjax_include.js index 17a0d7817e8e..468ad7046ade 100644 --- a/common/static/common/js/discussion/mathjax_include.js +++ b/common/static/common/js/discussion/mathjax_include.js @@ -2,12 +2,10 @@ if (typeof MathJax === 'undefined') { window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html index 262a3a5d4661..fa9a7d4a0192 100644 --- a/common/templates/mathjax_include.html +++ b/common/templates/mathjax_include.html @@ -43,12 +43,10 @@ window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ["\\(","\\)"], ['[mathjaxinline]','[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ["\\[","\\]"], ['[mathjax]','[/mathjax]'] ], diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index a129146d18ed..d2452aae9c1f 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -126,12 +126,10 @@ window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ["\\(","\\)"], ['[mathjaxinline]','[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ["\\[","\\]"], ['[mathjax]','[/mathjax]'] ], diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 045dc1d3ebae..b2968d03672b 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -2811,8 +2811,6 @@ def setUp(self): display_name='Vertical with No Mathjax HTML', ) MATHJAX_TAG_PAIRS = [ - ("$", "$"), - ("$$", "$$"), (r"\(", r"\)"), (r"\[", r"\]"), ("[mathjaxinline]", "[/mathjaxinline]"), @@ -2833,12 +2831,6 @@ def setUp(self): display_name="HTML Without Mathjax", data="

I talk about mathjax, but I have no actual Math!

", ) - self.html_with_lone_dollar = BlockFactory.create( - category='html', - parent_location=self.no_math_vertical.location, - display_name="HTML With Lone Dollar", - data="

Price: $5

", - ) self.course_key = self.course.id self.user = UserFactory(username='staff_user', profile__country='AX', is_staff=True) @@ -2857,8 +2849,6 @@ def test_mathjax_detection(self): response = self.client.get(url) assert response.status_code == 200 assert b"window.MathJax" in response.content - assert b"['$', '$']" in response.content - assert b"['$$', '$$']" in response.content # Check the one without Math... url = reverse("render_xblock", kwargs={ @@ -2868,14 +2858,6 @@ def test_mathjax_detection(self): assert response.status_code == 200 assert b"window.MathJax" not in response.content - # Check lone dollar sign isn't mistaken for MathJax - url = reverse("render_xblock", kwargs={ - 'usage_key_string': str(self.html_with_lone_dollar.location) - }) - response = self.client.get(url) - assert response.status_code == 200 - assert b"window.MathJax" not in response.content - # The containing vertical should still return MathJax (for now) url = reverse("render_xblock", kwargs={ 'usage_key_string': str(self.no_math_vertical.location) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 81381a1ee393..027f0d4f6591 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -1802,8 +1802,6 @@ def has_mathjax_content(self): # The following pairs are used to mark Mathjax syntax in XBlocks. There # are other options for the wiki, but we don't worry about those here. MATHJAX_TAG_PAIRS = [ - ("$", "$"), - ("$$", "$$"), (r"\(", r"\)"), (r"\[", r"\]"), ("[mathjaxinline]", "[/mathjaxinline]"), @@ -1812,8 +1810,7 @@ def has_mathjax_content(self): content = self.fragment.body_html() for (start_tag, end_tag) in MATHJAX_TAG_PAIRS: if start_tag in content and end_tag in content: - if start_tag != end_tag or content.count(start_tag) >= 2: - return True + return True return False diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index d46c45f57d00..61e58e794f43 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -9,12 +9,10 @@ window.MathJax = { tex: { inlineMath: [ - ['$', '$'], ['\\(', '\\)'], ['[mathjaxinline]', '[/mathjaxinline]'] ], displayMath: [ - ['$$', '$$'], ['\\[', '\\]'], ['[mathjax]', '[/mathjax]'] ], From d7e7ed0ce3556ac221730fd993ed5f9c4afbb27c Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Thu, 30 Jul 2026 16:46:53 +0530 Subject: [PATCH 11/12] fix: remove invalid settings --- cms/djangoapps/pipeline_js/js/xmodule.js | 4 +--- cms/static/cms/js/require-config.js | 3 +-- cms/static/cms/js/spec/main.js | 3 +-- cms/static/cms/js/spec/main_squire.js | 3 +-- common/static/common/js/discussion/mathjax_include.js | 3 +-- common/templates/mathjax_include.html | 7 ++----- common/templates/xblock_v2/xblock_iframe.html | 4 +--- lms/static/lms/js/spec/main.js | 3 +-- 8 files changed, 9 insertions(+), 21 deletions(-) diff --git a/cms/djangoapps/pipeline_js/js/xmodule.js b/cms/djangoapps/pipeline_js/js/xmodule.js index 643b640670c8..ba789cfa023c 100644 --- a/cms/djangoapps/pipeline_js/js/xmodule.js +++ b/cms/djangoapps/pipeline_js/js/xmodule.js @@ -37,9 +37,7 @@ define( processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true - // autocollapse: false, // Not found in v3 + collapsible: true }, }, }, diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js index a8edb19c9f2b..61c7fcb9dede 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -68,8 +68,7 @@ processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true + collapsible: true }, }, }, diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index 764248f5fe56..8a164e720abc 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -41,8 +41,7 @@ processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true + collapsible: true }, }, }, diff --git a/cms/static/cms/js/spec/main_squire.js b/cms/static/cms/js/spec/main_squire.js index e3545f2202a9..8f4b23f296f5 100644 --- a/cms/static/cms/js/spec/main_squire.js +++ b/cms/static/cms/js/spec/main_squire.js @@ -27,8 +27,7 @@ processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true + collapsible: true }, }, }, diff --git a/common/static/common/js/discussion/mathjax_include.js b/common/static/common/js/discussion/mathjax_include.js index 468ad7046ade..a3d171a8f596 100644 --- a/common/static/common/js/discussion/mathjax_include.js +++ b/common/static/common/js/discussion/mathjax_include.js @@ -20,8 +20,7 @@ if (typeof MathJax === 'undefined') { processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true + collapsible: true }, }, }, diff --git a/common/templates/mathjax_include.html b/common/templates/mathjax_include.html index fa9a7d4a0192..ba8522a93c40 100644 --- a/common/templates/mathjax_include.html +++ b/common/templates/mathjax_include.html @@ -27,8 +27,7 @@ processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true + collapsible: true }, }, }, @@ -61,9 +60,7 @@ processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true - // autocollapse: false, // Not found in v3 + collapsible: true }, }, }, diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index d2452aae9c1f..a8fe762a9ae6 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -144,9 +144,7 @@ processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true - // autocollapse: false, // Not found in v3 + collapsible: true }, }, }, diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 61e58e794f43..d5d01b5b6a04 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -27,8 +27,7 @@ processHtmlClass: 'tex2jax_process', menuOptions: { settings: { - collapsible: true, - explorer: true + collapsible: true }, }, }, From 0c6bf2b2f56e075fa9c13c0f27cc89a0143a0ccd Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 30 Oct 2026 16:57:35 +0530 Subject: [PATCH 12/12] chore: upragde ora2 and contrib --- requirements/edx/base.txt | 4 +-- requirements/edx/development.txt | 4 +-- uv.lock | 42 +++++++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index ea3eb9751a19..38bdf5329d05 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -867,7 +867,7 @@ openedx-forum==0.5.0 opentelemetry-api==1.45.1 # via google-api-core optimizely-sdk==5.7.0 -ora2==7.1.2 +ora2==7.1.3 packaging==26.3 # via # drf-yasg @@ -1318,7 +1318,7 @@ xblock==6.3.4 xblock-drag-and-drop-v2==5.1.2 xblock-google-drive==0.8.2 xblock-poll==1.16.1 -xblocks-contrib==1.0.7 +xblocks-contrib==1.0.8 xmlsec==1.3.14 # via python3-saml xss-utils==1.1.0 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index f2a51f27fa4d..4dcb35a3d7a0 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -974,7 +974,7 @@ openedx-forum==0.5.0 opentelemetry-api==1.45.1 # via google-api-core optimizely-sdk==5.7.0 -ora2==7.1.2 +ora2==7.1.3 packaging==26.3 # via # drf-yasg @@ -1598,7 +1598,7 @@ xblock==6.3.4 xblock-drag-and-drop-v2==5.1.2 xblock-google-drive==0.8.2 xblock-poll==1.16.1 -xblocks-contrib==1.0.7 +xblocks-contrib==1.0.8 xmlsec==1.3.14 # via python3-saml xss-utils==1.1.0 diff --git a/uv.lock b/uv.lock index e083f87755aa..deb2b8d5e10c 100644 --- a/uv.lock +++ b/uv.lock @@ -3112,6 +3112,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/42/f4/a0313ef208458d099 wheels = [ { url = "https://files.pythonhosted.org/packages/1c/a5/7094b0125310078cd37193b590925a6f7104f826e0ecf94a8d3ac75001c2/grimp-3.18-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:cad017a08dd7a7387b5208a640d4eee90a4152433080626aad09e6ce2d56a07d", size = 2153895, upload-time = "2026-10-26T09:57:57.693Z" }, { url = "https://files.pythonhosted.org/packages/73/03/dd45288eacd34c97ce76e2596a865bcf9815525417057379affca3423063/grimp-3.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4482b0551ff5596c5b578ae6820fd32a8e0db278c314eb24e7cfffd84f20cf06", size = 2099041, upload-time = "2026-10-26T09:57:48.107Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b9/3c59b026e38290c2935a657fd72cb22daea41a2e38a485a63ae162522d06/grimp-3.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:43d38ab816a9e5d3ed5e8448468e6ed61bccc491007bdafb10f1b2e433fcd215", size = 2276173, upload-time = "2026-10-30T09:05:26.777Z" }, + { url = "https://files.pythonhosted.org/packages/ba/15/f1aef5e8ca3a427108470281ee3a0185c9bca38277cc255060c81aa049d6/grimp-3.18-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:4d3bf444df87c4f3a2fe9a8d7ee95e6f28d37c961252821abc93ff872ee87097", size = 2224396, upload-time = "2026-10-30T09:05:38.203Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/7b72207aff83223fd081d41f335774f138973b8625e58508767a3245880a/grimp-3.18-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:26cec100e8af7e8c21bee1fb93c2c0e00ef468c5ceb42f03b4cd26f556e5ea67", size = 2365690, upload-time = "2026-10-30T09:06:04.795Z" }, + { url = "https://files.pythonhosted.org/packages/7c/13/922657cebff2fc8e25cac5f15165e63fc7880a33a6ebe7c59d0c3bc2f278/grimp-3.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c80ec28572574af7f867bc39b05a1768a295ffb3b73aa49ac6a188ae57b3c66d", size = 2629257, upload-time = "2026-10-30T09:05:46.886Z" }, + { url = "https://files.pythonhosted.org/packages/65/1a/960bb943b7b51ea5c036bee858b5e49416a6b98165ef01eae98a49f64576/grimp-3.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3b855e27874b633fa3a52e357b5804507fbd877ff097468197a2a463fd2c81b7", size = 2359483, upload-time = "2026-10-30T09:05:55.697Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cf/c133969021923097c0699f920721af4800f4d0540ec495596a3eb2c37e1f/grimp-3.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c046c18afd7123ef20abe3052b971c7645b1483dbde0b527ff0636f87077a77a", size = 2285072, upload-time = "2026-10-30T09:06:17.004Z" }, { url = "https://files.pythonhosted.org/packages/a9/a8/dc870213de5ac4360ab2433e888334fbdca8350bf7d2fdf455bdcd846706/grimp-3.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ca16afcf41957c89bf67970e08ffc69fbb339c1ebc84b6a728e2027914a0a9a", size = 2276176, upload-time = "2026-10-26T09:56:51.079Z" }, { url = "https://files.pythonhosted.org/packages/5c/5f/a1c80a1c0c2263b813c8c60ee4435c329d3e008872998e066104a1711f08/grimp-3.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:256a59bc8a8518dad521bcbd5d0d97398cb4e85304298a56950b504ce27a3818", size = 2224397, upload-time = "2026-10-26T09:57:02.294Z" }, { url = "https://files.pythonhosted.org/packages/64/25/d958b14791a80a2eff35901a0a688070134b182c46cb7cf7503eece9cc4f/grimp-3.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5760ae67db4a230e34e37a743facee2daab407176b1de8ad7a3e5fe87de1315b", size = 2365689, upload-time = "2026-10-26T09:57:27.532Z" }, @@ -3127,6 +3133,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d6/fc/188985ed3d5cba25e56d4772f3b43ad37f8cb6246a577b2987870a4baad3/grimp-3.18-cp312-cp312-win_arm64.whl", hash = "sha256:eb85a21ea7fed7335b6fcc6adf7335fe904b7cef37243092104d4cf0380a4587", size = 1925154, upload-time = "2026-10-26T09:58:40.063Z" }, { url = "https://files.pythonhosted.org/packages/bc/9b/ec9b451fe327b295d8cd1073fc3e395d07dc6be9cdbf50ca58014df9296e/grimp-3.18-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3965804099c7d87ca910f5215dd8b85abcb8ed19c4a1f111626b756b377d7416", size = 2153480, upload-time = "2026-10-26T09:57:59.235Z" }, { url = "https://files.pythonhosted.org/packages/d3/12/c4833b6feb84ef031626c62698ce1711898d0770208675faf0142cf53fef/grimp-3.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b791f9eadd965874682382214c4e3fd79b52476dea4e06b6043f4ecc3d6ef16", size = 2098697, upload-time = "2026-10-26T09:57:49.174Z" }, + { url = "https://files.pythonhosted.org/packages/6f/90/b6f79f177527e1dc6172c060358211bc6b923c49a262ad59497378f83dfd/grimp-3.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b3cdd4ab37d167faaed3fe4c4075e9598523d48c4e604ce36aec99147834a797", size = 2276129, upload-time = "2026-10-30T09:05:28.119Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0c/56705713dbf5908df12731917ab712e3ebee1da1e234135dddcafd86ae9b/grimp-3.18-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:90a837d042a64500536bd99ee41cf04e1b16f2e4eb378b8750b9f9366bc8533e", size = 2224129, upload-time = "2026-10-30T09:05:39.547Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8b/eb7da3168db995e6d918870b6b827d5436e4e045af5980cfa973978fe943/grimp-3.18-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e20504afdd0faa2fac8941d4c72fad40863619748d96abe7073caa7cb4f13c2", size = 2365249, upload-time = "2026-10-30T09:06:06.736Z" }, + { url = "https://files.pythonhosted.org/packages/6b/88/2b9b197a4285165fd79c12abd3cbc8b5f865008230db74c31b5fb9247271/grimp-3.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:64a250970137e097ce7b97b23014448856846af83934b054423a515d1bf87c35", size = 2629330, upload-time = "2026-10-30T09:05:48.26Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7c/46dc1ed6e71b262e283aec37d24be1f258e99ae17ed906e6cc1da6c70d9b/grimp-3.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a089f692d22fe5f5dba1e5c5c2cbd0c9f4b7ed420e0f3ed6e8efd2fc109ca137", size = 2361092, upload-time = "2026-10-30T09:05:57.302Z" }, + { url = "https://files.pythonhosted.org/packages/07/e8/9f8e3ade34fc572767b4c5fb8105083c5ede67f8df93e608d11cfc7a587a/grimp-3.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4426c7c57ade9ae8e323765b37b4c785b6e2cb374038b42d6b36110a50c4af46", size = 2285344, upload-time = "2026-10-30T09:06:18.565Z" }, { url = "https://files.pythonhosted.org/packages/bf/aa/77fe70a536ba9494e7e8c09a6d8071a511e40a5cd947fb772d248caeec58/grimp-3.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c59b458c6e07d90594b25076d6887847957c3c189e38a9c6a81cbefaeb13c6e", size = 2276130, upload-time = "2026-10-26T09:56:52.693Z" }, { url = "https://files.pythonhosted.org/packages/5b/81/6feb1cc684fe656d06f17edfd999705d37d392744db2bcb28429e77ca5de/grimp-3.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d107902da5048aca9be26111b8379f50e02dbdad4dfe54775ec8a2cb365018ef", size = 2224129, upload-time = "2026-10-26T09:57:03.427Z" }, { url = "https://files.pythonhosted.org/packages/1a/88/cfc843d0459af7923569b6e48275258be09e3b5796bc245fe353ccf43091/grimp-3.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf249c111e5e6e9fcc1bea1dc3f3e6069fac18bddda9b65ec8434da883948ab", size = 2365248, upload-time = "2026-10-26T09:57:28.704Z" }, @@ -3142,6 +3154,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/a4/438df0ddd0f02e3ce95625632558c63bdb6f75031e423314a649787b6554/grimp-3.18-cp313-cp313-win_arm64.whl", hash = "sha256:a2fe4fb3529b3e68e70cf1bb026effd129e6f3887f5236c6062b89680f9214bf", size = 1925031, upload-time = "2026-10-26T09:58:41.348Z" }, { url = "https://files.pythonhosted.org/packages/8d/95/f1659bff361c15293321f9ab4ddf0c93c0a32c1c6d2e6e7df4d6bf12a8e4/grimp-3.18-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6e26c3020843ceeda2bb699ea09e27b6654f7823b51e779b9c607e204b34e1f4", size = 2152974, upload-time = "2026-10-26T09:58:00.403Z" }, { url = "https://files.pythonhosted.org/packages/f1/9a/f4aa9e284fb82874c4f588143557ae4f871ed52c6689a55c49968b6e3b44/grimp-3.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7bc0587c8622e1275e217e924c27e54420f7a06db88d133f4adad18a0298b040", size = 2099498, upload-time = "2026-10-26T09:57:50.493Z" }, + { url = "https://files.pythonhosted.org/packages/e0/89/ca91f3a223f9b1dce8aaf23a7b92a8afdb3c7bf6597348c793f3eef6e5d1/grimp-3.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5083d474794a20d8b3d53c2955d9cc6e590fe3d55a467ec6e4940d2d1413e5d3", size = 2276232, upload-time = "2026-10-30T09:05:29.668Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/b782fc977992e0460991e9bde35e15d40e41057427ded9e8612a87a00400/grimp-3.18-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:e3e101de453ba3a5ed7e6776857e12e75913a71254779240b952b00fb99be327", size = 2224038, upload-time = "2026-10-30T09:05:40.995Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7e/d95e166c2aafc14a20bdcda3e251c197d411d765e674db7d3a48532b5e14/grimp-3.18-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d5ca964848d32b871a6b5755ec9e2c8499945268fb07065805616c2f0ccc7b94", size = 2365371, upload-time = "2026-10-30T09:06:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a3/c092a0c5458378962e11df965e6588743d9544019e1c0b375a2d144e2b7f/grimp-3.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:742661cf3af03b9cae83abbd47a86e3fee2e5241c9c0ca1506ee3a2d9d21c835", size = 2630405, upload-time = "2026-10-30T09:05:49.75Z" }, + { url = "https://files.pythonhosted.org/packages/41/bf/2f7fff6f1a7ed5209fdc42455c7aeb76d5e64017db1641cf30ee9e8c39dd/grimp-3.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cc513ef2584df912b7bc165a7a1d549cf8f6032e80fc11cf947ca9c2fcf3c5a2", size = 2359624, upload-time = "2026-10-30T09:05:58.832Z" }, + { url = "https://files.pythonhosted.org/packages/d7/bd/defaf1fa6f19173b1b4bcf87311a9bc72d66e6e6cddd57cec8473fd4030e/grimp-3.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47ff6d75a2b8db5c0b98be66be74ff7d7ba6d603feca3b5cbe6684a849ffb08c", size = 2286960, upload-time = "2026-10-30T09:06:19.997Z" }, { url = "https://files.pythonhosted.org/packages/e8/66/a96e7b9ecf3ffce2482d303663959a9f4d4ec33fceb667cc5071cd62a161/grimp-3.18-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1453b4b00c324cdaabb5c70f57d45a84882dd877883cefbda859fd66b400bcd6", size = 2276234, upload-time = "2026-10-26T09:56:53.902Z" }, { url = "https://files.pythonhosted.org/packages/a4/29/8bb9dccd6390ff78b6e49f6e073afe90c6add6f71e4f795e217ce0846fdb/grimp-3.18-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2611d37e446eee05e6621a28e4ae7b612899715cae637037404e7a0d91f327b", size = 2224037, upload-time = "2026-10-26T09:57:04.89Z" }, { url = "https://files.pythonhosted.org/packages/18/af/25bf0c6cd7c1fe4f77be0354a96337cee516a4b48bbfe48ca1745ec70cc6/grimp-3.18-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438544f3e7aaed32ddc09d8a8f6685e67e7ce43f0931f7149426fb7b483a6df6", size = 2365372, upload-time = "2026-10-26T09:57:29.904Z" }, @@ -3157,6 +3175,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/6b/d07a1f733c75522985e733990884a341009676ef41b135c5141d7599d401/grimp-3.18-cp314-cp314-win_arm64.whl", hash = "sha256:932e992a798c4b69ad77957cc09517c22cd1aa3b07cc85aba41e14406cb1a42f", size = 1925674, upload-time = "2026-10-26T09:58:42.745Z" }, { url = "https://files.pythonhosted.org/packages/1f/7c/aa8d7bfe872f4bfba342ee0dae17eadd34dcc1e73b8822765f0528eea3e4/grimp-3.18-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:73633cf2c822f34a38dd77e34e015afd6fc489998abeab8b91a7149fc63c1a1b", size = 2153687, upload-time = "2026-10-26T09:58:01.827Z" }, { url = "https://files.pythonhosted.org/packages/1c/73/613c1e57e3db9c81779e4e5f7e6bb2c69118da64be9a4aba69cc74401594/grimp-3.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1d7418bc8b511a12e21099b43349588785254d3d38ecf0dac88aa927c91ebc47", size = 2099246, upload-time = "2026-10-26T09:57:51.73Z" }, + { url = "https://files.pythonhosted.org/packages/30/38/8962a691c78fde0be777e67805e994944000551882dce617b03a99d5e02b/grimp-3.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:34090d38fba4223a6a9a5cc8f5c7523fdd9db7dfaef80dd90376d45242e59704", size = 2276732, upload-time = "2026-10-30T09:05:31.154Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ad/1c0ac6be3ea422ed8431bb392a0041c11300164761f2d2d3200e4f222f1d/grimp-3.18-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:3463911ead5015eaa91da3721b84c900cdcaec22d149133827a76ea86d798cb1", size = 2220324, upload-time = "2026-10-30T09:05:42.395Z" }, + { url = "https://files.pythonhosted.org/packages/09/00/a30fdaa8c0793db7dfc17590f341e1e82b30989602338205030d4e4394d0/grimp-3.18-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b989ad42e66d2ffb75dddfbc14bb980790fbbce6f58fdccc3df99cebf4661689", size = 2365815, upload-time = "2026-10-30T09:06:09.694Z" }, + { url = "https://files.pythonhosted.org/packages/2b/19/1965cf2d8d218f0d763872c32362cf5dc05eb5c192c7a5996089e41e048a/grimp-3.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4390a69445c586c1ad1c4aaab9e3ec44fcd3f662c04476dbb77b3b6dde5ed6ac", size = 2629781, upload-time = "2026-10-30T09:05:51.184Z" }, + { url = "https://files.pythonhosted.org/packages/26/eb/26a06c8eab6a252c8c1e0a3bbe3cf71ddd9b0e3c288c6ee474df635e3f3b/grimp-3.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a34f3414a8f944d1b30b958fe571f5ca07cd6fa8fd7dcffea89e995a6cd5ee0f", size = 2359278, upload-time = "2026-10-30T09:06:00.328Z" }, + { url = "https://files.pythonhosted.org/packages/cb/02/d9497138b299b45eebe856159605752be9304fd661ab87f507fdbc10fa2e/grimp-3.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98e62116c9a323f00b580b6d9a4aade24d047bdf37b3a4f5098098aa055063be", size = 2290843, upload-time = "2026-10-30T09:06:21.903Z" }, { url = "https://files.pythonhosted.org/packages/4b/95/eedf8708e71345d3a2adea470c493ecb1bc368e295d01a02b7c1777cc2c8/grimp-3.18-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ecaf989c284fbd0abc8710b79565b5ac2165b9d74e77ced52463c576e5b00a0", size = 2276734, upload-time = "2026-10-26T09:56:55.162Z" }, { url = "https://files.pythonhosted.org/packages/7c/43/8ee0455db564d983b0dd5875b709e360c68e54e454f7b14376f639b5ef27/grimp-3.18-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07abd5b67a0d2b8d1809cdea9f18d83c8a92525dc54b50726a30cfe89eb66d55", size = 2220324, upload-time = "2026-10-26T09:57:06.484Z" }, { url = "https://files.pythonhosted.org/packages/6b/84/5720f66a65e084f6cb126f041454b1d6921ff3bc9ce1ffed559508c2621d/grimp-3.18-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a59c0727f35f765a93c20cc0141fbab5bad4ae58b5fb80c9b7ed4e7ac3b3554", size = 2365814, upload-time = "2026-10-26T09:57:31.19Z" }, @@ -3172,6 +3196,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/d0/d0c2119662b549312166c8aac2e96028f5253075e60587d882e0aba4e673/grimp-3.18-cp314-cp314t-win_arm64.whl", hash = "sha256:a79cde5a2052c0c22dfd5ce0e3cb2fa0ef68625c480d779bd331f9d0bf3459f3", size = 1924204, upload-time = "2026-10-26T09:58:44.285Z" }, { url = "https://files.pythonhosted.org/packages/74/34/92e2e51eb0a832e56171196210793eb530d3d51a86814389ca4614237aca/grimp-3.18-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3a53760b5623c7a0a48caf5981355953f2c2a33c6d78593aad77f40c9070f526", size = 2153238, upload-time = "2026-10-26T09:58:03.035Z" }, { url = "https://files.pythonhosted.org/packages/68/50/10fa8494dcaf0d644a1ec66ad20773686dd2143e4e54fdee6738d6815c9c/grimp-3.18-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:18b0901afede6f7f630d285da0f68ec462830550d6e7760ad04dd3cfe94830fa", size = 2100255, upload-time = "2026-10-26T09:57:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/06/cf/4bc257061f01663f7c960b051620052052dcce7e2f1bc3450b6e1b2de71c/grimp-3.18-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8e76d4e7df434abf383a9ec9aeffa20162b46944f0ba7a8854eaec604e97d5", size = 2276193, upload-time = "2026-10-30T09:05:32.518Z" }, + { url = "https://files.pythonhosted.org/packages/82/43/88f5be6b47d95c22419bbf9dd4d39b5a991df9155f7ca685f4c68c9239c4/grimp-3.18-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4f0f47e58d205decaac5b2189b0225ba7a2c30f631cd57525f705f9e5193b30e", size = 2367638, upload-time = "2026-10-30T09:06:11.145Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a8/a26b49bc3705f9ea06a4bb7f0d6d49d31c49b554bdc8cfd59af24ce4f9d9/grimp-3.18-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3586faa07635845a7d541844ea4969af365cebe9cfaa405e7a504f51a04a3b82", size = 2287563, upload-time = "2026-10-30T09:06:23.444Z" }, { url = "https://files.pythonhosted.org/packages/30/8c/f10af5f3fad0f8c24373088c5e95354f43fdea5eb2b1af70f22cd102f6b8/grimp-3.18-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73d06cc1c29b3e96558e2f929a0253b4fc45e12a32d1b2dac0b950575fd7ff95", size = 2276196, upload-time = "2026-10-26T09:56:56.674Z" }, { url = "https://files.pythonhosted.org/packages/04/ee/e894a7721af42576295920eb4d1b01f043eccdeaf7b09e9c466da2ffa415/grimp-3.18-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45dda7406194bd486b1f43674fbbdeaf5c6b66696647944652f4ef205aff1792", size = 2367636, upload-time = "2026-10-26T09:57:32.446Z" }, { url = "https://files.pythonhosted.org/packages/0f/11/c59a63483cd45da04dad7efc6dfd0984f469c468d46f1ebbb63faabf1bda/grimp-3.18-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4647f81d393909e903b0cb4ce910b65c1f85c26bd20ea6addbd83c87eee5a1a7", size = 2287564, upload-time = "2026-10-26T09:57:42.883Z" }, @@ -3180,6 +3207,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/5c/d7fd4ad4b8efe237f3650a727815c81caa708731342d1d6582ee7fcf1286/grimp-3.18-cp315-cp315-win_arm64.whl", hash = "sha256:f52b5cce5bfa785f1e91c952124c8a264789acd25240a2478636db0c9b61dab3", size = 1925528, upload-time = "2026-10-26T09:58:45.978Z" }, { url = "https://files.pythonhosted.org/packages/bb/81/18f6a2b1df6a856b8e2c65dd779a1487ac994da63d7712668a75ae0fd022/grimp-3.18-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:050332a3e09da40624d147d0b8521fad50a9d205ab228737965a388cd252eb56", size = 2154062, upload-time = "2026-10-26T09:58:04.114Z" }, { url = "https://files.pythonhosted.org/packages/b1/52/206cc2c4255a8952b0b39b8d12f06c97629c4815a854bb535fbbeb1594f5/grimp-3.18-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:be75ccc563e7452949c7330ed06d8d128eaaff4433fdc09dde363ecd35552219", size = 2099675, upload-time = "2026-10-26T09:57:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/ef/50/dede3953e96942c675afe3f30a1b8e94e43d44d091d9dff8bfba6e3e624b/grimp-3.18-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a578687f1a54c9b114199d56574306599ca08f1b6a95b81f4ef0c60a7aede475", size = 2277279, upload-time = "2026-10-30T09:05:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/00/4c/d0e9f6564e423a927b3b15170434d78ff4508a4b60776f36d81cc465f35d/grimp-3.18-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2506958d0b6fe01759cb633d39489bd09f1fb0ce4b9a9228d9ce9ac00863cca1", size = 2367202, upload-time = "2026-10-30T09:06:12.672Z" }, + { url = "https://files.pythonhosted.org/packages/09/f9/3322baf92a741cb64f684e9da55123b4c07c0faacfe0a1910e14b20e3025/grimp-3.18-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:eb12a7c3e3f0e5a60265e8264a71b35a5fb0db80fee55d4cc7a312e992fc1c36", size = 2291033, upload-time = "2026-10-30T09:06:25.01Z" }, { url = "https://files.pythonhosted.org/packages/03/82/da3cb63c8d17ae558078099ffaca7dc6db6bc865952fa10949eceb299e28/grimp-3.18-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91b8b9eaf74953133b9516715bcbc274ebbcf925c8b8ce17ad5f0a433a8a75d", size = 2277276, upload-time = "2026-10-26T09:56:58.13Z" }, { url = "https://files.pythonhosted.org/packages/98/2e/8011ad74d892b6f704951f0ebbccca13b23cce78134502cd260a2bf0cc61/grimp-3.18-cp315-cp315t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bf220bf26eb007010772601b1143262d0faeb2d6161191ba2684abd5ccf5b4b", size = 2367201, upload-time = "2026-10-26T09:57:33.85Z" }, { url = "https://files.pythonhosted.org/packages/b7/e4/fceecab7b017f2d9358e9585691f8af541a378091ee70af919775c74feb5/grimp-3.18-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26b386fbb260be374bf8e4b5250ee608f4a0b0597c428b6ede3fdde6308b258", size = 2291034, upload-time = "2026-10-26T09:57:44.144Z" }, @@ -5587,7 +5617,7 @@ wheels = [ [[package]] name = "ora2" -version = "7.1.2" +version = "7.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bleach" }, @@ -5617,9 +5647,9 @@ dependencies = [ { name = "voluptuous" }, { name = "xblock" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/d9/8b25c08de518772c753162e4a3379bed9084e289a03ee287117031cc2aed/ora2-7.1.2.tar.gz", hash = "sha256:d9082095adc6d6f19f2866fbe9fdc05b469d20298eff01dd5f0e0a1f8979e8e9", size = 5047665, upload-time = "2026-10-23T15:00:58.986Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/f5/b91e66b078378cad4e71c4ba17af5208fe11e0f5f80f44f8effc619621dc/ora2-7.1.3.tar.gz", hash = "sha256:afcde7f09216c6d92bca3062246a3fe1b9ac202d615fecb897fedaf3fe280d22", size = 5082821, upload-time = "2026-10-29T13:46:26.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/9c/b10e2ca4c5f27b8466d51bee3060bd299eb25ed2bb1276e2342823f0f7e1/ora2-7.1.2-py3-none-any.whl", hash = "sha256:26525712fca2d38a0e869652da1e3230fdc71549cd0bf78b8bbcaa130b1da764", size = 2601579, upload-time = "2026-10-23T15:00:56.687Z" }, + { url = "https://files.pythonhosted.org/packages/91/5b/58a5052f004a1dad6da230f72ce24e46ee0e7260506d33bc36c111ab6341/ora2-7.1.3-py3-none-any.whl", hash = "sha256:532431f4e264019503c05166223e6a7a1e8bb26b5a72da2155076f1b376c6bef", size = 2602594, upload-time = "2026-10-29T13:46:24.922Z" }, ] [[package]] @@ -8567,7 +8597,7 @@ wheels = [ [[package]] name = "xblocks-contrib" -version = "1.0.7" +version = "1.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -8600,9 +8630,9 @@ dependencies = [ { name = "wrapt" }, { name = "xblock" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/0f/6dfd257da62f54023f89571f2145641b6def174bd354680bd1c029cb47ce/xblocks_contrib-1.0.7.tar.gz", hash = "sha256:23022249dfdc2960f59005064d337236806a909be055aca45c2c37123d8aea01", size = 22138641, upload-time = "2026-10-12T06:09:12.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/6a/789fc00b182da369312230086506111d14f7e97bb4ec08bf441c76f0fd5f/xblocks_contrib-1.0.8.tar.gz", hash = "sha256:b8c7c4b3f4de99fc0197e3950b8d606579c84a946be76dd417fac2ece62fea2f", size = 22143336, upload-time = "2026-10-29T17:24:31.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/35/33db566382d6f369267ccc82bae744c354fbbaa5ed6539c04982a003f375/xblocks_contrib-1.0.7-py3-none-any.whl", hash = "sha256:8c52480066b3df10b4219922a0a491715a975d105575b138f90cfab1ed5a5fd9", size = 3768557, upload-time = "2026-10-12T06:09:11.061Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/a859239c97b8691ab37d5f0562267fbda381f4b50a991c74a004907d8a1c/xblocks_contrib-1.0.8-py3-none-any.whl", hash = "sha256:704015799cbb1eac2b81f981d7284c3eadc0bbc9900d9522f735c9403bb00b85", size = 3769108, upload-time = "2026-10-29T17:24:28.693Z" }, ] [[package]]