411 lines
13 KiB
TypeScript
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,
|
|
};
|