MOBILE-2652 glossary: Edit offline entries

main
Noel De Martin 2023-03-29 14:02:26 +02:00
parent 957dece787
commit 3c443a26c4
14 changed files with 343 additions and 148 deletions

View File

@ -696,6 +696,7 @@
"addon.mod_glossary.concept": "glossary",
"addon.mod_glossary.definition": "glossary",
"addon.mod_glossary.deleteentry": "glossary",
"addon.mod_glossary.editentry": "glossary",
"addon.mod_glossary.entriestobesynced": "local_moodlemobileapp",
"addon.mod_glossary.entry": "glossary",
"addon.mod_glossary.entrydeleted": "glossary",

View File

@ -86,17 +86,11 @@ export class AddonModGlossaryEntriesSource extends CoreRoutedItemsManagerSource<
/**
* @inheritdoc
*/
getItemQueryParams(entry: AddonModGlossaryEntryItem): Params {
const params: Params = {
getItemQueryParams(): Params {
return {
cmId: this.CM_ID,
courseId: this.COURSE_ID,
};
if (this.isOfflineEntry(entry)) {
params.concept = entry.concept;
}
return params;
}
/**

View File

@ -45,6 +45,7 @@ import {
AddonModGlossaryProvider,
GLOSSARY_ENTRY_ADDED,
GLOSSARY_ENTRY_DELETED,
GLOSSARY_ENTRY_UPDATED,
} from '../../services/glossary';
import { AddonModGlossaryOfflineEntry } from '../../services/glossary-offline';
import {
@ -149,6 +150,13 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
this.showLoadingAndRefresh(false);
}),
CoreEvents.on(GLOSSARY_ENTRY_UPDATED, ({ glossaryId }) => {
if (this.glossary?.id !== glossaryId) {
return;
}
this.showLoadingAndRefresh(false);
}),
CoreEvents.on(GLOSSARY_ENTRY_DELETED, ({ glossaryId }) => {
if (this.glossary?.id !== glossaryId) {
return;

View File

@ -50,6 +50,10 @@ const routes: Routes = [
path: ':courseId/:cmId/entry/new',
loadChildren: () => import('./glossary-edit-lazy.module').then(m => m.AddonModGlossaryEditLazyModule),
},
{
path: ':courseId/:cmId/entry/:entrySlug/edit',
loadChildren: () => import('./glossary-edit-lazy.module').then(m => m.AddonModGlossaryEditLazyModule),
},
...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile),
...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
];

View File

@ -61,6 +61,11 @@ const mainMenuRoutes: Routes = [
loadChildren: () => import('./glossary-edit-lazy.module').then(m => m.AddonModGlossaryEditLazyModule),
data: { glossaryPathPrefix: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/` },
},
{
path: `${COURSE_CONTENTS_PATH}/${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entrySlug/edit`,
loadChildren: () => import('./glossary-edit-lazy.module').then(m => m.AddonModGlossaryEditLazyModule),
data: { glossaryPathPrefix: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/` },
},
...conditionalRoutes(
[{
path: `${COURSE_CONTENTS_PATH}/${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entrySlug`,

View File

@ -16,6 +16,7 @@
"concept": "Concept",
"definition": "Definition",
"deleteentry": "Delete entry",
"editentry": "Edit entry",
"entriestobesynced": "Entries to be synced",
"entry": "Entry",
"entrydeleted": "Entry deleted",

View File

@ -84,10 +84,17 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
*/
async ngOnInit(): Promise<void> {
try {
const entrySlug = CoreNavigator.getRouteParam<string>('entrySlug');
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
this.handler = new AddonModGlossaryNewFormHandler(this);
if (entrySlug?.startsWith('new-')) {
const timecreated = Number(entrySlug.slice(4));
this.editorExtraParams.timecreated = timecreated;
this.handler = new AddonModGlossaryOfflineFormHandler(this, timecreated);
} else {
this.handler = new AddonModGlossaryNewFormHandler(this);
}
} catch (error) {
CoreDomUtils.showErrorModal(error);
@ -297,82 +304,25 @@ abstract class AddonModGlossaryFormHandler {
}
/**
* Create an offline entry.
* Make sure that the new entry won't create any duplicates.
*
* @param glossary Glossary.
* @param timecreated Time created.
* @param uploadedAttachments Uploaded attachments.
*/
protected async createOfflineEntry(
glossary: AddonModGlossaryGlossary,
timecreated: number,
uploadedAttachments?: CoreFileUploaderStoreFilesResult,
): Promise<void> {
const data = this.page.data;
const options = this.getSaveOptions(glossary);
const definition = CoreTextUtils.formatHtmlLines(data.definition);
if (!glossary.allowduplicatedentries) {
// Check if the entry is duplicated in online or offline mode.
const isUsed = await AddonModGlossary.isConceptUsed(glossary.id, data.concept, {
timeCreated: data.timecreated,
cmId: this.page.cmId,
});
if (isUsed) {
// There's a entry with same name, reject with error message.
throw new CoreError(Translate.instant('addon.mod_glossary.errconceptalreadyexists'));
}
protected async checkDuplicates(glossary: AddonModGlossaryGlossary): Promise<void> {
if (glossary.allowduplicatedentries) {
return;
}
await AddonModGlossaryOffline.addOfflineEntry(
glossary.id,
data.concept,
definition,
this.page.courseId,
options,
uploadedAttachments,
timecreated,
undefined,
undefined,
data,
);
}
/**
* Create an online entry.
*
* @param glossary Glossary.
* @param timecreated Time created.
* @param uploadedAttachmentsId Id of the uploaded attachments.
* @param allowOffline Allow falling back to creating the entry offline.
* @returns Entry id.
*/
protected async createOnlineEntry(
glossary: AddonModGlossaryGlossary,
timecreated: number,
uploadedAttachmentsId?: number,
allowOffline?: boolean,
): Promise<number | false> {
const data = this.page.data;
const options = this.getSaveOptions(glossary);
const definition = CoreTextUtils.formatHtmlLines(data.definition);
const entryId = await AddonModGlossary.addEntry(
glossary.id,
data.concept,
definition,
this.page.courseId,
options,
uploadedAttachmentsId,
{
timeCreated: timecreated,
discardEntry: data,
allowOffline: allowOffline,
checkDuplicates: !glossary.allowduplicatedentries,
},
);
const isUsed = await AddonModGlossary.isConceptUsed(glossary.id, data.concept, {
timeCreated: data.timecreated,
cmId: this.page.cmId,
});
return entryId;
if (isUsed) {
// There's a entry with same name, reject with error message.
throw new CoreError(Translate.instant('addon.mod_glossary.errconceptalreadyexists'));
}
}
/**
@ -402,6 +352,119 @@ abstract class AddonModGlossaryFormHandler {
}
/**
* Helper to manage the form data for an offline entry.
*/
class AddonModGlossaryOfflineFormHandler extends AddonModGlossaryFormHandler {
private timecreated: number;
constructor(page: AddonModGlossaryEditPage, timecreated: number) {
super(page);
this.timecreated = timecreated;
}
/**
* @inheritdoc
*/
async loadData(glossary: AddonModGlossaryGlossary): Promise<void> {
const data = this.page.data;
const entry = await AddonModGlossaryOffline.getOfflineEntry(glossary.id, this.timecreated);
data.concept = entry.concept || '';
data.definition = entry.definition || '';
data.timecreated = entry.timecreated;
if (entry.options) {
data.categories = (entry.options.categories && (<string> entry.options.categories).split(',')) || [];
data.aliases = <string> entry.options.aliases || '';
data.usedynalink = !!entry.options.usedynalink;
if (data.usedynalink) {
data.casesensitive = !!entry.options.casesensitive;
data.fullmatch = !!entry.options.fullmatch;
}
}
// Treat offline attachments if any.
if (entry.attachments?.offline) {
data.attachments = await AddonModGlossaryHelper.getStoredFiles(glossary.id, entry.concept, entry.timecreated);
}
this.page.originalData = {
concept: data.concept,
definition: data.definition,
attachments: data.attachments.slice(),
timecreated: data.timecreated,
categories: data.categories.slice(),
aliases: data.aliases,
usedynalink: data.usedynalink,
casesensitive: data.casesensitive,
fullmatch: data.fullmatch,
};
this.page.definitionControl.setValue(data.definition);
}
/**
* @inheritdoc
*/
async save(glossary: AddonModGlossaryGlossary): Promise<boolean> {
const data = this.page.data;
// Upload attachments first if any.
let offlineAttachments: CoreFileUploaderStoreFilesResult | undefined = undefined;
if (data.attachments.length) {
offlineAttachments = await this.storeAttachments(glossary, data.timecreated);
}
// Save entry data.
await this.updateOfflineEntry(glossary, offlineAttachments);
// Delete the local files from the tmp folder.
CoreFileUploader.clearTmpFiles(data.attachments);
return false;
}
/**
* Update an offline entry.
*
* @param glossary Glossary.
* @param uploadedAttachments Uploaded attachments.
*/
protected async updateOfflineEntry(
glossary: AddonModGlossaryGlossary,
uploadedAttachments?: CoreFileUploaderStoreFilesResult,
): Promise<void> {
const originalData = this.page.originalData;
const data = this.page.data;
const options = this.getSaveOptions(glossary);
const definition = CoreTextUtils.formatHtmlLines(data.definition);
if (!originalData) {
return;
}
await this.checkDuplicates(glossary);
await AddonModGlossaryOffline.updateOfflineEntry(
{
glossaryid: glossary.id,
courseid: this.page.courseId,
concept: originalData.concept,
timecreated: originalData.timecreated,
},
data.concept,
definition,
options,
uploadedAttachments,
);
}
}
/**
* Helper to manage the form data for creating a new entry.
*/
@ -451,14 +514,74 @@ class AddonModGlossaryNewFormHandler extends AddonModGlossaryFormHandler {
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'glossary' });
}
CoreEvents.trigger(AddonModGlossaryProvider.ADD_ENTRY_EVENT, {
glossaryId: glossary.id,
entryId: entryId || undefined,
}, CoreSites.getCurrentSiteId());
return !!entryId;
}
/**
* Create an offline entry.
*
* @param glossary Glossary.
* @param timecreated Time created.
* @param uploadedAttachments Uploaded attachments.
*/
protected async createOfflineEntry(
glossary: AddonModGlossaryGlossary,
timecreated: number,
uploadedAttachments?: CoreFileUploaderStoreFilesResult,
): Promise<void> {
const data = this.page.data;
const options = this.getSaveOptions(glossary);
const definition = CoreTextUtils.formatHtmlLines(data.definition);
await this.checkDuplicates(glossary);
await AddonModGlossaryOffline.addOfflineEntry(
glossary.id,
data.concept,
definition,
this.page.courseId,
timecreated,
options,
uploadedAttachments,
undefined,
undefined,
);
}
/**
* Create an online entry.
*
* @param glossary Glossary.
* @param timecreated Time created.
* @param uploadedAttachmentsId Id of the uploaded attachments.
* @param allowOffline Allow falling back to creating the entry offline.
* @returns Entry id.
*/
protected async createOnlineEntry(
glossary: AddonModGlossaryGlossary,
timecreated: number,
uploadedAttachmentsId?: number,
allowOffline?: boolean,
): Promise<number | false> {
const data = this.page.data;
const options = this.getSaveOptions(glossary);
const definition = CoreTextUtils.formatHtmlLines(data.definition);
const entryId = await AddonModGlossary.addEntry(
glossary.id,
data.concept,
definition,
this.page.courseId,
options,
uploadedAttachmentsId,
{
timeCreated: timecreated,
allowOffline: allowOffline,
checkDuplicates: !glossary.allowduplicatedentries,
},
);
return entryId;
}
}
/**

View File

@ -52,11 +52,16 @@
</core-format-text>
</ion-label>
</ion-item>
<ion-item *ngIf="canDelete">
<ion-item *ngIf="canDelete || canEdit">
<div slot="end">
<ion-button fill="clear" (click)="deleteEntry()" [attr.aria-label]="'addon.mod_glossary.deleteentry' | translate">
<ion-button *ngIf="canDelete" fill="clear" (click)="deleteEntry()"
[attr.aria-label]="'addon.mod_glossary.deleteentry' | translate">
<ion-icon slot="icon-only" name="fas-trash" aria-hidden="true"></ion-icon>
</ion-button>
<ion-button *ngIf="canEdit" fill="clear" (click)="editEntry()"
[attr.aria-label]="'addon.mod_glossary.editentry' | translate">
<ion-icon slot="icon-only" name="fas-pen" aria-hidden="true"></ion-icon>
</ion-button>
</div>
</ion-item>
<div *ngIf="onlineEntry && onlineEntry.attachment">

View File

@ -29,12 +29,14 @@ import { CoreNetwork } from '@services/network';
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { AddonModGlossaryEntriesSource, AddonModGlossaryEntryItem } from '../../classes/glossary-entries-source';
import {
AddonModGlossary,
AddonModGlossaryEntry,
AddonModGlossaryGlossary,
AddonModGlossaryProvider,
GLOSSARY_ENTRY_UPDATED,
} from '../../services/glossary';
/**
@ -54,11 +56,13 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
offlineEntry?: AddonModGlossaryOfflineEntry;
entries!: AddonModGlossaryEntryEntriesSwipeManager;
glossary?: AddonModGlossaryGlossary;
entryUpdatedObserver?: CoreEventObserver;
loaded = false;
showAuthor = false;
showDate = false;
ratingInfo?: CoreRatingInfo;
tagsEnabled = false;
canEdit = false;
canDelete = false;
commentsEnabled = false;
courseId!: number;
@ -75,10 +79,8 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
*/
async ngOnInit(): Promise<void> {
let onlineEntryId: number | null = null;
let offlineEntry: {
concept: string;
timecreated: number;
} | null = null;
let offlineEntryTimeCreated: number | null = null;
try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
@ -97,10 +99,7 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
await this.entries.start();
if (entrySlug.startsWith('new-')) {
offlineEntry = {
concept : CoreNavigator.getRequiredRouteParam<string>('concept'),
timecreated: Number(entrySlug.slice(4)),
};
offlineEntryTimeCreated = Number(entrySlug.slice(4));
} else {
onlineEntryId = Number(entrySlug);
}
@ -111,6 +110,19 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
return;
}
this.entryUpdatedObserver = CoreEvents.on(GLOSSARY_ENTRY_UPDATED, data => {
if (data.glossaryId !== this.glossary?.id) {
return;
}
if (
(this.onlineEntry && this.onlineEntry.id === data.entryId) ||
(this.offlineEntry && this.offlineEntry.timecreated === data.timecreated)
) {
this.doRefresh();
}
});
try {
if (onlineEntryId) {
await this.loadOnlineEntry(onlineEntryId);
@ -120,8 +132,8 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
}
await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(onlineEntryId, this.componentId, this.glossary?.name));
} else if (offlineEntry) {
await this.loadOfflineEntry(offlineEntry.concept, offlineEntry.timecreated);
} else if (offlineEntryTimeCreated) {
await this.loadOfflineEntry(offlineEntryTimeCreated);
}
} finally {
this.loaded = true;
@ -133,6 +145,14 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
*/
ngOnDestroy(): void {
this.entries.destroy();
this.entryUpdatedObserver?.off();
}
/**
* Edit entry.
*/
async editEntry(): Promise<void> {
await CoreNavigator.navigate('./edit');
}
/**
@ -168,7 +188,7 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
const concept = this.offlineEntry.concept;
const timecreated = this.offlineEntry.timecreated;
await AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, concept, timecreated);
await AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, timecreated);
await AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timecreated);
}
@ -234,14 +254,14 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
/**
* Load offline entry data.
*
* @param concept Entry concept.
* @param timecreated Entry Timecreated.
*/
protected async loadOfflineEntry(concept: string, timecreated: number): Promise<void> {
protected async loadOfflineEntry(timecreated: number): Promise<void> {
try {
const glossary = await this.loadGlossary();
this.offlineEntry = await AddonModGlossaryOffline.getOfflineEntry(glossary.id, concept, timecreated);
this.offlineEntry = await AddonModGlossaryOffline.getOfflineEntry(glossary.id, timecreated);
this.canEdit = true;
this.canDelete = true;
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);

View File

@ -21,7 +21,7 @@ import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { CorePath } from '@singletons/path';
import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary';
import { AddonModGlossaryEntryOption, GLOSSARY_ENTRY_ADDED, GLOSSARY_ENTRY_DELETED } from './glossary';
import { AddonModGlossaryEntryOption, GLOSSARY_ENTRY_ADDED, GLOSSARY_ENTRY_DELETED, GLOSSARY_ENTRY_UPDATED } from './glossary';
/**
* Service to handle offline glossary.
@ -33,18 +33,16 @@ export class AddonModGlossaryOfflineProvider {
* Delete an offline 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.
* @returns Promise resolved if deleted, rejected if failure.
*/
async deleteOfflineEntry(glossaryId: number, concept: string, timecreated: number, siteId?: string): Promise<void> {
async deleteOfflineEntry(glossaryId: number, timecreated: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
glossaryid: glossaryId,
concept: concept,
timecreated,
timecreated: timecreated,
};
await site.getDb().deleteRecords(OFFLINE_ENTRIES_TABLE_NAME, conditions);
@ -70,14 +68,12 @@ export class AddonModGlossaryOfflineProvider {
* Get a stored offline 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.
* @returns Promise resolved with entry.
*/
async getOfflineEntry(
glossaryId: number,
concept: string,
timeCreated: number,
siteId?: string,
): Promise<AddonModGlossaryOfflineEntry> {
@ -85,7 +81,6 @@ export class AddonModGlossaryOfflineProvider {
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
glossaryid: glossaryId,
concept: concept,
timecreated: timeCreated,
};
@ -145,7 +140,7 @@ export class AddonModGlossaryOfflineProvider {
}
// If there's only one entry, check that is not the one we are editing.
return CoreUtils.promiseFails(this.getOfflineEntry(glossaryId, concept, timeCreated, siteId));
return entries[0].timecreated !== timeCreated;
} catch {
// No offline data found, return false.
return false;
@ -159,12 +154,11 @@ export class AddonModGlossaryOfflineProvider {
* @param concept Glossary entry concept.
* @param definition Glossary entry concept definition.
* @param courseId Course ID of the glossary.
* @param timecreated The time the entry was created. If not defined, current time.
* @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.
* @returns Promise resolved if stored, rejected if failure.
*/
async addOfflineEntry(
@ -172,15 +166,13 @@ export class AddonModGlossaryOfflineProvider {
concept: string,
definition: string,
courseId: number,
timecreated: number,
options?: Record<string, AddonModGlossaryEntryOption>,
attachments?: CoreFileUploaderStoreFilesResult,
timecreated?: number,
siteId?: string,
userId?: number,
discardEntry?: AddonModGlossaryDiscardedEntry,
): Promise<false> {
const site = await CoreSites.getSite(siteId);
timecreated = timecreated || Date.now();
const entry: AddonModGlossaryOfflineEntryDBRecord = {
glossaryid: glossaryId,
@ -194,11 +186,6 @@ export class AddonModGlossaryOfflineProvider {
timecreated,
};
// If editing an offline entry, delete previous first.
if (discardEntry) {
await this.deleteOfflineEntry(glossaryId, discardEntry.concept, discardEntry.timecreated, site.getId());
}
await site.getDb().insertRecord(OFFLINE_ENTRIES_TABLE_NAME, entry);
CoreEvents.trigger(GLOSSARY_ENTRY_ADDED, { glossaryId, timecreated }, siteId);
@ -206,6 +193,42 @@ export class AddonModGlossaryOfflineProvider {
return false;
}
/**
* Update an offline entry to be sent later.
*
* @param originalEntry Original entry data.
* @param concept Glossary entry concept.
* @param definition Glossary entry concept definition.
* @param options Options for the entry.
* @param attachments Result of CoreFileUploaderProvider#storeFilesToUpload for attachments.
*/
async updateOfflineEntry(
originalEntry: Pick< AddonModGlossaryOfflineEntryDBRecord, 'glossaryid'|'courseid'|'concept'|'timecreated'>,
concept: string,
definition: string,
options?: Record<string, AddonModGlossaryEntryOption>,
attachments?: CoreFileUploaderStoreFilesResult,
): Promise<void> {
const site = await CoreSites.getSite();
const entry: Omit<AddonModGlossaryOfflineEntryDBRecord, 'courseid'|'glossaryid'|'userid'|'timecreated'> = {
concept: concept,
definition: definition,
definitionformat: 'html',
options: JSON.stringify(options || {}),
attachments: JSON.stringify(attachments),
};
await site.getDb().updateRecords(OFFLINE_ENTRIES_TABLE_NAME, entry, {
...originalEntry,
userid: site.getUserId(),
});
CoreEvents.trigger(GLOSSARY_ENTRY_UPDATED, {
glossaryId: originalEntry.glossaryid,
timecreated: originalEntry.timecreated,
});
}
/**
* Get the path to the folder where to store files for offline attachments in a glossary.
*

View File

@ -285,7 +285,7 @@ export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProv
*/
protected async deleteAddEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
await Promise.all([
AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, concept, timeCreated, siteId),
AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, timeCreated, siteId),
AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timeCreated, siteId),
]);
}

View File

@ -30,6 +30,7 @@ import { AddonModGlossaryEntryDBRecord, ENTRIES_TABLE_NAME } from './database/gl
import { AddonModGlossaryOffline } from './glossary-offline';
export const GLOSSARY_ENTRY_ADDED = 'addon_mod_glossary_entry_added';
export const GLOSSARY_ENTRY_UPDATED = 'addon_mod_glossary_entry_updated';
export const GLOSSARY_ENTRY_DELETED = 'addon_mod_glossary_entry_deleted';
/**
@ -806,13 +807,10 @@ export class AddonModGlossaryProvider {
// Convenience function to store a new entry to be synchronized later.
const storeOffline = async (): Promise<false> => {
const discardTime = otherOptions.discardEntry?.timecreated;
if (otherOptions.checkDuplicates) {
// Check if the entry is duplicated in online or offline mode.
const conceptUsed = await this.isConceptUsed(glossaryId, concept, {
cmId: otherOptions.cmId,
timeCreated: discardTime,
siteId: otherOptions.siteId,
});
@ -831,12 +829,11 @@ export class AddonModGlossaryProvider {
concept,
definition,
courseId,
otherOptions.timeCreated ?? Date.now(),
entryOptions,
attachments,
otherOptions.timeCreated,
otherOptions.siteId,
undefined,
otherOptions.discardEntry,
);
return false;
@ -847,16 +844,6 @@ export class AddonModGlossaryProvider {
return storeOffline();
}
// If we are editing an offline entry, discard previous first.
if (otherOptions.discardEntry) {
await AddonModGlossaryOffline.deleteOfflineEntry(
glossaryId,
otherOptions.discardEntry.concept,
otherOptions.discardEntry.timecreated,
otherOptions.siteId,
);
}
try {
// Try to add it in online.
const entryId = await this.addEntryOnline(
@ -1071,6 +1058,7 @@ declare module '@singletons/events' {
*/
export interface CoreEventsData {
[GLOSSARY_ENTRY_ADDED]: AddonModGlossaryEntryAddedEventData;
[GLOSSARY_ENTRY_UPDATED]: AddonModGlossaryEntryUpdatedEventData;
[GLOSSARY_ENTRY_DELETED]: AddonModGlossaryEntryDeletedEventData;
}
@ -1085,12 +1073,22 @@ export type AddonModGlossaryEntryAddedEventData = {
timecreated?: number;
};
/**
* GLOSSARY_ENTRY_UPDATED event payload.
*/
export type AddonModGlossaryEntryUpdatedEventData = {
glossaryId: number;
entryId?: number;
timecreated?: number;
};
/**
* GLOSSARY_ENTRY_DELETED event payload.
*/
export type AddonModGlossaryEntryDeletedEventData = {
glossaryId: number;
entryId: number;
entryId?: number;
timecreated?: number;
};
/**
@ -1361,21 +1359,12 @@ export type AddonModGlossaryViewEntryWSParams = {
*/
export type AddonModGlossaryAddEntryOptions = {
timeCreated?: number; // The time the entry was created. If not defined, current time.
discardEntry?: AddonModGlossaryDiscardedEntry; // The entry provided will be discarded if found.
allowOffline?: boolean; // True if it can be stored in offline, false otherwise.
checkDuplicates?: boolean; // Check for duplicates before storing offline. Only used if allowOffline is true.
cmId?: number; // Module ID.
siteId?: string; // Site ID. If not defined, current site.
};
/**
* Entry to discard.
*/
export type AddonModGlossaryDiscardedEntry = {
concept: string;
timecreated: number;
};
/**
* Options to pass to the different get entries functions.
*/

View File

@ -154,7 +154,6 @@ Feature: Test basic usage of glossary in app
Then I should find "Garlic" in the app
And I should find "Allium sativum" in the app
@noeldebug
Scenario: Edit entries (basic info)
Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app
@ -166,6 +165,9 @@ Feature: Test basic usage of glossary in app
And I set the following fields to these values in the app:
| Concept | Broccoli |
| Definition | Brassica oleracea var. italica |
And I press "This entry should be automatically linked" "ion-toggle" in the app
And I press "This entry is case sensitive" "ion-toggle" in the app
And I press "Match whole words only" "ion-toggle" in the app
And I press "Save" in the app
Then I should find "Potato" in the app
And I should find "Broccoli" in the app
@ -176,6 +178,9 @@ Feature: Test basic usage of glossary in app
When I press "Edit entry" in the app
Then the field "Concept" matches value "Broccoli" in the app
And the field "Definition" matches value "Brassica oleracea var. italica" in the app
And "This entry should be automatically linked" "ion-toggle" should be selected in the app
And "This entry is case sensitive" "ion-toggle" should be selected in the app
And "Match whole words only" "ion-toggle" should be selected in the app
When I set the following fields to these values in the app:
| Concept | Pickle |
@ -189,9 +194,6 @@ Feature: Test basic usage of glossary in app
And I should find "Potato" in the app
But I should not find "Broccoli" in the app
# TODO test attachments? (yes, in all scenarios!!)
# TODO And I upload "stub.txt" to "File" ".action-sheet-button" in the app
Scenario: Delete entries
Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app

View File

@ -200,6 +200,17 @@ Feature: Test glossary navigation
When I swipe to the left in the app
Then I should find "Acerola is a fruit" in the app
# Edit
When I swipe to the right in the app
And I press "Edit entry" in the app
And I press "Save" in the app
Then I should find "Tomato is a fruit" in the app
When I press the back button in the app
Then I should find "Tomato" in the app
And I should find "Cashew" in the app
And I should find "Acerola" in the app
@ci_jenkins_skip
Scenario: Tablet navigation on glossary
Given I entered the course "Course 1" as "student1" in the app
@ -301,3 +312,12 @@ Feature: Test glossary navigation
When I press "Acerola" in the app
Then "Acerola" near "Tomato" should be selected in the app
And I should find "Acerola is a fruit" inside the split-view content in the app
# Edit
When I press "Tomato" in the app
And I press "Edit entry" in the app
And I press "Save" in the app
Then I should find "Tomato is a fruit" inside the split-view content in the app
And I should find "Tomato" in the app
And I should find "Cashew" in the app
And I should find "Acerola" in the app