Mostly migrate pl-fe notifications to notification groups
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
40c0c7512d
commit
fe5653cce9
11 changed files with 301 additions and 223 deletions
|
@ -1,4 +1,5 @@
|
||||||
import omit from 'lodash.omit';
|
import omit from 'lodash.omit';
|
||||||
|
import pick from 'lodash.pick';
|
||||||
import * as v from 'valibot';
|
import * as v from 'valibot';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -110,7 +111,7 @@ import {
|
||||||
MuteAccountParams,
|
MuteAccountParams,
|
||||||
UpdateFilterParams,
|
UpdateFilterParams,
|
||||||
} from './params/filtering';
|
} from './params/filtering';
|
||||||
import { GetGroupedNotificationsParams } from './params/grouped-notifications';
|
import { GetGroupedNotificationsParams, GetUnreadNotificationGroupCountParams } from './params/grouped-notifications';
|
||||||
import {
|
import {
|
||||||
CreateGroupParams,
|
CreateGroupParams,
|
||||||
GetGroupBlocksParams,
|
GetGroupBlocksParams,
|
||||||
|
@ -193,6 +194,7 @@ import request, { getNextLink, getPrevLink, type RequestBody, RequestMeta } from
|
||||||
import { buildFullPath } from './utils/url';
|
import { buildFullPath } from './utils/url';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
Account,
|
||||||
AdminAccount,
|
AdminAccount,
|
||||||
AdminAnnouncement,
|
AdminAnnouncement,
|
||||||
AdminModerationLogEntry,
|
AdminModerationLogEntry,
|
||||||
|
@ -299,6 +301,28 @@ class PlApiClient {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#paginatedSingleGet = async <T>(input: URL | RequestInfo, body: RequestBody, schema: v.BaseSchema<any, T, v.BaseIssue<unknown>>): Promise<PaginatedSingleResponse<T>> => {
|
||||||
|
const getMore = (input: string | null) => input ? async () => {
|
||||||
|
const response = await this.request(input);
|
||||||
|
|
||||||
|
return {
|
||||||
|
previous: getMore(getPrevLink(response)),
|
||||||
|
next: getMore(getNextLink(response)),
|
||||||
|
items: v.parse(schema, response.json),
|
||||||
|
partial: response.status === 206,
|
||||||
|
};
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
const response = await this.request(input, body);
|
||||||
|
|
||||||
|
return {
|
||||||
|
previous: getMore(getPrevLink(response)),
|
||||||
|
next: getMore(getNextLink(response)),
|
||||||
|
items: v.parse(schema, response.json),
|
||||||
|
partial: response.status === 206,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
#paginatedPleromaAccounts = async (params: {
|
#paginatedPleromaAccounts = async (params: {
|
||||||
query?: string;
|
query?: string;
|
||||||
filters?: string;
|
filters?: string;
|
||||||
|
@ -398,11 +422,16 @@ class PlApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupedNotificationsResults: GroupedNotificationsResults = {
|
const groupedNotificationsResults: GroupedNotificationsResults = {
|
||||||
accounts: items.map(({ account }) => account),
|
accounts: Object.values(items.reduce<Record<string, Account>>((accounts, notification) => {
|
||||||
statuses: items.reduce<Array<Status>>((statuses, notification) => {
|
accounts[notification.account.id] = notification.account;
|
||||||
if ('status' in notification) statuses.push(notification.status);
|
if ('target' in notification) accounts[notification.target.id] = notification.target;
|
||||||
|
|
||||||
|
return accounts;
|
||||||
|
}, {})),
|
||||||
|
statuses: Object.values(items.reduce<Record<string, Status>>((statuses, notification) => {
|
||||||
|
if ('status' in notification) statuses[notification.status.id] = notification.status;
|
||||||
return statuses;
|
return statuses;
|
||||||
}, []),
|
}, {})),
|
||||||
notification_groups: notificationGroups,
|
notification_groups: notificationGroups,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2767,11 +2796,66 @@ class PlApiClient {
|
||||||
};
|
};
|
||||||
|
|
||||||
public readonly groupedNotifications = {
|
public readonly groupedNotifications = {
|
||||||
getGroupedNotifications: async (params: GetGroupedNotificationsParams) => {
|
getGroupedNotifications: async (params: GetGroupedNotificationsParams, meta?: RequestMeta) => {
|
||||||
if (this.features.groupedNotifications) {
|
if (this.features.groupedNotifications) {
|
||||||
return this.#paginatedGet('/api/v2/notifications', { params }, groupedNotificationsResultsSchema);
|
return this.#paginatedSingleGet('/api/v2/notifications', { ...meta, params }, groupedNotificationsResultsSchema);
|
||||||
} else {
|
} else {
|
||||||
return this.#groupNotifications(await this.notifications.getNotifications(), params);
|
const response = await this.notifications.getNotifications(
|
||||||
|
pick(params, ['max_id', 'since_id', 'limit', 'min_id', 'types', 'exclude_types', 'account_id', 'include_filtered']),
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.#groupNotifications(response, params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getNotificationGroup: async (groupKey: string) => {
|
||||||
|
if (this.features.groupedNotifications) {
|
||||||
|
const response = await this.request(`/api/v2/notifications/${groupKey}`);
|
||||||
|
|
||||||
|
return v.parse(groupedNotificationsResultsSchema, response.json);
|
||||||
|
} else {
|
||||||
|
const response = await this.request(`/api/v1/notifications/${groupKey}`);
|
||||||
|
|
||||||
|
return this.#groupNotifications({
|
||||||
|
previous: null,
|
||||||
|
next: null,
|
||||||
|
items: [response.json],
|
||||||
|
partial: false,
|
||||||
|
}).items;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
dismissNotificationGroup: async (groupKey: string) => {
|
||||||
|
if (this.features.groupedNotifications) {
|
||||||
|
const response = await this.request(`/api/v2/notifications/${groupKey}/dismiss`, { method: 'POST' });
|
||||||
|
|
||||||
|
return response.json as {};
|
||||||
|
} else {
|
||||||
|
return this.notifications.dismissNotification(groupKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getNotificationGroupAccounts: async (groupKey: string) => {
|
||||||
|
if (this.features.groupedNotifications) {
|
||||||
|
const response = await this.request(`/api/v2/notifications/${groupKey}/accounts`);
|
||||||
|
|
||||||
|
return v.parse(filteredArray(accountSchema), response.json);
|
||||||
|
} else {
|
||||||
|
return (await (this.groupedNotifications.getNotificationGroup(groupKey))).accounts;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getUnreadNotificationGroupCount: async (params: GetUnreadNotificationGroupCountParams) => {
|
||||||
|
if (this.features.groupedNotifications) {
|
||||||
|
const response = await this.request('/api/v2/notifications/unread_count', { params });
|
||||||
|
|
||||||
|
return v.parse(v.object({
|
||||||
|
count: v.number(),
|
||||||
|
}), response.json);
|
||||||
|
} else {
|
||||||
|
return this.notifications.getUnreadNotificationCount(
|
||||||
|
pick(params || {}, ['max_id', 'since_id', 'limit', 'min_id', 'types', 'exclude_types', 'account_id']),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,9 +38,16 @@ const accountNotificationGroupSchema = v.object({
|
||||||
type: v.picklist(['follow', 'follow_request', 'admin.sign_up', 'bite']),
|
type: v.picklist(['follow', 'follow_request', 'admin.sign_up', 'bite']),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mentionNotificationGroupSchema = v.object({
|
||||||
|
...baseNotificationGroupSchema.entries,
|
||||||
|
type: v.literal('mention'),
|
||||||
|
subtype: v.fallback(v.nullable(v.picklist(['reply'])), null),
|
||||||
|
status_id: v.string(),
|
||||||
|
});
|
||||||
|
|
||||||
const statusNotificationGroupSchema = v.object({
|
const statusNotificationGroupSchema = v.object({
|
||||||
...baseNotificationGroupSchema.entries,
|
...baseNotificationGroupSchema.entries,
|
||||||
type: v.picklist(['status', 'mention', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']),
|
type: v.picklist(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']),
|
||||||
status_id: v.string(),
|
status_id: v.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -105,6 +112,7 @@ const notificationGroupSchema: v.BaseSchema<any, NotificationGroup, v.BaseIssue<
|
||||||
})),
|
})),
|
||||||
v.variant('type', [
|
v.variant('type', [
|
||||||
accountNotificationGroupSchema,
|
accountNotificationGroupSchema,
|
||||||
|
mentionNotificationGroupSchema,
|
||||||
statusNotificationGroupSchema,
|
statusNotificationGroupSchema,
|
||||||
reportNotificationGroupSchema,
|
reportNotificationGroupSchema,
|
||||||
severedRelationshipNotificationGroupSchema,
|
severedRelationshipNotificationGroupSchema,
|
||||||
|
@ -117,6 +125,7 @@ const notificationGroupSchema: v.BaseSchema<any, NotificationGroup, v.BaseIssue<
|
||||||
|
|
||||||
type NotificationGroup = v.InferOutput<
|
type NotificationGroup = v.InferOutput<
|
||||||
| typeof accountNotificationGroupSchema
|
| typeof accountNotificationGroupSchema
|
||||||
|
| typeof mentionNotificationGroupSchema
|
||||||
| typeof statusNotificationGroupSchema
|
| typeof statusNotificationGroupSchema
|
||||||
| typeof reportNotificationGroupSchema
|
| typeof reportNotificationGroupSchema
|
||||||
| typeof severedRelationshipNotificationGroupSchema
|
| typeof severedRelationshipNotificationGroupSchema
|
||||||
|
|
|
@ -41,6 +41,7 @@ export * from './filter';
|
||||||
export * from './group';
|
export * from './group';
|
||||||
export * from './group-member';
|
export * from './group-member';
|
||||||
export * from './group-relationship';
|
export * from './group-relationship';
|
||||||
|
export * from './grouped-notifications-results';
|
||||||
export * from './instance';
|
export * from './instance';
|
||||||
export * from './interaction-policy';
|
export * from './interaction-policy';
|
||||||
export * from './interaction-request';
|
export * from './interaction-request';
|
||||||
|
|
|
@ -15,4 +15,17 @@ interface GetGroupedNotificationsParams extends PaginationParams {
|
||||||
include_filtered?: boolean;
|
include_filtered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { GetGroupedNotificationsParams };
|
interface GetUnreadNotificationGroupCountParams {
|
||||||
|
/** Maximum number of results to return. Defaults to 100 notifications. Max 1000 notifications. */
|
||||||
|
limit?: number;
|
||||||
|
/** Types of notifications that should count towards unread notifications. */
|
||||||
|
types?: Array<string>;
|
||||||
|
/** Types of notifications that should not count towards unread notifications. */
|
||||||
|
exclude_types?: Array<string>;
|
||||||
|
/** Only count unread notifications received from the specified account. */
|
||||||
|
account_id?: string;
|
||||||
|
/** Restrict which notification types can be grouped. Use this if there are notification types for which your client does not support grouping. If omitted, the server will group notifications of all types it supports (currently, `favourite`, `follow` and `reblog`). If you do not want any notification grouping, use GET /api/v1/notifications/unread_count instead. */
|
||||||
|
grouped_types?: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { GetGroupedNotificationsParams, GetUnreadNotificationGroupCountParams };
|
||||||
|
|
|
@ -4,6 +4,7 @@ export * from './apps';
|
||||||
export * from './chats';
|
export * from './chats';
|
||||||
export * from './events';
|
export * from './events';
|
||||||
export * from './filtering';
|
export * from './filtering';
|
||||||
|
export * from './grouped-notifications';
|
||||||
export * from './groups';
|
export * from './groups';
|
||||||
export * from './instance';
|
export * from './instance';
|
||||||
export * from './interaction-requests';
|
export * from './interaction-requests';
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import IntlMessageFormat from 'intl-messageformat';
|
import IntlMessageFormat from 'intl-messageformat';
|
||||||
|
|
||||||
import 'intl-pluralrules';
|
import 'intl-pluralrules';
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import { getClient } from 'pl-fe/api';
|
import { getClient } from 'pl-fe/api';
|
||||||
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
|
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
|
||||||
import { normalizeNotification, normalizeNotifications, type Notification } from 'pl-fe/normalizers/notification';
|
import { normalizeNotification } from 'pl-fe/normalizers/notification';
|
||||||
import { getFilters, regexFromFilters } from 'pl-fe/selectors';
|
import { getFilters, regexFromFilters } from 'pl-fe/selectors';
|
||||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||||
import { isLoggedIn } from 'pl-fe/utils/auth';
|
import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||||
|
@ -18,7 +19,7 @@ import { importEntities } from './importer';
|
||||||
import { saveMarker } from './markers';
|
import { saveMarker } from './markers';
|
||||||
import { saveSettings } from './settings';
|
import { saveSettings } from './settings';
|
||||||
|
|
||||||
import type { Account, Notification as BaseNotification, PaginatedResponse, Status } from 'pl-api';
|
import type { Notification as BaseNotification, GetGroupedNotificationsParams, GroupedNotificationsResults, NotificationGroup, PaginatedSingleResponse } from 'pl-api';
|
||||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||||
|
|
||||||
const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE' as const;
|
const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE' as const;
|
||||||
|
@ -58,8 +59,8 @@ defineMessages({
|
||||||
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: Array<BaseNotification>) => {
|
const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: Array<NotificationGroup>) => {
|
||||||
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
|
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.sample_account_ids).flat();
|
||||||
|
|
||||||
if (accountIds.length > 0) {
|
if (accountIds.length > 0) {
|
||||||
dispatch(fetchRelationships(accountIds));
|
dispatch(fetchRelationships(accountIds));
|
||||||
|
@ -76,13 +77,16 @@ const updateNotifications = (notification: BaseNotification) =>
|
||||||
statuses: [getNotificationStatus(notification)],
|
statuses: [getNotificationStatus(notification)],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
if (showInColumn) {
|
if (showInColumn) {
|
||||||
|
const normalizedNotification = normalizeNotification(notification);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: NOTIFICATIONS_UPDATE,
|
type: NOTIFICATIONS_UPDATE,
|
||||||
notification: normalizeNotification(notification),
|
notification: normalizedNotification,
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchRelatedRelationships(dispatch, [notification]);
|
fetchRelatedRelationships(dispatch, [normalizedNotification]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -195,7 +199,7 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: Record<string, any> = {
|
const params: GetGroupedNotificationsParams = {
|
||||||
max_id: maxId,
|
max_id: maxId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -203,7 +207,7 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
||||||
if (features.notificationsIncludeTypes) {
|
if (features.notificationsIncludeTypes) {
|
||||||
params.types = NOTIFICATION_TYPES.filter(type => !EXCLUDE_TYPES.includes(type as any));
|
params.types = NOTIFICATION_TYPES.filter(type => !EXCLUDE_TYPES.includes(type as any));
|
||||||
} else {
|
} else {
|
||||||
params.exclude_types = EXCLUDE_TYPES;
|
params.exclude_types = [...EXCLUDE_TYPES];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const filtered = FILTER_TYPES[activeFilter] || [activeFilter];
|
const filtered = FILTER_TYPES[activeFilter] || [activeFilter];
|
||||||
|
@ -215,51 +219,65 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!maxId && notifications.items.size > 0) {
|
if (!maxId && notifications.items.size > 0) {
|
||||||
params.since_id = notifications.getIn(['items', 0, 'id']);
|
params.since_id = notifications.items.first()?.page_max_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(expandNotificationsRequest());
|
dispatch(expandNotificationsRequest());
|
||||||
|
|
||||||
return getClient(state).notifications.getNotifications(params, { signal: abortExpandNotifications.signal }).then(response => {
|
return getClient(state).groupedNotifications.getGroupedNotifications(params, { signal: abortExpandNotifications.signal }).then(({ items: { accounts, statuses, notification_groups }, next }) => {
|
||||||
const entries = (response.items).reduce((acc, item) => {
|
|
||||||
if (item.account?.id) {
|
|
||||||
acc.accounts[item.account.id] = item.account;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by Move notification
|
|
||||||
if (item.type === 'move' && item.target.id) {
|
|
||||||
acc.accounts[item.target.id] = item.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO actually check for type
|
|
||||||
// @ts-ignore
|
|
||||||
if (item.status?.id) {
|
|
||||||
// @ts-ignore
|
|
||||||
acc.statuses[item.status.id] = item.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, { accounts: {}, statuses: {} } as { accounts: Record<string, Account>; statuses: Record<string, Status> });
|
|
||||||
|
|
||||||
dispatch(importEntities({
|
dispatch(importEntities({
|
||||||
accounts: Object.values(entries.accounts),
|
accounts,
|
||||||
statuses: Object.values(entries.statuses),
|
statuses,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const deduplicatedNotifications = normalizeNotifications(response.items, state.notifications.items);
|
dispatch(expandNotificationsSuccess(notification_groups, next));
|
||||||
|
fetchRelatedRelationships(dispatch, notification_groups);
|
||||||
dispatch(expandNotificationsSuccess(deduplicatedNotifications, response.next));
|
|
||||||
fetchRelatedRelationships(dispatch, response.items);
|
|
||||||
done();
|
done();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(expandNotificationsFail(error));
|
dispatch(expandNotificationsFail(error));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// return getClient(state).notifications.getNotifications(params, { signal: abortExpandNotifications.signal }).then(response => {
|
||||||
|
// const entries = (response.items).reduce((acc, item) => {
|
||||||
|
// if (item.account?.id) {
|
||||||
|
// acc.accounts[item.account.id] = item.account;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Used by Move notification
|
||||||
|
// if (item.type === 'move' && item.target.id) {
|
||||||
|
// acc.accounts[item.target.id] = item.target;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // TODO actually check for type
|
||||||
|
// // @ts-ignore
|
||||||
|
// if (item.status?.id) {
|
||||||
|
// // @ts-ignore
|
||||||
|
// acc.statuses[item.status.id] = item.status;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return acc;
|
||||||
|
// }, { accounts: {}, statuses: {} } as { accounts: Record<string, Account>; statuses: Record<string, Status> });
|
||||||
|
|
||||||
|
// dispatch(importEntities({
|
||||||
|
// accounts: Object.values(entries.accounts),
|
||||||
|
// statuses: Object.values(entries.statuses),
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// const deduplicatedNotifications = normalizeNotifications(response.items, state.notifications.items);
|
||||||
|
|
||||||
|
// dispatch(expandNotificationsSuccess(deduplicatedNotifications, response.next));
|
||||||
|
// fetchRelatedRelationships(dispatch, response.items);
|
||||||
|
// done();
|
||||||
|
// }).catch(error => {
|
||||||
|
// dispatch(expandNotificationsFail(error));
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
const expandNotificationsRequest = () => ({ type: NOTIFICATIONS_EXPAND_REQUEST });
|
const expandNotificationsRequest = () => ({ type: NOTIFICATIONS_EXPAND_REQUEST });
|
||||||
|
|
||||||
const expandNotificationsSuccess = (notifications: Array<Notification>, next: (() => Promise<PaginatedResponse<BaseNotification>>) | null) => ({
|
const expandNotificationsSuccess = (notifications: Array<NotificationGroup>, next: (() => Promise<PaginatedSingleResponse<GroupedNotificationsResults>>) | null) => ({
|
||||||
type: NOTIFICATIONS_EXPAND_SUCCESS,
|
type: NOTIFICATIONS_EXPAND_SUCCESS,
|
||||||
notifications,
|
notifications,
|
||||||
next,
|
next,
|
||||||
|
@ -297,7 +315,7 @@ const markReadNotifications = () =>
|
||||||
if (!isLoggedIn(getState)) return;
|
if (!isLoggedIn(getState)) return;
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const topNotificationId = state.notifications.items.first()?.id;
|
const topNotificationId = state.notifications.items.first()?.page_max_id;
|
||||||
const lastReadId = state.notifications.lastRead;
|
const lastReadId = state.notifications.lastRead;
|
||||||
|
|
||||||
if (topNotificationId && (lastReadId === -1 || compareId(topNotificationId, lastReadId) > 0)) {
|
if (topNotificationId && (lastReadId === -1 || compareId(topNotificationId, lastReadId) > 0)) {
|
||||||
|
|
|
@ -24,11 +24,9 @@ import { useModalsStore } from 'pl-fe/stores/modals';
|
||||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||||
import { NotificationType } from 'pl-fe/utils/notification';
|
import { NotificationType } from 'pl-fe/utils/notification';
|
||||||
|
|
||||||
import type { Notification as BaseNotification } from 'pl-api';
|
import type { NotificationGroup } from 'pl-api';
|
||||||
import type { Account } from 'pl-fe/normalizers/account';
|
import type { Account } from 'pl-fe/normalizers/account';
|
||||||
import type { Notification as NotificationEntity } from 'pl-fe/normalizers/notification';
|
|
||||||
import type { Status as StatusEntity } from 'pl-fe/normalizers/status';
|
import type { Status as StatusEntity } from 'pl-fe/normalizers/status';
|
||||||
import type { MinifiedNotification } from 'pl-fe/reducers/notifications';
|
|
||||||
|
|
||||||
const notificationForScreenReader = (intl: IntlShape, message: string, timestamp: string) => {
|
const notificationForScreenReader = (intl: IntlShape, message: string, timestamp: string) => {
|
||||||
const output = [message];
|
const output = [message];
|
||||||
|
@ -184,13 +182,13 @@ const avatarSize = 48;
|
||||||
|
|
||||||
interface INotification {
|
interface INotification {
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
notification: MinifiedNotification;
|
notification: NotificationGroup;
|
||||||
onMoveUp?: (notificationId: string) => void;
|
onMoveUp?: (notificationId: string) => void;
|
||||||
onMoveDown?: (notificationId: string) => void;
|
onMoveDown?: (notificationId: string) => void;
|
||||||
onReblog?: (status: StatusEntity, e?: KeyboardEvent) => void;
|
onReblog?: (status: StatusEntity, e?: KeyboardEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNotificationStatus = (n: NotificationEntity | BaseNotification) => {
|
const getNotificationStatus = (n: Pick<Exclude<ReturnType<ReturnType<typeof makeGetNotification>>, null>, 'type' | 'status'>) => {
|
||||||
if (['mention', 'status', 'reblog', 'favourite', 'poll', 'update', 'emoji_reaction', 'event_reminder', 'participation_accepted', 'participation_request'].includes(n.type))
|
if (['mention', 'status', 'reblog', 'favourite', 'poll', 'update', 'emoji_reaction', 'event_reminder', 'participation_accepted', 'participation_request'].includes(n.type))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return n.status;
|
return n.status;
|
||||||
|
@ -207,15 +205,17 @@ const Notification: React.FC<INotification> = (props) => {
|
||||||
const { me } = useLoggedIn();
|
const { me } = useLoggedIn();
|
||||||
const { openModal } = useModalsStore();
|
const { openModal } = useModalsStore();
|
||||||
const { settings } = useSettingsStore();
|
const { settings } = useSettingsStore();
|
||||||
|
|
||||||
const notification = useAppSelector((state) => getNotification(state, props.notification));
|
const notification = useAppSelector((state) => getNotification(state, props.notification));
|
||||||
|
const status = getNotificationStatus(notification);
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
|
|
||||||
const type = notification.type;
|
const type = notification.type;
|
||||||
const { account, accounts } = notification;
|
const { accounts } = notification;
|
||||||
const status = getNotificationStatus(notification);
|
const account = accounts[0];
|
||||||
|
|
||||||
const getHandlers = () => ({
|
const getHandlers = () => ({
|
||||||
reply: handleMention,
|
reply: handleMention,
|
||||||
|
@ -289,13 +289,13 @@ const Notification: React.FC<INotification> = (props) => {
|
||||||
|
|
||||||
const handleMoveUp = () => {
|
const handleMoveUp = () => {
|
||||||
if (onMoveUp) {
|
if (onMoveUp) {
|
||||||
onMoveUp(notification.id);
|
onMoveUp(notification.group_key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveDown = () => {
|
const handleMoveDown = () => {
|
||||||
if (onMoveDown) {
|
if (onMoveDown) {
|
||||||
onMoveDown(notification.id);
|
onMoveDown(notification.group_key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ const Notification: React.FC<INotification> = (props) => {
|
||||||
name: account && typeof account === 'object' ? account.acct : '',
|
name: account && typeof account === 'object' ? account.acct : '',
|
||||||
targetName,
|
targetName,
|
||||||
}),
|
}),
|
||||||
notification.created_at,
|
notification.latest_page_notification_at!,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -433,7 +433,7 @@ const Notification: React.FC<INotification> = (props) => {
|
||||||
truncate
|
truncate
|
||||||
data-testid='message'
|
data-testid='message'
|
||||||
>
|
>
|
||||||
<RelativeTimestamp timestamp={notification.created_at} theme='muted' size='sm' className='whitespace-nowrap' />
|
<RelativeTimestamp timestamp={notification.latest_page_notification_at!} theme='muted' size='sm' className='whitespace-nowrap' />
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -32,7 +32,7 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const getNotifications = createSelector([
|
const getNotifications = createSelector([
|
||||||
(state: RootState) => state.notifications.items.toList(),
|
(state: RootState) => state.notifications.items.toList(),
|
||||||
], (notifications) => notifications.filter(item => item !== null && !item.duplicate));
|
], (notifications) => notifications.filter(item => item !== null));
|
||||||
|
|
||||||
const Notifications = () => {
|
const Notifications = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -54,8 +54,13 @@ const Notifications = () => {
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const handleLoadOlder = useCallback(debounce(() => {
|
const handleLoadOlder = useCallback(debounce(() => {
|
||||||
const last = notifications.last();
|
const minId = notifications.reduce<string | undefined>(
|
||||||
dispatch(expandNotifications({ maxId: last && last.id }));
|
(minId, notification) => minId && notification.page_min_id && notification.page_min_id > minId
|
||||||
|
? minId
|
||||||
|
: notification.page_min_id,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
dispatch(expandNotifications({ maxId: minId }));
|
||||||
}, 300, { leading: true }), [notifications]);
|
}, 300, { leading: true }), [notifications]);
|
||||||
|
|
||||||
const handleScroll = useCallback(debounce((startIndex?: number) => {
|
const handleScroll = useCallback(debounce((startIndex?: number) => {
|
||||||
|
@ -63,12 +68,12 @@ const Notifications = () => {
|
||||||
}, 100), []);
|
}, 100), []);
|
||||||
|
|
||||||
const handleMoveUp = (id: string) => {
|
const handleMoveUp = (id: string) => {
|
||||||
const elementIndex = notifications.findIndex(item => item !== null && item.id === id) - 1;
|
const elementIndex = notifications.findIndex(item => item !== null && item.group_key === id) - 1;
|
||||||
_selectChild(elementIndex);
|
_selectChild(elementIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveDown = (id: string) => {
|
const handleMoveDown = (id: string) => {
|
||||||
const elementIndex = notifications.findIndex(item => item !== null && item.id === id) + 1;
|
const elementIndex = notifications.findIndex(item => item !== null && item.group_key === id) + 1;
|
||||||
_selectChild(elementIndex);
|
_selectChild(elementIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -111,7 +116,7 @@ const Notifications = () => {
|
||||||
} else if (notifications.size > 0 || hasMore) {
|
} else if (notifications.size > 0 || hasMore) {
|
||||||
scrollableContent = notifications.map((item) => (
|
scrollableContent = notifications.map((item) => (
|
||||||
<Notification
|
<Notification
|
||||||
key={item.id}
|
key={item.group_key}
|
||||||
notification={item}
|
notification={item}
|
||||||
onMoveUp={handleMoveUp}
|
onMoveUp={handleMoveUp}
|
||||||
onMoveDown={handleMoveDown}
|
onMoveDown={handleMoveDown}
|
||||||
|
|
|
@ -1,61 +1,20 @@
|
||||||
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
|
import omit from 'lodash/omit';
|
||||||
|
|
||||||
import { normalizeAccount } from './account';
|
import type { Notification as BaseNotification, NotificationGroup } from 'pl-api';
|
||||||
|
|
||||||
import type { OrderedMap as ImmutableOrderedMap } from 'immutable';
|
const normalizeNotification = (notification: BaseNotification): NotificationGroup => ({
|
||||||
import type { Notification as BaseNotification } from 'pl-api';
|
...(omit(notification, ['account', 'status', 'target'])),
|
||||||
import type { MinifiedNotification } from 'pl-fe/reducers/notifications';
|
group_key: notification.id,
|
||||||
|
notifications_count: 1,
|
||||||
const STATUS_NOTIFICATION_TYPES = [
|
most_recent_notification_id: notification.id,
|
||||||
'favourite',
|
page_min_id: notification.id,
|
||||||
'reblog',
|
page_max_id: notification.id,
|
||||||
'emoji_reaction',
|
latest_page_notification_at: notification.created_at,
|
||||||
'event_reminder',
|
sample_account_ids: [notification.account.id],
|
||||||
'participation_accepted',
|
// @ts-ignore
|
||||||
'participation_request',
|
status_id: notification.status?.id,
|
||||||
];
|
// @ts-ignore
|
||||||
|
target_id: notification.target?.id,
|
||||||
const normalizeNotification = (notification: BaseNotification) => ({
|
|
||||||
...notification,
|
|
||||||
duplicate: false,
|
|
||||||
account: normalizeAccount(notification.account),
|
|
||||||
account_id: notification.account.id,
|
|
||||||
accounts: [normalizeAccount(notification.account)],
|
|
||||||
account_ids: [notification.account.id],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizeNotifications = (notifications: Array<BaseNotification>, stateNotifications?: ImmutableOrderedMap<string, MinifiedNotification>) => {
|
export { normalizeNotification };
|
||||||
const deduplicatedNotifications: Notification[] = [];
|
|
||||||
|
|
||||||
for (const notification of notifications) {
|
|
||||||
const existingNotification = stateNotifications?.get(notification.id);
|
|
||||||
|
|
||||||
// Do not update grouped notifications
|
|
||||||
if (existingNotification && (existingNotification.duplicate || existingNotification.account_ids.length)) continue;
|
|
||||||
|
|
||||||
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
|
|
||||||
const existingNotification = deduplicatedNotifications
|
|
||||||
.find(deduplicated =>
|
|
||||||
deduplicated.type === notification.type
|
|
||||||
&& ((notification.type === 'emoji_reaction' && deduplicated.type === 'emoji_reaction') ? notification.emoji === deduplicated.emoji : true)
|
|
||||||
&& getNotificationStatus(deduplicated)?.id === getNotificationStatus(notification)?.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingNotification) {
|
|
||||||
existingNotification.accounts.push(normalizeAccount(notification.account));
|
|
||||||
existingNotification.account_ids.push(notification.account.id);
|
|
||||||
deduplicatedNotifications.push({ ...normalizeNotification(notification), duplicate: true });
|
|
||||||
} else {
|
|
||||||
deduplicatedNotifications.push(normalizeNotification(notification));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deduplicatedNotifications.push(normalizeNotification(notification));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deduplicatedNotifications;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Notification = ReturnType<typeof normalizeNotification>;
|
|
||||||
|
|
||||||
export { normalizeNotification, normalizeNotifications, type Notification };
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Record as ImmutableRecord, OrderedMap as ImmutableOrderedMap } from 'immutable';
|
import { Record as ImmutableRecord, OrderedMap as ImmutableOrderedMap } from 'immutable';
|
||||||
import omit from 'lodash/omit';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ACCOUNT_BLOCK_SUCCESS,
|
ACCOUNT_BLOCK_SUCCESS,
|
||||||
|
@ -27,18 +26,17 @@ import {
|
||||||
} from '../actions/notifications';
|
} from '../actions/notifications';
|
||||||
import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines';
|
import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines';
|
||||||
|
|
||||||
import type { AccountWarning, Notification as BaseNotification, Markers, PaginatedResponse, Relationship, RelationshipSeveranceEvent, Report } from 'pl-api';
|
import type { Notification as BaseNotification, Markers, NotificationGroup, PaginatedResponse, Relationship } from 'pl-api';
|
||||||
import type { Notification } from 'pl-fe/normalizers/notification';
|
|
||||||
import type { AnyAction } from 'redux';
|
import type { AnyAction } from 'redux';
|
||||||
|
|
||||||
const QueuedNotificationRecord = ImmutableRecord({
|
const QueuedNotificationRecord = ImmutableRecord({
|
||||||
notification: {} as any as BaseNotification,
|
notification: {} as any as NotificationGroup,
|
||||||
intlMessages: {} as Record<string, string>,
|
intlMessages: {} as Record<string, string>,
|
||||||
intlLocale: '',
|
intlLocale: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const ReducerRecord = ImmutableRecord({
|
const ReducerRecord = ImmutableRecord({
|
||||||
items: ImmutableOrderedMap<string, MinifiedNotification>(),
|
items: ImmutableOrderedMap<string, NotificationGroup>(),
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
top: false,
|
top: false,
|
||||||
unread: 0,
|
unread: 0,
|
||||||
|
@ -54,103 +52,103 @@ type QueuedNotification = ReturnType<typeof QueuedNotificationRecord>;
|
||||||
const parseId = (id: string | number) => parseInt(id as string, 10);
|
const parseId = (id: string | number) => parseInt(id as string, 10);
|
||||||
|
|
||||||
// For sorting the notifications
|
// For sorting the notifications
|
||||||
const comparator = (a: Pick<Notification, 'id'>, b: Pick<Notification, 'id'>) => {
|
const comparator = (a: Pick<NotificationGroup, 'group_key'>, b: Pick<NotificationGroup, 'group_key'>) => {
|
||||||
const parse = (m: Pick<Notification, 'id'>) => parseId(m.id);
|
const parse = (m: Pick<NotificationGroup, 'group_key'>) => parseId(m.group_key);
|
||||||
if (parse(a) < parse(b)) return 1;
|
if (parse(a) < parse(b)) return 1;
|
||||||
if (parse(a) > parse(b)) return -1;
|
if (parse(a) > parse(b)) return -1;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const minifyNotification = (notification: Notification) => {
|
// const minifyNotification = (notification: Notification) => {
|
||||||
// @ts-ignore
|
// // @ts-ignore
|
||||||
const minifiedNotification: {
|
// const minifiedNotification: {
|
||||||
duplicate: boolean;
|
// duplicate: boolean;
|
||||||
account_id: string;
|
// account_id: string;
|
||||||
account_ids: string[];
|
// account_ids: string[];
|
||||||
created_at: string;
|
// created_at: string;
|
||||||
id: string;
|
// id: string;
|
||||||
} & (
|
// } & (
|
||||||
| { type: 'follow' | 'follow_request' | 'admin.sign_up' | 'bite' }
|
// | { type: 'follow' | 'follow_request' | 'admin.sign_up' | 'bite' }
|
||||||
| {
|
// | {
|
||||||
type: 'mention' | 'status' | 'reblog' | 'favourite' | 'poll' | 'update' | 'event_reminder';
|
// type: 'mention' | 'status' | 'reblog' | 'favourite' | 'poll' | 'update' | 'event_reminder';
|
||||||
status_id: string;
|
// status_id: string;
|
||||||
}
|
// }
|
||||||
| {
|
// | {
|
||||||
type: 'admin.report';
|
// type: 'admin.report';
|
||||||
report: Report;
|
// report: Report;
|
||||||
}
|
// }
|
||||||
| {
|
// | {
|
||||||
type: 'severed_relationships';
|
// type: 'severed_relationships';
|
||||||
relationship_severance_event: RelationshipSeveranceEvent;
|
// relationship_severance_event: RelationshipSeveranceEvent;
|
||||||
}
|
// }
|
||||||
| {
|
// | {
|
||||||
type: 'moderation_warning';
|
// type: 'moderation_warning';
|
||||||
moderation_warning: AccountWarning;
|
// moderation_warning: AccountWarning;
|
||||||
}
|
// }
|
||||||
| {
|
// | {
|
||||||
type: 'move';
|
// type: 'move';
|
||||||
target_id: string;
|
// target_id: string;
|
||||||
}
|
// }
|
||||||
| {
|
// | {
|
||||||
type: 'emoji_reaction';
|
// type: 'emoji_reaction';
|
||||||
emoji: string;
|
// emoji: string;
|
||||||
emoji_url: string | null;
|
// emoji_url: string | null;
|
||||||
status_id: string;
|
// status_id: string;
|
||||||
}
|
// }
|
||||||
| {
|
// | {
|
||||||
type: 'chat_mention';
|
// type: 'chat_mention';
|
||||||
chat_message_id: string;
|
// chat_message_id: string;
|
||||||
}
|
// }
|
||||||
| {
|
// | {
|
||||||
type: 'participation_accepted' | 'participation_request';
|
// type: 'participation_accepted' | 'participation_request';
|
||||||
status_id: string;
|
// status_id: string;
|
||||||
participation_message: string | null;
|
// participation_message: string | null;
|
||||||
}
|
// }
|
||||||
) = {
|
// ) = {
|
||||||
...omit(notification, ['account', 'accounts']),
|
// ...omit(notification, ['account', 'accounts']),
|
||||||
created_at: notification.created_at,
|
// created_at: notification.latest_page_notification_at,
|
||||||
id: notification.id,
|
// id: notification.id,
|
||||||
type: notification.type,
|
// type: notification.type,
|
||||||
};
|
// };
|
||||||
|
|
||||||
// @ts-ignore
|
// // @ts-ignore
|
||||||
if (notification.status) minifiedNotification.status_id = notification.status.id;
|
// if (notification.status) minifiedNotification.status_id = notification.status.id;
|
||||||
// @ts-ignore
|
// // @ts-ignore
|
||||||
if (notification.target) minifiedNotification.target_id = notification.target.id;
|
// if (notification.target) minifiedNotification.target_id = notification.target.id;
|
||||||
// @ts-ignore
|
// // @ts-ignore
|
||||||
if (notification.chat_message) minifiedNotification.chat_message_id = notification.chat_message.id;
|
// if (notification.chat_message) minifiedNotification.chat_message_id = notification.chat_message.id;
|
||||||
|
|
||||||
return minifiedNotification;
|
// return minifiedNotification;
|
||||||
};
|
// };
|
||||||
|
|
||||||
type MinifiedNotification = ReturnType<typeof minifyNotification>;
|
// type MinifiedNotification = ReturnType<typeof minifyNotification>;
|
||||||
|
|
||||||
// Count how many notifications appear after the given ID (for unread count)
|
// Count how many notifications appear after the given ID (for unread count)
|
||||||
const countFuture = (notifications: ImmutableOrderedMap<string, MinifiedNotification>, lastId: string | number) =>
|
const countFuture = (notifications: ImmutableOrderedMap<string, NotificationGroup>, lastId: string | number) =>
|
||||||
notifications.reduce((acc, notification) => {
|
notifications.reduce((acc, notification) => {
|
||||||
if (!notification.duplicate && parseId(notification.id) > parseId(lastId)) {
|
if (parseId(notification.group_key) > parseId(lastId)) {
|
||||||
return acc + 1;
|
return acc + 1;
|
||||||
} else {
|
} else {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const importNotification = (state: State, notification: Notification) => {
|
const importNotification = (state: State, notification: NotificationGroup) => {
|
||||||
const top = state.top;
|
const top = state.top;
|
||||||
|
|
||||||
if (!top && !notification.duplicate) state = state.update('unread', unread => unread + 1);
|
if (!top) state = state.update('unread', unread => unread + 1);
|
||||||
|
|
||||||
return state.update('items', map => {
|
return state.update('items', map => {
|
||||||
if (top && map.size > 40) {
|
if (top && map.size > 40) {
|
||||||
map = map.take(20);
|
map = map.take(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
return map.set(notification.id, minifyNotification(notification)).sort(comparator);
|
return map.set(notification.group_key, notification).sort(comparator);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const expandNormalizedNotifications = (state: State, notifications: Notification[], next: (() => Promise<PaginatedResponse<BaseNotification>>) | null) => {
|
const expandNormalizedNotifications = (state: State, notifications: NotificationGroup[], next: (() => Promise<PaginatedResponse<BaseNotification>>) | null) => {
|
||||||
const items = ImmutableOrderedMap(notifications.map(minifyNotification).map(n => [n.id, n]));
|
const items = ImmutableOrderedMap(notifications.map(n => [n.group_key, n]));
|
||||||
|
|
||||||
return state.withMutations(mutable => {
|
return state.withMutations(mutable => {
|
||||||
mutable.update('items', map => map.merge(items).sort(comparator));
|
mutable.update('items', map => map.merge(items).sort(comparator));
|
||||||
|
@ -161,10 +159,10 @@ const expandNormalizedNotifications = (state: State, notifications: Notification
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterNotifications = (state: State, relationship: Relationship) =>
|
const filterNotifications = (state: State, relationship: Relationship) =>
|
||||||
state.update('items', map => map.filterNot(item => item !== null && item.account_ids.includes(relationship.id)));
|
state.update('items', map => map.filterNot(item => item !== null && item.sample_account_ids.includes(relationship.id)));
|
||||||
|
|
||||||
const filterNotificationIds = (state: State, accountIds: Array<string>, type?: string) => {
|
const filterNotificationIds = (state: State, accountIds: Array<string>, type?: string) => {
|
||||||
const helper = (list: ImmutableOrderedMap<string, MinifiedNotification>) => list.filterNot(item => item !== null && accountIds.includes(item.account_ids[0]) && (type === undefined || type === item.type));
|
const helper = (list: ImmutableOrderedMap<string, NotificationGroup>) => list.filterNot(item => item !== null && accountIds.includes(item.sample_account_ids[0]) && (type === undefined || type === item.type));
|
||||||
return state.update('items', helper);
|
return state.update('items', helper);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -177,19 +175,19 @@ const deleteByStatus = (state: State, statusId: string) =>
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
state.update('items', map => map.filterNot(item => item !== null && item.status === statusId));
|
state.update('items', map => map.filterNot(item => item !== null && item.status === statusId));
|
||||||
|
|
||||||
const updateNotificationsQueue = (state: State, notification: BaseNotification, intlMessages: Record<string, string>, intlLocale: string) => {
|
const updateNotificationsQueue = (state: State, notification: NotificationGroup, intlMessages: Record<string, string>, intlLocale: string) => {
|
||||||
const queuedNotifications = state.queuedNotifications;
|
const queuedNotifications = state.queuedNotifications;
|
||||||
const listedNotifications = state.items;
|
const listedNotifications = state.items;
|
||||||
const totalQueuedNotificationsCount = state.totalQueuedNotificationsCount;
|
const totalQueuedNotificationsCount = state.totalQueuedNotificationsCount;
|
||||||
|
|
||||||
const alreadyExists = queuedNotifications.has(notification.id) || listedNotifications.has(notification.id);
|
const alreadyExists = queuedNotifications.has(notification.group_key) || listedNotifications.has(notification.group_key);
|
||||||
if (alreadyExists) return state;
|
if (alreadyExists) return state;
|
||||||
|
|
||||||
const newQueuedNotifications = queuedNotifications;
|
const newQueuedNotifications = queuedNotifications;
|
||||||
|
|
||||||
return state.withMutations(mutable => {
|
return state.withMutations(mutable => {
|
||||||
if (totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) {
|
if (totalQueuedNotificationsCount <= MAX_QUEUED_NOTIFICATIONS) {
|
||||||
mutable.set('queuedNotifications', newQueuedNotifications.set(notification.id, QueuedNotificationRecord({
|
mutable.set('queuedNotifications', newQueuedNotifications.set(notification.group_key, QueuedNotificationRecord({
|
||||||
notification,
|
notification,
|
||||||
intlMessages,
|
intlMessages,
|
||||||
intlLocale,
|
intlLocale,
|
||||||
|
@ -259,7 +257,4 @@ const notifications = (state: State = ReducerRecord(), action: AnyAction | Timel
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export { notifications as default };
|
||||||
notifications as default,
|
|
||||||
type MinifiedNotification,
|
|
||||||
};
|
|
||||||
|
|
|
@ -13,12 +13,10 @@ import { validId } from 'pl-fe/utils/auth';
|
||||||
import ConfigDB from 'pl-fe/utils/config-db';
|
import ConfigDB from 'pl-fe/utils/config-db';
|
||||||
import { shouldFilter } from 'pl-fe/utils/timelines';
|
import { shouldFilter } from 'pl-fe/utils/timelines';
|
||||||
|
|
||||||
import type { Account as BaseAccount, Filter, MediaAttachment, Relationship } from 'pl-api';
|
import type { Account as BaseAccount, Filter, MediaAttachment, NotificationGroup, Relationship } from 'pl-api';
|
||||||
import type { EntityStore } from 'pl-fe/entity-store/types';
|
import type { EntityStore } from 'pl-fe/entity-store/types';
|
||||||
import type { Account } from 'pl-fe/normalizers/account';
|
import type { Account } from 'pl-fe/normalizers/account';
|
||||||
import type { Group } from 'pl-fe/normalizers/group';
|
import type { Group } from 'pl-fe/normalizers/group';
|
||||||
import type { Notification } from 'pl-fe/normalizers/notification';
|
|
||||||
import type { MinifiedNotification } from 'pl-fe/reducers/notifications';
|
|
||||||
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
|
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
|
||||||
import type { MRFSimple } from 'pl-fe/schemas/pleroma';
|
import type { MRFSimple } from 'pl-fe/schemas/pleroma';
|
||||||
import type { RootState } from 'pl-fe/store';
|
import type { RootState } from 'pl-fe/store';
|
||||||
|
@ -27,7 +25,9 @@ const selectAccount = (state: RootState, accountId: string) =>
|
||||||
state.entities[Entities.ACCOUNTS]?.store[accountId] as Account | undefined;
|
state.entities[Entities.ACCOUNTS]?.store[accountId] as Account | undefined;
|
||||||
|
|
||||||
const selectAccounts = (state: RootState, accountIds: Array<string>) =>
|
const selectAccounts = (state: RootState, accountIds: Array<string>) =>
|
||||||
accountIds.map(accountId => state.entities[Entities.ACCOUNTS]?.store[accountId] as Account | undefined);
|
accountIds
|
||||||
|
.map(accountId => state.entities[Entities.ACCOUNTS]?.store[accountId] as Account | undefined)
|
||||||
|
.filter((account): account is Account => account !== undefined);
|
||||||
|
|
||||||
const selectOwnAccount = (state: RootState) => {
|
const selectOwnAccount = (state: RootState) => {
|
||||||
if (state.me) {
|
if (state.me) {
|
||||||
|
@ -177,23 +177,16 @@ const makeGetStatus = () => createSelector(
|
||||||
type SelectedStatus = Exclude<ReturnType<ReturnType<typeof makeGetStatus>>, null>;
|
type SelectedStatus = Exclude<ReturnType<ReturnType<typeof makeGetStatus>>, null>;
|
||||||
|
|
||||||
const makeGetNotification = () => createSelector([
|
const makeGetNotification = () => createSelector([
|
||||||
(_state: RootState, notification: MinifiedNotification) => notification,
|
(_state: RootState, notification: NotificationGroup) => notification,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(state: RootState, notification: MinifiedNotification) => selectAccount(state, notification.account_id),
|
(state: RootState, notification: NotificationGroup) => selectAccount(state, notification.target_id),
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(state: RootState, notification: MinifiedNotification) => selectAccount(state, notification.target_id),
|
(state: RootState, notification: NotificationGroup) => state.statuses.get(notification.status_id),
|
||||||
// @ts-ignore
|
(state: RootState, notification: NotificationGroup) => selectAccounts(state, notification.sample_account_ids),
|
||||||
(state: RootState, notification: MinifiedNotification) => state.statuses.get(notification.status_id),
|
], (notification, target, status, accounts) => ({
|
||||||
(state: RootState, notification: MinifiedNotification) => notification.account_ids ? selectAccounts(state, notification.account_ids) : null,
|
|
||||||
], (notification, account, target, status, accounts): MinifiedNotification & Notification => ({
|
|
||||||
...notification,
|
...notification,
|
||||||
// @ts-ignore
|
target,
|
||||||
account: account || null,
|
status,
|
||||||
// @ts-ignore
|
|
||||||
target: target || null,
|
|
||||||
// @ts-ignore
|
|
||||||
status: status || null,
|
|
||||||
// @ts-ignore
|
|
||||||
accounts,
|
accounts,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue