MOBILE-2652 glossary: Edit offline entries
parent
957dece787
commit
3c443a26c4
|
@ -696,6 +696,7 @@
|
||||||
"addon.mod_glossary.concept": "glossary",
|
"addon.mod_glossary.concept": "glossary",
|
||||||
"addon.mod_glossary.definition": "glossary",
|
"addon.mod_glossary.definition": "glossary",
|
||||||
"addon.mod_glossary.deleteentry": "glossary",
|
"addon.mod_glossary.deleteentry": "glossary",
|
||||||
|
"addon.mod_glossary.editentry": "glossary",
|
||||||
"addon.mod_glossary.entriestobesynced": "local_moodlemobileapp",
|
"addon.mod_glossary.entriestobesynced": "local_moodlemobileapp",
|
||||||
"addon.mod_glossary.entry": "glossary",
|
"addon.mod_glossary.entry": "glossary",
|
||||||
"addon.mod_glossary.entrydeleted": "glossary",
|
"addon.mod_glossary.entrydeleted": "glossary",
|
||||||
|
|
|
@ -86,17 +86,11 @@ export class AddonModGlossaryEntriesSource extends CoreRoutedItemsManagerSource<
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getItemQueryParams(entry: AddonModGlossaryEntryItem): Params {
|
getItemQueryParams(): Params {
|
||||||
const params: Params = {
|
return {
|
||||||
cmId: this.CM_ID,
|
cmId: this.CM_ID,
|
||||||
courseId: this.COURSE_ID,
|
courseId: this.COURSE_ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.isOfflineEntry(entry)) {
|
|
||||||
params.concept = entry.concept;
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -45,6 +45,7 @@ import {
|
||||||
AddonModGlossaryProvider,
|
AddonModGlossaryProvider,
|
||||||
GLOSSARY_ENTRY_ADDED,
|
GLOSSARY_ENTRY_ADDED,
|
||||||
GLOSSARY_ENTRY_DELETED,
|
GLOSSARY_ENTRY_DELETED,
|
||||||
|
GLOSSARY_ENTRY_UPDATED,
|
||||||
} from '../../services/glossary';
|
} from '../../services/glossary';
|
||||||
import { AddonModGlossaryOfflineEntry } from '../../services/glossary-offline';
|
import { AddonModGlossaryOfflineEntry } from '../../services/glossary-offline';
|
||||||
import {
|
import {
|
||||||
|
@ -149,6 +150,13 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
|
||||||
|
|
||||||
this.showLoadingAndRefresh(false);
|
this.showLoadingAndRefresh(false);
|
||||||
}),
|
}),
|
||||||
|
CoreEvents.on(GLOSSARY_ENTRY_UPDATED, ({ glossaryId }) => {
|
||||||
|
if (this.glossary?.id !== glossaryId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showLoadingAndRefresh(false);
|
||||||
|
}),
|
||||||
CoreEvents.on(GLOSSARY_ENTRY_DELETED, ({ glossaryId }) => {
|
CoreEvents.on(GLOSSARY_ENTRY_DELETED, ({ glossaryId }) => {
|
||||||
if (this.glossary?.id !== glossaryId) {
|
if (this.glossary?.id !== glossaryId) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -50,6 +50,10 @@ const routes: Routes = [
|
||||||
path: ':courseId/:cmId/entry/new',
|
path: ':courseId/:cmId/entry/new',
|
||||||
loadChildren: () => import('./glossary-edit-lazy.module').then(m => m.AddonModGlossaryEditLazyModule),
|
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(mobileRoutes, () => CoreScreen.isMobile),
|
||||||
...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
|
...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
|
||||||
];
|
];
|
||||||
|
|
|
@ -61,6 +61,11 @@ const mainMenuRoutes: Routes = [
|
||||||
loadChildren: () => import('./glossary-edit-lazy.module').then(m => m.AddonModGlossaryEditLazyModule),
|
loadChildren: () => import('./glossary-edit-lazy.module').then(m => m.AddonModGlossaryEditLazyModule),
|
||||||
data: { glossaryPathPrefix: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/` },
|
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(
|
...conditionalRoutes(
|
||||||
[{
|
[{
|
||||||
path: `${COURSE_CONTENTS_PATH}/${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entrySlug`,
|
path: `${COURSE_CONTENTS_PATH}/${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entrySlug`,
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"concept": "Concept",
|
"concept": "Concept",
|
||||||
"definition": "Definition",
|
"definition": "Definition",
|
||||||
"deleteentry": "Delete entry",
|
"deleteentry": "Delete entry",
|
||||||
|
"editentry": "Edit entry",
|
||||||
"entriestobesynced": "Entries to be synced",
|
"entriestobesynced": "Entries to be synced",
|
||||||
"entry": "Entry",
|
"entry": "Entry",
|
||||||
"entrydeleted": "Entry deleted",
|
"entrydeleted": "Entry deleted",
|
||||||
|
|
|
@ -84,10 +84,17 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
const entrySlug = CoreNavigator.getRouteParam<string>('entrySlug');
|
||||||
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
|
|
||||||
|
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);
|
this.handler = new AddonModGlossaryNewFormHandler(this);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
||||||
|
@ -297,23 +304,16 @@ abstract class AddonModGlossaryFormHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an offline entry.
|
* Make sure that the new entry won't create any duplicates.
|
||||||
*
|
*
|
||||||
* @param glossary Glossary.
|
* @param glossary Glossary.
|
||||||
* @param timecreated Time created.
|
|
||||||
* @param uploadedAttachments Uploaded attachments.
|
|
||||||
*/
|
*/
|
||||||
protected async createOfflineEntry(
|
protected async checkDuplicates(glossary: AddonModGlossaryGlossary): Promise<void> {
|
||||||
glossary: AddonModGlossaryGlossary,
|
if (glossary.allowduplicatedentries) {
|
||||||
timecreated: number,
|
return;
|
||||||
uploadedAttachments?: CoreFileUploaderStoreFilesResult,
|
}
|
||||||
): Promise<void> {
|
|
||||||
const data = this.page.data;
|
|
||||||
const options = this.getSaveOptions(glossary);
|
|
||||||
const definition = CoreTextUtils.formatHtmlLines(data.definition);
|
|
||||||
|
|
||||||
if (!glossary.allowduplicatedentries) {
|
const data = this.page.data;
|
||||||
// Check if the entry is duplicated in online or offline mode.
|
|
||||||
const isUsed = await AddonModGlossary.isConceptUsed(glossary.id, data.concept, {
|
const isUsed = await AddonModGlossary.isConceptUsed(glossary.id, data.concept, {
|
||||||
timeCreated: data.timecreated,
|
timeCreated: data.timecreated,
|
||||||
cmId: this.page.cmId,
|
cmId: this.page.cmId,
|
||||||
|
@ -325,56 +325,6 @@ abstract class AddonModGlossaryFormHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return entryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get additional options to save an entry.
|
* Get additional options to save an entry.
|
||||||
*
|
*
|
||||||
|
@ -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.
|
* 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(CoreEvents.ACTIVITY_DATA_SENT, { module: 'glossary' });
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreEvents.trigger(AddonModGlossaryProvider.ADD_ENTRY_EVENT, {
|
|
||||||
glossaryId: glossary.id,
|
|
||||||
entryId: entryId || undefined,
|
|
||||||
}, CoreSites.getCurrentSiteId());
|
|
||||||
|
|
||||||
return !!entryId;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -52,11 +52,16 @@
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="canDelete">
|
<ion-item *ngIf="canDelete || canEdit">
|
||||||
<div slot="end">
|
<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-icon slot="icon-only" name="fas-trash" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</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>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<div *ngIf="onlineEntry && onlineEntry.attachment">
|
<div *ngIf="onlineEntry && onlineEntry.attachment">
|
||||||
|
|
|
@ -29,12 +29,14 @@ import { CoreNetwork } from '@services/network';
|
||||||
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { AddonModGlossaryEntriesSource, AddonModGlossaryEntryItem } from '../../classes/glossary-entries-source';
|
import { AddonModGlossaryEntriesSource, AddonModGlossaryEntryItem } from '../../classes/glossary-entries-source';
|
||||||
import {
|
import {
|
||||||
AddonModGlossary,
|
AddonModGlossary,
|
||||||
AddonModGlossaryEntry,
|
AddonModGlossaryEntry,
|
||||||
AddonModGlossaryGlossary,
|
AddonModGlossaryGlossary,
|
||||||
AddonModGlossaryProvider,
|
AddonModGlossaryProvider,
|
||||||
|
GLOSSARY_ENTRY_UPDATED,
|
||||||
} from '../../services/glossary';
|
} from '../../services/glossary';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,11 +56,13 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
offlineEntry?: AddonModGlossaryOfflineEntry;
|
offlineEntry?: AddonModGlossaryOfflineEntry;
|
||||||
entries!: AddonModGlossaryEntryEntriesSwipeManager;
|
entries!: AddonModGlossaryEntryEntriesSwipeManager;
|
||||||
glossary?: AddonModGlossaryGlossary;
|
glossary?: AddonModGlossaryGlossary;
|
||||||
|
entryUpdatedObserver?: CoreEventObserver;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
showAuthor = false;
|
showAuthor = false;
|
||||||
showDate = false;
|
showDate = false;
|
||||||
ratingInfo?: CoreRatingInfo;
|
ratingInfo?: CoreRatingInfo;
|
||||||
tagsEnabled = false;
|
tagsEnabled = false;
|
||||||
|
canEdit = false;
|
||||||
canDelete = false;
|
canDelete = false;
|
||||||
commentsEnabled = false;
|
commentsEnabled = false;
|
||||||
courseId!: number;
|
courseId!: number;
|
||||||
|
@ -75,10 +79,8 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
let onlineEntryId: number | null = null;
|
let onlineEntryId: number | null = null;
|
||||||
let offlineEntry: {
|
let offlineEntryTimeCreated: number | null = null;
|
||||||
concept: string;
|
|
||||||
timecreated: number;
|
|
||||||
} | null = null;
|
|
||||||
try {
|
try {
|
||||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
|
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
|
||||||
|
@ -97,10 +99,7 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
await this.entries.start();
|
await this.entries.start();
|
||||||
|
|
||||||
if (entrySlug.startsWith('new-')) {
|
if (entrySlug.startsWith('new-')) {
|
||||||
offlineEntry = {
|
offlineEntryTimeCreated = Number(entrySlug.slice(4));
|
||||||
concept : CoreNavigator.getRequiredRouteParam<string>('concept'),
|
|
||||||
timecreated: Number(entrySlug.slice(4)),
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
onlineEntryId = Number(entrySlug);
|
onlineEntryId = Number(entrySlug);
|
||||||
}
|
}
|
||||||
|
@ -111,6 +110,19 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
return;
|
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 {
|
try {
|
||||||
if (onlineEntryId) {
|
if (onlineEntryId) {
|
||||||
await this.loadOnlineEntry(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));
|
await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(onlineEntryId, this.componentId, this.glossary?.name));
|
||||||
} else if (offlineEntry) {
|
} else if (offlineEntryTimeCreated) {
|
||||||
await this.loadOfflineEntry(offlineEntry.concept, offlineEntry.timecreated);
|
await this.loadOfflineEntry(offlineEntryTimeCreated);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
@ -133,6 +145,14 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.entries.destroy();
|
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 concept = this.offlineEntry.concept;
|
||||||
const timecreated = this.offlineEntry.timecreated;
|
const timecreated = this.offlineEntry.timecreated;
|
||||||
|
|
||||||
await AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, concept, timecreated);
|
await AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, timecreated);
|
||||||
await AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timecreated);
|
await AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timecreated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,14 +254,14 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Load offline entry data.
|
* Load offline entry data.
|
||||||
*
|
*
|
||||||
* @param concept Entry concept.
|
|
||||||
* @param timecreated Entry Timecreated.
|
* @param timecreated Entry Timecreated.
|
||||||
*/
|
*/
|
||||||
protected async loadOfflineEntry(concept: string, timecreated: number): Promise<void> {
|
protected async loadOfflineEntry(timecreated: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const glossary = await this.loadGlossary();
|
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;
|
this.canDelete = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
|
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { makeSingleton } from '@singletons';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CorePath } from '@singletons/path';
|
import { CorePath } from '@singletons/path';
|
||||||
import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary';
|
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.
|
* Service to handle offline glossary.
|
||||||
|
@ -33,18 +33,16 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
* Delete an offline entry.
|
* Delete an offline entry.
|
||||||
*
|
*
|
||||||
* @param glossaryId Glossary ID.
|
* @param glossaryId Glossary ID.
|
||||||
* @param concept Glossary entry concept.
|
|
||||||
* @param timecreated The time the entry was created.
|
* @param timecreated The time the entry was created.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @returns Promise resolved if deleted, rejected if failure.
|
* @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 site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
||||||
glossaryid: glossaryId,
|
glossaryid: glossaryId,
|
||||||
concept: concept,
|
timecreated: timecreated,
|
||||||
timecreated,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await site.getDb().deleteRecords(OFFLINE_ENTRIES_TABLE_NAME, conditions);
|
await site.getDb().deleteRecords(OFFLINE_ENTRIES_TABLE_NAME, conditions);
|
||||||
|
@ -70,14 +68,12 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
* Get a stored offline entry.
|
* Get a stored offline entry.
|
||||||
*
|
*
|
||||||
* @param glossaryId Glossary ID.
|
* @param glossaryId Glossary ID.
|
||||||
* @param concept Glossary entry concept.
|
|
||||||
* @param timeCreated The time the entry was created.
|
* @param timeCreated The time the entry was created.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @returns Promise resolved with entry.
|
* @returns Promise resolved with entry.
|
||||||
*/
|
*/
|
||||||
async getOfflineEntry(
|
async getOfflineEntry(
|
||||||
glossaryId: number,
|
glossaryId: number,
|
||||||
concept: string,
|
|
||||||
timeCreated: number,
|
timeCreated: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<AddonModGlossaryOfflineEntry> {
|
): Promise<AddonModGlossaryOfflineEntry> {
|
||||||
|
@ -85,7 +81,6 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
|
|
||||||
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
|
||||||
glossaryid: glossaryId,
|
glossaryid: glossaryId,
|
||||||
concept: concept,
|
|
||||||
timecreated: timeCreated,
|
timecreated: timeCreated,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -145,7 +140,7 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's only one entry, check that is not the one we are editing.
|
// 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 {
|
} catch {
|
||||||
// No offline data found, return false.
|
// No offline data found, return false.
|
||||||
return false;
|
return false;
|
||||||
|
@ -159,12 +154,11 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
* @param concept Glossary entry concept.
|
* @param concept Glossary entry concept.
|
||||||
* @param definition Glossary entry concept definition.
|
* @param definition Glossary entry concept definition.
|
||||||
* @param courseId Course ID of the glossary.
|
* @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 options Options for the entry.
|
||||||
* @param attachments Result of CoreFileUploaderProvider#storeFilesToUpload for attachments.
|
* @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 siteId Site ID. If not defined, current site.
|
||||||
* @param userId User the entry belong to. If not defined, current user in 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.
|
* @returns Promise resolved if stored, rejected if failure.
|
||||||
*/
|
*/
|
||||||
async addOfflineEntry(
|
async addOfflineEntry(
|
||||||
|
@ -172,15 +166,13 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
concept: string,
|
concept: string,
|
||||||
definition: string,
|
definition: string,
|
||||||
courseId: number,
|
courseId: number,
|
||||||
|
timecreated: number,
|
||||||
options?: Record<string, AddonModGlossaryEntryOption>,
|
options?: Record<string, AddonModGlossaryEntryOption>,
|
||||||
attachments?: CoreFileUploaderStoreFilesResult,
|
attachments?: CoreFileUploaderStoreFilesResult,
|
||||||
timecreated?: number,
|
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
discardEntry?: AddonModGlossaryDiscardedEntry,
|
|
||||||
): Promise<false> {
|
): Promise<false> {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
timecreated = timecreated || Date.now();
|
|
||||||
|
|
||||||
const entry: AddonModGlossaryOfflineEntryDBRecord = {
|
const entry: AddonModGlossaryOfflineEntryDBRecord = {
|
||||||
glossaryid: glossaryId,
|
glossaryid: glossaryId,
|
||||||
|
@ -194,11 +186,6 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
timecreated,
|
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);
|
await site.getDb().insertRecord(OFFLINE_ENTRIES_TABLE_NAME, entry);
|
||||||
|
|
||||||
CoreEvents.trigger(GLOSSARY_ENTRY_ADDED, { glossaryId, timecreated }, siteId);
|
CoreEvents.trigger(GLOSSARY_ENTRY_ADDED, { glossaryId, timecreated }, siteId);
|
||||||
|
@ -206,6 +193,42 @@ export class AddonModGlossaryOfflineProvider {
|
||||||
return false;
|
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.
|
* Get the path to the folder where to store files for offline attachments in a glossary.
|
||||||
*
|
*
|
||||||
|
|
|
@ -285,7 +285,7 @@ export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProv
|
||||||
*/
|
*/
|
||||||
protected async deleteAddEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
|
protected async deleteAddEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, concept, timeCreated, siteId),
|
AddonModGlossaryOffline.deleteOfflineEntry(glossaryId, timeCreated, siteId),
|
||||||
AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timeCreated, siteId),
|
AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timeCreated, siteId),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { AddonModGlossaryEntryDBRecord, ENTRIES_TABLE_NAME } from './database/gl
|
||||||
import { AddonModGlossaryOffline } from './glossary-offline';
|
import { AddonModGlossaryOffline } from './glossary-offline';
|
||||||
|
|
||||||
export const GLOSSARY_ENTRY_ADDED = 'addon_mod_glossary_entry_added';
|
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';
|
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.
|
// Convenience function to store a new entry to be synchronized later.
|
||||||
const storeOffline = async (): Promise<false> => {
|
const storeOffline = async (): Promise<false> => {
|
||||||
const discardTime = otherOptions.discardEntry?.timecreated;
|
|
||||||
|
|
||||||
if (otherOptions.checkDuplicates) {
|
if (otherOptions.checkDuplicates) {
|
||||||
// Check if the entry is duplicated in online or offline mode.
|
// Check if the entry is duplicated in online or offline mode.
|
||||||
const conceptUsed = await this.isConceptUsed(glossaryId, concept, {
|
const conceptUsed = await this.isConceptUsed(glossaryId, concept, {
|
||||||
cmId: otherOptions.cmId,
|
cmId: otherOptions.cmId,
|
||||||
timeCreated: discardTime,
|
|
||||||
siteId: otherOptions.siteId,
|
siteId: otherOptions.siteId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -831,12 +829,11 @@ export class AddonModGlossaryProvider {
|
||||||
concept,
|
concept,
|
||||||
definition,
|
definition,
|
||||||
courseId,
|
courseId,
|
||||||
|
otherOptions.timeCreated ?? Date.now(),
|
||||||
entryOptions,
|
entryOptions,
|
||||||
attachments,
|
attachments,
|
||||||
otherOptions.timeCreated,
|
|
||||||
otherOptions.siteId,
|
otherOptions.siteId,
|
||||||
undefined,
|
undefined,
|
||||||
otherOptions.discardEntry,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -847,16 +844,6 @@ export class AddonModGlossaryProvider {
|
||||||
return storeOffline();
|
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 {
|
||||||
// Try to add it in online.
|
// Try to add it in online.
|
||||||
const entryId = await this.addEntryOnline(
|
const entryId = await this.addEntryOnline(
|
||||||
|
@ -1071,6 +1058,7 @@ declare module '@singletons/events' {
|
||||||
*/
|
*/
|
||||||
export interface CoreEventsData {
|
export interface CoreEventsData {
|
||||||
[GLOSSARY_ENTRY_ADDED]: AddonModGlossaryEntryAddedEventData;
|
[GLOSSARY_ENTRY_ADDED]: AddonModGlossaryEntryAddedEventData;
|
||||||
|
[GLOSSARY_ENTRY_UPDATED]: AddonModGlossaryEntryUpdatedEventData;
|
||||||
[GLOSSARY_ENTRY_DELETED]: AddonModGlossaryEntryDeletedEventData;
|
[GLOSSARY_ENTRY_DELETED]: AddonModGlossaryEntryDeletedEventData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1085,12 +1073,22 @@ export type AddonModGlossaryEntryAddedEventData = {
|
||||||
timecreated?: number;
|
timecreated?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GLOSSARY_ENTRY_UPDATED event payload.
|
||||||
|
*/
|
||||||
|
export type AddonModGlossaryEntryUpdatedEventData = {
|
||||||
|
glossaryId: number;
|
||||||
|
entryId?: number;
|
||||||
|
timecreated?: number;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GLOSSARY_ENTRY_DELETED event payload.
|
* GLOSSARY_ENTRY_DELETED event payload.
|
||||||
*/
|
*/
|
||||||
export type AddonModGlossaryEntryDeletedEventData = {
|
export type AddonModGlossaryEntryDeletedEventData = {
|
||||||
glossaryId: number;
|
glossaryId: number;
|
||||||
entryId: number;
|
entryId?: number;
|
||||||
|
timecreated?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1361,21 +1359,12 @@ export type AddonModGlossaryViewEntryWSParams = {
|
||||||
*/
|
*/
|
||||||
export type AddonModGlossaryAddEntryOptions = {
|
export type AddonModGlossaryAddEntryOptions = {
|
||||||
timeCreated?: number; // The time the entry was created. If not defined, current time.
|
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.
|
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.
|
checkDuplicates?: boolean; // Check for duplicates before storing offline. Only used if allowOffline is true.
|
||||||
cmId?: number; // Module ID.
|
cmId?: number; // Module ID.
|
||||||
siteId?: string; // Site ID. If not defined, current site.
|
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.
|
* Options to pass to the different get entries functions.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -154,7 +154,6 @@ Feature: Test basic usage of glossary in app
|
||||||
Then I should find "Garlic" in the app
|
Then I should find "Garlic" in the app
|
||||||
And I should find "Allium sativum" in the app
|
And I should find "Allium sativum" in the app
|
||||||
|
|
||||||
@noeldebug
|
|
||||||
Scenario: Edit entries (basic info)
|
Scenario: Edit entries (basic info)
|
||||||
Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app
|
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:
|
And I set the following fields to these values in the app:
|
||||||
| Concept | Broccoli |
|
| Concept | Broccoli |
|
||||||
| Definition | Brassica oleracea var. italica |
|
| 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
|
And I press "Save" in the app
|
||||||
Then I should find "Potato" in the app
|
Then I should find "Potato" in the app
|
||||||
And I should find "Broccoli" 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
|
When I press "Edit entry" in the app
|
||||||
Then the field "Concept" matches value "Broccoli" 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 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:
|
When I set the following fields to these values in the app:
|
||||||
| Concept | Pickle |
|
| Concept | Pickle |
|
||||||
|
@ -189,9 +194,6 @@ Feature: Test basic usage of glossary in app
|
||||||
And I should find "Potato" in the app
|
And I should find "Potato" in the app
|
||||||
But I should not find "Broccoli" 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
|
Scenario: Delete entries
|
||||||
Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app
|
Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,17 @@ Feature: Test glossary navigation
|
||||||
When I swipe to the left in the app
|
When I swipe to the left in the app
|
||||||
Then I should find "Acerola is a fruit" 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
|
@ci_jenkins_skip
|
||||||
Scenario: Tablet navigation on glossary
|
Scenario: Tablet navigation on glossary
|
||||||
Given I entered the course "Course 1" as "student1" in the app
|
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
|
When I press "Acerola" in the app
|
||||||
Then "Acerola" near "Tomato" should be selected 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
|
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
|
||||||
|
|
Loading…
Reference in New Issue