pl-fe: Remove some immutable usage
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
f560896311
commit
563e5288fb
41 changed files with 482 additions and 463 deletions
|
@ -255,7 +255,7 @@ class PlApiClient {
|
|||
unlisten: (listener: any) => void;
|
||||
subscribe: (stream: string, params?: { list?: string; tag?: string }) => void;
|
||||
unsubscribe: (stream: string, params?: { list?: string; tag?: string }) => void;
|
||||
close: () => void;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
constructor(baseURL: string, accessToken?: string, {
|
||||
|
|
|
@ -8,7 +8,6 @@ import { getClient, type PlfeResponse } from '../api';
|
|||
|
||||
import { importEntities } from './importer';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
import type { History } from 'pl-fe/types/history';
|
||||
|
@ -190,7 +189,7 @@ const blockAccountRequest = (accountId: string) => ({
|
|||
accountId,
|
||||
});
|
||||
|
||||
const blockAccountSuccess = (relationship: Relationship, statuses: ImmutableMap<string, MinifiedStatus>) => ({
|
||||
const blockAccountSuccess = (relationship: Relationship, statuses: Record<string, MinifiedStatus>) => ({
|
||||
type: ACCOUNT_BLOCK_SUCCESS,
|
||||
relationship,
|
||||
statuses,
|
||||
|
@ -245,7 +244,7 @@ const muteAccountRequest = (accountId: string) => ({
|
|||
accountId,
|
||||
});
|
||||
|
||||
const muteAccountSuccess = (relationship: Relationship, statuses: ImmutableMap<string, MinifiedStatus>) => ({
|
||||
const muteAccountSuccess = (relationship: Relationship, statuses: Record<string, MinifiedStatus>) => ({
|
||||
type: ACCOUNT_MUTE_SUCCESS,
|
||||
relationship,
|
||||
statuses,
|
||||
|
|
|
@ -162,7 +162,7 @@ const submitEventFail = (error: unknown) => ({
|
|||
|
||||
const joinEvent = (statusId: string, participationMessage?: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const status = getState().statuses.get(statusId);
|
||||
const status = getState().statuses[statusId];
|
||||
|
||||
if (!status || !status.event || status.event.join_state) {
|
||||
return dispatch(noOp);
|
||||
|
@ -204,7 +204,7 @@ const joinEventFail = (error: unknown, statusId: string, previousState: string |
|
|||
|
||||
const leaveEvent = (statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const status = getState().statuses.get(statusId);
|
||||
const status = getState().statuses[statusId];
|
||||
|
||||
if (!status || !status.event || !status.event.join_state) {
|
||||
return dispatch(noOp);
|
||||
|
|
|
@ -114,7 +114,7 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () =
|
|||
const toggleStatusSensitivityModal = (intl: IntlShape, statusId: string, sensitive: boolean, afterConfirm = () => {}) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const acct = state.statuses.get(statusId)!.account.acct;
|
||||
const acct = state.statuses[statusId]!.account.acct;
|
||||
|
||||
useModalsStore.getState().openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(sensitive === false ? messages.markStatusSensitiveHeading : messages.markStatusNotSensitiveHeading),
|
||||
|
@ -133,7 +133,7 @@ const toggleStatusSensitivityModal = (intl: IntlShape, statusId: string, sensiti
|
|||
const deleteStatusModal = (intl: IntlShape, statusId: string, afterConfirm = () => {}) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const acct = state.statuses.get(statusId)!.account.acct;
|
||||
const acct = state.statuses[statusId]!.account.acct;
|
||||
|
||||
useModalsStore.getState().openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.deleteStatusHeading),
|
||||
|
|
|
@ -26,7 +26,7 @@ const simplePolicyMerge = (simplePolicy: MRFSimple, host: string, restrictions:
|
|||
const updateMrf = (host: string, restrictions: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
dispatch(fetchConfig()).then(() => {
|
||||
const configs = getState().admin.get('configs');
|
||||
const configs = getState().admin.configs;
|
||||
const simplePolicy = ConfigDB.toSimplePolicy(configs);
|
||||
const merged = simplePolicyMerge(simplePolicy, host, restrictions);
|
||||
const config = ConfigDB.fromSimplePolicy(merged);
|
||||
|
|
|
@ -17,8 +17,7 @@ const PLFE_CONFIG_REMEMBER_SUCCESS = 'PLFE_CONFIG_REMEMBER_SUCCESS' as const;
|
|||
|
||||
const getPlFeConfig = createSelector([
|
||||
(state: RootState) => state.plfe,
|
||||
(state: RootState) => state.auth.client.features,
|
||||
], (plfe, features) => {
|
||||
], (plfe) => {
|
||||
// Do some additional normalization with the state
|
||||
return normalizePlFeConfig(plfe);
|
||||
});
|
||||
|
|
|
@ -96,7 +96,7 @@ const createStatus = (params: CreateStatusParams, idempotencyKey: string, status
|
|||
const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
|
||||
const status = state.statuses.get(statusId)!;
|
||||
const status = state.statuses[statusId]!;
|
||||
const poll = status.poll_id ? state.polls.get(status.poll_id) : undefined;
|
||||
|
||||
dispatch({ type: STATUS_FETCH_SOURCE_REQUEST });
|
||||
|
@ -133,7 +133,7 @@ const deleteStatus = (statusId: string, withRedraft = false) =>
|
|||
|
||||
const state = getState();
|
||||
|
||||
const status = state.statuses.get(statusId)!;
|
||||
const status = state.statuses[statusId]!;
|
||||
const poll = status.poll_id ? state.polls.get(status.poll_id) : undefined;
|
||||
|
||||
dispatch({ type: STATUS_DELETE_REQUEST, params: status });
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { getLocale } from 'pl-fe/actions/settings';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { shouldFilter } from 'pl-fe/utils/timelines';
|
||||
|
@ -106,15 +104,17 @@ interface TimelineDeleteAction {
|
|||
type: typeof TIMELINE_DELETE;
|
||||
statusId: string;
|
||||
accountId: string;
|
||||
references: ImmutableMap<string, readonly [statusId: string, accountId: string]>;
|
||||
references: Record<string, readonly [statusId: string, accountId: string]>;
|
||||
reblogOf: string | null;
|
||||
}
|
||||
|
||||
const deleteFromTimelines = (statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const accountId = getState().statuses.get(statusId)?.account?.id!;
|
||||
const references = getState().statuses.filter(status => status.reblog_id === statusId).map(status => [status.id, status.account_id] as const);
|
||||
const reblogOf = getState().statuses.get(statusId)?.reblog_id || null;
|
||||
const accountId = getState().statuses[statusId]?.account?.id!;
|
||||
const references = Object.fromEntries(Object.entries(getState().statuses)
|
||||
.filter(([key, status]) => [key, status.reblog_id === statusId])
|
||||
.map(([key, status]) => [key, [status.id, status.account_id] as const]));
|
||||
const reblogOf = getState().statuses[statusId]?.reblog_id || null;
|
||||
|
||||
dispatch<TimelineDeleteAction>({
|
||||
type: TIMELINE_DELETE,
|
||||
|
|
|
@ -12,8 +12,8 @@ FaviconService.initFaviconService();
|
|||
|
||||
const getNotifTotals = (state: RootState): number => {
|
||||
const notifications = state.notifications.unread || 0;
|
||||
const reports = state.admin.openReports.count();
|
||||
const approvals = state.admin.awaitingApproval.count();
|
||||
const reports = state.admin.openReports.length;
|
||||
const approvals = state.admin.awaitingApproval.length;
|
||||
return notifications + reports + approvals;
|
||||
};
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ const SidebarNavigation = () => {
|
|||
const notificationCount = useAppSelector((state) => state.notifications.unread);
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
|
||||
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
|
||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length);
|
||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||
const draftCount = useAppSelector((state) => state.draft_statuses.size);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ const StatusHoverCard: React.FC<IStatusHoverCard> = ({ visible = true }) => {
|
|||
|
||||
const { statusId, ref, closeStatusHoverCard, updateStatusHoverCard } = useStatusHoverCardStore();
|
||||
|
||||
const status = useAppSelector(state => state.statuses.get(statusId!));
|
||||
const status = useAppSelector(state => state.statuses[statusId!]);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusId && !status) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useEffect } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
@ -27,7 +26,7 @@ const AccountGallery = () => {
|
|||
isUnavailable,
|
||||
} = useAccountLookup(username, { withRelationship: true });
|
||||
|
||||
const attachments: ImmutableList<AccountGalleryAttachment> = useAppSelector((state) => account ? getAccountGallery(state, account.id) : ImmutableList());
|
||||
const attachments: Array<AccountGalleryAttachment> = useAppSelector((state) => account ? getAccountGallery(state, account.id) : []);
|
||||
const isLoading = useAppSelector((state) => state.timelines.get(`account:${account?.id}:with_replies:media`)?.isLoading);
|
||||
const hasMore = useAppSelector((state) => state.timelines.get(`account:${account?.id}:with_replies:media`)?.hasMore);
|
||||
|
||||
|
@ -81,7 +80,7 @@ const AccountGallery = () => {
|
|||
|
||||
let loadOlder = null;
|
||||
|
||||
if (hasMore && !(isLoading && attachments.size === 0)) {
|
||||
if (hasMore && !(isLoading && attachments.length === 0)) {
|
||||
loadOlder = <LoadMore className='my-auto mt-4' visible={!isLoading} onClick={handleLoadOlder} />;
|
||||
}
|
||||
|
||||
|
@ -103,11 +102,11 @@ const AccountGallery = () => {
|
|||
key={`${attachment.status.id}+${attachment.id}`}
|
||||
attachment={attachment}
|
||||
onOpenMedia={handleOpenMedia}
|
||||
isLast={index === attachments.size - 1}
|
||||
isLast={index === attachments.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
{!isLoading && attachments.size === 0 && (
|
||||
{!isLoading && attachments.length === 0 && (
|
||||
<div className='empty-column-indicator col-span-2 sm:col-span-3'>
|
||||
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.' />
|
||||
</div>
|
||||
|
@ -116,7 +115,7 @@ const AccountGallery = () => {
|
|||
|
||||
{loadOlder}
|
||||
|
||||
{isLoading && attachments.size === 0 && (
|
||||
{isLoading && attachments.length === 0 && (
|
||||
<div className='relative flex-auto px-8 py-4'>
|
||||
<Spinner />
|
||||
</div>
|
||||
|
|
|
@ -15,8 +15,8 @@ const AdminTabs: React.FC = () => {
|
|||
const intl = useIntl();
|
||||
const match = useRouteMatch();
|
||||
|
||||
const approvalCount = useAppSelector(state => state.admin.awaitingApproval.count());
|
||||
const reportsCount = useAppSelector(state => state.admin.openReports.count());
|
||||
const approvalCount = useAppSelector(state => state.admin.awaitingApproval.length);
|
||||
const reportsCount = useAppSelector(state => state.admin.openReports.length);
|
||||
|
||||
const tabs = [{
|
||||
name: '/pl-fe/admin',
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
@ -22,9 +21,9 @@ const LatestAccountsPanel: React.FC<ILatestAccountsPanel> = ({ limit = 5 }) => {
|
|||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const accountIds = useAppSelector<ImmutableOrderedSet<string>>((state) => state.admin.get('latestUsers').take(limit));
|
||||
const accountIds = useAppSelector<Array<string>>((state) => state.admin.latestUsers.slice(0, limit));
|
||||
|
||||
const [total, setTotal] = useState<number | undefined>(accountIds.size);
|
||||
const [total, setTotal] = useState<number | undefined>(accountIds.length);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchUsers({
|
||||
|
@ -46,7 +45,7 @@ const LatestAccountsPanel: React.FC<ILatestAccountsPanel> = ({ limit = 5 }) => {
|
|||
onActionClick={handleAction}
|
||||
actionTitle={intl.formatMessage(messages.expand, { count: total })}
|
||||
>
|
||||
{accountIds.take(limit).map((account) => (
|
||||
{accountIds.slice(0, limit).map((account) => (
|
||||
<AccountContainer key={account} id={account} withRelationship={false} withDate />
|
||||
))}
|
||||
</Widget>
|
||||
|
|
|
@ -18,7 +18,7 @@ const UnapprovedAccount: React.FC<IUnapprovedAccount> = ({ accountId }) => {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const { account } = useAccount(accountId);
|
||||
const adminAccount = useAppSelector(state => state.admin.users.get(accountId));
|
||||
const adminAccount = useAppSelector(state => state.admin.users[accountId]);
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ const AwaitingApproval: React.FC = () => {
|
|||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const showLoading = isLoading && accountIds.count() === 0;
|
||||
const showLoading = isLoading && accountIds.length === 0;
|
||||
|
||||
return (
|
||||
<ScrollableList
|
||||
|
|
|
@ -20,7 +20,7 @@ const Reports: React.FC = () => {
|
|||
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
|
||||
const reports = useAppSelector(state => state.admin.openReports.toList());
|
||||
const reports = useAppSelector(state => state.admin.openReports);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchReports())
|
||||
|
@ -28,7 +28,7 @@ const Reports: React.FC = () => {
|
|||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const showLoading = isLoading && reports.count() === 0;
|
||||
const showLoading = isLoading && reports.length === 0;
|
||||
|
||||
return (
|
||||
<ScrollableList
|
||||
|
|
|
@ -37,7 +37,7 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
interface IUploadButton {
|
||||
onSelectFile: (src: string) => void;
|
||||
onSelectFile: (src: string) => void;
|
||||
}
|
||||
|
||||
const UploadButton: React.FC<IUploadButton> = ({ onSelectFile }) => {
|
||||
|
|
|
@ -40,7 +40,7 @@ const StatePlugin: React.FC<IStatePlugin> = ({ composeId, isWysiwyg }) => {
|
|||
for (const id of ids) {
|
||||
if (compose?.dismissed_quotes.includes(id)) continue;
|
||||
|
||||
if (state.statuses.get(id)) {
|
||||
if (state.statuses[id]) {
|
||||
quoteId = id;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { ElementNode, RangeSelection, TextNode } from 'lexical';
|
|||
|
||||
export const getSelectedNode = (
|
||||
selection: RangeSelection,
|
||||
): TextNode | ElementNode => {
|
||||
): TextNode | ElementNode => {
|
||||
const anchor = selection.anchor;
|
||||
const focus = selection.focus;
|
||||
const anchorNode = selection.anchor.getNode();
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import { fromJS } from 'immutable';
|
||||
|
||||
import manifestMap from './manifest-map';
|
||||
|
||||
// All this does is converts the result from manifest_map.js into an ImmutableMap
|
||||
const coinDB = fromJS(manifestMap);
|
||||
import coinDB from './manifest-map';
|
||||
|
||||
/** Get title from CoinDB based on ticker symbol */
|
||||
const getTitle = (ticker: string): string => {
|
||||
const title = coinDB.getIn([ticker, 'name']);
|
||||
const title = coinDB[ticker]?.name;
|
||||
return typeof title === 'string' ? title : '';
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
|
@ -38,7 +37,7 @@ const EventDiscussion: React.FC<IEventDiscussion> = ({ params: { statusId: statu
|
|||
|
||||
const me = useAppSelector((state) => state.me);
|
||||
|
||||
const descendantsIds = useAppSelector(state => getDescendantsIds(state, statusId).delete(statusId));
|
||||
const descendantsIds = useAppSelector(state => getDescendantsIds(state, statusId).filter(id => id !== statusId));
|
||||
|
||||
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
|
||||
|
||||
|
@ -61,12 +60,12 @@ const EventDiscussion: React.FC<IEventDiscussion> = ({ params: { statusId: statu
|
|||
}, [isLoaded, me]);
|
||||
|
||||
const handleMoveUp = (id: string) => {
|
||||
const index = ImmutableList(descendantsIds).indexOf(id);
|
||||
const index = descendantsIds.indexOf(id);
|
||||
_selectChild(index - 1);
|
||||
};
|
||||
|
||||
const handleMoveDown = (id: string) => {
|
||||
const index = ImmutableList(descendantsIds).indexOf(id);
|
||||
const index = descendantsIds.indexOf(id);
|
||||
_selectChild(index + 1);
|
||||
};
|
||||
|
||||
|
@ -110,7 +109,7 @@ const EventDiscussion: React.FC<IEventDiscussion> = ({ params: { statusId: statu
|
|||
);
|
||||
};
|
||||
|
||||
const renderChildren = (list: ImmutableOrderedSet<string>) => list.map(id => {
|
||||
const renderChildren = (list: Array<string>) => list.map(id => {
|
||||
if (id.endsWith('-tombstone')) {
|
||||
return renderTombstone(id);
|
||||
} else if (id.startsWith('末pending-')) {
|
||||
|
@ -120,7 +119,7 @@ const EventDiscussion: React.FC<IEventDiscussion> = ({ params: { statusId: statu
|
|||
}
|
||||
});
|
||||
|
||||
const hasDescendants = descendantsIds.size > 0;
|
||||
const hasDescendants = descendantsIds.length > 0;
|
||||
|
||||
if (!status && isLoaded) {
|
||||
return (
|
||||
|
@ -135,7 +134,7 @@ const EventDiscussion: React.FC<IEventDiscussion> = ({ params: { statusId: statu
|
|||
const children: JSX.Element[] = [];
|
||||
|
||||
if (hasDescendants) {
|
||||
children.push(...renderChildren(descendantsIds).toArray());
|
||||
children.push(...renderChildren(descendantsIds));
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -17,8 +17,6 @@ import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config';
|
|||
import { makeGetStatus } from 'pl-fe/selectors';
|
||||
import { useModalsStore } from 'pl-fe/stores/modals';
|
||||
|
||||
import type { Status as StatusEntity } from 'pl-fe/normalizers/status';
|
||||
|
||||
type RouteParams = { statusId: string };
|
||||
|
||||
interface IEventInformation {
|
||||
|
@ -30,7 +28,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
|||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const intl = useIntl();
|
||||
|
||||
const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity;
|
||||
const status = useAppSelector(state => getStatus(state, { id: params.statusId }))!;
|
||||
|
||||
const { openModal } = useModalsStore();
|
||||
const { tileServer } = usePlFeConfig();
|
||||
|
|
|
@ -52,7 +52,7 @@ interface IInteractionRequestStatus {
|
|||
}
|
||||
|
||||
const InteractionRequestStatus: React.FC<IInteractionRequestStatus> = ({ id: statusId, hasReply, isReply, actions }) => {
|
||||
const status = useAppSelector((state) => state.statuses.get(statusId));
|
||||
const status = useAppSelector((state) => state.statuses[statusId]);
|
||||
|
||||
if (!status) return null;
|
||||
|
||||
|
|
|
@ -128,7 +128,8 @@ const PlFeConfig: React.FC = () => {
|
|||
};
|
||||
|
||||
const addStreamItem = (path: ConfigPath, template: Template) => () => {
|
||||
const items = data.getIn(path) || ImmutableList();
|
||||
let items = data;
|
||||
path.forEach(key => items = items?.[key] || []);
|
||||
setConfig(path, items.push(template));
|
||||
};
|
||||
|
||||
|
@ -257,7 +258,7 @@ const PlFeConfig: React.FC = () => {
|
|||
<Input
|
||||
type='text'
|
||||
placeholder='/timeline/local'
|
||||
value={String(data.get('redirectRootNoLogin', ''))}
|
||||
value={String(data.redirectRootNoLogin || '')}
|
||||
onChange={handleChange(['redirectRootNoLogin'], (e) => e.target.value)}
|
||||
/>
|
||||
</ListItem>
|
||||
|
@ -269,7 +270,7 @@ const PlFeConfig: React.FC = () => {
|
|||
<Input
|
||||
type='text'
|
||||
placeholder='https://01234abcdef@glitch.tip.tld/5678'
|
||||
value={String(data.get('sentryDsn', ''))}
|
||||
value={String(data.sentryDsn || '')}
|
||||
onChange={handleChange(['sentryDsn'], (e) => e.target.value)}
|
||||
/>
|
||||
</ListItem>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React from 'react';
|
||||
|
||||
import StatusContainer from 'pl-fe/containers/status-container';
|
||||
|
@ -18,9 +17,9 @@ interface IThreadStatus {
|
|||
const ThreadStatus: React.FC<IThreadStatus> = (props): JSX.Element => {
|
||||
const { id, focusedStatusId } = props;
|
||||
|
||||
const replyToId = useAppSelector(state => state.contexts.inReplyTos.get(id));
|
||||
const replyCount = useAppSelector(state => state.contexts.replies.get(id, ImmutableOrderedSet()).size);
|
||||
const isLoaded = useAppSelector(state => Boolean(state.statuses.get(id)));
|
||||
const replyToId = useAppSelector(state => state.contexts.inReplyTos[id]);
|
||||
const replyCount = useAppSelector(state => (state.contexts.replies[id] || []).length);
|
||||
const isLoaded = useAppSelector(state => Boolean(state.statuses[id]));
|
||||
|
||||
const renderConnector = (): JSX.Element | null => {
|
||||
const isConnectedTop = replyToId && replyToId !== focusedStatusId;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import clsx from 'clsx';
|
||||
import { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
@ -35,36 +34,36 @@ const makeGetAncestorsIds = () => createSelector([
|
|||
(_: RootState, statusId: string | undefined) => statusId,
|
||||
(state: RootState) => state.contexts.inReplyTos,
|
||||
], (statusId, inReplyTos) => {
|
||||
let ancestorsIds = ImmutableOrderedSet<string>();
|
||||
let ancestorsIds: Array<string> = [];
|
||||
let id: string | undefined = statusId;
|
||||
|
||||
while (id && !ancestorsIds.includes(id)) {
|
||||
ancestorsIds = ImmutableOrderedSet([id]).union(ancestorsIds);
|
||||
id = inReplyTos.get(id);
|
||||
ancestorsIds = [id, ...ancestorsIds];
|
||||
id = inReplyTos[id];
|
||||
}
|
||||
|
||||
return ancestorsIds;
|
||||
return [...new Set(ancestorsIds)];
|
||||
});
|
||||
|
||||
const makeGetDescendantsIds = () => createSelector([
|
||||
(_: RootState, statusId: string) => statusId,
|
||||
(state: RootState) => state.contexts.replies,
|
||||
], (statusId, contextReplies) => {
|
||||
let descendantsIds = ImmutableOrderedSet<string>();
|
||||
let descendantsIds: Array<string> = [];
|
||||
const ids = [statusId];
|
||||
|
||||
while (ids.length > 0) {
|
||||
const id = ids.shift();
|
||||
if (!id) break;
|
||||
|
||||
const replies = contextReplies.get(id);
|
||||
const replies = contextReplies[id];
|
||||
|
||||
if (descendantsIds.includes(id)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (statusId !== id) {
|
||||
descendantsIds = descendantsIds.union([id]);
|
||||
descendantsIds = [...descendantsIds, id];
|
||||
}
|
||||
|
||||
if (replies) {
|
||||
|
@ -74,7 +73,7 @@ const makeGetDescendantsIds = () => createSelector([
|
|||
}
|
||||
}
|
||||
|
||||
return descendantsIds;
|
||||
return [...new Set(descendantsIds)];
|
||||
});
|
||||
|
||||
const makeGetThread = () => {
|
||||
|
@ -87,8 +86,8 @@ const makeGetThread = () => {
|
|||
(_, statusId: string) => statusId,
|
||||
],
|
||||
(ancestorsIds, descendantsIds, statusId) => {
|
||||
ancestorsIds = ancestorsIds.delete(statusId).subtract(descendantsIds);
|
||||
descendantsIds = descendantsIds.delete(statusId).subtract(ancestorsIds);
|
||||
ancestorsIds = ancestorsIds.filter(id => id !== statusId && !descendantsIds.includes(id));
|
||||
descendantsIds = descendantsIds.filter(id => id !== statusId && !ancestorsIds.includes(id));
|
||||
|
||||
return {
|
||||
ancestorsIds,
|
||||
|
@ -121,8 +120,8 @@ const Thread: React.FC<IThread> = ({
|
|||
|
||||
const { ancestorsIds, descendantsIds } = useAppSelector((state) => getThread(state, status.id));
|
||||
|
||||
let initialIndex = ancestorsIds.size;
|
||||
if (isModal && initialIndex !== 0) initialIndex = ancestorsIds.size + 1;
|
||||
let initialIndex = ancestorsIds.length;
|
||||
if (isModal && initialIndex !== 0) initialIndex = ancestorsIds.length + 1;
|
||||
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
const statusRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -213,13 +212,13 @@ const Thread: React.FC<IThread> = ({
|
|||
|
||||
const handleMoveUp = (id: string) => {
|
||||
if (id === status?.id) {
|
||||
_selectChild(ancestorsIds.size - 1);
|
||||
_selectChild(ancestorsIds.length - 1);
|
||||
} else {
|
||||
let index = ImmutableList(ancestorsIds).indexOf(id);
|
||||
let index = ancestorsIds.indexOf(id);
|
||||
|
||||
if (index === -1) {
|
||||
index = ImmutableList(descendantsIds).indexOf(id);
|
||||
_selectChild(ancestorsIds.size + index);
|
||||
index = descendantsIds.indexOf(id);
|
||||
_selectChild(ancestorsIds.length + index);
|
||||
} else {
|
||||
_selectChild(index - 1);
|
||||
}
|
||||
|
@ -228,13 +227,13 @@ const Thread: React.FC<IThread> = ({
|
|||
|
||||
const handleMoveDown = (id: string) => {
|
||||
if (id === status?.id) {
|
||||
_selectChild(ancestorsIds.size + 1);
|
||||
_selectChild(ancestorsIds.length + 1);
|
||||
} else {
|
||||
let index = ImmutableList(ancestorsIds).indexOf(id);
|
||||
let index = ancestorsIds.indexOf(id);
|
||||
|
||||
if (index === -1) {
|
||||
index = ImmutableList(descendantsIds).indexOf(id);
|
||||
_selectChild(ancestorsIds.size + index + 2);
|
||||
index = descendantsIds.indexOf(id);
|
||||
_selectChild(ancestorsIds.length + index + 2);
|
||||
} else {
|
||||
_selectChild(index + 1);
|
||||
}
|
||||
|
@ -289,7 +288,7 @@ const Thread: React.FC<IThread> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const renderChildren = (list: ImmutableOrderedSet<string>) => list.map(id => {
|
||||
const renderChildren = (list: Array<string>) => list.map(id => {
|
||||
if (id.endsWith('-tombstone')) {
|
||||
return renderTombstone(id);
|
||||
} else if (id.startsWith('末pending-')) {
|
||||
|
@ -301,8 +300,8 @@ const Thread: React.FC<IThread> = ({
|
|||
|
||||
// Scroll focused status into view when thread updates.
|
||||
useEffect(() => {
|
||||
virtualizer.current?.scrollToIndex(ancestorsIds.size);
|
||||
}, [status.id, ancestorsIds.size]);
|
||||
virtualizer.current?.scrollToIndex(ancestorsIds.length);
|
||||
}, [status.id, ancestorsIds.length]);
|
||||
|
||||
const handleOpenCompareHistoryModal = (status: Pick<Status, 'id'>) => {
|
||||
openModal('COMPARE_HISTORY', {
|
||||
|
@ -310,8 +309,8 @@ const Thread: React.FC<IThread> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const hasAncestors = ancestorsIds.size > 0;
|
||||
const hasDescendants = descendantsIds.size > 0;
|
||||
const hasAncestors = ancestorsIds.length > 0;
|
||||
const hasDescendants = descendantsIds.length > 0;
|
||||
|
||||
type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void };
|
||||
|
||||
|
@ -370,13 +369,13 @@ const Thread: React.FC<IThread> = ({
|
|||
}
|
||||
|
||||
if (hasAncestors) {
|
||||
children.push(...renderChildren(ancestorsIds).toArray());
|
||||
children.push(...renderChildren(ancestorsIds));
|
||||
}
|
||||
|
||||
children.push(focusedStatus);
|
||||
|
||||
if (hasDescendants) {
|
||||
children.push(...renderChildren(descendantsIds).toArray());
|
||||
children.push(...renderChildren(descendantsIds));
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -25,7 +25,7 @@ const CompareHistoryModal: React.FC<BaseModalProps & CompareHistoryModalProps> =
|
|||
const loading = useAppSelector(state => state.history.getIn([statusId, 'loading']));
|
||||
const versions = useAppSelector(state => state.history.get(statusId)?.items);
|
||||
|
||||
const status = useAppSelector(state => state.statuses.get(statusId));
|
||||
const status = useAppSelector(state => state.statuses[statusId]);
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('COMPARE_HISTORY');
|
||||
|
|
|
@ -15,7 +15,7 @@ interface IStatusCheckBox {
|
|||
}
|
||||
|
||||
const StatusCheckBox: React.FC<IStatusCheckBox> = ({ id, disabled, checked, toggleStatusReport }) => {
|
||||
const status = useAppSelector((state) => state.statuses.get(id));
|
||||
const status = useAppSelector((state) => state.statuses[id]);
|
||||
|
||||
const onToggle: React.ChangeEventHandler<HTMLInputElement> = (e) => toggleStatusReport(e.target.checked);
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ const reportSteps = {
|
|||
};
|
||||
|
||||
const SelectedStatus = ({ statusId }: { statusId: string }) => {
|
||||
const status = useAppSelector((state) => state.statuses.get(statusId));
|
||||
const status = useAppSelector((state) => state.statuses[statusId]);
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
|
|
|
@ -16,7 +16,6 @@ import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
|||
import { makeGetStatus } from 'pl-fe/selectors';
|
||||
|
||||
import type { BaseModalProps } from '../modal-root';
|
||||
import type { Status as StatusEntity } from 'pl-fe/normalizers/status';
|
||||
|
||||
interface SelectBookmarkFolderModalProps {
|
||||
statusId: string;
|
||||
|
@ -24,7 +23,7 @@ interface SelectBookmarkFolderModalProps {
|
|||
|
||||
const SelectBookmarkFolderModal: React.FC<SelectBookmarkFolderModalProps & BaseModalProps> = ({ statusId, onClose }) => {
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const status = useAppSelector(state => getStatus(state, { id: statusId })) as StatusEntity;
|
||||
const status = useAppSelector(state => getStatus(state, { id: statusId }))!;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [selectedFolder, setSelectedFolder] = useState(status.bookmark_folder);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -28,7 +27,7 @@ const GroupMediaPanel: React.FC<IGroupMediaPanel> = ({ group }) => {
|
|||
const isMember = !!group?.relationship?.member;
|
||||
const isPrivate = group?.locked;
|
||||
|
||||
const attachments: ImmutableList<AccountGalleryAttachment> = useAppSelector((state) => group ? getGroupGallery(state, group?.id) : ImmutableList());
|
||||
const attachments: Array<AccountGalleryAttachment> = useAppSelector((state) => group ? getGroupGallery(state, group?.id) : []);
|
||||
|
||||
const handleOpenMedia = (attachment: AccountGalleryAttachment): void => {
|
||||
if (attachment.type === 'video') {
|
||||
|
@ -55,7 +54,7 @@ const GroupMediaPanel: React.FC<IGroupMediaPanel> = ({ group }) => {
|
|||
const renderAttachments = () => {
|
||||
const nineAttachments = attachments.slice(0, 9);
|
||||
|
||||
if (!nineAttachments.isEmpty()) {
|
||||
if (nineAttachments.length) {
|
||||
return (
|
||||
<div className='grid grid-cols-3 gap-0.5 overflow-hidden rounded-md'>
|
||||
{nineAttachments.map((attachment, index) => (
|
||||
|
@ -63,7 +62,7 @@ const GroupMediaPanel: React.FC<IGroupMediaPanel> = ({ group }) => {
|
|||
key={`${attachment.status.id}+${attachment.id}`}
|
||||
attachment={attachment}
|
||||
onOpenMedia={handleOpenMedia}
|
||||
isLast={index === nineAttachments.size - 1}
|
||||
isLast={index === nineAttachments.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -25,7 +24,7 @@ const ProfileMediaPanel: React.FC<IProfileMediaPanel> = ({ account }) => {
|
|||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const attachments: ImmutableList<AccountGalleryAttachment> = useAppSelector((state) => account ? getAccountGallery(state, account?.id) : ImmutableList());
|
||||
const attachments: Array<AccountGalleryAttachment> = useAppSelector((state) => account ? getAccountGallery(state, account?.id) : []);
|
||||
|
||||
const handleOpenMedia = (attachment: AccountGalleryAttachment): void => {
|
||||
if (attachment.type === 'video') {
|
||||
|
@ -53,7 +52,7 @@ const ProfileMediaPanel: React.FC<IProfileMediaPanel> = ({ account }) => {
|
|||
const publicAttachments = attachments.filter(attachment => attachment.status.visibility === 'public');
|
||||
const nineAttachments = publicAttachments.slice(0, 9);
|
||||
|
||||
if (!nineAttachments.isEmpty()) {
|
||||
if (nineAttachments.length) {
|
||||
return (
|
||||
<div className='grid grid-cols-3 gap-0.5 overflow-hidden rounded-md'>
|
||||
{nineAttachments.map((attachment, index) => (
|
||||
|
@ -61,7 +60,7 @@ const ProfileMediaPanel: React.FC<IProfileMediaPanel> = ({ account }) => {
|
|||
key={`${attachment.status.id}+${attachment.id}`}
|
||||
attachment={attachment}
|
||||
onOpenMedia={handleOpenMedia}
|
||||
isLast={index === nineAttachments.size - 1}
|
||||
isLast={index === nineAttachments.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -37,12 +37,12 @@ const buildStatus = (state: RootState, pendingStatus: PendingStatus, idempotency
|
|||
account,
|
||||
content: pendingStatus.status.replace(new RegExp('\n', 'g'), '<br>'), /* eslint-disable-line no-control-regex */
|
||||
id: `末pending-${idempotencyKey}`,
|
||||
in_reply_to_account_id: state.statuses.getIn([inReplyToId, 'account'], null),
|
||||
in_reply_to_account_id: state.statuses[inReplyToId || '']?.account_id || null,
|
||||
in_reply_to_id: inReplyToId,
|
||||
media_attachments: (pendingStatus.media_ids || ImmutableList()).map((id: string) => ({ id })),
|
||||
mentions: buildMentions(pendingStatus),
|
||||
poll: buildPoll(pendingStatus),
|
||||
quote: pendingStatus.quote_id ? state.statuses.get(pendingStatus.quote_id) : null,
|
||||
quote: pendingStatus.quote_id ? state.statuses[pendingStatus.quote_id] : null,
|
||||
sensitive: pendingStatus.sensitive,
|
||||
visibility: pendingStatus.visibility,
|
||||
};
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
Map as ImmutableMap,
|
||||
List as ImmutableList,
|
||||
Record as ImmutableRecord,
|
||||
OrderedSet as ImmutableOrderedSet,
|
||||
fromJS,
|
||||
} from 'immutable';
|
||||
import omit from 'lodash/omit';
|
||||
import { create } from 'mutative';
|
||||
|
||||
import {
|
||||
ADMIN_CONFIG_FETCH_SUCCESS,
|
||||
|
@ -23,48 +17,41 @@ import type { AdminAccount, AdminGetAccountsParams, AdminReport as BaseAdminRepo
|
|||
import type { Config } from 'pl-fe/utils/config-db';
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
reports: ImmutableMap<string, MinifiedReport>(),
|
||||
openReports: ImmutableOrderedSet<string>(),
|
||||
users: ImmutableMap<string, MinifiedUser>(),
|
||||
latestUsers: ImmutableOrderedSet<string>(),
|
||||
awaitingApproval: ImmutableOrderedSet<string>(),
|
||||
configs: ImmutableList<Config>(),
|
||||
interface State {
|
||||
reports: Record<string, MinifiedReport>;
|
||||
openReports: Array<string>;
|
||||
users: Record<string, MinifiedUser>;
|
||||
latestUsers: Array<string>;
|
||||
awaitingApproval: Array<string>;
|
||||
configs: Array<Config>;
|
||||
needsReboot: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
reports: {},
|
||||
openReports: [],
|
||||
users: {},
|
||||
latestUsers: [],
|
||||
awaitingApproval: [],
|
||||
configs: [],
|
||||
needsReboot: false,
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
||||
// Lol https://javascript.plainenglish.io/typescript-essentials-conditionally-filter-types-488705bfbf56
|
||||
type FilterConditionally<Source, Condition> = Pick<Source, {[K in keyof Source]: Source[K] extends Condition ? K : never}[keyof Source]>;
|
||||
|
||||
type SetKeys = keyof FilterConditionally<State, ImmutableOrderedSet<string>>;
|
||||
};
|
||||
|
||||
const toIds = (items: any[]) => items.map(item => item.id);
|
||||
|
||||
const mergeSet = (state: State, key: SetKeys, users: Array<AdminAccount>): State => {
|
||||
const newIds = toIds(users);
|
||||
return state.update(key, (ids: ImmutableOrderedSet<string>) => ids.union(newIds));
|
||||
};
|
||||
|
||||
const replaceSet = (state: State, key: SetKeys, users: Array<AdminAccount>): State => {
|
||||
const newIds = toIds(users);
|
||||
return state.set(key, ImmutableOrderedSet(newIds));
|
||||
};
|
||||
|
||||
const maybeImportUnapproved = (state: State, users: Array<AdminAccount>, params?: AdminGetAccountsParams): State => {
|
||||
const maybeImportUnapproved = (state: State, users: Array<AdminAccount>, params?: AdminGetAccountsParams) => {
|
||||
if (params?.origin === 'local' && params.status === 'pending') {
|
||||
return mergeSet(state, 'awaitingApproval', users);
|
||||
const newIds = toIds(users);
|
||||
state.awaitingApproval = [...new Set([...state.awaitingApproval, ...newIds])];
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const maybeImportLatest = (state: State, users: Array<AdminAccount>, params?: AdminGetAccountsParams): State => {
|
||||
const maybeImportLatest = (state: State, users: Array<AdminAccount>, params?: AdminGetAccountsParams) => {
|
||||
if (params?.origin === 'local' && params.status === 'active') {
|
||||
return replaceSet(state, 'latestUsers', users);
|
||||
} else {
|
||||
return state;
|
||||
const newIds = toIds(users);
|
||||
state.latestUsers = newIds;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -72,28 +59,26 @@ const minifyUser = (user: AdminAccount) => omit(user, ['account']);
|
|||
|
||||
type MinifiedUser = ReturnType<typeof minifyUser>;
|
||||
|
||||
const importUsers = (state: State, users: Array<AdminAccount>, params: AdminGetAccountsParams): State =>
|
||||
state.withMutations(state => {
|
||||
maybeImportUnapproved(state, users, params);
|
||||
maybeImportLatest(state, users, params);
|
||||
const importUsers = (state: State, users: Array<AdminAccount>, params: AdminGetAccountsParams) => {
|
||||
maybeImportUnapproved(state, users, params);
|
||||
maybeImportLatest(state, users, params);
|
||||
|
||||
users.forEach(user => {
|
||||
const normalizedUser = minifyUser(user);
|
||||
state.setIn(['users', user.id], normalizedUser);
|
||||
});
|
||||
});
|
||||
|
||||
const deleteUser = (state: State, accountId: string): State =>
|
||||
state
|
||||
.update('awaitingApproval', orderedSet => orderedSet.delete(accountId))
|
||||
.deleteIn(['users', accountId]);
|
||||
|
||||
const approveUser = (state: State, user: AdminAccount): State =>
|
||||
state.withMutations(state => {
|
||||
users.forEach(user => {
|
||||
const normalizedUser = minifyUser(user);
|
||||
state.update('awaitingApproval', orderedSet => orderedSet.delete(user.id));
|
||||
state.setIn(['users', user.id], normalizedUser);
|
||||
state.users[user.id] = normalizedUser;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteUser = (state: State, accountId: string) => {
|
||||
state.awaitingApproval = state.awaitingApproval.filter(id => id !== accountId);
|
||||
delete state.users[accountId];
|
||||
};
|
||||
|
||||
const approveUser = (state: State, user: AdminAccount) => {
|
||||
const normalizedUser = minifyUser(user);
|
||||
state.awaitingApproval = state.awaitingApproval.filter(id => id !== user.id);
|
||||
state.users[user.id] = normalizedUser;
|
||||
};
|
||||
|
||||
const minifyReport = (report: AdminReport) => omit(
|
||||
report,
|
||||
|
@ -102,53 +87,51 @@ const minifyReport = (report: AdminReport) => omit(
|
|||
|
||||
type MinifiedReport = ReturnType<typeof minifyReport>;
|
||||
|
||||
const importReports = (state: State, reports: Array<BaseAdminReport>): State =>
|
||||
state.withMutations(state => {
|
||||
reports.forEach(report => {
|
||||
const minifiedReport = minifyReport(normalizeAdminReport(report));
|
||||
if (!minifiedReport.action_taken) {
|
||||
state.update('openReports', orderedSet => orderedSet.add(report.id));
|
||||
}
|
||||
state.setIn(['reports', report.id], minifiedReport);
|
||||
});
|
||||
const importReports = (state: State, reports: Array<BaseAdminReport>) => {
|
||||
reports.forEach(report => {
|
||||
const minifiedReport = minifyReport(normalizeAdminReport(report));
|
||||
if (!minifiedReport.action_taken) {
|
||||
state.openReports = [...new Set([...state.openReports, report.id])];
|
||||
}
|
||||
state.reports[report.id] = minifiedReport;
|
||||
});
|
||||
};
|
||||
|
||||
const handleReportDiffs = (state: State, report: MinifiedReport) =>
|
||||
const handleReportDiffs = (state: State, report: MinifiedReport) => {
|
||||
// Note: the reports here aren't full report objects
|
||||
// hence the need for a new function.
|
||||
state.withMutations(state => {
|
||||
switch (report.action_taken) {
|
||||
case false:
|
||||
state.update('openReports', orderedSet => orderedSet.add(report.id));
|
||||
break;
|
||||
default:
|
||||
state.update('openReports', orderedSet => orderedSet.delete(report.id));
|
||||
}
|
||||
});
|
||||
switch (report.action_taken) {
|
||||
case false:
|
||||
state.openReports = [...new Set([...state.openReports, report.id])];
|
||||
break;
|
||||
default:
|
||||
state.openReports = state.openReports.filter(id => id !== report.id);
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeConfig = (config: any): Config => ImmutableMap(fromJS(config));
|
||||
const importConfigs = (state: State, configs: any) => {
|
||||
state.configs = configs;
|
||||
};
|
||||
|
||||
const normalizeConfigs = (configs: any): ImmutableList<Config> => ImmutableList(fromJS(configs)).map(normalizeConfig);
|
||||
|
||||
const importConfigs = (state: State, configs: any): State => state.set('configs', normalizeConfigs(configs));
|
||||
|
||||
const admin = (state: State = ReducerRecord(), action: AnyAction): State => {
|
||||
const admin = (state = initialState, action: AnyAction): State => {
|
||||
switch (action.type) {
|
||||
case ADMIN_CONFIG_FETCH_SUCCESS:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return importConfigs(state, action.configs);
|
||||
return create(state, (draft) => importConfigs(draft, action.configs));
|
||||
case ADMIN_REPORTS_FETCH_SUCCESS:
|
||||
return importReports(state, action.reports);
|
||||
return create(state, (draft) => importReports(draft, action.reports));
|
||||
case ADMIN_REPORT_PATCH_SUCCESS:
|
||||
return handleReportDiffs(state, action.report);
|
||||
return create(state, (draft) => handleReportDiffs(draft, action.report));
|
||||
case ADMIN_USERS_FETCH_SUCCESS:
|
||||
return importUsers(state, action.users, action.params);
|
||||
return create(state, (draft) => importUsers(draft, action.users, action.params));
|
||||
case ADMIN_USER_DELETE_SUCCESS:
|
||||
return deleteUser(state, action.accountId);
|
||||
return create(state, (draft) => deleteUser(draft, action.accountId));
|
||||
case ADMIN_USER_APPROVE_REQUEST:
|
||||
return state.update('awaitingApproval', set => set.subtract(action.accountId));
|
||||
return create(state, (draft) => {
|
||||
draft.awaitingApproval = draft.awaitingApproval.filter(value => value !== action.accountId);
|
||||
});
|
||||
case ADMIN_USER_APPROVE_SUCCESS:
|
||||
return approveUser(state, action.user);
|
||||
return create(state, (draft) => approveUser(draft, action.user));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import {
|
||||
Map as ImmutableMap,
|
||||
Record as ImmutableRecord,
|
||||
OrderedSet as ImmutableOrderedSet,
|
||||
} from 'immutable';
|
||||
import { create } from 'mutative';
|
||||
|
||||
import { STATUS_IMPORT, STATUSES_IMPORT, type ImporterAction } from 'pl-fe/actions/importer';
|
||||
|
||||
|
@ -18,49 +17,47 @@ import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines';
|
|||
import type { Status } from 'pl-api';
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
inReplyTos: ImmutableMap<string, string>(),
|
||||
replies: ImmutableMap<string, ImmutableOrderedSet<string>>(),
|
||||
});
|
||||
interface State {
|
||||
inReplyTos: Record<string, string>;
|
||||
replies: Record<string, Array<string>>;
|
||||
}
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
const initialState: State = {
|
||||
inReplyTos: {},
|
||||
replies: {},
|
||||
};
|
||||
|
||||
/** Import a single status into the reducer, setting replies and replyTos. */
|
||||
const importStatus = (state: State, status: Pick<Status, 'id' | 'in_reply_to_id'>, idempotencyKey?: string): State => {
|
||||
const importStatus = (state: State, status: Pick<Status, 'id' | 'in_reply_to_id'>, idempotencyKey?: string) => {
|
||||
const { id, in_reply_to_id: inReplyToId } = status;
|
||||
if (!inReplyToId) return state;
|
||||
if (!inReplyToId) return;
|
||||
|
||||
return state.withMutations(state => {
|
||||
const replies = state.replies.get(inReplyToId) || ImmutableOrderedSet();
|
||||
const newReplies = replies.add(id).sort();
|
||||
const replies = state.replies[inReplyToId] || [];
|
||||
const newReplies = [...replies, id].toSorted();
|
||||
|
||||
state.setIn(['replies', inReplyToId], newReplies);
|
||||
state.setIn(['inReplyTos', id], inReplyToId);
|
||||
state.replies[inReplyToId] = newReplies;
|
||||
state.inReplyTos[id] = inReplyToId;
|
||||
|
||||
if (idempotencyKey) {
|
||||
deletePendingStatus(state, status, idempotencyKey);
|
||||
}
|
||||
});
|
||||
if (idempotencyKey) {
|
||||
deletePendingStatus(state, status, idempotencyKey);
|
||||
}
|
||||
};
|
||||
|
||||
/** Import multiple statuses into the state. */
|
||||
const importStatuses = (state: State, statuses: Array<Pick<Status, 'id' | 'in_reply_to_id'>>): State =>
|
||||
state.withMutations(state => {
|
||||
statuses.forEach(status => importStatus(state, status));
|
||||
});
|
||||
const importStatuses = (state: State, statuses: Array<Pick<Status, 'id' | 'in_reply_to_id'>>) =>
|
||||
statuses.forEach(status => importStatus(state, status));
|
||||
|
||||
/** Insert a fake status ID connecting descendant to ancestor. */
|
||||
const insertTombstone = (state: State, ancestorId: string, descendantId: string): State => {
|
||||
const insertTombstone = (state: State, ancestorId: string, descendantId: string) => {
|
||||
const tombstoneId = `${descendantId}-tombstone`;
|
||||
return state.withMutations(state => {
|
||||
importStatus(state, { id: tombstoneId, in_reply_to_id: ancestorId });
|
||||
importStatus(state, { id: descendantId, in_reply_to_id: tombstoneId });
|
||||
});
|
||||
|
||||
importStatus(state, { id: tombstoneId, in_reply_to_id: ancestorId });
|
||||
importStatus(state, { id: descendantId, in_reply_to_id: tombstoneId });
|
||||
};
|
||||
|
||||
/** Find the highest level status from this statusId. */
|
||||
const getRootNode = (state: State, statusId: string, initialId = statusId): string => {
|
||||
const parent = state.inReplyTos.get(statusId);
|
||||
const parent = state.inReplyTos[statusId];
|
||||
|
||||
if (!parent) {
|
||||
return statusId;
|
||||
|
@ -73,7 +70,7 @@ const getRootNode = (state: State, statusId: string, initialId = statusId): stri
|
|||
};
|
||||
|
||||
/** Route fromId to toId by inserting tombstones. */
|
||||
const connectNodes = (state: State, fromId: string, toId: string): State => {
|
||||
const connectNodes = (state: State, fromId: string, toId: string) => {
|
||||
const fromRoot = getRootNode(state, fromId);
|
||||
const toRoot = getRootNode(state, toId);
|
||||
|
||||
|
@ -85,25 +82,23 @@ const connectNodes = (state: State, fromId: string, toId: string): State => {
|
|||
};
|
||||
|
||||
/** Import a branch of ancestors or descendants, in relation to statusId. */
|
||||
const importBranch = (state: State, statuses: Array<Pick<Status, 'id' | 'in_reply_to_id'>>, statusId?: string): State =>
|
||||
state.withMutations(state => {
|
||||
statuses.forEach((status, i) => {
|
||||
const prevId = statusId && i === 0 ? statusId : (statuses[i - 1] || {}).id;
|
||||
const importBranch = (state: State, statuses: Array<Pick<Status, 'id' | 'in_reply_to_id'>>, statusId?: string) =>
|
||||
statuses.forEach((status, i) => {
|
||||
const prevId = statusId && i === 0 ? statusId : (statuses[i - 1] || {}).id;
|
||||
|
||||
if (status.in_reply_to_id) {
|
||||
importStatus(state, status);
|
||||
if (status.in_reply_to_id) {
|
||||
importStatus(state, status);
|
||||
|
||||
// On Mastodon, in_reply_to_id can refer to an unavailable status,
|
||||
// so traverse the tree up and insert a connecting tombstone if needed.
|
||||
if (statusId) {
|
||||
connectNodes(state, status.id, statusId);
|
||||
}
|
||||
} else if (prevId) {
|
||||
// On Pleroma, in_reply_to_id will be null if the parent is unavailable,
|
||||
// so insert the tombstone now.
|
||||
insertTombstone(state, prevId, status.id);
|
||||
// On Mastodon, in_reply_to_id can refer to an unavailable status,
|
||||
// so traverse the tree up and insert a connecting tombstone if needed.
|
||||
if (statusId) {
|
||||
connectNodes(state, status.id, statusId);
|
||||
}
|
||||
});
|
||||
} else if (prevId) {
|
||||
// On Pleroma, in_reply_to_id will be null if the parent is unavailable,
|
||||
// so insert the tombstone now.
|
||||
insertTombstone(state, prevId, status.id);
|
||||
}
|
||||
});
|
||||
|
||||
/** Import a status's ancestors and descendants. */
|
||||
|
@ -112,39 +107,36 @@ const normalizeContext = (
|
|||
id: string,
|
||||
ancestors: Array<Pick<Status, 'id' | 'in_reply_to_id'>>,
|
||||
descendants: Array<Pick<Status, 'id' | 'in_reply_to_id'>>,
|
||||
) => state.withMutations(state => {
|
||||
) => {
|
||||
importBranch(state, ancestors);
|
||||
importBranch(state, descendants, id);
|
||||
|
||||
if (ancestors.length > 0 && !state.getIn(['inReplyTos', id])) {
|
||||
if (ancestors.length > 0 && !state.inReplyTos[id]) {
|
||||
insertTombstone(state, ancestors[ancestors.length - 1].id, id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Remove a status from the reducer. */
|
||||
const deleteStatus = (state: State, statusId: string): State =>
|
||||
state.withMutations(state => {
|
||||
// Delete from its parent's tree
|
||||
const parentId = state.inReplyTos.get(statusId);
|
||||
if (parentId) {
|
||||
const parentReplies = state.replies.get(parentId) || ImmutableOrderedSet();
|
||||
const newParentReplies = parentReplies.delete(statusId);
|
||||
state.setIn(['replies', parentId], newParentReplies);
|
||||
}
|
||||
const deleteStatus = (state: State, statusId: string) => {
|
||||
// Delete from its parent's tree
|
||||
const parentId = state.inReplyTos[statusId];
|
||||
if (parentId) {
|
||||
const parentReplies = state.replies[parentId] || [];
|
||||
const newParentReplies = parentReplies.filter(id => id !== statusId);
|
||||
state.replies[parentId] = newParentReplies;
|
||||
}
|
||||
|
||||
// Dereference children
|
||||
const replies = state.replies.get(statusId) || ImmutableOrderedSet();
|
||||
replies.forEach(reply => state.deleteIn(['inReplyTos', reply]));
|
||||
// Dereference children
|
||||
const replies = state.replies[statusId] = [];
|
||||
replies.forEach(reply => delete state.inReplyTos[reply]);
|
||||
|
||||
state.deleteIn(['inReplyTos', statusId]);
|
||||
state.deleteIn(['replies', statusId]);
|
||||
});
|
||||
delete state.inReplyTos[statusId];
|
||||
delete state.replies[statusId];
|
||||
};
|
||||
|
||||
/** Delete multiple statuses from the reducer. */
|
||||
const deleteStatuses = (state: State, statusIds: string[]): State =>
|
||||
state.withMutations(state => {
|
||||
statusIds.forEach(statusId => deleteStatus(state, statusId));
|
||||
});
|
||||
const deleteStatuses = (state: State, statusIds: string[]) =>
|
||||
statusIds.forEach(statusId => deleteStatus(state, statusId));
|
||||
|
||||
/** Delete statuses upon blocking or muting a user. */
|
||||
const filterContexts = (
|
||||
|
@ -152,63 +144,60 @@ const filterContexts = (
|
|||
relationship: { id: string },
|
||||
/** The entire statuses map from the store. */
|
||||
statuses: ImmutableMap<string, Status>,
|
||||
): State => {
|
||||
) => {
|
||||
const ownedStatusIds = statuses
|
||||
.filter(status => status.account.id === relationship.id)
|
||||
.map(status => status.id)
|
||||
.toList()
|
||||
.toArray();
|
||||
|
||||
return deleteStatuses(state, ownedStatusIds);
|
||||
deleteStatuses(state, ownedStatusIds);
|
||||
};
|
||||
|
||||
/** Add a fake status ID for a pending status. */
|
||||
const importPendingStatus = (state: State, params: Pick<Status, 'id' | 'in_reply_to_id'>, idempotencyKey: string): State => {
|
||||
const importPendingStatus = (state: State, params: Pick<Status, 'id' | 'in_reply_to_id'>, idempotencyKey: string) => {
|
||||
const id = `末pending-${idempotencyKey}`;
|
||||
const { in_reply_to_id } = params;
|
||||
return importStatus(state, { id, in_reply_to_id });
|
||||
};
|
||||
|
||||
/** Delete a pending status from the reducer. */
|
||||
const deletePendingStatus = (state: State, params: Pick<Status, 'id' | 'in_reply_to_id'>, idempotencyKey: string): State => {
|
||||
const deletePendingStatus = (state: State, params: Pick<Status, 'id' | 'in_reply_to_id'>, idempotencyKey: string) => {
|
||||
const id = `末pending-${idempotencyKey}`;
|
||||
const { in_reply_to_id: inReplyToId } = params;
|
||||
|
||||
return state.withMutations(state => {
|
||||
state.deleteIn(['inReplyTos', id]);
|
||||
delete state.inReplyTos[id];
|
||||
|
||||
if (inReplyToId) {
|
||||
const replies = state.replies.get(inReplyToId) || ImmutableOrderedSet();
|
||||
const newReplies = replies.delete(id).sort();
|
||||
state.setIn(['replies', inReplyToId], newReplies);
|
||||
}
|
||||
});
|
||||
if (inReplyToId) {
|
||||
const replies = state.replies[inReplyToId] || [];
|
||||
const newReplies = replies.filter(replyId => replyId !== id).toSorted();
|
||||
state.replies[inReplyToId] = newReplies;
|
||||
}
|
||||
};
|
||||
|
||||
/** Contexts reducer. Used for building a nested tree structure for threads. */
|
||||
const replies = (state = ReducerRecord(), action: AnyAction | ImporterAction | StatusesAction | TimelineAction) => {
|
||||
const replies = (state = initialState, action: AnyAction | ImporterAction | StatusesAction | TimelineAction): State => {
|
||||
switch (action.type) {
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return filterContexts(state, action.relationship, action.statuses);
|
||||
return create(state, (draft) => filterContexts(draft, action.relationship, action.statuses));
|
||||
case CONTEXT_FETCH_SUCCESS:
|
||||
return normalizeContext(state, action.statusId, action.ancestors, action.descendants);
|
||||
return create(state, (draft) => normalizeContext(draft, action.statusId, action.ancestors, action.descendants));
|
||||
case TIMELINE_DELETE:
|
||||
return deleteStatuses(state, [action.statusId]);
|
||||
return create(state, (draft) => deleteStatuses(draft, [action.statusId]));
|
||||
case STATUS_CREATE_REQUEST:
|
||||
return importPendingStatus(state, action.params, action.idempotencyKey);
|
||||
return create(state, (draft) => importPendingStatus(draft, action.params, action.idempotencyKey));
|
||||
case STATUS_CREATE_SUCCESS:
|
||||
return deletePendingStatus(state, action.status, action.idempotencyKey);
|
||||
return create(state, (draft) => deletePendingStatus(draft, action.status, action.idempotencyKey));
|
||||
case STATUS_IMPORT:
|
||||
return importStatus(state, action.status, action.idempotencyKey);
|
||||
return create(state, (draft) => importStatus(draft, action.status, action.idempotencyKey));
|
||||
case STATUSES_IMPORT:
|
||||
return importStatuses(state, action.statuses);
|
||||
return create(state, (draft) => importStatuses(draft, action.statuses));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
ReducerRecord,
|
||||
replies as default,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { create } from 'mutative';
|
||||
import { type Instance, instanceSchema } from 'pl-api';
|
||||
import { type Instance, instanceSchema, PleromaConfig } from 'pl-api';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'pl-fe/actions/admin';
|
||||
|
@ -27,7 +27,7 @@ const getConfigValue = (instanceConfig: ImmutableMap<string, any>, key: string)
|
|||
return v ? v.getIn(['tuple', 1]) : undefined;
|
||||
};
|
||||
|
||||
const importConfigs = (state: State, configs: ImmutableList<any>) => {
|
||||
const importConfigs = (state: State, configs: PleromaConfig['configs']) => {
|
||||
// FIXME: This is pretty hacked together. Need to make a cleaner map.
|
||||
const config = ConfigDB.find(configs, ':pleroma', ':instance');
|
||||
const simplePolicy = ConfigDB.toSimplePolicy(configs);
|
||||
|
@ -35,7 +35,7 @@ const importConfigs = (state: State, configs: ImmutableList<any>) => {
|
|||
if (!config && !simplePolicy) return state;
|
||||
|
||||
if (config) {
|
||||
const value = config.get('value', ImmutableList());
|
||||
const value = config.value || [];
|
||||
const registrationsOpen = getConfigValue(value, ':registrations_open') as boolean | undefined;
|
||||
const approvalRequired = getConfigValue(value, ':account_approval_required') as boolean | undefined;
|
||||
|
||||
|
@ -97,7 +97,7 @@ const instance = (state = initialState, action: AnyAction | InstanceAction | Pre
|
|||
return handleInstanceFetchFail(state, action.error);
|
||||
case ADMIN_CONFIG_UPDATE_REQUEST:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return create(state, (draft) => importConfigs(draft, ImmutableList(fromJS(action.configs))));
|
||||
return create(state, (draft) => importConfigs(draft, action.configs));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { List as ImmutableList, Map as ImmutableMap, fromJS } from 'immutable';
|
||||
|
||||
import { PLEROMA_PRELOAD_IMPORT } from 'pl-fe/actions/preload';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import ConfigDB from 'pl-fe/utils/config-db';
|
||||
|
@ -11,58 +9,60 @@ import {
|
|||
PLFE_CONFIG_REQUEST_FAIL,
|
||||
} from '../actions/pl-fe';
|
||||
|
||||
const initialState = ImmutableMap<string, any>();
|
||||
import type { PleromaConfig } from 'pl-api';
|
||||
|
||||
const fallbackState = ImmutableMap<string, any>({
|
||||
const initialState: Record<string, any> = {};
|
||||
|
||||
const fallbackState = {
|
||||
brandColor: '#d80482',
|
||||
});
|
||||
};
|
||||
|
||||
const updateFromAdmin = (state: ImmutableMap<string, any>, configs: ImmutableList<ImmutableMap<string, any>>) => {
|
||||
const updateFromAdmin = (state: Record<string, any>, configs: PleromaConfig['configs']) => {
|
||||
try {
|
||||
return ConfigDB.find(configs, ':pleroma', ':frontend_configurations')!
|
||||
.get('value')
|
||||
.find((value: ImmutableMap<string, any>) => value.getIn(['tuple', 0]) === ':pl_fe')
|
||||
.getIn(['tuple', 1]);
|
||||
.value
|
||||
.find((value: Record<string, any>) => value.tuple?.[0] === ':pl_fe')
|
||||
.tuple?.[1];
|
||||
} catch {
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const preloadImport = (state: ImmutableMap<string, any>, action: Record<string, any>) => {
|
||||
const preloadImport = (state: Record<string, any>, action: Record<string, any>) => {
|
||||
const path = '/api/pleroma/frontend_configurations';
|
||||
const feData = action.data[path];
|
||||
|
||||
if (feData) {
|
||||
const plfe = feData.pl_fe;
|
||||
return plfe ? fallbackState.mergeDeep(fromJS(plfe)) : fallbackState;
|
||||
return plfe ? { ...fallbackState, ...plfe } : fallbackState;
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const persistPlFeConfig = (plFeConfig: ImmutableMap<string, any>, host: string) => {
|
||||
const persistPlFeConfig = (plFeConfig: Record<string, any>, host: string) => {
|
||||
if (host) {
|
||||
KVStore.setItem(`plfe_config:${host}`, plFeConfig.toJS()).catch(console.error);
|
||||
KVStore.setItem(`plfe_config:${host}`, plFeConfig).catch(console.error);
|
||||
}
|
||||
};
|
||||
|
||||
const importPlFeConfig = (plFeConfig: ImmutableMap<string, any>, host: string) => {
|
||||
const importPlFeConfig = (plFeConfig: Record<string, any>, host: string) => {
|
||||
persistPlFeConfig(plFeConfig, host);
|
||||
return plFeConfig;
|
||||
};
|
||||
|
||||
const plfe = (state = initialState, action: Record<string, any>) => {
|
||||
const plfe = (state = initialState, action: Record<string, any>): Record<string, any> => {
|
||||
switch (action.type) {
|
||||
case PLEROMA_PRELOAD_IMPORT:
|
||||
return preloadImport(state, action);
|
||||
case PLFE_CONFIG_REMEMBER_SUCCESS:
|
||||
return fromJS(action.plFeConfig);
|
||||
return action.plFeConfig;
|
||||
case PLFE_CONFIG_REQUEST_SUCCESS:
|
||||
return importPlFeConfig(fromJS(action.plFeConfig) as ImmutableMap<string, any>, action.host);
|
||||
return importPlFeConfig(action.plFeConfig || {}, action.host);
|
||||
case PLFE_CONFIG_REQUEST_FAIL:
|
||||
return fallbackState.mergeDeep(state);
|
||||
return { ...fallbackState, ...state };
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return updateFromAdmin(state, fromJS(action.configs) as ImmutableList<ImmutableMap<string, any>>);
|
||||
return updateFromAdmin(state, action.configs || []);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import omit from 'lodash/omit';
|
||||
import { create } from 'mutative';
|
||||
|
||||
import { normalizeStatus, Status as StatusRecord } from 'pl-fe/normalizers/status';
|
||||
import { simulateEmojiReact, simulateUnEmojiReact } from 'pl-fe/utils/emoji-reacts';
|
||||
|
@ -55,7 +55,7 @@ import { TIMELINE_DELETE, type TimelineAction } from '../actions/timelines';
|
|||
import type { Status as BaseStatus, Translation } from 'pl-api';
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
type State = ImmutableMap<string, MinifiedStatus>;
|
||||
type State = Record<string, MinifiedStatus>;
|
||||
|
||||
type MinifiedStatus = ReturnType<typeof minifyStatus>;
|
||||
|
||||
|
@ -78,68 +78,58 @@ const fixQuote = (status: StatusRecord, oldStatus?: StatusRecord): StatusRecord
|
|||
};
|
||||
|
||||
const fixStatus = (state: State, status: BaseStatus): MinifiedStatus => {
|
||||
const oldStatus = state.get(status.id);
|
||||
const oldStatus = state[status.id];
|
||||
|
||||
return minifyStatus(fixQuote(normalizeStatus(status, oldStatus)));
|
||||
};
|
||||
|
||||
const importStatus = (state: State, status: BaseStatus): State =>
|
||||
state.set(status.id, fixStatus(state, status));
|
||||
const importStatus = (state: State, status: BaseStatus) =>{
|
||||
state[status.id] = fixStatus(state, status);
|
||||
};
|
||||
|
||||
const importStatuses = (state: State, statuses: Array<BaseStatus>): State =>
|
||||
state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status)));
|
||||
const importStatuses = (state: State, statuses: Array<BaseStatus>) =>{
|
||||
statuses.forEach(status => importStatus(state, status));
|
||||
};
|
||||
|
||||
const deleteStatus = (state: State, statusId: string, references: Array<string>) => {
|
||||
references.forEach(ref => {
|
||||
state = deleteStatus(state, ref[0], []);
|
||||
deleteStatus(state, ref[0], []);
|
||||
});
|
||||
|
||||
return state.delete(statusId);
|
||||
delete state[statusId];
|
||||
};
|
||||
|
||||
const incrementReplyCount = (state: State, { in_reply_to_id, quote }: BaseStatus) => {
|
||||
if (in_reply_to_id && state.has(in_reply_to_id)) {
|
||||
const parent = state.get(in_reply_to_id)!;
|
||||
state = state.set(in_reply_to_id, {
|
||||
...parent,
|
||||
replies_count: (typeof parent.replies_count === 'number' ? parent.replies_count : 0) + 1,
|
||||
});
|
||||
if (in_reply_to_id && state[in_reply_to_id]) {
|
||||
const parent = state[in_reply_to_id];
|
||||
parent.replies_count = (typeof parent.replies_count === 'number' ? parent.replies_count : 0) + 1;
|
||||
}
|
||||
|
||||
if (quote?.id && state.has(quote.id)) {
|
||||
const parent = state.get(quote.id)!;
|
||||
state = state.set(quote.id, {
|
||||
...parent,
|
||||
quotes_count: (typeof parent.quotes_count === 'number' ? parent.quotes_count : 0) + 1,
|
||||
});
|
||||
if (quote?.id && state[quote.id]) {
|
||||
const parent = state[quote.id];
|
||||
parent.quotes_count = (typeof parent.quotes_count === 'number' ? parent.quotes_count : 0) + 1;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const decrementReplyCount = (state: State, { in_reply_to_id, quote }: BaseStatus) => {
|
||||
if (in_reply_to_id) {
|
||||
state = state.updateIn([in_reply_to_id, 'replies_count'], 0, count =>
|
||||
typeof count === 'number' ? Math.max(0, count - 1) : 0,
|
||||
);
|
||||
if (in_reply_to_id && state[in_reply_to_id]) {
|
||||
const parent = state[in_reply_to_id];
|
||||
parent.replies_count = Math.max(0, parent.replies_count - 1);
|
||||
}
|
||||
|
||||
if (quote?.id) {
|
||||
state = state.updateIn([quote.id, 'quotes_count'], 0, count =>
|
||||
typeof count === 'number' ? Math.max(0, count - 1) : 0,
|
||||
);
|
||||
const parent = state[quote.id];
|
||||
parent.quotes_count = Math.max(0, parent.quotes_count - 1);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
/** Simulate favourite/unfavourite of status for optimistic interactions */
|
||||
const simulateFavourite = (
|
||||
state: State,
|
||||
statusId: string,
|
||||
favourited: boolean,
|
||||
): State => {
|
||||
const status = state.get(statusId);
|
||||
const simulateFavourite = (state: State, statusId: string, favourited: boolean) => {
|
||||
const status = state[statusId];
|
||||
if (!status) return state;
|
||||
|
||||
const delta = favourited ? +1 : -1;
|
||||
|
@ -150,7 +140,7 @@ const simulateFavourite = (
|
|||
favourites_count: Math.max(0, status.favourites_count + delta),
|
||||
};
|
||||
|
||||
return state.set(statusId, updatedStatus);
|
||||
state[statusId] = updatedStatus;
|
||||
};
|
||||
|
||||
/** Simulate dislike/undislike of status for optimistic interactions */
|
||||
|
@ -158,8 +148,8 @@ const simulateDislike = (
|
|||
state: State,
|
||||
statusId: string,
|
||||
disliked: boolean,
|
||||
): State => {
|
||||
const status = state.get(statusId);
|
||||
) => {
|
||||
const status = state[statusId];
|
||||
if (!status) return state;
|
||||
|
||||
const delta = disliked ? +1 : -1;
|
||||
|
@ -170,133 +160,212 @@ const simulateDislike = (
|
|||
dislikes_count: Math.max(0, status.dislikes_count + delta),
|
||||
});
|
||||
|
||||
return state.set(statusId, updatedStatus);
|
||||
state[statusId] = updatedStatus;
|
||||
};
|
||||
|
||||
/** Import translation from translation service into the store. */
|
||||
const importTranslation = (state: State, statusId: string, translation: Translation) => {
|
||||
return state.update(statusId, undefined as any, (status) => ({
|
||||
...status,
|
||||
translation: translation,
|
||||
translating: false,
|
||||
}));
|
||||
if (!state[statusId]) return;
|
||||
state[statusId].translation = translation;
|
||||
state[statusId].translating = false;
|
||||
};
|
||||
|
||||
/** Delete translation from the store. */
|
||||
const deleteTranslation = (state: State, statusId: string) => state.deleteIn([statusId, 'translation']);
|
||||
const deleteTranslation = (state: State, statusId: string) => {
|
||||
state[statusId].translation = null;
|
||||
};
|
||||
|
||||
const initialState: State = ImmutableMap();
|
||||
const initialState: State = {};
|
||||
|
||||
const statuses = (state = initialState, action: AnyAction | EmojiReactsAction | EventsAction | ImporterAction | InteractionsAction | StatusesAction | TimelineAction): State => {
|
||||
switch (action.type) {
|
||||
case STATUS_IMPORT:
|
||||
return importStatus(state, action.status);
|
||||
return create(state, (draft) => importStatus(draft, action.status));
|
||||
case STATUSES_IMPORT:
|
||||
return importStatuses(state, action.statuses);
|
||||
return create(state, (draft) => importStatuses(draft, action.statuses));
|
||||
case STATUS_CREATE_REQUEST:
|
||||
return action.editing ? state : incrementReplyCount(state, action.params);
|
||||
return action.editing ? state : create(state, (draft) => incrementReplyCount(draft, action.params));
|
||||
case STATUS_CREATE_FAIL:
|
||||
return action.editing ? state : decrementReplyCount(state, action.params);
|
||||
return action.editing ? state : create(state, (draft) => decrementReplyCount(draft, action.params));
|
||||
case FAVOURITE_REQUEST:
|
||||
return simulateFavourite(state, action.statusId, true);
|
||||
return create(state, (draft) => simulateFavourite(draft, action.statusId, true));
|
||||
case UNFAVOURITE_REQUEST:
|
||||
return simulateFavourite(state, action.statusId, false);
|
||||
return create(state, (draft) => simulateFavourite(draft, action.statusId, false));
|
||||
case DISLIKE_REQUEST:
|
||||
return simulateDislike(state, action.statusId, true);
|
||||
return create(state, (draft) => simulateDislike(draft, action.statusId, true));
|
||||
case UNDISLIKE_REQUEST:
|
||||
return simulateDislike(state, action.statusId, false);
|
||||
return create(state, (draft) => simulateDislike(draft, action.statusId, false));
|
||||
case EMOJI_REACT_REQUEST:
|
||||
return state
|
||||
.updateIn(
|
||||
[action.statusId, 'emoji_reactions'],
|
||||
emojiReacts => simulateEmojiReact(emojiReacts as any, action.emoji, action.custom),
|
||||
);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.emoji_reactions = simulateEmojiReact(status.emoji_reactions, action.emoji, action.custom);
|
||||
}
|
||||
});
|
||||
case UNEMOJI_REACT_REQUEST:
|
||||
case EMOJI_REACT_FAIL:
|
||||
return state
|
||||
.updateIn(
|
||||
[action.statusId, 'emoji_reactions'],
|
||||
emojiReacts => simulateUnEmojiReact(emojiReacts as any, action.emoji),
|
||||
);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.emoji_reactions = simulateUnEmojiReact(status.emoji_reactions, action.emoji);
|
||||
}
|
||||
});
|
||||
case FAVOURITE_FAIL:
|
||||
return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'favourited'], false);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.favourited = false;
|
||||
}
|
||||
});
|
||||
case DISLIKE_FAIL:
|
||||
return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'disliked'], false);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.disliked = false;
|
||||
}
|
||||
});
|
||||
case REBLOG_REQUEST:
|
||||
return state
|
||||
.updateIn([action.statusId, 'reblogs_count'], 0, (count) => typeof count === 'number' ? count + 1 : 1)
|
||||
.setIn([action.statusId, 'reblogged'], true);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogs_count += 1;
|
||||
status.reblogged = true;
|
||||
}
|
||||
});
|
||||
case REBLOG_FAIL:
|
||||
return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'reblogged'], false);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogged = false;
|
||||
}
|
||||
});
|
||||
case UNREBLOG_REQUEST:
|
||||
return state
|
||||
.updateIn([action.statusId, 'reblogs_count'], 0, (count) => typeof count === 'number' ? Math.max(0, count - 1) : 0)
|
||||
.setIn([action.statusId, 'reblogged'], false);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogs_count = Math.max(0, status.reblogs_count - 1);
|
||||
status.reblogged = false;
|
||||
}
|
||||
});
|
||||
case UNREBLOG_FAIL:
|
||||
return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'reblogged'], true);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.reblogged = true;
|
||||
}
|
||||
});
|
||||
case STATUS_MUTE_SUCCESS:
|
||||
return state.setIn([action.statusId, 'muted'], true);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.muted = true;
|
||||
}
|
||||
});
|
||||
case STATUS_UNMUTE_SUCCESS:
|
||||
return state.setIn([action.statusId, 'muted'], false);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.muted = false;
|
||||
}
|
||||
});
|
||||
case STATUS_REVEAL_MEDIA:
|
||||
return state.withMutations(map => {
|
||||
return create(state, (draft) => {
|
||||
action.statusIds.forEach((id: string) => {
|
||||
if (!(state.get(id) === undefined)) {
|
||||
map.setIn([id, 'hidden'], false);
|
||||
const status = draft[id];
|
||||
if (status) {
|
||||
status.hidden = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
case STATUS_HIDE_MEDIA:
|
||||
return state.withMutations(map => {
|
||||
return create(state, (draft) => {
|
||||
action.statusIds.forEach((id: string) => {
|
||||
if (!(state.get(id) === undefined)) {
|
||||
map.setIn([id, 'hidden'], true);
|
||||
const status = draft[id];
|
||||
if (status) {
|
||||
status.hidden = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
case STATUS_EXPAND_SPOILER:
|
||||
return state.withMutations(map => {
|
||||
return create(state, (draft) => {
|
||||
action.statusIds.forEach((id: string) => {
|
||||
if (!(state.get(id) === undefined)) {
|
||||
map.setIn([id, 'expanded'], true);
|
||||
const status = draft[id];
|
||||
if (status) {
|
||||
status.expanded = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
case STATUS_COLLAPSE_SPOILER:
|
||||
return state.withMutations(map => {
|
||||
return create(state, (draft) => {
|
||||
action.statusIds.forEach((id: string) => {
|
||||
if (!(state.get(id) === undefined)) {
|
||||
map.setIn([id, 'expanded'], false);
|
||||
const status = draft[id];
|
||||
if (status) {
|
||||
status.expanded = false;
|
||||
status.translation = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
case STATUS_DELETE_REQUEST:
|
||||
return decrementReplyCount(state, action.params);
|
||||
return create(state, (draft) => decrementReplyCount(draft, action.params));
|
||||
case STATUS_DELETE_FAIL:
|
||||
return incrementReplyCount(state, action.params);
|
||||
return create(state, (draft) => incrementReplyCount(draft, action.params));
|
||||
case STATUS_TRANSLATE_REQUEST:
|
||||
return state.setIn([action.statusId, 'translating'], true);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.translating = true;
|
||||
}
|
||||
});
|
||||
case STATUS_TRANSLATE_SUCCESS:
|
||||
return importTranslation(state, action.statusId, action.translation);
|
||||
return create(state, (draft) => importTranslation(draft, action.statusId, action.translation));
|
||||
case STATUS_TRANSLATE_FAIL:
|
||||
return state
|
||||
.setIn([action.statusId, 'translating'], false)
|
||||
.setIn([action.statusId, 'translation'], false);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.translating = false;
|
||||
status.translation = false;
|
||||
}
|
||||
});
|
||||
case STATUS_TRANSLATE_UNDO:
|
||||
return deleteTranslation(state, action.statusId);
|
||||
return create(state, (draft) => deleteTranslation(draft, action.statusId));
|
||||
case STATUS_UNFILTER:
|
||||
return state.setIn([action.statusId, 'showFiltered'], false);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.showFiltered = false;
|
||||
}
|
||||
});
|
||||
case STATUS_LANGUAGE_CHANGE:
|
||||
return state.setIn([action.statusId, 'currentLanguage'], action.language);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status) {
|
||||
status.currentLanguage = action.language;
|
||||
}
|
||||
});
|
||||
case TIMELINE_DELETE:
|
||||
return deleteStatus(state, action.statusId, action.references);
|
||||
return create(state, (draft) => deleteStatus(draft, action.statusId, action.references));
|
||||
case EVENT_JOIN_REQUEST:
|
||||
return state.setIn([action.statusId, 'event', 'join_state'], 'pending');
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status?.event) {
|
||||
status.event.join_state = 'pending';
|
||||
}
|
||||
});
|
||||
case EVENT_JOIN_FAIL:
|
||||
case EVENT_LEAVE_REQUEST:
|
||||
return state.setIn([action.statusId, 'event', 'join_state'], null);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status?.event) {
|
||||
status.event.join_state = null;
|
||||
}
|
||||
});
|
||||
case EVENT_LEAVE_FAIL:
|
||||
return state.setIn([action.statusId, 'event', 'join_state'], action.previousState);
|
||||
return create(state, (draft) => {
|
||||
const status = draft[action.statusId];
|
||||
if (status?.event) {
|
||||
status.event.join_state = action.previousState;
|
||||
}
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import {
|
||||
List as ImmutableList,
|
||||
OrderedSet as ImmutableOrderedSet,
|
||||
Record as ImmutableRecord,
|
||||
} from 'immutable';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { getLocale } from 'pl-fe/actions/settings';
|
||||
// import { getLocale } from 'pl-fe/actions/settings';
|
||||
import { Entities } from 'pl-fe/entity-store/entities';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { getDomain } from 'pl-fe/utils/accounts';
|
||||
|
@ -126,11 +125,11 @@ type APIStatus = { id: string; username?: string };
|
|||
|
||||
const makeGetStatus = () => createSelector(
|
||||
[
|
||||
(state: RootState, { id }: APIStatus) => state.statuses.get(id),
|
||||
(state: RootState, { id }: APIStatus) => state.statuses.get(state.statuses.get(id)?.reblog_id || '', null),
|
||||
(state: RootState, { id }: APIStatus) => state.statuses.get(state.statuses.get(id)?.quote_id || '', null),
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[id],
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[state.statuses[id]?.reblog_id || ''] || null,
|
||||
(state: RootState, { id }: APIStatus) => state.statuses[state.statuses[id]?.quote_id || ''] || null,
|
||||
(state: RootState, { id }: APIStatus) => {
|
||||
const group = state.statuses.get(id)?.group_id;
|
||||
const group = state.statuses[id]?.group_id;
|
||||
if (group) return state.entities[Entities.GROUPS]?.store[group] as Group;
|
||||
return undefined;
|
||||
},
|
||||
|
@ -139,10 +138,11 @@ const makeGetStatus = () => createSelector(
|
|||
getFilters,
|
||||
(state: RootState) => state.me,
|
||||
(state: RootState) => state.auth.client.features,
|
||||
(state: RootState) => getLocale('en'),
|
||||
],
|
||||
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, filters, me, features, locale) => {
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, filters, me, features) => {
|
||||
// const locale = getLocale('en');
|
||||
|
||||
if (!statusBase) return null;
|
||||
const { account } = statusBase;
|
||||
const accountUsername = account.acct;
|
||||
|
@ -181,7 +181,7 @@ const makeGetNotification = () => createSelector([
|
|||
// @ts-ignore
|
||||
(state: RootState, notification: NotificationGroup) => selectAccount(state, notification.target_id),
|
||||
// @ts-ignore
|
||||
(state: RootState, notification: NotificationGroup) => state.statuses.get(notification.status_id),
|
||||
(state: RootState, notification: NotificationGroup) => state.statuses[notification.status_id],
|
||||
(state: RootState, notification: NotificationGroup) => selectAccounts(state, notification.sample_account_ids),
|
||||
], (notification, target, status, accounts): SelectedNotification => ({
|
||||
...notification,
|
||||
|
@ -213,28 +213,28 @@ const getAccountGallery = createSelector([
|
|||
(state: RootState, id: string) => state.timelines.get(`account:${id}:with_replies:media`)?.items || ImmutableOrderedSet<string>(),
|
||||
(state: RootState) => state.statuses,
|
||||
], (statusIds, statuses) =>
|
||||
statusIds.reduce((medias: ImmutableList<AccountGalleryAttachment>, statusId: string) => {
|
||||
const status = statuses.get(statusId);
|
||||
statusIds.reduce((medias: Array<AccountGalleryAttachment>, statusId: string) => {
|
||||
const status = statuses[statusId];
|
||||
if (!status) return medias;
|
||||
if (status.reblog_id) return medias;
|
||||
|
||||
return medias.concat(
|
||||
status.media_attachments.map(media => ({ ...media, status, account: status.account })));
|
||||
}, ImmutableList()),
|
||||
}, []),
|
||||
);
|
||||
|
||||
const getGroupGallery = createSelector([
|
||||
(state: RootState, id: string) => state.timelines.get(`group:${id}:media`)?.items || ImmutableOrderedSet<string>(),
|
||||
(state: RootState) => state.statuses,
|
||||
], (statusIds, statuses) =>
|
||||
statusIds.reduce((medias: ImmutableList<any>, statusId: string) => {
|
||||
const status = statuses.get(statusId);
|
||||
statusIds.reduce((medias: Array<AccountGalleryAttachment>, statusId: string) => {
|
||||
const status = statuses[statusId];
|
||||
if (!status) return medias;
|
||||
if (status.reblog_id) return medias;
|
||||
|
||||
return medias.concat(
|
||||
status.media_attachments.map(media => ({ ...media, status, account: status.account })));
|
||||
}, ImmutableList()),
|
||||
}, []),
|
||||
);
|
||||
|
||||
const makeGetReport = () => {
|
||||
|
@ -242,10 +242,10 @@ const makeGetReport = () => {
|
|||
|
||||
return createSelector(
|
||||
[
|
||||
(state: RootState, reportId: string) => state.admin.reports.get(reportId),
|
||||
(state: RootState, reportId: string) => selectAccount(state, state.admin.reports.get(reportId)?.account_id || ''),
|
||||
(state: RootState, reportId: string) => selectAccount(state, state.admin.reports.get(reportId)?.target_account_id || ''),
|
||||
(state: RootState, reportId: string) => state.admin.reports.get(reportId)!.status_ids
|
||||
(state: RootState, reportId: string) => state.admin.reports[reportId],
|
||||
(state: RootState, reportId: string) => selectAccount(state, state.admin.reports[reportId]?.account_id || ''),
|
||||
(state: RootState, reportId: string) => selectAccount(state, state.admin.reports[reportId]?.target_account_id || ''),
|
||||
(state: RootState, reportId: string) => state.admin.reports[reportId]!.status_ids
|
||||
.map((statusId) => getStatus(state, { id: statusId }))
|
||||
.filter((status): status is SelectedStatus => status !== null),
|
||||
],
|
||||
|
@ -295,7 +295,7 @@ const getSimplePolicy = createSelector([
|
|||
const getRemoteInstanceFavicon = (state: RootState, host: string) => {
|
||||
const accounts = state.entities[Entities.ACCOUNTS]?.store as EntityStore<Account>;
|
||||
const account = Object.entries(accounts).find(([_, account]) => account && getDomain(account) === host)?.[1];
|
||||
return account?.favicon;
|
||||
return account?.favicon || null;
|
||||
};
|
||||
|
||||
type HostFederation = {
|
||||
|
@ -319,25 +319,22 @@ const makeGetHosts = () =>
|
|||
.sort();
|
||||
});
|
||||
|
||||
const RemoteInstanceRecord = ImmutableRecord({
|
||||
host: '',
|
||||
favicon: null as string | null,
|
||||
federation: null as unknown as HostFederation,
|
||||
});
|
||||
|
||||
type RemoteInstance = ReturnType<typeof RemoteInstanceRecord>;
|
||||
interface RemoteInstance {
|
||||
host: string;
|
||||
favicon: string | null;
|
||||
federation: HostFederation;
|
||||
}
|
||||
|
||||
const makeGetRemoteInstance = () =>
|
||||
createSelector([
|
||||
(_state: RootState, host: string) => host,
|
||||
getRemoteInstanceFavicon,
|
||||
getRemoteInstanceFederation,
|
||||
], (host, favicon, federation) =>
|
||||
RemoteInstanceRecord({
|
||||
host,
|
||||
favicon,
|
||||
federation,
|
||||
}));
|
||||
], (host, favicon, federation): RemoteInstance => ({
|
||||
host,
|
||||
favicon,
|
||||
federation,
|
||||
}));
|
||||
|
||||
type ColumnQuery = { type: string; prefix?: string };
|
||||
|
||||
|
@ -347,7 +344,7 @@ const makeGetStatusIds = () => createSelector([
|
|||
(state: RootState) => state.statuses,
|
||||
], (columnSettings: any, statusIds: ImmutableOrderedSet<string>, statuses) =>
|
||||
statusIds.filter((id: string) => {
|
||||
const status = statuses.get(id);
|
||||
const status = statuses[id];
|
||||
if (!status) return true;
|
||||
return !shouldFilter(status, columnSettings);
|
||||
}),
|
||||
|
|
|
@ -1,36 +1,33 @@
|
|||
import {
|
||||
Map as ImmutableMap,
|
||||
List as ImmutableList,
|
||||
Set as ImmutableSet,
|
||||
} from 'immutable';
|
||||
import trimStart from 'lodash/trimStart';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { mrfSimpleSchema } from 'pl-fe/schemas/pleroma';
|
||||
|
||||
type Config = ImmutableMap<string, any>;
|
||||
import type { PleromaConfig } from 'pl-api';
|
||||
|
||||
type Policy = Record<string, any>;
|
||||
type Config = PleromaConfig['configs'][0];
|
||||
|
||||
const find = (
|
||||
configs: ImmutableList<Config>,
|
||||
configs: PleromaConfig['configs'],
|
||||
group: string,
|
||||
key: string,
|
||||
): Config | undefined => configs.find(config =>
|
||||
config.isSuperset(ImmutableMap({ group, key })),
|
||||
config.group === group && config.key === key,
|
||||
);
|
||||
|
||||
const toSimplePolicy = (configs: ImmutableList<Config>) => {
|
||||
const toSimplePolicy = (configs: PleromaConfig['configs']) => {
|
||||
const config = find(configs, ':pleroma', ':mrf_simple');
|
||||
|
||||
const reducer = (acc: ImmutableMap<string, any>, curr: ImmutableMap<string, any>) => {
|
||||
const key = curr.getIn(['tuple', 0]) as string;
|
||||
const hosts = curr.getIn(['tuple', 1]) as ImmutableList<string>;
|
||||
return acc.set(trimStart(key, ':'), ImmutableSet(hosts));
|
||||
const reducer = (acc: Record<string, any>, curr: Record<string, any>) => {
|
||||
const key = curr.tuple?.[0] as string;
|
||||
const hosts = curr.tuple?.[1] as Array<string>;
|
||||
return acc[trimStart(key, ':')] = hosts;
|
||||
};
|
||||
|
||||
if (config?.get) {
|
||||
const value = config.get('value', ImmutableList());
|
||||
const result = value.reduce(reducer, ImmutableMap());
|
||||
if (config) {
|
||||
const value = config.value || [];
|
||||
const result = value.reduce(reducer, {});
|
||||
return v.parse(mrfSimpleSchema, result.toJS());
|
||||
} else {
|
||||
return v.parse(mrfSimpleSchema, {});
|
||||
|
@ -38,7 +35,7 @@ const toSimplePolicy = (configs: ImmutableList<Config>) => {
|
|||
};
|
||||
|
||||
const fromSimplePolicy = (simplePolicy: Policy) => {
|
||||
const mapper = ([key, hosts]: [key: string, hosts: ImmutableList<string>]) => ({ tuple: [`:${key}`, hosts] });
|
||||
const mapper = ([key, hosts]: [key: string, hosts: Array<string>]) => ({ tuple: [`:${key}`, hosts] });
|
||||
|
||||
const value = Object.entries(simplePolicy).map(mapper);
|
||||
|
||||
|
|
Loading…
Reference in a new issue