Skip to content

Commit 437f309

Browse files
authored
Merge pull request #4176 from dpalou/MOBILE-4587
MOBILE-4587 qtype: Fix race condition with MathJax in D&D questions
2 parents df209b4 + 9081494 commit 437f309

File tree

7 files changed

+44
-27
lines changed

7 files changed

+44
-27
lines changed

src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan
177177
): Promise<void> {
178178
await this.waitForReady();
179179

180-
this.window.M!.filter_mathjaxloader!.typeset(container);
180+
await this.window.M!.filter_mathjaxloader!.typeset(container);
181181
}
182182

183183
/**
@@ -234,24 +234,32 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan
234234
}
235235
},
236236
// Called by the filter when an equation is found while rendering the page.
237-
typeset: function (container: HTMLElement): void {
237+
typeset: async function (container: HTMLElement): Promise<void> {
238238
if (!this._configured) {
239239
this._setLocale();
240240
}
241241

242-
if (that.window.MathJax !== undefined) {
243-
const processDelay = that.window.MathJax.Hub.processSectionDelay;
244-
// Set the process section delay to 0 when updating the formula.
245-
that.window.MathJax.Hub.processSectionDelay = 0;
242+
if (that.window.MathJax === undefined) {
243+
return;
244+
}
246245

247-
const equations = Array.from(container.querySelectorAll('.filter_mathjaxloader_equation'));
248-
equations.forEach((node) => {
249-
that.window.MathJax.Hub.Queue(['Typeset', that.window.MathJax.Hub, node], [that.fixUseUrls, node]);
250-
});
246+
const processDelay = that.window.MathJax.Hub.processSectionDelay;
247+
// Set the process section delay to 0 when updating the formula.
248+
that.window.MathJax.Hub.processSectionDelay = 0;
251249

252-
// Set the delay back to normal after processing.
253-
that.window.MathJax.Hub.processSectionDelay = processDelay;
254-
}
250+
const equations = Array.from(container.querySelectorAll('.filter_mathjaxloader_equation'));
251+
const promises = equations.map((node) => new Promise<void>((resolve) => {
252+
that.window.MathJax.Hub.Queue(
253+
['Typeset', that.window.MathJax.Hub, node],
254+
[that.fixUseUrls, node],
255+
[resolve],
256+
);
257+
}));
258+
259+
// Set the delay back to normal after processing.
260+
that.window.MathJax.Hub.processSectionDelay = processDelay;
261+
262+
await Promise.all(promises);
255263
},
256264
};
257265
}

src/addons/qtype/ddimageortext/component/addon-qtype-ddimageortext.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
<div class="fake-ion-item ion-text-wrap" [class.readonly]="question.readOnly" [hidden]="!question.loaded">
1919
<core-format-text *ngIf="question.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId"
2020
[text]="question.ddArea" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"
21-
(afterRender)="ddAreaRendered()" />
21+
(filterContentRenderingComplete)="ddAreaRendered()" />
2222
</div>
2323
</div>

src/addons/qtype/ddmarker/component/addon-qtype-ddmarker.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
<div class="fake-ion-item ion-text-wrap" [hidden]="!question.loaded">
1919
<core-format-text *ngIf="question.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId"
2020
[text]="question.ddArea" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"
21-
(afterRender)="ddAreaRendered()" />
21+
(filterContentRenderingComplete)="ddAreaRendered()" />
2222
</div>
2323
</div>

src/addons/qtype/ddwtos/classes/ddwtos.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
// limitations under the License.
1414

1515
import { CoreFormatTextDirective } from '@directives/format-text';
16-
import { CoreText } from '@singletons/text';
1716
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
1817
import { CoreCoordinates, CoreDom } from '@singletons/dom';
1918
import { CoreEventObserver } from '@singletons/events';
@@ -489,10 +488,6 @@ export class AddonQtypeDdwtosQuestion {
489488
return;
490489
}
491490

492-
groupItems.forEach((item) => {
493-
item.innerHTML = CoreText.decodeHTML(item.innerHTML);
494-
});
495-
496491
// Wait to render in order to calculate size.
497492
if (groupItems[0].parentElement) {
498493
// Wait for parent to be visible. We cannot wait for group items because they have visibility hidden.

src/addons/qtype/ddwtos/component/addon-qtype-ddwtos.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
<core-format-text *ngIf="question.answers" [component]="component" [componentId]="componentId" [text]="question.answers"
1818
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"
19-
(afterRender)="answersRendered()" />
19+
(filterContentRenderingComplete)="answersRendered()" />
2020
</div>
2121
</div>
2222
</div>

src/addons/qtype/ddwtos/component/ddwtos.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { AddonModQuizQuestionBasicData, CoreQuestionBaseComponent } from '@featu
1818
import { CoreQuestionHelper } from '@features/question/services/question-helper';
1919
import { CoreDomUtils } from '@services/utils/dom';
2020
import { AddonQtypeDdwtosQuestion } from '../classes/ddwtos';
21+
import { CoreText } from '@singletons/text';
2122

2223
/**
2324
* Component to render a drag-and-drop words into sentences question.
@@ -69,6 +70,13 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent<AddonMo
6970
}
7071

7172
this.question.readOnly = answerContainer.classList.contains('readonly');
73+
74+
// Decode content of drag homes. This must be done before filters are applied, otherwise some things don't work as expected.
75+
const groupItems = Array.from(answerContainer.querySelectorAll<HTMLElement>('span.draghome'));
76+
groupItems.forEach((item) => {
77+
item.innerHTML = CoreText.decodeHTML(item.innerHTML);
78+
});
79+
7280
// Add the drags container inside the answers so it's rendered inside core-format-text,
7381
// otherwise some styles could be different between the drag homes and the draggables.
7482
this.question.answers = answerContainer.outerHTML + '<div class="drags"></div>';

src/core/directives/format-text.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
9494
@Input({ transform: toBoolean }) hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
9595
@Input({ transform: toBoolean }) disabled = false; // If disabled, autoplay elements will be disabled.
9696

97-
@Output() afterRender: EventEmitter<void>; // Called when the data is rendered.
97+
@Output() afterRender = new EventEmitter<void>(); // Called when the data is rendered.
98+
@Output() filterContentRenderingComplete = new EventEmitter<void>(); // Called when the filters have finished rendering content.
9899
@Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked.
99100

100101
protected element: HTMLElement;
@@ -117,8 +118,6 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
117118
this.emptyText = this.hideIfEmpty ? '' : '&nbsp;';
118119
this.element.innerHTML = this.emptyText;
119120

120-
this.afterRender = new EventEmitter<void>();
121-
122121
this.element.addEventListener('click', (event) => this.elementClicked(event));
123122

124123
this.siteId = this.siteId || CoreSites.getCurrentSiteId();
@@ -340,15 +339,20 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
340339

341340
/**
342341
* Finish the rendering, displaying the element again and calling afterRender.
342+
*
343+
* @param triggerFilterRender Whether to emit the filterContentRenderingComplete output too.
343344
*/
344-
protected async finishRender(): Promise<void> {
345+
protected async finishRender(triggerFilterRender = true): Promise<void> {
345346
// Show the element again.
346347
this.element.classList.remove('core-loading');
347348

348349
await CoreWait.nextTick();
349350

350351
// Emit the afterRender output.
351352
this.afterRender.emit();
353+
if (triggerFilterRender) {
354+
this.filterContentRenderingComplete.emit();
355+
}
352356
}
353357

354358
/**
@@ -402,11 +406,13 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
402406
this.component,
403407
this.componentId,
404408
result.siteId,
405-
);
409+
).finally(() => {
410+
this.filterContentRenderingComplete.emit();
411+
});
406412
}
407413

408414
this.element.classList.remove('core-disable-media-adapt');
409-
await this.finishRender();
415+
await this.finishRender(!result.options.filter);
410416
}
411417

412418
/**

0 commit comments

Comments
 (0)