forked from CIT/Vmeda.Online
		
	Merge pull request #2814 from NoelDeMartin/MOBILE-3320
MOBILE-3320: Forum improvements
This commit is contained in:
		
						commit
						1cfadedeec
					
				@ -566,14 +566,14 @@ export class AddonCalendarProvider {
 | 
			
		||||
     */
 | 
			
		||||
    async getCalendarLookAhead(siteId?: string): Promise<number> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        let value: string | undefined;
 | 
			
		||||
        let value: string | undefined | null;
 | 
			
		||||
        try {
 | 
			
		||||
            value = await CoreUser.getUserPreference('calendar_lookahead');
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (typeof value == 'undefined') {
 | 
			
		||||
        if (typeof value == 'undefined' || value === null) {
 | 
			
		||||
            value = site.getStoredConfig('calendar_lookahead');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -588,7 +588,7 @@ export class AddonCalendarProvider {
 | 
			
		||||
     */
 | 
			
		||||
    async getCalendarTimeFormat(siteId?: string): Promise<string> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        let format: string | undefined;
 | 
			
		||||
        let format: string | undefined | null;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            format = await CoreUser.getUserPreference('calendar_timeformat');
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@
 | 
			
		||||
        </ion-label>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
    <ion-item class="ion-text-wrap">
 | 
			
		||||
        <ion-label></ion-label>
 | 
			
		||||
        <ion-label class="sr-only">{{ plugin.name }}</ion-label>
 | 
			
		||||
        <core-rich-text-editor [control]="control" [placeholder]="plugin.name"
 | 
			
		||||
            name="onlinetext_editor_text" (contentChanged)="onChange($event)" [component]="component"
 | 
			
		||||
            [componentId]="assign.cmid" [autoSave]="true" contextLevel="module" [contextInstanceId]="assign.cmid"
 | 
			
		||||
 | 
			
		||||
@ -16,11 +16,12 @@ import { AddonModDataFieldPluginComponent } from '@addons/mod/data/classes/field
 | 
			
		||||
import { AddonModDataEntryField } from '@addons/mod/data/services/data';
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { FormBuilder } from '@angular/forms';
 | 
			
		||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
 | 
			
		||||
import { SafeUrl } from '@angular/platform-browser';
 | 
			
		||||
import { CoreAnyError } from '@classes/errors/error';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreGeolocation, CoreGeolocationError, CoreGeolocationErrorReason } from '@services/geolocation';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { DomSanitizer } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to render data latlong field.
 | 
			
		||||
@ -35,10 +36,7 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
 | 
			
		||||
    east?: number;
 | 
			
		||||
    locationServicesEnabled = false;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        fb: FormBuilder,
 | 
			
		||||
        protected sanitizer: DomSanitizer,
 | 
			
		||||
    ) {
 | 
			
		||||
    constructor(fb: FormBuilder) {
 | 
			
		||||
        super(fb);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -82,7 +80,7 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.sanitizer.bypassSecurityTrustUrl(url);
 | 
			
		||||
        return DomSanitizer.bypassSecurityTrustUrl(url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -11,12 +11,12 @@
 | 
			
		||||
            [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'far-newspaper'" (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item
 | 
			
		||||
            *ngIf="discussions.onlineLoaded && discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline"
 | 
			
		||||
            *ngIf="discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline"
 | 
			
		||||
            [priority]="700" [content]="'addon.mod_forum.refreshdiscussions' | translate" [iconAction]="refreshIcon" [closeOnClick]="false"
 | 
			
		||||
            (action)="doRefresh(null, $event)">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item
 | 
			
		||||
            *ngIf="discussions.onlineLoaded && discussions.loaded && (hasOffline || hasOfflineRatings) && isOnline"
 | 
			
		||||
            *ngIf="discussions.loaded && (hasOffline || hasOfflineRatings) && isOnline"
 | 
			
		||||
            [priority]="600" [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false"
 | 
			
		||||
            (action)="doRefresh(null, $event, true)">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
@ -39,11 +39,11 @@
 | 
			
		||||
 | 
			
		||||
<!-- Content. -->
 | 
			
		||||
<core-split-view>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!discussions.onlineLoaded || !discussions.loaded" (ionRefresh)="doRefresh($event.target)">
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!discussions.loaded" (ionRefresh)="doRefresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <core-loading [hideUntil]="discussions.onlineLoaded && discussions.loaded">
 | 
			
		||||
    <core-loading [hideUntil]="discussions.loaded">
 | 
			
		||||
        <!-- Activity info. -->
 | 
			
		||||
        <core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
 | 
			
		||||
            (completionChanged)="onCompletionChange()">
 | 
			
		||||
@ -158,7 +158,7 @@
 | 
			
		||||
            </ion-item>
 | 
			
		||||
 | 
			
		||||
            <core-infinite-loading
 | 
			
		||||
                [enabled]="discussions.onlineLoaded && !discussions.completed" [error]="fetchMoreDiscussionsFailed"
 | 
			
		||||
                [enabled]="discussions.onlineLoaded && !discussions.completed" [error]="discussions.fetchFailed"
 | 
			
		||||
                (action)="fetchMoreDiscussions($event)">
 | 
			
		||||
            </core-infinite-loading>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
 | 
			
		||||
@ -73,7 +73,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
    moduleName = 'forum';
 | 
			
		||||
    descriptionNote?: string;
 | 
			
		||||
    forum?: AddonModForumData;
 | 
			
		||||
    fetchMoreDiscussionsFailed = false;
 | 
			
		||||
    discussions: AddonModForumDiscussionsManager;
 | 
			
		||||
    canAddDiscussion = false;
 | 
			
		||||
    addDiscussionText!: string;
 | 
			
		||||
@ -237,7 +236,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
     * @param showErrors Wether to show errors to the user or hide them.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        this.fetchMoreDiscussionsFailed = false;
 | 
			
		||||
        this.discussions.fetchFailed = false;
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
@ -259,7 +258,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
            if (refresh) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.errorgetforum', true);
 | 
			
		||||
 | 
			
		||||
                this.fetchMoreDiscussionsFailed = true; // Set to prevent infinite calls with infinite-loading.
 | 
			
		||||
                this.discussions.fetchFailed = true; // Set to prevent infinite calls with infinite-loading.
 | 
			
		||||
            } else {
 | 
			
		||||
                // Get forum failed, retry without using cache since it might be a new activity.
 | 
			
		||||
                await this.refreshContent(sync);
 | 
			
		||||
@ -422,7 +421,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchDiscussions(refresh: boolean): Promise<void> {
 | 
			
		||||
        const forum = this.forum!;
 | 
			
		||||
        this.fetchMoreDiscussionsFailed = false;
 | 
			
		||||
        this.discussions.fetchFailed = false;
 | 
			
		||||
 | 
			
		||||
        if (refresh) {
 | 
			
		||||
            this.page = 0;
 | 
			
		||||
@ -497,7 +496,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.errorgetforum', true);
 | 
			
		||||
 | 
			
		||||
            this.fetchMoreDiscussionsFailed = true;
 | 
			
		||||
            this.discussions.fetchFailed = true;
 | 
			
		||||
        } finally {
 | 
			
		||||
            complete();
 | 
			
		||||
        }
 | 
			
		||||
@ -715,6 +714,7 @@ type DiscussionItem = AddonModForumDiscussion | AddonModForumOfflineDiscussion |
 | 
			
		||||
class AddonModForumDiscussionsManager extends CorePageItemsListManager<DiscussionItem> {
 | 
			
		||||
 | 
			
		||||
    onlineLoaded = false;
 | 
			
		||||
    fetchFailed = false;
 | 
			
		||||
 | 
			
		||||
    private discussionsPathPrefix: string;
 | 
			
		||||
    private component: AddonModForumIndexComponent;
 | 
			
		||||
@ -726,6 +726,10 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio
 | 
			
		||||
        this.discussionsPathPrefix = discussionsPathPrefix;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get loaded(): boolean {
 | 
			
		||||
        return super.loaded && (this.onlineLoaded || this.fetchFailed);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get onlineDiscussions(): AddonModForumDiscussion[] {
 | 
			
		||||
        return this.items.filter(discussion => this.isOnlineDiscussion(discussion)) as AddonModForumDiscussion[];
 | 
			
		||||
    }
 | 
			
		||||
@ -782,6 +786,7 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio
 | 
			
		||||
        const otherDiscussions = this.items.filter(discussion => !this.isOnlineDiscussion(discussion));
 | 
			
		||||
 | 
			
		||||
        this.setItems(otherDiscussions.concat(onlineDiscussions), hasMoreItems);
 | 
			
		||||
        this.onlineLoaded = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -795,15 +800,6 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio
 | 
			
		||||
        this.setItems((offlineDiscussions as DiscussionItem[]).concat(otherDiscussions), this.hasMoreItems);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    setItems(discussions: DiscussionItem[], hasMoreItems: boolean = false): void {
 | 
			
		||||
        super.setItems(discussions, hasMoreItems);
 | 
			
		||||
 | 
			
		||||
        this.onlineLoaded = this.onlineLoaded || discussions.some(discussion => this.isOnlineDiscussion(discussion));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -395,7 +395,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
 | 
			
		||||
                            offlineReplies.push(reply);
 | 
			
		||||
 | 
			
		||||
                            // Disable reply of the parent. Reply in offline to the same post is not allowed, edit instead.
 | 
			
		||||
                            posts[reply.parentid!].capabilities.reply = false;
 | 
			
		||||
                            onlinePostsMap[reply.parentid!].capabilities.reply = false;
 | 
			
		||||
 | 
			
		||||
                            return;
 | 
			
		||||
                        }),
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,6 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable, Type } from '@angular/core';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
 | 
			
		||||
@ -24,7 +23,7 @@ import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { DomSanitizer, makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModLtiHelper } from '../lti-helper';
 | 
			
		||||
import { AddonModLti, AddonModLtiProvider } from '../lti';
 | 
			
		||||
import { AddonModLtiIndexComponent } from '../../components/index';
 | 
			
		||||
@ -51,8 +50,6 @@ export class AddonModLtiModuleHandlerService implements CoreCourseModuleHandler
 | 
			
		||||
        [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor(protected sanitizer: DomSanitizer) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
@ -124,11 +121,11 @@ export class AddonModLtiModuleHandlerService implements CoreCourseModuleHandler
 | 
			
		||||
            // Get the internal URL.
 | 
			
		||||
            const url = await CoreFilepool.getSrcByUrl(siteId, icon, AddonModLtiProvider.COMPONENT, module.id);
 | 
			
		||||
 | 
			
		||||
            handlerData.icon = this.sanitizer.bypassSecurityTrustUrl(url);
 | 
			
		||||
            handlerData.icon = DomSanitizer.bypassSecurityTrustUrl(url);
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Error downloading. If we're online we'll set the online url.
 | 
			
		||||
            if (CoreApp.isOnline()) {
 | 
			
		||||
                handlerData.icon = this.sanitizer.bypassSecurityTrustUrl(icon);
 | 
			
		||||
                handlerData.icon = DomSanitizer.bypassSecurityTrustUrl(icon);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { IonRouterOutlet } from '@ionic/angular';
 | 
			
		||||
import { BackButtonEvent } from '@ionic/core';
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ import { CoreLang } from '@services/lang';
 | 
			
		||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { Network, NgZone, Platform, SplashScreen } from '@singletons';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreApp, CoreAppProvider } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSubscriptions } from '@singletons/subscriptions';
 | 
			
		||||
@ -34,6 +34,10 @@ import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
 | 
			
		||||
const MOODLE_VERSION_PREFIX = 'version-';
 | 
			
		||||
const MOODLEAPP_VERSION_PREFIX = 'moodleapp-';
 | 
			
		||||
 | 
			
		||||
type AutomatedTestsWindow = Window & {
 | 
			
		||||
    changeDetector?: ChangeDetectorRef;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'app-root',
 | 
			
		||||
    templateUrl: 'app.component.html',
 | 
			
		||||
@ -46,6 +50,12 @@ export class AppComponent implements OnInit, AfterViewInit {
 | 
			
		||||
    protected lastUrls: Record<string, number> = {};
 | 
			
		||||
    protected lastInAppUrl?: string;
 | 
			
		||||
 | 
			
		||||
    constructor(changeDetector: ChangeDetectorRef) {
 | 
			
		||||
        if (CoreAppProvider.isAutomated()) {
 | 
			
		||||
            (window as AutomatedTestsWindow).changeDetector = changeDetector;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@
 | 
			
		||||
import {
 | 
			
		||||
    Component, Input, Output, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange,
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
 | 
			
		||||
import { SafeResourceUrl } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
import { CoreFile } from '@services/file';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
@ -23,6 +23,7 @@ import { CoreUrlUtils } from '@services/utils/url';
 | 
			
		||||
import { CoreIframeUtils } from '@services/utils/iframe';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreLogger } from '@singletons/logger';
 | 
			
		||||
import { DomSanitizer } from '@singletons';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-iframe',
 | 
			
		||||
@ -46,10 +47,7 @@ export class CoreIframeComponent implements OnChanges {
 | 
			
		||||
    protected logger: CoreLogger;
 | 
			
		||||
    protected initialized = false;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected sanitizer: DomSanitizer,
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.logger = CoreLogger.getInstance('CoreIframe');
 | 
			
		||||
        this.loaded = new EventEmitter<HTMLIFrameElement>();
 | 
			
		||||
    }
 | 
			
		||||
@ -105,7 +103,7 @@ export class CoreIframeComponent implements OnChanges {
 | 
			
		||||
 | 
			
		||||
            await CoreIframeUtils.fixIframeCookies(url);
 | 
			
		||||
 | 
			
		||||
            this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(CoreFile.convertFileSrc(url));
 | 
			
		||||
            this.safeUrl = DomSanitizer.bypassSecurityTrustResourceUrl(CoreFile.convertFileSrc(url));
 | 
			
		||||
 | 
			
		||||
            // Now that the URL has been set, initialize the iframe. Wait for the iframe to the added to the DOM.
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,8 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, Input, OnChanges, SimpleChange, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { SafeStyle } from '@angular/platform-browser';
 | 
			
		||||
import { DomSanitizer, Translate } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to show a progress bar and its value.
 | 
			
		||||
@ -40,8 +40,6 @@ export class CoreProgressBarComponent implements OnChanges {
 | 
			
		||||
 | 
			
		||||
    protected textSupplied = false;
 | 
			
		||||
 | 
			
		||||
    constructor(private sanitizer: DomSanitizer) { }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Detect changes on input properties.
 | 
			
		||||
     */
 | 
			
		||||
@ -69,7 +67,7 @@ export class CoreProgressBarComponent implements OnChanges {
 | 
			
		||||
                    this.text = String(this.progress);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.width = this.sanitizer.bypassSecurityTrustStyle(this.progress + '%');
 | 
			
		||||
                this.width = DomSanitizer.bypassSecurityTrustStyle(this.progress + '%');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,6 @@ import {
 | 
			
		||||
    Optional,
 | 
			
		||||
    ViewContainerRef,
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
 | 
			
		||||
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
@ -94,7 +93,6 @@ export class CoreFormatTextDirective implements OnChanges {
 | 
			
		||||
        element: ElementRef,
 | 
			
		||||
        @Optional() protected content: IonContent,
 | 
			
		||||
        protected viewContainerRef: ViewContainerRef,
 | 
			
		||||
        protected sanitizer: DomSanitizer,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.element = element.nativeElement;
 | 
			
		||||
        this.element.classList.add('core-format-text-loading'); // Hide contents until they're treated.
 | 
			
		||||
@ -520,7 +518,7 @@ export class CoreFormatTextDirective implements OnChanges {
 | 
			
		||||
        // Important: We need to look for links first because in 'img' we add new links without core-link.
 | 
			
		||||
        anchors.forEach((anchor) => {
 | 
			
		||||
            // Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually.
 | 
			
		||||
            const linkDir = new CoreLinkDirective(new ElementRef(anchor), this.content, this.sanitizer);
 | 
			
		||||
            const linkDir = new CoreLinkDirective(new ElementRef(anchor), this.content);
 | 
			
		||||
            linkDir.capture = this.captureLinks ?? true;
 | 
			
		||||
            linkDir.inApp = this.openLinksInApp;
 | 
			
		||||
            linkDir.ngOnInit();
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Directive, Input, OnInit, ElementRef, Optional, SecurityContext } from '@angular/core';
 | 
			
		||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
 | 
			
		||||
import { SafeUrl } from '@angular/platform-browser';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
 | 
			
		||||
import { CoreFileHelper } from '@services/file-helper';
 | 
			
		||||
@ -25,6 +25,7 @@ import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
 | 
			
		||||
import { CoreCustomURLSchemes } from '@services/urlschemes';
 | 
			
		||||
import { DomSanitizer } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Directive to open a link in external browser or in the app.
 | 
			
		||||
@ -48,7 +49,6 @@ export class CoreLinkDirective implements OnInit {
 | 
			
		||||
    constructor(
 | 
			
		||||
        element: ElementRef,
 | 
			
		||||
        @Optional() protected content: IonContent,
 | 
			
		||||
        protected sanitizer: DomSanitizer,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.element = element.nativeElement;
 | 
			
		||||
    }
 | 
			
		||||
@ -96,7 +96,7 @@ export class CoreLinkDirective implements OnInit {
 | 
			
		||||
        let href: string | null = null;
 | 
			
		||||
        if (this.href) {
 | 
			
		||||
            // Convert the URL back to string if needed.
 | 
			
		||||
            href = typeof this.href === 'string' ? this.href : this.sanitizer.sanitize(SecurityContext.URL, this.href);
 | 
			
		||||
            href = typeof this.href === 'string' ? this.href : DomSanitizer.sanitize(SecurityContext.URL, this.href);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        href = href || this.element.getAttribute('href') || this.element.getAttribute('xlink:href');
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { NgZone } from '@angular/core';
 | 
			
		||||
import Faker from 'faker';
 | 
			
		||||
@ -39,7 +38,7 @@ describe('CoreFormatTextDirective', () => {
 | 
			
		||||
        mockSingleton(Platform, { ready: () => Promise.resolve() });
 | 
			
		||||
        mockSingleton(CoreConfig, { get: (_, defaultValue) => defaultValue });
 | 
			
		||||
 | 
			
		||||
        CoreDomUtils.setInstance(new CoreDomUtilsProvider(mock<DomSanitizer>()));
 | 
			
		||||
        CoreDomUtils.setInstance(new CoreDomUtilsProvider());
 | 
			
		||||
        CoreUrlUtils.setInstance(new CoreUrlUtilsProvider());
 | 
			
		||||
        CoreUtils.setInstance(new CoreUtilsProvider(mock<NgZone>()));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -136,7 +136,7 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe
 | 
			
		||||
            await this.setDownloaded(module.id, siteId, extra);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            // Error prefetching, go back to previous status and reject the promise.
 | 
			
		||||
            return this.setPreviousStatus(module.id, siteId);
 | 
			
		||||
            await this.setPreviousStatus(module.id, siteId);
 | 
			
		||||
 | 
			
		||||
            throw error;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@
 | 
			
		||||
        class="core-rte-editor"
 | 
			
		||||
        role="textbox"
 | 
			
		||||
        contenteditable="true"
 | 
			
		||||
        [attr.aria-labelledby]="ariaLabelledBy"
 | 
			
		||||
        [attr.data-placeholder-text]="placeholder"
 | 
			
		||||
        (focus)="showToolbar($event)"
 | 
			
		||||
        (blur)="hideToolbar($event)"
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ import {
 | 
			
		||||
    AfterContentInit,
 | 
			
		||||
    OnDestroy,
 | 
			
		||||
    Optional,
 | 
			
		||||
    AfterViewInit,
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
import { FormControl } from '@angular/forms';
 | 
			
		||||
import { IonTextarea, IonContent, IonSlides } from '@ionic/angular';
 | 
			
		||||
@ -51,7 +52,7 @@ import { CoreEditorOffline } from '../../services/editor-offline';
 | 
			
		||||
    templateUrl: 'core-editor-rich-text-editor.html',
 | 
			
		||||
    styleUrls: ['rich-text-editor.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentInit, OnDestroy {
 | 
			
		||||
export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    // Based on: https://github.com/judgewest2000/Ionic3RichText/
 | 
			
		||||
    // @todo: Anchor button, fullscreen...
 | 
			
		||||
@ -87,6 +88,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
    protected valueChangeSubscription?: Subscription;
 | 
			
		||||
    protected keyboardObserver?: CoreEventObserver;
 | 
			
		||||
    protected resetObserver?: CoreEventObserver;
 | 
			
		||||
    protected labelObserver?: MutationObserver;
 | 
			
		||||
    protected initHeightInterval?: number;
 | 
			
		||||
    protected isCurrentView = true;
 | 
			
		||||
    protected toolbarButtonWidth = 44;
 | 
			
		||||
@ -109,6 +111,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
    toolbarPrevHidden = true;
 | 
			
		||||
    toolbarNextHidden = false;
 | 
			
		||||
    canScanQR = false;
 | 
			
		||||
    ariaLabelledBy?: string;
 | 
			
		||||
    infoMessage?: string;
 | 
			
		||||
    direction = 'ltr';
 | 
			
		||||
    toolbarStyles = {
 | 
			
		||||
@ -209,6 +212,20 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngAfterViewInit(): Promise<void> {
 | 
			
		||||
        const label = this.element.closest('ion-item')?.querySelector('ion-label');
 | 
			
		||||
 | 
			
		||||
        if (!label) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.labelObserver = new MutationObserver(() => this.ariaLabelledBy = label.getAttribute('id') ?? undefined);
 | 
			
		||||
        this.labelObserver.observe(label, { attributes: true, attributeFilter: ['id'] });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set listeners and observers.
 | 
			
		||||
     */
 | 
			
		||||
@ -1118,6 +1135,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
        this.resizeObserver?.disconnect();
 | 
			
		||||
        this.resetObserver?.off();
 | 
			
		||||
        this.keyboardObserver?.off();
 | 
			
		||||
        this.labelObserver?.disconnect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
 | 
			
		||||
    disabled = false;
 | 
			
		||||
    labelKey = '';
 | 
			
		||||
 | 
			
		||||
    protected aggregateObserver: CoreEventObserver;
 | 
			
		||||
    protected aggregateObserver?: CoreEventObserver;
 | 
			
		||||
    protected updateSiteObserver: CoreEventObserver;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
@ -57,27 +57,14 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
 | 
			
		||||
        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
			
		||||
            this.disabled = CoreRating.isRatingDisabledInSite();
 | 
			
		||||
        }, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        // Update aggrgate when the user adds or edits a rating.
 | 
			
		||||
        this.aggregateObserver =
 | 
			
		||||
            CoreEvents.on(CoreRatingProvider.AGGREGATE_CHANGED_EVENT, (data) => {
 | 
			
		||||
                if (this.item &&
 | 
			
		||||
                    data.contextLevel == this.contextLevel &&
 | 
			
		||||
                    data.instanceId == this.instanceId &&
 | 
			
		||||
                    data.component == this.ratingInfo.component &&
 | 
			
		||||
                    data.ratingArea == this.ratingInfo.ratingarea &&
 | 
			
		||||
                    data.itemId == this.itemId) {
 | 
			
		||||
                    this.item.aggregatestr = data.aggregate;
 | 
			
		||||
                    this.item.count = data.count;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Detect changes on input properties.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(): void {
 | 
			
		||||
        this.aggregateObserver && this.aggregateObserver.off();
 | 
			
		||||
        this.aggregateObserver?.off();
 | 
			
		||||
        delete this.aggregateObserver;
 | 
			
		||||
 | 
			
		||||
        this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid == this.itemId);
 | 
			
		||||
        if (!this.item) {
 | 
			
		||||
@ -107,6 +94,20 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.showCount = (this.aggregateMethod != CoreRatingProvider.AGGREGATE_COUNT);
 | 
			
		||||
 | 
			
		||||
        // Update aggrgate when the user adds or edits a rating.
 | 
			
		||||
        this.aggregateObserver =
 | 
			
		||||
            CoreEvents.on(CoreRatingProvider.AGGREGATE_CHANGED_EVENT, (data) => {
 | 
			
		||||
                if (this.item &&
 | 
			
		||||
                    data.contextLevel == this.contextLevel &&
 | 
			
		||||
                    data.instanceId == this.instanceId &&
 | 
			
		||||
                    data.component == this.ratingInfo.component &&
 | 
			
		||||
                    data.ratingArea == this.ratingInfo.ratingarea &&
 | 
			
		||||
                    data.itemId == this.itemId) {
 | 
			
		||||
                    this.item.aggregatestr = data.aggregate;
 | 
			
		||||
                    this.item.count = data.count;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -135,7 +136,7 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
 | 
			
		||||
     * Component being destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.aggregateObserver.off();
 | 
			
		||||
        this.aggregateObserver?.off();
 | 
			
		||||
        this.updateSiteObserver.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -122,7 +122,8 @@ export class CoreRatingProvider {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreRatingOffline.deleteRating(component, ratingArea, contextLevel, instanceId, itemId, siteId);
 | 
			
		||||
            this.addRatingOnline(
 | 
			
		||||
 | 
			
		||||
            const response = await this.addRatingOnline(
 | 
			
		||||
                component,
 | 
			
		||||
                ratingArea,
 | 
			
		||||
                contextLevel,
 | 
			
		||||
@ -134,6 +135,8 @@ export class CoreRatingProvider {
 | 
			
		||||
                aggregateMethod,
 | 
			
		||||
                siteId,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return response;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                // The WebService has thrown an error or offline not supported, reject.
 | 
			
		||||
 | 
			
		||||
@ -391,7 +391,7 @@ export class CoreUserProvider {
 | 
			
		||||
     * @param siteId Site Id. If not defined, use current site.
 | 
			
		||||
     * @return Preference value or null if preference not set.
 | 
			
		||||
     */
 | 
			
		||||
    async getUserPreference(name: string, siteId?: string): Promise<string> {
 | 
			
		||||
    async getUserPreference(name: string, siteId?: string): Promise<string | null> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const preference = await CoreUtils.ignoreErrors(CoreUserOffline.getPreference(name, siteId));
 | 
			
		||||
@ -403,20 +403,15 @@ export class CoreUserProvider {
 | 
			
		||||
 | 
			
		||||
        const wsValue = await this.getUserPreferenceOnline(name, siteId);
 | 
			
		||||
 | 
			
		||||
        if (!wsValue) {
 | 
			
		||||
            if (preference) {
 | 
			
		||||
                // Return the local value.
 | 
			
		||||
                return preference.value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw new CoreError('Preference not found');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (preference && preference.value != preference.onlinevalue && preference.onlinevalue == wsValue) {
 | 
			
		||||
            // Sync is pending for this preference, return stored value.
 | 
			
		||||
            return preference.value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!wsValue) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreUserOffline.setPreference(name, wsValue, wsValue);
 | 
			
		||||
 | 
			
		||||
        return wsValue;
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,7 @@ describe('CoreNavigator', () => {
 | 
			
		||||
        mockSingleton(Router, router);
 | 
			
		||||
        mockSingleton(CoreUtils, new CoreUtilsProvider(mock()));
 | 
			
		||||
        mockSingleton(CoreUrlUtils, new CoreUrlUtilsProvider());
 | 
			
		||||
        mockSingleton(CoreTextUtils, new CoreTextUtilsProvider(mock()));
 | 
			
		||||
        mockSingleton(CoreTextUtils, new CoreTextUtilsProvider());
 | 
			
		||||
        mockSingleton(CoreSites, { getCurrentSiteId: () => 42, isLoggedIn: () => true });
 | 
			
		||||
        mockSingleton(CoreMainMenu, { isMainMenuTab: path => Promise.resolve(currentMainMenuHandlers.includes(path)) });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -12,24 +12,22 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreTextUtilsProvider } from '@services/utils/text';
 | 
			
		||||
import { DomSanitizer } from '@singletons';
 | 
			
		||||
 | 
			
		||||
import { mock, mockSingleton } from '@/testing/utils';
 | 
			
		||||
import { mockSingleton } from '@/testing/utils';
 | 
			
		||||
 | 
			
		||||
describe('CoreTextUtilsProvider', () => {
 | 
			
		||||
 | 
			
		||||
    const config = { platform: 'android' };
 | 
			
		||||
    let sanitizer: DomSanitizer;
 | 
			
		||||
    let textUtils: CoreTextUtilsProvider;
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        mockSingleton(CoreApp, [], { isAndroid: () => config.platform === 'android' });
 | 
			
		||||
        mockSingleton(DomSanitizer, [], { bypassSecurityTrustUrl: url => url });
 | 
			
		||||
 | 
			
		||||
        sanitizer = mock<DomSanitizer>([], { bypassSecurityTrustUrl: url => url });
 | 
			
		||||
        textUtils = new CoreTextUtilsProvider(sanitizer);
 | 
			
		||||
        textUtils = new CoreTextUtilsProvider();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('adds ending slashes', () => {
 | 
			
		||||
@ -58,7 +56,7 @@ describe('CoreTextUtilsProvider', () => {
 | 
			
		||||
        // Assert
 | 
			
		||||
        expect(url).toEqual('geo:0,0?q=Moodle%20Spain%20HQ');
 | 
			
		||||
 | 
			
		||||
        expect(sanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
 | 
			
		||||
        expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
 | 
			
		||||
        expect(CoreApp.isAndroid).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -74,7 +72,7 @@ describe('CoreTextUtilsProvider', () => {
 | 
			
		||||
        // Assert
 | 
			
		||||
        expect(url).toEqual('http://maps.google.com?q=Moodle%20Spain%20HQ');
 | 
			
		||||
 | 
			
		||||
        expect(sanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
 | 
			
		||||
        expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
 | 
			
		||||
        expect(CoreApp.isAndroid).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,6 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable, SimpleChange, ElementRef, KeyValueChanges } from '@angular/core';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { ModalOptions, PopoverOptions, AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core';
 | 
			
		||||
import { Md5 } from 'ts-md5';
 | 
			
		||||
@ -62,7 +61,7 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
    protected activeLoadingModals: CoreIonLoadingElement[] = [];
 | 
			
		||||
    protected logger: CoreLogger;
 | 
			
		||||
 | 
			
		||||
    constructor(protected domSanitizer: DomSanitizer) {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.logger = CoreLogger.getInstance('CoreDomUtilsProvider');
 | 
			
		||||
 | 
			
		||||
        this.init();
 | 
			
		||||
 | 
			
		||||
@ -13,13 +13,13 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
 | 
			
		||||
import { SafeUrl } from '@angular/platform-browser';
 | 
			
		||||
import { ModalOptions } from '@ionic/core';
 | 
			
		||||
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreLang } from '@services/lang';
 | 
			
		||||
import { CoreAnyError, CoreError } from '@classes/errors/error';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { DomSanitizer, makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreWSFile } from '@services/ws';
 | 
			
		||||
import { Locutus } from '@singletons/locutus';
 | 
			
		||||
import { CoreViewerTextComponent } from '@features/viewer/components/text/text';
 | 
			
		||||
@ -94,8 +94,6 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
 | 
			
		||||
    protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element.
 | 
			
		||||
 | 
			
		||||
    constructor(private sanitizer: DomSanitizer) { }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add ending slash from a path or URL.
 | 
			
		||||
     *
 | 
			
		||||
@ -156,7 +154,7 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
     * @return URL to view the address.
 | 
			
		||||
     */
 | 
			
		||||
    buildAddressURL(address: string): SafeUrl {
 | 
			
		||||
        return this.sanitizer.bypassSecurityTrustUrl((CoreApp.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') +
 | 
			
		||||
        return DomSanitizer.bypassSecurityTrustUrl((CoreApp.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') +
 | 
			
		||||
                encodeURIComponent(address));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,9 +12,10 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { ApplicationRef, ApplicationInitStatus, Injector, NgZone as NgZoneService, Type } from '@angular/core';
 | 
			
		||||
import { AbstractType, ApplicationInitStatus, ApplicationRef, Injector, NgZone as NgZoneService, Type } from '@angular/core';
 | 
			
		||||
import { Router as RouterService } from '@angular/router';
 | 
			
		||||
import { HttpClient } from '@angular/common/http';
 | 
			
		||||
import { DomSanitizer as DomSanitizerService } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    Platform as PlatformService,
 | 
			
		||||
@ -109,7 +110,7 @@ export function setCreateSingletonMethodProxy(method: typeof createSingletonMeth
 | 
			
		||||
 * @return Singleton proxy.
 | 
			
		||||
 */
 | 
			
		||||
export function makeSingleton<Service extends object = object>( // eslint-disable-line @typescript-eslint/ban-types
 | 
			
		||||
    injectionToken: Type<Service> | Type<unknown> | string,
 | 
			
		||||
    injectionToken: Type<Service> | AbstractType<Service> | Type<unknown> | string,
 | 
			
		||||
): CoreSingletonProxy<Service> {
 | 
			
		||||
    const singleton = {
 | 
			
		||||
        setInstance(instance: Service) {
 | 
			
		||||
@ -199,6 +200,7 @@ export const ApplicationInit = makeSingleton(ApplicationInitStatus);
 | 
			
		||||
export const Application = makeSingleton(ApplicationRef);
 | 
			
		||||
export const NavController = makeSingleton(NavControllerService);
 | 
			
		||||
export const Router = makeSingleton(RouterService);
 | 
			
		||||
export const DomSanitizer = makeSingleton(DomSanitizerService);
 | 
			
		||||
 | 
			
		||||
// Convert external libraries injectables.
 | 
			
		||||
export const Translate = makeSingleton(TranslateService);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user