From f6d30fb7cb5a41ec6f73e200a255be96db6150c2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 14 Jun 2018 15:01:11 +0200 Subject: [PATCH] MOBILE-2428 dom: Format content of Alerts --- src/classes/site.ts | 15 +- src/core/login/providers/helper.ts | 7 +- src/providers/utils/dom.ts | 243 +++++++++++++++++++---------- 3 files changed, 173 insertions(+), 92 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index 9f380aa46..ce3693047 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -1226,13 +1226,14 @@ export class CoreSite { } if (alertMessage) { - const alert = this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000); - alert.onDidDismiss(() => { - if (inApp) { - resolve(this.utils.openInApp(url, options)); - } else { - resolve(this.utils.openInBrowser(url)); - } + this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000).then((alert) => { + alert.onDidDismiss(() => { + if (inApp) { + resolve(this.utils.openInApp(url, options)); + } else { + resolve(this.utils.openInBrowser(url)); + } + }); }); } else { if (inApp) { diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index e0cfbb6ec..15db817c6 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -685,9 +685,10 @@ export class CoreLoginHelperProvider { * @param {string} error Error message. */ openChangePassword(siteUrl: string, error: string): void { - const alert = this.domUtils.showAlert(this.translate.instant('core.notice'), error, undefined, 3000); - alert.onDidDismiss(() => { - this.utils.openInApp(siteUrl + '/login/change_password.php'); + this.domUtils.showAlert(this.translate.instant('core.notice'), error, undefined, 3000).then((alert) => { + alert.onDidDismiss(() => { + this.utils.openInApp(siteUrl + '/login/change_password.php'); + }); }); } diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index a7b961e9b..8a21c45ee 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -44,22 +44,6 @@ export class CoreDomUtilsProvider { private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider, private modalCtrl: ModalController) { } - /** - * Wraps a message with core-format-text if the message contains HTML tags. - * @todo Finish the adaptation - * - * @param {string} message Message to wrap. - * @return {string} Result message. - */ - private addFormatTextIfNeeded(message: string): string { - // @todo - if (this.textUtils.hasHTMLTags(message)) { - return '' + message + ''; - } - - return message; - } - /** * Equivalent to element.closest(). If the browser doesn't support element.closest, it will * traverse the parents to achieve the same functionality. @@ -776,24 +760,43 @@ export class CoreDomUtilsProvider { * @param {string} message Message to show. * @param {string} [buttonText] Text of the button. * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Alert} The alert modal. + * @return {Promise} Promise resolved with the alert modal. */ - showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Alert { - const alert = this.alertCtrl.create({ - title: title, - message: this.addFormatTextIfNeeded(message), // Add format-text to handle links. - buttons: [buttonText || this.translate.instant('core.ok')] - }); + showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise { + const hasHTMLTags = this.textUtils.hasHTMLTags(message); + let promise; - alert.present(); - - if (autocloseTime > 0) { - setTimeout(() => { - alert.dismiss(); - }, autocloseTime); + if (hasHTMLTags) { + // Format the text. + promise = this.textUtils.formatText(message); + } else { + promise = Promise.resolve(message); } - return alert; + return promise.then((message) => { + + const alert = this.alertCtrl.create({ + title: title, + message: message, + buttons: [buttonText || this.translate.instant('core.ok')] + }); + + alert.present().then(() => { + if (hasHTMLTags) { + // Treat all anchors so they don't override the app. + const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message'); + this.treatAnchors(alertMessageEl); + } + }); + + if (autocloseTime > 0) { + setTimeout(() => { + alert.dismiss(); + }, autocloseTime); + } + + return alert; + }); } /** @@ -803,9 +806,9 @@ export class CoreDomUtilsProvider { * @param {string} message Message to show. * @param {string} [buttonText] Text of the button. * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Alert} The alert modal. + * @return {Promise} Promise resolved with the alert modal. */ - showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number): Alert { + showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise { title = title ? this.translate.instant(title) : title; message = message ? this.translate.instant(message) : message; buttonText = buttonText ? this.translate.instant(buttonText) : buttonText; @@ -825,30 +828,50 @@ export class CoreDomUtilsProvider { */ showConfirm(message: string, title?: string, okText?: string, cancelText?: string, options?: any): Promise { return new Promise((resolve, reject): void => { - options = options || {}; + const hasHTMLTags = this.textUtils.hasHTMLTags(message); + let promise; - options.message = this.addFormatTextIfNeeded(message); // Add format-text to handle links. - options.title = title; - if (!title) { - options.cssClass = 'core-nohead'; + if (hasHTMLTags) { + // Format the text. + promise = this.textUtils.formatText(message); + } else { + promise = Promise.resolve(message); } - options.buttons = [ - { - text: cancelText || this.translate.instant('core.cancel'), - role: 'cancel', - handler: (): void => { - reject(this.createCanceledError()); - } - }, - { - text: okText || this.translate.instant('core.ok'), - handler: (): void => { - resolve(); - } - } - ]; - this.alertCtrl.create(options).present(); + promise.then((message) => { + options = options || {}; + + options.message = message; + options.title = title; + if (!title) { + options.cssClass = 'core-nohead'; + } + options.buttons = [ + { + text: cancelText || this.translate.instant('core.cancel'), + role: 'cancel', + handler: (): void => { + reject(this.createCanceledError()); + } + }, + { + text: okText || this.translate.instant('core.ok'), + handler: (): void => { + resolve(); + } + } + ]; + + const alert = this.alertCtrl.create(options); + + alert.present().then(() => { + if (hasHTMLTags) { + // Treat all anchors so they don't override the app. + const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message'); + this.treatAnchors(alertMessageEl); + } + }); + }); }); } @@ -858,9 +881,9 @@ export class CoreDomUtilsProvider { * @param {any} error Message to show. * @param {boolean} [needsTranslate] Whether the error needs to be translated. * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Alert} The alert modal. + * @return {Promise} Promise resolved with the alert modal. */ - showErrorModal(error: any, needsTranslate?: boolean, autocloseTime?: number): Alert { + showErrorModal(error: any, needsTranslate?: boolean, autocloseTime?: number): Promise { if (typeof error == 'object') { // We received an object instead of a string. Search for common properties. if (error.coreCanceled) { @@ -903,9 +926,9 @@ export class CoreDomUtilsProvider { * @param {any} [defaultError] Message to show if the error is not a string. * @param {boolean} [needsTranslate] Whether the error needs to be translated. * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Alert} The alert modal. + * @return {Promise} Promise resolved with the alert modal. */ - showErrorModalDefault(error: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Alert { + showErrorModalDefault(error: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Promise { if (error && error.coreCanceled) { // It's a canceled error, don't display an error. return; @@ -927,9 +950,9 @@ export class CoreDomUtilsProvider { * @param {any} [defaultError] Message to show if the error is not a string. * @param {boolean} [needsTranslate] Whether the error needs to be translated. * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Alert} The alert modal. + * @return {Promise} Promise resolved with the alert modal. */ - showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Alert { + showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Promise { const error = warnings && warnings.length && warnings[0].message; return this.showErrorModalDefault(error, defaultError, needsTranslate, autocloseTime); @@ -974,32 +997,52 @@ export class CoreDomUtilsProvider { */ showPrompt(message: string, title?: string, placeholder?: string, type: string = 'password'): Promise { return new Promise((resolve, reject): void => { - this.alertCtrl.create({ - message: this.addFormatTextIfNeeded(message), // Add format-text to handle links. - title: title, - inputs: [ - { - name: 'promptinput', - placeholder: placeholder || this.translate.instant('core.login.password'), - type: type - } - ], - buttons: [ - { - text: this.translate.instant('core.cancel'), - role: 'cancel', - handler: (): void => { - reject(); + const hasHTMLTags = this.textUtils.hasHTMLTags(message); + let promise; + + if (hasHTMLTags) { + // Format the text. + promise = this.textUtils.formatText(message); + } else { + promise = Promise.resolve(message); + } + + promise.then((message) => { + const alert = this.alertCtrl.create({ + message: message, + title: title, + inputs: [ + { + name: 'promptinput', + placeholder: placeholder || this.translate.instant('core.login.password'), + type: type } - }, - { - text: this.translate.instant('core.ok'), - handler: (data): void => { - resolve(data.promptinput); + ], + buttons: [ + { + text: this.translate.instant('core.cancel'), + role: 'cancel', + handler: (): void => { + reject(); + } + }, + { + text: this.translate.instant('core.ok'), + handler: (data): void => { + resolve(data.promptinput); + } } + ] + }); + + alert.present().then(() => { + if (hasHTMLTags) { + // Treat all anchors so they don't override the app. + const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message'); + this.treatAnchors(alertMessageEl); } - ] - }).present(); + }); + }); }); } @@ -1069,6 +1112,42 @@ export class CoreDomUtilsProvider { return element.children; } + /** + * Treat anchors inside alert/modals. + * + * @param {HTMLElement} container The HTMLElement that can contain anchors. + */ + protected treatAnchors(container: HTMLElement): void { + const anchors = Array.from(container.querySelectorAll('a')); + + anchors.forEach((anchor) => { + anchor.addEventListener('click', (event) => { + if (event.defaultPrevented) { + // Stop. + return; + } + + const href = anchor.getAttribute('href'); + if (href) { + event.preventDefault(); + event.stopPropagation(); + + // We cannot use CoreDomUtilsProvider.openInBrowser due to circular dependencies. + if (this.appProvider.isDesktop()) { + // It's a desktop app, use Electron shell library to open the browser. + const shell = require('electron').shell; + if (!shell.openExternal(href)) { + // Open browser failed, open a new window in the app. + window.open(href, '_system'); + } + } else { + window.open(href, '_system'); + } + } + }); + }); + } + /** * View an image in a new page or modal. *