MOBILE-2652 glossary: Edit online entries

main
Noel De Martin 2023-03-29 15:43:13 +02:00
parent 3c443a26c4
commit 9391fe4122
4 changed files with 192 additions and 1 deletions

View File

@ -16,11 +16,13 @@ import { Component, OnInit, ViewChild, ElementRef, Optional } from '@angular/cor
import { FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { CoreError } from '@classes/errors/error';
import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CanLeave } from '@guards/can-leave';
import { CoreFileEntry } from '@services/file-helper';
import { CoreNavigator } from '@services/navigator';
import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
@ -31,6 +33,7 @@ import { CoreForms } from '@singletons/form';
import {
AddonModGlossary,
AddonModGlossaryCategory,
AddonModGlossaryEntry,
AddonModGlossaryEntryOption,
AddonModGlossaryGlossary,
AddonModGlossaryProvider,
@ -92,6 +95,11 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
const timecreated = Number(entrySlug.slice(4));
this.editorExtraParams.timecreated = timecreated;
this.handler = new AddonModGlossaryOfflineFormHandler(this, timecreated);
} else if (entrySlug) {
const { entry } = await AddonModGlossary.getEntry(Number(entrySlug));
this.editorExtraParams.timecreated = entry.timecreated;
this.handler = new AddonModGlossaryOnlineFormHandler(this, entry);
} else {
this.handler = new AddonModGlossaryNewFormHandler(this);
}
@ -584,6 +592,77 @@ class AddonModGlossaryNewFormHandler extends AddonModGlossaryFormHandler {
}
/**
* Helper to manage the form data for an online entry.
*/
class AddonModGlossaryOnlineFormHandler extends AddonModGlossaryFormHandler {
private entry: AddonModGlossaryEntry;
constructor(page: AddonModGlossaryEditPage, entry: AddonModGlossaryEntry) {
super(page);
this.entry = entry;
}
/**
* @inheritdoc
*/
async loadData(): Promise<void> {
const data = this.page.data;
data.concept = this.entry.concept;
data.definition = this.entry.definition || '';
data.timecreated = this.entry.timecreated;
data.usedynalink = this.entry.usedynalink;
if (data.usedynalink) {
data.casesensitive = this.entry.casesensitive;
data.fullmatch = this.entry.fullmatch;
}
// Treat offline attachments if any.
if (this.entry.attachments) {
data.attachments = this.entry.attachments;
}
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> {
if (!CoreNetwork.isOnline()) {
throw new CoreNetworkError();
}
const data = this.page.data;
const options = this.getSaveOptions(glossary);
const definition = CoreTextUtils.formatHtmlLines(data.definition);
// Save entry data.
await AddonModGlossary.updateEntry(glossary.id, this.entry.id, data.concept, definition, options);
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'glossary' });
return true;
}
}
/**
* Form data.
*/

View File

@ -240,10 +240,12 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
try {
const result = await AddonModGlossary.getEntry(entryId);
const canDeleteEntries = CoreNetwork.isOnline() && await AddonModGlossary.canDeleteEntries();
const canUpdateEntries = CoreNetwork.isOnline() && await AddonModGlossary.canUpdateEntries();
this.onlineEntry = result.entry;
this.ratingInfo = result.ratinginfo;
this.canDelete = canDeleteEntries && !!result.permissions?.candelete;
this.canEdit = canUpdateEntries && !!result.permissions?.canupdate;
await this.loadGlossary();
} catch (error) {

View File

@ -619,6 +619,18 @@ export class AddonModGlossaryProvider {
return site.wsAvailable('mod_glossary_delete_entry');
}
/**
* Check whether the site can update glossary entries.
*
* @param siteId Site id.
* @returns Whether the site can update entries.
*/
async canUpdateEntries(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return site.wsAvailable('mod_glossary_update_entry');
}
/**
* Performs the whole fetch of the entries using the proper function and arguments.
*
@ -910,6 +922,43 @@ export class AddonModGlossaryProvider {
return response.entryid;
}
/**
* Update an existing entry on a glossary.
*
* @param glossaryId Glossary ID.
* @param entryId Entry ID.
* @param concept Glossary entry concept.
* @param definition Glossary entry concept definition.
* @param options Options for the entry.
* @param siteId Site ID. If not defined, current site.
*/
async updateEntry(
glossaryId: number,
entryId: number,
concept: string,
definition: string,
options?: Record<string, AddonModGlossaryEntryOption>,
siteId?: string,
): Promise<void> {
const site = await CoreSites.getSite(siteId);
const params: AddonModGlossaryUpdateEntryWSParams = {
entryid: entryId,
concept: concept,
definition: definition,
definitionformat: 1,
options: CoreUtils.objectToArrayOfObjects(options || {}, 'name', 'value'),
};
const response = await site.write<AddonModGlossaryUpdateEntryWSResponse>('mod_glossary_update_entry', params);
if (!response.result) {
throw new CoreError(response.warnings?.[0].message ?? 'Error updating entry');
}
CoreEvents.trigger(GLOSSARY_ENTRY_UPDATED, { glossaryId, entryId }, siteId);
}
/**
* Delete entry.
*
@ -1339,6 +1388,35 @@ export type AddonModGlossaryAddEntryWSResponse = {
warnings?: CoreWSExternalWarning[];
};
/**
* Params of mod_glossary_update_entry WS.
*/
export type AddonModGlossaryUpdateEntryWSParams = {
entryid: number; // Glossary entry id to update.
concept: string; // Glossary concept.
definition: string; // Glossary concept definition.
definitionformat: number; // Definition format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
options?: { // Optional settings.
name: string; // The allowed keys (value format) are:
// inlineattachmentsid (int); the draft file area id for inline attachments
// attachmentsid (int); the draft file area id for attachments
// categories (comma separated int); comma separated category ids
// aliases (comma separated str); comma separated aliases
// usedynalink (bool); whether the entry should be automatically linked.
// casesensitive (bool); whether the entry is case sensitive.
// fullmatch (bool); whether to match whole words only.
value: string | number; // The value of the option (validated inside the function).
}[];
};
/**
* Data returned by mod_glossary_update_entry WS.
*/
export type AddonModGlossaryUpdateEntryWSResponse = {
result: boolean; // The update result.
warnings?: CoreWSExternalWarning[];
};
/**
* Params of mod_glossary_view_glossary WS.
*/

View File

@ -157,7 +157,39 @@ Feature: Test basic usage of glossary in app
Scenario: Edit entries (basic info)
Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app
# TODO online
# Online
When I press "Add a new entry" in the app
And I set the following fields to these values in the app:
| Concept | Cashew |
| Definition | Cashew is a fruit |
And I press "Save" in the app
Then I should find "Cashew" in the app
When I press "Cashew" in the app
And I press "Edit entry" in the app
Then the field "Concept" matches value "Cashew" in the app
And the field "Definition" matches value "Cashew is a fruit" in the app
When I set the following fields to these values in the app:
| Concept | Coconut |
| Definition | Coconut is a fruit |
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 "Coconut is a fruit" in the app
But I should not find "Cashew is a fruit" in the app
When I press "Edit entry" in the app
Then "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 press "Save" in the app
And I press the back button in the app
Then I should find "Coconut" in the app
And I should find "Potato" in the app
But I should not find "Cashew" in the app
# Offline
When I press "Add a new entry" in the app