diff --git a/scripts/langindex.json b/scripts/langindex.json index ff29240d8..4d1fdd0b8 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -681,6 +681,7 @@ "addon.mod_forum.yourreply": "forum", "addon.mod_glossary.addentry": "glossary", "addon.mod_glossary.aliases": "glossary", + "addon.mod_glossary.areyousuredelete": "glossary", "addon.mod_glossary.attachment": "glossary", "addon.mod_glossary.browsemode": "local_moodlemobileapp", "addon.mod_glossary.byalphabet": "local_moodlemobileapp", @@ -694,9 +695,12 @@ "addon.mod_glossary.categories": "glossary", "addon.mod_glossary.concept": "glossary", "addon.mod_glossary.definition": "glossary", + "addon.mod_glossary.deleteentry": "glossary", "addon.mod_glossary.entriestobesynced": "local_moodlemobileapp", + "addon.mod_glossary.entrydeleted": "glossary", "addon.mod_glossary.entrypendingapproval": "local_moodlemobileapp", "addon.mod_glossary.entryusedynalink": "glossary", + "addon.mod_glossary.errordeleting": "local_moodlemobileapp", "addon.mod_glossary.errconceptalreadyexists": "glossary", "addon.mod_glossary.errorloadingentries": "local_moodlemobileapp", "addon.mod_glossary.errorloadingentry": "local_moodlemobileapp", diff --git a/src/addons/mod/glossary/classes/glossary-entries-source.ts b/src/addons/mod/glossary/classes/glossary-entries-source.ts index a58e0cfe7..3e0dc06c6 100644 --- a/src/addons/mod/glossary/classes/glossary-entries-source.ts +++ b/src/addons/mod/glossary/classes/glossary-entries-source.ts @@ -179,12 +179,14 @@ export class AddonModGlossaryEntriesSource extends CoreRoutedItemsManagerSource< /** * Invalidate glossary cache. + * + * @param invalidateGlossary Whether to invalidate the entire glossary or not */ - async invalidateCache(): Promise { - await Promise.all([ - AddonModGlossary.invalidateCourseGlossaries(this.COURSE_ID), + async invalidateCache(invalidateGlossary: boolean = true): Promise { + await Promise.all([ this.fetchInvalidate && this.fetchInvalidate(), - this.glossary && AddonModGlossary.invalidateCategories(this.glossary.id), + invalidateGlossary && AddonModGlossary.invalidateCourseGlossaries(this.COURSE_ID), + invalidateGlossary && this.glossary && AddonModGlossary.invalidateCategories(this.glossary.id), ]); } diff --git a/src/addons/mod/glossary/components/index/index.ts b/src/addons/mod/glossary/components/index/index.ts index 6b0acca1c..b7a797451 100644 --- a/src/addons/mod/glossary/components/index/index.ts +++ b/src/addons/mod/glossary/components/index/index.ts @@ -42,12 +42,14 @@ import { AddonModGlossaryEntryWithCategory, AddonModGlossaryGlossary, AddonModGlossaryProvider, + GLOSSARY_ENTRY_ADDED, + GLOSSARY_ENTRY_DELETED, } from '../../services/glossary'; import { AddonModGlossaryOfflineEntry } from '../../services/glossary-offline'; import { - AddonModGlossaryAutoSyncData, - AddonModGlossarySyncProvider, + AddonModGlossaryAutoSyncedData, AddonModGlossarySyncResult, + GLOSSARY_AUTO_SYNCED, } from '../../services/glossary-sync'; import { AddonModGlossaryModuleHandlerService } from '../../services/handlers/module'; import { AddonModGlossaryPrefetchHandler } from '../../services/handlers/prefetch'; @@ -75,13 +77,11 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity protected hasOfflineEntries = false; protected hasOfflineRatings = false; - protected syncEventName = AddonModGlossarySyncProvider.AUTO_SYNCED; - protected addEntryObserver?: CoreEventObserver; + protected syncEventName = GLOSSARY_AUTO_SYNCED; protected fetchedEntriesCanLoadMore = false; protected fetchedEntries: AddonModGlossaryEntry[] = []; protected sourceUnsubscribe?: () => void; - protected ratingOfflineObserver?: CoreEventObserver; - protected ratingSyncObserver?: CoreEventObserver; + protected observers?: CoreEventObserver[]; protected checkCompletionAfterLog = false; // Use CoreListItemsManager log system instead. getDivider?: (entry: AddonModGlossaryEntry) => string; @@ -136,30 +136,41 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity }); // When an entry is added, we reload the data. - this.addEntryObserver = CoreEvents.on(AddonModGlossaryProvider.ADD_ENTRY_EVENT, (data) => { - if (this.glossary && this.glossary.id === data.glossaryId) { - this.showLoadingAndRefresh(false); + this.observers = [ + CoreEvents.on(GLOSSARY_ENTRY_ADDED, ({ glossaryId }) => { + if (this.glossary?.id !== glossaryId) { + return; + } // Check completion since it could be configured to complete once the user adds a new entry. this.checkCompletion(); - } - }); + + this.showLoadingAndRefresh(false); + }), + CoreEvents.on(GLOSSARY_ENTRY_DELETED, ({ glossaryId }) => { + if (this.glossary?.id !== glossaryId) { + return; + } + + this.showLoadingAndRefresh(false); + }), + ]; // Listen for offline ratings saved and synced. - this.ratingOfflineObserver = CoreEvents.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => { + this.observers.push(CoreEvents.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => { if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' && data.instanceId == this.glossary.coursemodule) { this.hasOfflineRatings = true; this.hasOffline = true; } - }); - this.ratingSyncObserver = CoreEvents.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => { + })); + this.observers.push(CoreEvents.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => { if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' && data.instanceId == this.glossary.coursemodule) { this.hasOfflineRatings = false; this.hasOffline = this.hasOfflineEntries; } - }); + })); } /** @@ -227,7 +238,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity * @param syncEventData Data receiven on sync observer. * @returns True if refresh is needed, false otherwise. */ - protected isRefreshSyncNeeded(syncEventData: AddonModGlossaryAutoSyncData): boolean { + protected isRefreshSyncNeeded(syncEventData: AddonModGlossaryAutoSyncedData): boolean { return !!this.glossary && syncEventData.glossaryId == this.glossary.id && syncEventData.userId == CoreSites.getCurrentSiteUserId(); } @@ -410,9 +421,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity ngOnDestroy(): void { super.ngOnDestroy(); - this.addEntryObserver?.off(); - this.ratingOfflineObserver?.off(); - this.ratingSyncObserver?.off(); + this.observers?.forEach(observer => observer.off()); this.sourceUnsubscribe?.call(null); this.entries?.destroy(); } diff --git a/src/addons/mod/glossary/lang.json b/src/addons/mod/glossary/lang.json index ba4329f33..6206a6430 100644 --- a/src/addons/mod/glossary/lang.json +++ b/src/addons/mod/glossary/lang.json @@ -1,6 +1,7 @@ { "addentry": "Add a new entry", "aliases": "Keyword(s)", + "areyousuredelete": "Are you sure you want to delete this entry?", "attachment": "Attachment", "browsemode": "Browse entries", "byalphabet": "Alphabetically", @@ -14,10 +15,13 @@ "categories": "Categories", "concept": "Concept", "definition": "Definition", + "deleteentry": "Delete entry", "entriestobesynced": "Entries to be synced", + "entrydeleted": "Entry deleted", "entrypendingapproval": "This entry is pending approval.", "entryusedynalink": "This entry should be automatically linked", "errconceptalreadyexists": "This concept already exists. No duplicates allowed in this glossary.", + "errordeleting": "Error deleting entry.", "errorloadingentries": "An error occurred while loading entries.", "errorloadingentry": "An error occurred while loading the entry.", "errorloadingglossary": "An error occurred while loading the glossary.", diff --git a/src/addons/mod/glossary/pages/entry/entry.html b/src/addons/mod/glossary/pages/entry/entry.html index ee112af6e..28c2b60b1 100644 --- a/src/addons/mod/glossary/pages/entry/entry.html +++ b/src/addons/mod/glossary/pages/entry/entry.html @@ -46,6 +46,13 @@ + +
+ + + +
+
diff --git a/src/addons/mod/glossary/pages/entry/entry.ts b/src/addons/mod/glossary/pages/entry/entry.ts index f4ee71044..1db3fc59e 100644 --- a/src/addons/mod/glossary/pages/entry/entry.ts +++ b/src/addons/mod/glossary/pages/entry/entry.ts @@ -12,17 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, Optional, ViewChild } from '@angular/core'; import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreCommentsCommentsComponent } from '@features/comments/components/comments/comments'; import { CoreComments } from '@features/comments/services/comments'; import { CoreRatingInfo } from '@features/rating/services/rating'; import { CoreTag } from '@features/tag/services/tag'; import { IonRefresher } from '@ionic/angular'; import { CoreNavigator } from '@services/navigator'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreNetwork } from '@services/network'; +import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; +import { Translate } from '@singletons'; import { AddonModGlossaryEntriesSource } from '../../classes/glossary-entries-source'; import { AddonModGlossaryEntriesSwipeManager } from '../../classes/glossary-entries-swipe-manager'; import { @@ -53,13 +56,14 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { showDate = false; ratingInfo?: CoreRatingInfo; tagsEnabled = false; + canDelete = false; commentsEnabled = false; courseId!: number; cmId?: number; protected entryId!: number; - constructor(protected route: ActivatedRoute) {} + constructor(@Optional() protected splitView: CoreSplitViewComponent, protected route: ActivatedRoute) {} /** * @inheritdoc @@ -113,6 +117,46 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { this.entries?.destroy(); } + /** + * Delete entry. + */ + async deleteEntry(): Promise { + const entryId = this.entry?.id; + const glossaryId = this.glossary?.id; + const cancelled = await CoreUtils.promiseFails( + CoreDomUtils.showConfirm(Translate.instant('addon.mod_glossary.areyousuredelete')), + ); + + if (!entryId || !glossaryId || cancelled) { + return; + } + + const modal = await CoreDomUtils.showModalLoading(); + + try { + await AddonModGlossary.deleteEntry(glossaryId, entryId); + await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntry(entryId)); + await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByLetter(glossaryId)); + await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByAuthor(glossaryId)); + await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByCategory(glossaryId)); + await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByDate(glossaryId, 'CREATION')); + await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntriesByDate(glossaryId, 'UPDATE')); + await CoreUtils.ignoreErrors(this.entries?.getSource().invalidateCache(false)); + + CoreDomUtils.showToast('addon.mod_glossary.entrydeleted', true, ToastDuration.LONG); + + if (this.splitView?.outletActivated) { + await CoreNavigator.navigate('../'); + } else { + await CoreNavigator.back(); + } + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errordeleting', true); + } finally { + modal.dismiss(); + } + } + /** * Refresh the data. * @@ -142,9 +186,11 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { protected async fetchEntry(): Promise { try { const result = await AddonModGlossary.getEntry(this.entryId); + const canDeleteEntries = CoreNetwork.isOnline() && await AddonModGlossary.canDeleteEntries(); this.entry = result.entry; this.ratingInfo = result.ratinginfo; + this.canDelete = canDeleteEntries && !!result.permissions?.candelete; if (this.glossary) { // Glossary already loaded, nothing else to load. diff --git a/src/addons/mod/glossary/services/glossary-offline.ts b/src/addons/mod/glossary/services/glossary-offline.ts index a0dadeee1..b8df14b00 100644 --- a/src/addons/mod/glossary/services/glossary-offline.ts +++ b/src/addons/mod/glossary/services/glossary-offline.ts @@ -19,9 +19,10 @@ import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; +import { CoreEvents } from '@singletons/events'; import { CorePath } from '@singletons/path'; import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary'; -import { AddonModGlossaryDiscardedEntry, AddonModGlossaryEntryOption } from './glossary'; +import { AddonModGlossaryEntryOption, GLOSSARY_ENTRY_ADDED } from './glossary'; /** * Service to handle offline glossary. @@ -159,7 +160,7 @@ export class AddonModGlossaryOfflineProvider { * @param courseId Course ID of the glossary. * @param options Options for the entry. * @param attachments Result of CoreFileUploaderProvider#storeFilesToUpload for attachments. - * @param timeCreated The time the entry was created. If not defined, current time. + * @param 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. @@ -172,12 +173,13 @@ export class AddonModGlossaryOfflineProvider { courseId: number, options?: Record, attachments?: CoreFileUploaderStoreFilesResult, - timeCreated?: number, + timecreated?: number, siteId?: string, userId?: number, discardEntry?: AddonModGlossaryDiscardedEntry, ): Promise { const site = await CoreSites.getSite(siteId); + timecreated = timecreated || Date.now(); const entry: AddonModGlossaryOfflineEntryDBRecord = { glossaryid: glossaryId, @@ -188,7 +190,7 @@ export class AddonModGlossaryOfflineProvider { options: JSON.stringify(options || {}), attachments: JSON.stringify(attachments), userid: userId || site.getUserId(), - timecreated: timeCreated || Date.now(), + timecreated, }; // If editing an offline entry, delete previous first. @@ -198,6 +200,8 @@ export class AddonModGlossaryOfflineProvider { await site.getDb().insertRecord(OFFLINE_ENTRIES_TABLE_NAME, entry); + CoreEvents.trigger(GLOSSARY_ENTRY_ADDED, { glossaryId, timecreated }, siteId); + return false; } diff --git a/src/addons/mod/glossary/services/glossary-sync.ts b/src/addons/mod/glossary/services/glossary-sync.ts index 0fc65ad13..f404366ae 100644 --- a/src/addons/mod/glossary/services/glossary-sync.ts +++ b/src/addons/mod/glossary/services/glossary-sync.ts @@ -31,14 +31,14 @@ import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from './glossar import { CoreFileUploader } from '@features/fileuploader/services/fileuploader'; import { CoreFileEntry } from '@services/file-helper'; +export const GLOSSARY_AUTO_SYNCED = 'addon_mod_glossary_auto_synced'; + /** * Service to sync glossaries. */ @Injectable({ providedIn: 'root' }) export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProvider { - static readonly AUTO_SYNCED = 'addon_mod_glossary_autom_synced'; - protected componentTranslatableString = 'glossary'; constructor() { @@ -98,7 +98,7 @@ export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProv if (result?.updated) { // Sync successful, send event. - CoreEvents.trigger(AddonModGlossarySyncProvider.AUTO_SYNCED, { + CoreEvents.trigger(GLOSSARY_AUTO_SYNCED, { glossaryId: entry.glossaryid, userId: entry.userid, warnings: result.warnings, @@ -341,15 +341,28 @@ export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProv export const AddonModGlossarySync = makeSingleton(AddonModGlossarySyncProvider); +declare module '@singletons/events' { + + /** + * Augment CoreEventsData interface with events specific to this service. + * + * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation + */ + export interface CoreEventsData { + [GLOSSARY_AUTO_SYNCED]: AddonModGlossaryAutoSyncedData; + } + +} + /** * Data returned by a glossary sync. */ export type AddonModGlossarySyncResult = CoreSyncResult; /** - * Data passed to AUTO_SYNCED event. + * Data passed to GLOSSARY_AUTO_SYNCED event. */ -export type AddonModGlossaryAutoSyncData = { +export type AddonModGlossaryAutoSyncedData = { glossaryId: number; userId: number; warnings: string[]; diff --git a/src/addons/mod/glossary/services/glossary.ts b/src/addons/mod/glossary/services/glossary.ts index 831b9876c..3f37c1d27 100644 --- a/src/addons/mod/glossary/services/glossary.ts +++ b/src/addons/mod/glossary/services/glossary.ts @@ -25,11 +25,14 @@ import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@ import { CoreUtils } from '@services/utils/utils'; import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; import { makeSingleton, Translate } from '@singletons'; +import { CoreEvents } from '@singletons/events'; import { AddonModGlossaryEntryDBRecord, ENTRIES_TABLE_NAME } from './database/glossary'; import { AddonModGlossaryOffline } from './glossary-offline'; -import { AddonModGlossaryAutoSyncData, AddonModGlossarySyncProvider } from './glossary-sync'; import { CoreFileEntry } from '@services/file-helper'; +export const GLOSSARY_ENTRY_ADDED = 'addon_mod_glossary_entry_added'; +export const GLOSSARY_ENTRY_DELETED = 'addon_mod_glossary_entry_deleted'; + /** * Service that provides some features for glossaries. */ @@ -40,8 +43,6 @@ export class AddonModGlossaryProvider { static readonly LIMIT_ENTRIES = 25; static readonly LIMIT_CATEGORIES = 10; - static readonly ADD_ENTRY_EVENT = 'addon_mod_glossary_add_entry'; - private static readonly SHOW_ALL_CATEGORIES = 0; private static readonly ROOT_CACHE_KEY = 'mmaModGlossary:'; @@ -606,6 +607,18 @@ export class AddonModGlossaryProvider { throw new CoreError('Entry not found.'); } + /** + * Check whether the site can delete glossary entries. + * + * @param siteId Site id. + * @returns Whether the site can delete entries. + */ + async canDeleteEntries(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + return site.wsAvailable('mod_glossary_delete_entry'); + } + /** * Performs the whole fetch of the entries using the proper function and arguments. * @@ -847,7 +860,7 @@ export class AddonModGlossaryProvider { try { // Try to add it in online. - return await this.addEntryOnline( + const entryId = await this.addEntryOnline( glossaryId, concept, definition, @@ -855,6 +868,8 @@ export class AddonModGlossaryProvider { attachments, otherOptions.siteId, ); + + return entryId; } catch (error) { if (otherOptions.allowOffline && !CoreUtils.isWebServiceError(error)) { // Couldn't connect to server, store in offline. @@ -904,9 +919,25 @@ export class AddonModGlossaryProvider { const response = await site.write('mod_glossary_add_entry', params); + CoreEvents.trigger(GLOSSARY_ENTRY_ADDED, { glossaryId, entryId: response.entryid }, siteId); + return response.entryid; } + /** + * Delete entry. + * + * @param glossaryId Glossary id. + * @param entryId Entry id. + */ + async deleteEntry(glossaryId: number, entryId: number): Promise { + const site = CoreSites.getRequiredCurrentSite(); + + await site.write('mod_glossary_delete_entry', { entryid: entryId }); + + CoreEvents.trigger(GLOSSARY_ENTRY_DELETED, { glossaryId, entryId }); + } + /** * Check if a entry concept is already used. * @@ -1040,18 +1071,27 @@ declare module '@singletons/events' { * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation */ export interface CoreEventsData { - [AddonModGlossaryProvider.ADD_ENTRY_EVENT]: AddonModGlossaryAddEntryEventData; - [AddonModGlossarySyncProvider.AUTO_SYNCED]: AddonModGlossaryAutoSyncData; + [GLOSSARY_ENTRY_ADDED]: AddonModGlossaryEntryAddedEventData; + [GLOSSARY_ENTRY_DELETED]: AddonModGlossaryEntryDeletedEventData; } } /** - * Data passed to ADD_ENTRY_EVENT. + * GLOSSARY_ENTRY_ADDED event payload. */ -export type AddonModGlossaryAddEntryEventData = { +export type AddonModGlossaryEntryAddedEventData = { glossaryId: number; entryId?: number; + timecreated?: number; +}; + +/** + * GLOSSARY_ENTRY_DELETED event payload. + */ +export type AddonModGlossaryEntryDeletedEventData = { + glossaryId: number; + entryId: number; }; /** diff --git a/src/addons/mod/glossary/tests/behat/basic_usage.feature b/src/addons/mod/glossary/tests/behat/basic_usage.feature index 6a32f476c..48bea05b8 100644 --- a/src/addons/mod/glossary/tests/behat/basic_usage.feature +++ b/src/addons/mod/glossary/tests/behat/basic_usage.feature @@ -154,6 +154,54 @@ 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 + + # TODO online + + # Offline + When I press "Add a new entry" in the app + And I switch network connection to offline + And I set the following fields to these values in the app: + | Concept | Broccoli | + | Definition | Brassica oleracea var. italica | + And I press "Save" in the app + Then I should find "Potato" in the app + And I should find "Broccoli" in the app + + When I press "Broccoli" in the app + Then I should find "Brassica oleracea var. italica" in the 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 + + When I set the following fields to these values in the app: + | Concept | Pickle | + | Definition | Pickle Rick | + And I press "Save" in the app + Then I should find "Pickle Rick" in the app + But I should not find "Brassica oleracea var. italica" in the app + + When I press the back button in the app + Then I should find "Pickle" in the 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 + + When I press "Cucumber" in the app + And I press "Delete entry" in the app + And I press "OK" near "Are you sure you want to delete this entry?" in the app + Then I should find "Entry deleted" in the app + And I should find "Potato" in the app + But I should not find "Cucumber" in the app + Scenario: Sync Given I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app And I press "Add a new entry" in the app diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index 395afc37e..39ef612e6 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -1772,9 +1772,9 @@ export class CoreUtilsProvider { * @param fallback Value to return if the promise is rejected. * @returns Promise with ignored errors, resolving to the fallback result if provided. */ - async ignoreErrors(promise: Promise): Promise; + async ignoreErrors(promise?: Promise): Promise; async ignoreErrors(promise: Promise, fallback: Fallback): Promise; - async ignoreErrors(promise: Promise, fallback?: Fallback): Promise { + async ignoreErrors(promise?: Promise, fallback?: Fallback): Promise { try { const result = await promise;