Handle removals from notification groups

Renaud Chaput 2024-06-17 23:56:23 +02:00
parent f4f77e163c
commit aac2f1e9ba
No known key found for this signature in database
GPG Key ID: BCFC859D49B46990
5 changed files with 112 additions and 16 deletions

View File

@ -1,4 +1,7 @@
import { apiFetchNotifications } from 'mastodon/api/notifications'; import {
apiClearNotifications,
apiFetchNotifications,
} from 'mastodon/api/notifications';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import type { import type {
ApiNotificationGroupJSON, ApiNotificationGroupJSON,
@ -103,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
export const processNewNotificationForGroups = createAppAsyncThunk( export const processNewNotificationForGroups = createAppAsyncThunk(
'notificationsGroups/processNew', 'notificationsGroups/processNew',
(notification: NotificationJSON, { dispatch }) => { (notification: ApiNotificationJSON, { dispatch }) => {
dispatchAssociatedRecords(dispatch, [notification]); dispatchAssociatedRecords(dispatch, [notification]);
return notification; return notification;
@ -123,3 +126,8 @@ export const setNotificationsFilter = createAppAsyncThunk(
dispatch(saveSettings()); dispatch(saveSettings());
}, },
); );
export const clearNotifications = createDataLoadingThunk(
'notifications/clear',
() => apiClearNotifications(),
);

View File

@ -24,6 +24,8 @@ import { saveSettings } from './settings';
export * from "./notifications_typed"; export * from "./notifications_typed";
export { clearNotifications } from "./notification_groups";
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP'; export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
@ -32,7 +34,6 @@ export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET'; export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING'; export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
@ -257,16 +258,6 @@ export function expandNotificationsFail(error, isLoadingMore) {
}; };
} }
export function clearNotifications() {
return (dispatch) => {
dispatch({
type: NOTIFICATIONS_CLEAR,
});
api().post('/api/v1/notifications/clear');
};
}
export function scrollTopNotifications(top) { export function scrollTopNotifications(top) {
return { return {
type: NOTIFICATIONS_SCROLL_TOP, type: NOTIFICATIONS_SCROLL_TOP,

View File

@ -1,4 +1,4 @@
import api, { getLinks } from 'mastodon/api'; import api, { apiRequest, getLinks } from 'mastodon/api';
import type { ApiNotificationGroupJSON } from 'mastodon/api_types/notifications'; import type { ApiNotificationGroupJSON } from 'mastodon/api_types/notifications';
export const apiFetchNotifications = async ( export const apiFetchNotifications = async (
@ -15,3 +15,6 @@ export const apiFetchNotifications = async (
return { notifications: response.data, links: getLinks(response) }; return { notifications: response.data, links: getLinks(response) };
}; };
export const apiClearNotifications = () =>
apiRequest<undefined>('POST', 'v1/notifications/clear');

View File

@ -22,7 +22,6 @@ import {
NOTIFICATIONS_EXPAND_REQUEST, NOTIFICATIONS_EXPAND_REQUEST,
NOTIFICATIONS_EXPAND_FAIL, NOTIFICATIONS_EXPAND_FAIL,
NOTIFICATIONS_FILTER_SET, NOTIFICATIONS_FILTER_SET,
NOTIFICATIONS_CLEAR,
NOTIFICATIONS_SCROLL_TOP, NOTIFICATIONS_SCROLL_TOP,
NOTIFICATIONS_LOAD_PENDING, NOTIFICATIONS_LOAD_PENDING,
NOTIFICATIONS_MOUNT, NOTIFICATIONS_MOUNT,
@ -30,6 +29,7 @@ import {
NOTIFICATIONS_MARK_AS_READ, NOTIFICATIONS_MARK_AS_READ,
NOTIFICATIONS_SET_BROWSER_SUPPORT, NOTIFICATIONS_SET_BROWSER_SUPPORT,
NOTIFICATIONS_SET_BROWSER_PERMISSION, NOTIFICATIONS_SET_BROWSER_PERMISSION,
clearNotifications,
} from '../actions/notifications'; } from '../actions/notifications';
import { disconnectTimeline } from '../actions/timelines'; import { disconnectTimeline } from '../actions/timelines';
import { compareId } from '../compare_id'; import { compareId } from '../compare_id';
@ -290,7 +290,7 @@ export default function notifications(state = initialState, action) {
case authorizeFollowRequestSuccess.type: case authorizeFollowRequestSuccess.type:
case rejectFollowRequestSuccess.type: case rejectFollowRequestSuccess.type:
return filterNotifications(state, [action.payload.id], 'follow_request'); return filterNotifications(state, [action.payload.id], 'follow_request');
case NOTIFICATIONS_CLEAR: case clearNotifications.pending.type:
return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false); return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false);
case timelineDelete.type: case timelineDelete.type:
return deleteByStatus(state, action.payload.statusId); return deleteByStatus(state, action.payload.statusId);

View File

@ -1,10 +1,22 @@
import { createReducer, isAnyOf } from '@reduxjs/toolkit'; import { createReducer, isAnyOf } from '@reduxjs/toolkit';
import { import {
authorizeFollowRequestSuccess,
blockAccountSuccess,
muteAccountSuccess,
rejectFollowRequestSuccess,
} from 'mastodon/actions/accounts_typed';
import { blockDomainSuccess } from 'mastodon/actions/domain_blocks_typed';
import {
clearNotifications,
fetchNotifications, fetchNotifications,
fetchNotificationsGap, fetchNotificationsGap,
processNewNotificationForGroups, processNewNotificationForGroups,
} from 'mastodon/actions/notification_groups'; } from 'mastodon/actions/notification_groups';
import {
disconnectTimeline,
timelineDelete,
} from 'mastodon/actions/timelines_typed';
import { import {
NOTIFICATIONS_GROUP_MAX_AVATARS, NOTIFICATIONS_GROUP_MAX_AVATARS,
createNotificationGroupFromJSON, createNotificationGroupFromJSON,
@ -33,6 +45,48 @@ const initialState: NotificationGroupsState = {
readMarkerId: '0', readMarkerId: '0',
}; };
function removeNotificationsForAccounts(
state: NotificationGroupsState,
accountIds: string[],
onlyForType?: string,
) {
state.groups = state.groups
.map((group) => {
if (
group.type !== 'gap' &&
(!onlyForType || group.type === onlyForType)
) {
const previousLength = group.sampleAccountsIds.length;
group.sampleAccountsIds = group.sampleAccountsIds.filter(
(id) => !accountIds.includes(id),
);
const newLength = group.sampleAccountsIds.length;
const removed = previousLength - newLength;
group.notifications_count -= removed;
}
return group;
})
.filter(
(group) => group.type === 'gap' || group.sampleAccountsIds.length > 0,
);
}
function removeNotificationsForStatus(
state: NotificationGroupsState,
statusId: string,
) {
state.groups = state.groups.filter(
(group) =>
group.type === 'gap' ||
!('statusId' in group) ||
group.statusId !== statusId,
);
}
export const notificationsGroupsReducer = export const notificationsGroupsReducer =
createReducer<NotificationGroupsState>(initialState, (builder) => { createReducer<NotificationGroupsState>(initialState, (builder) => {
builder builder
@ -106,6 +160,46 @@ export const notificationsGroupsReducer =
); );
} }
}) })
.addCase(disconnectTimeline, (state, action) => {
if (action.payload.timeline === 'home')
state.groups.unshift({
type: 'gap',
loadUrl: 'TODO_LOAD_URL_TOP_OF_TL', // TODO
});
})
.addCase(timelineDelete, (state, action) => {
removeNotificationsForStatus(state, action.payload.statusId);
})
.addCase(clearNotifications.pending, (state) => {
state.groups = [];
state.unread = 0;
state.hasMore = false;
})
.addCase(blockAccountSuccess, (state, action) => {
removeNotificationsForAccounts(state, [action.payload.relationship.id]);
})
.addCase(muteAccountSuccess, (state, action) => {
if (action.payload.relationship.muting_notifications)
removeNotificationsForAccounts(state, [
action.payload.relationship.id,
]);
})
.addCase(blockDomainSuccess, (state, action) => {
removeNotificationsForAccounts(
state,
action.payload.accounts.map((account) => account.id),
);
})
.addMatcher(
isAnyOf(authorizeFollowRequestSuccess, rejectFollowRequestSuccess),
(state, action) => {
removeNotificationsForAccounts(
state,
[action.payload.id],
'follow_request',
);
},
)
.addMatcher( .addMatcher(
isAnyOf(fetchNotifications.pending, fetchNotificationsGap.pending), isAnyOf(fetchNotifications.pending, fetchNotificationsGap.pending),
(state) => { (state) => {