diff --git a/app/javascript/flavours/glitch/actions/notification_groups.ts b/app/javascript/flavours/glitch/actions/notification_groups.ts index e39c9d6846..f85dfac933 100644 --- a/app/javascript/flavours/glitch/actions/notification_groups.ts +++ b/app/javascript/flavours/glitch/actions/notification_groups.ts @@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts'; import type { ApiNotificationGroupJSON, ApiNotificationJSON, + NotificationType, } from 'flavours/glitch/api_types/notifications'; import { allNotificationTypes } from 'flavours/glitch/api_types/notifications'; import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses'; @@ -15,6 +16,7 @@ import { usePendingItems } from 'flavours/glitch/initial_state'; import type { NotificationGap } from 'flavours/glitch/reducers/notification_groups'; import { selectSettingsNotificationsExcludedTypes, + selectSettingsNotificationsGroupFollows, selectSettingsNotificationsQuickFilterActive, selectSettingsNotificationsShows, } from 'flavours/glitch/selectors/settings'; @@ -68,17 +70,19 @@ function dispatchAssociatedRecords( dispatch(importFetchedStatuses(fetchedStatuses)); } -const supportedGroupedNotificationTypes = ['favourite', 'reblog']; +function selectNotificationGroupedTypes(state: RootState) { + const types: NotificationType[] = ['favourite', 'reblog']; -export function shouldGroupNotificationType(type: string) { - return supportedGroupedNotificationTypes.includes(type); + if (selectSettingsNotificationsGroupFollows(state)) types.push('follow'); + + return types; } export const fetchNotifications = createDataLoadingThunk( 'notificationGroups/fetch', async (_params, { getState }) => apiFetchNotificationGroups({ - grouped_types: supportedGroupedNotificationTypes, + grouped_types: selectNotificationGroupedTypes(getState()), exclude_types: getExcludedTypes(getState()), }), ({ notifications, accounts, statuses }, { dispatch }) => { @@ -102,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk( 'notificationGroups/fetchGap', async (params: { gap: NotificationGap }, { getState }) => apiFetchNotificationGroups({ - grouped_types: supportedGroupedNotificationTypes, + grouped_types: selectNotificationGroupedTypes(getState()), max_id: params.gap.maxId, exclude_types: getExcludedTypes(getState()), }), @@ -119,7 +123,7 @@ export const pollRecentNotifications = createDataLoadingThunk( 'notificationGroups/pollRecentNotifications', async (_params, { getState }) => { return apiFetchNotificationGroups({ - grouped_types: supportedGroupedNotificationTypes, + grouped_types: selectNotificationGroupedTypes(getState()), max_id: undefined, exclude_types: getExcludedTypes(getState()), // In slow mode, we don't want to include notifications that duplicate the already-displayed ones @@ -168,7 +172,10 @@ export const processNewNotificationForGroups = createAppAsyncThunk( dispatchAssociatedRecords(dispatch, [notification]); - return notification; + return { + notification, + groupedTypes: selectNotificationGroupedTypes(state), + }; }, ); diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx b/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx index ea57f2999b..81a9d9e1d1 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx @@ -40,6 +40,7 @@ class ColumnSettings extends PureComponent { const alertStr = ; const showStr = ; const soundStr = ; + const groupStr = ; const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed'); const pushStr = showPushSettings && ; @@ -96,6 +97,10 @@ class ColumnSettings extends PureComponent { + +
+ +
diff --git a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js index 3c4c4b30ca..3e0da03e33 100644 --- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js +++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js @@ -56,11 +56,12 @@ const mapDispatchToProps = (dispatch) => ({ } else { dispatch(changeSetting(['notifications', ...path], checked)); } - } else if(path[0] === 'groupingBeta') { - dispatch(changeSetting(['notifications', ...path], checked)); - dispatch(initializeNotifications()); } else { dispatch(changeSetting(['notifications', ...path], checked)); + + if(path[0] === 'group' && path[1] === 'follow') { + dispatch(initializeNotifications()); + } } }, diff --git a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_follow.tsx b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_follow.tsx index f154e801fb..cabb671815 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_follow.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_follow.tsx @@ -1,16 +1,19 @@ import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; + import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react'; import { FollowersCounter } from 'flavours/glitch/components/counters'; import { FollowButton } from 'flavours/glitch/components/follow_button'; import { ShortNumber } from 'flavours/glitch/components/short_number'; +import { me } from 'flavours/glitch/initial_state'; import type { NotificationGroupFollow } from 'flavours/glitch/models/notification_group'; import { useAppSelector } from 'flavours/glitch/store'; import type { LabelRenderer } from './notification_group_with_status'; import { NotificationGroupWithStatus } from './notification_group_with_status'; -const labelRenderer: LabelRenderer = (displayedName, total) => { +const labelRenderer: LabelRenderer = (displayedName, total, seeMoreHref) => { if (total === 1) return ( { return ( + seeMoreHref ? {chunks} : chunks, }} /> ); @@ -46,6 +51,10 @@ export const NotificationFollow: React.FC<{ notification: NotificationGroupFollow; unread: boolean; }> = ({ notification, unread }) => { + const username = useAppSelector( + (state) => state.accounts.getIn([me, 'username']) as string, + ); + let actions: JSX.Element | undefined; let additionalContent: JSX.Element | undefined; @@ -68,6 +77,7 @@ export const NotificationFollow: React.FC<{ timestamp={notification.latest_page_notification_at} count={notification.notifications_count} labelRenderer={labelRenderer} + labelSeeMoreHref={`/@${username}/followers`} unread={unread} actions={actions} additionalContent={additionalContent} diff --git a/app/javascript/flavours/glitch/reducers/notification_groups.ts b/app/javascript/flavours/glitch/reducers/notification_groups.ts index d1dacc7964..09c0c5c3ee 100644 --- a/app/javascript/flavours/glitch/reducers/notification_groups.ts +++ b/app/javascript/flavours/glitch/reducers/notification_groups.ts @@ -21,7 +21,6 @@ import { unmountNotifications, refreshStaleNotificationGroups, pollRecentNotifications, - shouldGroupNotificationType, } from 'flavours/glitch/actions/notification_groups'; import { disconnectTimeline, @@ -30,6 +29,7 @@ import { import type { ApiNotificationJSON, ApiNotificationGroupJSON, + NotificationType, } from 'flavours/glitch/api_types/notifications'; import { compareId } from 'flavours/glitch/compare_id'; import { usePendingItems } from 'flavours/glitch/initial_state'; @@ -205,8 +205,9 @@ function mergeGapsAround( function processNewNotification( groups: NotificationGroupsState['groups'], notification: ApiNotificationJSON, + groupedTypes: NotificationType[], ) { - if (!shouldGroupNotificationType(notification.type)) { + if (!groupedTypes.includes(notification.type)) { notification = { ...notification, group_key: `ungrouped-${notification.id}`, @@ -476,11 +477,13 @@ export const notificationGroupsReducer = createReducer( trimNotifications(state); }) .addCase(processNewNotificationForGroups.fulfilled, (state, action) => { - const notification = action.payload; - if (notification) { + if (action.payload) { + const { notification, groupedTypes } = action.payload; + processNewNotification( usePendingItems ? state.pendingGroups : state.groups, notification, + groupedTypes, ); updateLastReadId(state); trimNotifications(state); diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index 660b5f11ca..bfea4abe44 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -79,6 +79,10 @@ const initialState = ImmutableMap({ 'admin.sign_up': true, 'admin.report': true, }), + + group: ImmutableMap({ + follow: true + }), }), firehose: ImmutableMap({ diff --git a/app/javascript/flavours/glitch/selectors/settings.ts b/app/javascript/flavours/glitch/selectors/settings.ts index d66f00f0e1..5229b15cf0 100644 --- a/app/javascript/flavours/glitch/selectors/settings.ts +++ b/app/javascript/flavours/glitch/selectors/settings.ts @@ -52,4 +52,7 @@ export const selectSettingsNotificationsMinimizeFilteredBanner = ( ) => state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean; +export const selectSettingsNotificationsGroupFollows = (state: RootState) => + state.settings.getIn(['notifications', 'group', 'follow']) as boolean; + /* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */