Add ability to group follow notifications in WebUI (#32520)
This commit is contained in:
parent
acc1973f3a
commit
6c87c76e18
@ -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),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -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' />;
|
||||
@ -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>
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -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}
|
||||
|
@ -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",
|
||||
@ -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",
|
||||
|
@ -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<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);
|
||||
|
@ -78,6 +78,10 @@ const initialState = ImmutableMap({
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true,
|
||||
}),
|
||||
|
||||
group: ImmutableMap({
|
||||
follow: true
|
||||
}),
|
||||
}),
|
||||
|
||||
firehose: ImmutableMap({
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user