Files
ncd-fe/packages/pl-fe/src/actions/statuses.ts
nicole mikołajczyk 0c5b15ec7b nicolium: use @ imports when they save a char
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-27 00:30:24 +01:00

411 lines
13 KiB
TypeScript

import { getClient } from '@/api';
import { queryClient } from '@/queries/client';
import { queryKeys } from '@/queries/keys';
import { scheduledStatusesQueryOptions } from '@/queries/statuses/scheduled-statuses';
import { useComposeStore } from '@/stores/compose';
import { useContextStore } from '@/stores/contexts';
import { useModalsStore } from '@/stores/modals';
import { usePendingStatusesStore } from '@/stores/pending-statuses';
import { useSettingsStore } from '@/stores/settings';
import { isLoggedIn } from '@/utils/auth';
import { shouldHaveCard } from '@/utils/status';
import { importEntities } from './importer';
import { deleteFromTimelines } from './timelines';
import type { NormalizedStatus as Status } from '@/reducers/statuses';
import type { AppDispatch, RootState } from '@/store';
import type { CreateStatusParams, Status as BaseStatus, ScheduledStatus } from 'pl-api';
import type { IntlShape } from 'react-intl';
const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST' as const;
const STATUS_CREATE_SUCCESS = 'STATUS_CREATE_SUCCESS' as const;
const STATUS_CREATE_FAIL = 'STATUS_CREATE_FAIL' as const;
const STATUS_FETCH_SOURCE_REQUEST = 'STATUS_FETCH_SOURCE_REQUEST' as const;
const STATUS_FETCH_SOURCE_SUCCESS = 'STATUS_FETCH_SOURCE_SUCCESS' as const;
const STATUS_FETCH_SOURCE_FAIL = 'STATUS_FETCH_SOURCE_FAIL' as const;
const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST' as const;
const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS' as const;
const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL' as const;
const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS' as const;
const STATUS_MUTE_SUCCESS = 'STATUS_MUTE_SUCCESS' as const;
const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS' as const;
const STATUS_UNFILTER = 'STATUS_UNFILTER' as const;
const createStatus =
(
params: CreateStatusParams,
idempotencyKey: string,
editedId: string | null,
redacting = false,
) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!params.preview) {
usePendingStatusesStore.getState().actions.importStatus(params, idempotencyKey);
useContextStore.getState().actions.importPendingStatus(params.in_reply_to_id, idempotencyKey);
dispatch<StatusesAction>({
type: STATUS_CREATE_REQUEST,
params,
idempotencyKey,
editing: !!editedId,
redacting,
});
}
const client = getClient(getState());
return (
editedId === null
? client.statuses.createStatus(params)
: redacting
? client.admin.statuses.redactStatus(editedId, params)
: client.statuses.editStatus(editedId, params)
)
.then((status) => {
if (params.preview) return status;
// The backend might still be processing the rich media attachment
const expectsCard = status.scheduled_at === null && !status.card && shouldHaveCard(status);
if (status.scheduled_at === null) {
dispatch(
importEntities(
{ statuses: [{ ...status, expectsCard }] },
{ idempotencyKey, withParents: true },
),
);
} else {
queryClient.invalidateQueries(scheduledStatusesQueryOptions);
}
dispatch<StatusesAction>({
type: STATUS_CREATE_SUCCESS,
status,
params,
idempotencyKey,
editing: !!editedId,
});
useContextStore
.getState()
.actions.deletePendingStatus(
'in_reply_to_id' in status ? status.in_reply_to_id : null,
idempotencyKey,
);
// Poll the backend for the updated card
if (expectsCard) {
const delay = 1000;
const poll = (retries = 5) => {
return getClient(getState())
.statuses.getStatus(status.id)
.then((response) => {
if (response.card) {
dispatch(importEntities({ statuses: [response] }));
} else if (retries > 0 && response) {
setTimeout(() => poll(retries - 1), delay);
}
})
.catch(console.error);
};
setTimeout(() => poll(), delay);
}
return status;
})
.catch((error) => {
usePendingStatusesStore.getState().actions.deleteStatus(idempotencyKey);
useContextStore
.getState()
.actions.deletePendingStatus(params.in_reply_to_id, idempotencyKey);
dispatch<StatusesAction>({
type: STATUS_CREATE_FAIL,
error,
params,
idempotencyKey,
editing: !!editedId,
});
throw error;
});
};
const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const status = state.statuses[statusId];
const poll = status.poll_id
? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id))
: undefined;
dispatch<StatusesAction>({ type: STATUS_FETCH_SOURCE_REQUEST });
return getClient(state)
.statuses.getStatusSource(statusId)
.then((response) => {
dispatch<StatusesAction>({ type: STATUS_FETCH_SOURCE_SUCCESS });
useComposeStore.getState().actions.setComposeToStatus(status, poll, response);
useModalsStore.getState().actions.openModal('COMPOSE');
})
.catch((error) => {
dispatch<StatusesAction>({ type: STATUS_FETCH_SOURCE_FAIL, error });
});
};
const fetchStatus =
(statusId: string, intl?: IntlShape) => (dispatch: AppDispatch, getState: () => RootState) => {
const params =
intl && useSettingsStore.getState().settings.autoTranslate
? {
language: intl.locale,
}
: undefined;
return getClient(getState())
.statuses.getStatus(statusId, params)
.then((status) => {
dispatch(importEntities({ statuses: [status] }));
return status;
});
};
const deleteStatus =
(statusId: string, withRedraft = false) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
const state = getState();
const status = state.statuses[statusId];
const poll = status.poll_id
? queryClient.getQueryData(queryKeys.statuses.polls.show(status.poll_id))
: undefined;
dispatch<StatusesAction>({ type: STATUS_DELETE_REQUEST, params: status });
return getClient(state)
.statuses.deleteStatus(statusId)
.then((source) => {
usePendingStatusesStore.getState().actions.deleteStatus(statusId);
dispatch<StatusesAction>({ type: STATUS_DELETE_SUCCESS, statusId });
dispatch(deleteFromTimelines(statusId));
if (withRedraft) {
useComposeStore.getState().actions.setComposeToStatus(status, poll, source, withRedraft);
useModalsStore.getState().actions.openModal('COMPOSE');
}
})
.catch((error) => {
dispatch<StatusesAction>({ type: STATUS_DELETE_FAIL, params: status, error });
});
};
const deleteStatusFromGroup =
(statusId: string, groupId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
const state = getState();
const status = state.statuses[statusId];
dispatch<StatusesAction>({ type: STATUS_DELETE_REQUEST, params: status });
return getClient(state)
.experimental.groups.deleteGroupStatus(statusId, groupId)
.then(() => {
usePendingStatusesStore.getState().actions.deleteStatus(statusId);
dispatch<StatusesAction>({ type: STATUS_DELETE_SUCCESS, statusId });
dispatch(deleteFromTimelines(statusId));
})
.catch((error) => {
dispatch<StatusesAction>({ type: STATUS_DELETE_FAIL, params: status, error });
});
};
const updateStatus = (status: BaseStatus) => (dispatch: AppDispatch) => {
dispatch(importEntities({ statuses: [status] }));
};
const fetchContext =
(statusId: string, intl?: IntlShape) => (dispatch: AppDispatch, getState: () => RootState) => {
const params =
intl && useSettingsStore.getState().settings.autoTranslate
? {
language: intl.locale,
}
: undefined;
return getClient(getState())
.statuses.getContext(statusId, params)
.then((context) => {
const { ancestors, descendants } = context;
const statuses = ancestors.concat(descendants);
dispatch(importEntities({ statuses }));
useContextStore.getState().actions.importContext(statusId, context);
dispatch<StatusesAction>({ type: CONTEXT_FETCH_SUCCESS, statusId, ancestors, descendants });
return context;
})
.catch((error) => {
if (error.response?.status === 404) {
dispatch(deleteFromTimelines(statusId));
}
});
};
const fetchStatusWithContext = (statusId: string, intl?: IntlShape) => (dispatch: AppDispatch) =>
Promise.all([dispatch(fetchContext(statusId, intl)), dispatch(fetchStatus(statusId, intl))]);
const muteStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
return getClient(getState())
.statuses.muteStatus(statusId)
.then(() => {
dispatch<StatusesAction>({ type: STATUS_MUTE_SUCCESS, statusId });
});
};
const unmuteStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
return getClient(getState())
.statuses.unmuteStatus(statusId)
.then(() => {
dispatch<StatusesAction>({ type: STATUS_UNMUTE_SUCCESS, statusId });
});
};
const toggleMuteStatus = (status: Pick<Status, 'id' | 'muted'>) =>
status.muted ? unmuteStatus(status.id) : muteStatus(status.id);
// let TRANSLATIONS_QUEUE: Set<string> = new Set();
// let TRANSLATIONS_TIMEOUT: NodeJS.Timeout | null = null;
// const translateStatus = (statusId: string, targetLanguage: string, lazy?: boolean) =>
// (dispatch: AppDispatch, getState: () => RootState) => {
// const client = getClient(getState);
// const features = client.features;
// const handleTranslateMany = () => {
// const copy = [...TRANSLATIONS_QUEUE];
// TRANSLATIONS_QUEUE = new Set();
// if (TRANSLATIONS_TIMEOUT) clearTimeout(TRANSLATIONS_TIMEOUT);
// return client.statuses.translateStatuses(copy, targetLanguage).then((response) => {
// response.forEach((translation) => {
// dispatch<StatusesAction>({
// type: STATUS_TRANSLATE_SUCCESS,
// statusId: translation.id,
// translation: translation,
// });
// copy
// .filter((statusId) => !response.some(({ id }) => id === statusId))
// .forEach((statusId) => dispatch<StatusesAction>({
// type: STATUS_TRANSLATE_FAIL,
// statusId,
// }));
// });
// }).catch(error => {
// dispatch<StatusesAction>({
// type: STATUS_TRANSLATE_FAIL,
// statusId,
// error,
// });
// });
// };
// if (features.lazyTranslations && lazy) {
// TRANSLATIONS_QUEUE.add(statusId);
// if (TRANSLATIONS_TIMEOUT) clearTimeout(TRANSLATIONS_TIMEOUT);
// TRANSLATIONS_TIMEOUT = setTimeout(() => handleTranslateMany(), 3000);
// } else if (features.lazyTranslations && TRANSLATIONS_QUEUE.size) {
// TRANSLATIONS_QUEUE.add(statusId);
// handleTranslateMany();
// }
// };
const unfilterStatus = (statusId: string) => ({
type: STATUS_UNFILTER,
statusId,
});
type StatusesAction =
| {
type: typeof STATUS_CREATE_REQUEST;
params: CreateStatusParams;
idempotencyKey: string;
editing: boolean;
redacting: boolean;
}
| {
type: typeof STATUS_CREATE_SUCCESS;
status: BaseStatus | ScheduledStatus;
params: CreateStatusParams;
idempotencyKey: string;
editing: boolean;
}
| {
type: typeof STATUS_CREATE_FAIL;
error: unknown;
params: CreateStatusParams;
idempotencyKey: string;
editing: boolean;
}
| { type: typeof STATUS_FETCH_SOURCE_REQUEST }
| { type: typeof STATUS_FETCH_SOURCE_SUCCESS }
| { type: typeof STATUS_FETCH_SOURCE_FAIL; error: unknown }
| { type: typeof STATUS_DELETE_REQUEST; params: Pick<Status, 'in_reply_to_id' | 'quote_id'> }
| { type: typeof STATUS_DELETE_SUCCESS; statusId: string }
| {
type: typeof STATUS_DELETE_FAIL;
params: Pick<Status, 'in_reply_to_id' | 'quote_id'>;
error: unknown;
}
| {
type: typeof CONTEXT_FETCH_SUCCESS;
statusId: string;
ancestors: Array<BaseStatus>;
descendants: Array<BaseStatus>;
}
| { type: typeof STATUS_MUTE_SUCCESS; statusId: string }
| { type: typeof STATUS_UNMUTE_SUCCESS; statusId: string }
| ReturnType<typeof unfilterStatus>;
export {
STATUS_CREATE_REQUEST,
STATUS_CREATE_SUCCESS,
STATUS_CREATE_FAIL,
STATUS_FETCH_SOURCE_REQUEST,
STATUS_FETCH_SOURCE_SUCCESS,
STATUS_FETCH_SOURCE_FAIL,
STATUS_DELETE_REQUEST,
STATUS_DELETE_SUCCESS,
STATUS_DELETE_FAIL,
CONTEXT_FETCH_SUCCESS,
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
STATUS_UNFILTER,
createStatus,
editStatus,
fetchStatus,
deleteStatus,
deleteStatusFromGroup,
updateStatus,
fetchContext,
fetchStatusWithContext,
muteStatus,
unmuteStatus,
toggleMuteStatus,
unfilterStatus,
type StatusesAction,
};