diff --git a/src/addons/notifications/pages/list/list.html b/src/addons/notifications/pages/list/list.html index c526fe5cf..aa11a1300 100644 --- a/src/addons/notifications/pages/list/list.html +++ b/src/addons/notifications/pages/list/list.html @@ -36,7 +36,7 @@ {{ notification.subject }} - {{ notification.userfromfullname }} + 0">{{ notification.userfromfullname }} {{ notification.timecreated | coreDateDayOrTime }} diff --git a/src/addons/notifications/pages/list/list.ts b/src/addons/notifications/pages/list/list.ts index 589e0c8c7..59f83b549 100644 --- a/src/addons/notifications/pages/list/list.ts +++ b/src/addons/notifications/pages/list/list.ts @@ -21,8 +21,11 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreEvents, CoreEventObserver } from '@singletons/events'; -import { AddonNotifications, AddonNotificationsAnyNotification, AddonNotificationsProvider } from '../../services/notifications'; -import { AddonNotificationsHelper } from '../../services/notifications-helper'; +import { + AddonNotifications, + AddonNotificationsNotificationMessageFormatted, + AddonNotificationsProvider, +} from '../../services/notifications'; import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; /** @@ -89,9 +92,7 @@ export class AddonNotificationsListPage implements OnInit, OnDestroy { this.loadMoreError = false; try { - const result = await AddonNotificationsHelper.getNotifications(refresh ? [] : this.notifications, { - onlyPopupNotifications: true, - }); + const result = await AddonNotifications.getNotifications(refresh ? [] : this.notifications); const notifications = result.notifications.map((notification) => this.formatText(notification)); @@ -156,9 +157,9 @@ export class AddonNotificationsListPage implements OnInit, OnDestroy { try { this.loadingMarkAllNotificationsAsRead = true; - const unread = await AddonNotifications.getUnreadNotificationsCount(); + const unreadCountData = await AddonNotifications.getUnreadNotificationsCount(); - this.canMarkAllNotificationsAsRead = unread > 0; + this.canMarkAllNotificationsAsRead = unreadCountData.count > 0; } finally { this.loadingMarkAllNotificationsAsRead = false; } @@ -198,14 +199,14 @@ export class AddonNotificationsListPage implements OnInit, OnDestroy { * * @param notification The notification object. */ - protected formatText(notification: AddonNotificationsAnyNotification): FormattedNotification { + protected formatText(notification: AddonNotificationsNotificationMessageFormatted): FormattedNotification { const formattedNotification: FormattedNotification = notification; formattedNotification.displayfullhtml = this.shouldDisplayFullHtml(notification); formattedNotification.iconurl = formattedNotification.iconurl || undefined; // Make sure the property exists. formattedNotification.mobiletext = formattedNotification.displayfullhtml ? notification.fullmessagehtml : - CoreTextUtils.replaceNewLines(formattedNotification.mobiletext!.replace(/-{4,}/ig, ''), ''); + CoreTextUtils.replaceNewLines((formattedNotification.mobiletext || '').replace(/-{4,}/ig, ''), ''); return formattedNotification; } @@ -253,7 +254,7 @@ export class AddonNotificationsListPage implements OnInit, OnDestroy { } -type FormattedNotification = AddonNotificationsAnyNotification & { +type FormattedNotification = AddonNotificationsNotificationMessageFormatted & { displayfullhtml?: boolean; // Whether to display the full HTML of the notification. iconurl?: string; }; diff --git a/src/addons/notifications/pages/settings/settings.html b/src/addons/notifications/pages/settings/settings.html index 536c149c6..e8e9e7b49 100644 --- a/src/addons/notifications/pages/settings/settings.html +++ b/src/addons/notifications/pages/settings/settings.html @@ -23,7 +23,7 @@ {{ 'addon.notifications.notifications' | translate }} - + {{ 'addon.notifications.playsound' | translate }} diff --git a/src/addons/notifications/pages/settings/settings.ts b/src/addons/notifications/pages/settings/settings.ts index f87ced644..d8cc886d3 100644 --- a/src/addons/notifications/pages/settings/settings.ts +++ b/src/addons/notifications/pages/settings/settings.ts @@ -161,7 +161,7 @@ export class AddonNotificationsSettingsPage implements OnInit, OnDestroy { * @param name Name of the selected processor. */ changeProcessor(name: string): void { - const processor = this.preferences!.processors.find((processor) => processor.name == name); + const processor = this.preferences?.processors.find((processor) => processor.name == name); if (processor) { this.loadProcessor(processor); @@ -246,6 +246,10 @@ export class AddonNotificationsSettingsPage implements OnInit, OnDestroy { * @return Promise resolved when done. */ async enableAll(enable?: boolean): Promise { + if (!this.preferences) { + return; + } + const modal = await CoreDomUtils.showModalLoading('core.sending', true); try { @@ -256,7 +260,7 @@ export class AddonNotificationsSettingsPage implements OnInit, OnDestroy { } catch (error) { // Show error and revert change. CoreDomUtils.showErrorModal(error); - this.preferences!.enableall = !this.preferences!.enableall; + this.preferences.enableall = !this.preferences.enableall; } finally { modal.dismiss(); } diff --git a/src/addons/notifications/services/handlers/mainmenu.ts b/src/addons/notifications/services/handlers/mainmenu.ts index 5cb4584d2..a19caaae9 100644 --- a/src/addons/notifications/services/handlers/mainmenu.ts +++ b/src/addons/notifications/services/handlers/mainmenu.ts @@ -110,10 +110,12 @@ export class AddonNotificationsMainMenuHandlerService implements CoreMainMenuHan } try { - const unreadCount = await AddonNotifications.getUnreadNotificationsCount(undefined, siteId); + const unreadCountData = await AddonNotifications.getUnreadNotificationsCount(undefined, siteId); - this.handlerData.badge = unreadCount > 0 ? String(unreadCount) : ''; - CorePushNotifications.updateAddonCounter('AddonNotifications', unreadCount, siteId); + this.handlerData.badge = unreadCountData.count > 0 ? + unreadCountData.count + (unreadCountData.hasMore ? '+' : '') : + ''; + CorePushNotifications.updateAddonCounter('AddonNotifications', unreadCountData.count, siteId); } catch { this.handlerData.badge = ''; } finally { diff --git a/src/addons/notifications/services/notifications-helper.ts b/src/addons/notifications/services/notifications-helper.ts index 77adc8eec..c83bea266 100644 --- a/src/addons/notifications/services/notifications-helper.ts +++ b/src/addons/notifications/services/notifications-helper.ts @@ -14,20 +14,15 @@ import { Injectable } from '@angular/core'; -import { CoreSites } from '@services/sites'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; import { AddonMessageOutputDelegate } from '@addons/messageoutput/services/messageoutput-delegate'; import { - AddonNotifications, - AddonNotificationsAnyNotification, - AddonNotificationsGetNotificationsOptions, AddonNotificationsPreferences, AddonNotificationsPreferencesComponent, AddonNotificationsPreferencesNotification, AddonNotificationsPreferencesNotificationProcessor, AddonNotificationsPreferencesProcessor, - AddonNotificationsProvider, } from './notifications'; /** @@ -58,57 +53,6 @@ export class AddonNotificationsHelperProvider { return formattedPreferences; } - /** - * Get some notifications. It will try to use the new WS if available. - * - * @param notifications Current list of loaded notifications. It's used to calculate the offset. - * @param options Other options. - * @return Promise resolved with notifications and if can load more. - */ - async getNotifications( - notifications: AddonNotificationsAnyNotification[], - options?: AddonNotificationsHelperGetNotificationsOptions, - ): Promise<{notifications: AddonNotificationsAnyNotification[]; canLoadMore: boolean}> { - - notifications = notifications || []; - options = options || {}; - options.limit = options.limit || AddonNotificationsProvider.LIST_LIMIT; - options.siteId = options.siteId || CoreSites.getCurrentSiteId(); - - if (options.onlyPopupNotifications) { - return AddonNotifications.getPopupNotifications(notifications.length, options); - } - - // Use get_messages. We need 2 calls, one for read and the other one for unread. - const unreadFrom = notifications.reduce((total, current) => total + (current.read ? 0 : 1), 0); - - const unread = await AddonNotifications.getUnreadNotifications(unreadFrom, options); - - let newNotifications = unread; - - if (unread.length < options.limit) { - // Limit not reached. Get read notifications until reach the limit. - const readLimit = options.limit - unread.length; - const readFrom = notifications.length - unreadFrom; - const readOptions = Object.assign({}, options, { limit: readLimit }); - - try { - const read = await AddonNotifications.getReadNotifications(readFrom, readOptions); - - newNotifications = unread.concat(read); - } catch (error) { - if (unread.length <= 0) { - throw error; - } - } - } - - return { - notifications: newNotifications, - canLoadMore: notifications.length >= options.limit, - }; - } - /** * Get a certain processor from a list of processors. * @@ -207,10 +151,3 @@ export type AddonNotificationsPreferencesNotificationFormatted = AddonNotificati export type AddonNotificationsPreferencesProcessorFormatted = AddonNotificationsPreferencesProcessor & { supported?: boolean; // Calculated in the app. Whether the processor is supported in the app. }; - -/** - * Options to pass to getNotifications. - */ -export type AddonNotificationsHelperGetNotificationsOptions = AddonNotificationsGetNotificationsOptions & { - onlyPopupNotifications?: boolean; // Whether to get only popup notifications. -}; diff --git a/src/addons/notifications/services/notifications.ts b/src/addons/notifications/services/notifications.ts index ed445daf1..4678f9393 100644 --- a/src/addons/notifications/services/notifications.ts +++ b/src/addons/notifications/services/notifications.ts @@ -22,6 +22,7 @@ import { CoreUser } from '@features/user/services/user'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreLogger } from '@singletons/logger'; import { makeSingleton } from '@singletons'; +import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; const ROOT_CACHE_KEY = 'mmaNotifications:'; @@ -46,24 +47,14 @@ export class AddonNotificationsProvider { * Function to format notification data. * * @param notifications List of notifications. - * @param read Whether the notifications are read or unread. * @return Promise resolved with notifications. */ protected async formatNotificationsData( - notifications: AddonNotificationsGetMessagesMessage[], - read?: boolean, - ): Promise; - protected async formatNotificationsData( - notifications: AddonNotificationsPopupNotification[], - read?: boolean, - ): Promise; - protected async formatNotificationsData( - notifications: (AddonNotificationsGetMessagesMessage | AddonNotificationsPopupNotification)[], - read?: boolean, - ): Promise { + notifications: AddonNotificationsNotificationMessage[], + ): Promise { const promises = notifications.map(async (notificationRaw) => { - const notification = notificationRaw; + const notification = notificationRaw; // Set message to show. if (notification.component && notification.component == 'mod_forum') { @@ -75,9 +66,7 @@ export class AddonNotificationsProvider { notification.moodlecomponent = notification.component; notification.notification = 1; notification.notif = 1; - if (typeof read != 'undefined') { - notification.read = read; - } + notification.read = notification.timeread > 0; if (typeof notification.customdata == 'string') { notification.customdata = CoreTextUtils.parseJSON>(notification.customdata, {}); @@ -93,6 +82,15 @@ export class AddonNotificationsProvider { } } + if (!notification.iconurl) { + // The iconurl is only returned in 4.0 or above. Calculate it if not present. + if (notification.component && notification.component.startsWith('mod_')) { + notification.iconurl = await CoreCourseModuleDelegate.getModuleIconSrc( + notification.component.replace('mod_', ''), + ); + } + } + if (notification.useridfrom > 0) { // Try to get the profile picture of the user. try { @@ -153,32 +151,99 @@ export class AddonNotificationsProvider { return ROOT_CACHE_KEY + 'list'; } + /** + * Get some notifications. + * + * @param notifications Current list of loaded notifications. It's used to calculate the offset. + * @param options Other options. + * @return Promise resolved with notifications and if can load more. + */ + async getNotifications( + notifications: AddonNotificationsNotificationMessageFormatted[], + options?: AddonNotificationsGetNotificationsOptions, + ): Promise<{notifications: AddonNotificationsNotificationMessageFormatted[]; canLoadMore: boolean}> { + + notifications = notifications || []; + options = options || {}; + options.limit = options.limit || AddonNotificationsProvider.LIST_LIMIT; + options.siteId = options.siteId || CoreSites.getCurrentSiteId(); + let newNotifications: AddonNotificationsNotificationMessageFormatted[]; + + // Request 1 more notification so we can know if there are more notifications. + const originalLimit = options.limit; + options.limit + 1; + + const site = await CoreSites.getSite(options.siteId); + + if (site.isVersionGreaterEqualThan('4.0')) { + // In 4.0 the app can request read and unread at the same time. + options.offset = notifications.length; + newNotifications = await this.getNotificationsWithStatus( + AddonNotificationsGetReadType.BOTH, + options, + ); + } else { + // We need 2 calls, one for read and the other one for unread. + options.offset = notifications.reduce((total, current) => total + (current.read ? 0 : 1), 0); + + const unread = await this.getNotificationsWithStatus(AddonNotificationsGetReadType.UNREAD, options); + + newNotifications = unread; + + if (unread.length < options.limit) { + // Limit not reached. Get read notifications until reach the limit. + const readOptions = { + ...options, + offset: notifications.length - options.offset, + limit: options.limit - unread.length, + }; + + try { + const read = await this.getNotificationsWithStatus(AddonNotificationsGetReadType.READ, readOptions); + + newNotifications = unread.concat(read); + } catch (error) { + if (unread.length <= 0) { + throw error; + } + } + } + } + + return { + notifications: newNotifications.slice(0, originalLimit), + canLoadMore: newNotifications.length > originalLimit, + }; + } + /** * Get notifications from site. * * @param read True if should get read notifications, false otherwise. - * @param offset Position of the first notification to get. * @param options Other options. * @return Promise resolved with notifications. */ - async getNotifications( - read: boolean, - offset: number, - options?: AddonNotificationsGetNotificationsOptions, - ): Promise { - options = options || {}; + protected async getNotificationsWithStatus( + read: AddonNotificationsGetReadType, + options: AddonNotificationsGetNotificationsOptions = {}, + ): Promise { + options.offset = options.offset || 0; options.limit = options.limit || AddonNotificationsProvider.LIST_LIMIT; - this.logger.debug(`Get ${(read ? 'read' : 'unread')} notifications from ${offset}. Limit: ${options.limit}`); + const typeText = read === AddonNotificationsGetReadType.READ ? + 'read' : + (read === AddonNotificationsGetReadType.UNREAD ? 'unread' : 'read and unread'); + this.logger.debug(`Get ${typeText} notifications from ${options.offset}. Limit: ${options.limit}`); const site = await CoreSites.getSite(options.siteId); + const data: AddonNotificationsGetMessagesWSParams = { useridto: site.getUserId(), useridfrom: 0, type: 'notifications', - read: !!read, + read: read, newestfirst: true, - limitfrom: offset, + limitfrom: options.offset, limitnum: options.limit, }; const preSets: CoreSiteWSPreSets = { @@ -191,78 +256,7 @@ export class AddonNotificationsProvider { const notifications = response.messages; - return this.formatNotificationsData(notifications, read); - } - - /** - * Get notifications from site using the new WebService. - * - * @param offset Position of the first notification to get. - * @param options Other options. - * @return Promise resolved with notifications and if can load more. - */ - async getPopupNotifications( - offset: number, - options?: AddonNotificationsGetNotificationsOptions, - ): Promise<{notifications: AddonNotificationsPopupNotificationFormatted[]; canLoadMore: boolean}> { - options = options || {}; - options.limit = options.limit || AddonNotificationsProvider.LIST_LIMIT; - - this.logger.debug(`Get popup notifications from ${offset}. Limit: ${options.limit}`); - - const site = await CoreSites.getSite(options.siteId); - const data: AddonNotificationsPopupGetPopupNotificationsWSParams = { - useridto: site.getUserId(), - newestfirst: true, - offset, - limit: options.limit + 1, // Get one more to calculate canLoadMore. - }; - const preSets: CoreSiteWSPreSets = { - cacheKey: this.getNotificationsCacheKey(), - ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. - }; - - // Get notifications. - const response = await site.read( - 'message_popup_get_popup_notifications', - data, - preSets, - ); - - const notifications = await this.formatNotificationsData(response.notifications.slice(0, options.limit)); - - return { - canLoadMore: response.notifications.length > options.limit, - notifications, - }; - } - - /** - * Get read notifications from site. - * - * @param offset Position of the first notification to get. - * @param options Other options. - * @return Promise resolved with notifications. - */ - getReadNotifications( - offset: number, - options?: AddonNotificationsGetNotificationsOptions, - ): Promise { - return this.getNotifications(true, offset, options); - } - - /** - * Get unread notifications from site. - * - * @param offset Position of the first notification to get. - * @param options Other options. - * @return Promise resolved with notifications. - */ - getUnreadNotifications( - offset: number, - options?: AddonNotificationsGetNotificationsOptions, - ): Promise { - return this.getNotifications(false, offset, options); + return this.formatNotificationsData(notifications); } /** @@ -272,26 +266,65 @@ export class AddonNotificationsProvider { * @param siteId Site ID. If not defined, use current site. * @return Promise resolved with the message notifications count. */ - async getUnreadNotificationsCount(userId?: number, siteId?: string): Promise { + async getUnreadNotificationsCount(userId?: number, siteId?: string): Promise<{ count: number; hasMore: boolean} > { const site = await CoreSites.getSite(siteId); - userId = userId || site.getUserId(); - const params: AddonNotificationsPopupGetUnreadPopupNotificationCountWSParams = { - useridto: userId, - }; - const preSets: CoreSiteWSPreSets = { - getFromCache: false, - emergencyCache: false, - saveToCache: false, - typeExpected: 'number', - }; + // @since 4.0 + if (site.wsAvailable('core_message_get_unread_notification_count')) { + const params: CoreMessageGetUnreadNotificationCountWSParams = { + useridto: userId || site.getUserId(), + }; - try { - return await site.read('message_popup_get_unread_popup_notification_count', params, preSets); - } catch { - // Return no messages if the call fails. - return 0; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getUnreadNotificationsCountCacheKey(params.useridto), + getFromCache: false, // Always try to get the latest number. + typeExpected: 'number', + }; + + try { + const count = await site.read('core_message_get_unread_notification_count', params, preSets); + + return { + count, + hasMore: false, + }; + } catch { + // Return no notifications if the call fails. + return { + count: 0, + hasMore: false, + }; + } } + + // Fallback call + try { + const unread = await this.getNotificationsWithStatus(AddonNotificationsGetReadType.UNREAD, { + limit: AddonNotificationsProvider.LIST_LIMIT + 1, + siteId, + }); + + return { + count: Math.min(unread.length, AddonNotificationsProvider.LIST_LIMIT), + hasMore: unread.length > AddonNotificationsProvider.LIST_LIMIT, + }; + } catch { + // Return no notifications if the call fails. + return { + count: 0, + hasMore: false, + }; + } + } + + /** + * Get cache key for unread notifications count WS calls. + * + * @param userId User ID. + * @return Cache key. + */ + protected getUnreadNotificationsCountCacheKey(userId: number): string { + return `${ROOT_CACHE_KEY}count:${userId}`; } /** @@ -427,7 +460,7 @@ export type AddonNotificationsGetMessagesWSParams = { useridto: number; // The user id who received the message, 0 for any user. useridfrom?: number; // The user id who send the message, 0 for any user. -10 or -20 for no-reply or support user. type?: string; // Type of message to return, expected values are: notifications, conversations and both. - read?: boolean; // True for getting read messages, false for unread. + read?: AddonNotificationsGetReadType; // 0=unread, 1=read. @since 4.0 it also accepts 2=both. newestfirst?: boolean; // True for ordering by newest first, false for oldest first. limitfrom?: number; // Limit from. limitnum?: number; // Limit number. @@ -437,14 +470,14 @@ export type AddonNotificationsGetMessagesWSParams = { * Data returned by core_message_get_messages WS. */ export type AddonNotificationsGetMessagesWSResponse = { - messages: AddonNotificationsGetMessagesMessage[]; + messages: AddonNotificationsNotificationMessage[]; warnings?: CoreWSExternalWarning[]; }; /** * Message data returned by core_message_get_messages. */ -export type AddonNotificationsGetMessagesMessage = { +export type AddonNotificationsNotificationMessage = { id: number; // Message id. useridfrom: number; // User from id. useridto: number; // User to id. @@ -464,70 +497,14 @@ export type AddonNotificationsGetMessagesMessage = { component?: string; // @since 3.7. The component that generated the notification. eventtype?: string; // @since 3.7. The type of notification. customdata?: string; // @since 3.7. Custom data to be passed to the message processor. + iconurl?: string; // @since 4.0. Icon URL, only for notifications. }; /** * Message data returned by core_message_get_messages with some calculated data. */ -export type AddonNotificationsGetMessagesMessageFormatted = - Omit & AddonNotificationsNotificationCalculatedData; - -/** - * Params of message_popup_get_popup_notifications WS. - */ -export type AddonNotificationsPopupGetPopupNotificationsWSParams = { - useridto: number; // The user id who received the message, 0 for current user. - newestfirst?: boolean; // True for ordering by newest first, false for oldest first. - limit?: number; // The number of results to return. - offset?: number; // Offset the result set by a given amount. -}; - -/** - * Result of WS message_popup_get_popup_notifications. - */ -export type AddonNotificationsGetPopupNotificationsResult = { - notifications: AddonNotificationsPopupNotification[]; - unreadcount: number; // The number of unread message for the given user. -}; - -/** - * Notification returned by message_popup_get_popup_notifications. - */ -export type AddonNotificationsPopupNotification = { - id: number; // Notification id (this is not guaranteed to be unique within this result set). - useridfrom: number; // User from id. - useridto: number; // User to id. - subject: string; // The notification subject. - shortenedsubject: string; // The notification subject shortened with ellipsis. - text: string; // The message text formated. - fullmessage: string; // The message. - fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). - fullmessagehtml: string; // The message in html. - smallmessage: string; // The shorten message. - contexturl: string; // Context URL. - contexturlname: string; // Context URL link name. - timecreated: number; // Time created. - timecreatedpretty: string; // Time created in a pretty format. - timeread: number; // Time read. - read: boolean; // Notification read status. - deleted: boolean; // Notification deletion status. - iconurl: string; // URL for notification icon. - component?: string; // The component that generated the notification. - eventtype?: string; // The type of notification. - customdata?: string; // @since 3.7. Custom data to be passed to the message processor. -}; - -/** - * Notification returned by message_popup_get_popup_notifications. - */ -export type AddonNotificationsPopupNotificationFormatted = - Omit & AddonNotificationsNotificationCalculatedData; - -/** - * Any kind of notification that can be retrieved. - */ -export type AddonNotificationsAnyNotification = - AddonNotificationsPopupNotificationFormatted | AddonNotificationsGetMessagesMessageFormatted; +export type AddonNotificationsNotificationMessageFormatted = + Omit & AddonNotificationsNotificationCalculatedData; /** * Result of WS core_message_get_user_notification_preferences. @@ -552,13 +529,6 @@ export type AddonNotificationsNotificationCalculatedData = { customdata?: Record; // Parsed custom data. }; -/** - * Params of message_popup_get_unread_popup_notification_count WS. - */ -export type AddonNotificationsPopupGetUnreadPopupNotificationCountWSParams = { - useridto: number; // The user id who received the message, 0 for any user. -}; - /** * Params of core_message_mark_all_notifications_as_read WS. */ @@ -585,8 +555,25 @@ export type CoreMessageMarkNotificationReadWSResponse = { }; /** - * Options to pass to getNotifications and getPopupNotifications. + * Params of core_message_get_unread_notification_count WS. + */ +export type CoreMessageGetUnreadNotificationCountWSParams = { + useridto: number; // User id who received the notification, 0 for any user. +}; + +/** + * Options to pass to getNotifications. */ export type AddonNotificationsGetNotificationsOptions = CoreSitesCommonWSOptions & { + offset?: number; // Offset to use. Defaults to 0. limit?: number; // Number of notifications to get. Defaults to LIST_LIMIT. }; + +/** + * Constants to get either read, unread or both notifications. + */ +export enum AddonNotificationsGetReadType { + UNREAD = 0, + READ = 1, + BOTH = 2, +} diff --git a/src/core/classes/site.ts b/src/core/classes/site.ts index 655b654c2..3d7a70130 100644 --- a/src/core/classes/site.ts +++ b/src/core/classes/site.ts @@ -86,6 +86,7 @@ export class CoreSite { '3.9': 2020061500, '3.10': 2020110900, '3.11': 2021051700, + '4.0': 2021100300, // @todo [4.0] replace with right value when released. Using a tmp value to be able to test new things. }; // Possible cache update frequencies.
{{ notification.subject }}
{{ notification.userfromfullname }}
0">{{ notification.userfromfullname }}