From aa60d8eaeb087829fac714564b04db63b4620229 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 9 Dec 2020 12:59:45 +0100 Subject: [PATCH] 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; }