mirror of
https://git.kescher.at/CatCatNya/catstodon.git
synced 2024-11-22 12:58:06 +01:00
[Glitch] Add ability to group follow notifications in WebUI
Port e507b4f884
to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
2b0410f903
commit
de5d6e98ae
7 changed files with 49 additions and 16 deletions
|
@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
|
||||||
import type {
|
import type {
|
||||||
ApiNotificationGroupJSON,
|
ApiNotificationGroupJSON,
|
||||||
ApiNotificationJSON,
|
ApiNotificationJSON,
|
||||||
|
NotificationType,
|
||||||
} from 'flavours/glitch/api_types/notifications';
|
} from 'flavours/glitch/api_types/notifications';
|
||||||
import { allNotificationTypes } from 'flavours/glitch/api_types/notifications';
|
import { allNotificationTypes } from 'flavours/glitch/api_types/notifications';
|
||||||
import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
|
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 type { NotificationGap } from 'flavours/glitch/reducers/notification_groups';
|
||||||
import {
|
import {
|
||||||
selectSettingsNotificationsExcludedTypes,
|
selectSettingsNotificationsExcludedTypes,
|
||||||
|
selectSettingsNotificationsGroupFollows,
|
||||||
selectSettingsNotificationsQuickFilterActive,
|
selectSettingsNotificationsQuickFilterActive,
|
||||||
selectSettingsNotificationsShows,
|
selectSettingsNotificationsShows,
|
||||||
} from 'flavours/glitch/selectors/settings';
|
} from 'flavours/glitch/selectors/settings';
|
||||||
|
@ -68,17 +70,19 @@ function dispatchAssociatedRecords(
|
||||||
dispatch(importFetchedStatuses(fetchedStatuses));
|
dispatch(importFetchedStatuses(fetchedStatuses));
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportedGroupedNotificationTypes = ['favourite', 'reblog'];
|
function selectNotificationGroupedTypes(state: RootState) {
|
||||||
|
const types: NotificationType[] = ['favourite', 'reblog'];
|
||||||
|
|
||||||
export function shouldGroupNotificationType(type: string) {
|
if (selectSettingsNotificationsGroupFollows(state)) types.push('follow');
|
||||||
return supportedGroupedNotificationTypes.includes(type);
|
|
||||||
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchNotifications = createDataLoadingThunk(
|
export const fetchNotifications = createDataLoadingThunk(
|
||||||
'notificationGroups/fetch',
|
'notificationGroups/fetch',
|
||||||
async (_params, { getState }) =>
|
async (_params, { getState }) =>
|
||||||
apiFetchNotificationGroups({
|
apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
}),
|
}),
|
||||||
({ notifications, accounts, statuses }, { dispatch }) => {
|
({ notifications, accounts, statuses }, { dispatch }) => {
|
||||||
|
@ -102,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
|
||||||
'notificationGroups/fetchGap',
|
'notificationGroups/fetchGap',
|
||||||
async (params: { gap: NotificationGap }, { getState }) =>
|
async (params: { gap: NotificationGap }, { getState }) =>
|
||||||
apiFetchNotificationGroups({
|
apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
max_id: params.gap.maxId,
|
max_id: params.gap.maxId,
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
}),
|
}),
|
||||||
|
@ -119,7 +123,7 @@ export const pollRecentNotifications = createDataLoadingThunk(
|
||||||
'notificationGroups/pollRecentNotifications',
|
'notificationGroups/pollRecentNotifications',
|
||||||
async (_params, { getState }) => {
|
async (_params, { getState }) => {
|
||||||
return apiFetchNotificationGroups({
|
return apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
max_id: undefined,
|
max_id: undefined,
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
|
// 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]);
|
dispatchAssociatedRecords(dispatch, [notification]);
|
||||||
|
|
||||||
return notification;
|
return {
|
||||||
|
notification,
|
||||||
|
groupedTypes: selectNotificationGroupedTypes(state),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ class ColumnSettings extends PureComponent {
|
||||||
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
|
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
|
||||||
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
|
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
|
||||||
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
|
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 showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
|
||||||
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
|
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
|
||||||
|
@ -96,6 +97,10 @@ class ColumnSettings extends PureComponent {
|
||||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
|
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
|
||||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
|
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='column-settings__row'>
|
||||||
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['group', 'follow']} onChange={onChange} label={groupStr} />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section role='group' aria-labelledby='notifications-follow-request'>
|
<section role='group' aria-labelledby='notifications-follow-request'>
|
||||||
|
|
|
@ -56,11 +56,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
} else {
|
} else {
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
dispatch(changeSetting(['notifications', ...path], checked));
|
||||||
}
|
}
|
||||||
} else if(path[0] === 'groupingBeta') {
|
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
|
||||||
dispatch(initializeNotifications());
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
dispatch(changeSetting(['notifications', ...path], checked));
|
||||||
|
|
||||||
|
if(path[0] === 'group' && path[1] === 'follow') {
|
||||||
|
dispatch(initializeNotifications());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||||
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
||||||
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
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 type { NotificationGroupFollow } from 'flavours/glitch/models/notification_group';
|
||||||
import { useAppSelector } from 'flavours/glitch/store';
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import type { LabelRenderer } from './notification_group_with_status';
|
import type { LabelRenderer } from './notification_group_with_status';
|
||||||
import { NotificationGroupWithStatus } 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)
|
if (total === 1)
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -23,10 +26,12 @@ const labelRenderer: LabelRenderer = (displayedName, total) => {
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notification.follow.name_and_others'
|
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={{
|
values={{
|
||||||
name: displayedName,
|
name: displayedName,
|
||||||
count: total - 1,
|
count: total - 1,
|
||||||
|
a: (chunks) =>
|
||||||
|
seeMoreHref ? <Link to={seeMoreHref}>{chunks}</Link> : chunks,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -46,6 +51,10 @@ export const NotificationFollow: React.FC<{
|
||||||
notification: NotificationGroupFollow;
|
notification: NotificationGroupFollow;
|
||||||
unread: boolean;
|
unread: boolean;
|
||||||
}> = ({ notification, unread }) => {
|
}> = ({ notification, unread }) => {
|
||||||
|
const username = useAppSelector(
|
||||||
|
(state) => state.accounts.getIn([me, 'username']) as string,
|
||||||
|
);
|
||||||
|
|
||||||
let actions: JSX.Element | undefined;
|
let actions: JSX.Element | undefined;
|
||||||
let additionalContent: JSX.Element | undefined;
|
let additionalContent: JSX.Element | undefined;
|
||||||
|
|
||||||
|
@ -68,6 +77,7 @@ export const NotificationFollow: React.FC<{
|
||||||
timestamp={notification.latest_page_notification_at}
|
timestamp={notification.latest_page_notification_at}
|
||||||
count={notification.notifications_count}
|
count={notification.notifications_count}
|
||||||
labelRenderer={labelRenderer}
|
labelRenderer={labelRenderer}
|
||||||
|
labelSeeMoreHref={`/@${username}/followers`}
|
||||||
unread={unread}
|
unread={unread}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
additionalContent={additionalContent}
|
additionalContent={additionalContent}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
unmountNotifications,
|
unmountNotifications,
|
||||||
refreshStaleNotificationGroups,
|
refreshStaleNotificationGroups,
|
||||||
pollRecentNotifications,
|
pollRecentNotifications,
|
||||||
shouldGroupNotificationType,
|
|
||||||
} from 'flavours/glitch/actions/notification_groups';
|
} from 'flavours/glitch/actions/notification_groups';
|
||||||
import {
|
import {
|
||||||
disconnectTimeline,
|
disconnectTimeline,
|
||||||
|
@ -30,6 +29,7 @@ import {
|
||||||
import type {
|
import type {
|
||||||
ApiNotificationJSON,
|
ApiNotificationJSON,
|
||||||
ApiNotificationGroupJSON,
|
ApiNotificationGroupJSON,
|
||||||
|
NotificationType,
|
||||||
} from 'flavours/glitch/api_types/notifications';
|
} from 'flavours/glitch/api_types/notifications';
|
||||||
import { compareId } from 'flavours/glitch/compare_id';
|
import { compareId } from 'flavours/glitch/compare_id';
|
||||||
import { usePendingItems } from 'flavours/glitch/initial_state';
|
import { usePendingItems } from 'flavours/glitch/initial_state';
|
||||||
|
@ -205,8 +205,9 @@ function mergeGapsAround(
|
||||||
function processNewNotification(
|
function processNewNotification(
|
||||||
groups: NotificationGroupsState['groups'],
|
groups: NotificationGroupsState['groups'],
|
||||||
notification: ApiNotificationJSON,
|
notification: ApiNotificationJSON,
|
||||||
|
groupedTypes: NotificationType[],
|
||||||
) {
|
) {
|
||||||
if (!shouldGroupNotificationType(notification.type)) {
|
if (!groupedTypes.includes(notification.type)) {
|
||||||
notification = {
|
notification = {
|
||||||
...notification,
|
...notification,
|
||||||
group_key: `ungrouped-${notification.id}`,
|
group_key: `ungrouped-${notification.id}`,
|
||||||
|
@ -476,11 +477,13 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
|
||||||
trimNotifications(state);
|
trimNotifications(state);
|
||||||
})
|
})
|
||||||
.addCase(processNewNotificationForGroups.fulfilled, (state, action) => {
|
.addCase(processNewNotificationForGroups.fulfilled, (state, action) => {
|
||||||
const notification = action.payload;
|
if (action.payload) {
|
||||||
if (notification) {
|
const { notification, groupedTypes } = action.payload;
|
||||||
|
|
||||||
processNewNotification(
|
processNewNotification(
|
||||||
usePendingItems ? state.pendingGroups : state.groups,
|
usePendingItems ? state.pendingGroups : state.groups,
|
||||||
notification,
|
notification,
|
||||||
|
groupedTypes,
|
||||||
);
|
);
|
||||||
updateLastReadId(state);
|
updateLastReadId(state);
|
||||||
trimNotifications(state);
|
trimNotifications(state);
|
||||||
|
|
|
@ -79,6 +79,10 @@ const initialState = ImmutableMap({
|
||||||
'admin.sign_up': true,
|
'admin.sign_up': true,
|
||||||
'admin.report': true,
|
'admin.report': true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
group: ImmutableMap({
|
||||||
|
follow: true
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
firehose: ImmutableMap({
|
firehose: ImmutableMap({
|
||||||
|
|
|
@ -52,4 +52,7 @@ export const selectSettingsNotificationsMinimizeFilteredBanner = (
|
||||||
) =>
|
) =>
|
||||||
state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean;
|
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 */
|
/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||||
|
|
Loading…
Reference in a new issue