commit
1b227930b7
|
@ -26,7 +26,9 @@
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
"lint": "ng lint",
|
"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": {
|
"dependencies": {
|
||||||
"@angular/common": "~10.0.0",
|
"@angular/common": "~10.0.0",
|
||||||
|
|
|
@ -22,6 +22,6 @@ import { CoreIconComponent } from './icon/icon';
|
||||||
imports: [],
|
imports: [],
|
||||||
exports: [
|
exports: [
|
||||||
CoreIconComponent,
|
CoreIconComponent,
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class CoreComponentsModule {}
|
export class CoreComponentsModule {}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { Component, Input, OnChanges, OnDestroy, ElementRef, SimpleChange } from
|
||||||
export class CoreIconComponent implements OnChanges, OnDestroy {
|
export class CoreIconComponent implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
// Common params.
|
// Common params.
|
||||||
@Input() name: string;
|
@Input() name = '';
|
||||||
@Input() color?: string;
|
@Input() color?: string;
|
||||||
@Input() slash?: boolean; // Display a red slash over the icon.
|
@Input() slash?: boolean; // Display a red slash over the icon.
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ export class CoreIconComponent implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
constructor(el: ElementRef) {
|
constructor(el: ElementRef) {
|
||||||
this.element = el.nativeElement;
|
this.element = el.nativeElement;
|
||||||
|
this.newElement = this.element
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +59,7 @@ export class CoreIconComponent implements OnChanges, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldElement = this.newElement ? this.newElement : this.element;
|
const oldElement = this.newElement;
|
||||||
|
|
||||||
// Use a new created element to avoid ion-icon working.
|
// Use a new created element to avoid ion-icon working.
|
||||||
// This is necessary to make the FontAwesome stuff work.
|
// 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');
|
this.newElement.classList.add('core-icon-dir-flip');
|
||||||
}
|
}
|
||||||
|
|
||||||
oldElement.parentElement.replaceChild(this.newElement, oldElement);
|
oldElement.parentElement?.replaceChild(this.newElement, oldElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 {}
|
|
@ -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 {
|
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.
|
* Takes some text and adds anchor tags to all links that aren't inside anchors.
|
||||||
|
|
|
@ -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 { NgModule } from '@angular/core';
|
||||||
import { CoreCreateLinksPipe } from './create-links.pipe';
|
import { CoreCreateLinksPipe } from './create-links.pipe';
|
||||||
|
import { CoreFormatDatePipe } from './format-date.pipe';
|
||||||
import { CoreNoTagsPipe } from './no-tags.pipe';
|
import { CoreNoTagsPipe } from './no-tags.pipe';
|
||||||
import { CoreTimeAgoPipe } from './time-ago.pipe';
|
import { CoreTimeAgoPipe } from './time-ago.pipe';
|
||||||
|
|
||||||
|
@ -22,12 +23,14 @@ import { CoreTimeAgoPipe } from './time-ago.pipe';
|
||||||
CoreCreateLinksPipe,
|
CoreCreateLinksPipe,
|
||||||
CoreNoTagsPipe,
|
CoreNoTagsPipe,
|
||||||
CoreTimeAgoPipe,
|
CoreTimeAgoPipe,
|
||||||
|
CoreFormatDatePipe,
|
||||||
],
|
],
|
||||||
imports: [],
|
imports: [],
|
||||||
exports: [
|
exports: [
|
||||||
CoreCreateLinksPipe,
|
CoreCreateLinksPipe,
|
||||||
CoreNoTagsPipe,
|
CoreNoTagsPipe,
|
||||||
CoreTimeAgoPipe,
|
CoreTimeAgoPipe,
|
||||||
|
CoreFormatDatePipe,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CorePipesModule {}
|
export class CorePipesModule {}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export class CoreGeolocationProvider {
|
||||||
*
|
*
|
||||||
* @param error Error.
|
* @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 {
|
protected isCordovaPermissionDeniedError(error?: any): boolean {
|
||||||
return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED;
|
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).
|
* @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.
|
* @return Promise resolved when the groups are retrieved.
|
||||||
*/
|
*/
|
||||||
async getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean):
|
async getActivityAllowedGroups(
|
||||||
Promise<CoreGroupGetActivityAllowedGroupsResponse> {
|
cmId: number,
|
||||||
|
userId?: number,
|
||||||
|
siteId?: string,
|
||||||
|
ignoreCache?: boolean,
|
||||||
|
): Promise<CoreGroupGetActivityAllowedGroupsResponse> {
|
||||||
const site = await CoreSites.instance.getSite(siteId);
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
userId = userId || site.getUserId();
|
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.
|
* @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):
|
async getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean):
|
||||||
Promise<CoreGroupGetActivityAllowedGroupsResponse> {
|
Promise<CoreGroupGetActivityAllowedGroupsResponse> {
|
||||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||||
|
|
||||||
// Get real groupmode, in case it's forced by the course.
|
// 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).
|
* @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.
|
* @return Promise resolved with the group info.
|
||||||
*/
|
*/
|
||||||
async getActivityGroupInfo(cmId: number, addAllParts?: boolean, userId?: number, siteId?: string, ignoreCache?: boolean):
|
async getActivityGroupInfo(
|
||||||
Promise<CoreGroupInfo> {
|
cmId: number,
|
||||||
|
addAllParts?: boolean,
|
||||||
|
userId?: number,
|
||||||
|
siteId?: string,
|
||||||
|
ignoreCache?: boolean,
|
||||||
|
): Promise<CoreGroupInfo> {
|
||||||
const groupInfo: CoreGroupInfo = {
|
const groupInfo: CoreGroupInfo = {
|
||||||
groups: [],
|
groups: [],
|
||||||
|
defaultGroupId: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupMode = await this.getActivityGroupMode(cmId, siteId, ignoreCache);
|
const groupMode = await this.getActivityGroupMode(cmId, siteId, ignoreCache);
|
||||||
|
@ -163,13 +173,13 @@ export class CoreGroupsProvider {
|
||||||
} else {
|
} else {
|
||||||
// The "canaccessallgroups" field was added in 3.4. Add all participants for visible groups in previous versions.
|
// 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)) {
|
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;
|
groupInfo.defaultGroupId = 0;
|
||||||
} else {
|
} else {
|
||||||
groupInfo.defaultGroupId = result.groups[0].id;
|
groupInfo.defaultGroupId = result.groups[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
groupInfo.groups = groupInfo.groups.concat(result.groups);
|
groupInfo.groups = groupInfo.groups!.concat(result.groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupInfo;
|
return groupInfo;
|
||||||
|
@ -233,6 +243,7 @@ export class CoreGroupsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo Get courses.
|
// @todo Get courses.
|
||||||
|
return <CoreGroup[]>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -249,7 +260,7 @@ export class CoreGroupsProvider {
|
||||||
|
|
||||||
const courseGroups = await Promise.all(promises);
|
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.
|
* @return Promise resolved when the data is invalidated.
|
||||||
*/
|
*/
|
||||||
async invalidateActivityGroupInfo(cmId: number, userId?: number, siteId?: string): Promise<void> {
|
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.invalidateActivityAllowedGroups(cmId, userId, siteId));
|
||||||
promises.push(this.invalidateActivityGroupMode(cmId, 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.
|
* 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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
protected observables: {[eventName: string]: {[component: string]: Subject<any>}} = {};
|
protected observables: {[eventName: string]: {[component: string]: Subject<any>}} = {};
|
||||||
|
|
||||||
protected triggerSubscription: Subscription;
|
protected triggerSubscription?: Subscription;
|
||||||
protected clickSubscription: Subscription;
|
protected clickSubscription?: Subscription;
|
||||||
protected clearSubscription: Subscription;
|
protected clearSubscription?: Subscription;
|
||||||
protected cancelSubscription: Subscription;
|
protected cancelSubscription?: Subscription;
|
||||||
protected addSubscription: Subscription;
|
protected addSubscription?: Subscription;
|
||||||
protected updateSubscription: Subscription;
|
protected updateSubscription?: Subscription;
|
||||||
protected queueRunner: CoreQueueRunner; // Queue to decrease the number of concurrent calls to the plugin (see MOBILE-3477).
|
protected queueRunner?: CoreQueueRunner; // Queue to decrease the number of concurrent calls to the plugin (see MOBILE-3477).
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.logger = CoreLogger.getInstance('CoreLocalNotificationsProvider');
|
this.logger = CoreLogger.getInstance('CoreLocalNotificationsProvider');
|
||||||
|
@ -216,8 +216,8 @@ export class CoreLocalNotificationsProvider {
|
||||||
*/
|
*/
|
||||||
canDisableSound(): boolean {
|
canDisableSound(): boolean {
|
||||||
// Only allow disabling sound in Android 7 or lower. In iOS and Android 8+ it can easily be done with system settings.
|
// 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() &&
|
return this.isAvailable() &&!CoreApp.instance.isDesktop() && CoreApp.instance.isAndroid() &&
|
||||||
Device.instance.version && Number(Device.instance.version.split('.')[0]) < 8;
|
Number(Device.instance.version?.split('.')[0]) < 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -323,15 +323,16 @@ export class CoreLocalNotificationsProvider {
|
||||||
* @param siteId Site ID.
|
* @param siteId Site ID.
|
||||||
* @return Promise resolved when the notification ID is generated.
|
* @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) {
|
if (!siteId || !component) {
|
||||||
return Promise.reject(new CoreError('Site ID or component not supplied.'));
|
return Promise.reject(new CoreError('Site ID or component not supplied.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getSiteCode(siteId).then((siteCode) => this.getComponentCode(component).then((componentCode) =>
|
const siteCode = await this.getSiteCode(siteId);
|
||||||
// We use the % operation to keep the number under Android's limit.
|
const componentCode = await this.getComponentCode(component);
|
||||||
(siteCode * 100000000 + componentCode * 10000000 + notificationId) % 2147483647,
|
|
||||||
));
|
// 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;
|
await this.dbReady;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stored = await this.appDB.getRecord<{id: number; at: number}>(CoreLocalNotificationsProvider.TRIGGERED_TABLE,
|
const stored = await this.appDB.getRecord<{ id: number; at: number }>(
|
||||||
{ id: notification.id });
|
CoreLocalNotificationsProvider.TRIGGERED_TABLE,
|
||||||
|
{ id: notification.id },
|
||||||
|
);
|
||||||
|
|
||||||
let triggered = (notification.trigger && notification.trigger.at) || 0;
|
let triggered = (notification.trigger && notification.trigger.at) || 0;
|
||||||
|
|
||||||
|
@ -399,7 +402,7 @@ export class CoreLocalNotificationsProvider {
|
||||||
*
|
*
|
||||||
* @param data Data received by the notification.
|
* @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 {
|
notifyClick(data: any): void {
|
||||||
this.notifyEvent('click', data);
|
this.notifyEvent('click', data);
|
||||||
}
|
}
|
||||||
|
@ -410,7 +413,7 @@ export class CoreLocalNotificationsProvider {
|
||||||
* @param eventName Name of the event to notify.
|
* @param eventName Name of the event to notify.
|
||||||
* @param data Data received by the notification.
|
* @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 {
|
notifyEvent(eventName: string, data: any): void {
|
||||||
// Execute the code in the Angular zone, so change detection doesn't stop working.
|
// Execute the code in the Angular zone, so change detection doesn't stop working.
|
||||||
NgZone.instance.run(() => {
|
NgZone.instance.run(() => {
|
||||||
|
@ -429,7 +432,7 @@ export class CoreLocalNotificationsProvider {
|
||||||
* @param data Notification data.
|
* @param data Notification data.
|
||||||
* @return Parsed 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 {
|
protected parseNotificationData(data: any): any {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -538,8 +541,8 @@ export class CoreLocalNotificationsProvider {
|
||||||
*/
|
*/
|
||||||
protected requestCode(table: string, id: string): Promise<number> {
|
protected requestCode(table: string, id: string): Promise<number> {
|
||||||
const deferred = CoreUtils.instance.promiseDefer<number>();
|
const deferred = CoreUtils.instance.promiseDefer<number>();
|
||||||
const key = table + '#' + id;
|
const key = table + '#' + id;
|
||||||
const isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0;
|
const isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0;
|
||||||
|
|
||||||
if (typeof this.codeRequestsQueue[key] != 'undefined') {
|
if (typeof this.codeRequestsQueue[key] != 'undefined') {
|
||||||
// There's already a pending request for this store and ID, add the promise to it.
|
// 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.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved with the file to use. Rejected if cannot download.
|
* @return Promise resolved with the file to use. Rejected if cannot download.
|
||||||
*/
|
*/
|
||||||
protected async getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string):
|
protected async getHandlerDownloadableFile(
|
||||||
Promise<CoreWSExternalFile> {
|
file: CoreWSExternalFile,
|
||||||
|
handler?: CorePluginFileHandler,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<CoreWSExternalFile> {
|
||||||
const isDownloadable = await this.isFileDownloadable(file, siteId);
|
const isDownloadable = await this.isFileDownloadable(file, siteId);
|
||||||
|
|
||||||
if (!isDownloadable.downloadable) {
|
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.
|
* @param args Arguments of the pluginfile URL defining component and filearea at least.
|
||||||
* @return RegExp to match the revision or undefined if not found.
|
* @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]).
|
// Get handler based on component (args[1]).
|
||||||
const handler = <CorePluginFileHandler> this.getHandler(args[1], true);
|
const handler = <CorePluginFileHandler> this.getHandler(args[1], true);
|
||||||
|
|
||||||
|
@ -110,7 +113,7 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
||||||
* @return List of URLs.
|
* @return List of URLs.
|
||||||
*/
|
*/
|
||||||
getDownloadableFilesFromHTML(container: HTMLElement): string[] {
|
getDownloadableFilesFromHTML(container: HTMLElement): string[] {
|
||||||
let files = [];
|
let files = <string[]>[];
|
||||||
|
|
||||||
for (const component in this.enabledHandlers) {
|
for (const component in this.enabledHandlers) {
|
||||||
const handler = <CorePluginFileHandler> this.enabledHandlers[component];
|
const handler = <CorePluginFileHandler> this.enabledHandlers[component];
|
||||||
|
@ -130,8 +133,8 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @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.
|
* @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 }> {
|
async getFilesDownloadSize(files: CoreWSExternalFile[], siteId: string): Promise<{ size: number; total: boolean }> {
|
||||||
const filteredFiles = [];
|
const filteredFiles = <CoreWSExternalFile[]>[];
|
||||||
|
|
||||||
await Promise.all(files.map(async (file) => {
|
await Promise.all(files.map(async (file) => {
|
||||||
const state = await CoreFilepool.instance.getFileStateByUrl(siteId, file.fileurl, file.timemodified);
|
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.
|
* @param onProgress Function to call on progress.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: CoreFilepoolOnProgressCallback):
|
async treatDownloadedFile(
|
||||||
Promise<void> {
|
fileUrl: string,
|
||||||
|
file: FileEntry,
|
||||||
|
siteId?: string,
|
||||||
|
onProgress?: CoreFilepoolOnProgressCallback,
|
||||||
|
): Promise<void> {
|
||||||
const handler = this.getHandlerForFile({ fileurl: fileUrl });
|
const handler = this.getHandlerForFile({ fileurl: fileUrl });
|
||||||
|
|
||||||
if (handler && handler.treatDownloadedFile) {
|
if (handler && handler.treatDownloadedFile) {
|
||||||
|
@ -373,8 +380,12 @@ export interface CorePluginFileHandler extends CoreDelegateHandler {
|
||||||
* @param onProgress Function to call on progress.
|
* @param onProgress Function to call on progress.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: CoreFilepoolOnProgressCallback):
|
treatDownloadedFile?(
|
||||||
Promise<void>;
|
fileUrl: string,
|
||||||
|
file: FileEntry,
|
||||||
|
siteId?: string,
|
||||||
|
onProgress?: CoreFilepoolOnProgressCallback):
|
||||||
|
Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -253,7 +253,7 @@ export class CoreTimeUtilsProvider {
|
||||||
formatDurationShort(duration: number): string {
|
formatDurationShort(duration: number): string {
|
||||||
const minutes = Math.floor(duration / 60);
|
const minutes = Math.floor(duration / 60);
|
||||||
const seconds = duration - minutes * 60;
|
const seconds = duration - minutes * 60;
|
||||||
const durations = [];
|
const durations = <string[]>[];
|
||||||
|
|
||||||
if (minutes > 0) {
|
if (minutes > 0) {
|
||||||
durations.push(minutes + '\'');
|
durations.push(minutes + '\'');
|
||||||
|
@ -298,16 +298,16 @@ export class CoreTimeUtilsProvider {
|
||||||
format = Translate.instance.instant(format ? format : 'core.strftimedaydatetime');
|
format = Translate.instance.instant(format ? format : 'core.strftimedaydatetime');
|
||||||
|
|
||||||
if (fixDay) {
|
if (fixDay) {
|
||||||
format = format.replace(/%d/g, '%e');
|
format = format!.replace(/%d/g, '%e');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fixHour) {
|
if (fixHour) {
|
||||||
format = format.replace('%I', '%l');
|
format = format!.replace('%I', '%l');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format could be in PHP format, convert it to moment.
|
// Format could be in PHP format, convert it to moment.
|
||||||
if (convert) {
|
if (convert) {
|
||||||
format = this.convertPHPToMoment(format);
|
format = this.convertPHPToMoment(format!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return moment(timestamp).format(format);
|
return moment(timestamp).format(format);
|
||||||
|
|
|
@ -102,9 +102,9 @@ export class CoreUrlUtilsProvider {
|
||||||
canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean {
|
canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean {
|
||||||
// Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work.
|
// 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).
|
// Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert).
|
||||||
return accessKey && !url.match(/[&?]file=/) && (
|
return !!accessKey && !url.match(/[&?]file=/) && (
|
||||||
url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 ||
|
url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 ||
|
||||||
url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0);
|
url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -255,12 +255,12 @@ export class CoreUrlUtilsProvider {
|
||||||
* @param url URL
|
* @param url URL
|
||||||
* @return Youtube Embed Video URL or null if not found.
|
* @return Youtube Embed Video URL or null if not found.
|
||||||
*/
|
*/
|
||||||
getYoutubeEmbedUrl(url: string): string {
|
getYoutubeEmbedUrl(url: string): string | void {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let videoId: string;
|
let videoId = '';
|
||||||
const params: CoreUrlParams = {};
|
const params: CoreUrlParams = {};
|
||||||
|
|
||||||
url = CoreTextUtils.instance.decodeHTML(url);
|
url = CoreTextUtils.instance.decodeHTML(url);
|
||||||
|
@ -327,7 +327,7 @@ export class CoreUrlUtilsProvider {
|
||||||
* @param url URL to treat.
|
* @param url URL to treat.
|
||||||
* @return Protocol, undefined if no protocol found.
|
* @return Protocol, undefined if no protocol found.
|
||||||
*/
|
*/
|
||||||
getUrlProtocol(url: string): string {
|
getUrlProtocol(url: string): string | void {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -345,7 +345,7 @@ export class CoreUrlUtilsProvider {
|
||||||
* @param url URL to treat.
|
* @param url URL to treat.
|
||||||
* @return Scheme, undefined if no scheme found.
|
* @return Scheme, undefined if no scheme found.
|
||||||
*/
|
*/
|
||||||
getUrlScheme(url: string): string {
|
getUrlScheme(url: string): string | void {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ export class CoreUrlUtilsProvider {
|
||||||
* @param url URL to treat.
|
* @param url URL to treat.
|
||||||
* @return Username. Undefined if no username found.
|
* @return Username. Undefined if no username found.
|
||||||
*/
|
*/
|
||||||
getUsernameFromUrl(url: string): string {
|
getUsernameFromUrl(url: string): string | void {
|
||||||
if (url.indexOf('@') > -1) {
|
if (url.indexOf('@') > -1) {
|
||||||
// Get URL without protocol.
|
// Get URL without protocol.
|
||||||
const withoutProtocol = url.replace(/^[^?@/]*:\/\//, '');
|
const withoutProtocol = url.replace(/^[^?@/]*:\/\//, '');
|
||||||
|
@ -402,7 +402,7 @@ export class CoreUrlUtilsProvider {
|
||||||
* @return Whether the URL is a gravatar URL.
|
* @return Whether the URL is a gravatar URL.
|
||||||
*/
|
*/
|
||||||
isGravatarUrl(url: string): boolean {
|
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 {
|
isLocalFileUrl(url: string): boolean {
|
||||||
const urlParts = CoreUrl.parse(url);
|
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.
|
* @return Whether the scheme belongs to a local file.
|
||||||
*/
|
*/
|
||||||
isLocalFileUrlScheme(scheme: string): boolean {
|
isLocalFileUrlScheme(scheme: string): boolean {
|
||||||
if (scheme) {
|
if (!scheme) {
|
||||||
scheme = scheme.toLowerCase();
|
return false;
|
||||||
}
|
}
|
||||||
|
scheme = scheme.toLowerCase();
|
||||||
|
|
||||||
return scheme == 'cdvfile' ||
|
return scheme == 'cdvfile' ||
|
||||||
scheme == 'file' ||
|
scheme == 'file' ||
|
||||||
|
@ -451,7 +452,7 @@ export class CoreUrlUtilsProvider {
|
||||||
* @return Whether the URL is a pluginfile URL.
|
* @return Whether the URL is a pluginfile URL.
|
||||||
*/
|
*/
|
||||||
isPluginFileUrl(url: string): boolean {
|
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.
|
* @return Whether the URL is a theme image URL.
|
||||||
*/
|
*/
|
||||||
isThemeImageUrl(url: string): boolean {
|
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 {
|
removeUrlParams(url: string): string {
|
||||||
const matches = url.match(/^[^?]+/);
|
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> {
|
async copyToClipboard(text: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await Clipboard.instance.copy(text);
|
await Clipboard.instance.copy(text);
|
||||||
|
|
||||||
// Show toast using ionicLoading.
|
|
||||||
CoreDomUtils.instance.showToast('core.copiedtoclipboard', true);
|
|
||||||
} catch {
|
} 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,
|
LoadingController as LoadingControllerService,
|
||||||
ModalController as ModalControllerService,
|
ModalController as ModalControllerService,
|
||||||
ToastController as ToastControllerService,
|
ToastController as ToastControllerService,
|
||||||
|
GestureController as GestureControllerService,
|
||||||
} from '@ionic/angular';
|
} from '@ionic/angular';
|
||||||
|
|
||||||
import { Clipboard as ClipboardService } from '@ionic-native/clipboard/ngx';
|
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 LoadingController extends makeSingleton(LoadingControllerService) {}
|
||||||
export class ModalController extends makeSingleton(ModalControllerService) {}
|
export class ModalController extends makeSingleton(ModalControllerService) {}
|
||||||
export class ToastController extends makeSingleton(ToastControllerService) {}
|
export class ToastController extends makeSingleton(ToastControllerService) {}
|
||||||
|
export class GestureController extends makeSingleton(GestureControllerService) {}
|
||||||
|
|
||||||
// Convert external libraries injectables.
|
// Convert external libraries injectables.
|
||||||
export class Translate extends makeSingleton(TranslateService) {}
|
export class Translate extends makeSingleton(TranslateService) {}
|
||||||
|
|
Loading…
Reference in New Issue