MOBILE-3637 book: Implement book activity
parent
5a15fca0a9
commit
c98fa810fa
|
@ -0,0 +1,28 @@
|
|||
// (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';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':courseId/:cmdId',
|
||||
loadChildren: () => import('./pages/index/index.module').then( m => m.AddonModBookIndexPageModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
})
|
||||
export class AddonModBookLazyModule {}
|
|
@ -0,0 +1,57 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { Routes } from '@angular/router';
|
||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
|
||||
import { AddonModBookComponentsModule } from './components/components.module';
|
||||
import { AddonModBookModuleHandler, AddonModBookModuleHandlerService } from './services/handlers/module';
|
||||
import { AddonModBookIndexLinkHandler } from './services/handlers/index-link';
|
||||
import { AddonModBookListLinkHandler } from './services/handlers/list-link';
|
||||
import { AddonModBookPrefetchHandler } from './services/handlers/prefetch';
|
||||
import { AddonModBookTagAreaHandler } from './services/handlers/tag-area';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: AddonModBookModuleHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('./book-lazy.module').then(m => m.AddonModBookLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||
AddonModBookComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () => {
|
||||
CoreCourseModuleDelegate.instance.registerHandler(AddonModBookModuleHandler.instance);
|
||||
CoreContentLinksDelegate.instance.registerHandler(AddonModBookIndexLinkHandler.instance);
|
||||
CoreContentLinksDelegate.instance.registerHandler(AddonModBookListLinkHandler.instance);
|
||||
CoreCourseModulePrefetchDelegate.instance.registerHandler(AddonModBookPrefetchHandler.instance);
|
||||
CoreTagAreaDelegate.instance.registerHandler(AddonModBookTagAreaHandler.instance);
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AddonModBookModule {}
|
|
@ -0,0 +1,47 @@
|
|||
// (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 { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/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';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModBookIndexComponent,
|
||||
AddonModBookTocComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
FormsModule,
|
||||
CoreSharedModule,
|
||||
CoreCourseComponentsModule,
|
||||
CoreTagComponentsModule,
|
||||
],
|
||||
exports: [
|
||||
AddonModBookIndexComponent,
|
||||
AddonModBookTocComponent,
|
||||
],
|
||||
})
|
||||
export class AddonModBookComponentsModule {}
|
|
@ -0,0 +1,52 @@
|
|||
<!-- 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"></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"></core-context-menu-item>
|
||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
||||
(action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
|
||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"
|
||||
iconAction="far-newspaper" (action)="gotoBlog()"></core-context-menu-item>
|
||||
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
|
||||
[iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
|
||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||
<core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
||||
</core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</core-navbar-buttons>
|
||||
|
||||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"></core-course-module-description>
|
||||
|
||||
<ion-card class="core-warning-card" *ngIf="warning">
|
||||
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
|
||||
<span [innerHTML]="warning"></span>
|
||||
</ion-card>
|
||||
|
||||
<div class="ion-padding safe-padding-horizontal">
|
||||
<core-navigation-bar *ngIf="displayNavBar" [previous]="previousChapter?.id"
|
||||
[previousTitle]="previousNavBarTitle" [next]="nextChapter?.id" [nextTitle]="nextNavBarTitle"
|
||||
(action)="changeChapter($event)">
|
||||
</core-navigation-bar>
|
||||
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="chapterContent" contextLevel="module"
|
||||
[contextInstanceId]="module?.id" [courseId]="courseId"></core-format-text>
|
||||
<div class="ion-margin-top" *ngIf="tagsEnabled && tags?.length > 0">
|
||||
<strong>{{ 'core.tag.tags' | translate }}: </strong>
|
||||
<core-tag-list [tags]="tags"></core-tag-list>
|
||||
</div>
|
||||
|
||||
<core-navigation-bar *ngIf="displayNavBar" [previous]="previousChapter?.id"
|
||||
[previousTitle]="previousNavBarTitle" [next]="nextChapter?.id" [nextTitle]="nextNavBarTitle"
|
||||
(action)="changeChapter($event)"></core-navigation-bar>
|
||||
</div>
|
||||
|
||||
</core-loading>
|
|
@ -0,0 +1,251 @@
|
|||
// (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 { Component, Optional, Input, OnInit } from '@angular/core';
|
||||
import { IonContent } from '@ionic/angular';
|
||||
import {
|
||||
CoreCourseModuleMainResourceComponent, CoreCourseResourceDownloadResult,
|
||||
} 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 { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
||||
import { ModalController, Translate } from '@singletons';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { AddonModBookTocComponent } from '../toc/toc';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
|
||||
/**
|
||||
* Component that displays a book.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-book-index',
|
||||
templateUrl: 'addon-mod-book-index.html',
|
||||
})
|
||||
export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
|
||||
|
||||
@Input() initialChapterId?: number; // The initial chapter ID to load.
|
||||
|
||||
component = AddonModBookProvider.COMPONENT;
|
||||
chapterContent?: string;
|
||||
previousChapter?: AddonModBookTocChapter;
|
||||
nextChapter?: AddonModBookTocChapter;
|
||||
tagsEnabled = false;
|
||||
displayNavBar = true;
|
||||
previousNavBarTitle?: string;
|
||||
nextNavBarTitle?: string;
|
||||
warning = '';
|
||||
tags?: CoreTagItem[];
|
||||
|
||||
protected chapters: AddonModBookTocChapter[] = [];
|
||||
protected currentChapter?: number;
|
||||
protected book?: AddonModBookBookWSData;
|
||||
protected displayTitlesInNavBar = false;
|
||||
protected contentsMap: AddonModBookContentsMap = {};
|
||||
|
||||
constructor(
|
||||
protected content?: IonContent,
|
||||
@Optional() courseContentsPage?: CoreCourseContentsPage,
|
||||
) {
|
||||
super('AddonModBookIndexComponent', courseContentsPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
super.ngOnInit();
|
||||
|
||||
this.tagsEnabled = CoreTag.instance.areTagsAvailableInSite();
|
||||
this.loadContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the TOC.
|
||||
*/
|
||||
async showToc(): Promise<void> {
|
||||
// Create the toc modal.
|
||||
const modal = await ModalController.instance.create({
|
||||
component: AddonModBookTocComponent,
|
||||
componentProps: {
|
||||
moduleId: this.module!.id,
|
||||
chapters: this.chapters,
|
||||
selected: this.currentChapter,
|
||||
courseId: this.courseId,
|
||||
book: this.book,
|
||||
},
|
||||
cssClass: 'core-modal-lateral',
|
||||
showBackdrop: true,
|
||||
backdropDismiss: true,
|
||||
// @todo enterAnimation: 'core-modal-lateral-transition',
|
||||
// @todo leaveAnimation: 'core-modal-lateral-transition',
|
||||
});
|
||||
|
||||
|
||||
await modal.present();
|
||||
|
||||
const result = await modal.onDidDismiss();
|
||||
|
||||
if (result.data) {
|
||||
this.changeChapter(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current chapter.
|
||||
*
|
||||
* @param chapterId Chapter to load.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
changeChapter(chapterId: number): void {
|
||||
if (chapterId && chapterId != this.currentChapter) {
|
||||
this.loaded = false;
|
||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
||||
this.loadChapter(chapterId, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the invalidate content function.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected invalidateContent(): Promise<void> {
|
||||
return AddonModBook.instance.invalidateContent(this.module!.id, this.courseId!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
const promises: Promise<void>[] = [];
|
||||
let downloadResult: CoreCourseResourceDownloadResult | undefined;
|
||||
|
||||
// Try to get the book data. Ignore errors since this WS isn't available in some Moodle versions.
|
||||
promises.push(CoreUtils.instance.ignoreErrors(AddonModBook.instance.getBook(this.courseId!, this.module!.id))
|
||||
.then((book) => {
|
||||
if (!book) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.book = book;
|
||||
this.dataRetrieved.emit(book);
|
||||
|
||||
this.description = book.intro;
|
||||
this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY;
|
||||
this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT;
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
// Get module status to determine if it needs to be downloaded.
|
||||
promises.push(this.downloadResourceIfNeeded(refresh).then((result) => {
|
||||
downloadResult = result;
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
|
||||
this.contentsMap = AddonModBook.instance.getContentsMap(this.module!.contents);
|
||||
this.chapters = AddonModBook.instance.getTocList(this.module!.contents);
|
||||
|
||||
if (typeof this.currentChapter == 'undefined' && typeof this.initialChapterId != 'undefined' && this.chapters) {
|
||||
// Initial chapter set. Validate that the chapter exists.
|
||||
const chapter = this.chapters.find((chapter) => chapter.id == this.initialChapterId);
|
||||
|
||||
if (chapter) {
|
||||
this.currentChapter = this.initialChapterId;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this.currentChapter == 'undefined') {
|
||||
// Load the first chapter.
|
||||
this.currentChapter = AddonModBook.instance.getFirstChapter(this.chapters);
|
||||
}
|
||||
|
||||
// Show chapter.
|
||||
try {
|
||||
await this.loadChapter(this.currentChapter!, refresh);
|
||||
|
||||
this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
|
||||
} catch {
|
||||
// Ignore errors, they're handled inside the loadChapter function.
|
||||
}
|
||||
} finally {
|
||||
this.fillContextMenu(refresh);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a book chapter.
|
||||
*
|
||||
* @param chapterId Chapter to load.
|
||||
* @param logChapterId Whether chapter ID should be passed to the log view function.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadChapter(chapterId: number, logChapterId: boolean): Promise<void> {
|
||||
this.currentChapter = chapterId;
|
||||
this.content?.scrollToTop();
|
||||
|
||||
try {
|
||||
const content = await AddonModBook.instance.getChapterContent(this.contentsMap, chapterId, this.module!.id);
|
||||
|
||||
this.tags = this.tagsEnabled ? this.contentsMap[this.currentChapter].tags : [];
|
||||
|
||||
this.chapterContent = content;
|
||||
this.previousChapter = AddonModBook.instance.getPreviousChapter(this.chapters, chapterId);
|
||||
this.nextChapter = AddonModBook.instance.getNextChapter(this.chapters, chapterId);
|
||||
|
||||
this.previousNavBarTitle = this.previousChapter && this.displayTitlesInNavBar
|
||||
? Translate.instance.instant('addon.mod_book.navprevtitle', { $a: this.previousChapter.title })
|
||||
: '';
|
||||
this.nextNavBarTitle = this.nextChapter && this.displayTitlesInNavBar
|
||||
? Translate.instance.instant('addon.mod_book.navnexttitle', { $a: this.nextChapter.title })
|
||||
: '';
|
||||
|
||||
// Chapter loaded, log view. We don't return the promise because we don't want to block the user for this.
|
||||
await CoreUtils.instance.ignoreErrors(AddonModBook.instance.logView(
|
||||
this.module!.instance!,
|
||||
logChapterId ? chapterId : undefined,
|
||||
this.module!.name,
|
||||
));
|
||||
|
||||
// Module is completed when last chapter is viewed, so we only check completion if the last is reached.
|
||||
if (!this.nextChapter) {
|
||||
CoreCourse.instance.checkModuleCompletion(this.courseId!, this.module!.completiondata);
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_book.errorchapter', true);
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'addon.mod_book.toc' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||
<ion-icon name="fas-times" slot="icon-only"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<nav>
|
||||
<ion-list>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" (click)="loadChapter(chapter.id)"
|
||||
[class.core-nav-item-selected]="selected == chapter.id"
|
||||
[class.item-dimmed]="chapter.hidden">
|
||||
<ion-label>
|
||||
<p [class.ion-padding-left]="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]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</nav>
|
||||
</ion-content>
|
|
@ -0,0 +1,5 @@
|
|||
.addon-mod-book-bullet {
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
margin-right: 3px;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// (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 { Component, Input, OnInit } from '@angular/core';
|
||||
import { ModalController } from '@singletons';
|
||||
import { AddonModBookTocChapter, AddonModBookBookWSData, AddonModBookNumbering } from '../../services/book';
|
||||
|
||||
/**
|
||||
* Modal to display the TOC of a book.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-book-toc',
|
||||
templateUrl: 'toc.html',
|
||||
styleUrls: ['toc.scss'],
|
||||
})
|
||||
export class AddonModBookTocComponent implements OnInit {
|
||||
|
||||
@Input() moduleId?: number;
|
||||
@Input() chapters: AddonModBookTocChapter[] = [];
|
||||
@Input() selected?: number;
|
||||
@Input() courseId?: number;
|
||||
showNumbers = true;
|
||||
addPadding = true;
|
||||
showBullets = false;
|
||||
|
||||
@Input() protected book?: AddonModBookBookWSData;
|
||||
|
||||
/**
|
||||
* Component loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (this.book) {
|
||||
this.showNumbers = this.book.numbering == AddonModBookNumbering.NUMBERS;
|
||||
this.showBullets = this.book.numbering == AddonModBookNumbering.BULLETS;
|
||||
this.addPadding = this.book.numbering != AddonModBookNumbering.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when a course is clicked.
|
||||
*
|
||||
* @param id ID of the clicked chapter.
|
||||
*/
|
||||
loadChapter(id: number): void {
|
||||
ModalController.instance.dismiss(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
ModalController.instance.dismiss();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"errorchapter": "Error reading chapter of book.",
|
||||
"modulenameplural": "Books",
|
||||
"navnexttitle": "Next: {{$a}}",
|
||||
"navprevtitle": "Previous: {{$a}}",
|
||||
"tagarea_book_chapters": "Book chapters",
|
||||
"toc": "Table of contents"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<!-- The buttons defined by the component will be added in here. -->
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!bookComponent?.loaded" (ionRefresh)="bookComponent?.doRefresh($event)">
|
||||
<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>
|
||||
</ion-content>
|
|
@ -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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { AddonModBookComponentsModule } from '../../components/components.module';
|
||||
import { AddonModBookIndexPage } from './index';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AddonModBookIndexPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreSharedModule,
|
||||
AddonModBookComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonModBookIndexPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AddonModBookIndexPageModule {}
|
|
@ -0,0 +1,57 @@
|
|||
// (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 { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { CoreCourseWSModule } from '@features/course/services/course';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { AddonModBookIndexComponent } from '../../components/index/index';
|
||||
import { AddonModBookBookWSData } from '../../services/book';
|
||||
|
||||
/**
|
||||
* Page that displays a book.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-mod-book-index',
|
||||
templateUrl: 'index.html',
|
||||
})
|
||||
export class AddonModBookIndexPage implements OnInit {
|
||||
|
||||
@ViewChild(AddonModBookIndexComponent) bookComponent?: AddonModBookIndexComponent;
|
||||
|
||||
title?: string;
|
||||
module?: CoreCourseWSModule;
|
||||
courseId?: number;
|
||||
chapterId?: number;
|
||||
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.module = CoreNavigator.instance.getRouteParam('module');
|
||||
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId');
|
||||
this.chapterId = CoreNavigator.instance.getRouteNumberParam('chapterId');
|
||||
this.title = this.module?.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update some data based on the book instance.
|
||||
*
|
||||
* @param book Book instance.
|
||||
*/
|
||||
updateData(book: AddonModBookBookWSData): void {
|
||||
this.title = book.name || this.title;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,479 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
|
||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||
import { CoreTagItem } from '@features/tag/services/tag';
|
||||
import { CoreWSExternalWarning, CoreWSExternalFile, CoreWS } from '@services/ws';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
|
||||
import { CoreCourse, CoreCourseModuleContentFile } from '@features/course/services/course';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreWSError } from '@classes/errors/wserror';
|
||||
|
||||
/**
|
||||
* Constants to define how the chapters and subchapters of a book should be displayed in that table of contents.
|
||||
*/
|
||||
export const enum AddonModBookNumbering {
|
||||
NONE = 0,
|
||||
NUMBERS = 1,
|
||||
BULLETS = 2,
|
||||
INDENTED = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants to define the navigation style used within a book.
|
||||
*/
|
||||
export const enum AddonModBookNavStyle {
|
||||
TOC_ONLY = 0,
|
||||
IMAGE = 1,
|
||||
TEXT = 2,
|
||||
}
|
||||
|
||||
const ROOT_CACHE_KEY = 'mmaModBook:';
|
||||
|
||||
|
||||
/**
|
||||
* Service that provides some features for books.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModBookProvider {
|
||||
|
||||
static readonly COMPONENT = 'mmaModBook';
|
||||
|
||||
/**
|
||||
* Get a book by course module ID.
|
||||
*
|
||||
* @param courseId Course ID.
|
||||
* @param cmId Course module ID.
|
||||
* @param options Other options.
|
||||
* @return Promise resolved when the book is retrieved.
|
||||
*/
|
||||
getBook(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModBookBookWSData> {
|
||||
return this.getBookByField(courseId, 'coursemodule', cmId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a book with key=value. If more than one is found, only the first will be returned.
|
||||
*
|
||||
* @param courseId Course ID.
|
||||
* @param key Name of the property to check.
|
||||
* @param value Value to search.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the book is retrieved.
|
||||
*/
|
||||
protected async getBookByField(
|
||||
courseId: number,
|
||||
key: string,
|
||||
value: number,
|
||||
options: CoreSitesCommonWSOptions = {},
|
||||
): Promise<AddonModBookBookWSData> {
|
||||
|
||||
const site = await CoreSites.instance.getSite(options.siteId);
|
||||
const params: AddonModBookGetBooksByCoursesWSParams = {
|
||||
courseids: [courseId],
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getBookDataCacheKey(courseId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
component: AddonModBookProvider.COMPONENT,
|
||||
...CoreSites.instance.getReadingStrategyPreSets(options.readingStrategy),
|
||||
};
|
||||
|
||||
const response: AddonModBookGetBooksByCoursesWSResponse = await site.read('mod_book_get_books_by_courses', params, preSets);
|
||||
|
||||
// Search the book.
|
||||
const book = response.books.find((book) => book[key] == value);
|
||||
if (book) {
|
||||
return book;
|
||||
}
|
||||
|
||||
throw new CoreWSError('Book not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for get book data WS calls.
|
||||
*
|
||||
* @param courseId Course ID.
|
||||
* @return Cache key.
|
||||
*/
|
||||
protected getBookDataCacheKey(courseId: number): string {
|
||||
return ROOT_CACHE_KEY + 'book:' + courseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a chapter contents.
|
||||
*
|
||||
* @param contentsMap Contents map returned by getContentsMap.
|
||||
* @param chapterId Chapter to retrieve.
|
||||
* @param moduleId The module ID.
|
||||
* @return Promise resolved with the contents.
|
||||
*/
|
||||
async getChapterContent(contentsMap: AddonModBookContentsMap, chapterId: number, moduleId: number): Promise<string> {
|
||||
|
||||
const indexUrl = contentsMap[chapterId] ? contentsMap[chapterId].indexUrl : undefined;
|
||||
if (!indexUrl) {
|
||||
// It shouldn't happen.
|
||||
throw new CoreWSError('Could not locate the index chapter.');
|
||||
}
|
||||
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
// We return the live URL.
|
||||
return CoreSites.instance.getCurrentSite()!.checkAndFixPluginfileURL(indexUrl);
|
||||
}
|
||||
|
||||
const siteId = CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
const url = await CoreFilepool.instance.downloadUrl(siteId, indexUrl, false, AddonModBookProvider.COMPONENT, moduleId);
|
||||
|
||||
const content = await CoreWS.instance.getText(url);
|
||||
|
||||
// Now that we have the content, we update the SRC to point back to the external resource.
|
||||
return CoreDomUtils.instance.restoreSourcesInHtml(content, contentsMap[chapterId].paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of book contents into an object where contents are organized in chapters.
|
||||
* Each chapter has an indexUrl and the list of contents in that chapter.
|
||||
*
|
||||
* @param contents The module contents.
|
||||
* @return Contents map.
|
||||
*/
|
||||
getContentsMap(contents: CoreCourseModuleContentFile[]): AddonModBookContentsMap {
|
||||
const map: AddonModBookContentsMap = {};
|
||||
|
||||
if (!contents) {
|
||||
return map;
|
||||
}
|
||||
|
||||
contents.forEach((content) => {
|
||||
if (!this.isFileDownloadable(content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search the chapter number in the filepath.
|
||||
const matches = content.filepath.match(/\/(\d+)\//);
|
||||
if (!matches || !matches[1]) {
|
||||
return;
|
||||
}
|
||||
let key: string;
|
||||
const chapter: string = matches[1];
|
||||
const filepathIsChapter = content.filepath == '/' + chapter + '/';
|
||||
|
||||
// Init the chapter if it's not defined yet.
|
||||
map[chapter] = map[chapter] || { paths: {} };
|
||||
|
||||
if (content.filename == 'index.html' && filepathIsChapter) {
|
||||
// Index of the chapter, set indexUrl and tags of the chapter.
|
||||
map[chapter].indexUrl = content.fileurl;
|
||||
map[chapter].tags = content.tags;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (filepathIsChapter) {
|
||||
// It's a file in the root folder OR the WS isn't returning the filepath as it should (MDL-53671).
|
||||
// Try to get the path to the file from the URL.
|
||||
const split = content.fileurl.split('mod_book/chapter' + content.filepath);
|
||||
key = split[1] || content.filename; // Use filename if we couldn't find the path.
|
||||
} else {
|
||||
// Remove the chapter folder from the path and add the filename.
|
||||
key = content.filepath.replace('/' + chapter + '/', '') + content.filename;
|
||||
}
|
||||
|
||||
map[chapter].paths[CoreTextUtils.instance.decodeURIComponent(key)] = content.fileurl;
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first chapter of a book.
|
||||
*
|
||||
* @param chapters The chapters list.
|
||||
* @return The chapter id.
|
||||
*/
|
||||
getFirstChapter(chapters: AddonModBookTocChapter[]): number | undefined {
|
||||
if (!chapters || !chapters.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return chapters[0].id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next chapter to the given one.
|
||||
*
|
||||
* @param chapters The chapters list.
|
||||
* @param chapterId The current chapter.
|
||||
* @return The next chapter.
|
||||
*/
|
||||
getNextChapter(chapters: AddonModBookTocChapter[], chapterId: number): AddonModBookTocChapter | undefined {
|
||||
const currentChapterIndex = chapters.findIndex((chapter) => chapter.id == chapterId);
|
||||
|
||||
if (currentChapterIndex >= 0 && typeof chapters[currentChapterIndex + 1] != 'undefined') {
|
||||
return chapters[currentChapterIndex + 1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous chapter to the given one.
|
||||
*
|
||||
* @param chapters The chapters list.
|
||||
* @param chapterId The current chapter.
|
||||
* @return The next chapter.
|
||||
*/
|
||||
getPreviousChapter(chapters: AddonModBookTocChapter[], chapterId: number): AddonModBookTocChapter | undefined {
|
||||
const currentChapterIndex = chapters.findIndex((chapter) => chapter.id == chapterId);
|
||||
|
||||
if (currentChapterIndex > 0) {
|
||||
return chapters[currentChapterIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the book toc as an array.
|
||||
*
|
||||
* @param contents The module contents.
|
||||
* @return The toc.
|
||||
*/
|
||||
getToc(contents: CoreCourseModuleContentFile[]): AddonModBookTocChapterParsed[] {
|
||||
if (!contents || !contents.length || typeof contents[0].content == 'undefined') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return CoreTextUtils.instance.parseJSON(contents[0].content, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the book toc as an array of chapters (not nested).
|
||||
*
|
||||
* @param contents The module contents.
|
||||
* @return The toc as a list.
|
||||
*/
|
||||
getTocList(contents: CoreCourseModuleContentFile[]): AddonModBookTocChapter[] {
|
||||
// Convenience function to get chapter info.
|
||||
const getChapterInfo = (
|
||||
chapter: AddonModBookTocChapterParsed,
|
||||
chapterNumber: number,
|
||||
previousNumber: string = '',
|
||||
): AddonModBookTocChapter => {
|
||||
const hidden = !!parseInt(chapter.hidden, 10);
|
||||
|
||||
const fullChapterNumber = previousNumber + (hidden ? 'x.' : chapterNumber + '.');
|
||||
|
||||
return {
|
||||
id: parseInt(chapter.href.replace('/index.html', ''), 10),
|
||||
title: chapter.title,
|
||||
level: chapter.level,
|
||||
indexNumber: fullChapterNumber,
|
||||
hidden: hidden,
|
||||
};
|
||||
};
|
||||
|
||||
const chapters: AddonModBookTocChapter[] = [];
|
||||
const toc = this.getToc(contents);
|
||||
|
||||
let chapterNumber = 1;
|
||||
toc.forEach((chapter) => {
|
||||
const tocChapter = getChapterInfo(chapter, chapterNumber);
|
||||
|
||||
// Add the chapter to the list.
|
||||
chapters.push(tocChapter);
|
||||
|
||||
if (chapter.subitems) {
|
||||
let subChapterNumber = 1;
|
||||
// Add all the subchapters to the list.
|
||||
chapter.subitems.forEach((subChapter) => {
|
||||
chapters.push(getChapterInfo(subChapter, subChapterNumber, tocChapter.indexNumber));
|
||||
subChapterNumber++;
|
||||
});
|
||||
}
|
||||
|
||||
chapterNumber++;
|
||||
});
|
||||
|
||||
return chapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates book data.
|
||||
*
|
||||
* @param courseId Course ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
async invalidateBookData(courseId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
await site.invalidateWsCacheForKey(this.getBookDataCacheKey(courseId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the prefetched content.
|
||||
*
|
||||
* @param moduleId The module ID.
|
||||
* @param courseId Course ID of the module.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<void> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
promises.push(this.invalidateBookData(courseId, siteId));
|
||||
promises.push(CoreFilepool.instance.invalidateFilesByComponent(siteId, AddonModBookProvider.COMPONENT, moduleId));
|
||||
promises.push(CoreCourse.instance.invalidateModule(moduleId, siteId));
|
||||
|
||||
return CoreUtils.instance.allPromises(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is downloadable. The file param must have a 'type' attribute like in core_course_get_contents response.
|
||||
*
|
||||
* @param file File to check.
|
||||
* @return Whether it's downloadable.
|
||||
*/
|
||||
isFileDownloadable(file: CoreCourseModuleContentFile): boolean {
|
||||
return file.type === 'file';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not the plugin is enabled.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
||||
*/
|
||||
async isPluginEnabled(siteId?: string): Promise<boolean> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return site.canDownloadFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a book as being viewed.
|
||||
*
|
||||
* @param id Module ID.
|
||||
* @param chapterId Chapter ID.
|
||||
* @param name Name of the book.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the WS call is successful.
|
||||
*/
|
||||
logView(id: number, chapterId?: number, name?: string, siteId?: string): Promise<void> {
|
||||
const params: AddonModBookViewBookWSParams = {
|
||||
bookid: id,
|
||||
chapterid: chapterId,
|
||||
};
|
||||
|
||||
return CoreCourseLogHelper.instance.logSingle(
|
||||
'mod_book_view_book',
|
||||
params,
|
||||
AddonModBookProvider.COMPONENT,
|
||||
id,
|
||||
name,
|
||||
'book',
|
||||
{ chapterid: chapterId },
|
||||
siteId,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonModBook extends makeSingleton(AddonModBookProvider) {}
|
||||
|
||||
/**
|
||||
* A book chapter inside the toc list.
|
||||
*/
|
||||
export type AddonModBookTocChapter = {
|
||||
id: number; // ID to identify the chapter.
|
||||
title: string; // Chapter's title.
|
||||
level: number; // The chapter's level.
|
||||
hidden: boolean; // The chapter is hidden.
|
||||
indexNumber: string; // The chapter's number'.
|
||||
};
|
||||
|
||||
/**
|
||||
* A book chapter parsed from JSON.
|
||||
*/
|
||||
type AddonModBookTocChapterParsed = {
|
||||
title: string; // Chapter's title.
|
||||
level: number; // The chapter's level.
|
||||
hidden: string; // The chapter is hidden.
|
||||
href: string;
|
||||
subitems: AddonModBookTocChapterParsed[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path
|
||||
* is identified by the relative path in the book, and the value is the URL of the file.
|
||||
*/
|
||||
export type AddonModBookContentsMap = {
|
||||
[chapter: string]: {
|
||||
indexUrl?: string;
|
||||
paths: {[path: string]: string};
|
||||
tags?: CoreTagItem[];
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Book returned by mod_book_get_books_by_courses.
|
||||
*/
|
||||
export type AddonModBookBookWSData = {
|
||||
id: number; // Book id.
|
||||
coursemodule: number; // Course module id.
|
||||
course: number; // Course id.
|
||||
name: string; // Book name.
|
||||
intro: string; // The Book intro.
|
||||
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
||||
introfiles?: CoreWSExternalFile[]; // @since 3.2.
|
||||
numbering: number; // Book numbering configuration.
|
||||
navstyle: number; // Book navigation style configuration.
|
||||
customtitles: number; // Book custom titles type.
|
||||
revision?: number; // Book revision.
|
||||
timecreated?: number; // Time of creation.
|
||||
timemodified?: number; // Time of last modification.
|
||||
section?: number; // Course section id.
|
||||
visible?: boolean; // Visible.
|
||||
groupmode?: number; // Group mode.
|
||||
groupingid?: number; // Group id.
|
||||
};
|
||||
|
||||
/**
|
||||
* Params of mod_book_get_books_by_courses WS.
|
||||
*/
|
||||
type AddonModBookGetBooksByCoursesWSParams = {
|
||||
courseids?: number[]; // Array of course ids.
|
||||
};
|
||||
|
||||
/**
|
||||
* Data returned by mod_book_get_books_by_courses WS.
|
||||
*/
|
||||
type AddonModBookGetBooksByCoursesWSResponse = {
|
||||
books: AddonModBookBookWSData[];
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Params of mod_book_view_book WS.
|
||||
*/
|
||||
type AddonModBookViewBookWSParams = {
|
||||
bookid: number; // Book instance id.
|
||||
chapterid?: number; // Chapter id.
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModBook } from '../book';
|
||||
|
||||
/**
|
||||
* Handler to treat links to book.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModBookIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
|
||||
|
||||
name = 'AddonModBookLinkHandler';
|
||||
|
||||
constructor() {
|
||||
super('AddonModBook', 'book', 'b');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
isEnabled(siteId: string): Promise<boolean> {
|
||||
return AddonModBook.instance.isPluginEnabled(siteId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonModBookIndexLinkHandler extends makeSingleton(AddonModBookIndexLinkHandlerService) {}
|
|
@ -0,0 +1,44 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModBook } from '../book';
|
||||
|
||||
/**
|
||||
* Handler to treat links to book list page.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModBookListLinkHandlerService extends CoreContentLinksModuleListHandler {
|
||||
|
||||
name = 'AddonModBookListLinkHandler';
|
||||
|
||||
constructor() {
|
||||
super('AddonModBook', 'book');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
* If not defined, defaults to true.
|
||||
*
|
||||
* @return Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return AddonModBook.instance.isPluginEnabled();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonModBookListLinkHandler extends makeSingleton(AddonModBookListLinkHandlerService) {}
|
|
@ -0,0 +1,94 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { AddonModBookIndexComponent } from '../../components/index';
|
||||
import { AddonModBook } from '../book';
|
||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
|
||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Handler to support book modules.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModBookModuleHandlerService implements CoreCourseModuleHandler {
|
||||
|
||||
static readonly PAGE_NAME = 'mod_book';
|
||||
|
||||
name = 'AddonModBook';
|
||||
modName = 'book';
|
||||
|
||||
supportedFeatures = {
|
||||
[CoreConstants.FEATURE_MOD_ARCHETYPE]: CoreConstants.MOD_ARCHETYPE_RESOURCE,
|
||||
[CoreConstants.FEATURE_GROUPS]: false,
|
||||
[CoreConstants.FEATURE_GROUPINGS]: false,
|
||||
[CoreConstants.FEATURE_MOD_INTRO]: true,
|
||||
[CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
|
||||
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: false,
|
||||
[CoreConstants.FEATURE_GRADE_OUTCOMES]: false,
|
||||
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
|
||||
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return AddonModBook.instance.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data required to display the module in the course contents view.
|
||||
*
|
||||
* @param module The module object.
|
||||
* @param courseId The course ID.
|
||||
* @param sectionId The section ID.
|
||||
* @return Data to render the module.
|
||||
*/
|
||||
getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData {
|
||||
return {
|
||||
icon: CoreCourse.instance.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
|
||||
title: module.name,
|
||||
class: 'addon-mod_book-handler',
|
||||
showDownloadButton: true,
|
||||
action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void {
|
||||
options = options || {};
|
||||
options.params = options.params || {};
|
||||
Object.assign(options.params, { module });
|
||||
const routeParams = '/' + courseId + '/' + module.id;
|
||||
|
||||
CoreNavigator.instance.navigateToSitePath(AddonModBookModuleHandlerService.PAGE_NAME + routeParams, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component to render the module. This is needed to support singleactivity course format.
|
||||
* The component returned must implement CoreCourseModuleMainComponent.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
async getMainComponent(): Promise<Type<unknown> | undefined> {
|
||||
return AddonModBookIndexComponent;
|
||||
}
|
||||
|
||||
}
|
||||
export class AddonModBookModuleHandler extends makeSingleton(AddonModBookModuleHandlerService) {}
|
|
@ -0,0 +1,86 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler';
|
||||
import { CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModBook, AddonModBookProvider } from '../book';
|
||||
|
||||
/**
|
||||
* Handler to prefetch books.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModBookPrefetchHandlerService extends CoreCourseResourcePrefetchHandlerBase {
|
||||
|
||||
name = 'AddonModBook';
|
||||
modName = 'book';
|
||||
component = AddonModBookProvider.COMPONENT;
|
||||
updatesNames = /^configuration$|^.*files$|^entries$/;
|
||||
|
||||
/**
|
||||
* Download or prefetch the content.
|
||||
*
|
||||
* @param module The module object returned by WS.
|
||||
* @param courseId Course ID.
|
||||
* @param prefetch True to prefetch, false to download right away.
|
||||
* @return Promise resolved when all content is downloaded. Data returned is not reliable.
|
||||
*/
|
||||
async downloadOrPrefetch(module: CoreCourseWSModule, courseId: number, prefetch?: boolean): Promise<void> {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
promises.push(super.downloadOrPrefetch(module, courseId, prefetch));
|
||||
// Ignore errors since this WS isn't available in some Moodle versions.
|
||||
promises.push(CoreUtils.instance.ignoreErrors(AddonModBook.instance.getBook(courseId, module.id)));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns module intro files.
|
||||
*
|
||||
* @param module The module object returned by WS.
|
||||
* @param courseId Course ID.
|
||||
* @return Promise resolved with list of intro files.
|
||||
*/
|
||||
async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
|
||||
const book = await CoreUtils.instance.ignoreErrors(AddonModBook.instance.getBook(courseId, module.id));
|
||||
|
||||
return this.getIntroFilesFromInstance(module, book);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the prefetched content.
|
||||
*
|
||||
* @param moduleId The module ID.
|
||||
* @param courseId Course ID the module belongs to.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
async invalidateContent(moduleId: number, courseId: number): Promise<void> {
|
||||
await AddonModBook.instance.invalidateContent(moduleId, courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return AddonModBook.instance.isPluginEnabled();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonModBookPrefetchHandler extends makeSingleton(AddonModBookPrefetchHandlerService) {}
|
|
@ -0,0 +1,79 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { CoreTagFeedComponent } from '@features/tag/components/feed/feed';
|
||||
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
|
||||
import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModBook } from '../book';
|
||||
|
||||
/**
|
||||
* Handler to support tags.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModBookTagAreaHandlerService implements CoreTagAreaHandler {
|
||||
|
||||
name = 'AddonModBookTagAreaHandler';
|
||||
type = 'mod_book/book_chapters';
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return AddonModBook.instance.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the rendered content of a tag index and returns the items.
|
||||
*
|
||||
* @param content Rendered content.
|
||||
* @return Area items (or promise resolved with the items).
|
||||
*/
|
||||
async parseContent(content: string): Promise<CoreTagFeedElement[]> {
|
||||
const items = CoreTagHelper.instance.parseFeedContent(content);
|
||||
|
||||
// Find module ids of the returned books, they are needed by the link delegate.
|
||||
await Promise.all(items.map((item) => {
|
||||
const params = item.url ? CoreUrlUtils.instance.extractUrlParams(item.url) : {};
|
||||
if (params.b && !params.id) {
|
||||
const bookId = parseInt(params.b, 10);
|
||||
|
||||
return CoreCourse.instance.getModuleBasicInfoByInstance(bookId, 'book').then((module) => {
|
||||
item.url += '&id=' + module.id;
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component to use to display items.
|
||||
*
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||
return CoreTagFeedComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AddonModBookTagAreaHandler extends makeSingleton(AddonModBookTagAreaHandlerService) {}
|
|
@ -14,11 +14,13 @@
|
|||
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AddonModBookModule } from './book/book.module';
|
||||
import { AddonModLessonModule } from './lesson/lesson.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
AddonModBookModule,
|
||||
AddonModLessonModule,
|
||||
],
|
||||
providers: [],
|
||||
|
|
|
@ -77,12 +77,18 @@ export class CoreConstants {
|
|||
static readonly OUTDATED = 'outdated';
|
||||
static readonly NOT_DOWNLOADABLE = 'notdownloadable';
|
||||
|
||||
// Download / prefetch status icon. @todo
|
||||
static readonly DOWNLOADED_ICON = 'cloud-done';
|
||||
static readonly DOWNLOADING_ICON = 'spinner';
|
||||
static readonly NOT_DOWNLOADED_ICON = 'cloud-download';
|
||||
static readonly OUTDATED_ICON = 'fas-redo-alt';
|
||||
static readonly NOT_DOWNLOADABLE_ICON = '';
|
||||
|
||||
// General download and sync icons.
|
||||
static readonly ICON_LOADING = 'spinner';
|
||||
static readonly ICON_REFRESH = 'fas-redo-alt';
|
||||
static readonly ICON_SYNC = 'fas-sync-alt';
|
||||
|
||||
// Constants from Moodle's resourcelib.
|
||||
static readonly RESOURCELIB_DISPLAY_AUTO = 0; // Try the best way.
|
||||
static readonly RESOURCELIB_DISPLAY_EMBED = 1; // Display using object tag.
|
||||
|
|
|
@ -58,7 +58,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
// Data for context menu.
|
||||
externalUrl?: string; // External URL to open in browser.
|
||||
description?: string; // Module description.
|
||||
refreshIcon = 'spinner'; // Refresh icon, normally spinner or refresh.
|
||||
refreshIcon = CoreConstants.ICON_LOADING; // Refresh icon, normally spinner or refresh.
|
||||
prefetchStatusIcon?: string; // Used when calling fillContextMenu.
|
||||
prefetchStatus?: string; // Used when calling fillContextMenu.
|
||||
prefetchText?: string; // Used when calling fillContextMenu.
|
||||
|
@ -132,14 +132,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
return;
|
||||
}
|
||||
|
||||
this.refreshIcon = 'spinner';
|
||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
||||
|
||||
try {
|
||||
await CoreUtils.instance.ignoreErrors(this.invalidateContent());
|
||||
|
||||
await this.loadContent(true);
|
||||
} finally {
|
||||
this.refreshIcon = 'fas-redo';
|
||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
this.refreshIcon = 'fas-redo';
|
||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,9 +64,9 @@ export class CoreH5PFramework {
|
|||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
const whereAndParams = db.getInOrEqual(libraryIds);
|
||||
whereAndParams[0] = 'mainlibraryid ' + whereAndParams[0];
|
||||
whereAndParams.sql = 'mainlibraryid ' + whereAndParams.sql;
|
||||
|
||||
await db.updateRecordsWhere(CONTENT_TABLE_NAME, { filtered: null }, whereAndParams[0], whereAndParams[1]);
|
||||
await db.updateRecordsWhere(CONTENT_TABLE_NAME, { filtered: null }, whereAndParams.sql, whereAndParams.params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -919,4 +919,3 @@ type LibraryDependency = {
|
|||
type LibraryAddonDBData = Omit<CoreH5PLibraryAddonData, 'addTo'> & {
|
||||
addTo: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -2196,15 +2196,16 @@ export class CoreFilepoolProvider {
|
|||
}
|
||||
|
||||
const fileIds = items.map((item) => item.fileId);
|
||||
|
||||
const whereAndParams = db.getInOrEqual(fileIds);
|
||||
|
||||
whereAndParams[0] = 'fileId ' + whereAndParams[0];
|
||||
whereAndParams.sql = 'fileId ' + whereAndParams.sql;
|
||||
|
||||
if (onlyUnknown) {
|
||||
whereAndParams[0] += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')';
|
||||
whereAndParams.sql += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')';
|
||||
}
|
||||
|
||||
await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, whereAndParams[0], whereAndParams[1]);
|
||||
await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, whereAndParams.sql, whereAndParams.params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue