Deduplicate notifications/reposts info
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
2d45d3598a
commit
9ee5472bf2
12 changed files with 203 additions and 87 deletions
|
@ -151,47 +151,50 @@ const isBroken = (status: APIEntity) => {
|
|||
}
|
||||
};
|
||||
|
||||
const importFetchedStatuses = (statuses: APIEntity[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const accounts: APIEntity[] = [];
|
||||
const normalStatuses: APIEntity[] = [];
|
||||
const polls: APIEntity[] = [];
|
||||
const importFetchedStatuses = (statuses: APIEntity[]) => (dispatch: AppDispatch) => {
|
||||
const accounts: APIEntity[] = [];
|
||||
const normalStatuses: APIEntity[] = [];
|
||||
const polls: APIEntity[] = [];
|
||||
|
||||
function processStatus(status: APIEntity) {
|
||||
// Skip broken statuses
|
||||
if (isBroken(status)) return;
|
||||
function processStatus(status: APIEntity) {
|
||||
// Skip broken statuses
|
||||
if (isBroken(status)) return;
|
||||
|
||||
normalStatuses.push(status);
|
||||
accounts.push(status.account);
|
||||
normalStatuses.push(status);
|
||||
|
||||
if (status.reblog?.id) {
|
||||
processStatus(status.reblog);
|
||||
}
|
||||
|
||||
// Fedibird quotes
|
||||
if (status.quote?.id) {
|
||||
processStatus(status.quote);
|
||||
}
|
||||
|
||||
if (status.pleroma?.quote?.id) {
|
||||
processStatus(status.pleroma.quote);
|
||||
}
|
||||
|
||||
if (status.poll?.id) {
|
||||
polls.push(status.poll);
|
||||
}
|
||||
|
||||
if (status.group?.id) {
|
||||
dispatch(importFetchedGroup(status.group));
|
||||
}
|
||||
accounts.push(status.account);
|
||||
if (status.accounts) {
|
||||
accounts.push(...status.accounts);
|
||||
}
|
||||
|
||||
statuses.forEach(processStatus);
|
||||
if (status.reblog?.id) {
|
||||
processStatus(status.reblog);
|
||||
}
|
||||
|
||||
dispatch(importPolls(polls));
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
};
|
||||
// Fedibird quotes
|
||||
if (status.quote?.id) {
|
||||
processStatus(status.quote);
|
||||
}
|
||||
|
||||
if (status.pleroma?.quote?.id) {
|
||||
processStatus(status.pleroma.quote);
|
||||
}
|
||||
|
||||
if (status.poll?.id) {
|
||||
polls.push(status.poll);
|
||||
}
|
||||
|
||||
if (status.group?.id) {
|
||||
dispatch(importFetchedGroup(status.group));
|
||||
}
|
||||
}
|
||||
|
||||
statuses.forEach(processStatus);
|
||||
|
||||
dispatch(importPolls(polls));
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
};
|
||||
|
||||
const importFetchedPoll = (poll: APIEntity) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
|
|
|
@ -46,7 +46,7 @@ const NOTIFICATIONS_MARK_READ_FAIL = 'NOTIFICATIONS_MARK_READ_FAIL';
|
|||
const MAX_QUEUED_NOTIFICATIONS = 40;
|
||||
|
||||
defineMessages({
|
||||
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
||||
mention: { id: 'notification.mentioned', defaultMessage: '{name} mentioned you' },
|
||||
group: { id: 'notifications.group', defaultMessage: '{count, plural, one {# notification} other {# notifications}}' },
|
||||
});
|
||||
|
||||
|
@ -175,6 +175,47 @@ const excludeTypesFromFilter = (filter: string) => {
|
|||
|
||||
const noOp = () => new Promise(f => f(undefined));
|
||||
|
||||
const STATUS_NOTIFICATION_TYPES = [
|
||||
'favourite',
|
||||
'group_favourite',
|
||||
'mention',
|
||||
'reblog',
|
||||
'group_reblog',
|
||||
'status',
|
||||
'poll',
|
||||
'update',
|
||||
'pleroma:emoji_reaction',
|
||||
'pleroma:event_reminder',
|
||||
'pleroma:participation_accepted',
|
||||
'pleroma:participation_request',
|
||||
];
|
||||
|
||||
const deduplicateNotifications = (notifications: any[]) => {
|
||||
const deduplicatedNotifications: any[] = [];
|
||||
|
||||
for (const notification of notifications) {
|
||||
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
|
||||
const existingNotification = deduplicatedNotifications
|
||||
.find(deduplicatedNotification => deduplicatedNotification.type === notification.type && deduplicatedNotification.status?.id === notification.status?.id);
|
||||
|
||||
if (existingNotification) {
|
||||
if (existingNotification?.accounts) {
|
||||
existingNotification.accounts.push(notification.account);
|
||||
} else {
|
||||
existingNotification.accounts = [existingNotification.account, notification.account];
|
||||
}
|
||||
existingNotification.id += ':' + notification.id;
|
||||
} else {
|
||||
deduplicatedNotifications.push(notification);
|
||||
}
|
||||
} else {
|
||||
deduplicatedNotifications.push(notification);
|
||||
}
|
||||
}
|
||||
|
||||
return deduplicatedNotifications;
|
||||
};
|
||||
|
||||
const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => any = noOp) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp);
|
||||
|
@ -240,7 +281,9 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
|||
const statusesFromGroups = (Object.values(entries.statuses) as Status[]).filter((status) => !!status.group);
|
||||
dispatch(fetchGroupRelationships(statusesFromGroups.map((status: any) => status.group?.id)));
|
||||
|
||||
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore));
|
||||
const deduplicatedNotifications = deduplicateNotifications(response.data);
|
||||
|
||||
dispatch(expandNotificationsSuccess(deduplicatedNotifications, next ? next.uri : null, isLoadingMore));
|
||||
fetchRelatedRelationships(dispatch, response.data);
|
||||
done();
|
||||
}).catch(error => {
|
||||
|
|
|
@ -147,6 +147,27 @@ const parseTags = (tags: Record<string, any[]> = {}, mode: 'any' | 'all' | 'none
|
|||
});
|
||||
};
|
||||
|
||||
const deduplicateStatuses = (statuses: any[]) => {
|
||||
const deduplicatedStatuses: any[] = [];
|
||||
|
||||
for (const status of statuses) {
|
||||
const reblogged = status.reblog && deduplicatedStatuses.find((deduplicatedStatuses) => deduplicatedStatuses.reblog?.id === status.reblog.id);
|
||||
|
||||
if (reblogged) {
|
||||
if (reblogged.accounts) {
|
||||
reblogged.accounts.push(status.account);
|
||||
} else {
|
||||
reblogged.accounts = [reblogged.account, status.account];
|
||||
}
|
||||
reblogged.id += ':' + status.id;
|
||||
} else {
|
||||
deduplicatedStatuses.push(status);
|
||||
}
|
||||
}
|
||||
|
||||
return deduplicatedStatuses;
|
||||
};
|
||||
|
||||
const expandTimeline = (timelineId: string, path: string, params: Record<string, any> = {}, intl?: IntlShape, done = noOp) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const timeline = getState().timelines.get(timelineId) || {} as Record<string, any>;
|
||||
|
@ -175,12 +196,15 @@ const expandTimeline = (timelineId: string, path: string, params: Record<string,
|
|||
return api(getState).get(path, { params }).then(response => {
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
|
||||
const statuses = deduplicateStatuses(response.data);
|
||||
dispatch(importFetchedStatuses(statuses.filter(status => status.accounts)));
|
||||
|
||||
const statusesFromGroups = (response.data as Status[]).filter((status) => !!status.group);
|
||||
dispatch(fetchGroupRelationships(statusesFromGroups.map((status: any) => status.group?.id)));
|
||||
|
||||
dispatch(expandTimelineSuccess(
|
||||
timelineId,
|
||||
response.data,
|
||||
statuses,
|
||||
getNextLink(response),
|
||||
getPrevLink(response),
|
||||
response.status === 206,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
import { useIntl, FormattedMessage, defineMessages, FormattedList } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { mentionCompose, replyCompose } from 'soapbox/actions/compose';
|
||||
|
@ -240,6 +241,31 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
/>
|
||||
);
|
||||
} else if (isReblog) {
|
||||
const accounts = status.accounts || ImmutableList([status.account]);
|
||||
|
||||
const renderedAccounts = accounts.slice(0, 2).map(account => !!account && (
|
||||
<Link to={`/@${account.acct}`} className='hover:underline'>
|
||||
<bdi className='truncate'>
|
||||
<strong
|
||||
className='text-gray-800 dark:text-gray-200'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: account.display_name_html,
|
||||
}}
|
||||
/>
|
||||
</bdi>
|
||||
</Link>
|
||||
)).toArray().filter(Boolean);
|
||||
|
||||
if (accounts.size > 2) {
|
||||
renderedAccounts.push(
|
||||
<FormattedMessage
|
||||
id='notification.more'
|
||||
defaultMessage='{count, plural, one {# other} other {# others}}'
|
||||
values={{ count: accounts.size - renderedAccounts.length }}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusInfo
|
||||
avatarSize={avatarSize}
|
||||
|
@ -249,18 +275,8 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
id='status.reblogged_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: (
|
||||
<Link to={`/@${status.account.acct}`} className='hover:underline'>
|
||||
<bdi className='truncate'>
|
||||
<strong
|
||||
className='text-gray-800 dark:text-gray-200'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: status.account.display_name_html,
|
||||
}}
|
||||
/>
|
||||
</bdi>
|
||||
</Link>
|
||||
),
|
||||
name: <FormattedList type='conjunction' value={renderedAccounts} />,
|
||||
count: accounts.size,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -102,13 +102,13 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
|
|||
const spoilerTextRef = useRef<AutosuggestInput>(null);
|
||||
const editorRef = useRef<LexicalEditor>(null);
|
||||
|
||||
const characterCountProgress = length(text) / maxTootChars;
|
||||
|
||||
const { isDraggedOver } = useDraggedFiles(formRef);
|
||||
|
||||
const text = editorRef.current?.getEditorState().read(() => $getRoot().getTextContent()) ?? '';
|
||||
const fulltext = [spoilerText, countableText(text)].join('');
|
||||
|
||||
const characterCountProgress = length(text) / maxTootChars;
|
||||
|
||||
const isEmpty = !(fulltext.trim() || anyMedia);
|
||||
const condensed = shouldCondense && !isDraggedOver && !composeFocused && isEmpty && !isUploading;
|
||||
const shouldAutoFocus = autoFocus && !showSearch;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useCallback } from 'react';
|
||||
import { defineMessages, useIntl, FormattedMessage, IntlShape, MessageDescriptor, defineMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl, FormattedMessage, IntlShape, MessageDescriptor, FormattedList } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { mentionCompose } from 'soapbox/actions/compose';
|
||||
|
@ -17,7 +18,11 @@ import { makeGetNotification } from 'soapbox/selectors';
|
|||
import { NotificationType, validType } from 'soapbox/utils/notification';
|
||||
|
||||
import type { ScrollPosition } from 'soapbox/components/status';
|
||||
import type { Account as AccountEntity, Status as StatusEntity, Notification as NotificationEntity } from 'soapbox/types/entities';
|
||||
import type {
|
||||
Account as AccountEntity,
|
||||
Status as StatusEntity,
|
||||
Notification as NotificationEntity,
|
||||
} from 'soapbox/types/entities';
|
||||
|
||||
const notificationForScreenReader = (intl: IntlShape, message: string, timestamp: Date) => {
|
||||
const output = [message];
|
||||
|
@ -58,11 +63,6 @@ const icons: Record<NotificationType, string> = {
|
|||
'pleroma:participation_accepted': require('@tabler/icons/calendar-event.svg'),
|
||||
};
|
||||
|
||||
const nameMessage = defineMessage({
|
||||
id: 'notification.name',
|
||||
defaultMessage: '{link}{others}',
|
||||
});
|
||||
|
||||
const messages: Record<NotificationType, MessageDescriptor> = defineMessages({
|
||||
follow: {
|
||||
id: 'notification.follow',
|
||||
|
@ -138,26 +138,30 @@ const buildMessage = (
|
|||
intl: IntlShape,
|
||||
type: NotificationType,
|
||||
account: AccountEntity,
|
||||
totalCount: number | null,
|
||||
accounts: ImmutableList<AccountEntity> | null,
|
||||
targetName: string,
|
||||
instanceTitle: string,
|
||||
): React.ReactNode => {
|
||||
const link = buildLink(account);
|
||||
const name = intl.formatMessage(nameMessage, {
|
||||
link,
|
||||
others: totalCount && totalCount > 0 ? (
|
||||
|
||||
if (!accounts) accounts = accounts || ImmutableList([account]);
|
||||
|
||||
const renderedAccounts = accounts.slice(0, 2).map(account => buildLink(account)).toArray().filter(Boolean);
|
||||
|
||||
if (accounts.size > 2) {
|
||||
renderedAccounts.push(
|
||||
<FormattedMessage
|
||||
id='notification.others'
|
||||
defaultMessage='+ {count, plural, one {# other} other {# others}}'
|
||||
values={{ count: totalCount - 1 }}
|
||||
/>
|
||||
) : '',
|
||||
});
|
||||
id='notification.more'
|
||||
defaultMessage='{count, plural, one {# other} other {# others}}'
|
||||
values={{ count: accounts.size - renderedAccounts.length }}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
return intl.formatMessage(messages[type], {
|
||||
name,
|
||||
name: <FormattedList type='conjunction' value={renderedAccounts} />,
|
||||
targetName,
|
||||
instance: instanceTitle,
|
||||
count: accounts.size,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -187,7 +191,7 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
const instance = useInstance();
|
||||
|
||||
const type = notification.type;
|
||||
const { account, status } = notification;
|
||||
const { account, accounts, status } = notification;
|
||||
|
||||
const getHandlers = () => ({
|
||||
reply: handleMention,
|
||||
|
@ -356,7 +360,9 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
|
||||
const targetName = notification.target && typeof notification.target === 'object' ? notification.target.acct : '';
|
||||
|
||||
const message: React.ReactNode = validType(type) && account && typeof account === 'object' ? buildMessage(intl, type, account, notification.total_count, targetName, instance.title) : null;
|
||||
const message: React.ReactNode = validType(type) && account && typeof account === 'object'
|
||||
? buildMessage(intl, type, account, accounts as ImmutableList<AccountEntity>, targetName, instance.title)
|
||||
: null;
|
||||
|
||||
const ariaLabel = validType(type) ? (
|
||||
notificationForScreenReader(
|
||||
|
|
|
@ -1062,25 +1062,25 @@
|
|||
"new_group_panel.action": "Utwórz grupę",
|
||||
"new_group_panel.subtitle": "Nie możesz znaleźć tego, czego szukasz? Utwórz własną prywatną lub publiczną grupę.",
|
||||
"new_group_panel.title": "Utwórz grupę",
|
||||
"notification.favourite": "{name} dodał(a) Twój wpis do ulubionych",
|
||||
"notification.follow": "{name} zaczął(-ęła) Cię obserwować",
|
||||
"notification.follow_request": "{name} poprosił(a) Cię o możliwość obserwacji",
|
||||
"notification.group_favourite": "{name} polubił(a) Twój graficzny wpis",
|
||||
"notification.group_reblog": "{name} udostępnił(a) Twój grupowy wpis",
|
||||
"notification.mention": "{name} wspomniał(a) o tobie",
|
||||
"notification.favourite": "{name} {count, plural, one {dodał(a)} other {dodali}} Twój wpis do ulubionych",
|
||||
"notification.follow": "{name} {count, plural, one {zaczął(-ęła)} other {zaczęli}} Cię obserwować",
|
||||
"notification.follow_request": "{name} {count, plural, one {poprosił(a)} other {poprosili}} Cię o możliwość obserwacji",
|
||||
"notification.group_favourite": "{name} {count, plural, one {polubił(a)} other {polubili}} Twój grupowy wpis",
|
||||
"notification.group_reblog": "{name} {count, plural, one {udostępnił(a)} other {udostępnili}} Twój grupowy wpis",
|
||||
"notification.mentioned": "{name} wspomniał(a) o tobie",
|
||||
"notification.move": "{name} przeniósł(-osła) się na {targetName}",
|
||||
"notification.more": "{count, plural, one {# inny użytkownik} other {# innych użytkowników}}",
|
||||
"notification.move": "{name} {count, plural, one {przeniosł(a)} other {przenieśli}} się na {targetName}",
|
||||
"notification.name": "{link}{others}",
|
||||
"notification.others": "+ {count} więcej",
|
||||
"notification.pleroma:chat_mention": "{name} wysłał(a) Ci wiadomośść",
|
||||
"notification.pleroma:emoji_reaction": "{name} zareagował(a) na Twój wpis",
|
||||
"notification.pleroma:chat_mention": "{name} {count, plural, one {wysłał(a)} other {wysłali}} Ci wiadomośść",
|
||||
"notification.pleroma:emoji_reaction": "{name} {count, plural, one {zareagował(a)} other {zareagowali}} na Twój wpis",
|
||||
"notification.pleroma:event_reminder": "Wydarzenie w którym bierzesz udział wkrótce się zaczyna",
|
||||
"notification.pleroma:participation_accepted": "Twoje zgłoszenie udziału do wydarzenia zostało przyjęte",
|
||||
"notification.pleroma:participation_request": "{name} cce wziąć udział w Twoim wydarzeniu",
|
||||
"notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyła się",
|
||||
"notification.reblog": "{name} podbił(a) Twój wpis",
|
||||
"notification.status": "{name} właśnie opublikował(a) wpis",
|
||||
"notification.update": "{name} zedytował(a) wpis, który podbiłeś(-aś)",
|
||||
"notification.status": "{name} właśnie {count, plural, one {opublikował(a)} other {opublikowali}} wpis",
|
||||
"notification.update": "{name} {count, plural, one {zedytował(a)} other {zedytowali}} wpis, który podbiłeś(-aś)",
|
||||
"notification.user_approved": "Witamy w {instance}!",
|
||||
"notifications.filter.all": "Wszystkie",
|
||||
"notifications.filter.boosts": "Podbicia",
|
||||
|
@ -1423,7 +1423,7 @@
|
|||
"status.read_more": "Czytaj dalej",
|
||||
"status.reblog": "Podbij",
|
||||
"status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu",
|
||||
"status.reblogged_by": "{name} podbił(a)",
|
||||
"status.reblogged_by": "{name} {count, plural, one {podbił(a)} other {podbili}}",
|
||||
"status.reblogged_by_with_group": "{name} udostępnił(a) z {group}",
|
||||
"status.reblogs.empty": "Nikt nie podbił jeszcze tego wpisu. Gdy ktoś to zrobi, pojawi się tutaj.",
|
||||
"status.redraft": "Usuń i przeredaguj",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* @see {@link https://docs.joinmastodon.org/entities/notification/}
|
||||
*/
|
||||
import {
|
||||
List as ImmutableList,
|
||||
Map as ImmutableMap,
|
||||
Record as ImmutableRecord,
|
||||
fromJS,
|
||||
|
@ -14,6 +15,7 @@ import type { Account, Status, EmbeddedEntity } from 'soapbox/types/entities';
|
|||
// https://docs.joinmastodon.org/entities/notification/
|
||||
export const NotificationRecord = ImmutableRecord({
|
||||
account: null as EmbeddedEntity<Account>,
|
||||
accounts: null as ImmutableList<EmbeddedEntity<Account>> | null,
|
||||
chat_message: null as ImmutableMap<string, any> | string | null, // pleroma:chat_mention
|
||||
created_at: new Date(),
|
||||
emoji: null as string | null, // pleroma:emoji_reaction
|
||||
|
|
|
@ -44,6 +44,7 @@ interface Tombstone {
|
|||
// https://docs.joinmastodon.org/entities/status/
|
||||
export const StatusRecord = ImmutableRecord({
|
||||
account: null as unknown as Account,
|
||||
accounts: null as ImmutableList<Account> | null,
|
||||
application: null as ImmutableMap<string, any> | null,
|
||||
approval_status: 'approved' as StatusApprovalStatus,
|
||||
bookmarked: false,
|
||||
|
@ -274,6 +275,17 @@ const parseAccount = (status: ImmutableMap<string, any>) => {
|
|||
}
|
||||
};
|
||||
|
||||
const parseAccounts = (status: ImmutableMap<string, any>) => {
|
||||
try {
|
||||
if (status.get('accounts')) {
|
||||
const accounts = status.get('accounts').map((account: ImmutableMap<string, any>) => accountSchema.parse(maybeFromJS(account)));
|
||||
return status.set('accounts', accounts);
|
||||
}
|
||||
} catch (_e) {
|
||||
return status.set('accounts', null);
|
||||
}
|
||||
};
|
||||
|
||||
const parseGroup = (status: ImmutableMap<string, any>) => {
|
||||
try {
|
||||
const group = groupSchema.parse(status.get('group').toJS());
|
||||
|
@ -303,6 +315,7 @@ export const normalizeStatus = (status: Record<string, any>) => {
|
|||
normalizeDislikes(status);
|
||||
normalizeTombstone(status);
|
||||
parseAccount(status);
|
||||
parseAccounts(status);
|
||||
parseGroup(status);
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -71,6 +71,7 @@ const comparator = (a: NotificationRecord, b: NotificationRecord) => {
|
|||
const minifyNotification = (notification: NotificationRecord) => {
|
||||
return notification.mergeWith((o, n) => n || o, {
|
||||
account: notification.getIn(['account', 'id']) as string,
|
||||
accounts: notification.accounts?.map((account: any) => account.get('id')),
|
||||
target: notification.getIn(['target', 'id']) as string,
|
||||
status: notification.getIn(['status', 'id']) as string,
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ const statusPleromaSchema = z.object({
|
|||
|
||||
const baseStatusSchema = z.object({
|
||||
account: accountSchema,
|
||||
accounts: z.array(accountSchema),
|
||||
application: z.object({
|
||||
name: z.string(),
|
||||
website: z.string().url().nullable().catch(null),
|
||||
|
|
|
@ -28,6 +28,10 @@ export function selectAccount(state: RootState, accountId: string) {
|
|||
return state.entities[Entities.ACCOUNTS]?.store[accountId] as AccountSchema | undefined;
|
||||
}
|
||||
|
||||
export function selectAccounts(state: RootState, accountIds: ImmutableList<string>) {
|
||||
return accountIds.map(accountId => state.entities[Entities.ACCOUNTS]?.store[accountId] as AccountSchema | undefined);
|
||||
}
|
||||
|
||||
export function selectOwnAccount(state: RootState) {
|
||||
if (state.me) {
|
||||
return selectAccount(state, state.me);
|
||||
|
@ -162,7 +166,8 @@ export const makeGetNotification = () => {
|
|||
(state: RootState, notification: Notification) => selectAccount(state, normalizeId(notification.account)),
|
||||
(state: RootState, notification: Notification) => selectAccount(state, normalizeId(notification.target)),
|
||||
(state: RootState, notification: Notification) => state.statuses.get(normalizeId(notification.status)),
|
||||
], (notification, account, target, status) => {
|
||||
(state: RootState, notification: Notification) => notification.accounts ? selectAccounts(state, notification.accounts?.map(normalizeId)) : null,
|
||||
], (notification, account, target, status, accounts) => {
|
||||
return notification.merge({
|
||||
// @ts-ignore
|
||||
account: account || null,
|
||||
|
@ -170,6 +175,8 @@ export const makeGetNotification = () => {
|
|||
target: target || null,
|
||||
// @ts-ignore
|
||||
status: status || null,
|
||||
// @ts-ignore
|
||||
accounts,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue