Skip to content

Commit

Permalink
Add ability to group follow notifications in WebUI (mastodon#32520)
Browse files Browse the repository at this point in the history
  • Loading branch information
renchap authored and ClearlyClaire committed Oct 18, 2024
1 parent 9334813 commit e507b4f
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 17 deletions.
21 changes: 14 additions & 7 deletions app/javascript/mastodon/actions/notification_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ 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';
import { usePendingItems } from 'mastodon/initial_state';
import type { NotificationGap } from 'mastodon/reducers/notification_groups';
import {
selectSettingsNotificationsExcludedTypes,
selectSettingsNotificationsGroupFollows,
selectSettingsNotificationsQuickFilterActive,
selectSettingsNotificationsShows,
} from 'mastodon/selectors/settings';
Expand Down Expand Up @@ -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 }) => {
Expand All @@ -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()),
}),
Expand All @@ -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
Expand Down Expand Up @@ -168,7 +172,10 @@ export const processNewNotificationForGroups = createAppAsyncThunk(

dispatchAssociatedRecords(dispatch, [notification]);

return notification;
return {
notification,
groupedTypes: selectNotificationGroupedTypes(state),
};
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ColumnSettings extends PureComponent {
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
const groupStr = <FormattedMessage id='notifications.column_settings.group' defaultMessage='Group' />;

const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
Expand Down Expand Up @@ -94,6 +95,7 @@ class ColumnSettings extends PureComponent {
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['group', 'follow']} onChange={onChange} label={groupStr} />
</div>
</section>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
},

Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<FormattedMessage
Expand All @@ -23,10 +26,12 @@ const labelRenderer: LabelRenderer = (displayedName, total) => {
return (
<FormattedMessage
id='notification.follow.name_and_others'
defaultMessage='{name} and {count, plural, one {# other} other {# others}} followed you'
defaultMessage='{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you'
values={{
name: displayedName,
count: total - 1,
a: (chunks) =>
seeMoreHref ? <Link to={seeMoreHref}>{chunks}</Link> : chunks,
}}
/>
);
Expand All @@ -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;

Expand All @@ -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}
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/mastodon/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@
"notification.favourite": "{name} favorited your post",
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> 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 <a>{count, plural, one {# other} other {# others}}</a> 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",
Expand Down Expand Up @@ -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",
Expand Down
11 changes: 7 additions & 4 deletions app/javascript/mastodon/reducers/notification_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
unmountNotifications,
refreshStaleNotificationGroups,
pollRecentNotifications,
shouldGroupNotificationType,
} from 'mastodon/actions/notification_groups';
import {
disconnectTimeline,
Expand All @@ -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';
Expand Down Expand Up @@ -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}`,
Expand Down Expand Up @@ -476,11 +477,13 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
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);
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/mastodon/reducers/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ const initialState = ImmutableMap({
'admin.sign_up': true,
'admin.report': true,
}),

group: ImmutableMap({
follow: true
}),
}),

firehose: ImmutableMap({
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/mastodon/selectors/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

0 comments on commit e507b4f

Please sign in to comment.