From 9081494e312c114444830ecf04470b382ccd0d5d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 16 Sep 2024 12:59:48 +0200 Subject: [PATCH] MOBILE-4587 qtype: Fix race condition with MathJax in D&D questions --- .../services/handlers/mathjaxloader.ts | 36 +++++++++++-------- .../component/addon-qtype-ddimageortext.html | 2 +- .../component/addon-qtype-ddmarker.html | 2 +- src/addons/qtype/ddwtos/classes/ddwtos.ts | 5 --- .../ddwtos/component/addon-qtype-ddwtos.html | 2 +- src/addons/qtype/ddwtos/component/ddwtos.ts | 8 +++++ src/core/directives/format-text.ts | 18 ++++++---- 7 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts index acf7683cb..9728707e1 100644 --- a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts +++ b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts @@ -177,7 +177,7 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan ): Promise { await this.waitForReady(); - this.window.M!.filter_mathjaxloader!.typeset(container); + await this.window.M!.filter_mathjaxloader!.typeset(container); } /** @@ -234,24 +234,32 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan } }, // Called by the filter when an equation is found while rendering the page. - typeset: function (container: HTMLElement): void { + typeset: async function (container: HTMLElement): Promise { if (!this._configured) { this._setLocale(); } - if (that.window.MathJax !== undefined) { - const processDelay = that.window.MathJax.Hub.processSectionDelay; - // Set the process section delay to 0 when updating the formula. - that.window.MathJax.Hub.processSectionDelay = 0; - - const equations = Array.from(container.querySelectorAll('.filter_mathjaxloader_equation')); - equations.forEach((node) => { - that.window.MathJax.Hub.Queue(['Typeset', that.window.MathJax.Hub, node], [that.fixUseUrls, node]); - }); - - // Set the delay back to normal after processing. - that.window.MathJax.Hub.processSectionDelay = processDelay; + if (that.window.MathJax === undefined) { + return; } + + const processDelay = that.window.MathJax.Hub.processSectionDelay; + // Set the process section delay to 0 when updating the formula. + that.window.MathJax.Hub.processSectionDelay = 0; + + const equations = Array.from(container.querySelectorAll('.filter_mathjaxloader_equation')); + const promises = equations.map((node) => new Promise((resolve) => { + that.window.MathJax.Hub.Queue( + ['Typeset', that.window.MathJax.Hub, node], + [that.fixUseUrls, node], + [resolve], + ); + })); + + // Set the delay back to normal after processing. + that.window.MathJax.Hub.processSectionDelay = processDelay; + + await Promise.all(promises); }, }; } diff --git a/src/addons/qtype/ddimageortext/component/addon-qtype-ddimageortext.html b/src/addons/qtype/ddimageortext/component/addon-qtype-ddimageortext.html index ede71fe99..0870e8fd0 100644 --- a/src/addons/qtype/ddimageortext/component/addon-qtype-ddimageortext.html +++ b/src/addons/qtype/ddimageortext/component/addon-qtype-ddimageortext.html @@ -18,6 +18,6 @@
+ (filterContentRenderingComplete)="ddAreaRendered()" />
diff --git a/src/addons/qtype/ddmarker/component/addon-qtype-ddmarker.html b/src/addons/qtype/ddmarker/component/addon-qtype-ddmarker.html index 563433642..7953f04e4 100644 --- a/src/addons/qtype/ddmarker/component/addon-qtype-ddmarker.html +++ b/src/addons/qtype/ddmarker/component/addon-qtype-ddmarker.html @@ -18,6 +18,6 @@
+ (filterContentRenderingComplete)="ddAreaRendered()" />
diff --git a/src/addons/qtype/ddwtos/classes/ddwtos.ts b/src/addons/qtype/ddwtos/classes/ddwtos.ts index ab7fb7641..aeb79d156 100644 --- a/src/addons/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addons/qtype/ddwtos/classes/ddwtos.ts @@ -13,7 +13,6 @@ // limitations under the License. import { CoreFormatTextDirective } from '@directives/format-text'; -import { CoreText } from '@singletons/text'; import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreCoordinates, CoreDom } from '@singletons/dom'; import { CoreEventObserver } from '@singletons/events'; @@ -489,10 +488,6 @@ export class AddonQtypeDdwtosQuestion { return; } - groupItems.forEach((item) => { - item.innerHTML = CoreText.decodeHTML(item.innerHTML); - }); - // Wait to render in order to calculate size. if (groupItems[0].parentElement) { // Wait for parent to be visible. We cannot wait for group items because they have visibility hidden. diff --git a/src/addons/qtype/ddwtos/component/addon-qtype-ddwtos.html b/src/addons/qtype/ddwtos/component/addon-qtype-ddwtos.html index cd87af483..58a688c80 100644 --- a/src/addons/qtype/ddwtos/component/addon-qtype-ddwtos.html +++ b/src/addons/qtype/ddwtos/component/addon-qtype-ddwtos.html @@ -16,7 +16,7 @@ + (filterContentRenderingComplete)="answersRendered()" /> diff --git a/src/addons/qtype/ddwtos/component/ddwtos.ts b/src/addons/qtype/ddwtos/component/ddwtos.ts index e8be5ddfb..eb9e11b5e 100644 --- a/src/addons/qtype/ddwtos/component/ddwtos.ts +++ b/src/addons/qtype/ddwtos/component/ddwtos.ts @@ -18,6 +18,7 @@ import { AddonModQuizQuestionBasicData, CoreQuestionBaseComponent } from '@featu import { CoreQuestionHelper } from '@features/question/services/question-helper'; import { CoreDomUtils } from '@services/utils/dom'; import { AddonQtypeDdwtosQuestion } from '../classes/ddwtos'; +import { CoreText } from '@singletons/text'; /** * Component to render a drag-and-drop words into sentences question. @@ -69,6 +70,13 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent('span.draghome')); + groupItems.forEach((item) => { + item.innerHTML = CoreText.decodeHTML(item.innerHTML); + }); + // Add the drags container inside the answers so it's rendered inside core-format-text, // otherwise some styles could be different between the drag homes and the draggables. this.question.answers = answerContainer.outerHTML + '
'; diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index b07e5ec39..e5086623c 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -94,7 +94,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec @Input({ transform: toBoolean }) hideIfEmpty = false; // If true, the tag will contain nothing if text is empty. @Input({ transform: toBoolean }) disabled = false; // If disabled, autoplay elements will be disabled. - @Output() afterRender: EventEmitter; // Called when the data is rendered. + @Output() afterRender = new EventEmitter(); // Called when the data is rendered. + @Output() filterContentRenderingComplete = new EventEmitter(); // Called when the filters have finished rendering content. @Output() onClick: EventEmitter = new EventEmitter(); // Called when clicked. protected element: HTMLElement; @@ -117,8 +118,6 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec this.emptyText = this.hideIfEmpty ? '' : ' '; this.element.innerHTML = this.emptyText; - this.afterRender = new EventEmitter(); - this.element.addEventListener('click', (event) => this.elementClicked(event)); this.siteId = this.siteId || CoreSites.getCurrentSiteId(); @@ -340,8 +339,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec /** * Finish the rendering, displaying the element again and calling afterRender. + * + * @param triggerFilterRender Whether to emit the filterContentRenderingComplete output too. */ - protected async finishRender(): Promise { + protected async finishRender(triggerFilterRender = true): Promise { // Show the element again. this.element.classList.remove('core-loading'); @@ -349,6 +350,9 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec // Emit the afterRender output. this.afterRender.emit(); + if (triggerFilterRender) { + this.filterContentRenderingComplete.emit(); + } } /** @@ -402,11 +406,13 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec this.component, this.componentId, result.siteId, - ); + ).finally(() => { + this.filterContentRenderingComplete.emit(); + }); } this.element.classList.remove('core-disable-media-adapt'); - await this.finishRender(); + await this.finishRender(!result.options.filter); } /**