From 323ccc8c760ad87bdbe2e87cf315b5f45aa2285f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 19 Jul 2024 15:37:32 +0200 Subject: [PATCH] MOBILE-4616 chore: Isolate convertHTMLToHTMLElement --- .../services/handlers/displayh5p.ts | 13 +++++---- .../services/handlers/mediaplugin.ts | 9 +++--- .../bigbluebuttonbn/components/index/index.ts | 5 ++-- .../mod/folder/services/handlers/module.ts | 4 +-- .../mod/lesson/services/lesson-helper.ts | 15 +++++----- src/addons/mod/lesson/services/lesson.ts | 4 +-- src/addons/mod/quiz/services/quiz-helper.ts | 3 +- src/addons/mod/quiz/services/quiz.ts | 4 +-- .../services/handlers/calculated.ts | 4 +-- .../qtype/essay/services/handlers/essay.ts | 12 ++++---- .../components/show-password/show-password.ts | 4 +-- .../services/handlers/course-tag-area.ts | 4 +-- .../features/grades/services/grades-helper.ts | 3 +- .../classes/base-question-component.ts | 5 ++-- .../question/services/question-helper.ts | 21 +++++++------- src/core/features/tag/services/tag-helper.ts | 4 +-- .../user/services/handlers/tag-area.ts | 4 +-- src/core/services/filepool.ts | 3 +- src/core/services/utils/dom.ts | 24 +++++++-------- src/core/singletons/dom.ts | 4 +-- src/core/singletons/text.ts | 22 +++----------- src/core/utils/create-html-element.ts | 29 +++++++++++++++++++ 22 files changed, 109 insertions(+), 91 deletions(-) create mode 100644 src/core/utils/create-html-element.ts diff --git a/src/addons/filter/displayh5p/services/handlers/displayh5p.ts b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts index e71a429fa..0ef26c726 100644 --- a/src/addons/filter/displayh5p/services/handlers/displayh5p.ts +++ b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts @@ -20,6 +20,7 @@ import { makeSingleton } from '@singletons'; import { CoreH5PPlayerComponent } from '@features/h5p/components/h5p-player/h5p-player'; import { CoreUrl } from '@singletons/url'; import { CoreH5PHelper } from '@features/h5p/classes/helper'; +import { CoreTemplateElement } from '@/core/utils/create-html-element'; /** * Handler to support the Display H5P filter. @@ -30,17 +31,15 @@ export class AddonFilterDisplayH5PHandlerService extends CoreFilterDefaultHandle name = 'AddonFilterDisplayH5PHandler'; filterName = 'displayh5p'; - protected template = document.createElement('template'); // A template element to convert HTML to element. - /** * @inheritdoc */ filter( text: string, ): string | Promise { - this.template.innerHTML = text; + CoreTemplateElement.innerHTML = text; - const h5pIframes = Array.from(this.template.content.querySelectorAll('iframe.h5p-iframe')); + const h5pIframes = Array.from(CoreTemplateElement.content.querySelectorAll('iframe.h5p-iframe')); // Replace all iframes with an empty div that will be treated in handleHtml. h5pIframes.forEach((iframe) => { @@ -53,7 +52,9 @@ export class AddonFilterDisplayH5PHandlerService extends CoreFilterDefaultHandle }); // Handle H5P iframes embedded using the embed HTML code. - const embeddedH5PIframes = Array.from(this.template.content.querySelectorAll('iframe.h5p-player')); + const embeddedH5PIframes = Array.from( + CoreTemplateElement.content.querySelectorAll('iframe.h5p-player'), + ); embeddedH5PIframes.forEach((iframe) => { // Add the preventredirect param to allow authenticating if auto-login fails. @@ -70,7 +71,7 @@ export class AddonFilterDisplayH5PHandlerService extends CoreFilterDefaultHandle } }); - return this.template.innerHTML; + return CoreTemplateElement.innerHTML; } /** diff --git a/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts index 5c5cea576..6d111dca4 100644 --- a/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts +++ b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreTemplateElement } from '@/core/utils/create-html-element'; import { AddonFilterMediaPluginVideoJS } from '@addons/filter/mediaplugin/services/videojs'; import { Injectable } from '@angular/core'; @@ -28,21 +29,19 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl name = 'AddonFilterMediaPluginHandler'; filterName = 'mediaplugin'; - protected template = document.createElement('template'); // A template element to convert HTML to element. - /** * @inheritdoc */ filter(text: string): string | Promise { - this.template.innerHTML = text; + CoreTemplateElement.innerHTML = text; - const videos = Array.from(this.template.content.querySelectorAll('video')); + const videos = Array.from(CoreTemplateElement.content.querySelectorAll('video')); videos.forEach((video) => { AddonFilterMediaPluginVideoJS.treatYoutubeVideos(video); }); - return this.template.innerHTML; + return CoreTemplateElement.innerHTML; } /** diff --git a/src/addons/mod/bigbluebuttonbn/components/index/index.ts b/src/addons/mod/bigbluebuttonbn/components/index/index.ts index 089ade130..29614ff71 100644 --- a/src/addons/mod/bigbluebuttonbn/components/index/index.ts +++ b/src/addons/mod/bigbluebuttonbn/components/index/index.ts @@ -33,6 +33,7 @@ import { } from '../../services/bigbluebuttonbn'; import { ADDON_MOD_BBB_COMPONENT } from '../../constants'; import { CoreLoadings } from '@services/loadings'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; /** * Component that displays a Big Blue Button activity. @@ -147,7 +148,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo this.recordings = recordingsTable.parsedData.map(recordingData => { const details: RecordingDetail[] = []; - const playbacksEl = CoreDomUtils.convertToElement(String(recordingData.playback)); + const playbacksEl = convertHTMLToHTMLElement(String(recordingData.playback)); const playbacks: RecordingPlayback[] = Array.from(playbacksEl.querySelectorAll('a')).map(playbackAnchor => ({ name: playbackAnchor.textContent ?? '', url: playbackAnchor.href, @@ -164,7 +165,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo value = CoreTimeUtils.userDate(Number(value), 'core.strftimedaydate'); } else if (columnData.allowHTML && typeof value === 'string') { // If the HTML is empty, don't display it. - const valueElement = CoreDomUtils.convertToElement(value); + const valueElement = convertHTMLToHTMLElement(value); if (!valueElement.querySelector('img') && (valueElement.textContent ?? '').trim() === '') { return; } diff --git a/src/addons/mod/folder/services/handlers/module.ts b/src/addons/mod/folder/services/handlers/module.ts index aefa39df7..4470e98f5 100644 --- a/src/addons/mod/folder/services/handlers/module.ts +++ b/src/addons/mod/folder/services/handlers/module.ts @@ -18,7 +18,7 @@ import { CoreModuleHandlerBase } from '@features/course/classes/module-base-hand import { CoreCourseModuleData } from '@features/course/services/course-helper'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; import { CoreNavigator } from '@services/navigator'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { makeSingleton } from '@singletons'; import { ADDON_MOD_FOLDER_PAGE_NAME } from '../../constants'; @@ -58,7 +58,7 @@ export class AddonModFolderModuleHandlerService extends CoreModuleHandlerBase im if (module.description) { // Module description can contain the folder contents if it's inline, remove it. - const descriptionElement = CoreDomUtils.convertToElement(module.description); + const descriptionElement = convertHTMLToHTMLElement(module.description); Array.from(descriptionElement.querySelectorAll('.foldertree, .folderbuttons, .tertiary-navigation')) .forEach(element => element.remove()); diff --git a/src/addons/mod/lesson/services/lesson-helper.ts b/src/addons/mod/lesson/services/lesson-helper.ts index d54343733..25e014a1a 100644 --- a/src/addons/mod/lesson/services/lesson-helper.ts +++ b/src/addons/mod/lesson/services/lesson-helper.ts @@ -28,6 +28,7 @@ import { import { CoreTime } from '@singletons/time'; import { CoreUtils } from '@services/utils/utils'; import { AddonModLessonPageSubtype } from '../constants'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; /** * Helper service that provides some features for quiz. @@ -46,7 +47,7 @@ export class AddonModLessonHelperProvider { * @returns Formatted data. */ formatActivityLink(activityLink: string): AddonModLessonActivityLink { - const element = CoreDomUtils.convertToElement(activityLink); + const element = convertHTMLToHTMLElement(activityLink); const anchor = element.querySelector('a'); if (!anchor) { @@ -76,7 +77,7 @@ export class AddonModLessonHelperProvider { buttonText: '', content: '', }; - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); // Search the input button. const button = element.querySelector('input[type="button"]'); @@ -100,7 +101,7 @@ export class AddonModLessonHelperProvider { */ getPageButtonsFromHtml(html: string): AddonModLessonPageButton[] { const buttons: AddonModLessonPageButton[] = []; - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); // Get the container of the buttons if it exists. let buttonsContainer = element.querySelector('.branchbuttoncontainer'); @@ -152,7 +153,7 @@ export class AddonModLessonHelperProvider { */ getPageContentsFromPageData(data: AddonModLessonGetPageDataWSResponse): string { // Search the page contents inside the whole page HTML. Use data.pagecontent because it's filtered. - const element = CoreDomUtils.convertToElement(data.pagecontent || ''); + const element = convertHTMLToHTMLElement(data.pagecontent || ''); const contents = element.querySelector('.contents'); if (contents) { @@ -178,7 +179,7 @@ export class AddonModLessonHelperProvider { * @returns Question data. */ getQuestionFromPageData(questionForm: FormGroup, pageData: AddonModLessonGetPageDataWSResponse): AddonModLessonQuestion { - const element = CoreDomUtils.convertToElement(pageData.pagecontent || ''); + const element = convertHTMLToHTMLElement(pageData.pagecontent || ''); // Get the container of the question answers if it exists. const fieldContainer = element.querySelector('.fcontainer'); @@ -463,7 +464,7 @@ export class AddonModLessonHelperProvider { * @returns Object with the data to render the answer. If the answer doesn't require any parsing, return a string with the HTML. */ getQuestionPageAnswerDataFromHtml(html: string): AddonModLessonAnswerData { - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); // Check if it has a checkbox. let input = element.querySelector('input[type="checkbox"][name*="answer"]'); @@ -588,7 +589,7 @@ export class AddonModLessonHelperProvider { * @returns Feedback without the question text. */ removeQuestionFromFeedback(html: string): string { - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); // Remove the question text. CoreDomUtils.removeElement(element, '.generalbox:not(.feedback):not(.correctanswer)'); diff --git a/src/addons/mod/lesson/services/lesson.ts b/src/addons/mod/lesson/services/lesson.ts index a6f2df32c..dd07e4ae8 100644 --- a/src/addons/mod/lesson/services/lesson.ts +++ b/src/addons/mod/lesson/services/lesson.ts @@ -18,7 +18,7 @@ import { CoreSite } from '@classes/sites/site'; import { CoreCourseCommonModWSOptions } from '@features/course/services/course'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { CoreText } from '@singletons/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; @@ -164,7 +164,7 @@ export class AddonModLessonProvider { if (page.answerdata && !this.answerPageIsQuestion(page)) { // It isn't a question page, but it can be an end of branch, etc. Check if the first answer has a button. if (page.answerdata.answers && page.answerdata.answers[0]) { - const element = CoreDomUtils.convertToElement(page.answerdata.answers[0][0]); + const element = convertHTMLToHTMLElement(page.answerdata.answers[0][0]); return !!element.querySelector('input[type="button"]'); } diff --git a/src/addons/mod/quiz/services/quiz-helper.ts b/src/addons/mod/quiz/services/quiz-helper.ts index 4026938fb..1113eb95a 100644 --- a/src/addons/mod/quiz/services/quiz-helper.ts +++ b/src/addons/mod/quiz/services/quiz-helper.ts @@ -42,6 +42,7 @@ import { CoreGroups } from '@services/groups'; import { CoreTimeUtils } from '@services/utils/time'; import { CoreModals } from '@services/modals'; import { CoreLoadings } from '@services/loadings'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; /** * Helper service that provides some features for quiz. @@ -301,7 +302,7 @@ export class AddonModQuizHelperProvider { * @returns Question's mark. */ getQuestionMarkFromHtml(html: string): string | undefined { - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); return CoreDomUtils.getContentsOfElement(element, '.grade'); } diff --git a/src/addons/mod/quiz/services/quiz.ts b/src/addons/mod/quiz/services/quiz.ts index 07604d170..80cdc81ee 100644 --- a/src/addons/mod/quiz/services/quiz.ts +++ b/src/addons/mod/quiz/services/quiz.ts @@ -29,7 +29,7 @@ import { } from '@features/question/services/question'; import { CoreQuestionDelegate } from '@features/question/services/question-delegate'; import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { CoreTimeUtils } from '@services/utils/time'; import { CoreUtils } from '@services/utils/utils'; import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; @@ -1588,7 +1588,7 @@ export class AddonModQuizProvider { * @returns Whether it's blocked. */ isQuestionBlocked(question: CoreQuestionQuestionParsed): boolean { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); return !!element.querySelector('.mod_quiz-blocked_question_warning'); } diff --git a/src/addons/qtype/calculated/services/handlers/calculated.ts b/src/addons/qtype/calculated/services/handlers/calculated.ts index 58f6ac24a..5c42adc04 100644 --- a/src/addons/qtype/calculated/services/handlers/calculated.ts +++ b/src/addons/qtype/calculated/services/handlers/calculated.ts @@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question'; import { CoreQuestionHandler } from '@features/question/services/question-delegate'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; import { AddonQtypeCalculatedComponent } from '../../component/calculated'; @@ -53,7 +53,7 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler { */ hasSeparateUnitField(question: CoreQuestionQuestionParsed): boolean { if (!question.parsedSettings) { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); return !!(element.querySelector('select[name*=unit]') || element.querySelector('input[type="radio"]')); } diff --git a/src/addons/qtype/essay/services/handlers/essay.ts b/src/addons/qtype/essay/services/handlers/essay.ts index 33d05bd09..517317035 100644 --- a/src/addons/qtype/essay/services/handlers/essay.ts +++ b/src/addons/qtype/essay/services/handlers/essay.ts @@ -22,7 +22,7 @@ import { CoreQuestionHandler } from '@features/question/services/question-delega import { CoreQuestionHelper } from '@features/question/services/question-helper'; import { CoreFileSession } from '@services/file-session'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { CoreText } from '@singletons/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreWSFile } from '@services/ws'; @@ -90,7 +90,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { }; } - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); return { text: !!element.querySelector('textarea[name*=_answer]'), @@ -116,7 +116,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { * @inheritdoc */ getPreventSubmitMessage(question: CoreQuestionQuestionParsed): string | undefined { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); const uploadFilesSupported = question.responsefileareas !== undefined; if (!uploadFilesSupported && element.querySelector('div[id*=filemanager]')) { @@ -293,7 +293,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { siteId?: string, ): Promise { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); const attachmentsInput = element.querySelector('.attachments input[name*=_attachments]'); // Search the textarea to get its name. @@ -375,7 +375,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { siteId?: string, ): Promise { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); const attachmentsInput = element.querySelector('.attachments input[name*=_attachments]'); if (attachmentsInput) { @@ -454,7 +454,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler { isPlainText = question.parsedSettings.responseformat == 'monospaced' || question.parsedSettings.responseformat == 'plain'; } else { - const questionEl = CoreDomUtils.convertToElement(question.html); + const questionEl = convertHTMLToHTMLElement(question.html); isPlainText = !!questionEl.querySelector('.qtype_essay_monospaced') || !!questionEl.querySelector('.qtype_essay_plain'); } diff --git a/src/core/components/show-password/show-password.ts b/src/core/components/show-password/show-password.ts index 88bdcc81f..ad01b6273 100644 --- a/src/core/components/show-password/show-password.ts +++ b/src/core/components/show-password/show-password.ts @@ -14,7 +14,7 @@ import { Component, AfterViewInit, Input, ContentChild, ViewEncapsulation } from '@angular/core'; import { IonInput } from '@ionic/angular'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { CoreUtils } from '@services/utils/utils'; import { CoreLogger } from '@singletons/logger'; @@ -84,7 +84,7 @@ export class CoreShowPasswordComponent implements AfterViewInit { return; } - const toggle = CoreDomUtils.convertToElement(''); + const toggle = convertHTMLToHTMLElement(''); input.parentElement?.appendChild(toggle.children[0]); } diff --git a/src/core/features/course/services/handlers/course-tag-area.ts b/src/core/features/course/services/handlers/course-tag-area.ts index 9c52f9b9a..93cb1dc5c 100644 --- a/src/core/features/course/services/handlers/course-tag-area.ts +++ b/src/core/features/course/services/handlers/course-tag-area.ts @@ -14,7 +14,7 @@ import { Injectable, Type } from '@angular/core'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; import { CoreCourseTagAreaComponent } from '../../components/tag-area/tag-area'; import { makeSingleton } from '@singletons'; @@ -45,7 +45,7 @@ export class CoreCourseTagAreaHandlerService implements CoreTagAreaHandler { */ parseContent(content: string): CoreCouseTagItems[] { const items: CoreCouseTagItems[] = []; - const element = CoreDomUtils.convertToElement(content); + const element = convertHTMLToHTMLElement(content); Array.from(element.querySelectorAll('div.coursebox')).forEach((coursebox) => { const courseId = parseInt(coursebox.getAttribute('data-courseid') || '', 10); diff --git a/src/core/features/grades/services/grades-helper.ts b/src/core/features/grades/services/grades-helper.ts index 3b76d57f1..be49fb3fd 100644 --- a/src/core/features/grades/services/grades-helper.ts +++ b/src/core/features/grades/services/grades-helper.ts @@ -44,6 +44,7 @@ import { CoreCourseHelper } from '@features/course/services/course-helper'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { CoreCourseAccess } from '@features/course/services/course-options-delegate'; import { CoreLoadings } from '@services/loadings'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; export const GRADES_PAGE_NAME = 'grades'; export const GRADES_PARTICIPANTS_PAGE_NAME = 'participant-grades'; @@ -573,7 +574,7 @@ export class CoreGradesHelperProvider { const modname = module?.[1]; if (modname !== undefined) { - const modicon = CoreDomUtils.convertToElement(text).querySelector('img')?.getAttribute('src') ?? undefined; + const modicon = convertHTMLToHTMLElement(text).querySelector('img')?.getAttribute('src') ?? undefined; row.itemtype = 'mod'; row.itemmodule = modname; diff --git a/src/core/features/question/classes/base-question-component.ts b/src/core/features/question/classes/base-question-component.ts index f54cd05dd..34085904c 100644 --- a/src/core/features/question/classes/base-question-component.ts +++ b/src/core/features/question/classes/base-question-component.ts @@ -25,6 +25,7 @@ import { CoreLogger } from '@singletons/logger'; import { CoreQuestionBehaviourButton, CoreQuestionHelper, CoreQuestionQuestion } from '../services/question-helper'; import { ContextLevel } from '@/core/constants'; import { toBoolean } from '@/core/transforms/boolean'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; /** * Base class for components to render a question. @@ -87,7 +88,7 @@ export class CoreQuestionBaseComponent(contentSelector); diff --git a/src/core/features/question/services/question-helper.ts b/src/core/features/question/services/question-helper.ts index ca4e4cbb7..be8afac1e 100644 --- a/src/core/features/question/services/question-helper.ts +++ b/src/core/features/question/services/question-helper.ts @@ -31,6 +31,7 @@ import { CoreUrl } from '@singletons/url'; import { ContextLevel } from '@/core/constants'; import { CoreIonicColorNames } from '@singletons/colors'; import { CoreViewer } from '@features/viewer/services/viewer'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; /** * Service with some common functions to handle questions. @@ -131,7 +132,7 @@ export class CoreQuestionHelperProvider { selector = selector || '.im-controls [type="submit"]'; - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); // Search the buttons. const buttons = Array.from(element.querySelectorAll(selector)); @@ -150,7 +151,7 @@ export class CoreQuestionHelperProvider { * @returns Wether the certainty is found. */ extractQbehaviourCBM(question: CoreQuestionQuestion): boolean { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); const labels = Array.from(element.querySelectorAll('.im-controls .certaintychoices label[for*="certainty"]')); question.behaviourCertaintyOptions = []; @@ -217,7 +218,7 @@ export class CoreQuestionHelperProvider { * @returns Whether the seen input is found. */ extractQbehaviourSeenInput(question: CoreQuestionQuestion): boolean { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); // Search the "seen" input. const seenInput = element.querySelector('input[type="hidden"][name*=seen]'); @@ -273,7 +274,7 @@ export class CoreQuestionHelperProvider { * @param attrName Name of the attribute to store the HTML in. */ protected extractQuestionLastElementNotInContent(question: CoreQuestionQuestion, selector: string, attrName: string): void { - const element = CoreDomUtils.convertToElement(question.html); + const element = convertHTMLToHTMLElement(question.html); const matches = Array.from(element.querySelectorAll(selector)); // Get the last element and check it's not in the question contents. @@ -358,7 +359,7 @@ export class CoreQuestionHelperProvider { * @returns Object where the keys are the names. */ getAllInputNamesFromHtml(html: string): Record { - const element = CoreDomUtils.convertToElement('
' + html + '
'); + const element = convertHTMLToHTMLElement('
' + html + '
'); const form = element.children[0]; const answers: Record = {}; @@ -424,7 +425,7 @@ export class CoreQuestionHelperProvider { * @returns Attachments. */ getQuestionAttachmentsFromHtml(html: string): CoreWSFile[] { - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); // Remove the filemanager (area to attach files to a question). CoreDomUtils.removeElement(element, 'div[id*=filemanager]'); @@ -461,7 +462,7 @@ export class CoreQuestionHelperProvider { } // Search the input holding the sequencecheck. - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); const input = element.querySelector('input[name*=sequencecheck]'); if (!input || input.name === undefined || input.value === undefined) { @@ -531,7 +532,7 @@ export class CoreQuestionHelperProvider { * @returns Validation error message if present. */ getValidationErrorFromHtml(html: string): string | undefined { - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); return CoreDomUtils.getContentsOfElement(element, '.validationerror'); } @@ -583,7 +584,7 @@ export class CoreQuestionHelperProvider { * @param question Question. */ loadLocalAnswersInHtml(question: CoreQuestionQuestion): void { - const element = CoreDomUtils.convertToElement('
' + question.html + '
'); + const element = convertHTMLToHTMLElement('
' + question.html + '
'); const form = element.children[0]; // Search all input elements. @@ -758,7 +759,7 @@ export class CoreQuestionHelperProvider { * @returns Whether the button is found. */ protected searchBehaviourButton(question: CoreQuestionQuestion, htmlProperty: string, selector: string): boolean { - const element = CoreDomUtils.convertToElement(question[htmlProperty]); + const element = convertHTMLToHTMLElement(question[htmlProperty]); const button = element.querySelector(selector); if (!button) { diff --git a/src/core/features/tag/services/tag-helper.ts b/src/core/features/tag/services/tag-helper.ts index becd52a95..da1d45ddf 100644 --- a/src/core/features/tag/services/tag-helper.ts +++ b/src/core/features/tag/services/tag-helper.ts @@ -14,7 +14,7 @@ import { makeSingleton } from '@singletons'; import { Injectable } from '@angular/core'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; /** * Service with helper functions for tags. @@ -30,7 +30,7 @@ export class CoreTagHelperProvider { */ parseFeedContent(content: string): CoreTagFeedElement[] { const items: CoreTagFeedElement[] = []; - const element = CoreDomUtils.convertToElement(content); + const element = convertHTMLToHTMLElement(content); Array.from(element.querySelectorAll('ul.tag_feed > li')).forEach((itemElement) => { const item: CoreTagFeedElement = { details: [] }; diff --git a/src/core/features/user/services/handlers/tag-area.ts b/src/core/features/user/services/handlers/tag-area.ts index 37111a061..9fae87eaf 100644 --- a/src/core/features/user/services/handlers/tag-area.ts +++ b/src/core/features/user/services/handlers/tag-area.ts @@ -14,7 +14,7 @@ import { Injectable, Type } from '@angular/core'; -import { CoreDomUtils } from '@services/utils/dom'; +import { convertHTMLToHTMLElement } from '@/core/utils/create-html-element'; import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; import { CoreUserTagAreaComponent } from '@features/user/components/tag-area/tag-area'; import { CoreTagFeedElement } from '@features/tag/services/tag-helper'; @@ -47,7 +47,7 @@ export class CoreUserTagAreaHandlerService implements CoreTagAreaHandler { */ parseContent(content: string): CoreUserTagFeedElement[] { const items: CoreUserTagFeedElement[] = []; - const element = CoreDomUtils.convertToElement(content); + const element = convertHTMLToHTMLElement(content); Array.from(element.querySelectorAll('div.user-box')).forEach((userbox: HTMLElement) => { const avatarLink = userbox.querySelector('a:first-child'); diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index 863c1523a..77201627a 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -58,6 +58,7 @@ import { asyncInstance, AsyncInstance } from '../utils/async-instance'; import { CorePath } from '@singletons/path'; import { CorePromisedValue } from '@classes/promised-value'; import { CoreAnalytics, CoreAnalyticsEventType } from './analytics'; +import { convertHTMLToHTMLElement } from '../utils/create-html-element'; /* * Factory for handling downloading files and retrieve downloaded files. @@ -1147,7 +1148,7 @@ export class CoreFilepoolProvider { extractDownloadableFilesFromHtml(html: string): string[] { let urls: string[] = []; - const element = CoreDomUtils.convertToElement(html); + const element = convertHTMLToHTMLElement(html); const elements: AnchorOrMediaElement[] = Array.from(element.querySelectorAll('a, img, audio, video, source, track')); for (let i = 0; i < elements.length; i++) { diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index b677d06b5..2f677a978 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -55,6 +55,7 @@ import { CorePopovers, OpenPopoverOptions } from '@services/popovers'; import { CoreViewer } from '@features/viewer/services/viewer'; import { CoreLoadings } from '@services/loadings'; import { CoreErrorHelper, CoreErrorObject } from '@services/error-helper'; +import { convertHTMLToHTMLElement, CoreTemplateElement } from '@/core/utils/create-html-element'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -68,8 +69,6 @@ export class CoreDomUtilsProvider { protected readonly INPUT_SUPPORT_KEYBOARD: string[] = ['date', 'datetime', 'datetime-local', 'email', 'month', 'number', 'password', 'search', 'tel', 'text', 'time', 'url', 'week']; - protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element. - protected matchesFunctionName?: string; // Name of the "matches" function to use when simulating a closest call. protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous. protected displayedAlerts: Record = {}; // To prevent duplicated alerts. @@ -196,12 +195,11 @@ export class CoreDomUtilsProvider { * * @param html Text to convert. * @returns Element. + * + * @deprecated since 4.5. Use convertToElement directly instead. */ convertToElement(html: string): HTMLElement { - // Add a div to hold the content, that's the element that will be returned. - this.template.innerHTML = '
' + html + '
'; - - return this.template.content.children[0]; + return convertHTMLToHTMLElement(html); } /** @@ -263,7 +261,7 @@ export class CoreDomUtilsProvider { * @returns Fixed HTML text. */ fixHtml(html: string): string { - this.template.innerHTML = html; + CoreTemplateElement.innerHTML = html; // eslint-disable-next-line no-control-regex const attrNameRegExp = /[^\x00-\x20\x7F-\x9F"'>/=]+/; @@ -278,9 +276,9 @@ export class CoreDomUtilsProvider { Array.from(element.children).forEach(fixElement); }; - Array.from(this.template.content.children).forEach(fixElement); + Array.from(CoreTemplateElement.content.children).forEach(fixElement); - return this.template.innerHTML; + return CoreTemplateElement.innerHTML; } /** @@ -389,7 +387,7 @@ export class CoreDomUtilsProvider { * @returns Attribute value. */ getHTMLElementAttribute(html: string, attribute: string): string | null { - return this.convertToElement(html).children[0].getAttribute(attribute); + return convertHTMLToHTMLElement(html).children[0].getAttribute(attribute); } /** @@ -650,7 +648,7 @@ export class CoreDomUtilsProvider { * @returns HTML without the element. */ removeElementFromHtml(html: string, selector: string, removeAll?: boolean): string { - const element = this.convertToElement(html); + const element = convertHTMLToHTMLElement(html); if (removeAll) { const selected = element.querySelectorAll(selector); @@ -698,7 +696,7 @@ export class CoreDomUtilsProvider { paths: {[url: string]: string}, anchorFn?: (anchor: HTMLElement, href: string) => void, ): string { - const element = this.convertToElement(html); + const element = convertHTMLToHTMLElement(html); // Treat elements with src (img, audio, video, ...). const media = Array.from(element.querySelectorAll('img, video, audio, source, track, iframe, embed')); @@ -1401,7 +1399,7 @@ export class CoreDomUtilsProvider { * @returns Same text converted to HTMLCollection. */ toDom(text: string): HTMLCollection { - const element = this.convertToElement(text); + const element = convertHTMLToHTMLElement(text); return element.children; } diff --git a/src/core/singletons/dom.ts b/src/core/singletons/dom.ts index a8050115e..e35035472 100644 --- a/src/core/singletons/dom.ts +++ b/src/core/singletons/dom.ts @@ -17,9 +17,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver } from '@singletons/events'; import { CorePlatform } from '@services/platform'; import { CoreWait } from './wait'; - -// A template element to convert HTML to element. -export const CoreTemplateElement: HTMLTemplateElement = document.createElement('template'); +import { CoreTemplateElement } from '../utils/create-html-element'; /** * Singleton with helper functions for dom. diff --git a/src/core/singletons/text.ts b/src/core/singletons/text.ts index 58c066655..9520127a5 100644 --- a/src/core/singletons/text.ts +++ b/src/core/singletons/text.ts @@ -16,7 +16,7 @@ import { Clipboard, Translate } from '@singletons'; import { CoreToasts } from '@services/toasts'; import { Locutus } from './locutus'; import { CoreError } from '@classes/errors/error'; -import { CoreTemplateElement } from './dom'; +import { convertHTMLToHTMLElement } from '../utils/create-html-element'; /** * Singleton with helper functions for text manipulation. @@ -190,7 +190,7 @@ export class CoreText { // First, we use a regexpr. text = text.replace(/(<([^>]+)>)/ig, ''); // Then, we rely on the browser. We need to wrap the text to be sure is HTML. - text = CoreText.convertToElement(text).textContent || ''; + text = convertHTMLToHTMLElement(text).textContent || ''; // Trim text text = options.trim ? text.trim() : text; // Recover or remove new lines. @@ -199,20 +199,6 @@ export class CoreText { return text; } - /** - * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body. - * This function is the same as in DomUtils, but we cannot use that one because of circular dependencies. - * - * @param html Text to convert. - * @returns Element. - */ - protected static convertToElement(html: string): HTMLElement { - // Add a div to hold the content, that's the element that will be returned. - CoreTemplateElement.innerHTML = '
' + html + '
'; - - return CoreTemplateElement.content.children[0]; - } - /** * Decode an escaped HTML text. This implementation is based on PHP's htmlspecialchars_decode. * @@ -243,7 +229,7 @@ export class CoreText { */ static decodeHTMLEntities(text: string): string { if (text) { - text = CoreText.convertToElement(text).textContent || ''; + text = convertHTMLToHTMLElement(text).textContent || ''; } return text; @@ -438,7 +424,7 @@ export class CoreText { * @returns Processed HTML string. */ static processHTML(text: string, process: (element: HTMLElement) => unknown): string { - const element = CoreText.convertToElement(text); + const element = convertHTMLToHTMLElement(text); process(element); diff --git a/src/core/utils/create-html-element.ts b/src/core/utils/create-html-element.ts new file mode 100644 index 000000000..2ce0b29cf --- /dev/null +++ b/src/core/utils/create-html-element.ts @@ -0,0 +1,29 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A template element to convert HTML to element. +export const CoreTemplateElement: HTMLTemplateElement = document.createElement('template'); + +/** + * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body. + * + * @param html Text to convert. + * @returns Element. + */ +export function convertHTMLToHTMLElement(html: string): HTMLElement { + // Add a div to hold the content, that's the element that will be returned. + CoreTemplateElement.innerHTML = '
' + html + '
'; + + return CoreTemplateElement.content.children[0]; +}