From b35ce619fbfa0b37413212d9bb152691d76ae527 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Dec 2020 14:50:54 +0100 Subject: [PATCH 1/5] MOBILE-3620 filter: Implement filter services --- src/core/classes/delegate.ts | 2 +- .../filter/services/filter-delegate.ts | 290 ++++++++++ .../features/filter/services/filter-helper.ts | 358 ++++++++++++ src/core/features/filter/services/filter.ts | 543 ++++++++++++++++++ .../services/handlers/default-filter.ts | 94 +++ 5 files changed, 1286 insertions(+), 1 deletion(-) create mode 100644 src/core/features/filter/services/filter-delegate.ts create mode 100644 src/core/features/filter/services/filter-helper.ts create mode 100644 src/core/features/filter/services/filter.ts create mode 100644 src/core/features/filter/services/handlers/default-filter.ts diff --git a/src/core/classes/delegate.ts b/src/core/classes/delegate.ts index 190391180..c9d0183b4 100644 --- a/src/core/classes/delegate.ts +++ b/src/core/classes/delegate.ts @@ -124,7 +124,7 @@ export class CoreDelegate { * @return Function returned value or default value. */ protected executeFunction(handlerName: string, fnName: string, params?: unknown[]): T | undefined { - return this.execute(this.handlers[handlerName], fnName, params); + return this.execute(this.handlers[handlerName], fnName, params); } /** diff --git a/src/core/features/filter/services/filter-delegate.ts b/src/core/features/filter/services/filter-delegate.ts new file mode 100644 index 000000000..dcd394f0b --- /dev/null +++ b/src/core/features/filter/services/filter-delegate.ts @@ -0,0 +1,290 @@ +// (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 { Injectable, ViewContainerRef } from '@angular/core'; + +import { CoreSites } from '@services/sites'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; +import { CoreFilterDefaultHandler } from './handlers/default-filter'; +import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; +import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; + +/** + * Interface that all filter handlers must implement. + */ +export interface CoreFilterHandler extends CoreDelegateHandler { + /** + * Name of the filter. It should match the "filter" field returned in core_filters_get_available_in_context. + */ + filterName: string; + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string): string | Promise; + + /** + * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was + * filtered. + * + * @param container The HTML container to handle. + * @param filter The filter. + * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return If async, promise resolved when done. + */ + handleHtml?( + container: HTMLElement, + filter: CoreFilterFilter, + options: CoreFilterFormatTextOptions, + viewContainerRef: ViewContainerRef, + component?: string, + componentId?: string | number, + siteId?: string, + ): void | Promise; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean; +} + +/** + * Delegate to register filters. + */ +@Injectable({ providedIn: 'root' }) +export class CoreFilterDelegateService extends CoreDelegate { + + protected featurePrefix = 'CoreFilterDelegate_'; + protected handlerNameProperty = 'filterName'; + + constructor(protected defaultHandler: CoreFilterDefaultHandler) { + super('CoreFilterDelegate', true); + } + + /** + * Apply a list of filters to some content. + * + * @param text The text to filter. + * @param filters Filters to apply. + * @param options Options passed to the filters. + * @param skipFilters Names of filters that shouldn't be applied. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filtered text. + */ + async filterText( + text: string, + filters?: CoreFilterFilter[], + options?: CoreFilterFormatTextOptions, + skipFilters?: string[], + siteId?: string, + ): Promise { + + // Wait for filters to be initialized. + await this.handlersInitPromise; + + const site = await CoreSites.instance.getSite(siteId); + + filters = filters || []; + options = options || {}; + + for (let i = 0; i < filters.length; i++) { + const filter = filters[i]; + if (!this.isEnabledAndShouldApply(filter, options, site, skipFilters)) { + continue; + } + + try { + const newText = await this.executeFunctionOnEnabled( + filter.filter, + 'filter', + [text, filter, options, siteId], + ); + + text = newText || text; + } catch (error) { + this.logger.error('Error applying filter' + filter.filter, error); + } + } + + // Remove tags for XHTML compatibility. + text = text.replace(/<\/?nolink>/gi, ''); + + return text; + } + + /** + * Get filters that have an enabled handler. + * + * @param contextLevel Context level of the filters. + * @param instanceId Instance ID. + * @return Filters. + */ + getEnabledFilters(contextLevel: string, instanceId: number): CoreFilterFilter[] { + const filters: CoreFilterFilter[] = []; + + for (const name in this.enabledHandlers) { + const handler = this.enabledHandlers[name]; + + filters.push({ + contextid: -1, + contextlevel: contextLevel, + filter: handler.filterName, + inheritedstate: 1, + instanceid: instanceId, + localstate: 1, + }); + } + + return filters; + } + + /** + * Let filters handle an HTML element. + * + * @param container The HTML container to handle. + * @param filters Filters to apply. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param options Options passed to the filters. + * @param skipFilters Names of filters that shouldn't be applied. + * @param component Component. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + async handleHtml( + container: HTMLElement, + filters: CoreFilterFilter[], + viewContainerRef?: ViewContainerRef, + options?: CoreFilterFormatTextOptions, + skipFilters?: string[], + component?: string, + componentId?: string | number, + siteId?: string, + ): Promise { + + // Wait for filters to be initialized. + await this.handlersInitPromise; + + const site = await CoreSites.instance.getSite(siteId); + + filters = filters || []; + options = options || {}; + + for (let i = 0; i < filters.length; i++) { + const filter = filters[i]; + if (!this.isEnabledAndShouldApply(filter, options, site, skipFilters)) { + continue; + } + + try { + await this.executeFunctionOnEnabled( + filter.filter, + 'handleHtml', + [container, filter, options, viewContainerRef, component, componentId, siteId], + ); + } catch (error) { + this.logger.error('Error handling HTML' + filter.filter, error); + } + } + } + + /** + * Check if a filter is enabled and should be applied. + * + * @param filters Filters to apply. + * @param options Options passed to the filters. + * @param site Site. + * @param skipFilters Names of filters that shouldn't be applied. + * @return Whether the filter is enabled and should be applied. + */ + isEnabledAndShouldApply( + filter: CoreFilterFilter, + options: CoreFilterFormatTextOptions, + site: CoreSite, + skipFilters?: string[], + ): boolean { + + if (filter.localstate == -1 || (filter.localstate == 0 && filter.inheritedstate == -1)) { + // Filter is disabled, ignore it. + return false; + } + + if (!this.shouldFilterBeApplied(filter, options, site)) { + // Filter shouldn't be applied. + return false; + } + + if (skipFilters && skipFilters.indexOf(filter.filter) != -1) { + // Skip this filter. + return false; + } + + return true; + } + + /** + * Check if at least 1 filter should be applied in a certain site and with certain options. + * + * @param filter Filter to check. + * @param options Options passed to the filters. + * @param site Site. If not defined, current site. + * @return Promise resolved with true: whether the filter should be applied. + */ + async shouldBeApplied(filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, site?: CoreSite): Promise { + // Wait for filters to be initialized. + await this.handlersInitPromise; + + for (let i = 0; i < filters.length; i++) { + if (this.shouldFilterBeApplied(filters[i], options, site)) { + return true; + } + } + + return false; + } + + /** + * Check whether a filter should be applied in a certain site and with certain options. + * + * @param filter Filter to check. + * @param options Options passed to the filters. + * @param site Site. If not defined, current site. + * @return Whether the filter should be applied. + */ + protected shouldFilterBeApplied(filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + if (!this.hasHandler(filter.filter, true)) { + return false; + } + + return !!(this.executeFunctionOnEnabled(filter.filter, 'shouldBeApplied', [options, site])); + } + +} + +export class CoreFilterDelegate extends makeSingleton(CoreFilterDelegateService) {} diff --git a/src/core/features/filter/services/filter-helper.ts b/src/core/features/filter/services/filter-helper.ts new file mode 100644 index 000000000..ec273ccc5 --- /dev/null +++ b/src/core/features/filter/services/filter-helper.ts @@ -0,0 +1,358 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreApp } from '@services/app'; +import { CoreSites } from '@services/sites'; +import { CoreFilterDelegate } from './filter-delegate'; +import { + CoreFilter, + CoreFilterFilter, + CoreFilterFormatTextOptions, + CoreFilterClassifiedFilters, + CoreFiltersGetAvailableInContextWSParamContext, +} from './filter'; +// import { CoreCourseProvider } from '@features/course/providers/course'; +// import { CoreCoursesProvider } from '@features/courses/providers/courses'; +import { makeSingleton } from '@singletons'; +import { CoreEvents, CoreEventSiteData } from '@singletons/events'; +import { CoreLogger } from '@singletons/logger'; +import { CoreSite } from '@classes/site'; + +/** + * Helper service to provide filter functionalities. + */ +@Injectable({ providedIn: 'root' }) +export class CoreFilterHelperProvider { + + protected logger: CoreLogger; + + /** + * When a module context is requested, we request all the modules in a course to decrease WS calls. If there are a lot of + * modules, checking the cache of all contexts can be really slow, so we use this memory cache to speed up the process. + */ + protected moduleContextsCache: { + [siteId: string]: { + [courseId: number]: { + [contextLevel: string]: { + contexts: CoreFilterClassifiedFilters; + time: number; + }; + }; + }; + } = {}; + + constructor() { + this.logger = CoreLogger.getInstance('CoreFilterHelperProvider'); + + CoreEvents.on(CoreEvents.WS_CACHE_INVALIDATED, (data: CoreEventSiteData) => { + delete this.moduleContextsCache[data.siteId || '']; + }); + + CoreEvents.on(CoreEvents.SITE_STORAGE_DELETED, (data: CoreEventSiteData) => { + delete this.moduleContextsCache[data.siteId || '']; + }); + } + + /** + * Get the contexts of all blocks in a course. + * + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the contexts. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getBlocksContexts(courseId: number, siteId?: string): Promise { + return []; + // @todo + // const blocks = await this.courseProvider.getCourseBlocks(courseId, siteId); + + // const contexts: CoreFiltersGetAvailableInContextWSParamContext[] = []; + + // blocks.forEach((block) => { + // contexts.push({ + // contextlevel: 'block', + // instanceid: block.instanceid, + // }); + // }); + + // return contexts; + } + + /** + * Get some filters from memory cache. If not in cache, get them and store them in cache. + * + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options for format text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filters. + */ + protected async getCacheableFilters( + contextLevel: string, + instanceId: number, + getFilters: () => Promise, + options: CoreFilterFormatTextOptions, + site: CoreSite, + ): Promise { + + // Check the memory cache first. + const result = this.getFromMemoryCache(options.courseId ?? -1, contextLevel, instanceId, site); + if (result) { + return result; + } + + const siteId = site.getId(); + + const contexts = await getFilters(); + + const filters = await CoreFilter.instance.getAvailableInContexts(contexts, siteId); + + this.storeInMemoryCache(options.courseId ?? -1, contextLevel, filters, siteId); + + return filters[contextLevel][instanceId] || []; + } + + /** + * If user is enrolled in the course, return contexts of all enrolled courses to decrease number of WS requests. + * + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the contexts. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getCourseContexts(courseId: number, siteId?: string): Promise { + // @todo + return []; + // const courseIds = await this.coursesProvider.getCourseIdsIfEnrolled(courseId, siteId); + + // const contexts: CoreFiltersGetAvailableInContextWSParamContext[] = []; + + // courseIds.forEach((courseId) => { + // contexts.push({ + // contextlevel: 'course', + // instanceid: courseId + // }); + // }); + + // return contexts; + } + + /** + * Get the contexts of all course modules in a course. + * + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the contexts. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getCourseModulesContexts(courseId: number, siteId?: string): Promise { + // @todo + return []; + + // const sections = await this.courseProvider.getSections(courseId, false, true, undefined, siteId); + + // const contexts: CoreFiltersGetAvailableInContextWSParamContext[] = []; + + // sections.forEach((section) => { + // if (section.modules) { + // section.modules.forEach((module) => { + // if (module.uservisible) { + // contexts.push({ + // contextlevel: 'module', + // instanceid: module.id + // }); + // } + // }); + // } + // }); + + // return contexts; + } + + /** + * Get the filters in a certain context, performing some checks like the site version. + * It's recommended to use this function instead of canGetFilters + getEnabledFilters because this function will check if + * it's really needed to call the WS. + * + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options for format text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filters. + */ + async getFilters( + contextLevel: string, + instanceId: number, + options?: CoreFilterFormatTextOptions, + siteId?: string, + ): Promise { + options = options || {}; + options.contextLevel = contextLevel; + options.instanceId = instanceId; + options.filter = false; + + try { + const site = await CoreSites.instance.getSite(siteId); + + siteId = site.getId(); + + const canGet = await CoreFilter.instance.canGetFilters(siteId); + if (!canGet) { + options.filter = true; + + // We cannot check which filters are available, apply them all. + return CoreFilterDelegate.instance.getEnabledFilters(contextLevel, instanceId); + } + + let hasFilters = true; + + if (contextLevel == 'system' || (contextLevel == 'course' && instanceId == site.getSiteHomeId())) { + // No need to check the site filters because we're requesting the same context, so we'd do the same twice. + } else { + // Check if site has any filter to treat. + hasFilters = await this.siteHasFiltersToTreat(options, siteId); + } + + if (!hasFilters) { + return []; + } + + options.filter = true; + + if (contextLevel == 'module' && options.courseId) { + // Get all the modules filters with a single call to decrease the number of WS calls. + const getFilters = this.getCourseModulesContexts.bind(this, options.courseId, siteId); + + return this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); + + } else if (contextLevel == 'course') { + // If enrolled, get all enrolled courses filters with a single call to decrease number of WS calls. + const getFilters = this.getCourseContexts.bind(this, instanceId, siteId); + + return this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); + } else if (contextLevel == 'block' && options.courseId) { // @todo && this.courseProvider.canGetCourseBlocks(site) + // Get all the course blocks filters with a single call to decrease number of WS calls. + const getFilters = this.getBlocksContexts.bind(this, options.courseId, siteId); + + return this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); + } + + return CoreFilter.instance.getAvailableInContext(contextLevel, instanceId, siteId); + } catch (error) { + this.logger.error('Error getting filters, return an empty array', error, contextLevel, instanceId); + + return []; + } + } + + /** + * Get filters and format text. + * + * @param text Text to filter. + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options for format text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the formatted text and the filters. + */ + async getFiltersAndFormatText( + text: string, + contextLevel: string, + instanceId: number, + options?: CoreFilterFormatTextOptions, + siteId?: string, + ): Promise<{text: string; filters: CoreFilterFilter[]}> { + + const filters = await this.getFilters(contextLevel, instanceId, options, siteId); + + text = await CoreFilter.instance.formatText(text, options, filters, siteId); + + return { text, filters: filters }; + } + + /** + * Get module context filters from the memory cache. + * + * @param courseId Course the module belongs to. + * @param contextLevel Context level. + * @param instanceId Instance ID. + * @param site Site. + * @return The filters, undefined if not found. + */ + protected getFromMemoryCache( + courseId: number, + contextLevel: string, + instanceId: number, + site: CoreSite, + ): CoreFilterFilter[] | undefined { + + const siteId = site.getId(); + + // Check if we have the context in the memory cache. + if (!this.moduleContextsCache[siteId]?.[courseId]?.[contextLevel]) { + return; + } + + const cachedData = this.moduleContextsCache[siteId][courseId][contextLevel]; + + if (!CoreApp.instance.isOnline() || Date.now() <= cachedData.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY)) { + // We can use cache, return the filters if found. + return cachedData.contexts[contextLevel] && cachedData.contexts[contextLevel][instanceId]; + } + } + + /** + * Check if site has available any filter that should be treated by the app. + * + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it has filters to treat. + */ + async siteHasFiltersToTreat(options?: CoreFilterFormatTextOptions, siteId?: string): Promise { + options = options || {}; + + const site = await CoreSites.instance.getSite(siteId); + + // Get filters at site level. + const filters = await CoreFilter.instance.getAvailableInContext('system', 0, site.getId()); + + return CoreFilterDelegate.instance.shouldBeApplied(filters, options, site); + } + + /** + * Store filters in the memory cache. + * + * @param contexts Filters to store, classified by contextlevel and instanceid + * @param siteId Site ID. + */ + protected storeInMemoryCache( + courseId: number, + contextLevel: string, + contexts: CoreFilterClassifiedFilters, + siteId: string, + ): void { + + this.moduleContextsCache[siteId] = this.moduleContextsCache[siteId] || {}; + this.moduleContextsCache[siteId][courseId] = this.moduleContextsCache[siteId][courseId] || {}; + this.moduleContextsCache[siteId][courseId][contextLevel] = { + contexts: contexts, + time: Date.now(), + }; + } + +} + +export class CoreFilterHelper extends makeSingleton(CoreFilterHelperProvider) {} diff --git a/src/core/features/filter/services/filter.ts b/src/core/features/filter/services/filter.ts new file mode 100644 index 000000000..fc973a02e --- /dev/null +++ b/src/core/features/filter/services/filter.ts @@ -0,0 +1,543 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreApp } from '@services/app'; +import { CoreSites } from '@services/sites'; +import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning } from '@services/ws'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreFilterDelegate } from './filter-delegate'; +import { makeSingleton } from '@singletons'; +import { CoreEvents, CoreEventSiteData } from '@singletons/events'; +import { CoreLogger } from '@singletons/logger'; + +/** + * Service to provide filter functionalities. + */ +@Injectable({ providedIn: 'root' }) +export class CoreFilterProvider { + + protected readonly ROOT_CACHE_KEY = 'mmFilter:'; + + protected logger: CoreLogger; + + /** + * Store the contexts in memory to speed up the process, it can take a lot of time otherwise. + */ + protected contextsCache: { + [siteId: string]: { + [contextlevel: string]: { + [instanceid: number]: { + filters: CoreFilterFilter[]; + time: number; + }; + }; + }; + } = {}; + + constructor() { + this.logger = CoreLogger.getInstance('CoreFilterProvider'); + + CoreEvents.on(CoreEvents.WS_CACHE_INVALIDATED, (data: CoreEventSiteData) => { + delete this.contextsCache[data.siteId || '']; + }); + + CoreEvents.on(CoreEvents.SITE_STORAGE_DELETED, (data: CoreEventSiteData) => { + delete this.contextsCache[data.siteId || '']; + }); + } + + /** + * Returns whether or not WS get available in context is available. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if ws is available, false otherwise. + * @since 3.4 + */ + async canGetAvailableInContext(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + return this.canGetAvailableInContextInSite(site); + } + + /** + * Returns whether or not WS get available in context is available in a certain site. + * + * @param site Site. If not defined, current site. + * @return Promise resolved with true if ws is available, false otherwise. + * @since 3.4 + */ + canGetAvailableInContextInSite(site?: CoreSite): boolean { + site = site || CoreSites.instance.getCurrentSite(); + + return !!(site?.wsAvailable('core_filters_get_available_in_context')); + } + + /** + * Returns whether or not we can get the available filters: the WS is available and the feature isn't disabled. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whethe can get filters. + */ + async canGetFilters(siteId?: string): Promise { + const wsAvailable = await this.canGetAvailableInContext(siteId); + const disabled = await this.checkFiltersDisabled(siteId); + + return wsAvailable && !disabled; + } + + /** + * Returns whether or not we can get the available filters: the WS is available and the feature isn't disabled. + * + * @param site Site. If not defined, current site. + * @return Promise resolved with boolean: whethe can get filters. + */ + canGetFiltersInSite(site?: CoreSite): boolean { + return this.canGetAvailableInContextInSite(site) && this.checkFiltersDisabledInSite(site); + } + + /** + * Returns whether or not checking the available filters is disabled in the site. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's disabled. + */ + async checkFiltersDisabled(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + return this.checkFiltersDisabledInSite(site); + } + + /** + * Returns whether or not checking the available filters is disabled in the site. + * + * @param site Site. If not defined, current site. + * @return Whether it's disabled. + */ + checkFiltersDisabledInSite(site?: CoreSite): boolean { + site = site || CoreSites.instance.getCurrentSite(); + + return !!(site?.isFeatureDisabled('CoreFilterDelegate')); + } + + /** + * Classify a list of filters into each context. + * + * @param contexts List of contexts. + * @param filters List of filters. + * @param hadSystemContext Whether the list of contexts originally had system context. + * @param hadSiteHomeContext Whether the list of contexts originally had site home context. + * @param site Site instance. + * @return Classified filters. + */ + protected classifyFilters( + contexts: CoreFiltersGetAvailableInContextWSParamContext[], + filters: CoreFilterFilter[], + hadSystemContext: boolean, + hadSiteHomeContext: boolean, + site: CoreSite, + ): CoreFilterClassifiedFilters { + const classified: CoreFilterClassifiedFilters = {}; + + // Initialize all contexts. + contexts.forEach((context) => { + classified[context.contextlevel] = classified[context.contextlevel] || {}; + classified[context.contextlevel][context.instanceid] = []; + }); + + if (contexts.length == 1 && !hadSystemContext) { + // Only 1 context, no need to iterate over the filters. + classified[contexts[0].contextlevel][contexts[0].instanceid] = filters; + + return classified; + } + + filters.forEach((filter) => { + if (hadSystemContext && filter.contextlevel == 'course' && filter.instanceid == site.getSiteHomeId()) { + if (hadSiteHomeContext) { + // We need to return both site home and system. Add site home first. + classified[filter.contextlevel][filter.instanceid].push(filter); + + // Now copy the object so it can be modified. + filter = Object.assign({}, filter); + } + + // Simulate the system context based on the inherited data. + filter.contextlevel = 'system'; + filter.instanceid = 0; + filter.contextid = -1; + filter.localstate = filter.inheritedstate; + } + + classified[filter.contextlevel][filter.instanceid].push(filter); + }); + + return classified; + } + + /** + * Given some HTML code, this function returns the text as safe HTML. + * + * @param text The text to be formatted. + * @param options Formatting options. + * @param filters The filters to apply. Required if filter is set to true. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the formatted text. + */ + async formatText( + text: string, + options?: CoreFilterFormatTextOptions, + filters?: CoreFilterFilter[], + siteId?: string, + ): Promise { + + if (!text || typeof text != 'string') { + // No need to do any filters and cleaning. + return ''; + } + + // Clone object if needed so we can modify it. + options = options ? Object.assign({}, options) : {}; + + if (typeof options.clean == 'undefined') { + options.clean = false; + } + + if (typeof options.filter == 'undefined') { + options.filter = true; + } + + if (!options.contextLevel) { + options.filter = false; + } + + if (options.filter) { + text = await CoreFilterDelegate.instance.filterText(text, filters, options, [], siteId); + } + + if (options.clean) { + text = CoreTextUtils.instance.cleanTags(text, options.singleLine); + } + + if (options.shortenLength && options.shortenLength > 0) { + text = CoreTextUtils.instance.shortenText(text, options.shortenLength); + } + + if (options.highlight) { + text = CoreTextUtils.instance.highlightText(text, options.highlight); + } + + return text; + } + + /** + * Get cache key for available in contexts WS calls. + * + * @param contexts The contexts to check. + * @return Cache key. + */ + protected getAvailableInContextsCacheKey(contexts: CoreFiltersGetAvailableInContextWSParamContext[]): string { + return this.getAvailableInContextsPrefixCacheKey() + JSON.stringify(contexts); + } + + /** + * Get prefixed cache key for available in contexts WS calls. + * + * @return Cache key. + */ + protected getAvailableInContextsPrefixCacheKey(): string { + return this.ROOT_CACHE_KEY + 'availableInContexts:'; + } + + /** + * Get the filters available in several contexts. + * + * @param contexts The contexts to check. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filters classified by context. + */ + async getAvailableInContexts( + contexts: CoreFiltersGetAvailableInContextWSParamContext[], + siteId?: string, + ): Promise { + + const site = await CoreSites.instance.getSite(siteId); + + siteId = site.getId(); + + const cacheResult = this.getFromMemoryCache(contexts, site); + + if (cacheResult) { + return cacheResult; + } + + const contextsToSend = contexts.slice(); // Copy the contexts array to be able to modify it. + + const { hadSystemContext, hadSiteHomeContext } = this.replaceSystemContext(contextsToSend, site); + + const data: CoreFiltersGetAvailableInContextWSParams = { + contexts: contextsToSend, + }; + const preSets = { + cacheKey: this.getAvailableInContextsCacheKey(contextsToSend), + updateFrequency: CoreSite.FREQUENCY_RARELY, + splitRequest: { + param: 'contexts', + maxLength: 300, + }, + }; + + const result = await site.read( + 'core_filters_get_available_in_context', + data, + preSets, + ); + + const classified = this.classifyFilters(contexts, result.filters, hadSystemContext, hadSiteHomeContext, site); + + this.storeInMemoryCache(classified, siteId); + + return classified; + } + + /** + * Get the filters available in a certain context. + * + * @param contextLevel The context level to check: system, user, coursecat, course, module, block, ... + * @param instanceId The instance ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filters. + */ + async getAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { + const result = await this.getAvailableInContexts([{ contextlevel: contextLevel, instanceid: instanceId }], siteId); + + return result[contextLevel][instanceId] || []; + } + + /** + * Get contexts filters from the memory cache. + * + * @param contexts Contexts to get. + * @param site Site. + * @return The filters classified by context and instance. + */ + protected getFromMemoryCache( + contexts: CoreFiltersGetAvailableInContextWSParamContext[], + site: CoreSite, + ): CoreFilterClassifiedFilters | undefined { + + if (!this.contextsCache[site.getId()]) { + return; + } + + // Check if we have the contexts in the memory cache. + const siteContexts = this.contextsCache[site.getId()]; + const isOnline = CoreApp.instance.isOnline(); + const result: CoreFilterClassifiedFilters = {}; + let allFound = true; + + for (let i = 0; i < contexts.length; i++) { + const context = contexts[i]; + const cachedCtxt = siteContexts[context.contextlevel]?.[context.instanceid]; + + // Check the context isn't "expired". The time stored in this cache will not match the one in the site cache. + if (cachedCtxt && (!isOnline || + Date.now() <= cachedCtxt.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY))) { + + result[context.contextlevel] = result[context.contextlevel] || {}; + result[context.contextlevel][context.instanceid] = cachedCtxt.filters; + } else { + allFound = false; + break; + } + } + + if (allFound) { + return result; + } + } + + /** + * Invalidates all available in context WS calls. + * + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + async invalidateAllAvailableInContext(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + await site.invalidateWsCacheForKeyStartingWith(this.getAvailableInContextsPrefixCacheKey()); + } + + /** + * Invalidates available in context WS call. + * + * @param contexts The contexts to check. + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + async invalidateAvailableInContexts( + contexts: CoreFiltersGetAvailableInContextWSParamContext[], + siteId?: string, + ): Promise { + const site = await CoreSites.instance.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getAvailableInContextsCacheKey(contexts)); + } + + /** + * Invalidates available in context WS call. + * + * @param contextLevel The context level to check. + * @param instanceId The instance ID. + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + async invalidateAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { + await this.invalidateAvailableInContexts([{ contextlevel: contextLevel, instanceid: instanceId }], siteId); + } + + /** + * Given a list of context to send to core_filters_get_available_in_context, search if the system context is in the list + * and, if so, replace it with a workaround. + * + * @param contexts The contexts to check. + * @param site Site instance. + * @return Whether the filters had system context and whether they had the site home context. + */ + protected replaceSystemContext( + contexts: CoreFiltersGetAvailableInContextWSParamContext[], + site: CoreSite, + ): { hadSystemContext: boolean; hadSiteHomeContext: boolean } { + const result = { + hadSystemContext: false, + hadSiteHomeContext: false, + }; + + // Check if any of the contexts is "system". We cannot use system context, so we'll have to use a wrokaround. + for (let i = 0; i < contexts.length; i++) { + const context = contexts[i]; + + if (context.contextlevel != 'system') { + continue; + } + + result.hadSystemContext = true; + + // Use course site home instead. Check if it's already in the list. + result.hadSiteHomeContext = contexts.some((context) => + context.contextlevel == 'course' && context.instanceid == site.getSiteHomeId()); + + if (result.hadSiteHomeContext) { + // Site home is already in list, remove this context from the list. + contexts.splice(i, 1); + } else { + // Site home not in list, use it instead of system. + contexts[i] = { + contextlevel: 'course', + instanceid: site.getSiteHomeId(), + }; + } + + break; + } + + return result; + } + + /** + * Store filters in the memory cache. + * + * @param filters Filters to store, classified by contextlevel and instanceid + * @param siteId Site ID. + */ + protected storeInMemoryCache(filters: CoreFilterClassifiedFilters, siteId: string): void { + this.contextsCache[siteId] = this.contextsCache[siteId] || {}; + + for (const contextLevel in filters) { + this.contextsCache[siteId][contextLevel] = this.contextsCache[siteId][contextLevel] || {}; + + for (const instanceId in filters[contextLevel]) { + this.contextsCache[siteId][contextLevel][instanceId] = { + filters: filters[contextLevel][instanceId], + time: Date.now(), + }; + } + } + } + +} + +export class CoreFilter extends makeSingleton(CoreFilterProvider) {} + +/** + * Params of core_filters_get_available_in_context WS. + */ +export type CoreFiltersGetAvailableInContextWSParams = { + contexts: CoreFiltersGetAvailableInContextWSParamContext[]; // The list of contexts to check. +}; + +/** + * Data about a context sent to core_filters_get_available_in_context. + */ +export type CoreFiltersGetAvailableInContextWSParamContext = { + contextlevel: string; // The context level where the filters are: (coursecat, course, module). + instanceid: number; // The instance id of item associated with the context. +}; + +/** + * Filter object returned by core_filters_get_available_in_context. + */ +export type CoreFilterFilter = { + contextlevel: string; // The context level where the filters are: (coursecat, course, module). + instanceid: number; // The instance id of item associated with the context. + contextid: number; // The context id. + filter: string; // Filter plugin name. + localstate: number; // Filter state: 1 for on, -1 for off, 0 if inherit. + inheritedstate: number; // 1 or 0 to use when localstate is set to inherit. +}; + +/** + * Result of core_filters_get_available_in_context. + */ +export type CoreFilterGetAvailableInContextResult = { + filters: CoreFilterFilter[]; // Available filters. + warnings: CoreWSExternalWarning[]; // List of warnings. +}; + +/** + * Options that can be passed to format text. + */ +export type CoreFilterFormatTextOptions = { + contextLevel?: string; // The context level where the text is. + instanceId?: number; // The instance id related to the context. + clean?: boolean; // If true all HTML will be removed. Default false. + filter?: boolean; // If true the string will be run through applicable filters as well. Default true. + singleLine?: boolean; // If true then new lines will be removed (all the text in a single line). + shortenLength?: number; // Number of characters to shorten the text. + highlight?: string; // Text to highlight. + wsNotFiltered?: boolean; // If true it means the WS didn't filter the text for some reason. + courseId?: number; // Course ID the text belongs to. It can be used to improve performance. +}; + +/** + * Filters classified by context and instance. + */ +export type CoreFilterClassifiedFilters = { + [contextlevel: string]: { + [instanceid: number]: CoreFilterFilter[]; + }; +}; diff --git a/src/core/features/filter/services/handlers/default-filter.ts b/src/core/features/filter/services/handlers/default-filter.ts new file mode 100644 index 000000000..dbee116d2 --- /dev/null +++ b/src/core/features/filter/services/handlers/default-filter.ts @@ -0,0 +1,94 @@ +// (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 { Injectable, ViewContainerRef } from '@angular/core'; + +import { CoreFilterHandler } from '../filter-delegate'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '../filter'; +import { CoreSite } from '@classes/site'; + +/** + * Default handler used when the module doesn't have a specific implementation. + */ +@Injectable({ providedIn: 'root' }) +export class CoreFilterDefaultHandler implements CoreFilterHandler { + + name = 'CoreFilterDefaultHandler'; + filterName = 'default'; + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter( + text: string, + filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars + options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): string | Promise { + return text; + } + + /** + * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was + * filtered. + * + * @param container The HTML container to handle. + * @param filter The filter. + * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return If async, promise resolved when done. + */ + handleHtml( + container: HTMLElement, // eslint-disable-line @typescript-eslint/no-unused-vars + filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars + options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars + viewContainerRef: ViewContainerRef, // eslint-disable-line @typescript-eslint/no-unused-vars + component?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + componentId?: string | number, // eslint-disable-line @typescript-eslint/no-unused-vars + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): void | Promise { + // To be overridden. + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return true; + } + +} From 487933cdc7cf8787551a75e1e3ef3acf4da625b6 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Dec 2020 15:10:02 +0100 Subject: [PATCH 2/5] MOBILE-3620 filter: Add filters to format-text --- src/core/directives/format-text.ts | 57 +++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index 9cf85dc6e..53574a3e5 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -12,7 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional } from '@angular/core'; +import { + Directive, + ElementRef, + Input, + Output, + EventEmitter, + OnChanges, + SimpleChange, + Optional, + ViewContainerRef, +} from '@angular/core'; import { NavController, IonContent } from '@ionic/angular'; import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events'; @@ -25,6 +35,9 @@ import { CoreSite } from '@classes/site'; import { Translate } from '@singletons'; import { CoreExternalContentDirective } from './external-content'; import { CoreLinkDirective } from './link'; +import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { CoreFilterHelper } from '@features/filter/services/filter-helper'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -73,6 +86,7 @@ export class CoreFormatTextDirective implements OnChanges { element: ElementRef, @Optional() protected navCtrl: NavController, @Optional() protected content: IonContent, + protected viewContainerRef: ViewContainerRef, ) { this.element = element.nativeElement; @@ -371,8 +385,17 @@ export class CoreFormatTextDirective implements OnChanges { } if (result.options.filter) { - // Let filters hnadle HTML. We do it here because we don't want them to block the render of the text. - // @todo + // Let filters handle HTML. We do it here because we don't want them to block the render of the text. + CoreFilterDelegate.instance.handleHtml( + this.element, + result.filters, + this.viewContainerRef, + result.options, + [], + this.component, + this.componentId, + result.siteId, + ); } this.element.classList.remove('core-disable-media-adapt'); @@ -388,6 +411,8 @@ export class CoreFormatTextDirective implements OnChanges { // Retrieve the site since it might be needed later. const site = await CoreUtils.instance.ignoreErrors(CoreSites.instance.getSite(this.siteId)); + const siteId = site?.getId(); + if (site && this.contextLevel == 'course' && this.contextInstanceId !== undefined && this.contextInstanceId <= 0) { this.contextInstanceId = site.getSiteHomeId(); } @@ -395,7 +420,7 @@ export class CoreFormatTextDirective implements OnChanges { const filter = typeof this.filter == 'undefined' ? !!(this.contextLevel && typeof this.contextInstanceId != 'undefined') : CoreUtils.instance.isTrueOrOne(this.filter); - const options = { + const options: CoreFilterFormatTextOptions = { clean: CoreUtils.instance.isTrueOrOne(this.clean), singleLine: CoreUtils.instance.isTrueOrOne(this.singleLine), highlight: this.highlight, @@ -404,13 +429,21 @@ export class CoreFormatTextDirective implements OnChanges { }; let formatted: string; + let filters: CoreFilterFilter[] = []; if (filter) { - // @todo - formatted = this.text!; + const filterResult = await CoreFilterHelper.instance.getFiltersAndFormatText( + this.text || '', + this.contextLevel || '', + this.contextInstanceId ?? -1, + options, + siteId, + ); + + filters = filterResult.filters; + formatted = filterResult.text; } else { - // @todo - formatted = this.text!; + formatted = await CoreFilter.instance.formatText(this.text || '', options, [], siteId); } formatted = this.treatWindowOpen(formatted); @@ -423,9 +456,9 @@ export class CoreFormatTextDirective implements OnChanges { return { div, - filters: [], + filters, options, - siteId: site?.getId(), + siteId, }; } @@ -747,7 +780,7 @@ export class CoreFormatTextDirective implements OnChanges { type FormatContentsResult = { div: HTMLElement; - filters: any[]; - options: any; + filters: CoreFilterFilter[]; + options: CoreFilterFormatTextOptions; siteId?: string; }; From aa60d8eaeb087829fac714564b04db63b4620229 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 9 Dec 2020 12:59:45 +0100 Subject: [PATCH 3/5] MOBILE-3620 filter: Implement filters addons --- src/addons/addons.module.ts | 2 + .../activitynames/activitynames.module.ts | 34 ++ .../services/handlers/activitynames.ts | 43 ++ src/addons/filter/algebra/algebra.module.ts | 34 ++ .../algebra/services/handlers/algebra.ts | 43 ++ src/addons/filter/censor/censor.module.ts | 34 ++ .../filter/censor/services/handlers/censor.ts | 43 ++ src/addons/filter/data/data.module.ts | 34 ++ .../filter/data/services/handlers/data.ts | 43 ++ .../filter/displayh5p/displayh5p.module.ts | 34 ++ .../services/handlers/displayh5p.ts | 110 +++++ .../emailprotect/emailprotect.module.ts | 34 ++ .../services/handlers/emailprotect.ts | 43 ++ src/addons/filter/emoticon/emoticon.module.ts | 34 ++ .../emoticon/services/handlers/emoticon.ts | 43 ++ src/addons/filter/filter.module.ts | 53 +++ src/addons/filter/glossary/glossary.module.ts | 34 ++ .../glossary/services/handlers/glossary.ts | 43 ++ .../mathjaxloader/mathjaxloader.module.ts | 38 ++ .../services/handlers/mathjaxloader.ts | 410 ++++++++++++++++++ .../filter/mediaplugin/mediaplugin.module.ts | 34 ++ .../services/handlers/mediaplugin.ts | 98 +++++ .../filter/multilang/multilang.module.ts | 34 ++ .../multilang/services/handlers/multilang.ts | 84 ++++ .../filter/tex/services/handlers/tex.ts | 43 ++ src/addons/filter/tex/tex.module.ts | 34 ++ .../filter/tidy/services/handlers/tidy.ts | 43 ++ src/addons/filter/tidy/tidy.module.ts | 34 ++ .../urltolink/services/handlers/urltolink.ts | 43 ++ .../filter/urltolink/urltolink.module.ts | 34 ++ src/core/services/utils/url.ts | 2 +- 31 files changed, 1668 insertions(+), 1 deletion(-) create mode 100644 src/addons/filter/activitynames/activitynames.module.ts create mode 100644 src/addons/filter/activitynames/services/handlers/activitynames.ts create mode 100644 src/addons/filter/algebra/algebra.module.ts create mode 100644 src/addons/filter/algebra/services/handlers/algebra.ts create mode 100644 src/addons/filter/censor/censor.module.ts create mode 100644 src/addons/filter/censor/services/handlers/censor.ts create mode 100644 src/addons/filter/data/data.module.ts create mode 100644 src/addons/filter/data/services/handlers/data.ts create mode 100644 src/addons/filter/displayh5p/displayh5p.module.ts create mode 100644 src/addons/filter/displayh5p/services/handlers/displayh5p.ts create mode 100644 src/addons/filter/emailprotect/emailprotect.module.ts create mode 100644 src/addons/filter/emailprotect/services/handlers/emailprotect.ts create mode 100644 src/addons/filter/emoticon/emoticon.module.ts create mode 100644 src/addons/filter/emoticon/services/handlers/emoticon.ts create mode 100644 src/addons/filter/filter.module.ts create mode 100644 src/addons/filter/glossary/glossary.module.ts create mode 100644 src/addons/filter/glossary/services/handlers/glossary.ts create mode 100644 src/addons/filter/mathjaxloader/mathjaxloader.module.ts create mode 100644 src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts create mode 100644 src/addons/filter/mediaplugin/mediaplugin.module.ts create mode 100644 src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts create mode 100644 src/addons/filter/multilang/multilang.module.ts create mode 100644 src/addons/filter/multilang/services/handlers/multilang.ts create mode 100644 src/addons/filter/tex/services/handlers/tex.ts create mode 100644 src/addons/filter/tex/tex.module.ts create mode 100644 src/addons/filter/tidy/services/handlers/tidy.ts create mode 100644 src/addons/filter/tidy/tidy.module.ts create mode 100644 src/addons/filter/urltolink/services/handlers/urltolink.ts create mode 100644 src/addons/filter/urltolink/urltolink.module.ts diff --git a/src/addons/addons.module.ts b/src/addons/addons.module.ts index f278d5c27..66777ebfa 100644 --- a/src/addons/addons.module.ts +++ b/src/addons/addons.module.ts @@ -15,10 +15,12 @@ import { NgModule } from '@angular/core'; import { AddonPrivateFilesModule } from './privatefiles/privatefiles.module'; +import { AddonFilterModule } from './filter/filter.module'; @NgModule({ imports: [ AddonPrivateFilesModule, + AddonFilterModule, ], }) export class AddonsModule {} diff --git a/src/addons/filter/activitynames/activitynames.module.ts b/src/addons/filter/activitynames/activitynames.module.ts new file mode 100644 index 000000000..7971ef7bd --- /dev/null +++ b/src/addons/filter/activitynames/activitynames.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterActivityNamesHandler } from './services/handlers/activitynames'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterActivityNamesHandler], + useFactory: (handler: AddonFilterActivityNamesHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterActivityNamesModule {} diff --git a/src/addons/filter/activitynames/services/handlers/activitynames.ts b/src/addons/filter/activitynames/services/handlers/activitynames.ts new file mode 100644 index 000000000..4fbcbeea4 --- /dev/null +++ b/src/addons/filter/activitynames/services/handlers/activitynames.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Activity names filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterActivityNamesHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterActivityNamesHandler'; + filterName = 'activitynames'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/algebra/algebra.module.ts b/src/addons/filter/algebra/algebra.module.ts new file mode 100644 index 000000000..b2b231fba --- /dev/null +++ b/src/addons/filter/algebra/algebra.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterAlgebraHandler } from './services/handlers/algebra'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterAlgebraHandler], + useFactory: (handler: AddonFilterAlgebraHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterAlgebraModule {} diff --git a/src/addons/filter/algebra/services/handlers/algebra.ts b/src/addons/filter/algebra/services/handlers/algebra.ts new file mode 100644 index 000000000..656737d3f --- /dev/null +++ b/src/addons/filter/algebra/services/handlers/algebra.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Algebra notation filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterAlgebraHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterAlgebraHandler'; + filterName = 'algebra'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/censor/censor.module.ts b/src/addons/filter/censor/censor.module.ts new file mode 100644 index 000000000..fb07088a8 --- /dev/null +++ b/src/addons/filter/censor/censor.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterCensorHandler } from './services/handlers/censor'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterCensorHandler], + useFactory: (handler: AddonFilterCensorHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterCensorModule {} diff --git a/src/addons/filter/censor/services/handlers/censor.ts b/src/addons/filter/censor/services/handlers/censor.ts new file mode 100644 index 000000000..1e8225047 --- /dev/null +++ b/src/addons/filter/censor/services/handlers/censor.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Word censorship filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterCensorHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterCensorHandler'; + filterName = 'censor'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/data/data.module.ts b/src/addons/filter/data/data.module.ts new file mode 100644 index 000000000..716948cb5 --- /dev/null +++ b/src/addons/filter/data/data.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterDataHandler } from './services/handlers/data'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterDataHandler], + useFactory: (handler: AddonFilterDataHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterDataModule {} diff --git a/src/addons/filter/data/services/handlers/data.ts b/src/addons/filter/data/services/handlers/data.ts new file mode 100644 index 000000000..fc4b24c0f --- /dev/null +++ b/src/addons/filter/data/services/handlers/data.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Database auto-link filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterDataHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterDataHandler'; + filterName = 'data'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/displayh5p/displayh5p.module.ts b/src/addons/filter/displayh5p/displayh5p.module.ts new file mode 100644 index 000000000..1145b1362 --- /dev/null +++ b/src/addons/filter/displayh5p/displayh5p.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterDisplayH5PHandler } from './services/handlers/displayh5p'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterDisplayH5PHandler], + useFactory: (handler: AddonFilterDisplayH5PHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterDisplayH5PModule {} diff --git a/src/addons/filter/displayh5p/services/handlers/displayh5p.ts b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts new file mode 100644 index 000000000..da70664d8 --- /dev/null +++ b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts @@ -0,0 +1,110 @@ +// (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 { Injectable, ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +// @todo import { CoreH5PPlayerComponent } from '@core/h5p/components/h5p-player/h5p-player'; + +/** + * Handler to support the Display H5P filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterDisplayH5PHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterDisplayH5PHandler'; + filterName = 'displayh5p'; + + protected template = document.createElement('template'); // A template element to convert HTML to element. + + constructor(protected factoryResolver: ComponentFactoryResolver) { + super(); + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter( + text: string, + filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars + options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): string | Promise { + this.template.innerHTML = text; + + const h5pIframes = Array.from(this.template.content.querySelectorAll('iframe.h5p-iframe')); + + // Replace all iframes with an empty div that will be treated in handleHtml. + h5pIframes.forEach((iframe) => { + const placeholder = document.createElement('div'); + + placeholder.classList.add('core-h5p-tmp-placeholder'); + placeholder.setAttribute('data-player-src', iframe.src); + + iframe.parentElement?.replaceChild(placeholder, iframe); + }); + + return this.template.innerHTML; + } + + /** + * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was + * filtered. + * + * @param container The HTML container to handle. + * @param filter The filter. + * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return If async, promise resolved when done. + */ + handleHtml( + container: HTMLElement, // eslint-disable-line @typescript-eslint/no-unused-vars + filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars + options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars + viewContainerRef: ViewContainerRef, // eslint-disable-line @typescript-eslint/no-unused-vars + component?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + componentId?: string | number, // eslint-disable-line @typescript-eslint/no-unused-vars + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): void | Promise { + // @todo + + // const placeholders = Array.from(container.querySelectorAll('div.core-h5p-tmp-placeholder')); + + // placeholders.forEach((placeholder) => { + // const url = placeholder.getAttribute('data-player-src'); + + // Create the component to display the player. + // const factory = this.factoryResolver.resolveComponentFactory(CoreH5PPlayerComponent); + // const componentRef = viewContainerRef.createComponent(factory); + + // componentRef.instance.src = url; + // componentRef.instance.component = component; + // componentRef.instance.componentId = componentId; + + // // Move the component to its right position. + // placeholder.parentElement?.replaceChild(componentRef.instance.elementRef.nativeElement, placeholder); + // }); + } + +} diff --git a/src/addons/filter/emailprotect/emailprotect.module.ts b/src/addons/filter/emailprotect/emailprotect.module.ts new file mode 100644 index 000000000..8e1fa7981 --- /dev/null +++ b/src/addons/filter/emailprotect/emailprotect.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterEmailProtectHandler } from './services/handlers/emailprotect'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterEmailProtectHandler], + useFactory: (handler: AddonFilterEmailProtectHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterEmailProtectModule {} diff --git a/src/addons/filter/emailprotect/services/handlers/emailprotect.ts b/src/addons/filter/emailprotect/services/handlers/emailprotect.ts new file mode 100644 index 000000000..301e2d335 --- /dev/null +++ b/src/addons/filter/emailprotect/services/handlers/emailprotect.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Email protection filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterEmailProtectHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterEmailProtectHandler'; + filterName = 'emailprotect'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/emoticon/emoticon.module.ts b/src/addons/filter/emoticon/emoticon.module.ts new file mode 100644 index 000000000..821b56a49 --- /dev/null +++ b/src/addons/filter/emoticon/emoticon.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterEmoticonHandler } from './services/handlers/emoticon'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterEmoticonHandler], + useFactory: (handler: AddonFilterEmoticonHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterEmoticonModule {} diff --git a/src/addons/filter/emoticon/services/handlers/emoticon.ts b/src/addons/filter/emoticon/services/handlers/emoticon.ts new file mode 100644 index 000000000..128502222 --- /dev/null +++ b/src/addons/filter/emoticon/services/handlers/emoticon.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Emoticon filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterEmoticonHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterEmoticonHandler'; + filterName = 'emoticon'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/filter.module.ts b/src/addons/filter/filter.module.ts new file mode 100644 index 000000000..8053a2522 --- /dev/null +++ b/src/addons/filter/filter.module.ts @@ -0,0 +1,53 @@ +// (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 { AddonFilterActivityNamesModule } from './activitynames/activitynames.module'; +import { AddonFilterAlgebraModule } from './algebra/algebra.module'; +import { AddonFilterCensorModule } from './censor/censor.module'; +import { AddonFilterDataModule } from './data/data.module'; +import { AddonFilterDisplayH5PModule } from './displayh5p/displayh5p.module'; +import { AddonFilterEmailProtectModule } from './emailprotect/emailprotect.module'; +import { AddonFilterEmoticonModule } from './emoticon/emoticon.module'; +import { AddonFilterGlossaryModule } from './glossary/glossary.module'; +import { AddonFilterMathJaxLoaderModule } from './mathjaxloader/mathjaxloader.module'; +import { AddonFilterMediaPluginModule } from './mediaplugin/mediaplugin.module'; +import { AddonFilterMultilangModule } from './multilang/multilang.module'; +import { AddonFilterTexModule } from './tex/tex.module'; +import { AddonFilterTidyModule } from './tidy/tidy.module'; +import { AddonFilterUrlToLinkModule } from './urltolink/urltolink.module'; + +@NgModule({ + declarations: [], + imports: [ + AddonFilterActivityNamesModule, + AddonFilterAlgebraModule, + AddonFilterCensorModule, + AddonFilterDataModule, + AddonFilterDisplayH5PModule, + AddonFilterEmailProtectModule, + AddonFilterEmoticonModule, + AddonFilterGlossaryModule, + AddonFilterMathJaxLoaderModule, + AddonFilterMediaPluginModule, + AddonFilterMultilangModule, + AddonFilterTexModule, + AddonFilterTidyModule, + AddonFilterUrlToLinkModule, + ], + providers: [], + exports: [], +}) +export class AddonFilterModule { } diff --git a/src/addons/filter/glossary/glossary.module.ts b/src/addons/filter/glossary/glossary.module.ts new file mode 100644 index 000000000..225403e35 --- /dev/null +++ b/src/addons/filter/glossary/glossary.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterGlossaryHandler } from './services/handlers/glossary'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterGlossaryHandler], + useFactory: (handler: AddonFilterGlossaryHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterGlossaryModule {} diff --git a/src/addons/filter/glossary/services/handlers/glossary.ts b/src/addons/filter/glossary/services/handlers/glossary.ts new file mode 100644 index 000000000..6b6abd4ee --- /dev/null +++ b/src/addons/filter/glossary/services/handlers/glossary.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Glossary auto-link filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterGlossaryHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterGlossaryHandler'; + filterName = 'glossary'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/mathjaxloader/mathjaxloader.module.ts b/src/addons/filter/mathjaxloader/mathjaxloader.module.ts new file mode 100644 index 000000000..bb5ded4e5 --- /dev/null +++ b/src/addons/filter/mathjaxloader/mathjaxloader.module.ts @@ -0,0 +1,38 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterMathJaxLoaderHandler } from './services/handlers/mathjaxloader'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterMathJaxLoaderHandler], + useFactory: (handler: AddonFilterMathJaxLoaderHandler) => async () => { + CoreFilterDelegate.instance.registerHandler(handler); + + await handler.initialize(); + }, + }, + ], +}) +export class AddonFilterMathJaxLoaderModule {} diff --git a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts new file mode 100644 index 000000000..94e58f341 --- /dev/null +++ b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts @@ -0,0 +1,410 @@ +// (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 { Injectable, ViewContainerRef } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreLang } from '@services/lang'; +import { CoreSites } from '@services/sites'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreEvents } from '@singletons/events'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the MathJax filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterMathJaxLoaderHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterMathJaxLoaderHandler'; + filterName = 'mathjaxloader'; + + // Default values for MathJax config for sites where we cannot retrieve it. + protected readonly DEFAULT_URL = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js'; + protected readonly DEFAULT_CONFIG = ` + MathJax.Hub.Config({ + extensions: [ + "Safe.js", + "tex2jax.js", + "mml2jax.js", + "MathEvents.js", + "MathZoom.js", + "MathMenu.js", + "toMathML.js", + "TeX/noErrors.js", + "TeX/noUndefined.js", + "TeX/AMSmath.js", + "TeX/AMSsymbols.js", + "fast-preview.js", + "AssistiveMML.js", + "[a11y]/accessibility-menu.js" + ], + jax: ["input/TeX","input/MathML","output/SVG"], + showMathMenu: false, + errorSettings: { message: ["!"] }, + skipStartupTypeset: true, + messageStyle: "none" + }); + `; + + // List of language codes found in the MathJax/localization/ directory. + protected readonly MATHJAX_LANG_CODES = [ + 'ar', 'ast', 'bcc', 'bg', 'br', 'ca', 'cdo', 'ce', 'cs', 'cy', 'da', 'de', 'diq', 'en', 'eo', 'es', 'fa', + 'fi', 'fr', 'gl', 'he', 'ia', 'it', 'ja', 'kn', 'ko', 'lb', 'lki', 'lt', 'mk', 'nl', 'oc', 'pl', 'pt', + 'pt-br', 'qqq', 'ru', 'scn', 'sco', 'sk', 'sl', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-hans', 'zh-hant', + ]; + + // List of explicit mappings and known exceptions (moodle => mathjax). + protected readonly EXPLICIT_MAPPING = { + 'zh-tw': 'zh-hant', + 'zh-cn': 'zh-hans', + }; + + protected window: MathJaxWindow = window; + + /** + * Initialize MathJax. + * + * @return Promise resolved when done. + */ + async initialize(): Promise { + this.loadJS(); + + // Update MathJax locale if app language changes. + CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, (lang: string) => { + if (typeof this.window.MathJax == 'undefined') { + return; + } + + this.window.MathJax.Hub.Queue(() => { + this.window.MathJax.Localization.setLocale(this.mapLanguageCode(lang)); + }); + }); + + // Get the current language. + const lang = await CoreLang.instance.getCurrentLanguage(); + + // Now call the configure function. + this.window.M!.filter_mathjaxloader!.configure({ + mathjaxconfig: this.DEFAULT_CONFIG, + lang: this.mapLanguageCode(lang), + }); + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + async filter( + text: string, + filter: CoreFilterFilter, + options: CoreFilterFormatTextOptions, + siteId?: string, + ): Promise { + + const site = await CoreSites.instance.getSite(siteId); + + // Don't apply this filter if Moodle is 3.7 or higher and the WS already filtered the content. + if (!options.wsNotFiltered && site.isVersionGreaterEqualThan('3.7')) { + return text; + } + + if (text.indexOf('class="filter_mathjaxloader_equation"') != -1) { + // The content seems to have treated mathjax already, don't do it. + return text; + } + + // We cannot get the filter settings, so we cannot know if it can be used as a replacement for the TeX filter. + // Assume it cannot (default value). + let hasDisplayOrInline = false; + if (text.match(/\\[[(]/) || text.match(/\$\$/)) { + // Only parse the text if there are mathjax symbols in it. + // The recognized math environments are \[ \] and $$ $$ for display mathematics and \( \) for inline mathematics. + // Wrap display and inline math environments in nolink spans. + const result = this.wrapMathInNoLink(text); + text = result.text; + hasDisplayOrInline = result.changed; + } + + if (hasDisplayOrInline) { + return '' + text + ''; + } + + return text; + } + + /** + * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was + * filtered. + * + * @param container The HTML container to handle. + * @param filter The filter. + * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return If async, promise resolved when done. + */ + async handleHtml( + container: HTMLElement, + filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars + options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars + viewContainerRef: ViewContainerRef, // eslint-disable-line @typescript-eslint/no-unused-vars + component?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + componentId?: string | number, // eslint-disable-line @typescript-eslint/no-unused-vars + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): Promise { + await this.waitForReady(); + + this.window.M!.filter_mathjaxloader!.typeset(container); + } + + /** + * Wrap a portion of the $text inside a no link span. The whole text is then returned. + * + * @param text The text to modify. + * @param start The start index of the substring in text that should be wrapped in the span. + * @param end The end index of the substring in text that should be wrapped in the span. + * @return The whole text with the span inserted around the defined substring. + */ + protected insertSpan(text: string, start: number, end: number): string { + return CoreTextUtils.instance.substrReplace( + text, + '' + text.substr(start, end - start + 1) + '', + start, + end - start + 1, + ); + } + + /** + * Load the JS to make MathJax work in the app. The JS loaded is extracted from Moodle filter's loader JS file. + */ + protected loadJS(): void { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const that = this; + + this.window.M = this.window.M || {}; + this.window.M.filter_mathjaxloader = this.window.M.filter_mathjaxloader || { + _lang: '', // eslint-disable-line @typescript-eslint/naming-convention + _configured: false, // eslint-disable-line @typescript-eslint/naming-convention + // Add the configuration to the head and set the lang. + configure: function (params: Record): void { + // Add a js configuration object to the head. + const script = document.createElement('script'); + script.type = 'text/x-mathjax-config'; + script.text = params.mathjaxconfig; + document.head.appendChild(script); + + // Save the lang config until MathJax is actually loaded. + this._lang = params.lang; // eslint-disable-line no-underscore-dangle + }, + // Set the correct language for the MathJax menus. + _setLocale: function (): void { + if (!this._configured) { // eslint-disable-line no-underscore-dangle + const lang = this._lang; // eslint-disable-line no-underscore-dangle + + if (typeof that.window.MathJax != 'undefined') { + that.window.MathJax.Hub.Queue(() => { + that.window.MathJax.Localization.setLocale(lang); + }); + that.window.MathJax.Hub.Configured(); + this._configured = true; // eslint-disable-line no-underscore-dangle + } + } + }, + // Called by the filter when an equation is found while rendering the page. + typeset: function (container: HTMLElement): void { + if (!this._configured) { // eslint-disable-line no-underscore-dangle + this._setLocale(); // eslint-disable-line no-underscore-dangle + } + + if (typeof that.window.MathJax != 'undefined') { + const processDelay = that.window.MathJax.Hub.processSectionDelay; + // Set the process section delay to 0 when updating the formula. + that.window.MathJax.Hub.processSectionDelay = 0; + + const equations = Array.from(container.querySelectorAll('.filter_mathjaxloader_equation')); + equations.forEach((node) => { + that.window.MathJax.Hub.Queue(['Typeset', that.window.MathJax.Hub, node]); + }); + + // Set the delay back to normal after processing. + that.window.MathJax.Hub.processSectionDelay = processDelay; + } + }, + }; + } + + /** + * Perform a mapping of the app language code to the equivalent for MathJax. + * + * @param langCode The app language code. + * @return The MathJax language code. + */ + protected mapLanguageCode(langCode: string): string { + + // If defined, explicit mapping takes the highest precedence. + if (this.EXPLICIT_MAPPING[langCode]) { + return this.EXPLICIT_MAPPING[langCode]; + } + + // If there is exact match, it will be probably right. + if (this.MATHJAX_LANG_CODES.indexOf(langCode) != -1) { + return langCode; + } + + // Finally try to find the best matching mathjax pack. + const parts = langCode.split('-'); + if (this.MATHJAX_LANG_CODES.indexOf(parts[0]) != -1) { + return parts[0]; + } + + // No more guessing, use default language. + return CoreLang.instance.getDefaultLanguage(); + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // Only apply the filter if logged in and we're filtering current site. + return !!(site && site.getId() == CoreSites.instance.getCurrentSiteId()); + } + + /** + * Wait for the MathJax library and our JS object to be loaded. + * + * @param retries Number of times this has been retried. + * @return Promise resolved when ready or if it took too long to load. + */ + protected async waitForReady(retries: number = 0): Promise { + if (this.window.MathJax || retries >= 20) { + // Loaded or too many retries, stop. + return; + } + + const deferred = CoreUtils.instance.promiseDefer(); + + setTimeout(async () => { + try { + await this.waitForReady(retries + 1); + } finally { + deferred.resolve(); + } + }, 250); + + return deferred.promise; + } + + /** + * Find math environments in the $text and wrap them in no link spans + * (). If math environments are nested, only + * the outer environment is wrapped in the span. + * + * The recognized math environments are \[ \] and $$ $$ for display + * mathematics and \( \) for inline mathematics. + * + * @param text The text to filter. + * @return Object containing the potentially modified text and a boolean that is true if any changes were made to the text. + */ + protected wrapMathInNoLink(text: string): {text: string; changed: boolean} { + let len = text.length; + let i = 1; + let displayStart = -1; + let displayBracket = false; + let displayDollar = false; + let inlineStart = -1; + let changesDone = false; + + // Loop over the $text once. + while (i < len) { + if (displayStart === -1) { + // No display math has started yet. + if (text[i - 1] === '\\') { + + if (text[i] === '[') { + // Display mode \[ begins. + displayStart = i - 1; + displayBracket = true; + } else if (text[i] === '(') { + // Inline math \( begins, not nested inside display math. + inlineStart = i - 1; + } else if (text[i] === ')' && inlineStart > -1) { + // Inline math ends, not nested inside display math. Wrap the span around it. + text = this.insertSpan(text, inlineStart, i); + + inlineStart = -1; // Reset. + i += 28; // The text length changed due to the . + len += 28; + changesDone = true; + } + + } else if (text[i - 1] === '$' && text[i] === '$') { + // Display mode $$ begins. + displayStart = i - 1; + displayDollar = true; + } + + } else { + // Display math open. + if ((text[i - 1] === '\\' && text[i] === ']' && displayBracket) || + (text[i - 1] === '$' && text[i] === '$' && displayDollar)) { + // Display math ends, wrap the span around it. + text = this.insertSpan(text, displayStart, i); + + displayStart = -1; // Reset. + displayBracket = false; + displayDollar = false; + i += 28; // The text length changed due to the . + len += 28; + changesDone = true; + } + } + + i++; + } + + return { + text: text, + changed: changesDone, + }; + } + +} + +type MathJaxWindow = Window & { + MathJax?: any; // eslint-disable-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any + M?: { // eslint-disable-line @typescript-eslint/naming-convention + filter_mathjaxloader?: { // eslint-disable-line @typescript-eslint/naming-convention + _lang: ''; // eslint-disable-line @typescript-eslint/naming-convention + _configured: false; // eslint-disable-line @typescript-eslint/naming-convention + // Add the configuration to the head and set the lang. + configure: (params: Record) => void; + _setLocale: () => void; // eslint-disable-line @typescript-eslint/naming-convention + typeset: (container: HTMLElement) => void; + }; + }; +}; diff --git a/src/addons/filter/mediaplugin/mediaplugin.module.ts b/src/addons/filter/mediaplugin/mediaplugin.module.ts new file mode 100644 index 000000000..31c76ada5 --- /dev/null +++ b/src/addons/filter/mediaplugin/mediaplugin.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterMediaPluginHandler } from './services/handlers/mediaplugin'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterMediaPluginHandler], + useFactory: (handler: AddonFilterMediaPluginHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterMediaPluginModule {} diff --git a/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts new file mode 100644 index 000000000..e5e57a91b --- /dev/null +++ b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts @@ -0,0 +1,98 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUrlUtils } from '@services/utils/url'; + +/** + * Handler to support the Multimedia filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterMediaPluginHandler'; + filterName = 'mediaplugin'; + + protected template = document.createElement('template'); // A template element to convert HTML to element. + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter( + text: string, + filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars + options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): string | Promise { + this.template.innerHTML = text; + + const videos = Array.from(this.template.content.querySelectorAll('video')); + + videos.forEach((video) => { + this.treatVideoFilters(video); + }); + + return this.template.innerHTML; + } + + /** + * Treat video filters. Currently only treating youtube video using video JS. + * + * @param el Video element. + * @param navCtrl NavController to use. + */ + protected treatVideoFilters(video: HTMLElement): void { + // Treat Video JS Youtube video links and translate them to iframes. + if (!video.classList.contains('video-js')) { + return; + } + + const dataSetupString = video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'; + const data = CoreTextUtils.instance.parseJSON(dataSetupString, {}); + const youtubeUrl = data.techOrder?.[0] == 'youtube' && CoreUrlUtils.instance.getYoutubeEmbedUrl(data.sources?.[0]?.src); + + if (!youtubeUrl) { + return; + } + + const iframe = document.createElement('iframe'); + iframe.id = video.id; + iframe.src = youtubeUrl; + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('allowfullscreen', '1'); + iframe.width = '100%'; + iframe.height = '300'; + + // Replace video tag by the iframe. + video.parentNode?.replaceChild(iframe, video); + } + +} + +type VideoDataSetup = { + techOrder?: string[]; + sources?: { + src?: string; + }[]; +}; diff --git a/src/addons/filter/multilang/multilang.module.ts b/src/addons/filter/multilang/multilang.module.ts new file mode 100644 index 000000000..eecc197a9 --- /dev/null +++ b/src/addons/filter/multilang/multilang.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterMultilangHandler } from './services/handlers/multilang'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterMultilangHandler], + useFactory: (handler: AddonFilterMultilangHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterMultilangModule {} diff --git a/src/addons/filter/multilang/services/handlers/multilang.ts b/src/addons/filter/multilang/services/handlers/multilang.ts new file mode 100644 index 000000000..04434a557 --- /dev/null +++ b/src/addons/filter/multilang/services/handlers/multilang.ts @@ -0,0 +1,84 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreLang } from '@services/lang'; +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Multilang filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterMultilangHandler'; + filterName = 'multilang'; + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + async filter( + text: string, + filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars + options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): Promise { + let language = await CoreLang.instance.getCurrentLanguage(); + + // Match the current language. + const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; + let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)', 'g'); + + if (!text.match(currentLangRegEx)) { + // Current lang not found. Try to find the first language. + const matches = text.match(anyLangRegEx); + if (matches?.[0]) { + language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)?.[1] || language; + currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)', 'g'); + } else { + // No multi-lang tag found, stop. + return text; + } + } + + // Extract contents of current language. + text = text.replace(currentLangRegEx, '$1'); + // Delete the rest of languages + text = text.replace(anyLangRegEx, ''); + + return text; + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // The filter should be applied if site is older than 3.7 or the WS didn't filter the text. + return !!(options.wsNotFiltered || (site && !site.isVersionGreaterEqualThan('3.7'))); + } + +} diff --git a/src/addons/filter/tex/services/handlers/tex.ts b/src/addons/filter/tex/services/handlers/tex.ts new file mode 100644 index 000000000..d871764b2 --- /dev/null +++ b/src/addons/filter/tex/services/handlers/tex.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the TeX notation filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterTexHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterTexHandler'; + filterName = 'tex'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/tex/tex.module.ts b/src/addons/filter/tex/tex.module.ts new file mode 100644 index 000000000..75242bd21 --- /dev/null +++ b/src/addons/filter/tex/tex.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterTexHandler } from './services/handlers/tex'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterTexHandler], + useFactory: (handler: AddonFilterTexHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterTexModule {} diff --git a/src/addons/filter/tidy/services/handlers/tidy.ts b/src/addons/filter/tidy/services/handlers/tidy.ts new file mode 100644 index 000000000..059428325 --- /dev/null +++ b/src/addons/filter/tidy/services/handlers/tidy.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the HTML tidy filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterTidyHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterTidyHandler'; + filterName = 'tidy'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/tidy/tidy.module.ts b/src/addons/filter/tidy/tidy.module.ts new file mode 100644 index 000000000..8fcca0242 --- /dev/null +++ b/src/addons/filter/tidy/tidy.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterTidyHandler } from './services/handlers/tidy'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterTidyHandler], + useFactory: (handler: AddonFilterTidyHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterTidyModule {} diff --git a/src/addons/filter/urltolink/services/handlers/urltolink.ts b/src/addons/filter/urltolink/services/handlers/urltolink.ts new file mode 100644 index 000000000..af2e740ad --- /dev/null +++ b/src/addons/filter/urltolink/services/handlers/urltolink.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; + +import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; +import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the URL to link and images filter. + */ +@Injectable({ providedIn: 'root' }) +export class AddonFilterUrlToLinkHandler extends CoreFilterDefaultHandler { + + name = 'AddonFilterUrlToLinkHandler'; + filterName = 'urltolink'; + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // This filter is handled by Moodle, nothing to do in the app. + return false; + } + +} diff --git a/src/addons/filter/urltolink/urltolink.module.ts b/src/addons/filter/urltolink/urltolink.module.ts new file mode 100644 index 000000000..e6d5e6dd6 --- /dev/null +++ b/src/addons/filter/urltolink/urltolink.module.ts @@ -0,0 +1,34 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreFilterDelegate } from '@features/filter/services/filter-delegate'; +import { AddonFilterUrlToLinkHandler } from './services/handlers/urltolink'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [AddonFilterUrlToLinkHandler], + useFactory: (handler: AddonFilterUrlToLinkHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + }, + ], +}) +export class AddonFilterUrlToLinkModule {} diff --git a/src/core/services/utils/url.ts b/src/core/services/utils/url.ts index ce8639ce1..3d19bcb83 100644 --- a/src/core/services/utils/url.ts +++ b/src/core/services/utils/url.ts @@ -255,7 +255,7 @@ export class CoreUrlUtilsProvider { * @param url URL * @return Youtube Embed Video URL or null if not found. */ - getYoutubeEmbedUrl(url: string): string | void { + getYoutubeEmbedUrl(url?: string): string | void { if (!url) { return; } From d6123fef1a45f19d86642a53743218ea8b0ea4eb Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 9 Dec 2020 15:37:11 +0100 Subject: [PATCH 4/5] MOBILE-3620 gulp: Install keytar for gulp push --- package-lock.json | 338 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 3 + 2 files changed, 329 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdc609117..c85e945ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3986,8 +3986,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "archy": { "version": "1.0.0", @@ -3995,6 +3994,16 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -4694,6 +4703,40 @@ "file-uri-to-path": "1.0.0" } }, + "bl": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "optional": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "optional": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -5581,8 +5624,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collect-v8-coverage": { "version": "1.0.1", @@ -5879,6 +5921,12 @@ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -7387,6 +7435,12 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "optional": true + }, "dep-graph": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/dep-graph/-/dep-graph-1.1.0.tgz", @@ -7439,6 +7493,12 @@ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "optional": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8455,6 +8515,12 @@ } } }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "optional": true + }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -9106,6 +9172,12 @@ "readable-stream": "^2.0.0" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "optional": true + }, "fs-extra": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", @@ -9172,6 +9244,59 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "genfun": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", @@ -9224,6 +9349,12 @@ "assert-plus": "^1.0.0" } }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", + "optional": true + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -10057,6 +10188,12 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "optional": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -10571,8 +10708,7 @@ "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "iferr": { "version": "0.1.5", @@ -12663,6 +12799,16 @@ "source-map-support": "^0.5.5" } }, + "keytar": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.2.0.tgz", + "integrity": "sha512-ECSaWvoLKI5SI0pGpZQeUV1/lpBYfkaxvoSp3zkiPOz05VavwSfLi8DdEaa9N2ekQZv3Chy+o7aP6n9mairBgw==", + "optional": true, + "requires": { + "node-addon-api": "^3.0.0", + "prebuild-install": "^6.0.0" + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -13671,6 +13817,12 @@ "minimist": "^1.2.5" } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "optional": true + }, "moment": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz", @@ -13776,6 +13928,12 @@ "to-regex": "^3.0.1" } }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "optional": true + }, "native-request": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.0.7.tgz", @@ -13825,6 +13983,29 @@ "lower-case": "^1.1.1" } }, + "node-abi": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.19.3.tgz", + "integrity": "sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg==", + "optional": true, + "requires": { + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "optional": true + } + } + }, + "node-addon-api": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.2.tgz", + "integrity": "sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==", + "optional": true + }, "node-fetch-npm": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz", @@ -13938,6 +14119,12 @@ "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", "dev": true }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", + "optional": true + }, "nopt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", @@ -14121,6 +14308,18 @@ "path-key": "^3.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -14139,8 +14338,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { "version": "2.2.0", @@ -14156,8 +14354,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -15986,6 +16183,29 @@ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, + "prebuild-install": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.0.0.tgz", + "integrity": "sha512-h2ZJ1PXHKWZpp1caLw0oX9sagVpL2YTk+ZwInQbQ3QqNd4J03O6MpFNmMTJlkfgPENWqe5kP0WjQLqz5OjLfsw==", + "optional": true, + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -17556,8 +17776,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -17663,6 +17882,40 @@ } } }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "optional": true + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "optional": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true + } + } + }, "simple-plist": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.1.tgz", @@ -18812,6 +19065,52 @@ } } }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "optional": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "optional": true + } + } + }, + "tar-stream": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", + "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "optional": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", @@ -20907,6 +21206,21 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", diff --git a/package.json b/package.json index 553d0de6d..880dcdbd0 100644 --- a/package.json +++ b/package.json @@ -221,5 +221,8 @@ "cordova-plugin-globalization": {}, "cordova-plugin-file-transfer": {} } + }, + "optionalDependencies": { + "keytar": "^7.2.0" } } From 29858ea5e70d83ef57c638ad54e32074cf4d7710 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 9 Dec 2020 16:42:21 +0100 Subject: [PATCH 5/5] MOBILE-3620 delegate: Make all delegate singletons --- .../activitynames/activitynames.module.ts | 4 +- .../services/handlers/activitynames.ts | 5 ++- src/addons/filter/algebra/algebra.module.ts | 4 +- .../algebra/services/handlers/algebra.ts | 5 ++- src/addons/filter/censor/censor.module.ts | 4 +- .../filter/censor/services/handlers/censor.ts | 5 ++- src/addons/filter/data/data.module.ts | 4 +- .../filter/data/services/handlers/data.ts | 5 ++- .../filter/displayh5p/displayh5p.module.ts | 4 +- .../services/handlers/displayh5p.ts | 5 ++- .../emailprotect/emailprotect.module.ts | 4 +- .../services/handlers/emailprotect.ts | 5 ++- src/addons/filter/emoticon/emoticon.module.ts | 4 +- .../emoticon/services/handlers/emoticon.ts | 5 ++- src/addons/filter/glossary/glossary.module.ts | 4 +- .../glossary/services/handlers/glossary.ts | 5 ++- .../mathjaxloader/mathjaxloader.module.ts | 8 ++-- .../services/handlers/mathjaxloader.ts | 5 ++- .../filter/mediaplugin/mediaplugin.module.ts | 4 +- .../services/handlers/mediaplugin.ts | 5 ++- .../filter/multilang/multilang.module.ts | 4 +- .../multilang/services/handlers/multilang.ts | 5 ++- .../filter/tex/services/handlers/tex.ts | 5 ++- src/addons/filter/tex/tex.module.ts | 4 +- .../filter/tidy/services/handlers/tidy.ts | 5 ++- src/addons/filter/tidy/tidy.module.ts | 4 +- .../urltolink/services/handlers/urltolink.ts | 5 ++- .../filter/urltolink/urltolink.module.ts | 4 +- .../privatefiles/privatefiles.module.ts | 26 ++++++------- .../services/handlers/mainmenu.ts | 9 +++-- src/core/components/file/file.ts | 6 +-- .../services/contentlinks-delegate.ts | 9 +++-- .../services/contentlinks-helper.ts | 3 +- src/core/features/courses/courses.module.ts | 36 ++++++++---------- .../services/handlers/dashboard-home.ts | 9 +++-- .../services/handlers/my-courses.home.ts | 9 +++-- .../fileuploader/fileuploader.module.ts | 38 +++++++------------ .../services/fileuploader-delegate.ts | 5 ++- .../services/fileuploader-helper.ts | 4 +- .../fileuploader/services/handlers/album.ts | 7 +++- .../fileuploader/services/handlers/audio.ts | 7 +++- .../fileuploader/services/handlers/camera.ts | 7 +++- .../fileuploader/services/handlers/file.ts | 8 ++-- .../fileuploader/services/handlers/video.ts | 7 +++- .../features/mainmenu/mainmenu-lazy.module.ts | 7 ++-- src/core/features/mainmenu/mainmenu.module.ts | 23 +++++------ src/core/features/mainmenu/pages/home/home.ts | 6 +-- src/core/features/mainmenu/pages/menu/menu.ts | 5 +-- src/core/features/mainmenu/pages/more/more.ts | 8 ++-- .../mainmenu/services/handlers/mainmenu.ts | 7 +++- .../mainmenu/services/home-delegate.ts | 5 ++- .../mainmenu/services/mainmenu-delegate.ts | 5 ++- .../features/mainmenu/services/mainmenu.ts | 4 +- src/core/features/settings/pages/site/site.ts | 3 +- .../settings/services/settings-delegate.ts | 5 ++- .../settings/services/settings-helper.ts | 6 +-- .../sitehome/services/handlers/index-link.ts | 7 +++- .../services/handlers/sitehome-home.ts | 9 +++-- src/core/features/sitehome/sitehome.module.ts | 31 +++++++-------- src/core/initializers/initialize-databases.ts | 4 +- .../initializers/prepare-automated-tests.ts | 6 +-- src/core/services/cron.ts | 18 ++++----- src/core/services/filepool.ts | 14 +++---- src/core/services/handlers/site-info-cron.ts | 2 +- src/core/services/plugin-file-delegate.ts | 4 +- 65 files changed, 278 insertions(+), 221 deletions(-) diff --git a/src/addons/filter/activitynames/activitynames.module.ts b/src/addons/filter/activitynames/activitynames.module.ts index 7971ef7bd..aad852015 100644 --- a/src/addons/filter/activitynames/activitynames.module.ts +++ b/src/addons/filter/activitynames/activitynames.module.ts @@ -26,8 +26,8 @@ import { AddonFilterActivityNamesHandler } from './services/handlers/activitynam { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterActivityNamesHandler], - useFactory: (handler: AddonFilterActivityNamesHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterActivityNamesHandler.instance), }, ], }) diff --git a/src/addons/filter/activitynames/services/handlers/activitynames.ts b/src/addons/filter/activitynames/services/handlers/activitynames.ts index 4fbcbeea4..b6f27260b 100644 --- a/src/addons/filter/activitynames/services/handlers/activitynames.ts +++ b/src/addons/filter/activitynames/services/handlers/activitynames.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Activity names filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterActivityNamesHandler extends CoreFilterDefaultHandler { +export class AddonFilterActivityNamesHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterActivityNamesHandler'; filterName = 'activitynames'; @@ -41,3 +42,5 @@ export class AddonFilterActivityNamesHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterActivityNamesHandler extends makeSingleton(AddonFilterActivityNamesHandlerService) {} diff --git a/src/addons/filter/algebra/algebra.module.ts b/src/addons/filter/algebra/algebra.module.ts index b2b231fba..1e6b6287c 100644 --- a/src/addons/filter/algebra/algebra.module.ts +++ b/src/addons/filter/algebra/algebra.module.ts @@ -26,8 +26,8 @@ import { AddonFilterAlgebraHandler } from './services/handlers/algebra'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterAlgebraHandler], - useFactory: (handler: AddonFilterAlgebraHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterAlgebraHandler.instance), }, ], }) diff --git a/src/addons/filter/algebra/services/handlers/algebra.ts b/src/addons/filter/algebra/services/handlers/algebra.ts index 656737d3f..378e2833e 100644 --- a/src/addons/filter/algebra/services/handlers/algebra.ts +++ b/src/addons/filter/algebra/services/handlers/algebra.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Algebra notation filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterAlgebraHandler extends CoreFilterDefaultHandler { +export class AddonFilterAlgebraHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterAlgebraHandler'; filterName = 'algebra'; @@ -41,3 +42,5 @@ export class AddonFilterAlgebraHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterAlgebraHandler extends makeSingleton(AddonFilterAlgebraHandlerService) {} diff --git a/src/addons/filter/censor/censor.module.ts b/src/addons/filter/censor/censor.module.ts index fb07088a8..28b749619 100644 --- a/src/addons/filter/censor/censor.module.ts +++ b/src/addons/filter/censor/censor.module.ts @@ -26,8 +26,8 @@ import { AddonFilterCensorHandler } from './services/handlers/censor'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterCensorHandler], - useFactory: (handler: AddonFilterCensorHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterCensorHandler.instance), }, ], }) diff --git a/src/addons/filter/censor/services/handlers/censor.ts b/src/addons/filter/censor/services/handlers/censor.ts index 1e8225047..074c76dd9 100644 --- a/src/addons/filter/censor/services/handlers/censor.ts +++ b/src/addons/filter/censor/services/handlers/censor.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Word censorship filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterCensorHandler extends CoreFilterDefaultHandler { +export class AddonFilterCensorHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterCensorHandler'; filterName = 'censor'; @@ -41,3 +42,5 @@ export class AddonFilterCensorHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterCensorHandler extends makeSingleton(AddonFilterCensorHandlerService) {} diff --git a/src/addons/filter/data/data.module.ts b/src/addons/filter/data/data.module.ts index 716948cb5..1e2488629 100644 --- a/src/addons/filter/data/data.module.ts +++ b/src/addons/filter/data/data.module.ts @@ -26,8 +26,8 @@ import { AddonFilterDataHandler } from './services/handlers/data'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterDataHandler], - useFactory: (handler: AddonFilterDataHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterDataHandler.instance), }, ], }) diff --git a/src/addons/filter/data/services/handlers/data.ts b/src/addons/filter/data/services/handlers/data.ts index fc4b24c0f..92006a5b2 100644 --- a/src/addons/filter/data/services/handlers/data.ts +++ b/src/addons/filter/data/services/handlers/data.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Database auto-link filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterDataHandler extends CoreFilterDefaultHandler { +export class AddonFilterDataHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterDataHandler'; filterName = 'data'; @@ -41,3 +42,5 @@ export class AddonFilterDataHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterDataHandler extends makeSingleton(AddonFilterDataHandlerService) {} diff --git a/src/addons/filter/displayh5p/displayh5p.module.ts b/src/addons/filter/displayh5p/displayh5p.module.ts index 1145b1362..9aaa97ea2 100644 --- a/src/addons/filter/displayh5p/displayh5p.module.ts +++ b/src/addons/filter/displayh5p/displayh5p.module.ts @@ -26,8 +26,8 @@ import { AddonFilterDisplayH5PHandler } from './services/handlers/displayh5p'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterDisplayH5PHandler], - useFactory: (handler: AddonFilterDisplayH5PHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterDisplayH5PHandler.instance), }, ], }) diff --git a/src/addons/filter/displayh5p/services/handlers/displayh5p.ts b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts index da70664d8..67a07b92c 100644 --- a/src/addons/filter/displayh5p/services/handlers/displayh5p.ts +++ b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts @@ -16,13 +16,14 @@ import { Injectable, ViewContainerRef, ComponentFactoryResolver } from '@angular import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; +import { makeSingleton } from '@singletons'; // @todo import { CoreH5PPlayerComponent } from '@core/h5p/components/h5p-player/h5p-player'; /** * Handler to support the Display H5P filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterDisplayH5PHandler extends CoreFilterDefaultHandler { +export class AddonFilterDisplayH5PHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterDisplayH5PHandler'; filterName = 'displayh5p'; @@ -108,3 +109,5 @@ export class AddonFilterDisplayH5PHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterDisplayH5PHandler extends makeSingleton(AddonFilterDisplayH5PHandlerService) {} diff --git a/src/addons/filter/emailprotect/emailprotect.module.ts b/src/addons/filter/emailprotect/emailprotect.module.ts index 8e1fa7981..f035274e6 100644 --- a/src/addons/filter/emailprotect/emailprotect.module.ts +++ b/src/addons/filter/emailprotect/emailprotect.module.ts @@ -26,8 +26,8 @@ import { AddonFilterEmailProtectHandler } from './services/handlers/emailprotect { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterEmailProtectHandler], - useFactory: (handler: AddonFilterEmailProtectHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterEmailProtectHandler.instance), }, ], }) diff --git a/src/addons/filter/emailprotect/services/handlers/emailprotect.ts b/src/addons/filter/emailprotect/services/handlers/emailprotect.ts index 301e2d335..972356e66 100644 --- a/src/addons/filter/emailprotect/services/handlers/emailprotect.ts +++ b/src/addons/filter/emailprotect/services/handlers/emailprotect.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Email protection filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterEmailProtectHandler extends CoreFilterDefaultHandler { +export class AddonFilterEmailProtectHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterEmailProtectHandler'; filterName = 'emailprotect'; @@ -41,3 +42,5 @@ export class AddonFilterEmailProtectHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterEmailProtectHandler extends makeSingleton(AddonFilterEmailProtectHandlerService) {} diff --git a/src/addons/filter/emoticon/emoticon.module.ts b/src/addons/filter/emoticon/emoticon.module.ts index 821b56a49..ada8d0aac 100644 --- a/src/addons/filter/emoticon/emoticon.module.ts +++ b/src/addons/filter/emoticon/emoticon.module.ts @@ -26,8 +26,8 @@ import { AddonFilterEmoticonHandler } from './services/handlers/emoticon'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterEmoticonHandler], - useFactory: (handler: AddonFilterEmoticonHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterEmoticonHandler.instance), }, ], }) diff --git a/src/addons/filter/emoticon/services/handlers/emoticon.ts b/src/addons/filter/emoticon/services/handlers/emoticon.ts index 128502222..0033e21fa 100644 --- a/src/addons/filter/emoticon/services/handlers/emoticon.ts +++ b/src/addons/filter/emoticon/services/handlers/emoticon.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Emoticon filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterEmoticonHandler extends CoreFilterDefaultHandler { +export class AddonFilterEmoticonHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterEmoticonHandler'; filterName = 'emoticon'; @@ -41,3 +42,5 @@ export class AddonFilterEmoticonHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterEmoticonHandler extends makeSingleton(AddonFilterEmoticonHandlerService) {} diff --git a/src/addons/filter/glossary/glossary.module.ts b/src/addons/filter/glossary/glossary.module.ts index 225403e35..d38234843 100644 --- a/src/addons/filter/glossary/glossary.module.ts +++ b/src/addons/filter/glossary/glossary.module.ts @@ -26,8 +26,8 @@ import { AddonFilterGlossaryHandler } from './services/handlers/glossary'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterGlossaryHandler], - useFactory: (handler: AddonFilterGlossaryHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterGlossaryHandler.instance), }, ], }) diff --git a/src/addons/filter/glossary/services/handlers/glossary.ts b/src/addons/filter/glossary/services/handlers/glossary.ts index 6b6abd4ee..412434058 100644 --- a/src/addons/filter/glossary/services/handlers/glossary.ts +++ b/src/addons/filter/glossary/services/handlers/glossary.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Glossary auto-link filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterGlossaryHandler extends CoreFilterDefaultHandler { +export class AddonFilterGlossaryHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterGlossaryHandler'; filterName = 'glossary'; @@ -41,3 +42,5 @@ export class AddonFilterGlossaryHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterGlossaryHandler extends makeSingleton(AddonFilterGlossaryHandlerService) {} diff --git a/src/addons/filter/mathjaxloader/mathjaxloader.module.ts b/src/addons/filter/mathjaxloader/mathjaxloader.module.ts index bb5ded4e5..3caa6efb9 100644 --- a/src/addons/filter/mathjaxloader/mathjaxloader.module.ts +++ b/src/addons/filter/mathjaxloader/mathjaxloader.module.ts @@ -26,11 +26,11 @@ import { AddonFilterMathJaxLoaderHandler } from './services/handlers/mathjaxload { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterMathJaxLoaderHandler], - useFactory: (handler: AddonFilterMathJaxLoaderHandler) => async () => { - CoreFilterDelegate.instance.registerHandler(handler); + deps: [], + useFactory: () => async () => { + CoreFilterDelegate.instance.registerHandler(AddonFilterMathJaxLoaderHandler.instance); - await handler.initialize(); + await AddonFilterMathJaxLoaderHandler.instance.initialize(); }, }, ], diff --git a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts index 94e58f341..5056dc1c5 100644 --- a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts +++ b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts @@ -22,12 +22,13 @@ import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreEvents } from '@singletons/events'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the MathJax filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterMathJaxLoaderHandler extends CoreFilterDefaultHandler { +export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterMathJaxLoaderHandler'; filterName = 'mathjaxloader'; @@ -395,6 +396,8 @@ export class AddonFilterMathJaxLoaderHandler extends CoreFilterDefaultHandler { } +export class AddonFilterMathJaxLoaderHandler extends makeSingleton(AddonFilterMathJaxLoaderHandlerService) {} + type MathJaxWindow = Window & { MathJax?: any; // eslint-disable-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any M?: { // eslint-disable-line @typescript-eslint/naming-convention diff --git a/src/addons/filter/mediaplugin/mediaplugin.module.ts b/src/addons/filter/mediaplugin/mediaplugin.module.ts index 31c76ada5..f1099474f 100644 --- a/src/addons/filter/mediaplugin/mediaplugin.module.ts +++ b/src/addons/filter/mediaplugin/mediaplugin.module.ts @@ -26,8 +26,8 @@ import { AddonFilterMediaPluginHandler } from './services/handlers/mediaplugin'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterMediaPluginHandler], - useFactory: (handler: AddonFilterMediaPluginHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterMediaPluginHandler.instance), }, ], }) diff --git a/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts index e5e57a91b..552df81e3 100644 --- a/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts +++ b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts @@ -18,12 +18,13 @@ import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/def import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUrlUtils } from '@services/utils/url'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Multimedia filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { +export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterMediaPluginHandler'; filterName = 'mediaplugin'; @@ -90,6 +91,8 @@ export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { } +export class AddonFilterMediaPluginHandler extends makeSingleton(AddonFilterMediaPluginHandlerService) {} + type VideoDataSetup = { techOrder?: string[]; sources?: { diff --git a/src/addons/filter/multilang/multilang.module.ts b/src/addons/filter/multilang/multilang.module.ts index eecc197a9..58fb590e3 100644 --- a/src/addons/filter/multilang/multilang.module.ts +++ b/src/addons/filter/multilang/multilang.module.ts @@ -26,8 +26,8 @@ import { AddonFilterMultilangHandler } from './services/handlers/multilang'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterMultilangHandler], - useFactory: (handler: AddonFilterMultilangHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterMultilangHandler.instance), }, ], }) diff --git a/src/addons/filter/multilang/services/handlers/multilang.ts b/src/addons/filter/multilang/services/handlers/multilang.ts index 04434a557..a0b4b3eca 100644 --- a/src/addons/filter/multilang/services/handlers/multilang.ts +++ b/src/addons/filter/multilang/services/handlers/multilang.ts @@ -18,12 +18,13 @@ import { CoreLang } from '@services/lang'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the Multilang filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler { +export class AddonFilterMultilangHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterMultilangHandler'; filterName = 'multilang'; @@ -82,3 +83,5 @@ export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterMultilangHandler extends makeSingleton(AddonFilterMultilangHandlerService) {} diff --git a/src/addons/filter/tex/services/handlers/tex.ts b/src/addons/filter/tex/services/handlers/tex.ts index d871764b2..c1412fbe3 100644 --- a/src/addons/filter/tex/services/handlers/tex.ts +++ b/src/addons/filter/tex/services/handlers/tex.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the TeX notation filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterTexHandler extends CoreFilterDefaultHandler { +export class AddonFilterTexHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterTexHandler'; filterName = 'tex'; @@ -41,3 +42,5 @@ export class AddonFilterTexHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterTexHandler extends makeSingleton(AddonFilterTexHandlerService) {} diff --git a/src/addons/filter/tex/tex.module.ts b/src/addons/filter/tex/tex.module.ts index 75242bd21..124f0b793 100644 --- a/src/addons/filter/tex/tex.module.ts +++ b/src/addons/filter/tex/tex.module.ts @@ -26,8 +26,8 @@ import { AddonFilterTexHandler } from './services/handlers/tex'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterTexHandler], - useFactory: (handler: AddonFilterTexHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterTexHandler.instance), }, ], }) diff --git a/src/addons/filter/tidy/services/handlers/tidy.ts b/src/addons/filter/tidy/services/handlers/tidy.ts index 059428325..62ab7ede8 100644 --- a/src/addons/filter/tidy/services/handlers/tidy.ts +++ b/src/addons/filter/tidy/services/handlers/tidy.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the HTML tidy filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterTidyHandler extends CoreFilterDefaultHandler { +export class AddonFilterTidyHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterTidyHandler'; filterName = 'tidy'; @@ -41,3 +42,5 @@ export class AddonFilterTidyHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterTidyHandler extends makeSingleton(AddonFilterTidyHandlerService) {} diff --git a/src/addons/filter/tidy/tidy.module.ts b/src/addons/filter/tidy/tidy.module.ts index 8fcca0242..fc8f3989c 100644 --- a/src/addons/filter/tidy/tidy.module.ts +++ b/src/addons/filter/tidy/tidy.module.ts @@ -26,8 +26,8 @@ import { AddonFilterTidyHandler } from './services/handlers/tidy'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterTidyHandler], - useFactory: (handler: AddonFilterTidyHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterTidyHandler.instance), }, ], }) diff --git a/src/addons/filter/urltolink/services/handlers/urltolink.ts b/src/addons/filter/urltolink/services/handlers/urltolink.ts index af2e740ad..cff0dd5d8 100644 --- a/src/addons/filter/urltolink/services/handlers/urltolink.ts +++ b/src/addons/filter/urltolink/services/handlers/urltolink.ts @@ -17,12 +17,13 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; /** * Handler to support the URL to link and images filter. */ @Injectable({ providedIn: 'root' }) -export class AddonFilterUrlToLinkHandler extends CoreFilterDefaultHandler { +export class AddonFilterUrlToLinkHandlerService extends CoreFilterDefaultHandler { name = 'AddonFilterUrlToLinkHandler'; filterName = 'urltolink'; @@ -41,3 +42,5 @@ export class AddonFilterUrlToLinkHandler extends CoreFilterDefaultHandler { } } + +export class AddonFilterUrlToLinkHandler extends makeSingleton(AddonFilterUrlToLinkHandlerService) {} diff --git a/src/addons/filter/urltolink/urltolink.module.ts b/src/addons/filter/urltolink/urltolink.module.ts index e6d5e6dd6..8e4ee1591 100644 --- a/src/addons/filter/urltolink/urltolink.module.ts +++ b/src/addons/filter/urltolink/urltolink.module.ts @@ -26,8 +26,8 @@ import { AddonFilterUrlToLinkHandler } from './services/handlers/urltolink'; { provide: APP_INITIALIZER, multi: true, - deps: [AddonFilterUrlToLinkHandler], - useFactory: (handler: AddonFilterUrlToLinkHandler) => () => CoreFilterDelegate.instance.registerHandler(handler), + deps: [], + useFactory: () => () => CoreFilterDelegate.instance.registerHandler(AddonFilterUrlToLinkHandler.instance), }, ], }) diff --git a/src/addons/privatefiles/privatefiles.module.ts b/src/addons/privatefiles/privatefiles.module.ts index 59af1bcbf..26e7073ab 100644 --- a/src/addons/privatefiles/privatefiles.module.ts +++ b/src/addons/privatefiles/privatefiles.module.ts @@ -12,16 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Routes } from '@angular/router'; import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate'; import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; -import { AddonPrivateFilesMainMenuHandler } from './services/handlers/mainmenu'; +import { AddonPrivateFilesMainMenuHandler, AddonPrivateFilesMainMenuHandlerService } from './services/handlers/mainmenu'; const routes: Routes = [ { - path: AddonPrivateFilesMainMenuHandler.PAGE_NAME, + path: AddonPrivateFilesMainMenuHandlerService.PAGE_NAME, loadChildren: () => import('@/addons/privatefiles/privatefiles-lazy.module').then(m => m.AddonPrivateFilesLazyModule), }, ]; @@ -30,16 +30,14 @@ const routes: Routes = [ imports: [CoreMainMenuRoutingModule.forChild({ children: routes })], exports: [CoreMainMenuRoutingModule], providers: [ - AddonPrivateFilesMainMenuHandler, + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreMainMenuDelegate.instance.registerHandler(AddonPrivateFilesMainMenuHandler.instance); + }, + }, ], }) -export class AddonPrivateFilesModule { - - constructor( - mainMenuDelegate: CoreMainMenuDelegate, - mainMenuHandler: AddonPrivateFilesMainMenuHandler, - ) { - mainMenuDelegate.registerHandler(mainMenuHandler); - } - -} +export class AddonPrivateFilesModule {} diff --git a/src/addons/privatefiles/services/handlers/mainmenu.ts b/src/addons/privatefiles/services/handlers/mainmenu.ts index 6e2e8ae2f..2adfe081c 100644 --- a/src/addons/privatefiles/services/handlers/mainmenu.ts +++ b/src/addons/privatefiles/services/handlers/mainmenu.ts @@ -16,12 +16,13 @@ import { Injectable } from '@angular/core'; import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate'; import { AddonPrivateFiles } from '@/addons/privatefiles/services/privatefiles'; +import { makeSingleton } from '@singletons'; /** * Handler to inject an option into main menu. */ -@Injectable() -export class AddonPrivateFilesMainMenuHandler implements CoreMainMenuHandler { +@Injectable({ providedIn: 'root' }) +export class AddonPrivateFilesMainMenuHandlerService implements CoreMainMenuHandler { static readonly PAGE_NAME = 'private'; @@ -46,10 +47,12 @@ export class AddonPrivateFilesMainMenuHandler implements CoreMainMenuHandler { return { icon: 'fas-folder', title: 'addon.privatefiles.files', - page: AddonPrivateFilesMainMenuHandler.PAGE_NAME, + page: AddonPrivateFilesMainMenuHandlerService.PAGE_NAME, subPage: 'root', class: 'addon-privatefiles-handler', }; } } + +export class AddonPrivateFilesMainMenuHandler extends makeSingleton(AddonPrivateFilesMainMenuHandlerService) {} diff --git a/src/core/components/file/file.ts b/src/core/components/file/file.ts index 4ef0645b6..60ad14966 100644 --- a/src/core/components/file/file.ts +++ b/src/core/components/file/file.ts @@ -60,9 +60,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { protected fileSize?: number; protected observer?: CoreEventObserver; - constructor( - protected pluginFileDelegate: CorePluginFileDelegate, - ) { + constructor() { this.onDelete = new EventEmitter(); } @@ -195,7 +193,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { } else { try { // File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big. - const size = await this.pluginFileDelegate.getFileSize(this.file, this.siteId); + const size = await CorePluginFileDelegate.instance.getFileSize(this.file, this.siteId); if (size) { await CoreDomUtils.instance.confirmDownloadSize({ size: size, total: true }); diff --git a/src/core/features/contentlinks/services/contentlinks-delegate.ts b/src/core/features/contentlinks/services/contentlinks-delegate.ts index 3ed6d250a..7328626f0 100644 --- a/src/core/features/contentlinks/services/contentlinks-delegate.ts +++ b/src/core/features/contentlinks/services/contentlinks-delegate.ts @@ -18,6 +18,7 @@ import { CoreSites } from '@services/sites'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; import { Params } from '@angular/router'; +import { makeSingleton } from '@singletons'; /** * Interface that all handlers must implement. @@ -132,10 +133,8 @@ export interface CoreContentLinksHandlerActions { /** * Delegate to register handlers to handle links. */ -@Injectable({ - providedIn: 'root', -}) -export class CoreContentLinksDelegate { +@Injectable({ providedIn: 'root' }) +export class CoreContentLinksDelegateService { protected logger: CoreLogger; protected handlers: { [s: string]: CoreContentLinksHandler } = {}; // All registered handlers. @@ -307,3 +306,5 @@ export class CoreContentLinksDelegate { } } + +export class CoreContentLinksDelegate extends makeSingleton(CoreContentLinksDelegateService) {} diff --git a/src/core/features/contentlinks/services/contentlinks-helper.ts b/src/core/features/contentlinks/services/contentlinks-helper.ts index af777f183..6ee01013a 100644 --- a/src/core/features/contentlinks/services/contentlinks-helper.ts +++ b/src/core/features/contentlinks/services/contentlinks-helper.ts @@ -33,7 +33,6 @@ import { Params } from '@angular/router'; export class CoreContentLinksHelperProvider { constructor( - protected contentLinksDelegate: CoreContentLinksDelegate, protected navCtrl: NavController, ) { } @@ -80,7 +79,7 @@ export class CoreContentLinksHelperProvider { username?: string, data?: unknown, ): Promise { - const actions = await this.contentLinksDelegate.getActionsFor(url, courseId, username, data); + const actions = await CoreContentLinksDelegate.instance.getActionsFor(url, courseId, username, data); if (!actions) { return; } diff --git a/src/core/features/courses/courses.module.ts b/src/core/features/courses/courses.module.ts index a51f0b3b7..3f5014a11 100644 --- a/src/core/features/courses/courses.module.ts +++ b/src/core/features/courses/courses.module.ts @@ -12,27 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Routes } from '@angular/router'; import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module'; import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate'; -import { CoreDashboardHomeHandler } from './services/handlers/dashboard-home'; -import { CoreCoursesMyCoursesHomeHandler } from './services/handlers/my-courses.home'; +import { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './services/handlers/dashboard-home'; +import { CoreCoursesMyCoursesHomeHandler, CoreCoursesMyCoursesHomeHandlerService } from './services/handlers/my-courses.home'; const mainMenuHomeChildrenRoutes: Routes = [ { path: '', pathMatch: 'full', - redirectTo: CoreDashboardHomeHandler.PAGE_NAME, + redirectTo: CoreDashboardHomeHandlerService.PAGE_NAME, }, { - path: CoreDashboardHomeHandler.PAGE_NAME, + path: CoreDashboardHomeHandlerService.PAGE_NAME, loadChildren: () => import('./pages/dashboard/dashboard.module').then(m => m.CoreCoursesDashboardPageModule), }, { - path: CoreCoursesMyCoursesHomeHandler.PAGE_NAME, + path: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME, loadChildren: () => import('./pages/my-courses/my-courses.module').then(m => m.CoreCoursesMyCoursesPageModule), }, ]; @@ -52,19 +52,15 @@ const mainMenuHomeSiblingRoutes: Routes = [ }), ], providers: [ - CoreDashboardHomeHandler, - CoreCoursesMyCoursesHomeHandler, + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreMainMenuHomeDelegate.instance.registerHandler(CoreDashboardHomeHandler.instance); + CoreMainMenuHomeDelegate.instance.registerHandler(CoreCoursesMyCoursesHomeHandler.instance); + }, + }, ], }) -export class CoreCoursesModule { - - constructor( - homeDelegate: CoreMainMenuHomeDelegate, - coursesDashboardHandler: CoreDashboardHomeHandler, - coursesMyCoursesHandler: CoreCoursesMyCoursesHomeHandler, - ) { - homeDelegate.registerHandler(coursesDashboardHandler); - homeDelegate.registerHandler(coursesMyCoursesHandler); - } - -} +export class CoreCoursesModule {} diff --git a/src/core/features/courses/services/handlers/dashboard-home.ts b/src/core/features/courses/services/handlers/dashboard-home.ts index f98e53457..0f2ce7e4e 100644 --- a/src/core/features/courses/services/handlers/dashboard-home.ts +++ b/src/core/features/courses/services/handlers/dashboard-home.ts @@ -14,12 +14,13 @@ import { Injectable } from '@angular/core'; import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; +import { makeSingleton } from '@singletons'; /** * Handler to add dashboard into home page. */ -Injectable(); -export class CoreDashboardHomeHandler implements CoreMainMenuHomeHandler { +@Injectable({ providedIn: 'root' }) +export class CoreDashboardHomeHandlerService implements CoreMainMenuHomeHandler { static readonly PAGE_NAME = 'dashboard'; @@ -55,7 +56,7 @@ export class CoreDashboardHomeHandler implements CoreMainMenuHomeHandler { getDisplayData(): CoreMainMenuHomeHandlerToDisplay { return { title: 'core.courses.mymoodle', - page: CoreDashboardHomeHandler.PAGE_NAME, + page: CoreDashboardHomeHandlerService.PAGE_NAME, class: 'core-courses-dashboard-handler', icon: 'fas-tachometer-alt', selectPriority: 1000, @@ -63,3 +64,5 @@ export class CoreDashboardHomeHandler implements CoreMainMenuHomeHandler { } } + +export class CoreDashboardHomeHandler extends makeSingleton(CoreDashboardHomeHandlerService) {} diff --git a/src/core/features/courses/services/handlers/my-courses.home.ts b/src/core/features/courses/services/handlers/my-courses.home.ts index 440bff94d..49477e125 100644 --- a/src/core/features/courses/services/handlers/my-courses.home.ts +++ b/src/core/features/courses/services/handlers/my-courses.home.ts @@ -14,12 +14,13 @@ import { Injectable } from '@angular/core'; import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; +import { makeSingleton } from '@singletons'; /** * Handler to add my courses into home page. */ -Injectable(); -export class CoreCoursesMyCoursesHomeHandler implements CoreMainMenuHomeHandler { +@Injectable({ providedIn: 'root' }) +export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeHandler { static readonly PAGE_NAME = 'courses'; @@ -55,7 +56,7 @@ export class CoreCoursesMyCoursesHomeHandler implements CoreMainMenuHomeHandler getDisplayData(): CoreMainMenuHomeHandlerToDisplay { return { title: 'core.courses.mycourses', - page: CoreCoursesMyCoursesHomeHandler.PAGE_NAME, + page: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME, class: 'core-courses-my-courses-handler', icon: 'fas-graduation-cap', selectPriority: 900, @@ -63,3 +64,5 @@ export class CoreCoursesMyCoursesHomeHandler implements CoreMainMenuHomeHandler } } + +export class CoreCoursesMyCoursesHomeHandler extends makeSingleton(CoreCoursesMyCoursesHomeHandlerService) {} diff --git a/src/core/features/fileuploader/fileuploader.module.ts b/src/core/features/fileuploader/fileuploader.module.ts index 819b0a747..24ff3c977 100644 --- a/src/core/features/fileuploader/fileuploader.module.ts +++ b/src/core/features/fileuploader/fileuploader.module.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { CoreFileUploaderDelegate } from './services/fileuploader-delegate'; import { CoreFileUploaderAlbumHandler } from './services/handlers/album'; @@ -26,28 +26,18 @@ import { CoreFileUploaderVideoHandler } from './services/handlers/video'; imports: [], declarations: [], providers: [ - CoreFileUploaderAlbumHandler, - CoreFileUploaderAudioHandler, - CoreFileUploaderCameraHandler, - CoreFileUploaderFileHandler, - CoreFileUploaderVideoHandler, + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreFileUploaderDelegate.instance.registerHandler(CoreFileUploaderAlbumHandler.instance); + CoreFileUploaderDelegate.instance.registerHandler(CoreFileUploaderAudioHandler.instance); + CoreFileUploaderDelegate.instance.registerHandler(CoreFileUploaderCameraHandler.instance); + CoreFileUploaderDelegate.instance.registerHandler(CoreFileUploaderVideoHandler.instance); + CoreFileUploaderDelegate.instance.registerHandler(CoreFileUploaderFileHandler.instance); + }, + }, ], }) -export class CoreFileUploaderModule { - - constructor( - delegate: CoreFileUploaderDelegate, - albumHandler: CoreFileUploaderAlbumHandler, - audioHandler: CoreFileUploaderAudioHandler, - cameraHandler: CoreFileUploaderCameraHandler, - videoHandler: CoreFileUploaderVideoHandler, - fileHandler: CoreFileUploaderFileHandler, - ) { - delegate.registerHandler(albumHandler); - delegate.registerHandler(audioHandler); - delegate.registerHandler(cameraHandler); - delegate.registerHandler(videoHandler); - delegate.registerHandler(fileHandler); - } - -} +export class CoreFileUploaderModule {} diff --git a/src/core/features/fileuploader/services/fileuploader-delegate.ts b/src/core/features/fileuploader/services/fileuploader-delegate.ts index 8fbb9852d..ec00018ab 100644 --- a/src/core/features/fileuploader/services/fileuploader-delegate.ts +++ b/src/core/features/fileuploader/services/fileuploader-delegate.ts @@ -18,6 +18,7 @@ import { FileEntry } from '@ionic-native/file'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreEvents } from '@singletons/events'; import { CoreWSUploadFileResult } from '@services/ws'; +import { makeSingleton } from '@singletons'; /** * Interface that all handlers must implement. @@ -141,7 +142,7 @@ export interface CoreFileUploaderHandlerDataToReturn extends CoreFileUploaderHan @Injectable({ providedIn: 'root', }) -export class CoreFileUploaderDelegate extends CoreDelegate { +export class CoreFileUploaderDelegateService extends CoreDelegate { constructor() { super('CoreFileUploaderDelegate', true); @@ -196,3 +197,5 @@ export class CoreFileUploaderDelegate extends CoreDelegate; protected actionSheet?: HTMLIonActionSheetElement; - constructor(protected uploaderDelegate: CoreFileUploaderDelegate) { + constructor() { this.logger = CoreLogger.getInstance('CoreFileUploaderHelperProvider'); } @@ -351,7 +351,7 @@ export class CoreFileUploaderHelperProvider { this.filePickerClosed(); }, }]; - const handlers = this.uploaderDelegate.getHandlers(mimetypes); + const handlers = CoreFileUploaderDelegate.instance.getHandlers(mimetypes); this.filePickerDeferred = CoreUtils.instance.promiseDefer(); diff --git a/src/core/features/fileuploader/services/handlers/album.ts b/src/core/features/fileuploader/services/handlers/album.ts index 36b7e2ed4..e926580bf 100644 --- a/src/core/features/fileuploader/services/handlers/album.ts +++ b/src/core/features/fileuploader/services/handlers/album.ts @@ -16,14 +16,15 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate'; import { CoreFileUploaderHelper } from '../fileuploader-helper'; /** * Handler to upload files from the album. */ -@Injectable() -export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { +@Injectable({ providedIn: 'root' }) +export class CoreFileUploaderAlbumHandlerService implements CoreFileUploaderHandler { name = 'CoreFileUploaderAlbum'; priority = 2000; @@ -75,3 +76,5 @@ export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { } } + +export class CoreFileUploaderAlbumHandler extends makeSingleton(CoreFileUploaderAlbumHandlerService) {} diff --git a/src/core/features/fileuploader/services/handlers/audio.ts b/src/core/features/fileuploader/services/handlers/audio.ts index ef4ccfaa4..a57cb3b8d 100644 --- a/src/core/features/fileuploader/services/handlers/audio.ts +++ b/src/core/features/fileuploader/services/handlers/audio.ts @@ -16,13 +16,14 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate'; import { CoreFileUploaderHelper } from '../fileuploader-helper'; /** * Handler to record an audio to upload it. */ -@Injectable() -export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { +@Injectable({ providedIn: 'root' }) +export class CoreFileUploaderAudioHandlerService implements CoreFileUploaderHandler { name = 'CoreFileUploaderAudio'; priority = 1600; @@ -90,3 +91,5 @@ export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { } } + +export class CoreFileUploaderAudioHandler extends makeSingleton(CoreFileUploaderAudioHandlerService) {} \ No newline at end of file diff --git a/src/core/features/fileuploader/services/handlers/camera.ts b/src/core/features/fileuploader/services/handlers/camera.ts index 682b0cfeb..d1aa80036 100644 --- a/src/core/features/fileuploader/services/handlers/camera.ts +++ b/src/core/features/fileuploader/services/handlers/camera.ts @@ -16,14 +16,15 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate'; import { CoreFileUploaderHelper } from '../fileuploader-helper'; /** * Handler to take a picture to upload it. */ -@Injectable() -export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { +@Injectable({ providedIn: 'root' }) +export class CoreFileUploaderCameraHandlerService implements CoreFileUploaderHandler { name = 'CoreFileUploaderCamera'; priority = 1800; @@ -75,3 +76,5 @@ export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { } } + +export class CoreFileUploaderCameraHandler extends makeSingleton(CoreFileUploaderCameraHandlerService) {} diff --git a/src/core/features/fileuploader/services/handlers/file.ts b/src/core/features/fileuploader/services/handlers/file.ts index 995669b34..11e1caff0 100644 --- a/src/core/features/fileuploader/services/handlers/file.ts +++ b/src/core/features/fileuploader/services/handlers/file.ts @@ -19,13 +19,13 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate'; import { CoreFileUploaderHelper } from '../fileuploader-helper'; import { CoreFileUploader } from '../fileuploader'; -import { Translate } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; /** * Handler to upload any type of file. */ -@Injectable() -export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { +@Injectable({ providedIn: 'root' }) +export class CoreFileUploaderFileHandlerService implements CoreFileUploaderHandler { name = 'CoreFileUploaderFile'; priority = 1200; @@ -156,3 +156,5 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { } } + +export class CoreFileUploaderFileHandler extends makeSingleton(CoreFileUploaderFileHandlerService) {} diff --git a/src/core/features/fileuploader/services/handlers/video.ts b/src/core/features/fileuploader/services/handlers/video.ts index 71212b06a..9feb2c7b5 100644 --- a/src/core/features/fileuploader/services/handlers/video.ts +++ b/src/core/features/fileuploader/services/handlers/video.ts @@ -16,13 +16,14 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate'; import { CoreFileUploaderHelper } from '../fileuploader-helper'; /** * Handler to record a video to upload it. */ -@Injectable() -export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { +@Injectable({ providedIn: 'root' }) +export class CoreFileUploaderVideoHandlerService implements CoreFileUploaderHandler { name = 'CoreFileUploaderVideo'; priority = 1400; @@ -90,3 +91,5 @@ export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { } } + +export class CoreFileUploaderVideoHandler extends makeSingleton(CoreFileUploaderVideoHandlerService) {} diff --git a/src/core/features/mainmenu/mainmenu-lazy.module.ts b/src/core/features/mainmenu/mainmenu-lazy.module.ts index 7fb36e1c0..0ba61adb2 100644 --- a/src/core/features/mainmenu/mainmenu-lazy.module.ts +++ b/src/core/features/mainmenu/mainmenu-lazy.module.ts @@ -25,7 +25,7 @@ import { resolveModuleRoutes } from '@/app/app-routing.module'; import { MAIN_MENU_ROUTES } from './mainmenu-routing.module'; import { CoreMainMenuPage } from './pages/menu/menu'; -import { CoreMainMenuHomeHandler } from './services/handlers/mainmenu'; +import { CoreMainMenuHomeHandlerService } from './services/handlers/mainmenu'; function buildRoutes(injector: Injector): Routes { const routes = resolveModuleRoutes(injector, MAIN_MENU_ROUTES); @@ -38,10 +38,10 @@ function buildRoutes(injector: Injector): Routes { { path: '', pathMatch: 'full', - redirectTo: CoreMainMenuHomeHandler.PAGE_NAME, + redirectTo: CoreMainMenuHomeHandlerService.PAGE_NAME, }, { - path: CoreMainMenuHomeHandler.PAGE_NAME, + path: CoreMainMenuHomeHandlerService.PAGE_NAME, loadChildren: () => import('./pages/home/home.module').then(m => m.CoreMainMenuHomePageModule), }, { @@ -68,7 +68,6 @@ function buildRoutes(injector: Injector): Routes { CoreMainMenuPage, ], providers: [ - CoreMainMenuHomeHandler, { provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] }, ], }) diff --git a/src/core/features/mainmenu/mainmenu.module.ts b/src/core/features/mainmenu/mainmenu.module.ts index 384ecdd2a..80842b4ed 100644 --- a/src/core/features/mainmenu/mainmenu.module.ts +++ b/src/core/features/mainmenu/mainmenu.module.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Routes } from '@angular/router'; import { AuthGuard } from '@guards/auth'; @@ -37,14 +37,15 @@ const appRoutes: Routes = [ @NgModule({ imports: [AppRoutingModule.forChild(appRoutes)], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreMainMenuDelegate.instance.registerHandler(CoreMainMenuHomeHandler.instance); + }, + }, + ], }) -export class CoreMainMenuModule { - - constructor( - mainMenuDelegate: CoreMainMenuDelegate, - homeMainMenuHandler: CoreMainMenuHomeHandler, - ) { - mainMenuDelegate.registerHandler(homeMainMenuHandler); - } - -} +export class CoreMainMenuModule {} diff --git a/src/core/features/mainmenu/pages/home/home.ts b/src/core/features/mainmenu/pages/home/home.ts index 7d505d6dc..ab24ce8aa 100644 --- a/src/core/features/mainmenu/pages/home/home.ts +++ b/src/core/features/mainmenu/pages/home/home.ts @@ -40,15 +40,13 @@ export class CoreMainMenuHomePage implements OnInit { protected subscription?: Subscription; protected updateSiteObserver?: CoreEventObserver; - constructor(protected homeDelegate: CoreMainMenuHomeDelegate) {} - /** * Initialize the component. */ ngOnInit(): void { this.loadSiteName(); - this.subscription = this.homeDelegate.getHandlersObservable().subscribe((handlers) => { + this.subscription = CoreMainMenuHomeDelegate.instance.getHandlersObservable().subscribe((handlers) => { handlers && this.initHandlers(handlers); }); @@ -90,7 +88,7 @@ export class CoreMainMenuHomePage implements OnInit { this.tabs = newTabs; - this.loaded = this.homeDelegate.areHandlersLoaded(); + this.loaded = CoreMainMenuHomeDelegate.instance.areHandlersLoaded(); } /** diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index 4d921683d..5d58bd6d6 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -57,7 +57,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { constructor( protected route: ActivatedRoute, protected navCtrl: NavController, - protected menuDelegate: CoreMainMenuDelegate, protected changeDetector: ChangeDetectorRef, protected router: Router, ) { @@ -98,7 +97,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { } }); - this.subscription = this.menuDelegate.getHandlersObservable().subscribe((handlers) => { + this.subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => { // Remove the handlers that should only appear in the More menu. this.allHandlers = handlers.filter((handler) => !handler.onlyInMore); @@ -164,7 +163,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { // Sort them by priority so new handlers are in the right position. this.tabs.sort((a, b) => (b.priority || 0) - (a.priority || 0)); - this.loaded = this.menuDelegate.areHandlersLoaded(); + this.loaded = CoreMainMenuDelegate.instance.areHandlersLoaded(); if (this.loaded && this.mainTabs && !this.mainTabs.getSelected()) { // Select the first tab. diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts index 48b466d84..8db63b6ec 100644 --- a/src/core/features/mainmenu/pages/more/more.ts +++ b/src/core/features/mainmenu/pages/more/more.ts @@ -49,9 +49,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { protected langObserver: CoreEventObserver; protected updateSiteObserver: CoreEventObserver; - constructor( - protected menuDelegate: CoreMainMenuDelegate, - ) { + constructor() { this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this)); this.updateSiteObserver = CoreEvents.on( @@ -69,7 +67,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { */ ngOnInit(): void { // Load the handlers. - this.subscription = this.menuDelegate.getHandlersObservable().subscribe((handlers) => { + this.subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => { this.allHandlers = handlers; this.initHandlers(); @@ -104,7 +102,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { // Get only the handlers that don't appear in the main view. this.handlers = this.allHandlers.filter((handler) => mainHandlers.indexOf(handler) == -1); - this.handlersLoaded = this.menuDelegate.areHandlersLoaded(); + this.handlersLoaded = CoreMainMenuDelegate.instance.areHandlersLoaded(); } /** diff --git a/src/core/features/mainmenu/services/handlers/mainmenu.ts b/src/core/features/mainmenu/services/handlers/mainmenu.ts index 06c4a8046..a530dafa0 100644 --- a/src/core/features/mainmenu/services/handlers/mainmenu.ts +++ b/src/core/features/mainmenu/services/handlers/mainmenu.ts @@ -13,13 +13,14 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { makeSingleton } from '@singletons'; import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../mainmenu-delegate'; /** * Handler to add Home into main menu. */ @Injectable({ providedIn: 'root' }) -export class CoreMainMenuHomeHandler implements CoreMainMenuHandler { +export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler { static readonly PAGE_NAME = 'home'; @@ -56,10 +57,12 @@ export class CoreMainMenuHomeHandler implements CoreMainMenuHandler { return { icon: 'fa-home', title: 'core.mainmenu.home', - page: CoreMainMenuHomeHandler.PAGE_NAME, + page: CoreMainMenuHomeHandlerService.PAGE_NAME, // @todo: subPage? The page can change due to core-tabs. class: 'core-home-handler', }; } } + +export class CoreMainMenuHomeHandler extends makeSingleton(CoreMainMenuHomeHandlerService) {} diff --git a/src/core/features/mainmenu/services/home-delegate.ts b/src/core/features/mainmenu/services/home-delegate.ts index 67eca45e5..2f553f9b1 100644 --- a/src/core/features/mainmenu/services/home-delegate.ts +++ b/src/core/features/mainmenu/services/home-delegate.ts @@ -17,6 +17,7 @@ import { Params } from '@angular/router'; import { CoreDelegateDisplayHandler, CoreDelegateToDisplay } from '@classes/delegate'; import { CoreSortedDelegate } from '@classes/delegate-sorted'; +import { makeSingleton } from '@singletons'; /** * Interface that all home handlers must implement. @@ -85,7 +86,7 @@ export interface CoreMainMenuHomeHandlerToDisplay extends CoreDelegateToDisplay, @Injectable({ providedIn: 'root', }) -export class CoreMainMenuHomeDelegate extends CoreSortedDelegate { +export class CoreMainMenuHomeDelegateService extends CoreSortedDelegate { protected featurePrefix = 'CoreMainMenuHomeDelegate_'; @@ -94,3 +95,5 @@ export class CoreMainMenuHomeDelegate extends CoreSortedDelegate { +export class CoreMainMenuDelegateService extends CoreSortedDelegate { protected featurePrefix = 'CoreMainMenuDelegate_'; @@ -106,3 +107,5 @@ export class CoreMainMenuDelegate extends CoreSortedDelegate= 576 && window.innerHeight >= 576); } @@ -47,7 +47,7 @@ export class CoreMainMenuProvider { getCurrentMainMenuHandlers(): Promise { const deferred = CoreUtils.instance.promiseDefer(); - const subscription = this.menuDelegate.getHandlersObservable().subscribe((handlers) => { + const subscription = CoreMainMenuDelegate.instance.getHandlersObservable().subscribe((handlers) => { subscription?.unsubscribe(); // Remove the handlers that should only appear in the More menu. diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts index bd8acb841..e3f1ad2bb 100644 --- a/src/core/features/settings/pages/site/site.ts +++ b/src/core/features/settings/pages/site/site.ts @@ -57,7 +57,6 @@ export class CoreSitePreferencesPage implements OnInit, OnDestroy { protected isDestroyed = false; constructor( - protected settingsDelegate: CoreSettingsDelegate, protected route: ActivatedRoute, protected router: Router, // Will be removed when splitview is implemented ) { @@ -97,7 +96,7 @@ export class CoreSitePreferencesPage implements OnInit, OnDestroy { * Fetch Data. */ protected async fetchData(): Promise { - this.handlers = this.settingsDelegate.getHandlers(); + this.handlers = CoreSettingsDelegate.instance.getHandlers(); const currentSite = CoreSites.instance.getCurrentSite(); this.siteInfo = currentSite!.getInfo(); diff --git a/src/core/features/settings/services/settings-delegate.ts b/src/core/features/settings/services/settings-delegate.ts index 68f3deec1..e9872e02c 100644 --- a/src/core/features/settings/services/settings-delegate.ts +++ b/src/core/features/settings/services/settings-delegate.ts @@ -17,6 +17,7 @@ import { Params } from '@angular/router'; import { CoreDelegateDisplayHandler, CoreDelegateToDisplay } from '@classes/delegate'; import { CoreSortedDelegate } from '@classes/delegate-sorted'; +import { makeSingleton } from '@singletons'; /** * Interface that all settings handlers must implement. @@ -65,10 +66,12 @@ export type CoreSettingsHandlerToDisplay = CoreDelegateToDisplay & CoreSettingsH @Injectable({ providedIn: 'root', }) -export class CoreSettingsDelegate extends CoreSortedDelegate { +export class CoreSettingsDelegateService extends CoreSortedDelegate { constructor() { super('CoreSettingsDelegate'); } } + +export class CoreSettingsDelegate extends makeSingleton(CoreSettingsDelegateService) {} diff --git a/src/core/features/settings/services/settings-helper.ts b/src/core/features/settings/services/settings-helper.ts index 43b04d2c3..cc462ba2c 100644 --- a/src/core/features/settings/services/settings-helper.ts +++ b/src/core/features/settings/services/settings-helper.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; -import { CoreCron } from '@services/cron'; +import { CoreCronDelegate } from '@services/cron'; import { CoreEvents } from '@singletons/events'; import { CoreFilepool } from '@services/filepool'; import { CoreSite } from '@classes/site'; @@ -277,7 +277,7 @@ export class CoreSettingsHelperProvider { } const site = await CoreSites.instance.getSite(siteId); - const hasSyncHandlers = CoreCron.instance.hasManualSyncHandlers(); + const hasSyncHandlers = CoreCronDelegate.instance.hasManualSyncHandlers(); if (site.isLoggedOut()) { // Cannot sync logged out sites. @@ -296,7 +296,7 @@ export class CoreSettingsHelperProvider { site.invalidateWsCache(), this.checkSiteLocalMobile(site), CoreSites.instance.updateSiteInfo(site.getId()), - CoreCron.instance.forceSyncExecution(site.getId()), + CoreCronDelegate.instance.forceSyncExecution(site.getId()), // eslint-disable-next-line arrow-body-style ]).then(() => { return; diff --git a/src/core/features/sitehome/services/handlers/index-link.ts b/src/core/features/sitehome/services/handlers/index-link.ts index c3dc6fcbc..7faa9d8ee 100644 --- a/src/core/features/sitehome/services/handlers/index-link.ts +++ b/src/core/features/sitehome/services/handlers/index-link.ts @@ -19,12 +19,13 @@ import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreSiteHome } from '../sitehome'; +import { makeSingleton } from '@singletons'; /** * Handler to treat links to site home index. */ -Injectable(); -export class CoreSiteHomeIndexLinkHandler extends CoreContentLinksHandlerBase { +@Injectable({ providedIn: 'root' }) +export class CoreSiteHomeIndexLinkHandlerService extends CoreContentLinksHandlerBase { name = 'CoreSiteHomeIndexLinkHandler'; featureName = 'CoreMainMenuDelegate_CoreSiteHome'; @@ -73,3 +74,5 @@ export class CoreSiteHomeIndexLinkHandler extends CoreContentLinksHandlerBase { } } + +export class CoreSiteHomeIndexLinkHandler extends makeSingleton(CoreSiteHomeIndexLinkHandlerService) {} diff --git a/src/core/features/sitehome/services/handlers/sitehome-home.ts b/src/core/features/sitehome/services/handlers/sitehome-home.ts index 491f1c498..96b64d941 100644 --- a/src/core/features/sitehome/services/handlers/sitehome-home.ts +++ b/src/core/features/sitehome/services/handlers/sitehome-home.ts @@ -16,12 +16,13 @@ import { Injectable } from '@angular/core'; import { CoreSites } from '@services/sites'; import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; import { CoreSiteHome } from '../sitehome'; +import { makeSingleton } from '@singletons'; /** * Handler to add site home into home page. */ -Injectable(); -export class CoreSiteHomeHomeHandler implements CoreMainMenuHomeHandler { +@Injectable({ providedIn: 'root' }) +export class CoreSiteHomeHomeHandlerService implements CoreMainMenuHomeHandler { static readonly PAGE_NAME = 'site'; @@ -58,7 +59,7 @@ export class CoreSiteHomeHomeHandler implements CoreMainMenuHomeHandler { return { title: 'core.sitehome.sitehome', - page: CoreSiteHomeHomeHandler.PAGE_NAME, + page: CoreSiteHomeHomeHandlerService.PAGE_NAME, class: 'core-sitehome-dashboard-handler', icon: 'fas-home', selectPriority: displaySiteHome ? 1100 : 900, @@ -66,3 +67,5 @@ export class CoreSiteHomeHomeHandler implements CoreMainMenuHomeHandler { } } + +export class CoreSiteHomeHomeHandler extends makeSingleton(CoreSiteHomeHomeHandlerService) {} diff --git a/src/core/features/sitehome/sitehome.module.ts b/src/core/features/sitehome/sitehome.module.ts index dd611c476..b302ff2d8 100644 --- a/src/core/features/sitehome/sitehome.module.ts +++ b/src/core/features/sitehome/sitehome.module.ts @@ -12,18 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Routes } from '@angular/router'; import { CoreSiteHomeIndexLinkHandler } from './services/handlers/index-link'; import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; -import { CoreSiteHomeHomeHandler } from './services/handlers/sitehome-home'; +import { CoreSiteHomeHomeHandler, CoreSiteHomeHomeHandlerService } from './services/handlers/sitehome-home'; import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate'; import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module'; const mainMenuHomeRoutes: Routes = [ { - path: CoreSiteHomeHomeHandler.PAGE_NAME, + path: CoreSiteHomeHomeHandlerService.PAGE_NAME, loadChildren: () => import('./pages/index/index.module').then(m => m.CoreSiteHomeIndexPageModule), }, ]; @@ -32,20 +32,15 @@ const mainMenuHomeRoutes: Routes = [ imports: [CoreMainMenuHomeRoutingModule.forChild({ children: mainMenuHomeRoutes })], exports: [CoreMainMenuHomeRoutingModule], providers: [ - CoreSiteHomeIndexLinkHandler, - CoreSiteHomeHomeHandler, + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreContentLinksDelegate.instance.registerHandler(CoreSiteHomeIndexLinkHandler.instance); + CoreMainMenuHomeDelegate.instance.registerHandler(CoreSiteHomeHomeHandler.instance); + }, + }, ], }) -export class CoreSiteHomeModule { - - constructor( - contentLinksDelegate: CoreContentLinksDelegate, - homeDelegate: CoreMainMenuHomeDelegate, - siteHomeIndexLinkHandler: CoreSiteHomeIndexLinkHandler, - siteHomeDashboardHandler: CoreSiteHomeHomeHandler, - ) { - contentLinksDelegate.registerHandler(siteHomeIndexLinkHandler); - homeDelegate.registerHandler(siteHomeDashboardHandler); - } - -} +export class CoreSiteHomeModule {} diff --git a/src/core/initializers/initialize-databases.ts b/src/core/initializers/initialize-databases.ts index 462aeaeef..9772871af 100644 --- a/src/core/initializers/initialize-databases.ts +++ b/src/core/initializers/initialize-databases.ts @@ -14,7 +14,7 @@ import { CoreApp } from '@services/app'; import { CoreConfig } from '@services/config'; -import { CoreCron } from '@services/cron'; +import { CoreCronDelegate } from '@services/cron'; import { CoreFilepool } from '@services/filepool'; import { CoreLocalNotifications } from '@services/local-notifications'; import { CoreSites } from '@services/sites'; @@ -23,7 +23,7 @@ export default async function(): Promise { await Promise.all([ CoreApp.instance.initialiseDatabase(), CoreConfig.instance.initialiseDatabase(), - CoreCron.instance.initialiseDatabase(), + CoreCronDelegate.instance.initialiseDatabase(), CoreFilepool.instance.initialiseDatabase(), CoreLocalNotifications.instance.initialiseDatabase(), CoreSites.instance.initialiseDatabase(), diff --git a/src/core/initializers/prepare-automated-tests.ts b/src/core/initializers/prepare-automated-tests.ts index 68b3298f5..4440e96fa 100644 --- a/src/core/initializers/prepare-automated-tests.ts +++ b/src/core/initializers/prepare-automated-tests.ts @@ -14,19 +14,19 @@ import { ApplicationRef } from '@angular/core'; import { CoreApp, CoreAppProvider } from '@services/app'; -import { CoreCron, CoreCronDelegate } from '@services/cron'; +import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron'; import { Application } from '@singletons'; type AutomatedTestsWindow = Window & { appRef?: ApplicationRef; appProvider?: CoreAppProvider; - cronProvider?: CoreCronDelegate; + cronProvider?: CoreCronDelegateService; }; function initializeAutomatedTestsWindow(window: AutomatedTestsWindow) { window.appRef = Application.instance; window.appProvider = CoreApp.instance; - window.cronProvider = CoreCron.instance; + window.cronProvider = CoreCronDelegate.instance; } export default function(): void { diff --git a/src/core/services/cron.ts b/src/core/services/cron.ts index bf799d90c..c30524cee 100644 --- a/src/core/services/cron.ts +++ b/src/core/services/cron.ts @@ -29,7 +29,7 @@ import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/database/cro * Service to handle cron processes. The registered processes will be executed every certain time. */ @Injectable({ providedIn: 'root' }) -export class CoreCronDelegate { +export class CoreCronDelegateService { // Constants. static readonly DEFAULT_INTERVAL = 3600000; // Default interval is 1 hour. @@ -108,7 +108,7 @@ export class CoreCronDelegate { // Cannot execute in this network connection, retry soon. const message = `Cannot execute handler because device is using limited connection: ${name}`; this.logger.debug(message); - this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL); + this.scheduleNextExecution(name, CoreCronDelegateService.MIN_INTERVAL); throw new CoreError(message); } @@ -130,7 +130,7 @@ export class CoreCronDelegate { // Handler call failed. Retry soon. const message = `Execution of handler '${name}' failed.`; this.logger.error(message, error); - this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL); + this.scheduleNextExecution(name, CoreCronDelegateService.MIN_INTERVAL); throw new CoreError(message); } @@ -160,7 +160,7 @@ export class CoreCronDelegate { // The handler took too long. Resolve because we don't want to retry soon. this.logger.debug(`Resolving execution of handler '${name}' because it took too long.`); resolve(); - }, CoreCronDelegate.MAX_TIME_PROCESS); + }, CoreCronDelegateService.MAX_TIME_PROCESS); }); } @@ -215,15 +215,15 @@ export class CoreCronDelegate { protected getHandlerInterval(name: string): number { if (!this.handlers[name] || !this.handlers[name].getInterval) { // Invalid, return default. - return CoreCronDelegate.DEFAULT_INTERVAL; + return CoreCronDelegateService.DEFAULT_INTERVAL; } // Don't allow intervals lower than the minimum. - const minInterval = CoreCronDelegate.MIN_INTERVAL; + const minInterval = CoreCronDelegateService.MIN_INTERVAL; const handlerInterval = this.handlers[name].getInterval!(); if (!handlerInterval) { - return CoreCronDelegate.DEFAULT_INTERVAL; + return CoreCronDelegateService.DEFAULT_INTERVAL; } else { return Math.max(minInterval, handlerInterval); } @@ -476,7 +476,7 @@ export class CoreCronDelegate { } -export class CoreCron extends makeSingleton(CoreCronDelegate) {} +export class CoreCronDelegate extends makeSingleton(CoreCronDelegateService) {} /** @@ -499,7 +499,7 @@ export interface CoreCronHandler { timeout?: number; /** - * Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL. + * Returns handler's interval in milliseconds. Defaults to CoreCronDelegateService.DEFAULT_INTERVAL. * * @return Interval time (in milliseconds). */ diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index 092a3ed54..52b84b08a 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -18,7 +18,7 @@ import { Md5 } from 'ts-md5/dist/md5'; import { CoreApp } from '@services/app'; import { CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; -import { CorePluginFile } from '@services/plugin-file-delegate'; +import { CorePluginFileDelegate } from '@services/plugin-file-delegate'; import { CoreSites } from '@services/sites'; import { CoreWS, CoreWSExternalFile } from '@services/ws'; import { CoreDomUtils } from '@services/utils/dom'; @@ -697,7 +697,7 @@ export class CoreFilepoolProvider { const entry = await CoreWS.instance.downloadFile(fileUrl, path, addExtension, onProgress); const fileEntry = entry; - await CorePluginFile.instance.treatDownloadedFile(fileUrl, fileEntry, siteId, onProgress); + await CorePluginFileDelegate.instance.treatDownloadedFile(fileUrl, fileEntry, siteId, onProgress); await this.addFileToPool(siteId, fileId, { downloadTime: Date.now(), @@ -1062,7 +1062,7 @@ export class CoreFilepoolProvider { } // Now get other files from plugin file handlers. - urls = urls.concat(CorePluginFile.instance.getDownloadableFilesFromHTML(element)); + urls = urls.concat(CorePluginFileDelegate.instance.getDownloadableFilesFromHTML(element)); return urls; } @@ -1159,7 +1159,7 @@ export class CoreFilepoolProvider { * @return Promise resolved with the file data to use. */ protected async fixPluginfileURL(siteId: string, fileUrl: string, timemodified: number = 0): Promise { - const file = await CorePluginFile.instance.getDownloadableFile({ fileurl: fileUrl, timemodified }); + const file = await CorePluginFileDelegate.instance.getDownloadableFile({ fileurl: fileUrl, timemodified }); const site = await CoreSites.instance.getSite(siteId); file.fileurl = await site.checkAndFixPluginfileURL(file.fileurl); @@ -1918,7 +1918,7 @@ export class CoreFilepoolProvider { return 0; } - const revisionRegex = CorePluginFile.instance.getComponentRevisionRegExp(args); + const revisionRegex = CorePluginFileDelegate.instance.getComponentRevisionRegExp(args); if (!revisionRegex) { return 0; } @@ -2662,7 +2662,7 @@ export class CoreFilepoolProvider { this.notifyFileDeleted(siteId, fileId, links); if (fileUrl) { - await CoreUtils.instance.ignoreErrors(CorePluginFile.instance.fileDeleted(fileUrl, path, siteId)); + await CoreUtils.instance.ignoreErrors(CorePluginFileDelegate.instance.fileDeleted(fileUrl, path, siteId)); } } @@ -2710,7 +2710,7 @@ export class CoreFilepoolProvider { return url; } - return CorePluginFile.instance.removeRevisionFromUrl(url, args); + return CorePluginFileDelegate.instance.removeRevisionFromUrl(url, args); } /** diff --git a/src/core/services/handlers/site-info-cron.ts b/src/core/services/handlers/site-info-cron.ts index 85a86852e..1cb3063e3 100644 --- a/src/core/services/handlers/site-info-cron.ts +++ b/src/core/services/handlers/site-info-cron.ts @@ -42,7 +42,7 @@ export class CoreSiteInfoCronHandler implements CoreCronHandler { } /** - * Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL. + * Returns handler's interval in milliseconds. Defaults to CoreCronDelegateService.DEFAULT_INTERVAL. * * @return Interval time (in milliseconds). */ diff --git a/src/core/services/plugin-file-delegate.ts b/src/core/services/plugin-file-delegate.ts index f52904356..67c377e33 100644 --- a/src/core/services/plugin-file-delegate.ts +++ b/src/core/services/plugin-file-delegate.ts @@ -25,7 +25,7 @@ import { makeSingleton } from '@singletons'; * Delegate to register pluginfile information handlers. */ @Injectable({ providedIn: 'root' }) -export class CorePluginFileDelegate extends CoreDelegate { +export class CorePluginFileDelegateService extends CoreDelegate { protected handlerNameProperty = 'component'; @@ -288,7 +288,7 @@ export class CorePluginFileDelegate extends CoreDelegate } -export class CorePluginFile extends makeSingleton(CorePluginFileDelegate) {} +export class CorePluginFileDelegate extends makeSingleton(CorePluginFileDelegateService) {} /** * Interface that all plugin file handlers must implement.