forked from CIT/Vmeda.Online
		
	MOBILE-3931 module: Remove unnecessary variables and menus from modules
This commit is contained in:
		
							parent
							
								
									d224876f42
								
							
						
					
					
						commit
						344ee6d57e
					
				@ -649,7 +649,6 @@
 | 
			
		||||
  "addon.mod_forum.posttomygroups": "forum",
 | 
			
		||||
  "addon.mod_forum.privatereply": "forum",
 | 
			
		||||
  "addon.mod_forum.re": "forum",
 | 
			
		||||
  "addon.mod_forum.refreshdiscussions": "local_moodlemobileapp",
 | 
			
		||||
  "addon.mod_forum.refreshposts": "local_moodlemobileapp",
 | 
			
		||||
  "addon.mod_forum.removefromfavourites": "forum",
 | 
			
		||||
  "addon.mod_forum.reply": "forum",
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="assign && (description || (assign.introattachments && assign.introattachments.length))"
 | 
			
		||||
            [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,6 @@ import { CoreGroupInfo, CoreGroups } from '@services/groups';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
@ -161,104 +160,75 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Expand the description.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    expandDescription(ev?: Event): void {
 | 
			
		||||
        ev?.preventDefault();
 | 
			
		||||
        ev?.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        if (this.assign && (this.description || this.assign.introattachments)) {
 | 
			
		||||
            CoreTextUtils.viewText(Translate.instant('core.description'), this.description || '', {
 | 
			
		||||
                component: this.component,
 | 
			
		||||
                componentId: this.module.id,
 | 
			
		||||
                files: this.assign.introattachments,
 | 
			
		||||
                filter: true,
 | 
			
		||||
                contextLevel: 'module',
 | 
			
		||||
                instanceId: this.module.id,
 | 
			
		||||
                courseId: this.courseId,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get assignment data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
 | 
			
		||||
        // Get assignment data.
 | 
			
		||||
        try {
 | 
			
		||||
            this.assign = await AddonModAssign.getAssignment(this.courseId, this.module.id);
 | 
			
		||||
        this.assign = await AddonModAssign.getAssignment(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.assign);
 | 
			
		||||
            this.description = this.assign.intro;
 | 
			
		||||
        this.dataRetrieved.emit(this.assign);
 | 
			
		||||
        this.description = this.assign.intro;
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the assign.
 | 
			
		||||
                await CoreUtils.ignoreErrors(this.syncActivity(showErrors));
 | 
			
		||||
            }
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the assign.
 | 
			
		||||
            await CoreUtils.ignoreErrors(this.syncActivity(showErrors));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            // Check if there's any offline data for this assign.
 | 
			
		||||
            this.hasOffline = await AddonModAssignOffline.hasAssignOfflineData(this.assign.id);
 | 
			
		||||
        // Check if there's any offline data for this assign.
 | 
			
		||||
        this.hasOffline = await AddonModAssignOffline.hasAssignOfflineData(this.assign.id);
 | 
			
		||||
 | 
			
		||||
            // Get assignment submissions.
 | 
			
		||||
            const submissions = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.module.id });
 | 
			
		||||
            const time = CoreTimeUtils.timestamp();
 | 
			
		||||
        // Get assignment submissions.
 | 
			
		||||
        const submissions = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.module.id });
 | 
			
		||||
        const time = CoreTimeUtils.timestamp();
 | 
			
		||||
 | 
			
		||||
            this.canViewAllSubmissions = submissions.canviewsubmissions;
 | 
			
		||||
        this.canViewAllSubmissions = submissions.canviewsubmissions;
 | 
			
		||||
 | 
			
		||||
            if (submissions.canviewsubmissions) {
 | 
			
		||||
        if (submissions.canviewsubmissions) {
 | 
			
		||||
 | 
			
		||||
                // Calculate the messages to display about time remaining and late submissions.
 | 
			
		||||
                if (this.assign.duedate > 0) {
 | 
			
		||||
                    if (this.assign.duedate - time <= 0) {
 | 
			
		||||
                        this.timeRemaining = Translate.instant('addon.mod_assign.assignmentisdue');
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this.timeRemaining = CoreTimeUtils.formatDuration(this.assign.duedate - time, 3);
 | 
			
		||||
 | 
			
		||||
                        if (this.assign.cutoffdate) {
 | 
			
		||||
                            if (this.assign.cutoffdate > time) {
 | 
			
		||||
                                this.lateSubmissions = Translate.instant(
 | 
			
		||||
                                    'addon.mod_assign.latesubmissionsaccepted',
 | 
			
		||||
                                    { $a: CoreTimeUtils.userDate(this.assign.cutoffdate * 1000) },
 | 
			
		||||
                                );
 | 
			
		||||
                            } else {
 | 
			
		||||
                                this.lateSubmissions = Translate.instant('addon.mod_assign.nomoresubmissionsaccepted');
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this.lateSubmissions = '';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
            // Calculate the messages to display about time remaining and late submissions.
 | 
			
		||||
            if (this.assign.duedate > 0) {
 | 
			
		||||
                if (this.assign.duedate - time <= 0) {
 | 
			
		||||
                    this.timeRemaining = Translate.instant('addon.mod_assign.assignmentisdue');
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.timeRemaining = '';
 | 
			
		||||
                    this.lateSubmissions = '';
 | 
			
		||||
                    this.timeRemaining = CoreTimeUtils.formatDuration(this.assign.duedate - time, 3);
 | 
			
		||||
 | 
			
		||||
                    if (this.assign.cutoffdate) {
 | 
			
		||||
                        if (this.assign.cutoffdate > time) {
 | 
			
		||||
                            this.lateSubmissions = Translate.instant(
 | 
			
		||||
                                'addon.mod_assign.latesubmissionsaccepted',
 | 
			
		||||
                                { $a: CoreTimeUtils.userDate(this.assign.cutoffdate * 1000) },
 | 
			
		||||
                            );
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this.lateSubmissions = Translate.instant('addon.mod_assign.nomoresubmissionsaccepted');
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this.lateSubmissions = '';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check if groupmode is enabled to avoid showing wrong numbers.
 | 
			
		||||
                this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false);
 | 
			
		||||
 | 
			
		||||
                await this.setGroup(CoreGroups.validateGroupId(this.group, this.groupInfo));
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.timeRemaining = '';
 | 
			
		||||
                this.lateSubmissions = '';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                // Check if the user can view their own submission.
 | 
			
		||||
                await AddonModAssign.getSubmissionStatus(this.assign.id, { cmId: this.module.id });
 | 
			
		||||
                this.canViewOwnSubmission = true;
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                this.canViewOwnSubmission = false;
 | 
			
		||||
            // Check if groupmode is enabled to avoid showing wrong numbers.
 | 
			
		||||
            this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false);
 | 
			
		||||
 | 
			
		||||
                if (error.errorcode !== 'nopermission') {
 | 
			
		||||
                    throw error;
 | 
			
		||||
                }
 | 
			
		||||
            await this.setGroup(CoreGroups.validateGroupId(this.group, this.groupInfo));
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // Check if the user can view their own submission.
 | 
			
		||||
            await AddonModAssign.getSubmissionStatus(this.assign.id, { cmId: this.module.id });
 | 
			
		||||
            this.canViewOwnSubmission = true;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this.canViewOwnSubmission = false;
 | 
			
		||||
 | 
			
		||||
            if (error.errorcode !== 'nopermission') {
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,23 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -71,21 +71,18 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.bbb = await AddonModBBB.getBBB(this.courseId, this.module.id);
 | 
			
		||||
    protected async fetchContent(): Promise<void> {
 | 
			
		||||
        this.bbb = await AddonModBBB.getBBB(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.description = this.bbb.intro;
 | 
			
		||||
            this.dataRetrieved.emit(this.bbb);
 | 
			
		||||
        this.description = this.bbb.intro;
 | 
			
		||||
        this.dataRetrieved.emit(this.bbb);
 | 
			
		||||
 | 
			
		||||
            this.groupInfo = await CoreGroups.getActivityGroupInfo(this.module.id, false);
 | 
			
		||||
        this.groupInfo = await CoreGroups.getActivityGroupInfo(this.module.id, false);
 | 
			
		||||
 | 
			
		||||
            this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
 | 
			
		||||
        this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
 | 
			
		||||
 | 
			
		||||
        await this.fetchMeetingInfo();
 | 
			
		||||
 | 
			
		||||
            await this.fetchMeetingInfo();
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,21 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
 | 
			
		||||
            [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -52,15 +52,11 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh?: boolean): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await Promise.all([
 | 
			
		||||
                this.loadBook(),
 | 
			
		||||
                this.loadTOC(),
 | 
			
		||||
            ]);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    protected async fetchContent(): Promise<void> {
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
            this.loadBook(),
 | 
			
		||||
            this.loadTOC(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -70,27 +70,23 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.chat = await AddonModChat.getChat(this.courseId, this.module.id);
 | 
			
		||||
    protected async fetchContent(): Promise<void> {
 | 
			
		||||
        this.chat = await AddonModChat.getChat(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.description = this.chat.intro;
 | 
			
		||||
            const now = CoreTimeUtils.timestamp();
 | 
			
		||||
            const span = (this.chat.chattime || 0) - now;
 | 
			
		||||
        this.description = this.chat.intro;
 | 
			
		||||
        const now = CoreTimeUtils.timestamp();
 | 
			
		||||
        const span = (this.chat.chattime || 0) - now;
 | 
			
		||||
 | 
			
		||||
            if (this.chat.chattime && this.chat.schedule && span > 0) {
 | 
			
		||||
                this.chatInfo = {
 | 
			
		||||
                    date: CoreTimeUtils.userDate(this.chat.chattime * 1000),
 | 
			
		||||
                    fromnow: CoreTimeUtils.formatTime(span),
 | 
			
		||||
                };
 | 
			
		||||
            } else {
 | 
			
		||||
                this.chatInfo = undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.chat);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        if (this.chat.chattime && this.chat.schedule && span > 0) {
 | 
			
		||||
            this.chatInfo = {
 | 
			
		||||
                date: CoreTimeUtils.userDate(this.chat.chattime * 1000),
 | 
			
		||||
                fromnow: CoreTimeUtils.formatTime(span),
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            this.chatInfo = undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.dataRetrieved.emit(this.chat);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -132,43 +132,39 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        this.now = Date.now();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
 | 
			
		||||
        this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the choice.
 | 
			
		||||
                const updated = await this.syncActivity(showErrors);
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the choice.
 | 
			
		||||
            const updated = await this.syncActivity(showErrors);
 | 
			
		||||
 | 
			
		||||
                if (updated) {
 | 
			
		||||
                    // Responses were sent, update the choice.
 | 
			
		||||
                    this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
 | 
			
		||||
                }
 | 
			
		||||
            if (updated) {
 | 
			
		||||
                // Responses were sent, update the choice.
 | 
			
		||||
                this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.choice.timeopen = (this.choice.timeopen || 0) * 1000;
 | 
			
		||||
            this.choice.timeclose = (this.choice.timeclose || 0) * 1000;
 | 
			
		||||
            this.openTimeReadable = CoreTimeUtils.userDate(this.choice.timeopen);
 | 
			
		||||
            this.closeTimeReadable = CoreTimeUtils.userDate(this.choice.timeclose);
 | 
			
		||||
 | 
			
		||||
            this.description = this.choice.intro;
 | 
			
		||||
            this.choiceNotOpenYet = !!this.choice.timeopen && this.choice.timeopen > this.now;
 | 
			
		||||
            this.choiceClosed = !!this.choice.timeclose && this.choice.timeclose <= this.now;
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.choice);
 | 
			
		||||
 | 
			
		||||
            // Check if there are responses stored in offline.
 | 
			
		||||
            this.hasOffline = await AddonModChoiceOffline.hasResponse(this.choice.id);
 | 
			
		||||
 | 
			
		||||
            // We need fetchOptions to finish before calling fetchResults because it needs hasAnsweredOnline variable.
 | 
			
		||||
            await this.fetchOptions(this.choice);
 | 
			
		||||
 | 
			
		||||
            await this.fetchResults(this.choice);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.choice.timeopen = (this.choice.timeopen || 0) * 1000;
 | 
			
		||||
        this.choice.timeclose = (this.choice.timeclose || 0) * 1000;
 | 
			
		||||
        this.openTimeReadable = CoreTimeUtils.userDate(this.choice.timeopen);
 | 
			
		||||
        this.closeTimeReadable = CoreTimeUtils.userDate(this.choice.timeclose);
 | 
			
		||||
 | 
			
		||||
        this.description = this.choice.intro;
 | 
			
		||||
        this.choiceNotOpenYet = !!this.choice.timeopen && this.choice.timeopen > this.now;
 | 
			
		||||
        this.choiceClosed = !!this.choice.timeclose && this.choice.timeclose <= this.now;
 | 
			
		||||
 | 
			
		||||
        this.dataRetrieved.emit(this.choice);
 | 
			
		||||
 | 
			
		||||
        // Check if there are responses stored in offline.
 | 
			
		||||
        this.hasOffline = await AddonModChoiceOffline.hasResponse(this.choice.id);
 | 
			
		||||
 | 
			
		||||
        // We need fetchOptions to finish before calling fetchResults because it needs hasAnsweredOnline variable.
 | 
			
		||||
        await this.fetchOptions(this.choice);
 | 
			
		||||
 | 
			
		||||
        await this.fetchResults(this.choice);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -433,7 +429,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async dataUpdated(online: boolean): Promise<void> {
 | 
			
		||||
        if (!online || !this.isPrefetched) {
 | 
			
		||||
        if (!online || !this.isPrefetched()) {
 | 
			
		||||
            // Not downloaded, just refresh the data.
 | 
			
		||||
            return this.refreshContent(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -4,34 +4,12 @@
 | 
			
		||||
        <ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700"
 | 
			
		||||
            [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="500" *ngIf="canAdd" [content]="'addon.mod_data.addentries' | translate" iconAction="fas-plus"
 | 
			
		||||
            (action)="gotoAddEntries()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="400" *ngIf="firstEntry" [content]="'addon.mod_data.single' | translate" iconAction="fas-file"
 | 
			
		||||
            (action)="gotoEntry(firstEntry)">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="300" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="200" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
 | 
			
		||||
@ -202,14 +202,9 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download data contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        let canAdd = false;
 | 
			
		||||
        let canSearch = false;
 | 
			
		||||
 | 
			
		||||
@ -270,7 +265,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.canAdd = canAdd;
 | 
			
		||||
            this.canSearch = canSearch;
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -172,7 +172,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
@ -201,9 +201,6 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
 | 
			
		||||
 | 
			
		||||
            await this.fetchFeedbackOverviewData();
 | 
			
		||||
        } finally {
 | 
			
		||||
            // Now fill the context menu.
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
 | 
			
		||||
            if (this.feedback) {
 | 
			
		||||
                // Check if there are responses stored in offline.
 | 
			
		||||
                this.hasOffline = await AddonModFeedbackOffline.hasFeedbackOfflineData(this.feedback.id);
 | 
			
		||||
 | 
			
		||||
@ -1,26 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="!subfolder" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Component, Input, OnInit, Optional } from '@angular/core';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
 | 
			
		||||
@ -57,7 +56,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
 | 
			
		||||
            this.contents = this.subfolder;
 | 
			
		||||
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@ -73,7 +71,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -87,24 +84,17 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download folder contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id);
 | 
			
		||||
        this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            const contents = await CoreCourse.getModuleContents(this.module, undefined, undefined, false, refresh);
 | 
			
		||||
        const contents = await CoreCourse.getModuleContents(this.module, undefined, undefined, false, refresh);
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.folderInstance || this.module);
 | 
			
		||||
        this.dataRetrieved.emit(this.folderInstance || this.module);
 | 
			
		||||
 | 
			
		||||
            this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
 | 
			
		||||
            this.contents = AddonModFolderHelper.formatContents(contents);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
        this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
 | 
			
		||||
        this.contents = AddonModFolderHelper.formatContents(contents);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,6 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="discussions && 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 && 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>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" [iconAction]="prefetchStatusIcon"
 | 
			
		||||
            [closeOnClick]="false" (action)="prefetch($event)">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" iconDescription="fas-archive" iconAction="fas-trash" [priority]="400"
 | 
			
		||||
            [content]="'core.clearstoreddata' | translate:{$a: size}" [closeOnClick]="false" (action)="removeFiles($event)">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="sortingAvailable" iconAction="fas-sort" [priority]="300" [content]="'core.sort' | translate"
 | 
			
		||||
            (action)="showSortOrderSelector()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
 | 
			
		||||
@ -296,13 +296,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download the component contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @param sync If the refresh needs syncing.
 | 
			
		||||
     * @param showErrors Wether to show errors to the user or hide them.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
    protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        this.fetchFailed = false;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
@ -329,8 +325,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
            this.fetchFailed = true; // Set to prevent infinite calls with infinite-loading.
 | 
			
		||||
 | 
			
		||||
            throw error; // Pass the error to the parent catch.
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,6 @@
 | 
			
		||||
    "posttomygroups": "Post a copy to all groups",
 | 
			
		||||
    "privatereply": "Reply privately",
 | 
			
		||||
    "re": "Re:",
 | 
			
		||||
    "refreshdiscussions": "Refresh discussions",
 | 
			
		||||
    "refreshposts": "Refresh posts",
 | 
			
		||||
    "removefromfavourites": "Unstar this discussion",
 | 
			
		||||
    "reply": "Reply",
 | 
			
		||||
 | 
			
		||||
@ -10,31 +10,9 @@
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700"
 | 
			
		||||
            [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600"
 | 
			
		||||
            (action)="doRefresh(null, $event, true)" [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="canAdd" [priority]="550" [content]="'addon.mod_glossary.addentry' | translate"
 | 
			
		||||
            (action)="openNewEntry()" iconAction="fas-plus">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
 | 
			
		||||
@ -177,39 +177,35 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
    protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        const entries = await this.promisedEntries;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await entries.getSource().loadGlossary();
 | 
			
		||||
        await entries.getSource().loadGlossary();
 | 
			
		||||
 | 
			
		||||
            if (!this.glossary) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.description = this.glossary.intro || this.description;
 | 
			
		||||
            this.canAdd = !!this.glossary.canaddentry || false;
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.glossary);
 | 
			
		||||
 | 
			
		||||
            if (!entries.getSource().fetchMode) {
 | 
			
		||||
                this.switchMode('letter_all');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the glossary.
 | 
			
		||||
                await this.syncActivity(showErrors);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const [hasOfflineRatings] = await Promise.all([
 | 
			
		||||
                CoreRatingOffline.hasRatings('mod_glossary', 'entry', ContextLevel.MODULE, this.glossary.coursemodule),
 | 
			
		||||
                refresh ? entries.reload() : entries.load(),
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            this.hasOfflineRatings = hasOfflineRatings;
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        if (!this.glossary) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.description = this.glossary.intro || this.description;
 | 
			
		||||
        this.canAdd = !!this.glossary.canaddentry || false;
 | 
			
		||||
 | 
			
		||||
        this.dataRetrieved.emit(this.glossary);
 | 
			
		||||
 | 
			
		||||
        if (!entries.getSource().fetchMode) {
 | 
			
		||||
            this.switchMode('letter_all');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the glossary.
 | 
			
		||||
            await this.syncActivity(showErrors);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const [hasOfflineRatings] = await Promise.all([
 | 
			
		||||
            CoreRatingOffline.hasRatings('mod_glossary', 'entry', ContextLevel.MODULE, this.glossary.coursemodule),
 | 
			
		||||
            refresh ? entries.reload() : entries.load(),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        this.hasOfflineRatings = hasOfflineRatings;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -3,31 +3,10 @@
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="h5pActivity && h5pActivity.enabletracking && accessInfo && !accessInfo.canreviewattempts"
 | 
			
		||||
            [priority]="1000" [content]="'addon.mod_h5pactivity.attempts_report' | translate" (action)="viewMyAttempts()"
 | 
			
		||||
            iconAction="stats-chart">
 | 
			
		||||
            iconAction="fas-chart-bar">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="canViewAllAttempts" [priority]="1000" [content]="'addon.mod_h5pactivity.attempts_report' | translate"
 | 
			
		||||
            (action)="viewAllAttempts()" iconAction="stats-chart">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
            (action)="viewAllAttempts()" iconAction="fas-chart-bar">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -112,51 +112,47 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.h5pActivity = await AddonModH5PActivity.getH5PActivity(this.courseId, this.module.id, {
 | 
			
		||||
                siteId: this.siteId,
 | 
			
		||||
            });
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        this.h5pActivity = await AddonModH5PActivity.getH5PActivity(this.courseId, this.module.id, {
 | 
			
		||||
            siteId: this.siteId,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.h5pActivity);
 | 
			
		||||
            this.description = this.h5pActivity.intro;
 | 
			
		||||
            this.displayOptions = CoreH5PHelper.decodeDisplayOptions(this.h5pActivity.displayoptions);
 | 
			
		||||
        this.dataRetrieved.emit(this.h5pActivity);
 | 
			
		||||
        this.description = this.h5pActivity.intro;
 | 
			
		||||
        this.displayOptions = CoreH5PHelper.decodeDisplayOptions(this.h5pActivity.displayoptions);
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                await this.syncActivity(showErrors);
 | 
			
		||||
            }
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            await this.syncActivity(showErrors);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            await Promise.all([
 | 
			
		||||
                this.checkHasOffline(),
 | 
			
		||||
                this.fetchAccessInfo(),
 | 
			
		||||
                this.fetchDeployedFileData(),
 | 
			
		||||
            ]);
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
            this.checkHasOffline(),
 | 
			
		||||
            this.fetchAccessInfo(),
 | 
			
		||||
            this.fetchDeployedFileData(),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
            this.trackComponent = this.accessInfo?.cansubmit ? AddonModH5PActivityProvider.TRACK_COMPONENT : '';
 | 
			
		||||
            this.canViewAllAttempts = !!this.h5pActivity.enabletracking && !!this.accessInfo?.canreviewattempts &&
 | 
			
		||||
        this.trackComponent = this.accessInfo?.cansubmit ? AddonModH5PActivityProvider.TRACK_COMPONENT : '';
 | 
			
		||||
        this.canViewAllAttempts = !!this.h5pActivity.enabletracking && !!this.accessInfo?.canreviewattempts &&
 | 
			
		||||
                AddonModH5PActivity.canGetUsersAttemptsInSite();
 | 
			
		||||
 | 
			
		||||
            if (this.h5pActivity.package && this.h5pActivity.package[0]) {
 | 
			
		||||
                // The online player should use the original file, not the trusted one.
 | 
			
		||||
                this.onlinePlayerUrl = CoreH5P.h5pPlayer.calculateOnlinePlayerUrl(
 | 
			
		||||
                    this.site.getURL(),
 | 
			
		||||
                    this.h5pActivity.package[0].fileurl,
 | 
			
		||||
                    this.displayOptions,
 | 
			
		||||
                    this.trackComponent,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        if (this.h5pActivity.package && this.h5pActivity.package[0]) {
 | 
			
		||||
            // The online player should use the original file, not the trusted one.
 | 
			
		||||
            this.onlinePlayerUrl = CoreH5P.h5pPlayer.calculateOnlinePlayerUrl(
 | 
			
		||||
                this.site.getURL(),
 | 
			
		||||
                this.h5pActivity.package[0].fileurl,
 | 
			
		||||
                this.displayOptions,
 | 
			
		||||
                this.trackComponent,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if (!this.siteCanDownload || this.state == CoreConstants.DOWNLOADED) {
 | 
			
		||||
                // Cannot download the file or already downloaded, play the package directly.
 | 
			
		||||
                this.play();
 | 
			
		||||
        if (!this.siteCanDownload || this.state == CoreConstants.DOWNLOADED) {
 | 
			
		||||
            // Cannot download the file or already downloaded, play the package directly.
 | 
			
		||||
            this.play();
 | 
			
		||||
 | 
			
		||||
            } else if ((this.state == CoreConstants.NOT_DOWNLOADED || this.state == CoreConstants.OUTDATED) && CoreApp.isOnline() &&
 | 
			
		||||
        } else if ((this.state == CoreConstants.NOT_DOWNLOADED || this.state == CoreConstants.OUTDATED) && CoreApp.isOnline() &&
 | 
			
		||||
                    this.deployedFile?.filesize && CoreFilepool.shouldDownload(this.deployedFile.filesize)) {
 | 
			
		||||
                // Package is small, download it automatically. Don't block this function for this.
 | 
			
		||||
                this.downloadAutomatically();
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
            // Package is small, download it automatically. Don't block this function for this.
 | 
			
		||||
            this.downloadAutomatically();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -529,19 +525,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
 | 
			
		||||
        this.checkHasOffline();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async gotoBlog(): Promise<void> {
 | 
			
		||||
        this.isOpeningPage = true;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await super.gotoBlog();
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.isOpeningPage = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component destroyed.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -3,26 +3,6 @@
 | 
			
		||||
    <ion-button *ngIf="loaded" (click)="showToc()" aria-haspopup="true" [attr.aria-label]="'addon.mod_imscp.toc' | translate">
 | 
			
		||||
        <ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
 | 
			
		||||
            [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
 | 
			
		||||
 | 
			
		||||
    protected items: AddonModImscpTocItem[] = [];
 | 
			
		||||
    protected currentHref?: string;
 | 
			
		||||
    protected displayDescription = false;
 | 
			
		||||
 | 
			
		||||
    constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
 | 
			
		||||
        super('AddonModImscpIndexComponent', courseContentsPage);
 | 
			
		||||
@ -70,42 +71,33 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download imscp contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            const downloadResult = await this.downloadResourceIfNeeded(refresh);
 | 
			
		||||
        const downloadResult = await this.downloadResourceIfNeeded(refresh);
 | 
			
		||||
 | 
			
		||||
            const imscp = await AddonModImscp.getImscp(this.courseId, this.module.id);
 | 
			
		||||
            this.description = imscp.intro;
 | 
			
		||||
            this.dataRetrieved.emit(imscp);
 | 
			
		||||
        const imscp = await AddonModImscp.getImscp(this.courseId, this.module.id);
 | 
			
		||||
        this.description = imscp.intro;
 | 
			
		||||
        this.dataRetrieved.emit(imscp);
 | 
			
		||||
 | 
			
		||||
            // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | 
			
		||||
            const contents = await CoreCourse.getModuleContents(this.module);
 | 
			
		||||
        // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | 
			
		||||
        const contents = await CoreCourse.getModuleContents(this.module);
 | 
			
		||||
 | 
			
		||||
            this.items = AddonModImscp.createItemList(contents);
 | 
			
		||||
        this.items = AddonModImscp.createItemList(contents);
 | 
			
		||||
 | 
			
		||||
            if (this.items.length && this.currentHref === undefined) {
 | 
			
		||||
                this.currentHref = this.items[0].href;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                await this.loadItemHref(this.currentHref);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'addon.mod_imscp.deploymenterror', true);
 | 
			
		||||
 | 
			
		||||
                throw new CoreSilentError(error);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
 | 
			
		||||
 | 
			
		||||
        } finally {
 | 
			
		||||
            // Pass false because downloadResourceIfNeeded already invalidates and refresh data if refresh=true.
 | 
			
		||||
            this.fillContextMenu(false);
 | 
			
		||||
        if (this.items.length && this.currentHref === undefined) {
 | 
			
		||||
            this.currentHref = this.items[0].href;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.loadItemHref(this.currentHref);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_imscp.deploymenterror', true);
 | 
			
		||||
 | 
			
		||||
            throw new CoreSilentError(error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -136,85 +136,76 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the lesson data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            let lessonReady = true;
 | 
			
		||||
            this.askPassword = false;
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        let lessonReady = true;
 | 
			
		||||
        this.askPassword = false;
 | 
			
		||||
 | 
			
		||||
            this.lesson = await AddonModLesson.getLesson(this.courseId, this.module.id);
 | 
			
		||||
        this.lesson = await AddonModLesson.getLesson(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.lesson);
 | 
			
		||||
            this.description = this.lesson.intro; // Show description only if intro is present.
 | 
			
		||||
        this.dataRetrieved.emit(this.lesson);
 | 
			
		||||
        this.description = this.lesson.intro; // Show description only if intro is present.
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the lesson.
 | 
			
		||||
                await this.syncActivity(showErrors);
 | 
			
		||||
            }
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the lesson.
 | 
			
		||||
            await this.syncActivity(showErrors);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            this.accessInfo = await AddonModLesson.getAccessInformation(this.lesson.id, { cmId: this.module.id });
 | 
			
		||||
            this.canManage = this.accessInfo.canmanage;
 | 
			
		||||
            this.canViewReports = this.accessInfo.canviewreports;
 | 
			
		||||
            this.preventReasons = [];
 | 
			
		||||
            const promises: Promise<void>[] = [];
 | 
			
		||||
        this.accessInfo = await AddonModLesson.getAccessInformation(this.lesson.id, { cmId: this.module.id });
 | 
			
		||||
        this.canManage = this.accessInfo.canmanage;
 | 
			
		||||
        this.canViewReports = this.accessInfo.canviewreports;
 | 
			
		||||
        this.preventReasons = [];
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
            if (AddonModLesson.isLessonOffline(this.lesson)) {
 | 
			
		||||
                // Handle status.
 | 
			
		||||
                this.setStatusListener();
 | 
			
		||||
        if (AddonModLesson.isLessonOffline(this.lesson)) {
 | 
			
		||||
            // Handle status.
 | 
			
		||||
            this.setStatusListener();
 | 
			
		||||
 | 
			
		||||
                promises.push(this.loadOfflineData());
 | 
			
		||||
            }
 | 
			
		||||
            promises.push(this.loadOfflineData());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if (this.accessInfo.preventaccessreasons.length) {
 | 
			
		||||
                let preventReason = AddonModLesson.getPreventAccessReason(this.accessInfo, false);
 | 
			
		||||
                const askPassword = preventReason?.reason == 'passwordprotectedlesson';
 | 
			
		||||
        if (this.accessInfo.preventaccessreasons.length) {
 | 
			
		||||
            let preventReason = AddonModLesson.getPreventAccessReason(this.accessInfo, false);
 | 
			
		||||
            const askPassword = preventReason?.reason == 'passwordprotectedlesson';
 | 
			
		||||
 | 
			
		||||
                if (askPassword) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        // The lesson requires a password. Check if there is one in memory or DB.
 | 
			
		||||
                        const password = this.password ?
 | 
			
		||||
                            this.password :
 | 
			
		||||
                            await AddonModLesson.getStoredPassword(this.lesson.id);
 | 
			
		||||
            if (askPassword) {
 | 
			
		||||
                try {
 | 
			
		||||
                    // The lesson requires a password. Check if there is one in memory or DB.
 | 
			
		||||
                    const password = this.password ?
 | 
			
		||||
                        this.password :
 | 
			
		||||
                        await AddonModLesson.getStoredPassword(this.lesson.id);
 | 
			
		||||
 | 
			
		||||
                        await this.validatePassword(password);
 | 
			
		||||
                    await this.validatePassword(password);
 | 
			
		||||
 | 
			
		||||
                        // Now that we have the password, get the access reason again ignoring the password.
 | 
			
		||||
                        preventReason = AddonModLesson.getPreventAccessReason(this.accessInfo, true);
 | 
			
		||||
                        if (preventReason) {
 | 
			
		||||
                            this.preventReasons = [preventReason];
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch {
 | 
			
		||||
                        // No password or the validation failed. Show password form.
 | 
			
		||||
                        this.askPassword = true;
 | 
			
		||||
                        this.preventReasons = [preventReason!];
 | 
			
		||||
                        lessonReady = false;
 | 
			
		||||
                    // Now that we have the password, get the access reason again ignoring the password.
 | 
			
		||||
                    preventReason = AddonModLesson.getPreventAccessReason(this.accessInfo, true);
 | 
			
		||||
                    if (preventReason) {
 | 
			
		||||
                        this.preventReasons = [preventReason];
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Lesson cannot be started.
 | 
			
		||||
                } catch {
 | 
			
		||||
                    // No password or the validation failed. Show password form.
 | 
			
		||||
                    this.askPassword = true;
 | 
			
		||||
                    this.preventReasons = [preventReason!];
 | 
			
		||||
                    lessonReady = false;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Lesson cannot be started.
 | 
			
		||||
                this.preventReasons = [preventReason!];
 | 
			
		||||
                lessonReady = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if (this.selectedTab == 1 && this.canViewReports) {
 | 
			
		||||
                // Only fetch the report data if the tab is selected.
 | 
			
		||||
                promises.push(this.fetchReportData());
 | 
			
		||||
            }
 | 
			
		||||
        if (this.selectedTab == 1 && this.canViewReports) {
 | 
			
		||||
            // Only fetch the report data if the tab is selected.
 | 
			
		||||
            promises.push(this.fetchReportData());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            await Promise.all(promises);
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
            if (lessonReady) {
 | 
			
		||||
                // Lesson can be started, don't ask the password and don't show prevent messages.
 | 
			
		||||
                this.lessonReady();
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        if (lessonReady) {
 | 
			
		||||
            // Lesson can be started, don't ask the password and don't show prevent messages.
 | 
			
		||||
            this.lessonReady();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -633,8 +624,6 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.loaded = false;
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.validatePassword(<string> password);
 | 
			
		||||
@ -652,8 +641,6 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
            CoreDomUtils.showErrorModal(error);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
            this.syncIcon = CoreConstants.ICON_SYNC;
 | 
			
		||||
 | 
			
		||||
            CoreForms.triggerFormSubmittedEvent(this.formElement, true, this.siteId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
@ -24,7 +9,7 @@
 | 
			
		||||
<core-loading [hideUntil]="loaded" class="safe-area-padding">
 | 
			
		||||
 | 
			
		||||
    <!-- Activity info. -->
 | 
			
		||||
    <core-course-module-info [module]="module" [description]="lti && lti.showdescriptionlaunch && description" [component]="component"
 | 
			
		||||
    <core-course-module-info [module]="module" [description]="displayDescription && description" [component]="component"
 | 
			
		||||
        [componentId]="componentId" [courseId]="courseId">
 | 
			
		||||
    </core-course-module-info>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
 | 
			
		||||
 | 
			
		||||
    component = AddonModLtiProvider.COMPONENT;
 | 
			
		||||
    moduleName = 'lti';
 | 
			
		||||
    displayDescription = false;
 | 
			
		||||
 | 
			
		||||
    lti?: AddonModLtiLti; // The LTI object.
 | 
			
		||||
 | 
			
		||||
@ -55,15 +56,13 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.lti = await AddonModLti.getLti(this.courseId, this.module.id);
 | 
			
		||||
    protected async fetchContent(): Promise<void> {
 | 
			
		||||
        this.lti = await AddonModLti.getLti(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.description = this.lti.intro;
 | 
			
		||||
            this.dataRetrieved.emit(this.lti);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
        this.description = this.lti.intro;
 | 
			
		||||
 | 
			
		||||
        this.displayDescription = this.lti && !!this.lti.showdescriptionlaunch;
 | 
			
		||||
        this.dataRetrieved.emit(this.lti);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,26 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
 | 
			
		||||
            [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -71,29 +71,22 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download page contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh?: boolean): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            // Download the resource if it needs to be downloaded.
 | 
			
		||||
            const downloadResult = await this.downloadResourceIfNeeded(refresh);
 | 
			
		||||
        // Download the resource if it needs to be downloaded.
 | 
			
		||||
        const downloadResult = await this.downloadResourceIfNeeded(refresh);
 | 
			
		||||
 | 
			
		||||
            // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | 
			
		||||
            const contents = await CoreCourse.getModuleContents(this.module);
 | 
			
		||||
        // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | 
			
		||||
        const contents = await CoreCourse.getModuleContents(this.module);
 | 
			
		||||
 | 
			
		||||
            const results = await Promise.all([
 | 
			
		||||
                this.loadPageData(),
 | 
			
		||||
                AddonModPageHelper.getPageHtml(contents, this.module.id),
 | 
			
		||||
            ]);
 | 
			
		||||
        const results = await Promise.all([
 | 
			
		||||
            this.loadPageData(),
 | 
			
		||||
            AddonModPageHelper.getPageHtml(contents, this.module.id),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
            this.contents = results[1];
 | 
			
		||||
            this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
        this.contents = results[1];
 | 
			
		||||
        this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'far-newspaper'"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
@ -214,7 +190,7 @@
 | 
			
		||||
 | 
			
		||||
            <!-- Button to open in browser if it cannot be attempted in the app. -->
 | 
			
		||||
            <ion-button class="ion-text-wrap ion-margin" *ngIf="!buttonText && ((!hasSupportedQuestions && unsupportedQuestions.length) ||
 | 
			
		||||
                unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="externalUrl" core-link
 | 
			
		||||
                unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="module.url" core-link
 | 
			
		||||
                [showBrowserWarning]="false">
 | 
			
		||||
                {{ 'core.openinbrowser' | translate }}
 | 
			
		||||
                <ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
 | 
			
		||||
 | 
			
		||||
@ -180,82 +180,73 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the quiz data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            // First get the quiz instance.
 | 
			
		||||
            const quiz = await AddonModQuiz.getQuiz(this.courseId, this.module.id);
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        // First get the quiz instance.
 | 
			
		||||
        const quiz = await AddonModQuiz.getQuiz(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.gradeMethodReadable = AddonModQuiz.getQuizGradeMethod(quiz.grademethod);
 | 
			
		||||
            this.now = Date.now();
 | 
			
		||||
            this.dataRetrieved.emit(quiz);
 | 
			
		||||
            this.description = quiz.intro || this.description;
 | 
			
		||||
            this.candidateQuiz = quiz;
 | 
			
		||||
        this.gradeMethodReadable = AddonModQuiz.getQuizGradeMethod(quiz.grademethod);
 | 
			
		||||
        this.now = Date.now();
 | 
			
		||||
        this.dataRetrieved.emit(quiz);
 | 
			
		||||
        this.description = quiz.intro || this.description;
 | 
			
		||||
        this.candidateQuiz = quiz;
 | 
			
		||||
 | 
			
		||||
            // Try to get warnings from automatic sync.
 | 
			
		||||
            const warnings = await AddonModQuizSync.getSyncWarnings(quiz.id);
 | 
			
		||||
        // Try to get warnings from automatic sync.
 | 
			
		||||
        const warnings = await AddonModQuizSync.getSyncWarnings(quiz.id);
 | 
			
		||||
 | 
			
		||||
            if (warnings?.length) {
 | 
			
		||||
                // Show warnings and delete them so they aren't shown again.
 | 
			
		||||
                CoreDomUtils.showErrorModal(CoreTextUtils.buildMessage(warnings));
 | 
			
		||||
        if (warnings?.length) {
 | 
			
		||||
            // Show warnings and delete them so they aren't shown again.
 | 
			
		||||
            CoreDomUtils.showErrorModal(CoreTextUtils.buildMessage(warnings));
 | 
			
		||||
 | 
			
		||||
                await AddonModQuizSync.setSyncWarnings(quiz.id, []);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (AddonModQuiz.isQuizOffline(quiz)) {
 | 
			
		||||
                if (sync) {
 | 
			
		||||
                    // Try to sync the quiz.
 | 
			
		||||
                    try {
 | 
			
		||||
                        await this.syncActivity(showErrors);
 | 
			
		||||
                    } catch {
 | 
			
		||||
                        // Ignore errors, keep getting data even if sync fails.
 | 
			
		||||
                        this.autoReview = undefined;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.autoReview = undefined;
 | 
			
		||||
                this.showStatusSpinner = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (AddonModQuiz.isQuizOffline(quiz)) {
 | 
			
		||||
                // Handle status.
 | 
			
		||||
                this.setStatusListener();
 | 
			
		||||
 | 
			
		||||
                // Get last synchronization time and check if sync button should be seen.
 | 
			
		||||
                this.syncTime = await AddonModQuizSync.getReadableSyncTime(quiz.id);
 | 
			
		||||
                this.hasOffline = await AddonModQuizSync.hasDataToSync(quiz.id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get quiz access info.
 | 
			
		||||
            this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
            this.showReviewColumn = this.quizAccessInfo.canreviewmyattempts;
 | 
			
		||||
            this.accessRules = this.quizAccessInfo.accessrules;
 | 
			
		||||
            this.unsupportedRules = AddonModQuiz.getUnsupportedRules(this.quizAccessInfo.activerulenames);
 | 
			
		||||
 | 
			
		||||
            if (quiz.preferredbehaviour) {
 | 
			
		||||
                this.behaviourSupported = CoreQuestionBehaviourDelegate.isBehaviourSupported(quiz.preferredbehaviour);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get question types in the quiz.
 | 
			
		||||
            const types = await AddonModQuiz.getQuizRequiredQtypes(quiz.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
            this.unsupportedQuestions = AddonModQuiz.getUnsupportedQuestions(types);
 | 
			
		||||
            this.hasSupportedQuestions = !!types.find((type) => type != 'random' && this.unsupportedQuestions.indexOf(type) == -1);
 | 
			
		||||
 | 
			
		||||
            await this.getAttempts(quiz);
 | 
			
		||||
 | 
			
		||||
            // Quiz is ready to be shown, move it to the variable that is displayed.
 | 
			
		||||
            this.quiz = quiz;
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
            await AddonModQuizSync.setSyncWarnings(quiz.id, []);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (AddonModQuiz.isQuizOffline(quiz)) {
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to sync the quiz.
 | 
			
		||||
                try {
 | 
			
		||||
                    await this.syncActivity(showErrors);
 | 
			
		||||
                } catch {
 | 
			
		||||
                    // Ignore errors, keep getting data even if sync fails.
 | 
			
		||||
                    this.autoReview = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.autoReview = undefined;
 | 
			
		||||
            this.showStatusSpinner = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (AddonModQuiz.isQuizOffline(quiz)) {
 | 
			
		||||
            // Handle status.
 | 
			
		||||
            this.setStatusListener();
 | 
			
		||||
 | 
			
		||||
            // Get last synchronization time and check if sync button should be seen.
 | 
			
		||||
            this.syncTime = await AddonModQuizSync.getReadableSyncTime(quiz.id);
 | 
			
		||||
            this.hasOffline = await AddonModQuizSync.hasDataToSync(quiz.id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get quiz access info.
 | 
			
		||||
        this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        this.showReviewColumn = this.quizAccessInfo.canreviewmyattempts;
 | 
			
		||||
        this.accessRules = this.quizAccessInfo.accessrules;
 | 
			
		||||
        this.unsupportedRules = AddonModQuiz.getUnsupportedRules(this.quizAccessInfo.activerulenames);
 | 
			
		||||
 | 
			
		||||
        if (quiz.preferredbehaviour) {
 | 
			
		||||
            this.behaviourSupported = CoreQuestionBehaviourDelegate.isBehaviourSupported(quiz.preferredbehaviour);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get question types in the quiz.
 | 
			
		||||
        const types = await AddonModQuiz.getQuizRequiredQtypes(quiz.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        this.unsupportedQuestions = AddonModQuiz.getUnsupportedQuestions(types);
 | 
			
		||||
        this.hasSupportedQuestions = !!types.find((type) => type != 'random' && this.unsupportedQuestions.indexOf(type) == -1);
 | 
			
		||||
 | 
			
		||||
        await this.getAttempts(quiz);
 | 
			
		||||
 | 
			
		||||
        // Quiz is ready to be shown, move it to the variable that is displayed.
 | 
			
		||||
        this.quiz = quiz;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -465,16 +456,12 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
 | 
			
		||||
        // Refresh data.
 | 
			
		||||
        this.loaded = false;
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.content?.scrollToTop();
 | 
			
		||||
 | 
			
		||||
        await promise;
 | 
			
		||||
        await CoreUtils.ignoreErrors(this.refreshContent(true));
 | 
			
		||||
 | 
			
		||||
        this.loaded = true;
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_SYNC;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,21 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
 | 
			
		||||
            [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
@ -25,9 +9,8 @@
 | 
			
		||||
<core-loading [hideUntil]="loaded" class="safe-area-padding">
 | 
			
		||||
 | 
			
		||||
    <!-- Activity info. -->
 | 
			
		||||
    <core-course-module-info [module]="module" [courseId]="courseId"
 | 
			
		||||
        [description]="mode != 'iframe' && (mode != 'embedded' || displayDescription) && description" [component]="component"
 | 
			
		||||
        [componentId]="componentId">
 | 
			
		||||
    <core-course-module-info [module]="module" [courseId]="courseId" [description]="displayDescription && description"
 | 
			
		||||
        [component]="component" [componentId]="componentId">
 | 
			
		||||
    </core-course-module-info>
 | 
			
		||||
 | 
			
		||||
    <ion-card class="core-warning-card" *ngIf="warning">
 | 
			
		||||
 | 
			
		||||
@ -116,76 +116,74 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
 | 
			
		||||
            throw new CoreError(Translate.instant('core.filenotfound'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let hasCalledDownloadResource = false;
 | 
			
		||||
 | 
			
		||||
        // Get the resource instance to get the latest name/description and to know if it's embedded.
 | 
			
		||||
        const resource = await AddonModResource.getResourceData(this.courseId, this.module.id);
 | 
			
		||||
        this.description = resource.intro || '';
 | 
			
		||||
        const options: AddonModResourceCustomData =
 | 
			
		||||
            resource.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {};
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.displayDescription = options.printintro === undefined || !!options.printintro;
 | 
			
		||||
            this.dataRetrieved.emit(resource);
 | 
			
		||||
        this.displayDescription = options.printintro === undefined || !!options.printintro;
 | 
			
		||||
        this.dataRetrieved.emit(resource);
 | 
			
		||||
 | 
			
		||||
            if (AddonModResourceHelper.isDisplayedInIframe(this.module)) {
 | 
			
		||||
                hasCalledDownloadResource = true;
 | 
			
		||||
        if (AddonModResourceHelper.isDisplayedInIframe(this.module)) {
 | 
			
		||||
 | 
			
		||||
                const downloadResult = await this.downloadResourceIfNeeded(refresh, true);
 | 
			
		||||
                const src = await AddonModResourceHelper.getIframeSrc(this.module);
 | 
			
		||||
                this.mode = 'iframe';
 | 
			
		||||
            const downloadResult = await this.downloadResourceIfNeeded(refresh, true);
 | 
			
		||||
            const src = await AddonModResourceHelper.getIframeSrc(this.module);
 | 
			
		||||
            this.mode = 'iframe';
 | 
			
		||||
 | 
			
		||||
                if (this.src && src.toString() == this.src.toString()) {
 | 
			
		||||
                    // Re-loading same page.
 | 
			
		||||
                    // Set it to empty and then re-set the src in the next digest so it detects it has changed.
 | 
			
		||||
                    this.src = '';
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        this.src = src;
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
            if (this.src && src.toString() == this.src.toString()) {
 | 
			
		||||
                // Re-loading same page.
 | 
			
		||||
                // Set it to empty and then re-set the src in the next digest so it detects it has changed.
 | 
			
		||||
                this.src = '';
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    this.src = src;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.warning = downloadResult.failed
 | 
			
		||||
                    ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!)
 | 
			
		||||
                    : '';
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (resource && 'display' in resource && AddonModResourceHelper.isDisplayedEmbedded(this.module, resource.display)) {
 | 
			
		||||
                this.mode = 'embedded';
 | 
			
		||||
                this.warning = '';
 | 
			
		||||
 | 
			
		||||
                this.contentText = await AddonModResourceHelper.getEmbeddedHtml(this.module);
 | 
			
		||||
                this.mode = this.contentText.length > 0 ? 'embedded' : 'external';
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                this.mode = 'external';
 | 
			
		||||
                this.warning = '';
 | 
			
		||||
                let mimetype: string;
 | 
			
		||||
 | 
			
		||||
                if (this.isIOS) {
 | 
			
		||||
                    this.shouldOpenInBrowser = CoreFileHelper.shouldOpenInBrowser(contents[0]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ('contentsinfo' in this.module && this.module.contentsinfo) {
 | 
			
		||||
                    mimetype = this.module.contentsinfo.mimetypes[0];
 | 
			
		||||
                    this.readableSize = CoreTextUtils.bytesToSize(this.module.contentsinfo.filessize, 1);
 | 
			
		||||
                    this.timemodified = this.module.contentsinfo.lastmodified * 1000;
 | 
			
		||||
                } else {
 | 
			
		||||
                    mimetype = await CoreUtils.getMimeTypeFromUrl(CoreFileHelper.getFileUrl(contents[0]));
 | 
			
		||||
                    this.readableSize = CoreTextUtils.bytesToSize(contents[0].filesize, 1);
 | 
			
		||||
                    this.timemodified = contents[0].timemodified * 1000;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.timecreated = contents[0].timecreated * 1000;
 | 
			
		||||
                this.isExternalFile = !!contents[0].isexternalfile;
 | 
			
		||||
                this.type = CoreMimetypeUtils.getMimetypeDescription(mimetype);
 | 
			
		||||
                this.isStreamedFile = CoreMimetypeUtils.isStreamedMimetype(mimetype);
 | 
			
		||||
                this.src = src;
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            // Pass false in some cases because downloadResourceIfNeeded already invalidates and refresh data if refresh=true.
 | 
			
		||||
            this.fillContextMenu(hasCalledDownloadResource ? false : refresh);
 | 
			
		||||
 | 
			
		||||
            // Never show description on iframe.
 | 
			
		||||
            this.displayDescription = false;
 | 
			
		||||
 | 
			
		||||
            this.warning = downloadResult.failed
 | 
			
		||||
                ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!)
 | 
			
		||||
                : '';
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (resource && 'display' in resource && AddonModResourceHelper.isDisplayedEmbedded(this.module, resource.display)) {
 | 
			
		||||
            this.mode = 'embedded';
 | 
			
		||||
            this.warning = '';
 | 
			
		||||
 | 
			
		||||
            this.contentText = await AddonModResourceHelper.getEmbeddedHtml(this.module);
 | 
			
		||||
            this.mode = this.contentText.length > 0 ? 'embedded' : 'external';
 | 
			
		||||
        } else {
 | 
			
		||||
            this.mode = 'external';
 | 
			
		||||
            this.warning = '';
 | 
			
		||||
            let mimetype: string;
 | 
			
		||||
 | 
			
		||||
            // Always show description on external.
 | 
			
		||||
            this.displayDescription = true;
 | 
			
		||||
 | 
			
		||||
            if (this.isIOS) {
 | 
			
		||||
                this.shouldOpenInBrowser = CoreFileHelper.shouldOpenInBrowser(contents[0]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ('contentsinfo' in this.module && this.module.contentsinfo) {
 | 
			
		||||
                mimetype = this.module.contentsinfo.mimetypes[0];
 | 
			
		||||
                this.readableSize = CoreTextUtils.bytesToSize(this.module.contentsinfo.filessize, 1);
 | 
			
		||||
                this.timemodified = this.module.contentsinfo.lastmodified * 1000;
 | 
			
		||||
            } else {
 | 
			
		||||
                mimetype = await CoreUtils.getMimeTypeFromUrl(CoreFileHelper.getFileUrl(contents[0]));
 | 
			
		||||
                this.readableSize = CoreTextUtils.bytesToSize(contents[0].filesize, 1);
 | 
			
		||||
                this.timemodified = contents[0].timemodified * 1000;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.timecreated = contents[0].timecreated * 1000;
 | 
			
		||||
            this.isExternalFile = !!contents[0].isexternalfile;
 | 
			
		||||
            this.type = CoreMimetypeUtils.getMimetypeDescription(mimetype);
 | 
			
		||||
            this.isStreamedFile = CoreMimetypeUtils.isStreamedMimetype(mimetype);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
@ -176,7 +152,7 @@
 | 
			
		||||
                    <p class="text-danger">{{ errorMessage | translate }}</p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-button class="ion-margin ion-text-wrap" expand="block" [href]="externalUrl" core-link [showBrowserWarning]="false">
 | 
			
		||||
            <ion-button class="ion-margin ion-text-wrap" expand="block" [href]="module.url" core-link [showBrowserWarning]="false">
 | 
			
		||||
                {{ 'core.openinbrowser' | translate }}
 | 
			
		||||
                <ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -175,43 +175,39 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            // Get the SCORM instance.
 | 
			
		||||
            this.scorm = await AddonModScorm.getScorm(this.courseId, this.module.id, { moduleUrl: this.module.url });
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        // Get the SCORM instance.
 | 
			
		||||
        this.scorm = await AddonModScorm.getScorm(this.courseId, this.module.id, { moduleUrl: this.module.url });
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.scorm);
 | 
			
		||||
            this.description = this.scorm.intro || this.description;
 | 
			
		||||
            this.errorMessage = AddonModScorm.isScormUnsupported(this.scorm);
 | 
			
		||||
        this.dataRetrieved.emit(this.scorm);
 | 
			
		||||
        this.description = this.scorm.intro || this.description;
 | 
			
		||||
        this.errorMessage = AddonModScorm.isScormUnsupported(this.scorm);
 | 
			
		||||
 | 
			
		||||
            if (this.scorm.warningMessage) {
 | 
			
		||||
                return; // SCORM is closed or not open yet, we can't get more data.
 | 
			
		||||
            }
 | 
			
		||||
        if (this.scorm.warningMessage) {
 | 
			
		||||
            return; // SCORM is closed or not open yet, we can't get more data.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the SCORM.
 | 
			
		||||
                await CoreUtils.ignoreErrors(this.syncActivity(showErrors));
 | 
			
		||||
            }
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the SCORM.
 | 
			
		||||
            await CoreUtils.ignoreErrors(this.syncActivity(showErrors));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            const [syncTime, accessInfo] = await Promise.all([
 | 
			
		||||
                AddonModScormSync.getReadableSyncTime(this.scorm.id),
 | 
			
		||||
                AddonModScorm.getAccessInformation(this.scorm.id, { cmId: this.module.id }),
 | 
			
		||||
                this.fetchAttemptData(this.scorm),
 | 
			
		||||
            ]);
 | 
			
		||||
        const [syncTime, accessInfo] = await Promise.all([
 | 
			
		||||
            AddonModScormSync.getReadableSyncTime(this.scorm.id),
 | 
			
		||||
            AddonModScorm.getAccessInformation(this.scorm.id, { cmId: this.module.id }),
 | 
			
		||||
            this.fetchAttemptData(this.scorm),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
            this.syncTime = syncTime;
 | 
			
		||||
            this.accessInfo = accessInfo;
 | 
			
		||||
        this.syncTime = syncTime;
 | 
			
		||||
        this.accessInfo = accessInfo;
 | 
			
		||||
 | 
			
		||||
            // Check whether to launch the SCORM immediately.
 | 
			
		||||
            if (this.skip === undefined) {
 | 
			
		||||
                this.skip = !this.hasOffline && !this.errorMessage &&
 | 
			
		||||
        // Check whether to launch the SCORM immediately.
 | 
			
		||||
        if (this.skip === undefined) {
 | 
			
		||||
            this.skip = !this.hasOffline && !this.errorMessage &&
 | 
			
		||||
                    (!this.scorm.lastattemptlock || this.attemptsLeft > 0) &&
 | 
			
		||||
                    this.accessInfo.canskipview && !this.accessInfo.canviewreport &&
 | 
			
		||||
                    this.scorm.skipview! >= AddonModScormProvider.SKIPVIEW_FIRST &&
 | 
			
		||||
                    (this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0);
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
@ -41,7 +16,7 @@
 | 
			
		||||
    <!-- Survey already done -->
 | 
			
		||||
    <ion-card class="ion-padding" *ngIf="survey && survey.surveydone">
 | 
			
		||||
        <p class="ion-padding">{{ 'addon.mod_survey.surveycompletednograph' | translate }}</p>
 | 
			
		||||
        <ion-button expand="block" [href]="externalUrl" core-link>
 | 
			
		||||
        <ion-button expand="block" [href]="module.url" core-link>
 | 
			
		||||
            <ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
 | 
			
		||||
            {{ 'addon.mod_survey.results' | translate }}
 | 
			
		||||
        </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -115,39 +115,30 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download survey contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.description = this.survey.intro;
 | 
			
		||||
            this.dataRetrieved.emit(this.survey);
 | 
			
		||||
        this.description = this.survey.intro;
 | 
			
		||||
        this.dataRetrieved.emit(this.survey);
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the survey.
 | 
			
		||||
                const answersSent = await this.syncActivity(showErrors);
 | 
			
		||||
                if (answersSent) {
 | 
			
		||||
                    // Answers were sent, update the survey.
 | 
			
		||||
                    this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
 | 
			
		||||
                }
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the survey.
 | 
			
		||||
            const answersSent = await this.syncActivity(showErrors);
 | 
			
		||||
            if (answersSent) {
 | 
			
		||||
                // Answers were sent, update the survey.
 | 
			
		||||
                this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            // Check if there are answers stored in offline.
 | 
			
		||||
            this.hasOffline = this.survey.surveydone
 | 
			
		||||
                ? false
 | 
			
		||||
                : await AddonModSurveyOffline.hasAnswers(this.survey.id);
 | 
			
		||||
        // Check if there are answers stored in offline.
 | 
			
		||||
        this.hasOffline = this.survey.surveydone
 | 
			
		||||
            ? false
 | 
			
		||||
            : await AddonModSurveyOffline.hasAnswers(this.survey.id);
 | 
			
		||||
 | 
			
		||||
            if (!this.survey.surveydone && !this.hasOffline) {
 | 
			
		||||
                await this.fetchQuestions();
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        if (!this.survey.surveydone && !this.hasOffline) {
 | 
			
		||||
            await this.fetchQuestions();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
 | 
			
		||||
            [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -13,34 +13,12 @@
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline && !pageIsOffline" [priority]="700"
 | 
			
		||||
            [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && isOnline && (hasOffline || pageIsOffline)" [priority]="600"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="canEdit && (isOnline || pageIsOffline)" [priority]="590" [content]="'core.edit' | translate"
 | 
			
		||||
            iconAction="fas-edit" (action)="goToEditPage()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="canEdit" [priority]="580" [content]="'addon.mod_wiki.createpage' | translate" iconAction="fas-plus"
 | 
			
		||||
            (action)="goToNewPage()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
 | 
			
		||||
@ -21,14 +21,16 @@ import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
 | 
			
		||||
import { CoreUser } from '@features/user/services/user';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreGroup, CoreGroups } from '@services/groups';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { Network, Translate, NgZone } from '@singletons';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { Md5 } from 'ts-md5';
 | 
			
		||||
import { AddonModWikiPageDBRecord } from '../../services/database/wiki';
 | 
			
		||||
import { AddonModWikiModuleHandlerService } from '../../services/handlers/module';
 | 
			
		||||
@ -76,6 +78,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
    moduleName = 'wiki';
 | 
			
		||||
    groupWiki = false;
 | 
			
		||||
 | 
			
		||||
    isOnline = false;
 | 
			
		||||
 | 
			
		||||
    wiki?: AddonModWikiWiki; // The wiki instance.
 | 
			
		||||
    isMainPage = false; // Whether the user is viewing wiki's main page (just entered the wiki).
 | 
			
		||||
    canEdit = false; // Whether user can edit the page.
 | 
			
		||||
@ -104,12 +108,23 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
    protected ignoreManualSyncEvent = false; // Whether manual sync event should be ignored.
 | 
			
		||||
    protected currentUserId?: number; // Current user ID.
 | 
			
		||||
    protected currentPath!: string;
 | 
			
		||||
    protected onlineSubscription: Subscription; // It will observe the status of the network connection.
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected content?: IonContent,
 | 
			
		||||
        @Optional() courseContentsPage?: CoreCourseContentsPage,
 | 
			
		||||
    ) {
 | 
			
		||||
        super('AddonModLessonIndexComponent', content, courseContentsPage);
 | 
			
		||||
 | 
			
		||||
        this.isOnline = CoreApp.isOnline();
 | 
			
		||||
 | 
			
		||||
        // Refresh online status when changes.
 | 
			
		||||
        this.onlineSubscription = Network.onChange().subscribe(() => {
 | 
			
		||||
            // Execute the callback in the Angular zone, so change detection doesn't stop working.
 | 
			
		||||
            NgZone.run(() => {
 | 
			
		||||
                this.isOnline = CoreApp.isOnline();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -212,7 +227,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            // Get the wiki instance.
 | 
			
		||||
            this.wiki = await AddonModWiki.getWiki(this.courseId, this.module.id);
 | 
			
		||||
@ -240,7 +255,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.description = this.wiki.intro || this.module.description;
 | 
			
		||||
            this.externalUrl = this.module.url;
 | 
			
		||||
            this.componentId = this.module.id;
 | 
			
		||||
 | 
			
		||||
            await this.fetchSubwikis(this.wiki.id);
 | 
			
		||||
@ -278,8 +292,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw error;
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -835,6 +847,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
 | 
			
		||||
 | 
			
		||||
        this.manualSyncObserver?.off();
 | 
			
		||||
        this.newPageObserver?.off();
 | 
			
		||||
        this.onlineSubscription.unsubscribe();
 | 
			
		||||
        if (this.wiki) {
 | 
			
		||||
            AddonModWiki.wikiPageClosed(this.wiki.id, this.currentPath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
 | 
			
		||||
            iconAction="fas-external-link-alt" [showBrowserWarning]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
 | 
			
		||||
            (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="200" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
 | 
			
		||||
    <ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
 | 
			
		||||
        <ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
@ -216,55 +216,45 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download feedback contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.workshop = await AddonModWorkshop.getWorkshop(this.courseId, this.module.id);
 | 
			
		||||
    protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        this.workshop = await AddonModWorkshop.getWorkshop(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.description = this.workshop.intro;
 | 
			
		||||
            this.dataRetrieved.emit(this.workshop);
 | 
			
		||||
        this.description = this.workshop.intro;
 | 
			
		||||
        this.dataRetrieved.emit(this.workshop);
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the feedback.
 | 
			
		||||
                await this.syncActivity(showErrors);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check if there are answers stored in offline.
 | 
			
		||||
            this.access = await AddonModWorkshop.getWorkshopAccessInformation(this.workshop.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
            if (this.access.canviewallsubmissions) {
 | 
			
		||||
                this.groupInfo = await CoreGroups.getActivityGroupInfo(this.workshop.coursemodule);
 | 
			
		||||
                this.group = CoreGroups.validateGroupId(this.group, this.groupInfo);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.phases = await AddonModWorkshop.getUserPlanPhases(this.workshop.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
            this.phases[this.workshop.phase].tasks.forEach((task) => {
 | 
			
		||||
                if (!task.link && (task.code == 'examples' || task.code == 'prepareexamples')) {
 | 
			
		||||
                    // Add links to manage examples.
 | 
			
		||||
                    task.link = this.externalUrl!;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Check if there are info stored in offline.
 | 
			
		||||
            this.hasOffline = await AddonModWorkshopOffline.hasWorkshopOfflineData(this.workshop.id);
 | 
			
		||||
            if (this.hasOffline) {
 | 
			
		||||
                this.offlineSubmissions = await AddonModWorkshopOffline.getSubmissions(this.workshop.id);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.offlineSubmissions = [];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await this.setPhaseInfo();
 | 
			
		||||
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the feedback.
 | 
			
		||||
            await this.syncActivity(showErrors);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check if there are answers stored in offline.
 | 
			
		||||
        this.access = await AddonModWorkshop.getWorkshopAccessInformation(this.workshop.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        if (this.access.canviewallsubmissions) {
 | 
			
		||||
            this.groupInfo = await CoreGroups.getActivityGroupInfo(this.workshop.coursemodule);
 | 
			
		||||
            this.group = CoreGroups.validateGroupId(this.group, this.groupInfo);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.phases = await AddonModWorkshop.getUserPlanPhases(this.workshop.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        this.phases[this.workshop.phase].tasks.forEach((task) => {
 | 
			
		||||
            if (!task.link && (task.code == 'examples' || task.code == 'prepareexamples')) {
 | 
			
		||||
                // Add links to manage examples.
 | 
			
		||||
                task.link = this.module.url || '';
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Check if there are info stored in offline.
 | 
			
		||||
        this.hasOffline = await AddonModWorkshopOffline.hasWorkshopOfflineData(this.workshop.id);
 | 
			
		||||
        if (this.hasOffline) {
 | 
			
		||||
            this.offlineSubmissions = await AddonModWorkshopOffline.getSubmissions(this.workshop.id);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.offlineSubmissions = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.setPhaseInfo();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -394,7 +384,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
 | 
			
		||||
                componentProps: {
 | 
			
		||||
                    phases: CoreUtils.objectToArray(this.phases),
 | 
			
		||||
                    workshopPhase: this.workshop!.phase,
 | 
			
		||||
                    externalUrl: this.externalUrl,
 | 
			
		||||
                    externalUrl: this.module.url,
 | 
			
		||||
                    showSubmit: this.showSubmit,
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -17,15 +17,11 @@ import { IonContent } from '@ionic/angular';
 | 
			
		||||
 | 
			
		||||
import { CoreCourseModuleMainResourceComponent } from './main-resource-component';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { Network, NgZone } from '@singletons';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreCourse } from '../services/course';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { CoreCourseContentsPage } from '../pages/contents/contents';
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -40,12 +36,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
 | 
			
		||||
    moduleName?: string; // Raw module name to be translated. It will be translated on init.
 | 
			
		||||
 | 
			
		||||
    // Data for context menu.
 | 
			
		||||
    syncIcon?: string; // Sync icon.
 | 
			
		||||
    isOnline?: boolean; // If the app is online or not.
 | 
			
		||||
 | 
			
		||||
    protected syncObserver?: CoreEventObserver; // It will observe the sync auto event.
 | 
			
		||||
    protected onlineSubscription: Subscription; // It will observe the status of the network connection.
 | 
			
		||||
    protected syncEventName?: string; // Auto sync event name.
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
@ -54,14 +45,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
        courseContentsPage?: CoreCourseContentsPage,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(loggerName, courseContentsPage);
 | 
			
		||||
 | 
			
		||||
        // Refresh online status when changes.
 | 
			
		||||
        this.onlineSubscription = Network.onChange().subscribe(() => {
 | 
			
		||||
            // Execute the callback in the Angular zone, so change detection doesn't stop working.
 | 
			
		||||
            NgZone.run(() => {
 | 
			
		||||
                this.isOnline = CoreApp.isOnline();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -71,7 +54,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
        await super.ngOnInit();
 | 
			
		||||
 | 
			
		||||
        this.hasOffline = false;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.moduleName = CoreCourse.translateModuleName(this.moduleName || '');
 | 
			
		||||
 | 
			
		||||
        if (this.syncEventName) {
 | 
			
		||||
@ -118,20 +100,12 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        await CoreUtils.ignoreErrors(Promise.all([
 | 
			
		||||
            this.invalidateContent(),
 | 
			
		||||
            this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
 | 
			
		||||
        ]));
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreUtils.ignoreErrors(Promise.all([
 | 
			
		||||
                this.invalidateContent(),
 | 
			
		||||
                this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
 | 
			
		||||
            ]));
 | 
			
		||||
 | 
			
		||||
            await this.loadContent(true, sync, showErrors);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
            this.syncIcon = CoreConstants.ICON_SYNC;
 | 
			
		||||
        }
 | 
			
		||||
        await this.loadContent(true, sync, showErrors);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -142,17 +116,10 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.loaded = false;
 | 
			
		||||
        this.content?.scrollToTop();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.loadContent(false, sync, showErrors);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
            this.syncIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
        }
 | 
			
		||||
        await this.loadContent(false, sync, showErrors);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -163,8 +130,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected showLoadingAndRefresh(sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.loaded = false;
 | 
			
		||||
        this.content?.scrollToTop();
 | 
			
		||||
 | 
			
		||||
@ -193,8 +158,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        this.isOnline = CoreApp.isOnline();
 | 
			
		||||
 | 
			
		||||
        if (!this.module) {
 | 
			
		||||
            // This can happen if course format changes from single activity to weekly/topics.
 | 
			
		||||
            return;
 | 
			
		||||
@ -215,8 +178,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
            this.syncIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -269,8 +230,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        super.ngOnDestroy();
 | 
			
		||||
 | 
			
		||||
        this.onlineSubscription?.unsubscribe();
 | 
			
		||||
        this.syncObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,14 +13,10 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { AddonBlog } from '@addons/blog/services/blog';
 | 
			
		||||
import { AddonBlogMainMenuHandlerService } from '@addons/blog/services/handlers/mainmenu';
 | 
			
		||||
import { OnInit, OnDestroy, Input, Output, EventEmitter, Component, Optional, Inject } from '@angular/core';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { CoreAnyError } from '@classes/errors/error';
 | 
			
		||||
import { IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
 | 
			
		||||
@ -60,20 +56,11 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
    component?: string; // Component name.
 | 
			
		||||
    componentId?: number; // Component ID.
 | 
			
		||||
    hasOffline = false; // Resources don't have any data to sync.
 | 
			
		||||
    blog?: boolean; // If blog is available.
 | 
			
		||||
 | 
			
		||||
    // Data for context menu.
 | 
			
		||||
    externalUrl?: string; // External URL to open in browser.
 | 
			
		||||
    description?: string; // Module description.
 | 
			
		||||
    refreshIcon = CoreConstants.ICON_LOADING; // Refresh icon, normally spinner or refresh.
 | 
			
		||||
    prefetchStatusIcon?: string; // Used when calling fillContextMenu.
 | 
			
		||||
    prefetchStatus?: string; // Used when calling fillContextMenu.
 | 
			
		||||
    prefetchText?: string; // Used when calling fillContextMenu.
 | 
			
		||||
    size?: string; // Used when calling fillContextMenu.
 | 
			
		||||
    downloadTimeReadable?: string; // Last download time in a readable format. Used when calling fillContextMenu.
 | 
			
		||||
    isDestroyed = false; // Whether the component is destroyed, used when calling fillContextMenu.
 | 
			
		||||
    contextMenuStatusObserver?: CoreEventObserver; // Observer of package status, used when calling fillContextMenu.
 | 
			
		||||
    contextFileStatusObserver?: CoreEventObserver; // Observer of file status, used when calling fillContextMenu.
 | 
			
		||||
    prefetchStatus?: string;
 | 
			
		||||
    downloadTimeReadable?: string; // Last download time in a readable format.
 | 
			
		||||
    isDestroyed = false; // Whether the component is destroyed.
 | 
			
		||||
 | 
			
		||||
    protected fetchContentDefaultError = 'core.course.errorgetmodule'; // Default error to show when loading contents.
 | 
			
		||||
    protected isCurrentView = false; // Whether the component is in the current view.
 | 
			
		||||
@ -84,6 +71,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
    protected logger: CoreLogger;
 | 
			
		||||
    protected debouncedUpdateModule?: () => void; // Update the module after a certain time.
 | 
			
		||||
    protected showCompletion = false; // Whether to show completion inside the activity.
 | 
			
		||||
    protected displayDescription = true; // Wether to show Module description on module page, and not on summary or the contrary.
 | 
			
		||||
    protected packageStatusObserver?: CoreEventObserver; // Observer of package status.
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        @Optional() @Inject('') loggerName: string = 'CoreCourseModuleMainResourceComponent',
 | 
			
		||||
@ -93,13 +82,12 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        this.siteId = CoreSites.getCurrentSiteId();
 | 
			
		||||
        this.description = this.module.description;
 | 
			
		||||
        this.componentId = this.module.id;
 | 
			
		||||
        this.externalUrl = this.module.url;
 | 
			
		||||
        this.courseId = this.courseId || this.module.course;
 | 
			
		||||
        this.showCompletion = !!CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('3.11');
 | 
			
		||||
 | 
			
		||||
@ -119,14 +107,22 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
            }, 10000);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.blog = await AddonBlog.isPluginEnabled();
 | 
			
		||||
        this.packageStatusObserver = CoreEvents.on(
 | 
			
		||||
            CoreEvents.PACKAGE_STATUS_CHANGED,
 | 
			
		||||
            (data) => {
 | 
			
		||||
                if (data.componentId == module.id && data.component == this.component) {
 | 
			
		||||
                    this.getPackageStatus();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            this.siteId,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh the data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresher Refresher.
 | 
			
		||||
     * @param done Function to call when done.
 | 
			
		||||
     * @param done Function to call when done. Never used.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
@ -145,7 +141,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
        await CoreUtils.ignoreErrors(this.refreshContent(true, showErrors));
 | 
			
		||||
 | 
			
		||||
        refresher?.complete();
 | 
			
		||||
        done && done();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -162,22 +157,16 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        await CoreUtils.ignoreErrors(Promise.all([
 | 
			
		||||
            this.invalidateContent(),
 | 
			
		||||
            this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
 | 
			
		||||
        ]));
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreUtils.ignoreErrors(Promise.all([
 | 
			
		||||
                this.invalidateContent(),
 | 
			
		||||
                this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
 | 
			
		||||
            ]));
 | 
			
		||||
 | 
			
		||||
            if (this.showCompletion) {
 | 
			
		||||
                this.fetchModule();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await this.loadContent(true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
        if (this.showCompletion) {
 | 
			
		||||
            this.fetchModule();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.loadContent(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -214,6 +203,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.fetchContent(refresh);
 | 
			
		||||
            await this.getPackageStatus(refresh);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!refresh && !CoreSites.getCurrentSite()?.isOfflineDisabled() && this.isNotFoundError(error)) {
 | 
			
		||||
                // Module not found, retry without using cache.
 | 
			
		||||
@ -223,7 +213,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -238,11 +227,20 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fill the context menu options
 | 
			
		||||
     * Updage package status.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If prefetch info has to be refreshed.
 | 
			
		||||
     */
 | 
			
		||||
    protected fillContextMenu(refresh: boolean = false): void {
 | 
			
		||||
        // All data obtained, now fill the context menu.
 | 
			
		||||
        CoreCourseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component);
 | 
			
		||||
    async getPackageStatus(refresh = false): Promise<void> {
 | 
			
		||||
        if (!this.module) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const moduleInfo =
 | 
			
		||||
                await CoreCourseHelper.getModulePrefetchInfo(this.module, this.courseId, refresh, this.component);
 | 
			
		||||
 | 
			
		||||
        this.downloadTimeReadable = CoreTextUtils.ucFirst(moduleInfo.downloadTimeReadable);
 | 
			
		||||
        this.prefetchStatus = moduleInfo.status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -253,48 +251,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
        return this.prefetchStatus != CoreConstants.NOT_DOWNLOADABLE && this.prefetchStatus != CoreConstants.NOT_DOWNLOADED;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Expand the description.
 | 
			
		||||
     *
 | 
			
		||||
     * @deprecated Use openModuleSummary instead.
 | 
			
		||||
     */
 | 
			
		||||
    expandDescription(): void {
 | 
			
		||||
        this.openModuleSummary();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Go to blog posts.
 | 
			
		||||
     */
 | 
			
		||||
    async gotoBlog(): Promise<void> {
 | 
			
		||||
        const params: Params = { cmId: this.module.id };
 | 
			
		||||
 | 
			
		||||
        await CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prefetch the module.
 | 
			
		||||
     *
 | 
			
		||||
     * @param done Function to call when done.
 | 
			
		||||
     */
 | 
			
		||||
    prefetch(done?: () => void): void {
 | 
			
		||||
        CoreCourseHelper.contextMenuPrefetch(this, this.module, this.courseId, done);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Confirm and remove downloaded files.
 | 
			
		||||
     *
 | 
			
		||||
     * @param done Function to call when done.
 | 
			
		||||
     */
 | 
			
		||||
    removeFiles(done?: () => void): void {
 | 
			
		||||
        if (this.prefetchStatus == CoreConstants.DOWNLOADING) {
 | 
			
		||||
            CoreDomUtils.showAlertTranslated(undefined, 'core.course.cannotdeletewhiledownloading');
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CoreCourseHelper.confirmAndRemoveFiles(this.module, this.courseId, done);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get message about an error occurred while downloading files.
 | 
			
		||||
     *
 | 
			
		||||
@ -459,7 +415,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                moduleId: this.module.id,
 | 
			
		||||
                module: this.module,
 | 
			
		||||
                description: this.description,
 | 
			
		||||
                description: !this.displayDescription ? this.description : '',
 | 
			
		||||
                component: this.component,
 | 
			
		||||
                courseId: this.courseId,
 | 
			
		||||
                hasOffline: this.hasOffline,
 | 
			
		||||
@ -493,10 +449,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.isDestroyed = true;
 | 
			
		||||
        this.contextMenuStatusObserver?.off();
 | 
			
		||||
        this.contextFileStatusObserver?.off();
 | 
			
		||||
        this.statusObserver?.off();
 | 
			
		||||
        this.completionObserver?.off();
 | 
			
		||||
        this.packageStatusObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -493,6 +493,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     * @param courseId Course ID the module belongs to.
 | 
			
		||||
     * @param done Function to call when done. It will close the context menu.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @deprecated since 4.0
 | 
			
		||||
     */
 | 
			
		||||
    async confirmAndRemoveFiles(module: CoreCourseModuleData, courseId: number, done?: () => void): Promise<void> {
 | 
			
		||||
        let modal: CoreIonLoadingElement | undefined;
 | 
			
		||||
@ -580,6 +581,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     * @param courseId Course ID the module belongs to.
 | 
			
		||||
     * @param done Function to call when done. It will close the context menu.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     * @deprecated since 4.0
 | 
			
		||||
     */
 | 
			
		||||
    async contextMenuPrefetch(
 | 
			
		||||
        instance: ComponentWithContextMenu,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user