diff --git a/app/javascript/mastodon/actions/notification_groups.ts b/app/javascript/mastodon/actions/notification_groups.ts
index a359913e6..a3c8095ac 100644
--- a/app/javascript/mastodon/actions/notification_groups.ts
+++ b/app/javascript/mastodon/actions/notification_groups.ts
@@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import type {
ApiNotificationGroupJSON,
ApiNotificationJSON,
+ NotificationType,
} from 'mastodon/api_types/notifications';
import { allNotificationTypes } from 'mastodon/api_types/notifications';
import type { ApiStatusJSON } from 'mastodon/api_types/statuses';
@@ -15,6 +16,7 @@ import { usePendingItems } from 'mastodon/initial_state';
import type { NotificationGap } from 'mastodon/reducers/notification_groups';
import {
selectSettingsNotificationsExcludedTypes,
+ selectSettingsNotificationsGroupFollows,
selectSettingsNotificationsQuickFilterActive,
selectSettingsNotificationsShows,
} from 'mastodon/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/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx
index ed2947c17..9616adcb9 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx
@@ -38,6 +38,7 @@ class ColumnSettings extends PureComponent {
const alertStr = ;
const showStr = ;
const soundStr = ;
+ const groupStr = ;
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && ;
@@ -94,6 +95,7 @@ class ColumnSettings extends PureComponent {
{showPushSettings && }
+
diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
index 8bcc7ab4e..4ac6cfa62 100644
--- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
+++ b/app/javascript/mastodon/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/mastodon/features/notifications_v2/components/notification_follow.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_follow.tsx
index 6a9a45d24..2c90777b9 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/notification_follow.tsx
+++ b/app/javascript/mastodon/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 'mastodon/components/counters';
import { FollowButton } from 'mastodon/components/follow_button';
import { ShortNumber } from 'mastodon/components/short_number';
+import { me } from 'mastodon/initial_state';
import type { NotificationGroupFollow } from 'mastodon/models/notification_group';
import { useAppSelector } from 'mastodon/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/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 7f8dc7477..eb29c0371 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -508,7 +508,7 @@
"notification.favourite": "{name} favorited your post",
"notification.favourite.name_and_others_with_link": "{name} and {count, plural, one {# other} other {# others}} favorited your post",
"notification.follow": "{name} followed you",
- "notification.follow.name_and_others": "{name} and {count, plural, one {# other} other {# others}} followed you",
+ "notification.follow.name_and_others": "{name} and {count, plural, one {# other} other {# others}} followed you",
"notification.follow_request": "{name} has requested to follow you",
"notification.follow_request.name_and_others": "{name} and {count, plural, one {# other} other {# others}} has requested to follow you",
"notification.label.mention": "Mention",
@@ -567,6 +567,7 @@
"notifications.column_settings.filter_bar.category": "Quick filter bar",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.follow_request": "New follow requests:",
+ "notifications.column_settings.group": "Group",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.poll": "Poll results:",
"notifications.column_settings.push": "Push notifications",
diff --git a/app/javascript/mastodon/reducers/notification_groups.ts b/app/javascript/mastodon/reducers/notification_groups.ts
index 91e91d754..7a165f5fe 100644
--- a/app/javascript/mastodon/reducers/notification_groups.ts
+++ b/app/javascript/mastodon/reducers/notification_groups.ts
@@ -21,7 +21,6 @@ import {
unmountNotifications,
refreshStaleNotificationGroups,
pollRecentNotifications,
- shouldGroupNotificationType,
} from 'mastodon/actions/notification_groups';
import {
disconnectTimeline,
@@ -30,6 +29,7 @@ import {
import type {
ApiNotificationJSON,
ApiNotificationGroupJSON,
+ NotificationType,
} from 'mastodon/api_types/notifications';
import { compareId } from 'mastodon/compare_id';
import { usePendingItems } from 'mastodon/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/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index e5ff2ff91..fc02ac718 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -78,6 +78,10 @@ const initialState = ImmutableMap({
'admin.sign_up': true,
'admin.report': true,
}),
+
+ group: ImmutableMap({
+ follow: true
+ }),
}),
firehose: ImmutableMap({
diff --git a/app/javascript/mastodon/selectors/settings.ts b/app/javascript/mastodon/selectors/settings.ts
index e722ad091..ca3437416 100644
--- a/app/javascript/mastodon/selectors/settings.ts
+++ b/app/javascript/mastodon/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 */