MOBILE-2652 glossary: Edit online entries
parent
3c443a26c4
commit
9391fe4122
|
@ -16,11 +16,13 @@ import { Component, OnInit, ViewChild, ElementRef, Optional } from '@angular/cor
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||||
import { CanLeave } from '@guards/can-leave';
|
import { CanLeave } from '@guards/can-leave';
|
||||||
import { CoreFileEntry } from '@services/file-helper';
|
import { CoreFileEntry } from '@services/file-helper';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreNetwork } from '@services/network';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
@ -31,6 +33,7 @@ import { CoreForms } from '@singletons/form';
|
||||||
import {
|
import {
|
||||||
AddonModGlossary,
|
AddonModGlossary,
|
||||||
AddonModGlossaryCategory,
|
AddonModGlossaryCategory,
|
||||||
|
AddonModGlossaryEntry,
|
||||||
AddonModGlossaryEntryOption,
|
AddonModGlossaryEntryOption,
|
||||||
AddonModGlossaryGlossary,
|
AddonModGlossaryGlossary,
|
||||||
AddonModGlossaryProvider,
|
AddonModGlossaryProvider,
|
||||||
|
@ -92,6 +95,11 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
|
||||||
const timecreated = Number(entrySlug.slice(4));
|
const timecreated = Number(entrySlug.slice(4));
|
||||||
this.editorExtraParams.timecreated = timecreated;
|
this.editorExtraParams.timecreated = timecreated;
|
||||||
this.handler = new AddonModGlossaryOfflineFormHandler(this, 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 {
|
} else {
|
||||||
this.handler = new AddonModGlossaryNewFormHandler(this);
|
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.
|
* Form data.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -240,10 +240,12 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
||||||
try {
|
try {
|
||||||
const result = await AddonModGlossary.getEntry(entryId);
|
const result = await AddonModGlossary.getEntry(entryId);
|
||||||
const canDeleteEntries = CoreNetwork.isOnline() && await AddonModGlossary.canDeleteEntries();
|
const canDeleteEntries = CoreNetwork.isOnline() && await AddonModGlossary.canDeleteEntries();
|
||||||
|
const canUpdateEntries = CoreNetwork.isOnline() && await AddonModGlossary.canUpdateEntries();
|
||||||
|
|
||||||
this.onlineEntry = result.entry;
|
this.onlineEntry = result.entry;
|
||||||
this.ratingInfo = result.ratinginfo;
|
this.ratingInfo = result.ratinginfo;
|
||||||
this.canDelete = canDeleteEntries && !!result.permissions?.candelete;
|
this.canDelete = canDeleteEntries && !!result.permissions?.candelete;
|
||||||
|
this.canEdit = canUpdateEntries && !!result.permissions?.canupdate;
|
||||||
|
|
||||||
await this.loadGlossary();
|
await this.loadGlossary();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -619,6 +619,18 @@ export class AddonModGlossaryProvider {
|
||||||
return site.wsAvailable('mod_glossary_delete_entry');
|
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.
|
* Performs the whole fetch of the entries using the proper function and arguments.
|
||||||
*
|
*
|
||||||
|
@ -910,6 +922,43 @@ export class AddonModGlossaryProvider {
|
||||||
return response.entryid;
|
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.
|
* Delete entry.
|
||||||
*
|
*
|
||||||
|
@ -1339,6 +1388,35 @@ export type AddonModGlossaryAddEntryWSResponse = {
|
||||||
warnings?: CoreWSExternalWarning[];
|
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.
|
* Params of mod_glossary_view_glossary WS.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -157,7 +157,39 @@ Feature: Test basic usage of glossary in app
|
||||||
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
|
||||||
|
|
||||||
# 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
|
# Offline
|
||||||
When I press "Add a new entry" in the app
|
When I press "Add a new entry" in the app
|
||||||
|
|
Loading…
Reference in New Issue