commit
						6485ded480
					
				| @ -27,8 +27,6 @@ import * as moment from 'moment'; | |||||||
| @Injectable() | @Injectable() | ||||||
| export class AddonModLessonHelperProvider { | export class AddonModLessonHelperProvider { | ||||||
| 
 | 
 | ||||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 |  | ||||||
| 
 |  | ||||||
|     constructor(private domUtils: CoreDomUtilsProvider, private fb: FormBuilder, private translate: TranslateService, |     constructor(private domUtils: CoreDomUtilsProvider, private fb: FormBuilder, private translate: TranslateService, | ||||||
|             private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { } |             private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { } | ||||||
| 
 | 
 | ||||||
| @ -39,8 +37,9 @@ export class AddonModLessonHelperProvider { | |||||||
|      * @return {{formatted: boolean, label: string, href: string}} Formatted data. |      * @return {{formatted: boolean, label: string, href: string}} Formatted data. | ||||||
|      */ |      */ | ||||||
|     formatActivityLink(activityLink: string): {formatted: boolean, label: string, href: string} { |     formatActivityLink(activityLink: string): {formatted: boolean, label: string, href: string} { | ||||||
|         this.div.innerHTML = activityLink; |         const element = this.domUtils.convertToElement(activityLink), | ||||||
|         const anchor = this.div.querySelector('a'); |             anchor = element.querySelector('a'); | ||||||
|  | 
 | ||||||
|         if (!anchor) { |         if (!anchor) { | ||||||
|             // Anchor not found, return the original HTML.
 |             // Anchor not found, return the original HTML.
 | ||||||
|             return { |             return { | ||||||
| @ -67,11 +66,11 @@ export class AddonModLessonHelperProvider { | |||||||
|         const data = { |         const data = { | ||||||
|                 buttonText: '', |                 buttonText: '', | ||||||
|                 content: '' |                 content: '' | ||||||
|             }; |             }, | ||||||
|  |             element = this.domUtils.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         // Search the input button.
 |         // Search the input button.
 | ||||||
|         this.div.innerHTML = html; |         const button = <HTMLInputElement> element.querySelector('input[type="button"]'); | ||||||
|         const button = <HTMLInputElement> this.div.querySelector('input[type="button"]'); |  | ||||||
| 
 | 
 | ||||||
|         if (button) { |         if (button) { | ||||||
|             // Extract the button content and remove it from the HTML.
 |             // Extract the button content and remove it from the HTML.
 | ||||||
| @ -79,7 +78,7 @@ export class AddonModLessonHelperProvider { | |||||||
|             button.remove(); |             button.remove(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         data.content = this.div.innerHTML.trim(); |         data.content = element.innerHTML.trim(); | ||||||
| 
 | 
 | ||||||
|         return data; |         return data; | ||||||
|     } |     } | ||||||
| @ -91,19 +90,19 @@ export class AddonModLessonHelperProvider { | |||||||
|      * @return {any[]} List of buttons. |      * @return {any[]} List of buttons. | ||||||
|      */ |      */ | ||||||
|     getPageButtonsFromHtml(html: string): any[] { |     getPageButtonsFromHtml(html: string): any[] { | ||||||
|         const buttons = []; |         const buttons = [], | ||||||
|  |             element = this.domUtils.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         // Get the container of the buttons if it exists.
 |         // Get the container of the buttons if it exists.
 | ||||||
|         this.div.innerHTML = html; |         let buttonsContainer = element.querySelector('.branchbuttoncontainer'); | ||||||
|         let buttonsContainer = this.div.querySelector('.branchbuttoncontainer'); |  | ||||||
| 
 | 
 | ||||||
|         if (!buttonsContainer) { |         if (!buttonsContainer) { | ||||||
|             // Button container not found, might be a legacy lesson (from 1.9).
 |             // Button container not found, might be a legacy lesson (from 1.9).
 | ||||||
|             if (!this.div.querySelector('form input[type="submit"]')) { |             if (!element.querySelector('form input[type="submit"]')) { | ||||||
|                 // No buttons found.
 |                 // No buttons found.
 | ||||||
|                 return buttons; |                 return buttons; | ||||||
|             } |             } | ||||||
|             buttonsContainer = this.div; |             buttonsContainer = element; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const forms = Array.from(buttonsContainer.querySelectorAll('form')); |         const forms = Array.from(buttonsContainer.querySelectorAll('form')); | ||||||
| @ -144,8 +143,8 @@ export class AddonModLessonHelperProvider { | |||||||
|      */ |      */ | ||||||
|     getPageContentsFromPageData(data: any): string { |     getPageContentsFromPageData(data: any): string { | ||||||
|         // Search the page contents inside the whole page HTML. Use data.pagecontent because it's filtered.
 |         // Search the page contents inside the whole page HTML. Use data.pagecontent because it's filtered.
 | ||||||
|         this.div.innerHTML = data.pagecontent; |         const element = this.domUtils.convertToElement(data.pagecontent), | ||||||
|         const contents = this.div.querySelector('.contents'); |             contents = element.querySelector('.contents'); | ||||||
| 
 | 
 | ||||||
|         if (contents) { |         if (contents) { | ||||||
|             return contents.innerHTML.trim(); |             return contents.innerHTML.trim(); | ||||||
| @ -163,20 +162,20 @@ export class AddonModLessonHelperProvider { | |||||||
|      * @return {any} Question data. |      * @return {any} Question data. | ||||||
|      */ |      */ | ||||||
|     getQuestionFromPageData(questionForm: FormGroup, pageData: any): any { |     getQuestionFromPageData(questionForm: FormGroup, pageData: any): any { | ||||||
|         const question: any = {}; |         const question: any = {}, | ||||||
|  |             element = this.domUtils.convertToElement(pageData.pagecontent); | ||||||
| 
 | 
 | ||||||
|         // Get the container of the question answers if it exists.
 |         // Get the container of the question answers if it exists.
 | ||||||
|         this.div.innerHTML = pageData.pagecontent; |         const fieldContainer = element.querySelector('.fcontainer'); | ||||||
|         const fieldContainer = this.div.querySelector('.fcontainer'); |  | ||||||
| 
 | 
 | ||||||
|         // Get hidden inputs and add their data to the form group.
 |         // Get hidden inputs and add their data to the form group.
 | ||||||
|         const hiddenInputs = <HTMLInputElement[]> Array.from(this.div.querySelectorAll('input[type="hidden"]')); |         const hiddenInputs = <HTMLInputElement[]> Array.from(element.querySelectorAll('input[type="hidden"]')); | ||||||
|         hiddenInputs.forEach((input) => { |         hiddenInputs.forEach((input) => { | ||||||
|             questionForm.addControl(input.name, this.fb.control(input.value)); |             questionForm.addControl(input.name, this.fb.control(input.value)); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Get the submit button and extract its value.
 |         // Get the submit button and extract its value.
 | ||||||
|         const submitButton = <HTMLInputElement> this.div.querySelector('input[type="submit"]'); |         const submitButton = <HTMLInputElement> element.querySelector('input[type="submit"]'); | ||||||
|         question.submitLabel = submitButton ? submitButton.value : this.translate.instant('addon.mod_lesson.submit'); |         question.submitLabel = submitButton ? submitButton.value : this.translate.instant('addon.mod_lesson.submit'); | ||||||
| 
 | 
 | ||||||
|         if (!fieldContainer) { |         if (!fieldContainer) { | ||||||
| @ -358,28 +357,27 @@ export class AddonModLessonHelperProvider { | |||||||
|      *               the HTML. |      *               the HTML. | ||||||
|      */ |      */ | ||||||
|     getQuestionPageAnswerDataFromHtml(html: string): any { |     getQuestionPageAnswerDataFromHtml(html: string): any { | ||||||
|         const data: any = {}; |         const data: any = {}, | ||||||
| 
 |             element = this.domUtils.convertToElement(html); | ||||||
|         this.div.innerHTML = html; |  | ||||||
| 
 | 
 | ||||||
|         // Check if it has a checkbox.
 |         // Check if it has a checkbox.
 | ||||||
|         let input = <HTMLInputElement> this.div.querySelector('input[type="checkbox"][name*="answer"]'); |         let input = <HTMLInputElement> element.querySelector('input[type="checkbox"][name*="answer"]'); | ||||||
| 
 | 
 | ||||||
|         if (input) { |         if (input) { | ||||||
|             // Truefalse or multichoice.
 |             // Truefalse or multichoice.
 | ||||||
|             data.isCheckbox = true; |             data.isCheckbox = true; | ||||||
|             data.checked = !!input.checked; |             data.checked = !!input.checked; | ||||||
|             data.name = input.name; |             data.name = input.name; | ||||||
|             data.highlight = !!this.div.querySelector('.highlight'); |             data.highlight = !!element.querySelector('.highlight'); | ||||||
| 
 | 
 | ||||||
|             input.remove(); |             input.remove(); | ||||||
|             data.content = this.div.innerHTML.trim(); |             data.content = element.innerHTML.trim(); | ||||||
| 
 | 
 | ||||||
|             return data; |             return data; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Check if it has an input text or number.
 |         // Check if it has an input text or number.
 | ||||||
|         input = <HTMLInputElement> this.div.querySelector('input[type="number"],input[type="text"]'); |         input = <HTMLInputElement> element.querySelector('input[type="number"],input[type="text"]'); | ||||||
|         if (input) { |         if (input) { | ||||||
|             // Short answer or numeric.
 |             // Short answer or numeric.
 | ||||||
|             data.isText = true; |             data.isText = true; | ||||||
| @ -389,7 +387,7 @@ export class AddonModLessonHelperProvider { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Check if it has a select.
 |         // Check if it has a select.
 | ||||||
|         const select = <HTMLSelectElement> this.div.querySelector('select'); |         const select = <HTMLSelectElement> element.querySelector('select'); | ||||||
|         if (select && select.options) { |         if (select && select.options) { | ||||||
|             // Matching.
 |             // Matching.
 | ||||||
|             const selectedOption = select.options[select.selectedIndex]; |             const selectedOption = select.options[select.selectedIndex]; | ||||||
| @ -402,7 +400,7 @@ export class AddonModLessonHelperProvider { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             select.remove(); |             select.remove(); | ||||||
|             data.content = this.div.innerHTML.trim(); |             data.content = element.innerHTML.trim(); | ||||||
| 
 | 
 | ||||||
|             return data; |             return data; | ||||||
|         } |         } | ||||||
| @ -477,11 +475,11 @@ export class AddonModLessonHelperProvider { | |||||||
|      * @return {string} Feedback without the question text. |      * @return {string} Feedback without the question text. | ||||||
|      */ |      */ | ||||||
|     removeQuestionFromFeedback(html: string): string { |     removeQuestionFromFeedback(html: string): string { | ||||||
|         this.div.innerHTML = html; |         const element = this.domUtils.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         // Remove the question text.
 |         // Remove the question text.
 | ||||||
|         this.domUtils.removeElement(this.div, '.generalbox:not(.feedback):not(.correctanswer)'); |         this.domUtils.removeElement(element, '.generalbox:not(.feedback):not(.correctanswer)'); | ||||||
| 
 | 
 | ||||||
|         return this.div.innerHTML.trim(); |         return element.innerHTML.trim(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; | |||||||
| import { CoreLoggerProvider } from '@providers/logger'; | import { CoreLoggerProvider } from '@providers/logger'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
|  | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { CoreGradesProvider } from '@core/grades/providers/grades'; | import { CoreGradesProvider } from '@core/grades/providers/grades'; | ||||||
| import { CoreSiteWSPreSets } from '@classes/site'; | import { CoreSiteWSPreSets } from '@classes/site'; | ||||||
| @ -170,10 +171,9 @@ export class AddonModLessonProvider { | |||||||
| 
 | 
 | ||||||
|     protected ROOT_CACHE_KEY = 'mmaModLesson:'; |     protected ROOT_CACHE_KEY = 'mmaModLesson:'; | ||||||
|     protected logger; |     protected logger; | ||||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 |  | ||||||
| 
 | 
 | ||||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, |     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, | ||||||
|             private translate: TranslateService, private textUtils: CoreTextUtilsProvider, |             private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider, | ||||||
|             private lessonOfflineProvider: AddonModLessonOfflineProvider) { |             private lessonOfflineProvider: AddonModLessonOfflineProvider) { | ||||||
|         this.logger = logger.getInstance('AddonModLessonProvider'); |         this.logger = logger.getInstance('AddonModLessonProvider'); | ||||||
| 
 | 
 | ||||||
| @ -233,9 +233,9 @@ export class AddonModLessonProvider { | |||||||
|         if (page.answerdata && !this.answerPageIsQuestion(page)) { |         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.
 |             // 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]) { |             if (page.answerdata.answers && page.answerdata.answers[0]) { | ||||||
|                 this.div.innerHTML = page.answerdata.answers[0][0]; |                 const element = this.domUtils.convertToElement(page.answerdata.answers[0][0]); | ||||||
| 
 | 
 | ||||||
|                 return !!this.div.querySelector('input[type="button"]'); |                 return !!element.querySelector('input[type="button"]'); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -27,8 +27,6 @@ import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate'; | |||||||
| @Injectable() | @Injectable() | ||||||
| export class AddonModQuizHelperProvider { | export class AddonModQuizHelperProvider { | ||||||
| 
 | 
 | ||||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 |  | ||||||
| 
 |  | ||||||
|     constructor(private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, |     constructor(private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, | ||||||
|             private accessRuleDelegate: AddonModQuizAccessRuleDelegate, private quizProvider: AddonModQuizProvider, |             private accessRuleDelegate: AddonModQuizAccessRuleDelegate, private quizProvider: AddonModQuizProvider, | ||||||
|             private modalCtrl: ModalController, private quizOfflineProvider: AddonModQuizOfflineProvider) { } |             private modalCtrl: ModalController, private quizOfflineProvider: AddonModQuizOfflineProvider) { } | ||||||
| @ -153,9 +151,9 @@ export class AddonModQuizHelperProvider { | |||||||
|      * @return {string}      Question's mark. |      * @return {string}      Question's mark. | ||||||
|      */ |      */ | ||||||
|     getQuestionMarkFromHtml(html: string): string { |     getQuestionMarkFromHtml(html: string): string { | ||||||
|         this.div.innerHTML = html; |         const element = this.domUtils.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         return this.domUtils.getContentsOfElement(this.div, '.grade'); |         return this.domUtils.getContentsOfElement(element, '.grade'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; | |||||||
| import { CoreFilepoolProvider } from '@providers/filepool'; | import { CoreFilepoolProvider } from '@providers/filepool'; | ||||||
| import { CoreLoggerProvider } from '@providers/logger'; | import { CoreLoggerProvider } from '@providers/logger'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
|  | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| @ -56,13 +57,13 @@ export class AddonModQuizProvider { | |||||||
| 
 | 
 | ||||||
|     protected ROOT_CACHE_KEY = 'mmaModQuiz:'; |     protected ROOT_CACHE_KEY = 'mmaModQuiz:'; | ||||||
|     protected logger; |     protected logger; | ||||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 |  | ||||||
| 
 | 
 | ||||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, |     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, | ||||||
|             private translate: TranslateService, private textUtils: CoreTextUtilsProvider, |             private translate: TranslateService, private textUtils: CoreTextUtilsProvider, | ||||||
|             private gradesHelper: CoreGradesHelperProvider, private questionDelegate: CoreQuestionDelegate, |             private gradesHelper: CoreGradesHelperProvider, private questionDelegate: CoreQuestionDelegate, | ||||||
|             private filepoolProvider: CoreFilepoolProvider, private timeUtils: CoreTimeUtilsProvider, |             private filepoolProvider: CoreFilepoolProvider, private timeUtils: CoreTimeUtilsProvider, | ||||||
|             private accessRulesDelegate: AddonModQuizAccessRuleDelegate, private quizOfflineProvider: AddonModQuizOfflineProvider) { |             private accessRulesDelegate: AddonModQuizAccessRuleDelegate, private quizOfflineProvider: AddonModQuizOfflineProvider, | ||||||
|  |             private domUtils: CoreDomUtilsProvider) { | ||||||
|         this.logger = logger.getInstance('AddonModQuizProvider'); |         this.logger = logger.getInstance('AddonModQuizProvider'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1480,9 +1481,9 @@ export class AddonModQuizProvider { | |||||||
|      * @return {boolean} Whether it's blocked. |      * @return {boolean} Whether it's blocked. | ||||||
|      */ |      */ | ||||||
|     isQuestionBlocked(question: any): boolean { |     isQuestionBlocked(question: any): boolean { | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         return !!this.div.querySelector('.mod_quiz-blocked_question_warning'); |         return !!element.querySelector('.mod_quiz-blocked_question_warning'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Injectable, Injector } from '@angular/core'; | import { Injectable, Injector } from '@angular/core'; | ||||||
|  | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { CoreQuestionHandler } from '@core/question/providers/delegate'; | import { CoreQuestionHandler } from '@core/question/providers/delegate'; | ||||||
| import { AddonQtypeNumericalHandler } from '@addon/qtype/numerical/providers/handler'; | import { AddonQtypeNumericalHandler } from '@addon/qtype/numerical/providers/handler'; | ||||||
| @ -27,7 +28,8 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | |||||||
|     name = 'AddonQtypeCalculated'; |     name = 'AddonQtypeCalculated'; | ||||||
|     type = 'qtype_calculated'; |     type = 'qtype_calculated'; | ||||||
| 
 | 
 | ||||||
|     constructor(private utils: CoreUtilsProvider, private numericalHandler: AddonQtypeNumericalHandler) { } |     constructor(private utils: CoreUtilsProvider, private numericalHandler: AddonQtypeNumericalHandler, | ||||||
|  |             private domUtils: CoreDomUtilsProvider) { } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Return the Component to use to display the question. |      * Return the Component to use to display the question. | ||||||
| @ -120,9 +122,8 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | |||||||
|      * @return {boolean} Whether the question requires units. |      * @return {boolean} Whether the question requires units. | ||||||
|      */ |      */ | ||||||
|     requiresUnits(question: any): boolean { |     requiresUnits(question: any): boolean { | ||||||
|         const div = document.createElement('div'); |         const element = this.domUtils.convertToElement(question.html); | ||||||
|         div.innerHTML = question.html; |  | ||||||
| 
 | 
 | ||||||
|         return !!(div.querySelector('select[name*=unit]') || div.querySelector('input[type="radio"]')); |         return !!(element.querySelector('select[name*=unit]') || element.querySelector('input[type="radio"]')); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -47,13 +47,12 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent | |||||||
|             return this.questionHelper.showComponentError(this.onAbort); |             return this.questionHelper.showComponentError(this.onAbort); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const div = document.createElement('div'); |         const element = this.domUtils.convertToElement(this.question.html); | ||||||
|         div.innerHTML = this.question.html; |  | ||||||
| 
 | 
 | ||||||
|         // Get D&D area and question text.
 |         // Get D&D area and question text.
 | ||||||
|         const ddArea = div.querySelector('.ddarea'); |         const ddArea = element.querySelector('.ddarea'); | ||||||
| 
 | 
 | ||||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); |         this.question.text = this.domUtils.getContentsOfElement(element, '.qtext'); | ||||||
|         if (!ddArea || typeof this.question.text == 'undefined') { |         if (!ddArea || typeof this.question.text == 'undefined') { | ||||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); |             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -47,14 +47,13 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple | |||||||
|             return this.questionHelper.showComponentError(this.onAbort); |             return this.questionHelper.showComponentError(this.onAbort); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const div = document.createElement('div'); |         const element = this.domUtils.convertToElement(this.question.html); | ||||||
|         div.innerHTML = this.question.html; |  | ||||||
| 
 | 
 | ||||||
|         // Get D&D area, form and question text.
 |         // Get D&D area, form and question text.
 | ||||||
|         const ddArea = div.querySelector('.ddarea'), |         const ddArea = element.querySelector('.ddarea'), | ||||||
|             ddForm = div.querySelector('.ddform'); |             ddForm = element.querySelector('.ddform'); | ||||||
| 
 | 
 | ||||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); |         this.question.text = this.domUtils.getContentsOfElement(element, '.qtext'); | ||||||
|         if (!ddArea || !ddForm || typeof this.question.text == 'undefined') { |         if (!ddArea || !ddForm || typeof this.question.text == 'undefined') { | ||||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); |             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||||
| 
 | 
 | ||||||
| @ -64,7 +63,7 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple | |||||||
|         // Build the D&D area HTML.
 |         // Build the D&D area HTML.
 | ||||||
|         this.question.ddArea = ddArea.outerHTML; |         this.question.ddArea = ddArea.outerHTML; | ||||||
| 
 | 
 | ||||||
|         const wrongParts = div.querySelector('.wrongparts'); |         const wrongParts = element.querySelector('.wrongparts'); | ||||||
|         if (wrongParts) { |         if (wrongParts) { | ||||||
|             this.question.ddArea += wrongParts.outerHTML; |             this.question.ddArea += wrongParts.outerHTML; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -47,17 +47,16 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme | |||||||
|             return this.questionHelper.showComponentError(this.onAbort); |             return this.questionHelper.showComponentError(this.onAbort); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const div = document.createElement('div'); |         const element = this.domUtils.convertToElement(this.question.html); | ||||||
|         div.innerHTML = this.question.html; |  | ||||||
| 
 | 
 | ||||||
|         // Replace Moodle's correct/incorrect and feedback classes with our own.
 |         // Replace Moodle's correct/incorrect and feedback classes with our own.
 | ||||||
|         this.questionHelper.replaceCorrectnessClasses(div); |         this.questionHelper.replaceCorrectnessClasses(element); | ||||||
|         this.questionHelper.replaceFeedbackClasses(div); |         this.questionHelper.replaceFeedbackClasses(element); | ||||||
| 
 | 
 | ||||||
|         // Treat the correct/incorrect icons.
 |         // Treat the correct/incorrect icons.
 | ||||||
|         this.questionHelper.treatCorrectnessIcons(div); |         this.questionHelper.treatCorrectnessIcons(element); | ||||||
| 
 | 
 | ||||||
|         const answerContainer = div.querySelector('.answercontainer'); |         const answerContainer = element.querySelector('.answercontainer'); | ||||||
|         if (!answerContainer) { |         if (!answerContainer) { | ||||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); |             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||||
| 
 | 
 | ||||||
| @ -67,7 +66,7 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme | |||||||
|         this.question.readOnly = answerContainer.classList.contains('readonly'); |         this.question.readOnly = answerContainer.classList.contains('readonly'); | ||||||
|         this.question.answers = answerContainer.outerHTML; |         this.question.answers = answerContainer.outerHTML; | ||||||
| 
 | 
 | ||||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); |         this.question.text = this.domUtils.getContentsOfElement(element, '.qtext'); | ||||||
|         if (typeof this.question.text == 'undefined') { |         if (typeof this.question.text == 'undefined') { | ||||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); |             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||||
| 
 | 
 | ||||||
| @ -75,7 +74,7 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Get the inputs where the answers will be stored and add them to the question text.
 |         // Get the inputs where the answers will be stored and add them to the question text.
 | ||||||
|         const inputEls = <HTMLElement[]> Array.from(div.querySelectorAll('input[type="hidden"]:not([name*=sequencecheck])')); |         const inputEls = <HTMLElement[]> Array.from(element.querySelectorAll('input[type="hidden"]:not([name*=sequencecheck])')); | ||||||
| 
 | 
 | ||||||
|         inputEls.forEach((inputEl) => { |         inputEls.forEach((inputEl) => { | ||||||
|             this.question.text += inputEl.outerHTML; |             this.question.text += inputEl.outerHTML; | ||||||
|  | |||||||
| @ -33,10 +33,10 @@ export class AddonQtypeDescriptionComponent extends CoreQuestionBaseComponent im | |||||||
|      * Component being initialized. |      * Component being initialized. | ||||||
|      */ |      */ | ||||||
|     ngOnInit(): void { |     ngOnInit(): void { | ||||||
|         const questionDiv = this.initComponent(); |         const questionEl = this.initComponent(); | ||||||
|         if (questionDiv) { |         if (questionEl) { | ||||||
|             // Get the "seen" hidden input.
 |             // Get the "seen" hidden input.
 | ||||||
|             const input = <HTMLInputElement> questionDiv.querySelector('input[type="hidden"][name*=seen]'); |             const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=seen]'); | ||||||
|             if (input) { |             if (input) { | ||||||
|                 this.question.seenInput = { |                 this.question.seenInput = { | ||||||
|                     name: input.name, |                     name: input.name, | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ | |||||||
| 
 | 
 | ||||||
| import { Injectable, Injector } from '@angular/core'; | import { Injectable, Injector } from '@angular/core'; | ||||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
|  | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { CoreQuestionHandler } from '@core/question/providers/delegate'; | import { CoreQuestionHandler } from '@core/question/providers/delegate'; | ||||||
| import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; | import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; | ||||||
| @ -28,10 +29,8 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | |||||||
|     name = 'AddonQtypeEssay'; |     name = 'AddonQtypeEssay'; | ||||||
|     type = 'qtype_essay'; |     type = 'qtype_essay'; | ||||||
| 
 | 
 | ||||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 |  | ||||||
| 
 |  | ||||||
|     constructor(private utils: CoreUtilsProvider, private questionHelper: CoreQuestionHelperProvider, |     constructor(private utils: CoreUtilsProvider, private questionHelper: CoreQuestionHelperProvider, | ||||||
|             private textUtils: CoreTextUtilsProvider) { } |             private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider) { } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Return the name of the behaviour to use for the question. |      * Return the name of the behaviour to use for the question. | ||||||
| @ -65,14 +64,14 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | |||||||
|      * @return {string} Prevent submit message. Undefined or empty if can be submitted. |      * @return {string} Prevent submit message. Undefined or empty if can be submitted. | ||||||
|      */ |      */ | ||||||
|     getPreventSubmitMessage(question: any): string { |     getPreventSubmitMessage(question: any): string { | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         if (this.div.querySelector('div[id*=filemanager]')) { |         if (element.querySelector('div[id*=filemanager]')) { | ||||||
|             // The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question.
 |             // The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question.
 | ||||||
|             return 'core.question.errorattachmentsnotsupported'; |             return 'core.question.errorattachmentsnotsupported'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (this.questionHelper.hasDraftFileUrls(this.div.innerHTML)) { |         if (this.questionHelper.hasDraftFileUrls(element.innerHTML)) { | ||||||
|             return 'core.question.errorinlinefilesnotsupported'; |             return 'core.question.errorinlinefilesnotsupported'; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -85,10 +84,10 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | |||||||
|      * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. |      * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. | ||||||
|      */ |      */ | ||||||
|     isCompleteResponse(question: any, answers: any): number { |     isCompleteResponse(question: any, answers: any): number { | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         const hasInlineText = answers['answer'] && answers['answer'] !== '', |         const hasInlineText = answers['answer'] && answers['answer'] !== '', | ||||||
|             allowsAttachments = !!this.div.querySelector('div[id*=filemanager]'); |             allowsAttachments = !!element.querySelector('div[id*=filemanager]'); | ||||||
| 
 | 
 | ||||||
|         if (!allowsAttachments) { |         if (!allowsAttachments) { | ||||||
|             return hasInlineText ? 1 : 0; |             return hasInlineText ? 1 : 0; | ||||||
| @ -141,10 +140,10 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | |||||||
|      * @return {void|Promise<any>} Return a promise resolved when done if async, void if sync. |      * @return {void|Promise<any>} Return a promise resolved when done if async, void if sync. | ||||||
|      */ |      */ | ||||||
|     prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> { |     prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> { | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         // Search the textarea to get its name.
 |         // Search the textarea to get its name.
 | ||||||
|         const textarea = <HTMLTextAreaElement> this.div.querySelector('textarea[name*=_answer]'); |         const textarea = <HTMLTextAreaElement> element.querySelector('textarea[name*=_answer]'); | ||||||
| 
 | 
 | ||||||
|         if (textarea && typeof answers[textarea.name] != 'undefined') { |         if (textarea && typeof answers[textarea.name] != 'undefined') { | ||||||
|             // Add some HTML to the text if needed.
 |             // Add some HTML to the text if needed.
 | ||||||
|  | |||||||
| @ -824,6 +824,7 @@ body.keyboard-is-open core-ion-tabs .tabbar { | |||||||
| // Fix links and videos in ion-radio and ion-checkbox. | // Fix links and videos in ion-radio and ion-checkbox. | ||||||
| .item.item-radio, .item.item-checkbox { | .item.item-radio, .item.item-checkbox { | ||||||
|   .input-wrapper { |   .input-wrapper { | ||||||
|  |     position: relative; | ||||||
|     z-index: 5; |     z-index: 5; | ||||||
|     pointer-events: none; |     pointer-events: none; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -51,12 +51,12 @@ export class CoreQuestionBaseComponent { | |||||||
|      */ |      */ | ||||||
|     initCalculatedComponent(): void | HTMLElement { |     initCalculatedComponent(): void | HTMLElement { | ||||||
|         // Treat the input text first.
 |         // Treat the input text first.
 | ||||||
|         const questionDiv = this.initInputTextComponent(); |         const questionEl = this.initInputTextComponent(); | ||||||
|         if (questionDiv) { |         if (questionEl) { | ||||||
| 
 | 
 | ||||||
|             // Check if the question has a select for units.
 |             // Check if the question has a select for units.
 | ||||||
|             const selectModel: any = {}, |             const selectModel: any = {}, | ||||||
|                 select = <HTMLSelectElement> questionDiv.querySelector('select[name*=unit]'), |                 select = <HTMLSelectElement> questionEl.querySelector('select[name*=unit]'), | ||||||
|                 options = select && Array.from(select.querySelectorAll('option')); |                 options = select && Array.from(select.querySelectorAll('option')); | ||||||
| 
 | 
 | ||||||
|             if (select && options && options.length) { |             if (select && options && options.length) { | ||||||
| @ -94,24 +94,24 @@ export class CoreQuestionBaseComponent { | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // Get the accessibility label.
 |                 // Get the accessibility label.
 | ||||||
|                 const accessibilityLabel = questionDiv.querySelector('label[for="' + select.id + '"]'); |                 const accessibilityLabel = questionEl.querySelector('label[for="' + select.id + '"]'); | ||||||
|                 selectModel.accessibilityLabel = accessibilityLabel && accessibilityLabel.innerHTML; |                 selectModel.accessibilityLabel = accessibilityLabel && accessibilityLabel.innerHTML; | ||||||
| 
 | 
 | ||||||
|                 this.question.select = selectModel; |                 this.question.select = selectModel; | ||||||
| 
 | 
 | ||||||
|                 // Check which one should be displayed first: the select or the input.
 |                 // Check which one should be displayed first: the select or the input.
 | ||||||
|                 const input = questionDiv.querySelector('input[type="text"][name*=answer]'); |                 const input = questionEl.querySelector('input[type="text"][name*=answer]'); | ||||||
|                 this.question.selectFirst = |                 this.question.selectFirst = | ||||||
|                         questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(select.outerHTML); |                         questionEl.innerHTML.indexOf(input.outerHTML) > questionEl.innerHTML.indexOf(select.outerHTML); | ||||||
| 
 | 
 | ||||||
|                 return questionDiv; |                 return questionEl; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Check if the question has radio buttons for units.
 |             // Check if the question has radio buttons for units.
 | ||||||
|             const radios = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="radio"]')); |             const radios = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="radio"]')); | ||||||
|             if (!radios.length) { |             if (!radios.length) { | ||||||
|                 // No select and no radio buttons. The units need to be entered in the input text.
 |                 // No select and no radio buttons. The units need to be entered in the input text.
 | ||||||
|                 return questionDiv; |                 return questionEl; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.question.options = []; |             this.question.options = []; | ||||||
| @ -126,7 +126,7 @@ export class CoreQuestionBaseComponent { | |||||||
|                         disabled: radioEl.disabled |                         disabled: radioEl.disabled | ||||||
|                     }, |                     }, | ||||||
|                     // Get the label with the question text.
 |                     // Get the label with the question text.
 | ||||||
|                     label = <HTMLElement> questionDiv.querySelector('label[for="' + option.id + '"]'); |                     label = <HTMLElement> questionEl.querySelector('label[for="' + option.id + '"]'); | ||||||
| 
 | 
 | ||||||
|                 this.question.optionsName = option.name; |                 this.question.optionsName = option.name; | ||||||
| 
 | 
 | ||||||
| @ -154,9 +154,9 @@ export class CoreQuestionBaseComponent { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Check which one should be displayed first: the options or the input.
 |             // Check which one should be displayed first: the options or the input.
 | ||||||
|             const input = questionDiv.querySelector('input[type="text"][name*=answer]'); |             const input = questionEl.querySelector('input[type="text"][name*=answer]'); | ||||||
|             this.question.optionsFirst = |             this.question.optionsFirst = | ||||||
|                     questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(radios[0].outerHTML); |                     questionEl.innerHTML.indexOf(input.outerHTML) > questionEl.innerHTML.indexOf(radios[0].outerHTML); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -172,18 +172,17 @@ export class CoreQuestionBaseComponent { | |||||||
|             return this.questionHelper.showComponentError(this.onAbort); |             return this.questionHelper.showComponentError(this.onAbort); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const div = document.createElement('div'); |         const element = this.domUtils.convertToElement(this.question.html); | ||||||
|         div.innerHTML = this.question.html; |  | ||||||
| 
 | 
 | ||||||
|         // Extract question text.
 |         // Extract question text.
 | ||||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); |         this.question.text = this.domUtils.getContentsOfElement(element, '.qtext'); | ||||||
|         if (typeof this.question.text == 'undefined') { |         if (typeof this.question.text == 'undefined') { | ||||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); |             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||||
| 
 | 
 | ||||||
|             return this.questionHelper.showComponentError(this.onAbort); |             return this.questionHelper.showComponentError(this.onAbort); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return div; |         return element; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -192,24 +191,24 @@ export class CoreQuestionBaseComponent { | |||||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. |      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||||
|      */ |      */ | ||||||
|     initEssayComponent(): void | HTMLElement { |     initEssayComponent(): void | HTMLElement { | ||||||
|         const questionDiv = this.initComponent(); |         const questionEl = this.initComponent(); | ||||||
| 
 | 
 | ||||||
|         if (questionDiv) { |         if (questionEl) { | ||||||
|             // First search the textarea.
 |             // First search the textarea.
 | ||||||
|             const textarea = <HTMLTextAreaElement> questionDiv.querySelector('textarea[name*=_answer]'); |             const textarea = <HTMLTextAreaElement> questionEl.querySelector('textarea[name*=_answer]'); | ||||||
|             this.question.allowsAttachments = !!questionDiv.querySelector('div[id*=filemanager]'); |             this.question.allowsAttachments = !!questionEl.querySelector('div[id*=filemanager]'); | ||||||
|             this.question.isMonospaced = !!questionDiv.querySelector('.qtype_essay_monospaced'); |             this.question.isMonospaced = !!questionEl.querySelector('.qtype_essay_monospaced'); | ||||||
|             this.question.isPlainText = this.question.isMonospaced || !!questionDiv.querySelector('.qtype_essay_plain'); |             this.question.isPlainText = this.question.isMonospaced || !!questionEl.querySelector('.qtype_essay_plain'); | ||||||
|             this.question.hasDraftFiles = this.questionHelper.hasDraftFileUrls(questionDiv.innerHTML); |             this.question.hasDraftFiles = this.questionHelper.hasDraftFileUrls(questionEl.innerHTML); | ||||||
| 
 | 
 | ||||||
|             if (!textarea) { |             if (!textarea) { | ||||||
|                 // Textarea not found, we might be in review. Search the answer and the attachments.
 |                 // Textarea not found, we might be in review. Search the answer and the attachments.
 | ||||||
|                 this.question.answer = this.domUtils.getContentsOfElement(questionDiv, '.qtype_essay_response'); |                 this.question.answer = this.domUtils.getContentsOfElement(questionEl, '.qtype_essay_response'); | ||||||
|                 this.question.attachments = this.questionHelper.getQuestionAttachmentsFromHtml( |                 this.question.attachments = this.questionHelper.getQuestionAttachmentsFromHtml( | ||||||
|                         this.domUtils.getContentsOfElement(questionDiv, '.attachments')); |                         this.domUtils.getContentsOfElement(questionEl, '.attachments')); | ||||||
|             } else { |             } else { | ||||||
|                 // Textarea found.
 |                 // Textarea found.
 | ||||||
|                 const input = <HTMLInputElement> questionDiv.querySelector('input[type="hidden"][name*=answerformat]'), |                 const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=answerformat]'), | ||||||
|                     content = textarea.innerHTML; |                     content = textarea.innerHTML; | ||||||
| 
 | 
 | ||||||
|                 this.question.textarea = { |                 this.question.textarea = { | ||||||
| @ -241,11 +240,10 @@ export class CoreQuestionBaseComponent { | |||||||
|             return this.questionHelper.showComponentError(this.onAbort); |             return this.questionHelper.showComponentError(this.onAbort); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const div = document.createElement('div'); |         const element = this.domUtils.convertToElement(this.question.html); | ||||||
|         div.innerHTML = this.question.html; |  | ||||||
| 
 | 
 | ||||||
|         // Get question content.
 |         // Get question content.
 | ||||||
|         const content = <HTMLElement> div.querySelector(contentSelector); |         const content = <HTMLElement> element.querySelector(contentSelector); | ||||||
|         if (!content) { |         if (!content) { | ||||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); |             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||||
| 
 | 
 | ||||||
| @ -257,11 +255,11 @@ export class CoreQuestionBaseComponent { | |||||||
|         this.domUtils.removeElement(content, '.validationerror'); |         this.domUtils.removeElement(content, '.validationerror'); | ||||||
| 
 | 
 | ||||||
|         // Replace Moodle's correct/incorrect and feedback classes with our own.
 |         // Replace Moodle's correct/incorrect and feedback classes with our own.
 | ||||||
|         this.questionHelper.replaceCorrectnessClasses(div); |         this.questionHelper.replaceCorrectnessClasses(element); | ||||||
|         this.questionHelper.replaceFeedbackClasses(div); |         this.questionHelper.replaceFeedbackClasses(element); | ||||||
| 
 | 
 | ||||||
|         // Treat the correct/incorrect icons.
 |         // Treat the correct/incorrect icons.
 | ||||||
|         this.questionHelper.treatCorrectnessIcons(div); |         this.questionHelper.treatCorrectnessIcons(element); | ||||||
| 
 | 
 | ||||||
|         // Set the question text.
 |         // Set the question text.
 | ||||||
|         this.question.text = content.innerHTML; |         this.question.text = content.innerHTML; | ||||||
| @ -273,10 +271,10 @@ export class CoreQuestionBaseComponent { | |||||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. |      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||||
|      */ |      */ | ||||||
|     initInputTextComponent(): void | HTMLElement { |     initInputTextComponent(): void | HTMLElement { | ||||||
|         const questionDiv = this.initComponent(); |         const questionEl = this.initComponent(); | ||||||
|         if (questionDiv) { |         if (questionEl) { | ||||||
|             // Get the input element.
 |             // Get the input element.
 | ||||||
|             const input = <HTMLInputElement> questionDiv.querySelector('input[type="text"][name*=answer]'); |             const input = <HTMLInputElement> questionEl.querySelector('input[type="text"][name*=answer]'); | ||||||
|             if (!input) { |             if (!input) { | ||||||
|                 this.logger.warn('Aborting because couldn\'t find input.', this.question.name); |                 this.logger.warn('Aborting because couldn\'t find input.', this.question.name); | ||||||
| 
 | 
 | ||||||
| @ -302,7 +300,7 @@ export class CoreQuestionBaseComponent { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return questionDiv; |         return questionEl; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -311,11 +309,11 @@ export class CoreQuestionBaseComponent { | |||||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. |      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||||
|      */ |      */ | ||||||
|     initMatchComponent(): void | HTMLElement { |     initMatchComponent(): void | HTMLElement { | ||||||
|         const questionDiv = this.initComponent(); |         const questionEl = this.initComponent(); | ||||||
| 
 | 
 | ||||||
|         if (questionDiv) { |         if (questionEl) { | ||||||
|             // Find rows.
 |             // Find rows.
 | ||||||
|             const rows = Array.from(questionDiv.querySelectorAll('table.answer tr')); |             const rows = Array.from(questionEl.querySelectorAll('table.answer tr')); | ||||||
|             if (!rows || !rows.length) { |             if (!rows || !rows.length) { | ||||||
|                 this.logger.warn('Aborting because couldn\'t find any row.', this.question.name); |                 this.logger.warn('Aborting because couldn\'t find any row.', this.question.name); | ||||||
| 
 | 
 | ||||||
| @ -394,7 +392,7 @@ export class CoreQuestionBaseComponent { | |||||||
|             this.question.loaded = true; |             this.question.loaded = true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return questionDiv; |         return questionEl; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -403,19 +401,19 @@ export class CoreQuestionBaseComponent { | |||||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. |      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||||
|      */ |      */ | ||||||
|     initMultichoiceComponent(): void | HTMLElement { |     initMultichoiceComponent(): void | HTMLElement { | ||||||
|         const questionDiv = this.initComponent(); |         const questionEl = this.initComponent(); | ||||||
| 
 | 
 | ||||||
|         if (questionDiv) { |         if (questionEl) { | ||||||
| 
 | 
 | ||||||
|             // Get the prompt.
 |             // Get the prompt.
 | ||||||
|             this.question.prompt = this.domUtils.getContentsOfElement(questionDiv, '.prompt'); |             this.question.prompt = this.domUtils.getContentsOfElement(questionEl, '.prompt'); | ||||||
| 
 | 
 | ||||||
|             // Search radio buttons first (single choice).
 |             // Search radio buttons first (single choice).
 | ||||||
|             let options = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="radio"]')); |             let options = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="radio"]')); | ||||||
|             if (!options || !options.length) { |             if (!options || !options.length) { | ||||||
|                 // Radio buttons not found, it should be a multi answer. Search for checkbox.
 |                 // Radio buttons not found, it should be a multi answer. Search for checkbox.
 | ||||||
|                 this.question.multi = true; |                 this.question.multi = true; | ||||||
|                 options = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="checkbox"]')); |                 options = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="checkbox"]')); | ||||||
| 
 | 
 | ||||||
|                 if (!options || !options.length) { |                 if (!options || !options.length) { | ||||||
|                     // No checkbox found either. Abort.
 |                     // No checkbox found either. Abort.
 | ||||||
| @ -441,7 +439,7 @@ export class CoreQuestionBaseComponent { | |||||||
|                 this.question.optionsName = option.name; |                 this.question.optionsName = option.name; | ||||||
| 
 | 
 | ||||||
|                 // Get the label with the question text.
 |                 // Get the label with the question text.
 | ||||||
|                 const label = questionDiv.querySelector('label[for="' + option.id + '"]'); |                 const label = questionEl.querySelector('label[for="' + option.id + '"]'); | ||||||
|                 if (label) { |                 if (label) { | ||||||
|                     option.text = label.innerHTML; |                     option.text = label.innerHTML; | ||||||
| 
 | 
 | ||||||
| @ -483,6 +481,6 @@ export class CoreQuestionBaseComponent { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return questionDiv; |         return questionEl; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,7 +29,6 @@ import { CoreQuestionDelegate } from './delegate'; | |||||||
| @Injectable() | @Injectable() | ||||||
| export class CoreQuestionHelperProvider { | export class CoreQuestionHelperProvider { | ||||||
|     protected lastErrorShown = 0; |     protected lastErrorShown = 0; | ||||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 |  | ||||||
| 
 | 
 | ||||||
|     constructor(private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, |     constructor(private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, | ||||||
|         private questionProvider: CoreQuestionProvider, private sitesProvider: CoreSitesProvider, |         private questionProvider: CoreQuestionProvider, private sitesProvider: CoreSitesProvider, | ||||||
| @ -70,15 +69,15 @@ export class CoreQuestionHelperProvider { | |||||||
|     extractQbehaviourButtons(question: any, selector?: string): void { |     extractQbehaviourButtons(question: any, selector?: string): void { | ||||||
|         selector = selector || '.im-controls input[type="submit"]'; |         selector = selector || '.im-controls input[type="submit"]'; | ||||||
| 
 | 
 | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         // Search the buttons.
 |         // Search the buttons.
 | ||||||
|         const buttons = <HTMLInputElement[]> Array.from(this.div.querySelectorAll(selector)); |         const buttons = <HTMLInputElement[]> Array.from(element.querySelectorAll(selector)); | ||||||
|         buttons.forEach((button) => { |         buttons.forEach((button) => { | ||||||
|             this.addBehaviourButton(question, button); |             this.addBehaviourButton(question, button); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         question.html = this.div.innerHTML; |         question.html = element.innerHTML; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -91,9 +90,9 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @return {boolean} Wether the certainty is found. |      * @return {boolean} Wether the certainty is found. | ||||||
|      */ |      */ | ||||||
|     extractQbehaviourCBM(question: any): boolean { |     extractQbehaviourCBM(question: any): boolean { | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         const labels = Array.from(this.div.querySelectorAll('.im-controls .certaintychoices label[for*="certainty"]')); |         const labels = Array.from(element.querySelectorAll('.im-controls .certaintychoices label[for*="certainty"]')); | ||||||
|         question.behaviourCertaintyOptions = []; |         question.behaviourCertaintyOptions = []; | ||||||
| 
 | 
 | ||||||
|         labels.forEach((label) => { |         labels.forEach((label) => { | ||||||
| @ -158,10 +157,10 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @return {boolean} Whether the seen input is found. |      * @return {boolean} Whether the seen input is found. | ||||||
|      */ |      */ | ||||||
|     extractQbehaviourSeenInput(question: any): boolean { |     extractQbehaviourSeenInput(question: any): boolean { | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         // Search the "seen" input.
 |         // Search the "seen" input.
 | ||||||
|         const seenInput = <HTMLInputElement> this.div.querySelector('input[type="hidden"][name*=seen]'); |         const seenInput = <HTMLInputElement> element.querySelector('input[type="hidden"][name*=seen]'); | ||||||
|         if (seenInput) { |         if (seenInput) { | ||||||
|             // Get the data and remove the input.
 |             // Get the data and remove the input.
 | ||||||
|             question.behaviourSeenInput = { |             question.behaviourSeenInput = { | ||||||
| @ -169,7 +168,7 @@ export class CoreQuestionHelperProvider { | |||||||
|                 value: seenInput.value |                 value: seenInput.value | ||||||
|             }; |             }; | ||||||
|             seenInput.parentElement.removeChild(seenInput); |             seenInput.parentElement.removeChild(seenInput); | ||||||
|             question.html = this.div.innerHTML; |             question.html = element.innerHTML; | ||||||
| 
 | 
 | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
| @ -214,9 +213,9 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @param {string} attrName Name of the attribute to store the HTML in. |      * @param {string} attrName Name of the attribute to store the HTML in. | ||||||
|      */ |      */ | ||||||
|     protected extractQuestionLastElementNotInContent(question: any, selector: string, attrName: string): void { |     protected extractQuestionLastElementNotInContent(question: any, selector: string, attrName: string): void { | ||||||
|         this.div.innerHTML = question.html; |         const element = this.domUtils.convertToElement(question.html); | ||||||
| 
 | 
 | ||||||
|         const matches = <HTMLElement[]> Array.from(this.div.querySelectorAll(selector)); |         const matches = <HTMLElement[]> Array.from(element.querySelectorAll(selector)); | ||||||
| 
 | 
 | ||||||
|         // Get the last element and check it's not in the question contents.
 |         // Get the last element and check it's not in the question contents.
 | ||||||
|         let last = matches.pop(); |         let last = matches.pop(); | ||||||
| @ -225,7 +224,7 @@ export class CoreQuestionHelperProvider { | |||||||
|                 // Not in question contents. Add it to a separate attribute and remove it from the HTML.
 |                 // Not in question contents. Add it to a separate attribute and remove it from the HTML.
 | ||||||
|                 question[attrName] = last.innerHTML; |                 question[attrName] = last.innerHTML; | ||||||
|                 last.parentElement.removeChild(last); |                 last.parentElement.removeChild(last); | ||||||
|                 question.html = this.div.innerHTML; |                 question.html = element.innerHTML; | ||||||
| 
 | 
 | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| @ -283,11 +282,10 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @return {any} Object where the keys are the names. |      * @return {any} Object where the keys are the names. | ||||||
|      */ |      */ | ||||||
|     getAllInputNamesFromHtml(html: string): any { |     getAllInputNamesFromHtml(html: string): any { | ||||||
|         const form = document.createElement('form'), |         const element = this.domUtils.convertToElement('<form>' + html + '</form>'), | ||||||
|  |             form = <HTMLFormElement> element.children[0], | ||||||
|             answers = {}; |             answers = {}; | ||||||
| 
 | 
 | ||||||
|         form.innerHTML = html; |  | ||||||
| 
 |  | ||||||
|         // Search all input elements.
 |         // Search all input elements.
 | ||||||
|         Array.from(form.elements).forEach((element: HTMLInputElement) => { |         Array.from(form.elements).forEach((element: HTMLInputElement) => { | ||||||
|             const name = element.name || ''; |             const name = element.name || ''; | ||||||
| @ -350,13 +348,13 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @return {Object[]}    Attachments. |      * @return {Object[]}    Attachments. | ||||||
|      */ |      */ | ||||||
|     getQuestionAttachmentsFromHtml(html: string): any[] { |     getQuestionAttachmentsFromHtml(html: string): any[] { | ||||||
|         this.div.innerHTML = html; |         const element = this.domUtils.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         // Remove the filemanager (area to attach files to a question).
 |         // Remove the filemanager (area to attach files to a question).
 | ||||||
|         this.domUtils.removeElement(this.div, 'div[id*=filemanager]'); |         this.domUtils.removeElement(element, 'div[id*=filemanager]'); | ||||||
| 
 | 
 | ||||||
|         // Search the anchors.
 |         // Search the anchors.
 | ||||||
|         const anchors = Array.from(this.div.querySelectorAll('a')), |         const anchors = Array.from(element.querySelectorAll('a')), | ||||||
|             attachments = []; |             attachments = []; | ||||||
| 
 | 
 | ||||||
|         anchors.forEach((anchor) => { |         anchors.forEach((anchor) => { | ||||||
| @ -383,10 +381,10 @@ export class CoreQuestionHelperProvider { | |||||||
|      */ |      */ | ||||||
|     getQuestionSequenceCheckFromHtml(html: string): {name: string, value: string} { |     getQuestionSequenceCheckFromHtml(html: string): {name: string, value: string} { | ||||||
|         if (html) { |         if (html) { | ||||||
|             this.div.innerHTML = html; |             const element = this.domUtils.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|             // Search the input holding the sequencecheck.
 |             // Search the input holding the sequencecheck.
 | ||||||
|             const input = <HTMLInputElement> this.div.querySelector('input[name*=sequencecheck]'); |             const input = <HTMLInputElement> element.querySelector('input[name*=sequencecheck]'); | ||||||
|             if (input && typeof input.name != 'undefined' && typeof input.value != 'undefined') { |             if (input && typeof input.name != 'undefined' && typeof input.value != 'undefined') { | ||||||
|                 return { |                 return { | ||||||
|                     name: input.name, |                     name: input.name, | ||||||
| @ -415,9 +413,9 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @return {string} Validation error message if present. |      * @return {string} Validation error message if present. | ||||||
|      */ |      */ | ||||||
|     getValidationErrorFromHtml(html: string): string { |     getValidationErrorFromHtml(html: string): string { | ||||||
|         this.div.innerHTML = html; |         const element = this.domUtils.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         return this.domUtils.getContentsOfElement(this.div, '.validationerror'); |         return this.domUtils.getContentsOfElement(element, '.validationerror'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -443,8 +441,8 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @param {any} question Question. |      * @param {any} question Question. | ||||||
|      */ |      */ | ||||||
|     loadLocalAnswersInHtml(question: any): void { |     loadLocalAnswersInHtml(question: any): void { | ||||||
|         const form = document.createElement('form'); |         const element = this.domUtils.convertToElement('<form>' + question.html + '</form>'), | ||||||
|         form.innerHTML = question.html; |             form = <HTMLFormElement> element.children[0]; | ||||||
| 
 | 
 | ||||||
|         // Search all input elements.
 |         // Search all input elements.
 | ||||||
|         Array.from(form.elements).forEach((element: HTMLInputElement | HTMLButtonElement) => { |         Array.from(form.elements).forEach((element: HTMLInputElement | HTMLButtonElement) => { | ||||||
| @ -574,9 +572,9 @@ export class CoreQuestionHelperProvider { | |||||||
|      * @return {boolean} Whether the button is found. |      * @return {boolean} Whether the button is found. | ||||||
|      */ |      */ | ||||||
|     protected searchBehaviourButton(question: any, htmlProperty: string, selector: string): boolean { |     protected searchBehaviourButton(question: any, htmlProperty: string, selector: string): boolean { | ||||||
|         this.div.innerHTML = question[htmlProperty]; |         const element = this.domUtils.convertToElement(question[htmlProperty]); | ||||||
| 
 | 
 | ||||||
|         const button = <HTMLInputElement> this.div.querySelector(selector); |         const button = <HTMLInputElement> element.querySelector(selector); | ||||||
|         if (button) { |         if (button) { | ||||||
|             // Add a behaviour button to the question's "behaviourButtons" property.
 |             // Add a behaviour button to the question's "behaviourButtons" property.
 | ||||||
|             this.addBehaviourButton(question, button); |             this.addBehaviourButton(question, button); | ||||||
| @ -585,7 +583,7 @@ export class CoreQuestionHelperProvider { | |||||||
|             button.parentElement.removeChild(button); |             button.parentElement.removeChild(button); | ||||||
| 
 | 
 | ||||||
|             // Update the question's html.
 |             // Update the question's html.
 | ||||||
|             question[htmlProperty] = this.div.innerHTML; |             question[htmlProperty] = element.innerHTML; | ||||||
| 
 | 
 | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
|   <meta charset="UTF-8"> |   <meta charset="UTF-8"> | ||||||
|   <title>Moodle Desktop</title> <!-- Title is used in desktop, but should be ignored in mobile apps. --> |   <title>Moodle Desktop</title> <!-- Title is used in desktop, but should be ignored in mobile apps. --> | ||||||
|   <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> |   <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> | ||||||
|   <meta http-equiv="Content-Security-Policy" content="default-src * filesystem: cdvfile: file: gap: https://ssl.gstatic.com; img-src * filesystem: gap: data: cdvfile: file: https://ssl.gstatic.com android-webview-video-poster: blob:; style-src 'self' 'unsafe-inline' filesystem: cdvfile: file:; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:* filesystem: cdvfile: file:; media-src * filesystem: cdvfile: file: gap: blob:"> |   <meta http-equiv="Content-Security-Policy" content="default-src * filesystem: cdvfile: file: data: gap: https://ssl.gstatic.com; img-src * filesystem: gap: data: cdvfile: file: https://ssl.gstatic.com android-webview-video-poster: blob:; style-src 'self' 'unsafe-inline' filesystem: cdvfile: file:; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:* filesystem: cdvfile: file:; media-src * filesystem: cdvfile: file: gap: blob:"> | ||||||
|   <meta name="format-detection" content="telephone=no"> |   <meta name="format-detection" content="telephone=no"> | ||||||
|   <meta name="msapplication-tap-highlight" content="no"> |   <meta name="msapplication-tap-highlight" content="no"> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -35,7 +35,8 @@ export class CoreDomUtilsProvider { | |||||||
|         'search', 'tel', 'text', 'time', 'url', 'week']; |         'search', 'tel', 'text', 'time', 'url', 'week']; | ||||||
|     protected INSTANCE_ID_ATTR_NAME = 'core-instance-id'; |     protected INSTANCE_ID_ATTR_NAME = 'core-instance-id'; | ||||||
| 
 | 
 | ||||||
|     protected element = document.createElement('div'); // Fake element to use in some functions, to prevent creating it each time.
 |     protected parser = new DOMParser(); // Parser to treat HTML.
 | ||||||
|  | 
 | ||||||
|     protected matchesFn: string; // Name of the "matches" function to use when simulating a closest call.
 |     protected matchesFn: string; // Name of the "matches" function to use when simulating a closest call.
 | ||||||
|     protected instances: {[id: string]: any} = {}; // Store component/directive instances by id.
 |     protected instances: {[id: string]: any} = {}; // Store component/directive instances by id.
 | ||||||
|     protected lastInstanceId = 0; |     protected lastInstanceId = 0; | ||||||
| @ -124,6 +125,28 @@ export class CoreDomUtilsProvider { | |||||||
|         return Promise.resolve(); |         return Promise.resolve(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body. | ||||||
|  |      * | ||||||
|  |      * @param {string} html Text to convert. | ||||||
|  |      * @return {HTMLElement} Element. | ||||||
|  |      */ | ||||||
|  |     convertToElement(html: string): HTMLElement { | ||||||
|  |         if (this.parser) { | ||||||
|  |             const doc = this.parser.parseFromString(html, 'text/html'); | ||||||
|  | 
 | ||||||
|  |             // Verify that the doc is valid. In some OS like Android 4.4 only XML parsing is supported, so doc is null.
 | ||||||
|  |             if (doc) { | ||||||
|  |                 return doc.body; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const element = document.createElement('div'); | ||||||
|  |         element.innerHTML = html; | ||||||
|  | 
 | ||||||
|  |         return element; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Create a "cancelled" error. These errors won't display an error message in showErrorModal functions. |      * Create a "cancelled" error. These errors won't display an error message in showErrorModal functions. | ||||||
|      * |      * | ||||||
| @ -169,8 +192,8 @@ export class CoreDomUtilsProvider { | |||||||
|         const urls = []; |         const urls = []; | ||||||
|         let elements; |         let elements; | ||||||
| 
 | 
 | ||||||
|         this.element.innerHTML = html; |         const element = this.convertToElement(html); | ||||||
|         elements = this.element.querySelectorAll('a, img, audio, video, source, track'); |         elements = element.querySelectorAll('a, img, audio, video, source, track'); | ||||||
| 
 | 
 | ||||||
|         for (let i = 0; i < elements.length; i++) { |         for (let i = 0; i < elements.length; i++) { | ||||||
|             const element = elements[i]; |             const element = elements[i]; | ||||||
| @ -599,21 +622,21 @@ export class CoreDomUtilsProvider { | |||||||
|     removeElementFromHtml(html: string, selector: string, removeAll?: boolean): string { |     removeElementFromHtml(html: string, selector: string, removeAll?: boolean): string { | ||||||
|         let selected; |         let selected; | ||||||
| 
 | 
 | ||||||
|         this.element.innerHTML = html; |         const element = this.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         if (removeAll) { |         if (removeAll) { | ||||||
|             selected = this.element.querySelectorAll(selector); |             selected = element.querySelectorAll(selector); | ||||||
|             for (let i = 0; i < selected.length; i++) { |             for (let i = 0; i < selected.length; i++) { | ||||||
|                 selected[i].remove(); |                 selected[i].remove(); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             selected = this.element.querySelector(selector); |             selected = element.querySelector(selector); | ||||||
|             if (selected) { |             if (selected) { | ||||||
|                 selected.remove(); |                 selected.remove(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.element.innerHTML; |         return element.innerHTML; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -665,10 +688,10 @@ export class CoreDomUtilsProvider { | |||||||
|         let media, |         let media, | ||||||
|             anchors; |             anchors; | ||||||
| 
 | 
 | ||||||
|         this.element.innerHTML = html; |         const element = this.convertToElement(html); | ||||||
| 
 | 
 | ||||||
|         // Treat elements with src (img, audio, video, ...).
 |         // Treat elements with src (img, audio, video, ...).
 | ||||||
|         media = this.element.querySelectorAll('img, video, audio, source, track'); |         media = element.querySelectorAll('img, video, audio, source, track'); | ||||||
|         media.forEach((media: HTMLElement) => { |         media.forEach((media: HTMLElement) => { | ||||||
|             let newSrc = paths[this.textUtils.decodeURIComponent(media.getAttribute('src'))]; |             let newSrc = paths[this.textUtils.decodeURIComponent(media.getAttribute('src'))]; | ||||||
| 
 | 
 | ||||||
| @ -686,7 +709,7 @@ export class CoreDomUtilsProvider { | |||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Now treat links.
 |         // Now treat links.
 | ||||||
|         anchors = this.element.querySelectorAll('a'); |         anchors = element.querySelectorAll('a'); | ||||||
|         anchors.forEach((anchor: HTMLElement) => { |         anchors.forEach((anchor: HTMLElement) => { | ||||||
|             const href = this.textUtils.decodeURIComponent(anchor.getAttribute('href')), |             const href = this.textUtils.decodeURIComponent(anchor.getAttribute('href')), | ||||||
|                 newUrl = paths[href]; |                 newUrl = paths[href]; | ||||||
| @ -700,7 +723,7 @@ export class CoreDomUtilsProvider { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         return this.element.innerHTML; |         return element.innerHTML; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1104,13 +1127,13 @@ export class CoreDomUtilsProvider { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Converts HTML formatted text to DOM element. |      * Converts HTML formatted text to DOM element(s). | ||||||
|  |      * | ||||||
|      * @param {string} text HTML text. |      * @param {string} text HTML text. | ||||||
|      * @return {HTMLCollection} Same text converted to HTMLCollection. |      * @return {HTMLCollection} Same text converted to HTMLCollection. | ||||||
|      */ |      */ | ||||||
|     toDom(text: string): HTMLCollection { |     toDom(text: string): HTMLCollection { | ||||||
|         const element = document.createElement('div'); |         const element = this.convertToElement(text); | ||||||
|         element.innerHTML = text; |  | ||||||
| 
 | 
 | ||||||
|         return element.children; |         return element.children; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -68,7 +68,7 @@ export class CoreTextUtilsProvider { | |||||||
|         {old: /_mmaModWorkshop/g, new: '_AddonModWorkshop'}, |         {old: /_mmaModWorkshop/g, new: '_AddonModWorkshop'}, | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected element = document.createElement('div'); // Fake element to use in some functions, to prevent creating it each time.
 |     protected parser = new DOMParser(); // Parser to treat HTML.
 | ||||||
| 
 | 
 | ||||||
|     constructor(private translate: TranslateService, private langProvider: CoreLangProvider, private modalCtrl: ModalController) { } |     constructor(private translate: TranslateService, private langProvider: CoreLangProvider, private modalCtrl: ModalController) { } | ||||||
| 
 | 
 | ||||||
| @ -142,8 +142,8 @@ export class CoreTextUtilsProvider { | |||||||
|         // First, we use a regexpr.
 |         // First, we use a regexpr.
 | ||||||
|         text = text.replace(/(<([^>]+)>)/ig, ''); |         text = text.replace(/(<([^>]+)>)/ig, ''); | ||||||
|         // Then, we rely on the browser. We need to wrap the text to be sure is HTML.
 |         // Then, we rely on the browser. We need to wrap the text to be sure is HTML.
 | ||||||
|         this.element.innerHTML = text; |         const element = this.convertToElement(text); | ||||||
|         text = this.element.textContent; |         text = element.textContent; | ||||||
|         // Recover or remove new lines.
 |         // Recover or remove new lines.
 | ||||||
|         text = this.replaceNewLines(text, singleLine ? ' ' : '<br>'); |         text = this.replaceNewLines(text, singleLine ? ' ' : '<br>'); | ||||||
| 
 | 
 | ||||||
| @ -176,6 +176,29 @@ export class CoreTextUtilsProvider { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 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 {string} html Text to convert. | ||||||
|  |      * @return {HTMLElement} Element. | ||||||
|  |      */ | ||||||
|  |     protected convertToElement(html: string): HTMLElement { | ||||||
|  |         if (this.parser) { | ||||||
|  |             const doc = this.parser.parseFromString(html, 'text/html'); | ||||||
|  | 
 | ||||||
|  |             // Verify that the doc is valid. In some OS like Android 4.4 only XML parsing is supported, so doc is null.
 | ||||||
|  |             if (doc) { | ||||||
|  |                 return doc.body; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const element = document.createElement('div'); | ||||||
|  |         element.innerHTML = html; | ||||||
|  | 
 | ||||||
|  |         return element; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Count words in a text. |      * Count words in a text. | ||||||
|      * |      * | ||||||
| @ -225,9 +248,8 @@ export class CoreTextUtilsProvider { | |||||||
|      */ |      */ | ||||||
|     decodeHTMLEntities(text: string): string { |     decodeHTMLEntities(text: string): string { | ||||||
|         if (text) { |         if (text) { | ||||||
|             this.element.innerHTML = text; |             const element = this.convertToElement(text); | ||||||
|             text = this.element.textContent; |             text = element.textContent; | ||||||
|             this.element.textContent = ''; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return text; |         return text; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user