forked from CIT/Vmeda.Online
		
	
						commit
						9af7648275
					
				@ -2125,6 +2125,7 @@
 | 
			
		||||
  "core.resources": "moodle",
 | 
			
		||||
  "core.restore": "moodle",
 | 
			
		||||
  "core.restricted": "moodle",
 | 
			
		||||
  "core.resume": "local_moodlemobileapp",
 | 
			
		||||
  "core.retry": "local_moodlemobileapp",
 | 
			
		||||
  "core.save": "moodle",
 | 
			
		||||
  "core.savechanges": "assign",
 | 
			
		||||
@ -2251,7 +2252,7 @@
 | 
			
		||||
  "core.sorry": "local_moodlemobileapp",
 | 
			
		||||
  "core.sort": "moodle",
 | 
			
		||||
  "core.sortby": "moodle",
 | 
			
		||||
  "core.start": "grouptool",
 | 
			
		||||
  "core.start": "local_moodlemobileapp",
 | 
			
		||||
  "core.storingfiles": "local_moodlemobileapp",
 | 
			
		||||
  "core.strftimedate": "langconfig",
 | 
			
		||||
  "core.strftimedatefullshort": "langconfig",
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { AddonCalendar, AddonCalendarEventType, AddonCalendarProvider } from '../calendar';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Database variables for AddonDatabase service.
 | 
			
		||||
 * Database variables for AddonCalendarProvider service.
 | 
			
		||||
 */
 | 
			
		||||
export const EVENTS_TABLE = 'addon_calendar_events_3';
 | 
			
		||||
