nicolium: experimental timelines: block/mute side-effects, pending statuses
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -8,6 +8,7 @@ import { useContextStore } from '@/stores/contexts';
|
||||
import { useModalsStore } from '@/stores/modals';
|
||||
import { usePendingStatusesStore } from '@/stores/pending-statuses';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useTimelinesStore } from '@/stores/timelines';
|
||||
import { isLoggedIn } from '@/utils/auth';
|
||||
import { shouldHaveCard } from '@/utils/status';
|
||||
|
||||
@ -86,6 +87,7 @@ const createStatus =
|
||||
if (!params.preview) {
|
||||
usePendingStatusesStore.getState().actions.importStatus(params, idempotencyKey);
|
||||
useContextStore.getState().actions.importPendingStatus(params.in_reply_to_id, idempotencyKey);
|
||||
useTimelinesStore.getState().actions.importPendingStatus(params, idempotencyKey);
|
||||
if (!editedId) {
|
||||
incrementReplyCount(params);
|
||||
}
|
||||
@ -137,6 +139,12 @@ const createStatus =
|
||||
idempotencyKey,
|
||||
);
|
||||
|
||||
if (status.scheduled_at === null) {
|
||||
useTimelinesStore.getState().actions.replacePendingStatus(idempotencyKey, status.id);
|
||||
} else {
|
||||
useTimelinesStore.getState().actions.deletePendingStatus(idempotencyKey);
|
||||
}
|
||||
|
||||
// Poll the backend for the updated card
|
||||
if (expectsCard) {
|
||||
const delay = 1000;
|
||||
@ -161,6 +169,7 @@ const createStatus =
|
||||
})
|
||||
.catch((error) => {
|
||||
usePendingStatusesStore.getState().actions.deleteStatus(idempotencyKey);
|
||||
useTimelinesStore.getState().actions.deletePendingStatus(idempotencyKey);
|
||||
useContextStore
|
||||
.getState()
|
||||
.actions.deletePendingStatus(params.in_reply_to_id, idempotencyKey);
|
||||
@ -235,6 +244,7 @@ const deleteStatus =
|
||||
.statuses.deleteStatus(statusId)
|
||||
.then((source) => {
|
||||
usePendingStatusesStore.getState().actions.deleteStatus(statusId);
|
||||
useTimelinesStore.getState().actions.deleteStatus(statusId);
|
||||
updateStatus(
|
||||
statusId,
|
||||
(s) => {
|
||||
@ -267,6 +277,7 @@ const deleteStatusFromGroup =
|
||||
.experimental.groups.deleteGroupStatus(statusId, groupId)
|
||||
.then(() => {
|
||||
usePendingStatusesStore.getState().actions.deleteStatus(statusId);
|
||||
useTimelinesStore.getState().actions.deleteStatus(statusId);
|
||||
updateStatus(
|
||||
statusId,
|
||||
(s) => {
|
||||
|
||||
@ -12,6 +12,7 @@ import Icon from '@/components/ui/icon';
|
||||
import Portal from '@/components/ui/portal';
|
||||
import Emojify from '@/features/emoji/emojify';
|
||||
import PlaceholderStatus from '@/features/placeholder/components/placeholder-status';
|
||||
import PendingStatus from '@/features/ui/components/pending-status';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useAccounts } from '@/queries/accounts/use-accounts';
|
||||
import { type SelectedStatus, useStatus } from '@/queries/statuses/use-status';
|
||||
@ -51,6 +52,18 @@ const PlaceholderTimelineStatus = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
interface ITimelinePendingStatus {
|
||||
idempotencyKey: string;
|
||||
}
|
||||
|
||||
const TimelinePendingStatus: React.FC<ITimelinePendingStatus> = ({ idempotencyKey }) => {
|
||||
return (
|
||||
<div className='⁂-timeline-status relative border-b border-solid border-gray-200 dark:border-gray-800'>
|
||||
<PendingStatus idempotencyKey={idempotencyKey} variant='slim' />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface ITimelineStatusInfo {
|
||||
status: SelectedStatus;
|
||||
rebloggedBy: Array<string>;
|
||||
@ -245,6 +258,8 @@ const Timeline: React.FC<ITimeline> = ({ query, contextType = 'public' }) => {
|
||||
// variant={divideType === 'border' ? 'slim' : 'rounded'}
|
||||
/>
|
||||
);
|
||||
} else if (entry.type === 'pending-status') {
|
||||
return <TimelinePendingStatus key={entry.id} idempotencyKey={entry.id} />;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { skipToken, useQuery } from '@tanstack/react-query';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
@ -11,7 +12,7 @@ import PlaceholderCard from '@/features/placeholder/components/placeholder-card'
|
||||
import PlaceholderMediaGallery from '@/features/placeholder/components/placeholder-media-gallery';
|
||||
import QuotedStatus from '@/features/status/containers/quoted-status-container';
|
||||
import { useOwnAccount } from '@/hooks/use-own-account';
|
||||
import { usePollQuery } from '@/queries/statuses/use-poll';
|
||||
import { queryKeys } from '@/queries/keys';
|
||||
import { usePendingStatus } from '@/stores/pending-statuses';
|
||||
|
||||
import { buildStatus } from '../util/pending-status-builder';
|
||||
@ -19,6 +20,7 @@ import { buildStatus } from '../util/pending-status-builder';
|
||||
import PollPreview from './poll-preview';
|
||||
|
||||
import type { NormalizedStatus as StatusEntity } from '@/normalizers/status';
|
||||
import type { Poll } from 'pl-api';
|
||||
|
||||
const shouldHaveCard = (pendingStatus: StatusEntity) =>
|
||||
Boolean(/https?:\/\/\S*/.test(pendingStatus.content));
|
||||
@ -54,7 +56,11 @@ const PendingStatus: React.FC<IPendingStatus> = ({
|
||||
const status =
|
||||
pendingStatus && ownAccount ? buildStatus(ownAccount, pendingStatus, idempotencyKey) : null;
|
||||
|
||||
const { data: poll } = usePollQuery(status?.poll_id ?? '');
|
||||
const { data: poll } = useQuery<Poll>({
|
||||
queryKey: queryKeys.statuses.polls.show(status?.poll_id ?? ''),
|
||||
queryFn: skipToken,
|
||||
enabled: !!status?.poll_id,
|
||||
});
|
||||
|
||||
if (!status) return null;
|
||||
if (!ownAccount) return null;
|
||||
|
||||
@ -14,6 +14,7 @@ import { useLoggedIn } from '@/hooks/use-logged-in';
|
||||
import { useOwnAccount } from '@/hooks/use-own-account';
|
||||
import { queryKeys } from '@/queries/keys';
|
||||
import { useContextsActions } from '@/stores/contexts';
|
||||
import { useTimelinesStore } from '@/stores/timelines';
|
||||
|
||||
import type {
|
||||
BlockAccountParams,
|
||||
@ -192,6 +193,7 @@ const useBlockAccountMutation = (accountId: string) => {
|
||||
|
||||
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
|
||||
filterContexts(data);
|
||||
useTimelinesStore.getState().actions.filterTimelines(data.id);
|
||||
|
||||
return dispatch<AccountsAction>({
|
||||
type: ACCOUNT_BLOCK_SUCCESS,
|
||||
@ -260,6 +262,7 @@ const useMuteAccountMutation = (accountId: string) => {
|
||||
|
||||
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
|
||||
filterContexts(data);
|
||||
useTimelinesStore.getState().actions.filterTimelines(data.id);
|
||||
|
||||
return dispatch<AccountsAction>({
|
||||
type: ACCOUNT_MUTE_SUCCESS,
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { create } from 'zustand';
|
||||
import { mutative } from 'zustand-mutative';
|
||||
|
||||
import type { Status } from 'pl-api';
|
||||
import { findStatuses } from '@/queries/statuses/use-status';
|
||||
|
||||
import type { NormalizedStatus } from '@/normalizers/status';
|
||||
import type { CreateStatusParams, Status } from 'pl-api';
|
||||
|
||||
type TimelineEntry =
|
||||
| {
|
||||
@ -44,6 +47,10 @@ interface State {
|
||||
deleteStatus: (statusId: string) => void;
|
||||
setLoading: (timelineId: string, isFetching: boolean) => void;
|
||||
dequeueEntries: (timelineId: string) => void;
|
||||
importPendingStatus: (params: CreateStatusParams, idempotencyKey: string) => void;
|
||||
replacePendingStatus: (idempotencyKey: string, newId: string) => void;
|
||||
deletePendingStatus: (idempotencyKey: string) => void;
|
||||
filterTimelines: (accountId: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
@ -108,6 +115,21 @@ const processPage = (statuses: Array<Status>): Array<TimelineEntry> => {
|
||||
return timelinePage;
|
||||
};
|
||||
|
||||
const getTimelinesForStatus = (
|
||||
status: Pick<Status, 'visibility' | 'group'> | Pick<CreateStatusParams, 'visibility'>,
|
||||
): Array<string> => {
|
||||
switch (status.visibility) {
|
||||
case 'group':
|
||||
return [`group:${'group' in status && status.group?.id}`];
|
||||
case 'direct':
|
||||
return [];
|
||||
case 'public':
|
||||
return ['home', 'public:local', 'public', 'bubble'];
|
||||
default:
|
||||
return ['home'];
|
||||
}
|
||||
};
|
||||
|
||||
const useTimelinesStore = create<State>()(
|
||||
mutative((set) => ({
|
||||
timelines: {} as Record<string, TimelineData>,
|
||||
@ -179,6 +201,79 @@ const useTimelinesStore = create<State>()(
|
||||
timeline.queuedEntries = [];
|
||||
timeline.queuedCount = 0;
|
||||
}),
|
||||
importPendingStatus: (params, idempotencyKey) =>
|
||||
set((state) => {
|
||||
if (params.scheduled_at) return;
|
||||
|
||||
const timelineIds = getTimelinesForStatus(params);
|
||||
|
||||
for (const timelineId of timelineIds) {
|
||||
const timeline = state.timelines[timelineId];
|
||||
if (!timeline) continue;
|
||||
|
||||
if (
|
||||
timeline.entries.some((e) => e.type === 'pending-status' && e.id === idempotencyKey)
|
||||
)
|
||||
continue;
|
||||
|
||||
timeline.entries.unshift({ type: 'pending-status', id: idempotencyKey });
|
||||
}
|
||||
}),
|
||||
replacePendingStatus: (idempotencyKey, newId) =>
|
||||
set((state) => {
|
||||
for (const timeline of Object.values(state.timelines)) {
|
||||
const idx = timeline.entries.findIndex(
|
||||
(e) => e.type === 'pending-status' && e.id === idempotencyKey,
|
||||
);
|
||||
if (idx !== -1) {
|
||||
timeline.entries[idx] = {
|
||||
type: 'status',
|
||||
id: newId,
|
||||
rebloggedBy: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}),
|
||||
deletePendingStatus: (idempotencyKey) =>
|
||||
set((state) => {
|
||||
for (const timeline of Object.values(state.timelines)) {
|
||||
const idx = timeline.entries.findIndex(
|
||||
(e) => e.type === 'pending-status' && e.id === idempotencyKey,
|
||||
);
|
||||
if (idx !== -1) {
|
||||
timeline.entries.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}),
|
||||
filterTimelines: (accountId) =>
|
||||
set((state) => {
|
||||
const ownedStatuses = findStatuses(
|
||||
(status: NormalizedStatus) => status.account_id === accountId,
|
||||
);
|
||||
|
||||
const statusIdsToRemove = new Set<string>();
|
||||
|
||||
for (const [, status] of ownedStatuses) {
|
||||
statusIdsToRemove.add(status.id);
|
||||
}
|
||||
|
||||
for (const timeline of Object.values(state.timelines)) {
|
||||
timeline.entries = timeline.entries.filter((entry) => {
|
||||
if (entry.type !== 'status') return true;
|
||||
if (statusIdsToRemove.has(entry.id)) return false;
|
||||
|
||||
const index = entry.rebloggedBy.indexOf(accountId);
|
||||
if (index !== -1) entry.rebloggedBy.splice(index, 1);
|
||||
|
||||
return true;
|
||||
});
|
||||
timeline.queuedEntries = timeline.queuedEntries.filter(
|
||||
(status) =>
|
||||
status.account.id !== accountId && status.reblog?.account.id !== accountId,
|
||||
);
|
||||
timeline.queuedCount = timeline.queuedEntries.length;
|
||||
}
|
||||
}),
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user