From 6a61c9baef4079af9cae20a2a15df6a36a171398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 15 Oct 2020 15:12:04 +0200 Subject: [PATCH 1/6] MOBILE-3565 pipes: Fix linting on pipes --- src/app/pipes/create-links.pipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pipes/create-links.pipe.ts b/src/app/pipes/create-links.pipe.ts index 72a8ced9f..9bcf6ddbb 100644 --- a/src/app/pipes/create-links.pipe.ts +++ b/src/app/pipes/create-links.pipe.ts @@ -22,7 +22,7 @@ import { Pipe, PipeTransform } from '@angular/core'; }) export class CoreCreateLinksPipe implements PipeTransform { - protected static replacePattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])(?![^<]*>|[^<>]*<\/)/gim; + protected static replacePattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])(?![^<]*>|[^<>]*<\/)/gim; /** * Takes some text and adds anchor tags to all links that aren't inside anchors. From 8a43c39f491788b2e94ba5a0412ca9a85303a828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 15 Oct 2020 15:50:50 +0200 Subject: [PATCH 2/6] MOBILE-3565 pipes: Add format date pipe --- src/app/pipes/format-date.pipe.ts | 70 +++++++++++++++++++++++++++++++ src/app/pipes/pipes.module.ts | 3 ++ 2 files changed, 73 insertions(+) create mode 100644 src/app/pipes/format-date.pipe.ts diff --git a/src/app/pipes/format-date.pipe.ts b/src/app/pipes/format-date.pipe.ts new file mode 100644 index 000000000..7be53b6dc --- /dev/null +++ b/src/app/pipes/format-date.pipe.ts @@ -0,0 +1,70 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Pipe, PipeTransform } from '@angular/core'; +import { CoreTimeUtils } from '@services/utils/time'; +import { CoreLogger } from '@singletons/logger'; + +/** + * Filter to format a date. + */ +@Pipe({ + name: 'coreFormatDate', +}) +export class CoreFormatDatePipe implements PipeTransform { + + protected logger: CoreLogger; + + constructor() { + this.logger = CoreLogger.getInstance('CoreFormatDatePipe'); + } + + /** + * Format a date. + * + * @param timestamp Timestamp to format (in milliseconds). If not defined, use current time. + * @param format Format to use. It should be a string code to handle i18n (e.g. core.strftimetime). + * Defaults to strftimedaydatetime. + * @param convert If true, convert the format from PHP to Moment. Set it to false for Moment formats. + * @return Formatted date. + */ + transform(timestamp: string | number, format?: string, convert?: boolean): string { + timestamp = timestamp || Date.now(); + format = format || 'strftimedaydatetime'; + + if (typeof timestamp == 'string') { + // Convert the value to a number. + const numberTimestamp = parseInt(timestamp, 10); + if (isNaN(numberTimestamp)) { + this.logger.error('Invalid value received', timestamp); + + return timestamp; + } + timestamp = numberTimestamp; + } + + // Add "core." if needed. + if (format.indexOf('strf') == 0 || format.indexOf('df') == 0) { + format = 'core.' + format; + } + + if (typeof convert == 'undefined') { + // Initialize convert param. Set it to false if it's a core.df format, set it to true otherwise. + convert = format.indexOf('core.df') != 0; + } + + return CoreTimeUtils.instance.userDate(timestamp, format, convert); + } + +} diff --git a/src/app/pipes/pipes.module.ts b/src/app/pipes/pipes.module.ts index 62373d7f6..4108c1255 100644 --- a/src/app/pipes/pipes.module.ts +++ b/src/app/pipes/pipes.module.ts @@ -14,6 +14,7 @@ import { NgModule } from '@angular/core'; import { CoreCreateLinksPipe } from './create-links.pipe'; +import { CoreFormatDatePipe } from './format-date.pipe'; import { CoreNoTagsPipe } from './no-tags.pipe'; import { CoreTimeAgoPipe } from './time-ago.pipe'; @@ -22,12 +23,14 @@ import { CoreTimeAgoPipe } from './time-ago.pipe'; CoreCreateLinksPipe, CoreNoTagsPipe, CoreTimeAgoPipe, + CoreFormatDatePipe, ], imports: [], exports: [ CoreCreateLinksPipe, CoreNoTagsPipe, CoreTimeAgoPipe, + CoreFormatDatePipe, ], }) export class CorePipesModule {} From 919b4f5d44ff528b0027d0c5f82a587856ecd3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 15 Oct 2020 17:06:50 +0200 Subject: [PATCH 3/6] MOBILE-3565 directives: Add long press gesture directive --- src/app/directives/directives.module.ts | 27 ++++++++++ src/app/directives/long-press.directive.ts | 59 ++++++++++++++++++++++ src/app/singletons/core.singletons.ts | 2 + 3 files changed, 88 insertions(+) create mode 100644 src/app/directives/directives.module.ts create mode 100644 src/app/directives/long-press.directive.ts diff --git a/src/app/directives/directives.module.ts b/src/app/directives/directives.module.ts new file mode 100644 index 000000000..729290dd5 --- /dev/null +++ b/src/app/directives/directives.module.ts @@ -0,0 +1,27 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CoreLongPressDirective } from './long-press.directive'; + +@NgModule({ + declarations: [ + CoreLongPressDirective, + ], + imports: [], + exports: [ + CoreLongPressDirective, + ], +}) +export class CoreDirectivesModule {} diff --git a/src/app/directives/long-press.directive.ts b/src/app/directives/long-press.directive.ts new file mode 100644 index 000000000..ca4cda73e --- /dev/null +++ b/src/app/directives/long-press.directive.ts @@ -0,0 +1,59 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Based on https://medium.com/madewithply/ionic-4-long-press-gestures-96cf1e44098b + +import { Directive, ElementRef, OnInit, OnDestroy, Output, EventEmitter } from '@angular/core'; +import { Gesture } from '@ionic/angular'; +import { GestureController } from '@singletons/core.singletons'; +/** + * Directive to add long press actions to html elements. + */ +@Directive({ + selector: '[longPress]', +}) +export class CoreLongPressDirective implements OnInit, OnDestroy { + + element: HTMLElement; + pressGesture: Gesture; + + @Output() longPress = new EventEmitter(); + + constructor(el: ElementRef) { + this.element = el.nativeElement; + this.element.setAttribute('tappable', ''); + } + + /** + * Initialize gesture listening. + */ + ngOnInit(): void { + this.pressGesture = GestureController.instance.create({ + el: this.element, + threshold: 0, + gestureName: 'longpress', + onEnd: ev => this.longPress.emit(ev.event), + }, true); + + this.pressGesture.enable(); + } + + /** + * Destroy gesture listening. + */ + ngOnDestroy(): void { + this.pressGesture.destroy(); + } + +} diff --git a/src/app/singletons/core.singletons.ts b/src/app/singletons/core.singletons.ts index 45fcba1a6..561a8dd98 100644 --- a/src/app/singletons/core.singletons.ts +++ b/src/app/singletons/core.singletons.ts @@ -21,6 +21,7 @@ import { LoadingController as LoadingControllerService, ModalController as ModalControllerService, ToastController as ToastControllerService, + GestureController as GestureControllerService, } from '@ionic/angular'; import { Clipboard as ClipboardService } from '@ionic-native/clipboard/ngx'; @@ -99,6 +100,7 @@ export class AlertController extends makeSingleton(AlertControllerService) {} export class LoadingController extends makeSingleton(LoadingControllerService) {} export class ModalController extends makeSingleton(ModalControllerService) {} export class ToastController extends makeSingleton(ToastControllerService) {} +export class GestureController extends makeSingleton(GestureControllerService) {} // Convert external libraries injectables. export class Translate extends makeSingleton(TranslateService) {} From 41dd1a6a741e0eb8209bd324eb849ffba4008c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 15 Oct 2020 17:07:37 +0200 Subject: [PATCH 4/6] MOBILE-3565 utils: Fix copy to clipboard on browser --- src/app/services/utils/utils.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/services/utils/utils.ts b/src/app/services/utils/utils.ts index e48f2493f..eb77397e0 100644 --- a/src/app/services/utils/utils.ts +++ b/src/app/services/utils/utils.ts @@ -308,12 +308,17 @@ export class CoreUtilsProvider { async copyToClipboard(text: string): Promise { try { await Clipboard.instance.copy(text); - - // Show toast using ionicLoading. - CoreDomUtils.instance.showToast('core.copiedtoclipboard', true); } catch { - // Ignore errors. + // Use HTML Copy command. + const virtualInput = document.createElement('textarea'); + virtualInput.innerHTML = text; + virtualInput.select(); + virtualInput.setSelectionRange(0, 99999); + document.execCommand('copy'); } + + // Show toast using ionicLoading. + CoreDomUtils.instance.showToast('core.copiedtoclipboard', true); } /** From 7ea419c3497562ac598d244100c51dc0fa809937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 20 Oct 2020 12:24:04 +0200 Subject: [PATCH 5/6] MOBILE-3565 gulp: Run gulp watch on serve --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 72bd72e26..678bdd3f2 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "test:watch": "jest --watch", "test:coverage": "jest --coverage", "lint": "ng lint", - "ionic:serve:before": "npx gulp" + "ionic:serve:before": "npx gulp", + "ionic:serve": "npx gulp watch & ng serve", + "ionic:build:before": "npx gulp" }, "dependencies": { "@angular/common": "~10.0.0", From 560b9137e80636769f1498dc665bbf27667bf7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 20 Oct 2020 14:20:51 +0200 Subject: [PATCH 6/6] MOBILE-3565 core: Fix linting on some services and pipes --- src/app/components/components.module.ts | 2 +- src/app/components/icon/icon.ts | 7 ++-- src/app/directives/long-press.directive.ts | 4 +- src/app/services/geolocation.ts | 2 +- src/app/services/groups.ts | 31 ++++++++++----- src/app/services/local-notifications.ts | 45 ++++++++++++---------- src/app/services/plugin-file-delegate.ts | 31 ++++++++++----- src/app/services/utils/time.ts | 8 ++-- src/app/services/utils/url.ts | 31 +++++++-------- 9 files changed, 94 insertions(+), 67 deletions(-) diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index 55bee79bb..67c7a4a6c 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -22,6 +22,6 @@ import { CoreIconComponent } from './icon/icon'; imports: [], exports: [ CoreIconComponent, - ] + ], }) export class CoreComponentsModule {} diff --git a/src/app/components/icon/icon.ts b/src/app/components/icon/icon.ts index 7ae4328cf..eea96b488 100644 --- a/src/app/components/icon/icon.ts +++ b/src/app/components/icon/icon.ts @@ -28,7 +28,7 @@ import { Component, Input, OnChanges, OnDestroy, ElementRef, SimpleChange } from export class CoreIconComponent implements OnChanges, OnDestroy { // Common params. - @Input() name: string; + @Input() name = ''; @Input() color?: string; @Input() slash?: boolean; // Display a red slash over the icon. @@ -48,6 +48,7 @@ export class CoreIconComponent implements OnChanges, OnDestroy { constructor(el: ElementRef) { this.element = el.nativeElement; + this.newElement = this.element } /** @@ -58,7 +59,7 @@ export class CoreIconComponent implements OnChanges, OnDestroy { return; } - const oldElement = this.newElement ? this.newElement : this.element; + const oldElement = this.newElement; // Use a new created element to avoid ion-icon working. // This is necessary to make the FontAwesome stuff work. @@ -102,7 +103,7 @@ export class CoreIconComponent implements OnChanges, OnDestroy { this.newElement.classList.add('core-icon-dir-flip'); } - oldElement.parentElement.replaceChild(this.newElement, oldElement); + oldElement.parentElement?.replaceChild(this.newElement, oldElement); } /** diff --git a/src/app/directives/long-press.directive.ts b/src/app/directives/long-press.directive.ts index ca4cda73e..14a892eda 100644 --- a/src/app/directives/long-press.directive.ts +++ b/src/app/directives/long-press.directive.ts @@ -26,7 +26,7 @@ import { GestureController } from '@singletons/core.singletons'; export class CoreLongPressDirective implements OnInit, OnDestroy { element: HTMLElement; - pressGesture: Gesture; + pressGesture?: Gesture; @Output() longPress = new EventEmitter(); @@ -53,7 +53,7 @@ export class CoreLongPressDirective implements OnInit, OnDestroy { * Destroy gesture listening. */ ngOnDestroy(): void { - this.pressGesture.destroy(); + this.pressGesture?.destroy(); } } diff --git a/src/app/services/geolocation.ts b/src/app/services/geolocation.ts index 317e053f2..110b368e8 100644 --- a/src/app/services/geolocation.ts +++ b/src/app/services/geolocation.ts @@ -116,7 +116,7 @@ export class CoreGeolocationProvider { * * @param error Error. */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any protected isCordovaPermissionDeniedError(error?: any): boolean { return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED; } diff --git a/src/app/services/groups.ts b/src/app/services/groups.ts index ed829076a..3eb041db4 100644 --- a/src/app/services/groups.ts +++ b/src/app/services/groups.ts @@ -60,8 +60,12 @@ export class CoreGroupsProvider { * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @return Promise resolved when the groups are retrieved. */ - async getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): - Promise { + async getActivityAllowedGroups( + cmId: number, + userId?: number, + siteId?: string, + ignoreCache?: boolean, + ): Promise { const site = await CoreSites.instance.getSite(siteId); userId = userId || site.getUserId(); @@ -111,7 +115,7 @@ export class CoreGroupsProvider { * @return Promise resolved when the groups are retrieved. If not allowed, empty array will be returned. */ async getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): - Promise { + Promise { siteId = siteId || CoreSites.instance.getCurrentSiteId(); // Get real groupmode, in case it's forced by the course. @@ -136,10 +140,16 @@ export class CoreGroupsProvider { * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @return Promise resolved with the group info. */ - async getActivityGroupInfo(cmId: number, addAllParts?: boolean, userId?: number, siteId?: string, ignoreCache?: boolean): - Promise { + async getActivityGroupInfo( + cmId: number, + addAllParts?: boolean, + userId?: number, + siteId?: string, + ignoreCache?: boolean, + ): Promise { const groupInfo: CoreGroupInfo = { groups: [], + defaultGroupId: 0, }; const groupMode = await this.getActivityGroupMode(cmId, siteId, ignoreCache); @@ -163,13 +173,13 @@ export class CoreGroupsProvider { } else { // The "canaccessallgroups" field was added in 3.4. Add all participants for visible groups in previous versions. if (result.canaccessallgroups || (typeof result.canaccessallgroups == 'undefined' && groupInfo.visibleGroups)) { - groupInfo.groups.push({ id: 0, name: Translate.instance.instant('core.allparticipants') }); + groupInfo.groups!.push({ id: 0, name: Translate.instance.instant('core.allparticipants') }); groupInfo.defaultGroupId = 0; } else { groupInfo.defaultGroupId = result.groups[0].id; } - groupInfo.groups = groupInfo.groups.concat(result.groups); + groupInfo.groups = groupInfo.groups!.concat(result.groups); } return groupInfo; @@ -233,6 +243,7 @@ export class CoreGroupsProvider { } // @todo Get courses. + return []; } /** @@ -249,7 +260,7 @@ export class CoreGroupsProvider { const courseGroups = await Promise.all(promises); - return [].concat(...courseGroups); + return ([]).concat(...courseGroups); } /** @@ -339,7 +350,7 @@ export class CoreGroupsProvider { * @return Promise resolved when the data is invalidated. */ async invalidateActivityGroupInfo(cmId: number, userId?: number, siteId?: string): Promise { - const promises = []; + const promises = []>[]; promises.push(this.invalidateActivityAllowedGroups(cmId, userId, siteId)); promises.push(this.invalidateActivityGroupMode(cmId, siteId)); @@ -457,7 +468,7 @@ export type CoreGroupInfo = { /** * The group ID to use by default. If all participants is visible, 0 will be used. First group ID otherwise. */ - defaultGroupId?: number; + defaultGroupId: number; }; /** diff --git a/src/app/services/local-notifications.ts b/src/app/services/local-notifications.ts index f3638d86e..385837a89 100644 --- a/src/app/services/local-notifications.ts +++ b/src/app/services/local-notifications.ts @@ -100,13 +100,13 @@ export class CoreLocalNotificationsProvider { // eslint-disable-next-line @typescript-eslint/no-explicit-any protected observables: {[eventName: string]: {[component: string]: Subject}} = {}; - protected triggerSubscription: Subscription; - protected clickSubscription: Subscription; - protected clearSubscription: Subscription; - protected cancelSubscription: Subscription; - protected addSubscription: Subscription; - protected updateSubscription: Subscription; - protected queueRunner: CoreQueueRunner; // Queue to decrease the number of concurrent calls to the plugin (see MOBILE-3477). + protected triggerSubscription?: Subscription; + protected clickSubscription?: Subscription; + protected clearSubscription?: Subscription; + protected cancelSubscription?: Subscription; + protected addSubscription?: Subscription; + protected updateSubscription?: Subscription; + protected queueRunner?: CoreQueueRunner; // Queue to decrease the number of concurrent calls to the plugin (see MOBILE-3477). constructor() { this.logger = CoreLogger.getInstance('CoreLocalNotificationsProvider'); @@ -216,8 +216,8 @@ export class CoreLocalNotificationsProvider { */ canDisableSound(): boolean { // Only allow disabling sound in Android 7 or lower. In iOS and Android 8+ it can easily be done with system settings. - return this.isAvailable() && !CoreApp.instance.isDesktop() && CoreApp.instance.isAndroid() && - Device.instance.version && Number(Device.instance.version.split('.')[0]) < 8; + return this.isAvailable() &&!CoreApp.instance.isDesktop() && CoreApp.instance.isAndroid() && + Number(Device.instance.version?.split('.')[0]) < 8; } /** @@ -323,15 +323,16 @@ export class CoreLocalNotificationsProvider { * @param siteId Site ID. * @return Promise resolved when the notification ID is generated. */ - protected getUniqueNotificationId(notificationId: number, component: string, siteId: string): Promise { + protected async getUniqueNotificationId(notificationId: number, component: string, siteId: string): Promise { if (!siteId || !component) { return Promise.reject(new CoreError('Site ID or component not supplied.')); } - return this.getSiteCode(siteId).then((siteCode) => this.getComponentCode(component).then((componentCode) => - // We use the % operation to keep the number under Android's limit. - (siteCode * 100000000 + componentCode * 10000000 + notificationId) % 2147483647, - )); + const siteCode = await this.getSiteCode(siteId); + const componentCode = await this.getComponentCode(component); + + // We use the % operation to keep the number under Android's limit. + return (siteCode * 100000000 + componentCode * 10000000 + notificationId) % 2147483647; } /** @@ -371,8 +372,10 @@ export class CoreLocalNotificationsProvider { await this.dbReady; try { - const stored = await this.appDB.getRecord<{id: number; at: number}>(CoreLocalNotificationsProvider.TRIGGERED_TABLE, - { id: notification.id }); + const stored = await this.appDB.getRecord<{ id: number; at: number }>( + CoreLocalNotificationsProvider.TRIGGERED_TABLE, + { id: notification.id }, + ); let triggered = (notification.trigger && notification.trigger.at) || 0; @@ -399,7 +402,7 @@ export class CoreLocalNotificationsProvider { * * @param data Data received by the notification. */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any notifyClick(data: any): void { this.notifyEvent('click', data); } @@ -410,7 +413,7 @@ export class CoreLocalNotificationsProvider { * @param eventName Name of the event to notify. * @param data Data received by the notification. */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any notifyEvent(eventName: string, data: any): void { // Execute the code in the Angular zone, so change detection doesn't stop working. NgZone.instance.run(() => { @@ -429,7 +432,7 @@ export class CoreLocalNotificationsProvider { * @param data Notification data. * @return Parsed data. */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any protected parseNotificationData(data: any): any { if (!data) { return {}; @@ -538,8 +541,8 @@ export class CoreLocalNotificationsProvider { */ protected requestCode(table: string, id: string): Promise { const deferred = CoreUtils.instance.promiseDefer(); - const key = table + '#' + id; - const isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0; + const key = table + '#' + id; + const isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0; if (typeof this.codeRequestsQueue[key] != 'undefined') { // There's already a pending request for this store and ID, add the promise to it. diff --git a/src/app/services/plugin-file-delegate.ts b/src/app/services/plugin-file-delegate.ts index 273632bc4..8621d13b8 100644 --- a/src/app/services/plugin-file-delegate.ts +++ b/src/app/services/plugin-file-delegate.ts @@ -70,8 +70,11 @@ export class CorePluginFileDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the file to use. Rejected if cannot download. */ - protected async getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string): - Promise { + protected async getHandlerDownloadableFile( + file: CoreWSExternalFile, + handler?: CorePluginFileHandler, + siteId?: string, + ): Promise { const isDownloadable = await this.isFileDownloadable(file, siteId); if (!isDownloadable.downloadable) { @@ -93,7 +96,7 @@ export class CorePluginFileDelegate extends CoreDelegate { * @param args Arguments of the pluginfile URL defining component and filearea at least. * @return RegExp to match the revision or undefined if not found. */ - getComponentRevisionRegExp(args: string[]): RegExp { + getComponentRevisionRegExp(args: string[]): RegExp | void { // Get handler based on component (args[1]). const handler = this.getHandler(args[1], true); @@ -110,7 +113,7 @@ export class CorePluginFileDelegate extends CoreDelegate { * @return List of URLs. */ getDownloadableFilesFromHTML(container: HTMLElement): string[] { - let files = []; + let files = []; for (const component in this.enabledHandlers) { const handler = this.enabledHandlers[component]; @@ -130,8 +133,8 @@ export class CorePluginFileDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with file size and a boolean to indicate if it is the total size or only partial. */ - async getFilesDownloadSize(files: CoreWSExternalFile[], siteId?: string): Promise<{ size: number; total: boolean }> { - const filteredFiles = []; + async getFilesDownloadSize(files: CoreWSExternalFile[], siteId: string): Promise<{ size: number; total: boolean }> { + const filteredFiles = []; await Promise.all(files.map(async (file) => { const state = await CoreFilepool.instance.getFileStateByUrl(siteId, file.fileurl, file.timemodified); @@ -270,8 +273,12 @@ export class CorePluginFileDelegate extends CoreDelegate { * @param onProgress Function to call on progress. * @return Promise resolved when done. */ - async treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: CoreFilepoolOnProgressCallback): - Promise { + async treatDownloadedFile( + fileUrl: string, + file: FileEntry, + siteId?: string, + onProgress?: CoreFilepoolOnProgressCallback, + ): Promise { const handler = this.getHandlerForFile({ fileurl: fileUrl }); if (handler && handler.treatDownloadedFile) { @@ -373,8 +380,12 @@ export interface CorePluginFileHandler extends CoreDelegateHandler { * @param onProgress Function to call on progress. * @return Promise resolved when done. */ - treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: CoreFilepoolOnProgressCallback): - Promise; + treatDownloadedFile?( + fileUrl: string, + file: FileEntry, + siteId?: string, + onProgress?: CoreFilepoolOnProgressCallback): + Promise; } /** diff --git a/src/app/services/utils/time.ts b/src/app/services/utils/time.ts index b3bba19be..d06b45b47 100644 --- a/src/app/services/utils/time.ts +++ b/src/app/services/utils/time.ts @@ -253,7 +253,7 @@ export class CoreTimeUtilsProvider { formatDurationShort(duration: number): string { const minutes = Math.floor(duration / 60); const seconds = duration - minutes * 60; - const durations = []; + const durations = []; if (minutes > 0) { durations.push(minutes + '\''); @@ -298,16 +298,16 @@ export class CoreTimeUtilsProvider { format = Translate.instance.instant(format ? format : 'core.strftimedaydatetime'); if (fixDay) { - format = format.replace(/%d/g, '%e'); + format = format!.replace(/%d/g, '%e'); } if (fixHour) { - format = format.replace('%I', '%l'); + format = format!.replace('%I', '%l'); } // Format could be in PHP format, convert it to moment. if (convert) { - format = this.convertPHPToMoment(format); + format = this.convertPHPToMoment(format!); } return moment(timestamp).format(format); diff --git a/src/app/services/utils/url.ts b/src/app/services/utils/url.ts index 4af96bb88..c5ceb35a3 100644 --- a/src/app/services/utils/url.ts +++ b/src/app/services/utils/url.ts @@ -102,9 +102,9 @@ export class CoreUrlUtilsProvider { canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean { // Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work. // Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert). - return accessKey && !url.match(/[&?]file=/) && ( - url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || - url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0); + return !!accessKey && !url.match(/[&?]file=/) && ( + url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || + url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0); } /** @@ -255,12 +255,12 @@ export class CoreUrlUtilsProvider { * @param url URL * @return Youtube Embed Video URL or null if not found. */ - getYoutubeEmbedUrl(url: string): string { + getYoutubeEmbedUrl(url: string): string | void { if (!url) { return; } - let videoId: string; + let videoId = ''; const params: CoreUrlParams = {}; url = CoreTextUtils.instance.decodeHTML(url); @@ -327,7 +327,7 @@ export class CoreUrlUtilsProvider { * @param url URL to treat. * @return Protocol, undefined if no protocol found. */ - getUrlProtocol(url: string): string { + getUrlProtocol(url: string): string | void { if (!url) { return; } @@ -345,7 +345,7 @@ export class CoreUrlUtilsProvider { * @param url URL to treat. * @return Scheme, undefined if no scheme found. */ - getUrlScheme(url: string): string { + getUrlScheme(url: string): string | void { if (!url) { return; } @@ -362,7 +362,7 @@ export class CoreUrlUtilsProvider { * @param url URL to treat. * @return Username. Undefined if no username found. */ - getUsernameFromUrl(url: string): string { + getUsernameFromUrl(url: string): string | void { if (url.indexOf('@') > -1) { // Get URL without protocol. const withoutProtocol = url.replace(/^[^?@/]*:\/\//, ''); @@ -402,7 +402,7 @@ export class CoreUrlUtilsProvider { * @return Whether the URL is a gravatar URL. */ isGravatarUrl(url: string): boolean { - return url && url.indexOf('gravatar.com/avatar') !== -1; + return url?.indexOf('gravatar.com/avatar') !== -1; } /** @@ -424,7 +424,7 @@ export class CoreUrlUtilsProvider { isLocalFileUrl(url: string): boolean { const urlParts = CoreUrl.parse(url); - return this.isLocalFileUrlScheme(urlParts.protocol); + return this.isLocalFileUrlScheme(urlParts?.protocol || ''); } /** @@ -434,9 +434,10 @@ export class CoreUrlUtilsProvider { * @return Whether the scheme belongs to a local file. */ isLocalFileUrlScheme(scheme: string): boolean { - if (scheme) { - scheme = scheme.toLowerCase(); + if (!scheme) { + return false; } + scheme = scheme.toLowerCase(); return scheme == 'cdvfile' || scheme == 'file' || @@ -451,7 +452,7 @@ export class CoreUrlUtilsProvider { * @return Whether the URL is a pluginfile URL. */ isPluginFileUrl(url: string): boolean { - return url && url.indexOf('/pluginfile.php') !== -1; + return url?.indexOf('/pluginfile.php') !== -1; } /** @@ -461,7 +462,7 @@ export class CoreUrlUtilsProvider { * @return Whether the URL is a theme image URL. */ isThemeImageUrl(url: string): boolean { - return url && url.indexOf('/theme/image.php') !== -1; + return url?.indexOf('/theme/image.php') !== -1; } /** @@ -488,7 +489,7 @@ export class CoreUrlUtilsProvider { removeUrlParams(url: string): string { const matches = url.match(/^[^?]+/); - return matches && matches[0]; + return matches ? matches[0] : ''; } /**