forked from EVOgeek/Vmeda.Online
		
	
						commit
						1b227930b7
					
				| @ -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", | ||||
|  | ||||
| @ -22,6 +22,6 @@ import { CoreIconComponent } from './icon/icon'; | ||||
|     imports: [], | ||||
|     exports: [ | ||||
|         CoreIconComponent, | ||||
|     ] | ||||
|     ], | ||||
| }) | ||||
| export class CoreComponentsModule {} | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
							
								
								
									
										27
									
								
								src/app/directives/directives.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/directives/directives.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 {} | ||||
							
								
								
									
										59
									
								
								src/app/directives/long-press.directive.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/app/directives/long-press.directive.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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. | ||||
|  | ||||
							
								
								
									
										70
									
								
								src/app/pipes/format-date.pipe.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/app/pipes/format-date.pipe.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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 {} | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
|  | ||||
| @ -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<CoreGroupGetActivityAllowedGroupsResponse> { | ||||
|     async getActivityAllowedGroups( | ||||
|         cmId: number, | ||||
|         userId?: number, | ||||
|         siteId?: string, | ||||
|         ignoreCache?: boolean, | ||||
|     ): Promise<CoreGroupGetActivityAllowedGroupsResponse> { | ||||
|         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<CoreGroupGetActivityAllowedGroupsResponse> { | ||||
|     Promise<CoreGroupGetActivityAllowedGroupsResponse> { | ||||
|         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<CoreGroupInfo> { | ||||
|     async getActivityGroupInfo( | ||||
|         cmId: number, | ||||
|         addAllParts?: boolean, | ||||
|         userId?: number, | ||||
|         siteId?: string, | ||||
|         ignoreCache?: boolean, | ||||
|     ): Promise<CoreGroupInfo> { | ||||
|         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 <CoreGroup[]>[]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -249,7 +260,7 @@ export class CoreGroupsProvider { | ||||
| 
 | ||||
|         const courseGroups = await Promise.all(promises); | ||||
| 
 | ||||
|         return [].concat(...courseGroups); | ||||
|         return (<CoreGroup[]>[]).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<void> { | ||||
|         const promises = []; | ||||
|         const promises = <Promise<void>[]>[]; | ||||
|         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; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -100,13 +100,13 @@ export class CoreLocalNotificationsProvider { | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     protected observables: {[eventName: string]: {[component: string]: Subject<any>}} = {}; | ||||
| 
 | ||||
|     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<number> { | ||||
|     protected async getUniqueNotificationId(notificationId: number, component: string, siteId: string): Promise<number> { | ||||
|         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<number> { | ||||
|         const deferred = CoreUtils.instance.promiseDefer<number>(); | ||||
|             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.
 | ||||
|  | ||||
| @ -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<CoreWSExternalFile> { | ||||
|     protected async getHandlerDownloadableFile( | ||||
|         file: CoreWSExternalFile, | ||||
|         handler?: CorePluginFileHandler, | ||||
|         siteId?: string, | ||||
|     ): Promise<CoreWSExternalFile> { | ||||
|         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 = <CorePluginFileHandler> 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 = <string[]>[]; | ||||
| 
 | ||||
|         for (const component in this.enabledHandlers) { | ||||
|             const handler = <CorePluginFileHandler> 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 = <CoreWSExternalFile[]>[]; | ||||
| 
 | ||||
|         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<void> { | ||||
|     async treatDownloadedFile( | ||||
|         fileUrl: string, | ||||
|         file: FileEntry, | ||||
|         siteId?: string, | ||||
|         onProgress?: CoreFilepoolOnProgressCallback, | ||||
|     ): Promise<void> { | ||||
|         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<void>; | ||||
|     treatDownloadedFile?( | ||||
|         fileUrl: string, | ||||
|         file: FileEntry, | ||||
|         siteId?: string, | ||||
|         onProgress?: CoreFilepoolOnProgressCallback): | ||||
|     Promise<void>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -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 = <string[]>[]; | ||||
| 
 | ||||
|         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); | ||||
|  | ||||
| @ -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] : ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -308,12 +308,17 @@ export class CoreUtilsProvider { | ||||
|     async copyToClipboard(text: string): Promise<void> { | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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) {} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user