MOBILE-3644 glossary: Migrate services
parent
c9b99927c4
commit
010475b790
|
@ -0,0 +1,69 @@
|
|||
// (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, Type } from '@angular/core';
|
||||
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 { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
|
||||
import { CoreCronDelegate } from '@services/cron';
|
||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/glossary';
|
||||
import { AddonModGlossaryProvider } from './services/glossary';
|
||||
import { AddonModGlossaryHelperProvider } from './services/glossary-helper';
|
||||
import { AddonModGlossaryOfflineProvider } from './services/glossary-offline';
|
||||
import { AddonModGlossarySyncProvider } from './services/glossary-sync';
|
||||
import { AddonModGlossaryEditLinkHandler } from './services/handlers/edit-link';
|
||||
import { AddonModGlossaryEntryLinkHandler } from './services/handlers/entry-link';
|
||||
import { AddonModGlossaryIndexLinkHandler } from './services/handlers/index-link';
|
||||
import { AddonModGlossaryListLinkHandler } from './services/handlers/list-link';
|
||||
import { AddonModGlossaryModuleHandler } from './services/handlers/module';
|
||||
import { AddonModGlossaryPrefetchHandler } from './services/handlers/prefetch';
|
||||
import { AddonModGlossarySyncCronHandler } from './services/handlers/sync-cron';
|
||||
import { AddonModGlossaryTagAreaHandler } from './services/handlers/tag-area';
|
||||
|
||||
export const ADDON_MOD_GLOSSARY_SERVICES: Type<unknown>[] = [
|
||||
AddonModGlossaryProvider,
|
||||
AddonModGlossaryOfflineProvider,
|
||||
AddonModGlossarySyncProvider,
|
||||
AddonModGlossaryHelperProvider,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: CORE_SITE_SCHEMAS,
|
||||
useValue: [SITE_SCHEMA, OFFLINE_SITE_SCHEMA],
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () => {
|
||||
CoreCourseModuleDelegate.registerHandler(AddonModGlossaryModuleHandler.instance);
|
||||
CoreCourseModulePrefetchDelegate.registerHandler(AddonModGlossaryPrefetchHandler.instance);
|
||||
CoreCronDelegate.register(AddonModGlossarySyncCronHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonModGlossaryIndexLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonModGlossaryListLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonModGlossaryEditLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonModGlossaryEntryLinkHandler.instance);
|
||||
CoreTagAreaDelegate.registerHandler(AddonModGlossaryTagAreaHandler.instance);
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AddonModGlossaryModule {}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"addentry": "Add a new entry",
|
||||
"aliases": "Keyword(s)",
|
||||
"attachment": "Attachment",
|
||||
"browsemode": "Browse entries",
|
||||
"byalphabet": "Alphabetically",
|
||||
"byauthor": "Group by author",
|
||||
"bycategory": "Group by category",
|
||||
"bynewestfirst": "Newest first",
|
||||
"byrecentlyupdated": "Recently updated",
|
||||
"bysearch": "Search",
|
||||
"cannoteditentry": "Cannot edit entry",
|
||||
"casesensitive": "This entry is case sensitive",
|
||||
"categories": "Categories",
|
||||
"concept": "Concept",
|
||||
"definition": "Definition",
|
||||
"entriestobesynced": "Entries to be synced",
|
||||
"entrypendingapproval": "This entry is pending approval.",
|
||||
"entryusedynalink": "This entry should be automatically linked",
|
||||
"errconceptalreadyexists": "This concept already exists. No duplicates allowed in this glossary.",
|
||||
"errorloadingentries": "An error occurred while loading entries.",
|
||||
"errorloadingentry": "An error occurred while loading the entry.",
|
||||
"errorloadingglossary": "An error occurred while loading the glossary.",
|
||||
"fillfields": "Concept and definition are mandatory fields.",
|
||||
"fullmatch": "Match whole words only",
|
||||
"linking": "Auto-linking",
|
||||
"modulenameplural": "Glossaries",
|
||||
"noentriesfound": "No entries were found.",
|
||||
"searchquery": "Search query",
|
||||
"tagarea_glossary_entries": "Glossary entries"
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// (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 AddonModGlossaryProvider.
|
||||
*/
|
||||
export const ENTRIES_TABLE_NAME = 'addon_mod_glossary_entry_glossaryid';
|
||||
export const SITE_SCHEMA: CoreSiteSchema = {
|
||||
name: 'AddonModGlossaryProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: ENTRIES_TABLE_NAME,
|
||||
columns: [
|
||||
{
|
||||
name: 'entryid',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
name: 'glossaryid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'pagefrom',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Database variables for AddonModGlossaryOfflineProvider.
|
||||
*/
|
||||
export const OFFLINE_ENTRIES_TABLE_NAME = 'addon_mod_glossary_entrues';
|
||||
export const OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
|
||||
name: 'AddonModGlossaryOfflineProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: OFFLINE_ENTRIES_TABLE_NAME,
|
||||
columns: [
|
||||
{
|
||||
name: 'glossaryid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'concept',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'definition',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'definitionformat',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'userid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'timecreated',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'options',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'attachments',
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['glossaryid', 'concept', 'timecreated'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Glossary entry to get glossaryid from entryid.
|
||||
*/
|
||||
export type AddonModGlossaryEntryDBRecord = {
|
||||
entryid: number;
|
||||
glossaryid: number;
|
||||
pagefrom: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Glossary offline entry.
|
||||
*/
|
||||
export type AddonModGlossaryOfflineEntryDBRecord = {
|
||||
glossaryid: number;
|
||||
courseid: number;
|
||||
concept: string;
|
||||
definition: string;
|
||||
definitionformat: string;
|
||||
userid: number;
|
||||
timecreated: number;
|
||||
options: string;
|
||||
attachments: string;
|
||||
};
|
|
@ -0,0 +1,112 @@
|
|||
// (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 { FileEntry } from '@ionic-native/file/ngx';
|
||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AddonModGlossaryOffline } from './glossary-offline';
|
||||
import { AddonModGlossaryNewEntry, AddonModGlossaryNewEntryWithFiles } from './glossary';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
|
||||
/**
|
||||
* Helper to gather some common functions for glossary.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryHelperProvider {
|
||||
|
||||
/**
|
||||
* Delete stored attachment files for a new entry.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param entryName The name of the entry.
|
||||
* @param timeCreated The time the entry was created.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when deleted.
|
||||
*/
|
||||
async deleteStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise<void> {
|
||||
const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
|
||||
|
||||
await CoreUtils.ignoreErrors(CoreFile.removeDir(folderPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of stored attachment files for a new entry. See AddonModGlossaryHelperProvider#storeFiles.
|
||||
*
|
||||
* @param glossaryId lossary ID.
|
||||
* @param entryName The name of the entry.
|
||||
* @param timeCreated The time the entry was created.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the files.
|
||||
*/
|
||||
async getStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise<FileEntry[]> {
|
||||
const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
|
||||
|
||||
return CoreFileUploader.getStoredFiles(folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the data of an entry has changed.
|
||||
*
|
||||
* @param entry Current data.
|
||||
* @param files Files attached.
|
||||
* @param original Original content.
|
||||
* @return True if data has changed, false otherwise.
|
||||
*/
|
||||
hasEntryDataChanged(
|
||||
entry: AddonModGlossaryNewEntry,
|
||||
files: (CoreWSExternalFile | FileEntry)[],
|
||||
original?: AddonModGlossaryNewEntryWithFiles,
|
||||
): boolean {
|
||||
if (!original || typeof original.concept == 'undefined') {
|
||||
// There is no original data.
|
||||
return !!(entry.definition || entry.concept || files.length > 0);
|
||||
}
|
||||
|
||||
if (original.definition != entry.definition || original.concept != entry.concept) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CoreFileUploader.areFileListDifferent(files, original.files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of files (either online files or local files), store the local files in a local folder
|
||||
* to be submitted later.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param entryName The name of the entry.
|
||||
* @param timeCreated The time the entry was created.
|
||||
* @param files List of files.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
async storeFiles(
|
||||
glossaryId: number,
|
||||
entryName: string,
|
||||
timeCreated: number,
|
||||
files: (CoreWSExternalFile | FileEntry)[],
|
||||
siteId?: string,
|
||||
): Promise<CoreFileUploaderStoreFilesResult> {
|
||||
// Get the folder where to store the files.
|
||||
const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
|
||||
|
||||
return CoreFileUploader.storeFilesToUpload(folderPath, files);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryHelper = makeSingleton(AddonModGlossaryHelperProvider);
|
|
@ -0,0 +1,258 @@
|
|||
// (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 { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary';
|
||||
import { AddonModGlossaryDiscardedEntry, AddonModGlossaryEntryOption } from './glossary';
|
||||
|
||||
/**
|
||||
* Service to handle offline glossary.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryOfflineProvider {
|
||||
|
||||
/**
|
||||
* Delete a new entry.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param concept Glossary entry concept.
|
||||
* @param timeCreated The time the entry was created.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if deleted, rejected if failure.
|
||||
*/
|
||||
async deleteNewEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
||||
glossaryid: glossaryId,
|
||||
concept: concept,
|
||||
timecreated: timeCreated,
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(OFFLINE_ENTRIES_TABLE_NAME, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the stored new entries from all the glossaries.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with entries.
|
||||
*/
|
||||
async getAllNewEntries(siteId?: string): Promise<AddonModGlossaryOfflineEntry[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME);
|
||||
|
||||
return records.map(record => this.parseRecord(record));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a stored new entry.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param concept Glossary entry concept.
|
||||
* @param timeCreated The time the entry was created.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with entry.
|
||||
*/
|
||||
async getNewEntry(
|
||||
glossaryId: number,
|
||||
concept: string,
|
||||
timeCreated: number,
|
||||
siteId?: string,
|
||||
): Promise<AddonModGlossaryOfflineEntry> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
||||
glossaryid: glossaryId,
|
||||
concept: concept,
|
||||
timecreated: timeCreated,
|
||||
};
|
||||
|
||||
const record = await site.getDb().getRecord<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
|
||||
|
||||
return this.parseRecord(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the stored add entry data from a certain glossary.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @param userId User the entries belong to. If not defined, current user in site.
|
||||
* @return Promise resolved with entries.
|
||||
*/
|
||||
async getGlossaryNewEntries(glossaryId: number, siteId?: string, userId?: number): Promise<AddonModGlossaryOfflineEntry[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
||||
glossaryid: glossaryId,
|
||||
userid: userId || site.getUserId(),
|
||||
};
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
|
||||
|
||||
return records.map(record => this.parseRecord(record));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a concept is used offline.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param concept Concept to check.
|
||||
* @param timeCreated Time of the entry we are editing.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with true if concept is found, false otherwise.
|
||||
*/
|
||||
async isConceptUsed(glossaryId: number, concept: string, timeCreated?: number, siteId?: string): Promise<boolean> {
|
||||
try {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
||||
glossaryid: glossaryId,
|
||||
concept: concept,
|
||||
};
|
||||
|
||||
const entries =
|
||||
await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
|
||||
|
||||
if (!entries.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entries.length > 1 || !timeCreated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there's only one entry, check that is not the one we are editing.
|
||||
return CoreUtils.promiseFails(this.getNewEntry(glossaryId, concept, timeCreated, siteId));
|
||||
} catch {
|
||||
// No offline data found, return false.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new entry to be sent later.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param concept Glossary entry concept.
|
||||
* @param definition Glossary entry concept definition.
|
||||
* @param courseId Course ID of the glossary.
|
||||
* @param options Options for the entry.
|
||||
* @param attachments Result of CoreFileUploaderProvider#storeFilesToUpload for attachments.
|
||||
* @param timeCreated The time the entry was created. If not defined, current time.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @param userId User the entry belong to. If not defined, current user in site.
|
||||
* @param discardEntry The entry provided will be discarded if found.
|
||||
* @return Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
async addNewEntry(
|
||||
glossaryId: number,
|
||||
concept: string,
|
||||
definition: string,
|
||||
courseId: number,
|
||||
options?: Record<string, AddonModGlossaryEntryOption>,
|
||||
attachments?: CoreFileUploaderStoreFilesResult,
|
||||
timeCreated?: number,
|
||||
siteId?: string,
|
||||
userId?: number,
|
||||
discardEntry?: AddonModGlossaryDiscardedEntry,
|
||||
): Promise<false> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const entry: AddonModGlossaryOfflineEntryDBRecord = {
|
||||
glossaryid: glossaryId,
|
||||
courseid: courseId,
|
||||
concept: concept,
|
||||
definition: definition,
|
||||
definitionformat: 'html',
|
||||
options: JSON.stringify(options || {}),
|
||||
attachments: JSON.stringify(attachments),
|
||||
userid: userId || site.getUserId(),
|
||||
timecreated: timeCreated || Date.now(),
|
||||
};
|
||||
|
||||
// If editing an offline entry, delete previous first.
|
||||
if (discardEntry) {
|
||||
await this.deleteNewEntry(glossaryId, discardEntry.concept, discardEntry.timecreated, site.getId());
|
||||
}
|
||||
|
||||
await site.getDb().insertRecord(OFFLINE_ENTRIES_TABLE_NAME, entry);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the folder where to store files for offline attachments in a glossary.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the path.
|
||||
*/
|
||||
async getGlossaryFolder(glossaryId: number, siteId?: string): Promise<string> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const siteFolderPath = CoreFile.getSiteFolder(site.getId());
|
||||
const folderPath = 'offlineglossary/' + glossaryId;
|
||||
|
||||
return CoreTextUtils.concatenatePaths(siteFolderPath, folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the folder where to store files for a new offline entry.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param concept The name of the entry.
|
||||
* @param timeCreated Time to allow duplicated entries.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the path.
|
||||
*/
|
||||
async getEntryFolder(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<string> {
|
||||
const folderPath = await this.getGlossaryFolder(glossaryId, siteId);
|
||||
|
||||
return CoreTextUtils.concatenatePaths(folderPath, 'newentry_' + concept + '_' + timeCreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse "options" and "attachments" columns of a fetched record.
|
||||
*
|
||||
* @param records Record object
|
||||
* @return Record object with columns parsed.
|
||||
*/
|
||||
protected parseRecord(record: AddonModGlossaryOfflineEntryDBRecord): AddonModGlossaryOfflineEntry {
|
||||
return Object.assign(record, {
|
||||
options: <Record<string, AddonModGlossaryEntryOption>> CoreTextUtils.parseJSON(record.options),
|
||||
attachments: record.attachments ?
|
||||
<CoreFileUploaderStoreFilesResult> CoreTextUtils.parseJSON(record.attachments) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryOffline = makeSingleton(AddonModGlossaryOfflineProvider);
|
||||
|
||||
/**
|
||||
* Glossary offline entry with parsed data.
|
||||
*/
|
||||
export type AddonModGlossaryOfflineEntry = Omit<AddonModGlossaryOfflineEntryDBRecord, 'options'|'attachments'> & {
|
||||
options: Record<string, AddonModGlossaryEntryOption>;
|
||||
attachments?: CoreFileUploaderStoreFilesResult;
|
||||
};
|
|
@ -0,0 +1,368 @@
|
|||
// (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 { ContextLevel } from '@/core/constants';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FileEntry } from '@ionic-native/file/ngx';
|
||||
import { CoreSyncBlockedError } from '@classes/base-sync';
|
||||
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
|
||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
|
||||
import { CoreRatingSync } from '@features/rating/services/rating-sync';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { AddonModGlossary, AddonModGlossaryProvider } from './glossary';
|
||||
import { AddonModGlossaryHelper } from './glossary-helper';
|
||||
import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from './glossary-offline';
|
||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
|
||||
|
||||
/**
|
||||
* Service to sync glossaries.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModGlossarySyncResult> {
|
||||
|
||||
static readonly AUTO_SYNCED = 'addon_mod_glossary_autom_synced';
|
||||
|
||||
protected componentTranslatableString = 'glossary';
|
||||
|
||||
constructor() {
|
||||
super('AddonModGlossarySyncProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to synchronize all the glossaries in a certain site or in all sites.
|
||||
*
|
||||
* @param siteId Site ID to sync. If not defined, sync all sites.
|
||||
* @param force Wether to force sync not depending on last execution.
|
||||
* @return Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
syncAllGlossaries(siteId?: string, force?: boolean): Promise<void> {
|
||||
return this.syncOnSites('all glossaries', this.syncAllGlossariesFunc.bind(this, !!force), siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all glossaries on a site.
|
||||
*
|
||||
* @param force Wether to force sync not depending on last execution.
|
||||
* @param siteId Site ID to sync.
|
||||
* @return Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
protected async syncAllGlossariesFunc(force: boolean, siteId: string): Promise<void> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
await Promise.all([
|
||||
this.syncAllGlossariesEntries(force, siteId),
|
||||
this.syncRatings(undefined, force, siteId),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync entried of all glossaries on a site.
|
||||
*
|
||||
* @param force Wether to force sync not depending on last execution.
|
||||
* @param siteId Site ID to sync.
|
||||
* @return Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
protected async syncAllGlossariesEntries(force: boolean, siteId: string): Promise<void> {
|
||||
const entries = await AddonModGlossaryOffline.getAllNewEntries(siteId);
|
||||
|
||||
// Do not sync same glossary twice.
|
||||
const treated: Record<number, boolean> = {};
|
||||
|
||||
await Promise.all(entries.map(async (entry) => {
|
||||
if (treated[entry.glossaryid]) {
|
||||
return;
|
||||
}
|
||||
|
||||
treated[entry.glossaryid] = true;
|
||||
|
||||
const result = force ?
|
||||
await this.syncGlossaryEntries(entry.glossaryid, entry.userid, siteId) :
|
||||
await this.syncGlossaryEntriesIfNeeded(entry.glossaryid, entry.userid, siteId);
|
||||
|
||||
if (result?.updated) {
|
||||
// Sync successful, send event.
|
||||
CoreEvents.trigger(AddonModGlossarySyncProvider.AUTO_SYNCED, {
|
||||
glossaryId: entry.glossaryid,
|
||||
userId: entry.userid,
|
||||
warnings: result.warnings,
|
||||
}, siteId);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a glossary only if a certain time has passed since the last time.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param userId User the entry belong to.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the glossary is synced or if it doesn't need to be synced.
|
||||
*/
|
||||
async syncGlossaryEntriesIfNeeded(
|
||||
glossaryId: number,
|
||||
userId: number,
|
||||
siteId?: string,
|
||||
): Promise<AddonModGlossarySyncResult | undefined> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
const syncId = this.getGlossarySyncId(glossaryId, userId);
|
||||
|
||||
const needed = await this.isSyncNeeded(syncId, siteId);
|
||||
|
||||
if (needed) {
|
||||
return this.syncGlossaryEntries(glossaryId, userId, siteId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize all offline entries of a glossary.
|
||||
*
|
||||
* @param glossaryId Glossary ID to be synced.
|
||||
* @param userId User the entries belong to.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
syncGlossaryEntries(glossaryId: number, userId?: number, siteId?: string): Promise<AddonModGlossarySyncResult> {
|
||||
userId = userId || CoreSites.getCurrentSiteUserId();
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
const syncId = this.getGlossarySyncId(glossaryId, userId);
|
||||
if (this.isSyncing(syncId, siteId)) {
|
||||
// There's already a sync ongoing for this glossary, return the promise.
|
||||
return this.getOngoingSync(syncId, siteId)!;
|
||||
}
|
||||
|
||||
// Verify that glossary isn't blocked.
|
||||
if (CoreSync.isBlocked(AddonModGlossaryProvider.COMPONENT, syncId, siteId)) {
|
||||
this.logger.debug('Cannot sync glossary ' + glossaryId + ' because it is blocked.');
|
||||
|
||||
throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
|
||||
}
|
||||
|
||||
this.logger.debug('Try to sync glossary ' + glossaryId + ' for user ' + userId);
|
||||
|
||||
const syncPromise = this.performSyncGlossaryEntries(glossaryId, userId, siteId);
|
||||
|
||||
return this.addOngoingSync(syncId, syncPromise, siteId);
|
||||
}
|
||||
|
||||
protected async performSyncGlossaryEntries(
|
||||
glossaryId: number,
|
||||
userId: number,
|
||||
siteId: string,
|
||||
): Promise<AddonModGlossarySyncResult> {
|
||||
const result: AddonModGlossarySyncResult = {
|
||||
warnings: [],
|
||||
updated: false,
|
||||
};
|
||||
const syncId = this.getGlossarySyncId(glossaryId, userId);
|
||||
|
||||
// Sync offline logs.
|
||||
await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModGlossaryProvider.COMPONENT, glossaryId, siteId));
|
||||
|
||||
// Get offline responses to be sent.
|
||||
const entries = await CoreUtils.ignoreErrors(
|
||||
AddonModGlossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId),
|
||||
<AddonModGlossaryOfflineEntry[]> [],
|
||||
);
|
||||
|
||||
if (!entries.length) {
|
||||
// Nothing to sync.
|
||||
await CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId));
|
||||
|
||||
return result;
|
||||
} else if (!CoreApp.isOnline()) {
|
||||
// Cannot sync in offline.
|
||||
throw new CoreNetworkError();
|
||||
}
|
||||
|
||||
let courseId: number | undefined;
|
||||
|
||||
await Promise.all(entries.map(async (data) => {
|
||||
courseId = courseId || data.courseid;
|
||||
|
||||
try {
|
||||
// First of all upload the attachments (if any).
|
||||
const itemId = await this.uploadAttachments(glossaryId, data, siteId);
|
||||
|
||||
// Now try to add the entry.
|
||||
await AddonModGlossary.addEntryOnline(glossaryId, data.concept, data.definition, data.options, itemId, siteId);
|
||||
|
||||
result.updated = true;
|
||||
|
||||
await this.deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId);
|
||||
} catch (error) {
|
||||
if (!CoreUtils.isWebServiceError(error)) {
|
||||
// Couldn't connect to server, reject.
|
||||
throw error;
|
||||
}
|
||||
|
||||
// The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
|
||||
result.updated = true;
|
||||
|
||||
await this.deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId);
|
||||
|
||||
// Responses deleted, add a warning.
|
||||
result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
|
||||
component: this.componentTranslate,
|
||||
name: data.concept,
|
||||
error: CoreTextUtils.getErrorMessageFromError(error),
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
if (result.updated && courseId) {
|
||||
// Data has been sent to server. Now invalidate the WS calls.
|
||||
try {
|
||||
const glossary = await AddonModGlossary.getGlossaryById(courseId, glossaryId);
|
||||
|
||||
await AddonModGlossary.invalidateGlossaryEntries(glossary, true);
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
// Sync finished, set sync time.
|
||||
await CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize offline ratings.
|
||||
*
|
||||
* @param cmId Course module to be synced. If not defined, sync all glossaries.
|
||||
* @param force Wether to force sync not depending on last execution.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
async syncRatings(cmId?: number, force?: boolean, siteId?: string): Promise<AddonModGlossarySyncResult> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
const results = await CoreRatingSync.syncRatings('mod_glossary', 'entry', ContextLevel.MODULE, cmId, 0, force, siteId);
|
||||
|
||||
let updated = false;
|
||||
const warnings: string[] = [];
|
||||
|
||||
await CoreUtils.allPromises(results.map(async (result) => {
|
||||
if (result.updated.length) {
|
||||
updated = true;
|
||||
|
||||
// Invalidate entry of updated ratings.
|
||||
await Promise.all(result.updated.map((itemId) => AddonModGlossary.invalidateEntry(itemId, siteId)));
|
||||
}
|
||||
|
||||
if (result.warnings.length) {
|
||||
const glossary = await AddonModGlossary.getGlossary(result.itemSet.courseId, result.itemSet.instanceId, { siteId });
|
||||
|
||||
result.warnings.forEach((warning) => {
|
||||
warnings.push(Translate.instant('core.warningofflinedatadeleted', {
|
||||
component: this.componentTranslate,
|
||||
name: glossary.name,
|
||||
error: warning,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
return { updated, warnings };
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a new entry.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param concept Glossary entry concept.
|
||||
* @param timeCreated Time to allow duplicated entries.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when deleted.
|
||||
*/
|
||||
protected async deleteAddEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
|
||||
await Promise.all([
|
||||
AddonModGlossaryOffline.deleteNewEntry(glossaryId, concept, timeCreated, siteId),
|
||||
AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timeCreated, siteId),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload attachments of an offline entry.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param entry Offline entry.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with draftid if uploaded, resolved with 0 if nothing to upload.
|
||||
*/
|
||||
protected async uploadAttachments(glossaryId: number, entry: AddonModGlossaryOfflineEntry, siteId?: string): Promise<number> {
|
||||
if (!entry.attachments) {
|
||||
// No attachments.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Has some attachments to sync.
|
||||
let files: (CoreWSExternalFile | FileEntry)[] = entry.attachments.online || [];
|
||||
|
||||
if (entry.attachments.offline) {
|
||||
// Has offline files.
|
||||
const storedFiles = await CoreUtils.ignoreErrors(
|
||||
AddonModGlossaryHelper.getStoredFiles(glossaryId, entry.concept, entry.timecreated, siteId),
|
||||
[], // Folder not found, no files to add.
|
||||
);
|
||||
|
||||
files = files.concat(storedFiles);
|
||||
}
|
||||
|
||||
return CoreFileUploader.uploadOrReuploadFiles(files, AddonModGlossaryProvider.COMPONENT, glossaryId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of a glossary sync.
|
||||
*
|
||||
* @param glossaryId Glossary ID.
|
||||
* @param userId User the entries belong to.. If not defined, current user.
|
||||
* @return Sync ID.
|
||||
*/
|
||||
protected getGlossarySyncId(glossaryId: number, userId?: number): string {
|
||||
userId = userId || CoreSites.getCurrentSiteUserId();
|
||||
|
||||
return 'glossary#' + glossaryId + '#' + userId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossarySync = makeSingleton(AddonModGlossarySyncProvider);
|
||||
|
||||
/**
|
||||
* Data returned by a glossary sync.
|
||||
*/
|
||||
export type AddonModGlossarySyncResult = {
|
||||
warnings: string[]; // List of warnings.
|
||||
updated: boolean; // Whether some data was sent to the server or offline data was updated.
|
||||
};
|
||||
|
||||
/**
|
||||
* Data passed to AUTO_SYNCED event.
|
||||
*/
|
||||
export type AddonModGlossaryAutoSyncData = {
|
||||
glossaryId: number;
|
||||
userId: number;
|
||||
warnings: string[];
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,78 @@
|
|||
// (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 { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModGlossaryModuleHandlerService } from './module';
|
||||
|
||||
/**
|
||||
* Content links handler for glossary new entry.
|
||||
* Match mod/glossary/edit.php?cmid=6 with a valid data.
|
||||
* Currently it only supports new entry.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryEditLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||
|
||||
name = 'AddonModGlossaryEditLinkHandler';
|
||||
featureName = 'CoreCourseModuleDelegate_AddonModGlossary';
|
||||
pattern = /\/mod\/glossary\/edit\.php.*([?&](cmid)=\d+)/;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||
return [{
|
||||
action: async (siteId: string) => {
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
|
||||
const cmId = Number(params.cmid);
|
||||
|
||||
try {
|
||||
const module = await CoreCourse.getModuleBasicInfo(cmId, siteId);
|
||||
|
||||
await CoreNavigator.navigateToSitePath(
|
||||
AddonModGlossaryModuleHandlerService.PAGE_NAME + '/edit/0',
|
||||
{
|
||||
params: {
|
||||
cmId: module.id,
|
||||
courseId: module.course,
|
||||
},
|
||||
siteId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true);
|
||||
} finally {
|
||||
// Just in case. In fact we need to dismiss the modal before showing a toast or error message.
|
||||
modal.dismiss();
|
||||
}
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
|
||||
return typeof params.cmid != 'undefined';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryEditLinkHandler = makeSingleton(AddonModGlossaryEditLinkHandlerService);
|
|
@ -0,0 +1,75 @@
|
|||
// (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 { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModGlossary } from '../glossary';
|
||||
import { AddonModGlossaryModuleHandlerService } from './module';
|
||||
|
||||
/**
|
||||
* Handler to treat links to glossary entries.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryEntryLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||
|
||||
name = 'AddonModGlossaryEntryLinkHandler';
|
||||
featureName = 'CoreCourseModuleDelegate_AddonModGlossary';
|
||||
pattern = /\/mod\/glossary\/(showentry|view)\.php.*([&?](eid|g|mode|hook)=\d+)/;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||
return [{
|
||||
action: async (siteId: string) => {
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
|
||||
try {
|
||||
const entryId = params.mode == 'entry' ? Number(params.hook) : Number(params.eid);
|
||||
|
||||
const response = await AddonModGlossary.getEntry(entryId, { siteId });
|
||||
|
||||
const module = await CoreCourse.getModuleBasicInfoByInstance(
|
||||
response.entry.glossaryid,
|
||||
'glossary',
|
||||
siteId,
|
||||
);
|
||||
|
||||
await CoreNavigator.navigateToSitePath(
|
||||
AddonModGlossaryModuleHandlerService.PAGE_NAME + `/entry/${entryId}`,
|
||||
{
|
||||
params: {
|
||||
cmId: module.id,
|
||||
courseId: module.course,
|
||||
},
|
||||
siteId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryEntryLinkHandler = makeSingleton(AddonModGlossaryEntryLinkHandlerService);
|
|
@ -0,0 +1,33 @@
|
|||
// (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 { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Handler to treat links to glossary index.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
|
||||
|
||||
name = 'AddonModGlossaryIndexLinkHandler';
|
||||
|
||||
constructor() {
|
||||
super('AddonModGlossary', 'glossary', 'g');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryIndexLinkHandler = makeSingleton(AddonModGlossaryIndexLinkHandlerService);
|
|
@ -0,0 +1,33 @@
|
|||
// (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';
|
||||
|
||||
/**
|
||||
* Handler to treat links to glossary list page.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryListLinkHandlerService extends CoreContentLinksModuleListHandler {
|
||||
|
||||
name = 'AddonModGlossaryListLinkHandler';
|
||||
|
||||
constructor() {
|
||||
super('AddonModGlossary', 'glossary');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryListLinkHandler = makeSingleton(AddonModGlossaryListLinkHandlerService);
|
|
@ -0,0 +1,92 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
|
||||
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
|
||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModGlossaryIndexComponent } from '../../components/index/index';
|
||||
|
||||
/**
|
||||
* Handler to support glossary modules.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryModuleHandlerService implements CoreCourseModuleHandler {
|
||||
|
||||
static readonly PAGE_NAME = 'mod_glossary';
|
||||
|
||||
name = 'AddonModGlossary';
|
||||
modName = 'glossary';
|
||||
|
||||
supportedFeatures = {
|
||||
[CoreConstants.FEATURE_GROUPS]: false,
|
||||
[CoreConstants.FEATURE_GROUPINGS]: false,
|
||||
[CoreConstants.FEATURE_MOD_INTRO]: true,
|
||||
[CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
|
||||
[CoreConstants.FEATURE_COMPLETION_HAS_RULES]: true,
|
||||
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: true,
|
||||
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
|
||||
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
|
||||
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
|
||||
[CoreConstants.FEATURE_RATE]: true,
|
||||
[CoreConstants.FEATURE_PLAGIARISM]: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData {
|
||||
return {
|
||||
icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
|
||||
title: module.name,
|
||||
class: 'addon-mod_glossary-handler',
|
||||
showDownloadButton: true,
|
||||
action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => {
|
||||
options = options || {};
|
||||
options.params = options.params || {};
|
||||
Object.assign(options.params, { module });
|
||||
const routeParams = '/' + courseId + '/' + module.id;
|
||||
|
||||
CoreNavigator.navigateToSitePath(AddonModGlossaryModuleHandlerService.PAGE_NAME + routeParams, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getMainComponent(): Promise<Type<unknown>> {
|
||||
return AddonModGlossaryIndexComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
displayRefresherInSingleActivity(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryModuleHandler = makeSingleton(AddonModGlossaryModuleHandlerService);
|
|
@ -0,0 +1,235 @@
|
|||
// (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 { CoreComments } from '@features/comments/services/comments';
|
||||
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
|
||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
|
||||
import { CoreUser } from '@features/user/services/user';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModGlossary, AddonModGlossaryEntry, AddonModGlossaryGlossary, AddonModGlossaryProvider } from '../glossary';
|
||||
import { AddonModGlossarySync, AddonModGlossarySyncResult } from '../glossary-sync';
|
||||
|
||||
/**
|
||||
* Handler to prefetch forums.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase {
|
||||
|
||||
name = 'AddonModGlossary';
|
||||
modName = 'glossary';
|
||||
component = AddonModGlossaryProvider.COMPONENT;
|
||||
updatesNames = /^configuration$|^.*files$|^entries$/;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
|
||||
try {
|
||||
const glossary = await AddonModGlossary.getGlossary(courseId, module.id);
|
||||
|
||||
const entries = await AddonModGlossary.fetchAllEntries(
|
||||
AddonModGlossary.getEntriesByLetter.bind(AddonModGlossary.instance, glossary.id, 'ALL'),
|
||||
{
|
||||
cmId: module.id,
|
||||
},
|
||||
);
|
||||
|
||||
return this.getFilesFromGlossaryAndEntries(module, glossary, entries);
|
||||
} catch {
|
||||
// Glossary not found, return empty list.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of downloadable files. It includes entry embedded files.
|
||||
*
|
||||
* @param module Module to get the files.
|
||||
* @param glossary Glossary
|
||||
* @param entries Entries of the Glossary.
|
||||
* @return List of Files.
|
||||
*/
|
||||
protected getFilesFromGlossaryAndEntries(
|
||||
module: CoreCourseAnyModuleData,
|
||||
glossary: AddonModGlossaryGlossary,
|
||||
entries: AddonModGlossaryEntry[],
|
||||
): CoreWSExternalFile[] {
|
||||
let files = this.getIntroFilesFromInstance(module, glossary);
|
||||
|
||||
const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
|
||||
|
||||
// Get entries files.
|
||||
entries.forEach((entry) => {
|
||||
files = files.concat(entry.attachments || []);
|
||||
|
||||
if (getInlineFiles && entry.definitioninlinefiles && entry.definitioninlinefiles.length) {
|
||||
files = files.concat(entry.definitioninlinefiles);
|
||||
} else if (entry.definition && !getInlineFiles) {
|
||||
files = files.concat(CoreFilepool.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition));
|
||||
}
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
invalidateContent(moduleId: number, courseId: number): Promise<void> {
|
||||
return AddonModGlossary.invalidateContent(moduleId, courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> {
|
||||
return this.prefetchPackage(module, courseId, this.prefetchGlossary.bind(this, module, courseId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch a glossary.
|
||||
*
|
||||
* @param module The module object returned by WS.
|
||||
* @param courseId Course ID the module belongs to.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async prefetchGlossary(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise<void> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
const options = {
|
||||
cmId: module.id,
|
||||
readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
|
||||
siteId,
|
||||
};
|
||||
|
||||
// Prefetch the glossary data.
|
||||
const glossary = await AddonModGlossary.getGlossary(courseId, module.id, { siteId });
|
||||
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
glossary.browsemodes.forEach((mode) => {
|
||||
switch (mode) {
|
||||
case 'letter': // Always done. Look bellow.
|
||||
break;
|
||||
case 'cat':
|
||||
promises.push(AddonModGlossary.fetchAllEntries(
|
||||
AddonModGlossary.getEntriesByCategory.bind(
|
||||
AddonModGlossary.instance,
|
||||
glossary.id,
|
||||
AddonModGlossaryProvider.SHOW_ALL_CATEGORIES,
|
||||
),
|
||||
options,
|
||||
));
|
||||
break;
|
||||
case 'date':
|
||||
promises.push(AddonModGlossary.fetchAllEntries(
|
||||
AddonModGlossary.getEntriesByDate.bind(
|
||||
AddonModGlossary.instance,
|
||||
glossary.id,
|
||||
'CREATION',
|
||||
'DESC',
|
||||
),
|
||||
options,
|
||||
));
|
||||
promises.push(AddonModGlossary.fetchAllEntries(
|
||||
AddonModGlossary.getEntriesByDate.bind(
|
||||
AddonModGlossary.instance,
|
||||
glossary.id,
|
||||
'UPDATE',
|
||||
'DESC',
|
||||
),
|
||||
options,
|
||||
));
|
||||
break;
|
||||
case 'author':
|
||||
promises.push(AddonModGlossary.fetchAllEntries(
|
||||
AddonModGlossary.getEntriesByAuthor.bind(
|
||||
AddonModGlossary.instance,
|
||||
glossary.id,
|
||||
'ALL',
|
||||
'LASTNAME',
|
||||
'ASC',
|
||||
),
|
||||
options,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch all entries to get information from.
|
||||
promises.push(AddonModGlossary.fetchAllEntries(
|
||||
AddonModGlossary.getEntriesByLetter.bind(AddonModGlossary.instance, glossary.id, 'ALL'),
|
||||
options,
|
||||
).then((entries) => {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
const commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
||||
|
||||
entries.forEach((entry) => {
|
||||
// Don't fetch individual entries, it's too many WS calls.
|
||||
if (glossary.allowcomments && commentsEnabled) {
|
||||
promises.push(CoreComments.getComments(
|
||||
'module',
|
||||
glossary.coursemodule,
|
||||
'mod_glossary',
|
||||
entry.id,
|
||||
'glossary_entry',
|
||||
0,
|
||||
siteId,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
const files = this.getFilesFromGlossaryAndEntries(module, glossary, entries);
|
||||
promises.push(CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id));
|
||||
|
||||
// Prefetch user avatars.
|
||||
promises.push(CoreUser.prefetchUserAvatars(entries, 'userpictureurl', siteId));
|
||||
|
||||
return Promise.all(promises);
|
||||
}));
|
||||
|
||||
// Get all categories.
|
||||
promises.push(AddonModGlossary.getAllCategories(glossary.id, options));
|
||||
|
||||
// Prefetch data for link handlers.
|
||||
promises.push(CoreCourse.getModuleBasicInfo(module.id, siteId));
|
||||
promises.push(CoreCourse.getModuleBasicInfoByInstance(glossary.id, 'glossary', siteId));
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise<AddonModGlossarySyncResult> {
|
||||
const results = await Promise.all([
|
||||
AddonModGlossarySync.syncGlossaryEntries(module.instance!, undefined, siteId),
|
||||
AddonModGlossarySync.syncRatings(module.id, undefined, siteId),
|
||||
]);
|
||||
|
||||
return {
|
||||
updated: results[0].updated || results[1].updated,
|
||||
warnings: results[0].warnings.concat(results[1].warnings),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryPrefetchHandler = makeSingleton(AddonModGlossaryPrefetchHandlerService);
|
|
@ -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 { CoreCronHandler } from '@services/cron';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModGlossarySync } from '../glossary-sync';
|
||||
|
||||
/**
|
||||
* Synchronization cron handler.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossarySyncCronHandlerService implements CoreCronHandler {
|
||||
|
||||
name = 'AddonModGlossarySyncCronHandler';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
execute(siteId?: string, force?: boolean): Promise<void> {
|
||||
return AddonModGlossarySync.syncAllGlossaries(siteId, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getInterval(): number {
|
||||
return AddonModGlossarySync.syncInterval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossarySyncCronHandler = makeSingleton(AddonModGlossarySyncCronHandlerService);
|
|
@ -0,0 +1,53 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
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 { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Handler to support tags.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModGlossaryTagAreaHandlerService implements CoreTagAreaHandler {
|
||||
|
||||
name = 'AddonModGlossaryTagAreaHandler';
|
||||
type = 'mod_glossary/glossary_entries';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
parseContent(content: string): CoreTagFeedElement[] {
|
||||
return CoreTagHelper.parseFeedContent(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getComponent(): Type<unknown> {
|
||||
return CoreTagFeedComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModGlossaryTagAreaHandler = makeSingleton(AddonModGlossaryTagAreaHandlerService);
|
|
@ -32,6 +32,7 @@ import { AddonModSurveyModule } from './survey/survey.module';
|
|||
import { AddonModScormModule } from './scorm/scorm.module';
|
||||
import { AddonModChoiceModule } from './choice/choice.module';
|
||||
import { AddonModWikiModule } from './wiki/wiki.module';
|
||||
import { AddonModGlossaryModule } from './glossary/glossary.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -53,6 +54,7 @@ import { AddonModWikiModule } from './wiki/wiki.module';
|
|||
AddonModScormModule,
|
||||
AddonModChoiceModule,
|
||||
AddonModWikiModule,
|
||||
AddonModGlossaryModule,
|
||||
],
|
||||
})
|
||||
export class AddonModModule { }
|
||||
|
|
|
@ -130,7 +130,7 @@ import { ADDON_MOD_DATA_SERVICES } from '@addons/mod/data/data.module';
|
|||
// @todo import { ADDON_MOD_FEEDBACK_SERVICES } from '@addons/mod/feedback/feedback.module';
|
||||
import { ADDON_MOD_FOLDER_SERVICES } from '@addons/mod/folder/folder.module';
|
||||
import { ADDON_MOD_FORUM_SERVICES } from '@addons/mod/forum/forum.module';
|
||||
// @todo import { ADDON_MOD_GLOSSARY_SERVICES } from '@addons/mod/glossary/glossary.module';
|
||||
import { ADDON_MOD_GLOSSARY_SERVICES } from '@addons/mod/glossary/glossary.module';
|
||||
import { ADDON_MOD_H5P_ACTIVITY_SERVICES } from '@addons/mod/h5pactivity/h5pactivity.module';
|
||||
import { ADDON_MOD_IMSCP_SERVICES } from '@addons/mod/imscp/imscp.module';
|
||||
import { ADDON_MOD_LESSON_SERVICES } from '@addons/mod/lesson/lesson.module';
|
||||
|
@ -296,7 +296,7 @@ export class CoreCompileProvider {
|
|||
// @todo ...ADDON_MOD_FEEDBACK_SERVICES,
|
||||
...ADDON_MOD_FOLDER_SERVICES,
|
||||
...ADDON_MOD_FORUM_SERVICES,
|
||||
// @todo ...ADDON_MOD_GLOSSARY_SERVICES,
|
||||
...ADDON_MOD_GLOSSARY_SERVICES,
|
||||
...ADDON_MOD_H5P_ACTIVITY_SERVICES,
|
||||
...ADDON_MOD_IMSCP_SERVICES,
|
||||
...ADDON_MOD_LESSON_SERVICES,
|
||||
|
|
Loading…
Reference in New Issue