diff --git a/app/javascript/mastodon/features/notifications_v2/index.tsx b/app/javascript/mastodon/features/notifications_v2/index.tsx index 05dcf422e9..21afd9516e 100644 --- a/app/javascript/mastodon/features/notifications_v2/index.tsx +++ b/app/javascript/mastodon/features/notifications_v2/index.tsx @@ -26,6 +26,7 @@ import type { NotificationGap } from 'mastodon/reducers/notification_groups'; import { selectUnreadNotificationGroupsCount, selectPendingNotificationGroupsCount, + selectAnyPendingNotification, } from 'mastodon/selectors/notifications'; import { selectNeedsNotificationPermission, @@ -95,7 +96,7 @@ export const Notifications: React.FC<{ const lastReadId = useAppSelector((s) => selectSettingsNotificationsShowUnread(s) - ? s.notificationGroups.lastReadId + ? s.notificationGroups.readMarkerId : '0', ); @@ -105,11 +106,13 @@ export const Notifications: React.FC<{ selectUnreadNotificationGroupsCount, ); + const anyPendingNotification = useAppSelector(selectAnyPendingNotification); + const isUnread = unreadNotificationsCount > 0; const canMarkAsRead = useAppSelector(selectSettingsNotificationsShowUnread) && - unreadNotificationsCount > 0; + anyPendingNotification; const needsNotificationPermission = useAppSelector( selectNeedsNotificationPermission, diff --git a/app/javascript/mastodon/reducers/notification_groups.ts b/app/javascript/mastodon/reducers/notification_groups.ts index e59f3e7ca1..3a36023211 100644 --- a/app/javascript/mastodon/reducers/notification_groups.ts +++ b/app/javascript/mastodon/reducers/notification_groups.ts @@ -48,6 +48,7 @@ interface NotificationGroupsState { scrolledToTop: boolean; isLoading: boolean; lastReadId: string; + readMarkerId: string; mounted: number; isTabVisible: boolean; } @@ -58,7 +59,8 @@ const initialState: NotificationGroupsState = { scrolledToTop: false, isLoading: false, // The following properties are used to track unread notifications - lastReadId: '0', // used for unread notifications + lastReadId: '0', // used internally for unread notifications + readMarkerId: '0', // user-facing and updated when focus changes mounted: 0, // number of mounted notification list components, usually 0 or 1 isTabVisible: true, }; @@ -284,6 +286,12 @@ function updateLastReadId( } } +function commitLastReadId(state: NotificationGroupsState) { + if (shouldMarkNewNotificationsAsRead(state)) { + state.readMarkerId = state.lastReadId; + } +} + export const notificationGroupsReducer = createReducer( initialState, (builder) => { @@ -457,6 +465,7 @@ export const notificationGroupsReducer = createReducer( compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0 ) state.lastReadId = mostRecentGroup.page_max_id; + commitLastReadId(state); }) .addCase(fetchMarkers.fulfilled, (state, action) => { if ( @@ -470,6 +479,7 @@ export const notificationGroupsReducer = createReducer( }) .addCase(mountNotifications, (state) => { state.mounted += 1; + commitLastReadId(state); updateLastReadId(state); }) .addCase(unmountNotifications, (state) => { @@ -477,6 +487,7 @@ export const notificationGroupsReducer = createReducer( }) .addCase(focusApp, (state) => { state.isTabVisible = true; + commitLastReadId(state); updateLastReadId(state); }) .addCase(unfocusApp, (state) => { diff --git a/app/javascript/mastodon/selectors/notifications.ts b/app/javascript/mastodon/selectors/notifications.ts index 1b1ed2154c..962dedd650 100644 --- a/app/javascript/mastodon/selectors/notifications.ts +++ b/app/javascript/mastodon/selectors/notifications.ts @@ -27,6 +27,22 @@ export const selectUnreadNotificationGroupsCount = createSelector( }, ); +// Whether there is any unread notification according to the user-facing state +export const selectAnyPendingNotification = createSelector( + [ + (s: RootState) => s.notificationGroups.readMarkerId, + (s: RootState) => s.notificationGroups.groups, + ], + (notificationMarker, groups) => { + return groups.some( + (group) => + group.type !== 'gap' && + group.page_max_id && + compareId(group.page_max_id, notificationMarker) > 0, + ); + }, +); + export const selectPendingNotificationGroupsCount = createSelector( [(s: RootState) => s.notificationGroups.pendingGroups], (pendingGroups) =>