export const REMINDERS_TABLE = 'addon_calendar_reminders';
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,10 @@ export class AddonModAssignPushClickHandlerService implements CorePushNotificati
 | 
			
		||||
        const moduleId = Number(contextUrlParams.id);
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.ignoreErrors(AddonModAssign.invalidateContent(moduleId, courseId, notification.site));
 | 
			
		||||
        await CoreCourseHelper.navigateToModule(moduleId, notification.site, courseId);
 | 
			
		||||
        await CoreCourseHelper.navigateToModule(moduleId, {
 | 
			
		||||
            courseId,
 | 
			
		||||
            siteId: notification.site,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,10 @@ const routes: Routes = [
 | 
			
		||||
        path: ':courseId/:cmId',
 | 
			
		||||
        component: AddonModBookIndexPage,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: ':courseId/:cmId/contents',
 | 
			
		||||
        loadChildren: () => import('./pages/contents/contents.module').then(m => m.AddonModBookContentsPageModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,8 @@ import { AddonModBookListLinkHandler } from './services/handlers/list-link';
 | 
			
		||||
import { AddonModBookPrefetchHandler } from './services/handlers/prefetch';
 | 
			
		||||
import { AddonModBookTagAreaHandler } from './services/handlers/tag-area';
 | 
			
		||||
import { AddonModBookProvider } from './services/book';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { BOOK_SITE_SCHEMA } from './services/database/book';
 | 
			
		||||
 | 
			
		||||
export const ADDON_MOD_BOOK_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonModBookProvider,
 | 
			
		||||
@ -44,6 +46,11 @@ const routes: Routes = [
 | 
			
		||||
        AddonModBookComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: CORE_SITE_SCHEMAS,
 | 
			
		||||
            useValue: [BOOK_SITE_SCHEMA],
 | 
			
		||||
            multi: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            provide: APP_INITIALIZER,
 | 
			
		||||
            multi: true,
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,6 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
			
		||||
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
 | 
			
		||||
 | 
			
		||||
import { AddonModBookIndexComponent } from './index/index';
 | 
			
		||||
import { AddonModBookTocComponent } from './toc/toc';
 | 
			
		||||
@ -29,7 +28,6 @@ import { AddonModBookTocComponent } from './toc/toc';
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
        CoreTagComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModBookIndexComponent,
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,5 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <ion-button (click)="showToc()" [attr.aria-label]="'addon.mod_book.toc' | translate" aria-haspopup="true" *ngIf="loaded">
 | 
			
		||||
        <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>
 | 
			
		||||
@ -28,31 +25,31 @@
 | 
			
		||||
        [courseId]="courseId">
 | 
			
		||||
    </core-course-module-info>
 | 
			
		||||
 | 
			
		||||
    <ion-card class="core-warning-card" *ngIf="warning">
 | 
			
		||||
    <ion-list>
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
 | 
			
		||||
            <ion-label><span [innerHTML]="warning"></span></ion-label>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <h2>{{ 'addon.mod_book.toc' | translate }}</h2>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <div class="safe-area-padding-horizontal">
 | 
			
		||||
        <core-navigation-bar *ngIf="displayNavBar" [items]="navigationItems" [showTitles]="displayTitlesInNavBar"
 | 
			
		||||
            previousTranslate="addon.mod_book.navprevtitle" nextTranslate="addon.mod_book.navnexttitle" (action)="changeChapter($event.id)">
 | 
			
		||||
        </core-navigation-bar>
 | 
			
		||||
        <ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" [class.item-dimmed]="chapter.hidden" button detail="true"
 | 
			
		||||
            (click)="openBook(chapter.id)">
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <p [class.ion-padding-start]="addPadding && chapter.level == 1 ? true : null">
 | 
			
		||||
                    <span *ngIf="showNumbers" class="addon-mod-book-number">{{chapter.indexNumber}} </span>
 | 
			
		||||
                    <span *ngIf="showBullets" class="addon-mod-book-bullet">• </span>
 | 
			
		||||
                    <core-format-text [text]="chapter.title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
			
		||||
                    </core-format-text>
 | 
			
		||||
                </p>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
 | 
			
		||||
        <core-swipe-slides [manager]="manager" [options]="slidesOpts">
 | 
			
		||||
            <ng-template let-chapter="item">
 | 
			
		||||
                <div class="ion-padding">
 | 
			
		||||
                    <core-format-text [component]="component" [componentId]="componentId" [text]="chapter.content" contextLevel="module"
 | 
			
		||||
                        [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text>
 | 
			
		||||
                    <div class="ion-margin-top" *ngIf="chapter.tags?.length > 0">
 | 
			
		||||
                        <strong>{{ 'core.tag.tags' | translate }}: </strong>
 | 
			
		||||
                        <core-tag-list [tags]="chapter.tags"></core-tag-list>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
        </core-swipe-slides>
 | 
			
		||||
    </div>
 | 
			
		||||
        <ion-button class="ion-margin" expand="block" (click)="openBook()">
 | 
			
		||||
            <span *ngIf="!hasStartedBook">{{ 'core.start' | translate }}</span>
 | 
			
		||||
            <span *ngIf="hasStartedBook">{{ 'core.resume' | translate }}</span>
 | 
			
		||||
        </ion-button>
 | 
			
		||||
 | 
			
		||||
    </ion-list>
 | 
			
		||||
 | 
			
		||||
</core-loading>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,33 +12,15 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, Optional, Input, OnInit, ViewChild, ElementRef, OnDestroy } from '@angular/core';
 | 
			
		||||
import { Component, Optional, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModBookProvider,
 | 
			
		||||
    AddonModBookContentsMap,
 | 
			
		||||
    AddonModBookTocChapter,
 | 
			
		||||
    AddonModBookNavStyle,
 | 
			
		||||
    AddonModBook,
 | 
			
		||||
    AddonModBookBookWSData,
 | 
			
		||||
} from '../../services/book';
 | 
			
		||||
import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { AddonModBook, AddonModBookBookWSData, AddonModBookNumbering, AddonModBookTocChapter } from '../../services/book';
 | 
			
		||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { AddonModBookTocComponent } from '../toc/toc';
 | 
			
		||||
import { CoreNavigationBarItem } from '@components/navigation-bar/navigation-bar';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreSwipeSlidesComponent, CoreSwipeSlidesOptions } from '@components/swipe-slides/swipe-slides';
 | 
			
		||||
import { CoreSwipeSlidesItemsManagerSource } from '@classes/items-management/swipe-slides-items-manager-source';
 | 
			
		||||
import { CoreCourseModule } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreSwipeSlidesItemsManager } from '@classes/items-management/swipe-slides-items-manager';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays a book.
 | 
			
		||||
 * Component that displays a book entry page.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-book-index',
 | 
			
		||||
@ -46,191 +28,86 @@ import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
})
 | 
			
		||||
export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(CoreSwipeSlidesComponent) slides?: CoreSwipeSlidesComponent;
 | 
			
		||||
    showNumbers = true;
 | 
			
		||||
    addPadding = true;
 | 
			
		||||
    showBullets = false;
 | 
			
		||||
    chapters: AddonModBookTocChapter[] = [];
 | 
			
		||||
    hasStartedBook = false;
 | 
			
		||||
 | 
			
		||||
    @Input() initialChapterId?: number; // The initial chapter ID to load.
 | 
			
		||||
    protected book?: AddonModBookBookWSData;
 | 
			
		||||
 | 
			
		||||
    component = AddonModBookProvider.COMPONENT;
 | 
			
		||||
    manager?: CoreSwipeSlidesItemsManager<LoadedChapter, AddonModBookSlidesItemsManagerSource>;
 | 
			
		||||
    warning = '';
 | 
			
		||||
    displayNavBar = true;
 | 
			
		||||
    navigationItems: CoreNavigationBarItem<AddonModBookTocChapter>[] = [];
 | 
			
		||||
    displayTitlesInNavBar = false;
 | 
			
		||||
    slidesOpts: CoreSwipeSlidesOptions = {
 | 
			
		||||
        autoHeight: true,
 | 
			
		||||
        scrollOnChange: 'top',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    protected firstLoad = true;
 | 
			
		||||
    protected element: HTMLElement;
 | 
			
		||||
    protected managerUnsubscribe?: () => void;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        elementRef: ElementRef,
 | 
			
		||||
        @Optional() courseContentsPage?: CoreCourseContentsPage,
 | 
			
		||||
    ) {
 | 
			
		||||
    constructor( @Optional() courseContentsPage?: CoreCourseContentsPage) {
 | 
			
		||||
        super('AddonModBookIndexComponent', courseContentsPage);
 | 
			
		||||
 | 
			
		||||
        this.element = elementRef.nativeElement;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
 | 
			
		||||
        const source = new AddonModBookSlidesItemsManagerSource(
 | 
			
		||||
            this.courseId,
 | 
			
		||||
            this.module,
 | 
			
		||||
            CoreTag.areTagsAvailableInSite(),
 | 
			
		||||
            this.initialChapterId,
 | 
			
		||||
        );
 | 
			
		||||
        this.manager = new CoreSwipeSlidesItemsManager(source);
 | 
			
		||||
        this.managerUnsubscribe = this.manager.addListener({
 | 
			
		||||
            onSelectedItemUpdated: (item) => {
 | 
			
		||||
                this.onChapterViewed(item.id);
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.loadContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get book(): AddonModBookBookWSData | undefined {
 | 
			
		||||
        return this.manager?.getSource().book;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get chapters(): AddonModBookTocChapter[] {
 | 
			
		||||
        return this.manager?.getSource().chapters || [];
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh?: boolean): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await Promise.all([
 | 
			
		||||
                this.loadBook(),
 | 
			
		||||
                this.loadTOC(),
 | 
			
		||||
            ]);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the TOC.
 | 
			
		||||
     * Load book data.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async showToc(): Promise<void> {
 | 
			
		||||
        // Create the toc modal.
 | 
			
		||||
        const visibleChapter = this.manager?.getSelectedItem();
 | 
			
		||||
    protected async loadBook(): Promise<void> {
 | 
			
		||||
        this.book = await AddonModBook.getBook(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
        const modalData = await CoreDomUtils.openSideModal<number>({
 | 
			
		||||
            component: AddonModBookTocComponent,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                moduleId: this.module.id,
 | 
			
		||||
                chapters: this.chapters,
 | 
			
		||||
                selected: visibleChapter,
 | 
			
		||||
        this.dataRetrieved.emit(this.book);
 | 
			
		||||
 | 
			
		||||
        this.description = this.book.intro;
 | 
			
		||||
        this.showNumbers = this.book.numbering == AddonModBookNumbering.NUMBERS;
 | 
			
		||||
        this.showBullets = this.book.numbering == AddonModBookNumbering.BULLETS;
 | 
			
		||||
        this.addPadding = this.book.numbering != AddonModBookNumbering.NONE;
 | 
			
		||||
 | 
			
		||||
        const lastChapterViewed = await AddonModBook.getLastChapterViewed(this.book.id);
 | 
			
		||||
        this.hasStartedBook = lastChapterViewed !== undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load book TOC.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadTOC(): Promise<void> {
 | 
			
		||||
        const contents = await CoreCourse.getModuleContents(this.module, this.courseId);
 | 
			
		||||
 | 
			
		||||
        this.chapters = AddonModBook.getTocList(contents);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open the book in a certain chapter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param chapterId Chapter to open, undefined for first chapter.
 | 
			
		||||
     */
 | 
			
		||||
    openBook(chapterId?: number): void {
 | 
			
		||||
        CoreNavigator.navigate('contents', {
 | 
			
		||||
            params: {
 | 
			
		||||
                cmId: this.module.id,
 | 
			
		||||
                courseId: this.courseId,
 | 
			
		||||
                book: this.book,
 | 
			
		||||
                chapterId,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (modalData) {
 | 
			
		||||
            this.changeChapter(modalData);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Change the current chapter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param chapterId Chapter to load.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    changeChapter(chapterId: number): void {
 | 
			
		||||
        if (!chapterId) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.slides?.slideToItem({ id: chapterId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the invalidate content function.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async invalidateContent(): Promise<void> {
 | 
			
		||||
        await this.manager?.getSource().invalidateContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download book contents and load the current chapter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            const source = this.manager?.getSource();
 | 
			
		||||
            if (!source) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const downloadResult = await this.downloadResourceIfNeeded(refresh);
 | 
			
		||||
 | 
			
		||||
            const book = await source.loadBookData();
 | 
			
		||||
 | 
			
		||||
            if (book) {
 | 
			
		||||
                this.dataRetrieved.emit(book);
 | 
			
		||||
 | 
			
		||||
                this.description = book.intro;
 | 
			
		||||
                this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY;
 | 
			
		||||
                this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | 
			
		||||
            await source.loadContents();
 | 
			
		||||
 | 
			
		||||
            await source.load();
 | 
			
		||||
 | 
			
		||||
            this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error || '') : '';
 | 
			
		||||
        } finally {
 | 
			
		||||
            // Pass false because downloadResourceIfNeeded already invalidates and refresh data if refresh=true.
 | 
			
		||||
            this.fillContextMenu(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update data related to chapter being viewed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param chapterId Chapter viewed.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async onChapterViewed(chapterId: number): Promise<void> {
 | 
			
		||||
        // Don't log the chapter ID when the user has just opened the book.
 | 
			
		||||
        const logChapterId = this.firstLoad;
 | 
			
		||||
        this.firstLoad = false;
 | 
			
		||||
 | 
			
		||||
        if (this.displayNavBar) {
 | 
			
		||||
            this.navigationItems = this.getNavigationItems(chapterId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Chapter loaded, log view.
 | 
			
		||||
        await CoreUtils.ignoreErrors(AddonModBook.logView(
 | 
			
		||||
            this.module.instance,
 | 
			
		||||
            logChapterId ? chapterId : undefined,
 | 
			
		||||
            this.module.name,
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        const currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId);
 | 
			
		||||
        const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined;
 | 
			
		||||
 | 
			
		||||
        // Module is completed when last chapter is viewed, so we only check completion if the last is reached.
 | 
			
		||||
        if (isLastChapter) {
 | 
			
		||||
            CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts chapters to navigation items.
 | 
			
		||||
     *
 | 
			
		||||
     * @param chapterId Current chapter Id.
 | 
			
		||||
     * @return Navigation items.
 | 
			
		||||
     */
 | 
			
		||||
    protected getNavigationItems(chapterId: number): CoreNavigationBarItem<AddonModBookTocChapter>[] {
 | 
			
		||||
        return this.chapters.map((chapter) => ({
 | 
			
		||||
            item: chapter,
 | 
			
		||||
            title: chapter.title,
 | 
			
		||||
            current: chapter.id == chapterId,
 | 
			
		||||
            enabled: true,
 | 
			
		||||
        }));
 | 
			
		||||
        this.hasStartedBook = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -238,99 +115,6 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        super.ngOnDestroy();
 | 
			
		||||
 | 
			
		||||
        this.managerUnsubscribe && this.managerUnsubscribe();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoadedChapter = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    content?: string;
 | 
			
		||||
    tags?: CoreTagItem[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper to manage swiping within a collection of chapters.
 | 
			
		||||
 */
 | 
			
		||||
class AddonModBookSlidesItemsManagerSource extends CoreSwipeSlidesItemsManagerSource<LoadedChapter> {
 | 
			
		||||
 | 
			
		||||
    readonly COURSE_ID: number;
 | 
			
		||||
    readonly MODULE: CoreCourseModule;
 | 
			
		||||
    readonly TAGS_ENABLED: boolean;
 | 
			
		||||
 | 
			
		||||
    book?: AddonModBookBookWSData;
 | 
			
		||||
    chapters: AddonModBookTocChapter[] = [];
 | 
			
		||||
    contentsMap: AddonModBookContentsMap = {};
 | 
			
		||||
 | 
			
		||||
    constructor(courseId: number, module: CoreCourseModule, tagsEnabled: boolean, initialChapterId?: number) {
 | 
			
		||||
        super(initialChapterId ? { id: initialChapterId } : undefined);
 | 
			
		||||
 | 
			
		||||
        this.COURSE_ID = courseId;
 | 
			
		||||
        this.MODULE = module;
 | 
			
		||||
        this.TAGS_ENABLED = tagsEnabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getItemId(item: LoadedChapter): string | number {
 | 
			
		||||
        return item.id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load book data from WS.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async loadBookData(): Promise<AddonModBookBookWSData> {
 | 
			
		||||
        this.book = await AddonModBook.getBook(this.COURSE_ID, this.MODULE.id);
 | 
			
		||||
 | 
			
		||||
        return this.book;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load module contents.
 | 
			
		||||
     */
 | 
			
		||||
    async loadContents(): Promise<void> {
 | 
			
		||||
        const contents = await CoreCourse.getModuleContents(this.MODULE, this.COURSE_ID);
 | 
			
		||||
 | 
			
		||||
        this.contentsMap = AddonModBook.getContentsMap(contents);
 | 
			
		||||
        this.chapters = AddonModBook.getTocList(contents);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadItems(): Promise<LoadedChapter[]> {
 | 
			
		||||
        try {
 | 
			
		||||
            const newChapters = await Promise.all(this.chapters.map(async (chapter) => {
 | 
			
		||||
                const content = await AddonModBook.getChapterContent(this.contentsMap, chapter.id, this.MODULE.id);
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                    id: chapter.id,
 | 
			
		||||
                    content,
 | 
			
		||||
                    tags: this.TAGS_ENABLED ? this.contentsMap[chapter.id].tags : [],
 | 
			
		||||
                };
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
            return newChapters;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!CoreTextUtils.getErrorMessageFromError(error)) {
 | 
			
		||||
                throw new CoreError(Translate.instant('addon.mod_book.errorchapter'));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw error;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the invalidate content function.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    invalidateContent(): Promise<void> {
 | 
			
		||||
        return AddonModBook.invalidateContent(this.MODULE.id, this.COURSE_ID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,4 +5,4 @@
 | 
			
		||||
    "navprevtitle": "Previous: {{$a}}",
 | 
			
		||||
    "tagarea_book_chapters": "Book chapters",
 | 
			
		||||
    "toc": "Table of contents"
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										53
									
								
								src/addons/mod/book/pages/contents/contents.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/addons/mod/book/pages/contents/contents.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [text]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title>
 | 
			
		||||
            <h1>
 | 
			
		||||
                <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
 | 
			
		||||
                </core-format-text>
 | 
			
		||||
            </h1>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <ion-button (click)="showToc()" [attr.aria-label]="'addon.mod_book.toc' | translate" aria-haspopup="true" *ngIf="loaded">
 | 
			
		||||
                <ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <core-loading [hideUntil]="loaded">
 | 
			
		||||
 | 
			
		||||
        <ion-card class="core-warning-card" *ngIf="warning">
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
 | 
			
		||||
                <ion-label><span [innerHTML]="warning"></span></ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <div class="safe-area-padding-horizontal">
 | 
			
		||||
            <core-navigation-bar *ngIf="displayNavBar" [items]="navigationItems" [showTitles]="displayTitlesInNavBar"
 | 
			
		||||
                previousTranslate="addon.mod_book.navprevtitle" nextTranslate="addon.mod_book.navnexttitle"
 | 
			
		||||
                (action)="changeChapter($event.id)">
 | 
			
		||||
            </core-navigation-bar>
 | 
			
		||||
 | 
			
		||||
            <core-swipe-slides [manager]="manager" [options]="slidesOpts">
 | 
			
		||||
                <ng-template let-chapter="item">
 | 
			
		||||
                    <div class="ion-padding">
 | 
			
		||||
                        <core-format-text [component]="component" [componentId]="cmId" [text]="chapter.content" contextLevel="module"
 | 
			
		||||
                            [contextInstanceId]="cmId" [courseId]="courseId"></core-format-text>
 | 
			
		||||
                        <div class="ion-margin-top" *ngIf="chapter.tags?.length > 0">
 | 
			
		||||
                            <strong>{{ 'core.tag.tags' | translate }}: </strong>
 | 
			
		||||
                            <core-tag-list [tags]="chapter.tags"></core-tag-list>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
            </core-swipe-slides>
 | 
			
		||||
        </div>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										40
									
								
								src/addons/mod/book/pages/contents/contents.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/addons/mod/book/pages/contents/contents.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { AddonModBookContentsPage } from './contents';
 | 
			
		||||
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        component: AddonModBookContentsPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreTagComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModBookContentsPage,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [RouterModule],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModBookContentsPageModule {}
 | 
			
		||||
							
								
								
									
										437
									
								
								src/addons/mod/book/pages/contents/contents.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										437
									
								
								src/addons/mod/book/pages/contents/contents.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,437 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreSwipeSlidesItemsManager } from '@classes/items-management/swipe-slides-items-manager';
 | 
			
		||||
import { CoreSwipeSlidesItemsManagerSource } from '@classes/items-management/swipe-slides-items-manager-source';
 | 
			
		||||
import { CoreNavigationBarItem } from '@components/navigation-bar/navigation-bar';
 | 
			
		||||
import { CoreSwipeSlidesComponent, CoreSwipeSlidesOptions } from '@components/swipe-slides/swipe-slides';
 | 
			
		||||
import { CoreCourseResourceDownloadResult } from '@features/course/classes/main-resource-component';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseModuleData } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
 | 
			
		||||
import { IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { AddonModBookTocComponent } from '../../components/toc/toc';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModBook,
 | 
			
		||||
    AddonModBookBookWSData,
 | 
			
		||||
    AddonModBookContentsMap,
 | 
			
		||||
    AddonModBookNavStyle,
 | 
			
		||||
    AddonModBookProvider,
 | 
			
		||||
    AddonModBookTocChapter,
 | 
			
		||||
} from '../../services/book';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a book contents.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-book-contents',
 | 
			
		||||
    templateUrl: 'contents.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModBookContentsPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(CoreSwipeSlidesComponent) slides?: CoreSwipeSlidesComponent;
 | 
			
		||||
 | 
			
		||||
    title!: string;
 | 
			
		||||
    cmId!: number;
 | 
			
		||||
    courseId!: number;
 | 
			
		||||
    initialChapterId?: number;
 | 
			
		||||
    component = AddonModBookProvider.COMPONENT;
 | 
			
		||||
    manager?: CoreSwipeSlidesItemsManager<LoadedChapter, AddonModBookSlidesItemsManagerSource>;
 | 
			
		||||
    warning = '';
 | 
			
		||||
    displayNavBar = true;
 | 
			
		||||
    navigationItems: CoreNavigationBarItem<AddonModBookTocChapter>[] = [];
 | 
			
		||||
    displayTitlesInNavBar = false;
 | 
			
		||||
    slidesOpts: CoreSwipeSlidesOptions = {
 | 
			
		||||
        autoHeight: true,
 | 
			
		||||
        scrollOnChange: 'top',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    loaded = false;
 | 
			
		||||
 | 
			
		||||
    protected firstLoad = true;
 | 
			
		||||
    protected managerUnsubscribe?: () => void;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        try {
 | 
			
		||||
            this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
 | 
			
		||||
            this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
 | 
			
		||||
            this.initialChapterId = CoreNavigator.getRouteNumberParam('chapterId');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModal(error);
 | 
			
		||||
 | 
			
		||||
            CoreNavigator.back();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const source = new AddonModBookSlidesItemsManagerSource(
 | 
			
		||||
            this.courseId,
 | 
			
		||||
            this.cmId,
 | 
			
		||||
            CoreTag.areTagsAvailableInSite(),
 | 
			
		||||
            this.initialChapterId,
 | 
			
		||||
        );
 | 
			
		||||
        this.manager = new CoreSwipeSlidesItemsManager(source);
 | 
			
		||||
        this.managerUnsubscribe = this.manager.addListener({
 | 
			
		||||
            onSelectedItemUpdated: (item) => {
 | 
			
		||||
                this.onChapterViewed(item.id);
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.fetchContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get module(): CoreCourseModuleData | undefined {
 | 
			
		||||
        return this.manager?.getSource().module;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get book(): AddonModBookBookWSData | undefined {
 | 
			
		||||
        return this.manager?.getSource().book;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get chapters(): AddonModBookTocChapter[] {
 | 
			
		||||
        return this.manager?.getSource().chapters || [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download book contents and load the current chapter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            const source = this.manager?.getSource();
 | 
			
		||||
            if (!source) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const { module, book } = await source.loadBookData();
 | 
			
		||||
 | 
			
		||||
            const downloadResult = await this.downloadResourceIfNeeded(module, refresh);
 | 
			
		||||
 | 
			
		||||
            this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY;
 | 
			
		||||
            this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT;
 | 
			
		||||
            this.title = book.name;
 | 
			
		||||
 | 
			
		||||
            // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | 
			
		||||
            await source.loadContents();
 | 
			
		||||
 | 
			
		||||
            await source.load();
 | 
			
		||||
 | 
			
		||||
            if (downloadResult?.failed) {
 | 
			
		||||
                const error = CoreTextUtils.getErrorMessageFromError(downloadResult.error) || downloadResult.error;
 | 
			
		||||
                this.warning = Translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
 | 
			
		||||
            } else {
 | 
			
		||||
                this.warning = '';
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download a resource if needed.
 | 
			
		||||
     * If the download call fails the promise won't be rejected, but the error will be included in the returned object.
 | 
			
		||||
     * If module.contents cannot be loaded then the Promise will be rejected.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async downloadResourceIfNeeded(
 | 
			
		||||
        module: CoreCourseModuleData,
 | 
			
		||||
        refresh = false,
 | 
			
		||||
    ): Promise<CoreCourseResourceDownloadResult> {
 | 
			
		||||
 | 
			
		||||
        const result: CoreCourseResourceDownloadResult = {
 | 
			
		||||
            failed: false,
 | 
			
		||||
        };
 | 
			
		||||
        let contentsAlreadyLoaded = false;
 | 
			
		||||
 | 
			
		||||
        // Get module status to determine if it needs to be downloaded.
 | 
			
		||||
        const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId, undefined, refresh);
 | 
			
		||||
 | 
			
		||||
        if (status !== CoreConstants.DOWNLOADED) {
 | 
			
		||||
            // Download content. This function also loads module contents if needed.
 | 
			
		||||
            try {
 | 
			
		||||
                await CoreCourseModulePrefetchDelegate.downloadModule(module, this.courseId);
 | 
			
		||||
 | 
			
		||||
                // If we reach here it means the download process already loaded the contents, no need to do it again.
 | 
			
		||||
                contentsAlreadyLoaded = true;
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                // Mark download as failed but go on since the main files could have been downloaded.
 | 
			
		||||
                result.failed = true;
 | 
			
		||||
                result.error = error;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!module.contents?.length || (refresh && !contentsAlreadyLoaded)) {
 | 
			
		||||
            // Try to load the contents.
 | 
			
		||||
            const ignoreCache = refresh && CoreApp.isOnline();
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                await CoreCourse.loadModuleContents(module, undefined, undefined, false, ignoreCache);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                // Error loading contents. If we ignored cache, try to get the cached value.
 | 
			
		||||
                if (ignoreCache && !module.contents) {
 | 
			
		||||
                    await CoreCourse.loadModuleContents(module);
 | 
			
		||||
                } else if (!module.contents) {
 | 
			
		||||
                    // Not able to load contents, throw the error.
 | 
			
		||||
                    throw error;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Change the current chapter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param chapterId Chapter to load.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    changeChapter(chapterId: number): void {
 | 
			
		||||
        if (!chapterId) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.slides?.slideToItem({ id: chapterId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh the data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresher Refresher.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async doRefresh(refresher?: IonRefresher): Promise<void> {
 | 
			
		||||
        if (this.manager) {
 | 
			
		||||
            await CoreUtils.ignoreErrors(Promise.all([
 | 
			
		||||
                this.manager.getSource().invalidateContent(),
 | 
			
		||||
                CoreCourseModulePrefetchDelegate.invalidateCourseUpdates(this.courseId), // To detect if book was updated.
 | 
			
		||||
            ]));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.ignoreErrors(this.fetchContent(true));
 | 
			
		||||
 | 
			
		||||
        refresher?.complete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the TOC.
 | 
			
		||||
     */
 | 
			
		||||
    async showToc(): Promise<void> {
 | 
			
		||||
        // Create the toc modal.
 | 
			
		||||
        const visibleChapter = this.manager?.getSelectedItem();
 | 
			
		||||
 | 
			
		||||
        const modalData = await CoreDomUtils.openSideModal<number>({
 | 
			
		||||
            component: AddonModBookTocComponent,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                moduleId: this.cmId,
 | 
			
		||||
                chapters: this.chapters,
 | 
			
		||||
                selected: visibleChapter,
 | 
			
		||||
                courseId: this.courseId,
 | 
			
		||||
                book: this.book,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (modalData) {
 | 
			
		||||
            this.changeChapter(modalData);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update data related to chapter being viewed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param chapterId Chapter viewed.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async onChapterViewed(chapterId: number): Promise<void> {
 | 
			
		||||
        // Don't log the chapter ID when the user has just opened the book.
 | 
			
		||||
        const logChapterId = this.firstLoad;
 | 
			
		||||
        this.firstLoad = false;
 | 
			
		||||
 | 
			
		||||
        if (this.displayNavBar) {
 | 
			
		||||
            this.navigationItems = this.getNavigationItems(chapterId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.book) {
 | 
			
		||||
            AddonModBook.storeLastChapterViewed(this.book.id, chapterId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.module) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Chapter loaded, log view.
 | 
			
		||||
        await CoreUtils.ignoreErrors(AddonModBook.logView(
 | 
			
		||||
            this.module.instance,
 | 
			
		||||
            logChapterId ? chapterId : undefined,
 | 
			
		||||
            this.module.name,
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        const currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId);
 | 
			
		||||
        const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined;
 | 
			
		||||
 | 
			
		||||
        // Module is completed when last chapter is viewed, so we only check completion if the last is reached.
 | 
			
		||||
        if (isLastChapter) {
 | 
			
		||||
            CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts chapters to navigation items.
 | 
			
		||||
     *
 | 
			
		||||
     * @param chapterId Current chapter Id.
 | 
			
		||||
     * @return Navigation items.
 | 
			
		||||
     */
 | 
			
		||||
    protected getNavigationItems(chapterId: number): CoreNavigationBarItem<AddonModBookTocChapter>[] {
 | 
			
		||||
        return this.chapters.map((chapter) => ({
 | 
			
		||||
            item: chapter,
 | 
			
		||||
            title: chapter.title,
 | 
			
		||||
            current: chapter.id == chapterId,
 | 
			
		||||
            enabled: true,
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.managerUnsubscribe && this.managerUnsubscribe();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoadedChapter = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    content?: string;
 | 
			
		||||
    tags?: CoreTagItem[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper to manage swiping within a collection of chapters.
 | 
			
		||||
 */
 | 
			
		||||
class AddonModBookSlidesItemsManagerSource extends CoreSwipeSlidesItemsManagerSource<LoadedChapter> {
 | 
			
		||||
 | 
			
		||||
    readonly COURSE_ID: number;
 | 
			
		||||
    readonly CM_ID: number;
 | 
			
		||||
    readonly TAGS_ENABLED: boolean;
 | 
			
		||||
 | 
			
		||||
    module?: CoreCourseModuleData;
 | 
			
		||||
    book?: AddonModBookBookWSData;
 | 
			
		||||
    chapters: AddonModBookTocChapter[] = [];
 | 
			
		||||
    contentsMap: AddonModBookContentsMap = {};
 | 
			
		||||
 | 
			
		||||
    constructor(courseId: number, cmId: number, tagsEnabled: boolean, initialChapterId?: number) {
 | 
			
		||||
        super(initialChapterId ? { id: initialChapterId } : undefined);
 | 
			
		||||
 | 
			
		||||
        this.COURSE_ID = courseId;
 | 
			
		||||
        this.CM_ID = cmId;
 | 
			
		||||
        this.TAGS_ENABLED = tagsEnabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getItemId(item: LoadedChapter): string | number {
 | 
			
		||||
        return item.id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load book data from WS.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async loadBookData(): Promise<{ module: CoreCourseModuleData; book: AddonModBookBookWSData }> {
 | 
			
		||||
        this.module = await CoreCourse.getModule(this.CM_ID, this.COURSE_ID);
 | 
			
		||||
        this.book = await AddonModBook.getBook(this.COURSE_ID, this.CM_ID);
 | 
			
		||||
 | 
			
		||||
        if (!this.initialItem) {
 | 
			
		||||
            // No chapter ID specified. Calculate last viewed.
 | 
			
		||||
            const lastViewed = await AddonModBook.getLastChapterViewed(this.book.id);
 | 
			
		||||
 | 
			
		||||
            if (lastViewed) {
 | 
			
		||||
                this.initialItem = { id: lastViewed };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            module: this.module,
 | 
			
		||||
            book: this.book,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load module contents.
 | 
			
		||||
     */
 | 
			
		||||
    async loadContents(): Promise<void> {
 | 
			
		||||
        if (!this.module) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const contents = await CoreCourse.getModuleContents(this.module, this.COURSE_ID);
 | 
			
		||||
 | 
			
		||||
        this.contentsMap = AddonModBook.getContentsMap(contents);
 | 
			
		||||
        this.chapters = AddonModBook.getTocList(contents);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadItems(): Promise<LoadedChapter[]> {
 | 
			
		||||
        try {
 | 
			
		||||
            const newChapters = await Promise.all(this.chapters.map(async (chapter) => {
 | 
			
		||||
                const content = await AddonModBook.getChapterContent(this.contentsMap, chapter.id, this.CM_ID);
 | 
			
		||||
 | 
			
		||||
                return {
 | 
			
		||||
                    id: chapter.id,
 | 
			
		||||
                    content,
 | 
			
		||||
                    tags: this.TAGS_ENABLED ? this.contentsMap[chapter.id].tags : [],
 | 
			
		||||
                };
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
            return newChapters;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!CoreTextUtils.getErrorMessageFromError(error)) {
 | 
			
		||||
                throw new CoreError(Translate.instant('addon.mod_book.errorchapter'));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw error;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the invalidate content function.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    invalidateContent(): Promise<void> {
 | 
			
		||||
        return AddonModBook.invalidateContent(this.CM_ID, this.COURSE_ID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -19,6 +19,6 @@
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <addon-mod-book-index [module]="module" [courseId]="courseId" [initialChapterId]="chapterId" (dataRetrieved)="updateData($event)">
 | 
			
		||||
    <addon-mod-book-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)">
 | 
			
		||||
    </addon-mod-book-index>
 | 
			
		||||
</ion-content>
 | 
			
		||||
 | 
			
		||||
@ -12,30 +12,19 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { Component, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { AddonModBookIndexComponent } from '../../components/index/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a book.
 | 
			
		||||
 * Page that displays a book entry page.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-book-index',
 | 
			
		||||
    templateUrl: 'index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModBookIndexPage extends CoreCourseModuleMainActivityPage<AddonModBookIndexComponent> implements OnInit {
 | 
			
		||||
export class AddonModBookIndexPage extends CoreCourseModuleMainActivityPage<AddonModBookIndexComponent> {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(AddonModBookIndexComponent) activityComponent?: AddonModBookIndexComponent;
 | 
			
		||||
 | 
			
		||||
    chapterId?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
        this.chapterId = CoreNavigator.getRouteNumberParam('chapterId');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,11 @@ import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreFile } from '@services/file';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { lazyMap, LazyMap } from '@/core/utils/lazy-map';
 | 
			
		||||
import { asyncInstance, AsyncInstance } from '@/core/utils/async-instance';
 | 
			
		||||
import { CoreDatabaseTable } from '@classes/database/database-table';
 | 
			
		||||
import { AddonModBookLastChapterViewedDBRecord, LAST_CHAPTER_VIEWED_TABLE } from './database/book';
 | 
			
		||||
import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constants to define how the chapters and subchapters of a book should be displayed in that table of contents.
 | 
			
		||||
@ -56,6 +61,19 @@ export class AddonModBookProvider {
 | 
			
		||||
 | 
			
		||||
    static readonly COMPONENT = 'mmaModBook';
 | 
			
		||||
 | 
			
		||||
    protected lastChapterViewedTables: LazyMap<AsyncInstance<CoreDatabaseTable<AddonModBookLastChapterViewedDBRecord>>>;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.lastChapterViewedTables = lazyMap(
 | 
			
		||||
            siteId => asyncInstance(
 | 
			
		||||
                () => CoreSites.getSiteTable(LAST_CHAPTER_VIEWED_TABLE, {
 | 
			
		||||
                    siteId,
 | 
			
		||||
                    config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
 | 
			
		||||
                }),
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a book by course module ID.
 | 
			
		||||
     *
 | 
			
		||||
@ -216,6 +234,24 @@ export class AddonModBookProvider {
 | 
			
		||||
        return chapters[0].id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get last chapter viewed in the app for a book.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id Book instance ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with last chapter viewed, undefined if none.
 | 
			
		||||
     */
 | 
			
		||||
    async getLastChapterViewed(id: number, siteId?: string): Promise<number | undefined> {
 | 
			
		||||
        try {
 | 
			
		||||
            const site = await CoreSites.getSite(siteId);
 | 
			
		||||
            const entry = await this.lastChapterViewedTables[site.getId()].getOneByPrimaryKey({ id });
 | 
			
		||||
 | 
			
		||||
            return entry.chapterid;
 | 
			
		||||
        } catch {
 | 
			
		||||
            // No last chapter viewed.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the book toc as an array.
 | 
			
		||||
     *
 | 
			
		||||
@ -363,6 +399,20 @@ export class AddonModBookProvider {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Store last chapter viewed in the app for a book.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id Book instance ID.
 | 
			
		||||
     * @param chapterId Chapter ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with last chapter viewed, undefined if none.
 | 
			
		||||
     */
 | 
			
		||||
    async storeLastChapterViewed(id: number, chapterId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await this.lastChapterViewedTables[site.getId()].insert({ id, chapterid: chapterId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModBook = makeSingleton(AddonModBookProvider);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										46
									
								
								src/addons/mod/book/services/database/book.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/addons/mod/book/services/database/book.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreSiteSchema } from '@services/sites';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Database variables for AddonModBookProvider service.
 | 
			
		||||
 */
 | 
			
		||||
export const LAST_CHAPTER_VIEWED_TABLE = 'addon_mod_book_last_chapter_viewed';
 | 
			
		||||
export const BOOK_SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    name: 'AddonModBookProvider',
 | 
			
		||||
    version: 1,
 | 
			
		||||
    tables: [
 | 
			
		||||
        {
 | 
			
		||||
            name: LAST_CHAPTER_VIEWED_TABLE,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'id',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                    primaryKey: true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'chapterid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                    notNull: true,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AddonModBookLastChapterViewedDBRecord = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    chapterid: number;
 | 
			
		||||
};
 | 
			
		||||
@ -15,6 +15,7 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
 | 
			
		||||
import { CoreNavigationOptions } from '@services/navigator';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModBook } from '../book';
 | 
			
		||||
 | 
			
		||||
@ -31,20 +32,32 @@ export class AddonModBookIndexLinkHandlerService extends CoreContentLinksModuleI
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the mod params necessary to open an activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @param url      The URL to treat.
 | 
			
		||||
     * @param params   The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
 | 
			
		||||
     * @return List of params to pass to navigateToModule / navigateToModuleByInstance.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getModNavOptions(url: string, params: Record<string, string>): CoreNavigationOptions {
 | 
			
		||||
        const chapterId = params.chapterid ? parseInt(params.chapterid, 10) : undefined;
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            nextNavigation: {
 | 
			
		||||
                path: 'contents',
 | 
			
		||||
                options: {
 | 
			
		||||
                    params: {
 | 
			
		||||
                        chapterId,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getPageParams(url: string, params: Record<string, string>): Params {
 | 
			
		||||
        return params.chapterid ? { chapterId: parseInt(params.chapterid, 10) } : {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the handler is enabled for a certain site (site + user) and a URL.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether the handler is enabled for the URL and site.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(siteId: string): Promise<boolean> {
 | 
			
		||||
        return AddonModBook.isPluginEnabled(siteId);
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,10 @@ export class AddonModFeedbackPushClickHandlerService implements CorePushNotifica
 | 
			
		||||
        if (notification.name == 'submission') {
 | 
			
		||||
            return AddonModFeedbackHelper.handleShowEntriesLink(contextUrlParams, notification.site);
 | 
			
		||||
        } else {
 | 
			
		||||
            return CoreCourseHelper.navigateToModule(moduleId, notification.site, courseId);
 | 
			
		||||
            return CoreCourseHelper.navigateToModule(moduleId, {
 | 
			
		||||
                courseId,
 | 
			
		||||
                siteId: notification.site,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,7 +76,11 @@ export class AddonModLessonGradeLinkHandlerService extends CoreContentLinksModul
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                // User cannot view the report, go to lesson index.
 | 
			
		||||
                CoreCourseHelper.navigateToModule(moduleId, siteId, module.course, module.section);
 | 
			
		||||
                CoreCourseHelper.navigateToModule(moduleId, {
 | 
			
		||||
                    courseId: module.course,
 | 
			
		||||
                    sectionId: module.section,
 | 
			
		||||
                    siteId,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
			
		||||
 | 
			
		||||
@ -61,7 +61,10 @@ export class AddonModLessonIndexLinkHandlerService extends CoreContentLinksModul
 | 
			
		||||
                if (params.userpassword) {
 | 
			
		||||
                    this.navigateToModuleWithPassword(parseInt(params.id, 10), courseId!, params.userpassword, siteId);
 | 
			
		||||
                } else {
 | 
			
		||||
                    CoreCourseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId);
 | 
			
		||||
                    CoreCourseHelper.navigateToModule(parseInt(params.id, 10), {
 | 
			
		||||
                        courseId,
 | 
			
		||||
                        siteId,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        }];
 | 
			
		||||
@ -94,10 +97,17 @@ export class AddonModLessonIndexLinkHandlerService extends CoreContentLinksModul
 | 
			
		||||
            // Store the password so it's automatically used.
 | 
			
		||||
            await CoreUtils.ignoreErrors(AddonModLesson.storePassword(module.instance, password, siteId));
 | 
			
		||||
 | 
			
		||||
            await CoreCourseHelper.navigateToModule(moduleId, siteId, module.course, module.section);
 | 
			
		||||
            await CoreCourseHelper.navigateToModule(moduleId, {
 | 
			
		||||
                courseId: module.course,
 | 
			
		||||
                sectionId: module.section,
 | 
			
		||||
                siteId,
 | 
			
		||||
            });
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Error, go to index page.
 | 
			
		||||
            await CoreCourseHelper.navigateToModule(moduleId, siteId, courseId);
 | 
			
		||||
            await CoreCourseHelper.navigateToModule(moduleId, {
 | 
			
		||||
                courseId,
 | 
			
		||||
                siteId,
 | 
			
		||||
            });
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -72,7 +72,10 @@ export class AddonModQuizPushClickHandlerService implements CorePushNotification
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.ignoreErrors(AddonModQuiz.invalidateContent(moduleId, courseId, notification.site));
 | 
			
		||||
 | 
			
		||||
        return CoreCourseHelper.navigateToModule(moduleId, notification.site, courseId);
 | 
			
		||||
        return CoreCourseHelper.navigateToModule(moduleId, {
 | 
			
		||||
            courseId,
 | 
			
		||||
            siteId: notification.site,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -78,10 +78,11 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
 | 
			
		||||
                    // No user specified or current user. Navigate to module.
 | 
			
		||||
                    CoreCourseHelper.navigateToModule(
 | 
			
		||||
                        Number(params.id),
 | 
			
		||||
                        siteId,
 | 
			
		||||
                        courseId,
 | 
			
		||||
                        undefined,
 | 
			
		||||
                        this.useModNameToGetModule ? this.modName : undefined,
 | 
			
		||||
                        {
 | 
			
		||||
                            courseId,
 | 
			
		||||
                            modName: this.useModNameToGetModule ? this.modName : undefined,
 | 
			
		||||
                            siteId,
 | 
			
		||||
                        },
 | 
			
		||||
                    );
 | 
			
		||||
                } else if (this.canReview) {
 | 
			
		||||
                    // Use the goToReview function.
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import { CoreContentLinksHandlerBase } from './base-handler';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksAction } from '../services/contentlinks-delegate';
 | 
			
		||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreNavigationOptions } from '@services/navigator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to handle URLs pointing to the index of a module.
 | 
			
		||||
@ -58,12 +59,27 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
 | 
			
		||||
     * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
 | 
			
		||||
     * @param courseId Course ID related to the URL. Optional but recommended.
 | 
			
		||||
     * @return List of params to pass to navigateToModule / navigateToModuleByInstance.
 | 
			
		||||
     * @deprecated since 4.0
 | 
			
		||||
     */
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    getPageParams(url: string, params: Record<string, string>, courseId?: number): Params {
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the navigation options to open the module.
 | 
			
		||||
     *
 | 
			
		||||
     * @param url The URL to treat.
 | 
			
		||||
     * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
 | 
			
		||||
     * @param siteId The site ID.
 | 
			
		||||
     * @param courseId Course ID related to the URL. Optional but recommended.
 | 
			
		||||
     * @return Navigation options to open the module.
 | 
			
		||||
     */
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    getModNavOptions(url: string, params: Record<string, string>, siteId: string, courseId?: number): CoreNavigationOptions {
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the list of actions for a link (url).
 | 
			
		||||
     *
 | 
			
		||||
@ -81,7 +97,18 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
 | 
			
		||||
    ): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
 | 
			
		||||
 | 
			
		||||
        courseId = Number(courseId || params.courseid || params.cid);
 | 
			
		||||
        const pageParams = this.getPageParams(url, params, courseId);
 | 
			
		||||
        const getModNavOptions = (siteId: string): CoreNavigationOptions => {
 | 
			
		||||
            let modNavOptions = this.getModNavOptions(url, params, siteId, courseId);
 | 
			
		||||
            if (!modNavOptions) {
 | 
			
		||||
                // Use the old function, currently deprecated.
 | 
			
		||||
                const pageParams = this.getPageParams(url, params, courseId);
 | 
			
		||||
                if (pageParams && Object.keys(pageParams).length > 0) {
 | 
			
		||||
                    modNavOptions = { params: pageParams };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return modNavOptions;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (this.instanceIdParam && params[this.instanceIdParam] !== undefined) {
 | 
			
		||||
            const instanceId = parseInt(params[this.instanceIdParam], 10);
 | 
			
		||||
@ -91,11 +118,12 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
 | 
			
		||||
                    CoreCourseHelper.navigateToModuleByInstance(
 | 
			
		||||
                        instanceId,
 | 
			
		||||
                        this.modName,
 | 
			
		||||
                        siteId,
 | 
			
		||||
                        courseId,
 | 
			
		||||
                        undefined,
 | 
			
		||||
                        this.useModNameToGetModule,
 | 
			
		||||
                        pageParams,
 | 
			
		||||
                        {
 | 
			
		||||
                            courseId,
 | 
			
		||||
                            useModNameToGetModule: this.useModNameToGetModule,
 | 
			
		||||
                            modNavOptions: getModNavOptions(siteId),
 | 
			
		||||
                            siteId,
 | 
			
		||||
                        },
 | 
			
		||||
                    );
 | 
			
		||||
                },
 | 
			
		||||
            }];
 | 
			
		||||
@ -105,11 +133,12 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
 | 
			
		||||
            action: (siteId) => {
 | 
			
		||||
                CoreCourseHelper.navigateToModule(
 | 
			
		||||
                    parseInt(params.id, 10),
 | 
			
		||||
                    siteId,
 | 
			
		||||
                    courseId,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    this.useModNameToGetModule ? this.modName : undefined,
 | 
			
		||||
                    pageParams,
 | 
			
		||||
                    {
 | 
			
		||||
                        courseId,
 | 
			
		||||
                        modName: this.useModNameToGetModule ? this.modName : undefined,
 | 
			
		||||
                        modNavOptions: getModNavOptions(siteId),
 | 
			
		||||
                        siteId,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
        }];
 | 
			
		||||
 | 
			
		||||
@ -311,7 +311,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
                error,
 | 
			
		||||
            ]);
 | 
			
		||||
        } else {
 | 
			
		||||
            error = CoreTextUtils.getErrorMessageFromError(error) || error;
 | 
			
		||||
            error = CoreTextUtils.getErrorMessageFromError(error) || '';
 | 
			
		||||
 | 
			
		||||
            return Translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseWSSection } fro
 | 
			
		||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CONTENTS_PAGE_NAME } from '@features/course/course.module';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreCollapsibleHeaderDirective } from '@directives/collapsible-header';
 | 
			
		||||
@ -57,7 +57,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
    protected sections: CoreCourseWSSection[] = []; // List of course sections.
 | 
			
		||||
    protected firstTabName?: string;
 | 
			
		||||
    protected module?: CoreCourseModuleData;
 | 
			
		||||
    protected modParams?: Params;
 | 
			
		||||
    protected modNavOptions?: CoreNavigationOptions;
 | 
			
		||||
    protected isGuest = false;
 | 
			
		||||
    protected contentsTab: CoreTabsOutletTab & { pageParams: Params } = {
 | 
			
		||||
        page: CONTENTS_PAGE_NAME,
 | 
			
		||||
@ -136,8 +136,15 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
 | 
			
		||||
        this.module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
 | 
			
		||||
        this.modParams = CoreNavigator.getRouteParam<Params>('modParams');
 | 
			
		||||
        this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
 | 
			
		||||
        this.modNavOptions = CoreNavigator.getRouteParam<CoreNavigationOptions>('modNavOptions');
 | 
			
		||||
        if (!this.modNavOptions) {
 | 
			
		||||
            // Fallback to old way of passing params. @deprecated since 4.0.
 | 
			
		||||
            const modParams = CoreNavigator.getRouteParam<Params>('modParams');
 | 
			
		||||
            if (modParams) {
 | 
			
		||||
                this.modNavOptions = { params: modParams };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.currentPagePath = CoreNavigator.getCurrentPath();
 | 
			
		||||
        this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page);
 | 
			
		||||
@ -171,7 +178,10 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // Now that the first tab has been selected we can load the module.
 | 
			
		||||
        CoreCourseHelper.openModule(this.module, this.course.id, this.contentsTab.pageParams.sectionId, this.modParams);
 | 
			
		||||
        CoreCourseHelper.openModule(this.module, this.course.id, {
 | 
			
		||||
            sectionId: this.contentsTab.pageParams.sectionId,
 | 
			
		||||
            modNavOptions: this.modNavOptions,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        delete this.module;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1573,36 +1573,27 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     *
 | 
			
		||||
     * @param instanceId Activity instance ID.
 | 
			
		||||
     * @param modName Module name of the activity.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param courseId Course ID. If not defined we'll try to retrieve it from the site.
 | 
			
		||||
     * @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site.
 | 
			
		||||
     * @param useModNameToGetModule If true, the app will retrieve all modules of this type with a single WS call. This reduces the
 | 
			
		||||
     *                              number of WS calls, but it isn't recommended for modules that can return a lot of contents.
 | 
			
		||||
     * @param modParams Params to pass to the module
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async navigateToModuleByInstance(
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        modName: string,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
        courseId?: number,
 | 
			
		||||
        sectionId?: number,
 | 
			
		||||
        useModNameToGetModule: boolean = false,
 | 
			
		||||
        modParams?: Params,
 | 
			
		||||
        options: CoreCourseNavigateToModuleByInstanceOptions = {},
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
 | 
			
		||||
        const modal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const module = await CoreCourse.getModuleBasicInfoByInstance(instanceId, modName, { siteId });
 | 
			
		||||
            const module = await CoreCourse.getModuleBasicInfoByInstance(instanceId, modName, { siteId: options.siteId });
 | 
			
		||||
 | 
			
		||||
            this.navigateToModule(
 | 
			
		||||
                module.id,
 | 
			
		||||
                siteId,
 | 
			
		||||
                module.course,
 | 
			
		||||
                sectionId,
 | 
			
		||||
                useModNameToGetModule ? modName : undefined,
 | 
			
		||||
                modParams,
 | 
			
		||||
                {
 | 
			
		||||
                    ...options,
 | 
			
		||||
                    courseId: module.course,
 | 
			
		||||
                    modName: options.useModNameToGetModule ? modName : undefined,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
			
		||||
@ -1616,23 +1607,16 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     * Navigate to a module.
 | 
			
		||||
     *
 | 
			
		||||
     * @param moduleId Module's ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param courseId Course ID. If not defined we'll try to retrieve it from the site.
 | 
			
		||||
     * @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site.
 | 
			
		||||
     * @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the
 | 
			
		||||
     *                number of WS calls, but it isn't recommended for modules that can return a lot of contents.
 | 
			
		||||
     * @param modParams Params to pass to the module
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async navigateToModule(
 | 
			
		||||
        moduleId: number,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
        courseId?: number,
 | 
			
		||||
        sectionId?: number,
 | 
			
		||||
        modName?: string,
 | 
			
		||||
        modParams?: Params,
 | 
			
		||||
        options: CoreCourseNavigateToModuleOptions = {},
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
        const siteId = options.siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
        let courseId = options.courseId;
 | 
			
		||||
        let sectionId = options.sectionId;
 | 
			
		||||
 | 
			
		||||
        const modal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
@ -1651,10 +1635,9 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
            const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
            // Get the module.
 | 
			
		||||
            const module =
 | 
			
		||||
                await CoreCourse.getModule(moduleId, courseId, sectionId, false, false, siteId, modName);
 | 
			
		||||
            const module = await CoreCourse.getModule(moduleId, courseId, sectionId, false, false, siteId, options.modName);
 | 
			
		||||
 | 
			
		||||
            if (CoreSites.getCurrentSiteId() == site.getId()) {
 | 
			
		||||
            if (CoreSites.getCurrentSiteId() === site.getId()) {
 | 
			
		||||
                // Try to use the module's handler to navigate cleanly.
 | 
			
		||||
                module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
 | 
			
		||||
                    module.modname,
 | 
			
		||||
@ -1667,7 +1650,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
                if (module.handlerData?.action) {
 | 
			
		||||
                    modal.dismiss();
 | 
			
		||||
 | 
			
		||||
                    return module.handlerData.action(new Event('click'), module, courseId, { params: modParams });
 | 
			
		||||
                    return module.handlerData.action(new Event('click'), module, courseId, options.modNavOptions);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1675,7 +1658,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
                course: { id: courseId },
 | 
			
		||||
                module,
 | 
			
		||||
                sectionId,
 | 
			
		||||
                modParams,
 | 
			
		||||
                modNavOptions: options.modNavOptions,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if (courseId == site.getSiteHomeId()) {
 | 
			
		||||
@ -1704,23 +1687,25 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module to open.
 | 
			
		||||
     * @param courseId The course ID of the module.
 | 
			
		||||
     * @param sectionId The section ID of the module.
 | 
			
		||||
     * @param modParams Params to pass to the module
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @param True if module can be opened, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async openModule(module: CoreCourseModuleData, courseId: number, sectionId?: number, modParams?: Params): Promise<boolean> {
 | 
			
		||||
    async openModule(module: CoreCourseModuleData, courseId: number, options: CoreCourseOpenModuleOptions = {}): Promise<boolean> {
 | 
			
		||||
        if (!module.handlerData) {
 | 
			
		||||
            module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
 | 
			
		||||
                module.modname,
 | 
			
		||||
                module,
 | 
			
		||||
                courseId,
 | 
			
		||||
                sectionId,
 | 
			
		||||
                options.sectionId,
 | 
			
		||||
                false,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (module.handlerData?.action) {
 | 
			
		||||
            module.handlerData.action(new Event('click'), module, courseId, { animated: false, params: modParams });
 | 
			
		||||
            module.handlerData.action(new Event('click'), module, courseId, {
 | 
			
		||||
                animated: false,
 | 
			
		||||
                ...options.modNavOptions,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
@ -2206,6 +2191,39 @@ export type CoreCourseConfirmPrefetchCoursesOptions = CoreCoursePrefetchCoursesO
 | 
			
		||||
    onProgress?: (data: CoreCourseCoursesProgress) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Common options for navigate to module functions.
 | 
			
		||||
 */
 | 
			
		||||
type CoreCourseNavigateToModuleCommonOptions = {
 | 
			
		||||
    courseId?: number; // Course ID. If not defined we'll try to retrieve it from the site.
 | 
			
		||||
    sectionId?: number; // Section the module belongs to. If not defined we'll try to retrieve it from the site.
 | 
			
		||||
    modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
 | 
			
		||||
    siteId?: string; // Site ID. If not defined, current site.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Options for navigate to module by instance function.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseNavigateToModuleByInstanceOptions = CoreCourseNavigateToModuleCommonOptions & {
 | 
			
		||||
    // True to retrieve all instances with a single WS call. Not recommended if can return a lot of contents.
 | 
			
		||||
    useModNameToGetModule?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Options for navigate to module function.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseNavigateToModuleOptions = CoreCourseNavigateToModuleCommonOptions & {
 | 
			
		||||
    modName?: string; // To retrieve all instances with a single WS call. Not recommended if can return a lot of contents.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Options for open module function.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseOpenModuleOptions = {
 | 
			
		||||
    sectionId?: number; // Section the module belongs to.
 | 
			
		||||
    modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type ComponentWithContextMenu = {
 | 
			
		||||
    prefetchStatusIcon?: string;
 | 
			
		||||
    isDestroyed?: boolean;
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreBlockHelper } from '@features/block/services/block-helper';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
 | 
			
		||||
@ -73,8 +73,15 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        const module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
 | 
			
		||||
        if (module) {
 | 
			
		||||
            const modParams = CoreNavigator.getRouteParam<Params>('modParams');
 | 
			
		||||
            CoreCourseHelper.openModule(module, this.siteHomeId, undefined, modParams);
 | 
			
		||||
            let modNavOptions = CoreNavigator.getRouteParam<CoreNavigationOptions>('modNavOptions');
 | 
			
		||||
            if (!modNavOptions) {
 | 
			
		||||
                // Fallback to old way of passing params. @deprecated since 4.0.
 | 
			
		||||
                const modParams = CoreNavigator.getRouteParam<Params>('modParams');
 | 
			
		||||
                if (modParams) {
 | 
			
		||||
                    modNavOptions = { params: modParams };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            CoreCourseHelper.openModule(module, this.siteHomeId, { modNavOptions });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.loadContent().finally(() => {
 | 
			
		||||
 | 
			
		||||
@ -248,6 +248,7 @@
 | 
			
		||||
    "restore": "Restore",
 | 
			
		||||
    "restricted": "Restricted",
 | 
			
		||||
    "retry": "Retry",
 | 
			
		||||
    "resume": "Resume",
 | 
			
		||||
    "save": "Save",
 | 
			
		||||
    "savechanges": "Save changes",
 | 
			
		||||
    "scanqr": "Scan QR code",
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ information provided here is intended especially for developers.
 | 
			
		||||
- displaySectionSelector has been deprecated on CoreCourseFormatHandler, use displayCourseIndex instead.
 | 
			
		||||
- Most of the functions or callbacks that handle redirects/deeplinks have been modified to accept an object instead of just path + options. E.g.: CoreLoginHelper.isSiteLoggedOut, CoreLoginHelper.openBrowserForSSOLogin, CoreLoginHelper.openBrowserForOAuthLogin, CoreLoginHelper.prepareForSSOLogin, CoreApp.storeRedirect, CoreSites.loadSite.
 | 
			
		||||
- Course preview page route has changed from course/:courseId/preview to course/:courseId/summary to match with the page name and characteristics.
 | 
			
		||||
- The parameters of the following functions in CoreCourseHelper have changed: navigateToModuleByInstance, navigateToModule, openModule.
 | 
			
		||||
 | 
			
		||||
=== 3.9.5 ===
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user