diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 071a24b46..ed8018df9 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -22,6 +22,8 @@ import { AddonModForumProvider, AddonModForumSortOrder, AddonModForumDiscussion, + AddonModForumNewDiscussionData, + AddonModForumReplyDiscussionData, } from '@addons/mod/forum/services/forum.service'; import { AddonModForumOffline, AddonModForumOfflineDiscussion } from '@addons/mod/forum/services/offline.service'; import { ModalController, PopoverController, Translate } from '@singletons'; @@ -127,7 +129,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom AddonModForumProvider.REPLY_DISCUSSION_EVENT, this.eventReceived.bind(this, false), ); - this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data: any) => { + this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data => { if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module!.id) { AddonModForum.instance.invalidateDiscussionsList(this.forum!.id).finally(() => { if (data.discussionId) { @@ -152,7 +154,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom } if (typeof data.deleted != 'undefined' && data.deleted) { - if (data.post.parentid == 0 && CoreScreen.instance.isTablet && !this.discussions.empty) { + if (data.post?.parentid == 0 && CoreScreen.instance.isTablet && !this.discussions.empty) { // Discussion deleted, clear details page. this.discussions.select(this.discussions[0]); } @@ -275,7 +277,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom if (updated) { // Sync successful, send event. - CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, { + CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, { forumId: forum.id, userId: CoreSites.instance.getCurrentSiteUserId(), source: 'index', @@ -553,18 +555,22 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom * @param isNewDiscussion Whether it's a new discussion event. * @param data Event data. */ - protected eventReceived(isNewDiscussion: boolean, data: any): void { + protected eventReceived( + isNewDiscussion: boolean, + data: AddonModForumNewDiscussionData | AddonModForumReplyDiscussionData, + ): void { if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module?.id) { this.showLoadingAndRefresh(false).finally(() => { // If it's a new discussion in tablet mode, try to open it. if (isNewDiscussion && CoreScreen.instance.isTablet) { + const newDiscussionData = data as AddonModForumNewDiscussionData; const discussion = this.discussions.items.find(disc => { if (this.discussions.isOfflineDiscussion(disc)) { - return disc.timecreated === data.discTimecreated; + return disc.timecreated === newDiscussionData.discTimecreated; } if (this.discussions.isOnlineDiscussion(disc)) { - return CoreArray.contains(data.discussionIds, disc.discussion); + return CoreArray.contains(newDiscussionData.discussionIds ?? [], disc.discussion); } return false; diff --git a/src/addons/mod/forum/pages/discussion/discussion.page.ts b/src/addons/mod/forum/pages/discussion/discussion.page.ts index d36221650..5f8bc2d1d 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.page.ts +++ b/src/addons/mod/forum/pages/discussion/discussion.page.ts @@ -37,7 +37,7 @@ import { } from '../../services/forum.service'; import { AddonModForumHelper } from '../../services/helper.service'; import { AddonModForumOffline } from '../../services/offline.service'; -import { AddonModForumManualSyncData, AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync.service'; +import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync.service'; type SortType = 'flat-newest' | 'flat-oldest' | 'nested'; @@ -170,7 +170,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes } // Refresh data if this discussion is synchronized automatically. - this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, (data: any) => { + this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, data => { if (data.forumId == this.forumId && this.discussionId == data.discussionId && data.userId == CoreSites.instance.getCurrentSiteUserId()) { // Refresh the data. @@ -180,7 +180,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes }, CoreSites.instance.getCurrentSiteId()); // Refresh data if this forum discussion is synchronized from discussions list. - this.syncManualObserver = CoreEvents.on(AddonModForumSyncProvider.MANUAL_SYNCED, (data: AddonModForumManualSyncData) => { + this.syncManualObserver = CoreEvents.on(AddonModForumSyncProvider.MANUAL_SYNCED, data => { if (data.source != 'discussion' && data.forumId == this.forumId && data.userId == CoreSites.instance.getCurrentSiteUserId()) { // Refresh the data. @@ -196,7 +196,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes // @todo Listen for offline ratings saved and synced. - this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data: any) => { + this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data => { if ((this.forumId && this.forumId === data.forumId) || data.cmId === this.cmId) { AddonModForum.instance.invalidateDiscussionsList(this.forumId).finally(() => { if (typeof data.locked != 'undefined') { @@ -210,7 +210,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes } if (typeof data.deleted != 'undefined' && data.deleted) { - if (!data.post.parentid) { + if (!data.post?.parentid) { // @todo // if (this.svComponent && this.svComponent.isOn()) { // this.svComponent.emptyDetails(); @@ -545,7 +545,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes if (result && result.updated) { // Sync successful, send event. - CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, { + CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, { forumId: this.forumId, userId: CoreSites.instance.getCurrentSiteUserId(), source: 'discussion', diff --git a/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts b/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts index b6341cbc5..17a956650 100644 --- a/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts +++ b/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts @@ -120,7 +120,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea } // Refresh data if this discussion is synchronized automatically. - this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, (data: any) => { + this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, data => { if (data.forumId == this.forumId && data.userId == CoreSites.instance.getCurrentSiteUserId()) { CoreDomUtils.instance.showAlertTranslated('core.notice', 'core.contenteditingsynced'); this.returnToDiscussions(); diff --git a/src/addons/mod/forum/services/forum.service.ts b/src/addons/mod/forum/services/forum.service.ts index 201f252ea..844afb822 100644 --- a/src/addons/mod/forum/services/forum.service.ts +++ b/src/addons/mod/forum/services/forum.service.ts @@ -30,6 +30,22 @@ import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumRepl const ROOT_CACHE_KEY = 'mmaModForum:'; +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 { + [AddonModForumProvider.NEW_DISCUSSION_EVENT]: AddonModForumNewDiscussionData; + [AddonModForumProvider.REPLY_DISCUSSION_EVENT]: AddonModForumReplyDiscussionData; + [AddonModForumProvider.CHANGE_DISCUSSION_EVENT]: AddonModForumChangeDiscussionData; + [AddonModForumProvider.MARK_READ_EVENT]: AddonModForumMarkReadData; + } + +} + /** * Service that provides some features for forums. * @@ -2151,3 +2167,44 @@ export type AddonModForumUpdateDiscussionPostWSParams = { * Data returned by mod_forum_update_discussion_post WS. */ export type AddonModForumUpdateDiscussionPostWSResponse = CoreStatusWithWarningsWSResponse; + +/** + * Data passed to NEW_DISCUSSION_EVENT event. + */ +export type AddonModForumNewDiscussionData = { + forumId: number; + cmId: number; + discussionIds?: number[] | null; + discTimecreated?: number; +}; + +/** + * Data passed to REPLY_DISCUSSION_EVENT event. + */ +export type AddonModForumReplyDiscussionData = { + forumId: number; + discussionId: number; + cmId: number; +}; + +/** + * Data passed to CHANGE_DISCUSSION_EVENT event. + */ +export type AddonModForumChangeDiscussionData = { + forumId: number; + discussionId: number; + cmId: number; + deleted?: boolean; + post?: AddonModForumPost; + locked?: boolean; + pinned?: boolean; + starred?: boolean; +}; + +/** + * Data passed to MARK_READ_EVENT event. + */ +export type AddonModForumMarkReadData = { + courseId: number; + moduleId: number; +}; diff --git a/src/addons/mod/forum/services/handlers/module.ts b/src/addons/mod/forum/services/handlers/module.ts index 3477ada82..cccd98a82 100644 --- a/src/addons/mod/forum/services/handlers/module.ts +++ b/src/addons/mod/forum/services/handlers/module.ts @@ -95,7 +95,7 @@ export class AddonModForumModuleHandlerService implements CoreCourseModuleHandle const event = CoreEvents.on( AddonModForumProvider.MARK_READ_EVENT, - (eventData: { courseId?: number; moduleId?: number; siteId?: string }) => { + eventData => { if (eventData.courseId !== courseId || eventData.moduleId !== module.id) { return; } diff --git a/src/addons/mod/forum/services/sync.service.ts b/src/addons/mod/forum/services/sync.service.ts index 716e55b9b..100b29fc3 100644 --- a/src/addons/mod/forum/services/sync.service.ts +++ b/src/addons/mod/forum/services/sync.service.ts @@ -35,6 +35,20 @@ import { import { AddonModForumHelper } from './helper.service'; import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from './offline.service'; +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 { + [AddonModForumSyncProvider.AUTO_SYNCED]: AddonModForumAutoSyncData; + [AddonModForumSyncProvider.MANUAL_SYNCED]: AddonModForumManualSyncData; + } + +} + /** * Service to sync forums. */ @@ -95,7 +109,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider(AddonModForumSyncProvider.AUTO_SYNCED, { + CoreEvents.trigger(AddonModForumSyncProvider.AUTO_SYNCED, { forumId: discussion.forumid, userId: discussion.userid, warnings: result.warnings, @@ -127,7 +141,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider(AddonModForumSyncProvider.AUTO_SYNCED, { + CoreEvents.trigger(AddonModForumSyncProvider.AUTO_SYNCED, { forumId: reply.forumid, discussionId: reply.discussionid, userId: reply.userid, diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts index 030b21ce7..cc311602e 100644 --- a/src/core/singletons/events.ts +++ b/src/core/singletons/events.ts @@ -28,6 +28,25 @@ export interface CoreEventObserver { off: () => void; } +/** + * Event payloads. + */ +export interface CoreEventsData { + [CoreEvents.SITE_UPDATED]: CoreEventSiteUpdatedData; + [CoreEvents.SITE_ADDED]: CoreEventSiteAddedData; + [CoreEvents.SESSION_EXPIRED]: CoreEventSessionExpiredData; + [CoreEvents.CORE_LOADING_CHANGED]: CoreEventLoadingChangedData; + [CoreEvents.COURSE_STATUS_CHANGED]: CoreEventCourseStatusChanged; + [CoreEvents.PACKAGE_STATUS_CHANGED]: CoreEventPackageStatusChanged; + [CoreEvents.USER_DELETED]: CoreEventUserDeletedData; + [CoreEvents.FORM_ACTION]: CoreEventFormActionData; + [CoreEvents.NOTIFICATION_SOUND_CHANGED]: CoreEventNotificationSoundChangedData; + [CoreEvents.SELECT_COURSE_TAB]: CoreEventSelectCourseTabData; + [CoreEvents.COMPLETION_MODULE_VIEWED]: CoreEventCompletionModuleViewedData; + [CoreEvents.SECTION_STATUS_CHANGED]: CoreEventSectionStatusChangedData; + [CoreEvents.ACTIVITY_DATA_SENT]: CoreEventActivityDataSentData; +}; + /* * Service to send and listen to events. */ @@ -84,15 +103,15 @@ export class CoreEvents { * @param siteId Site where to trigger the event. Undefined won't check the site. * @return Observer to stop listening. */ - static on( - eventName: string, - callBack: (value: T & { siteId?: string }) => void, + static on( + eventName: Event, + callBack: (value: CoreEventData & { siteId?: string }) => void, siteId?: string, ): CoreEventObserver { // If it's a unique event and has been triggered already, call the callBack. // We don't need to create an observer because the event won't be triggered again. if (this.uniqueEvents[eventName]) { - callBack( this.uniqueEvents[eventName].data); + callBack(this.uniqueEvents[eventName].data as CoreEventData & { siteId?: string }); // Return a fake observer to prevent errors. return { @@ -106,14 +125,16 @@ export class CoreEvents { if (typeof this.observables[eventName] == 'undefined') { // No observable for this event, create a new one. - this.observables[eventName] = new Subject(); + this.observables[eventName] = new Subject(); } - const subscription = this.observables[eventName].subscribe((value: T & {siteId?: string}) => { - if (!siteId || value.siteId == siteId) { - callBack(value); - } - }); + const subscription = this.observables[eventName].subscribe( + (value: CoreEventData & { siteId?: string }) => { + if (!siteId || value.siteId == siteId) { + callBack(value); + } + }, + ); // Create and return a CoreEventObserver. return { @@ -155,7 +176,11 @@ export class CoreEvents { * @param data Data to pass to the observers. * @param siteId Site where to trigger the event. Undefined means no Site. */ - static trigger(eventName: string, data?: T, siteId?: string): void { + static trigger( + eventName: Event, + data?: CoreEventData, + siteId?: string, + ): void { this.logger.debug(`Event '${eventName}' triggered.`); if (this.observables[eventName]) { if (siteId) { @@ -172,7 +197,11 @@ export class CoreEvents { * @param data Data to pass to the observers. * @param siteId Site where to trigger the event. Undefined means no Site. */ - static triggerUnique(eventName: string, data: T, siteId?: string): void { + static triggerUnique( + eventName: Event, + data: CoreEventData, + siteId?: string, + ): void { if (this.uniqueEvents[eventName]) { this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`); } else { @@ -196,6 +225,11 @@ export class CoreEvents { } +/** + * Resolve payload type for a given event. + */ +export type CoreEventData = Event extends keyof CoreEventsData ? CoreEventsData[Event] : Fallback; + /** * Some events contains siteId added by the trigger function. This type is intended to be combined with others. */