250 lines
7.7 KiB
TypeScript
250 lines
7.7 KiB
TypeScript
import { createAction } from '@reduxjs/toolkit';
|
|
|
|
import {
|
|
apiClearNotifications,
|
|
apiFetchNotificationGroups,
|
|
} from 'mastodon/api/notifications';
|
|
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';
|
|
import type { AppDispatch, RootState } from 'mastodon/store';
|
|
import {
|
|
createAppAsyncThunk,
|
|
createDataLoadingThunk,
|
|
} from 'mastodon/store/typed_functions';
|
|
|
|
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
|
import { NOTIFICATIONS_FILTER_SET } from './notifications';
|
|
import { saveSettings } from './settings';
|
|
|
|
function excludeAllTypesExcept(filter: string) {
|
|
return allNotificationTypes.filter((item) => item !== filter);
|
|
}
|
|
|
|
function getExcludedTypes(state: RootState) {
|
|
const activeFilter = selectSettingsNotificationsQuickFilterActive(state);
|
|
|
|
return activeFilter === 'all'
|
|
? selectSettingsNotificationsExcludedTypes(state)
|
|
: excludeAllTypesExcept(activeFilter);
|
|
}
|
|
|
|
function dispatchAssociatedRecords(
|
|
dispatch: AppDispatch,
|
|
notifications: ApiNotificationGroupJSON[] | ApiNotificationJSON[],
|
|
) {
|
|
const fetchedAccounts: ApiAccountJSON[] = [];
|
|
const fetchedStatuses: ApiStatusJSON[] = [];
|
|
|
|
notifications.forEach((notification) => {
|
|
if (notification.type === 'admin.report') {
|
|
fetchedAccounts.push(notification.report.target_account);
|
|
}
|
|
|
|
if (notification.type === 'moderation_warning') {
|
|
fetchedAccounts.push(notification.moderation_warning.target_account);
|
|
}
|
|
|
|
if ('status' in notification && notification.status) {
|
|
fetchedStatuses.push(notification.status);
|
|
}
|
|
});
|
|
|
|
if (fetchedAccounts.length > 0)
|
|
dispatch(importFetchedAccounts(fetchedAccounts));
|
|
|
|
if (fetchedStatuses.length > 0)
|
|
dispatch(importFetchedStatuses(fetchedStatuses));
|
|
}
|
|
|
|
function selectNotificationGroupedTypes(state: RootState) {
|
|
const types: NotificationType[] = ['favourite', 'reblog'];
|
|
|
|
if (selectSettingsNotificationsGroupFollows(state)) types.push('follow');
|
|
|
|
return types;
|
|
}
|
|
|
|
export const fetchNotifications = createDataLoadingThunk(
|
|
'notificationGroups/fetch',
|
|
async (_params, { getState }) =>
|
|
apiFetchNotificationGroups({
|
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
|
exclude_types: getExcludedTypes(getState()),
|
|
}),
|
|
({ notifications, accounts, statuses }, { dispatch }) => {
|
|
dispatch(importFetchedAccounts(accounts));
|
|
dispatch(importFetchedStatuses(statuses));
|
|
dispatchAssociatedRecords(dispatch, notifications);
|
|
const payload: (ApiNotificationGroupJSON | NotificationGap)[] =
|
|
notifications;
|
|
|
|
// TODO: might be worth not using gaps for that…
|
|
// if (nextLink) payload.push({ type: 'gap', loadUrl: nextLink.uri });
|
|
if (notifications.length > 1)
|
|
payload.push({ type: 'gap', maxId: notifications.at(-1)?.page_min_id });
|
|
|
|
return payload;
|
|
// dispatch(submitMarkers());
|
|
},
|
|
);
|
|
|
|
export const fetchNotificationsGap = createDataLoadingThunk(
|
|
'notificationGroups/fetchGap',
|
|
async (params: { gap: NotificationGap }, { getState }) =>
|
|
apiFetchNotificationGroups({
|
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
|
max_id: params.gap.maxId,
|
|
exclude_types: getExcludedTypes(getState()),
|
|
}),
|
|
({ notifications, accounts, statuses }, { dispatch }) => {
|
|
dispatch(importFetchedAccounts(accounts));
|
|
dispatch(importFetchedStatuses(statuses));
|
|
dispatchAssociatedRecords(dispatch, notifications);
|
|
|
|
return { notifications };
|
|
},
|
|
);
|
|
|
|
export const pollRecentNotifications = createDataLoadingThunk(
|
|
'notificationGroups/pollRecentNotifications',
|
|
async (_params, { getState }) => {
|
|
return apiFetchNotificationGroups({
|
|
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
|
|
since_id: usePendingItems
|
|
? getState().notificationGroups.groups.find(
|
|
(group) => group.type !== 'gap',
|
|
)?.page_max_id
|
|
: undefined,
|
|
});
|
|
},
|
|
({ notifications, accounts, statuses }, { dispatch }) => {
|
|
dispatch(importFetchedAccounts(accounts));
|
|
dispatch(importFetchedStatuses(statuses));
|
|
dispatchAssociatedRecords(dispatch, notifications);
|
|
|
|
return { notifications };
|
|
},
|
|
);
|
|
|
|
export const processNewNotificationForGroups = createAppAsyncThunk(
|
|
'notificationGroups/processNew',
|
|
(notification: ApiNotificationJSON, { dispatch, getState }) => {
|
|
const state = getState();
|
|
const activeFilter = selectSettingsNotificationsQuickFilterActive(state);
|
|
const notificationShows = selectSettingsNotificationsShows(state);
|
|
|
|
const showInColumn =
|
|
activeFilter === 'all'
|
|
? notificationShows[notification.type]
|
|
: activeFilter === notification.type;
|
|
|
|
if (!showInColumn) return;
|
|
|
|
if (
|
|
(notification.type === 'mention' || notification.type === 'update') &&
|
|
notification.status?.filtered
|
|
) {
|
|
const filters = notification.status.filtered.filter((result) =>
|
|
result.filter.context.includes('notifications'),
|
|
);
|
|
|
|
if (filters.some((result) => result.filter.filter_action === 'hide')) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
dispatchAssociatedRecords(dispatch, [notification]);
|
|
|
|
return {
|
|
notification,
|
|
groupedTypes: selectNotificationGroupedTypes(state),
|
|
};
|
|
},
|
|
);
|
|
|
|
export const loadPending = createAction('notificationGroups/loadPending');
|
|
|
|
export const updateScrollPosition = createAppAsyncThunk(
|
|
'notificationGroups/updateScrollPosition',
|
|
({ top }: { top: boolean }, { dispatch, getState }) => {
|
|
if (
|
|
top &&
|
|
getState().notificationGroups.mergedNotifications === 'needs-reload'
|
|
) {
|
|
void dispatch(fetchNotifications());
|
|
}
|
|
|
|
return { top };
|
|
},
|
|
);
|
|
|
|
export const setNotificationsFilter = createAppAsyncThunk(
|
|
'notifications/filter/set',
|
|
({ filterType }: { filterType: string }, { dispatch }) => {
|
|
dispatch({
|
|
type: NOTIFICATIONS_FILTER_SET,
|
|
path: ['notifications', 'quickFilter', 'active'],
|
|
value: filterType,
|
|
});
|
|
void dispatch(fetchNotifications());
|
|
dispatch(saveSettings());
|
|
},
|
|
);
|
|
|
|
export const clearNotifications = createDataLoadingThunk(
|
|
'notifications/clear',
|
|
() => apiClearNotifications(),
|
|
);
|
|
|
|
export const markNotificationsAsRead = createAction(
|
|
'notificationGroups/markAsRead',
|
|
);
|
|
|
|
export const mountNotifications = createAppAsyncThunk(
|
|
'notificationGroups/mount',
|
|
(_, { dispatch, getState }) => {
|
|
const state = getState();
|
|
|
|
if (
|
|
state.notificationGroups.mounted === 0 &&
|
|
state.notificationGroups.mergedNotifications === 'needs-reload'
|
|
) {
|
|
void dispatch(fetchNotifications());
|
|
}
|
|
},
|
|
);
|
|
|
|
export const unmountNotifications = createAction('notificationGroups/unmount');
|
|
|
|
export const refreshStaleNotificationGroups = createAppAsyncThunk<{
|
|
deferredRefresh: boolean;
|
|
}>('notificationGroups/refreshStale', (_, { dispatch, getState }) => {
|
|
const state = getState();
|
|
|
|
if (
|
|
state.notificationGroups.scrolledToTop ||
|
|
!state.notificationGroups.mounted
|
|
) {
|
|
void dispatch(fetchNotifications());
|
|
return { deferredRefresh: false };
|
|
}
|
|
|
|
return { deferredRefresh: true };
|
|
});
|