diff --git a/packages/pl-fe/src/actions/compose.ts b/packages/pl-fe/src/actions/compose.ts index 0491b4be5..d7edbe89b 100644 --- a/packages/pl-fe/src/actions/compose.ts +++ b/packages/pl-fe/src/actions/compose.ts @@ -6,6 +6,7 @@ import { isNativeEmoji } from 'pl-fe/features/emoji'; import emojiSearch from 'pl-fe/features/emoji/search'; import { Language } from 'pl-fe/features/preferences'; import { queryClient } from 'pl-fe/queries/client'; +import { cancelDraftStatus } from 'pl-fe/queries/statuses/use-draft-statuses'; import { selectAccount, selectOwnAccount, makeGetAccount } from 'pl-fe/selectors'; import { tagHistory } from 'pl-fe/settings'; import { useModalsStore } from 'pl-fe/stores/modals'; @@ -313,7 +314,12 @@ const handleComposeSubmit = (dispatch: AppDispatch, getState: () => RootState, c const accountUrl = getAccount(state, state.me as string)!.url; const draftId = getState().compose[composeId]!.draft_id; - dispatch(submitComposeSuccess(composeId, data, accountUrl, draftId)); + dispatch(submitComposeSuccess(composeId, data)); + + if (draftId) { + cancelDraftStatus(queryClient, accountUrl, draftId); + } + if (data.scheduled_at === null) { dispatch(insertIntoTagHistory(composeId, data.tags || [], status)); toast.success(redact ? messages.redactSuccess : edit ? messages.editSuccess : messages.success, { @@ -481,12 +487,10 @@ const submitComposeRequest = (composeId: string) => ({ composeId, }); -const submitComposeSuccess = (composeId: string, status: BaseStatus | ScheduledStatus, accountUrl: string, draftId?: string | null) => ({ +const submitComposeSuccess = (composeId: string, status: BaseStatus | ScheduledStatus) => ({ type: COMPOSE_SUBMIT_SUCCESS, composeId, status, - accountUrl, - draftId, }); const submitComposeFail = (composeId: string, error: unknown) => ({ diff --git a/packages/pl-fe/src/actions/draft-statuses.ts b/packages/pl-fe/src/actions/draft-statuses.ts deleted file mode 100644 index a009fc5d9..000000000 --- a/packages/pl-fe/src/actions/draft-statuses.ts +++ /dev/null @@ -1,91 +0,0 @@ - -import { makeGetAccount } from 'pl-fe/selectors'; -import KVStore from 'pl-fe/storage/kv-store'; - -import type { AppDispatch, RootState } from 'pl-fe/store'; -import type { APIEntity } from 'pl-fe/types/entities'; - -const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const; - -const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS' as const; -const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS' as const; - -const getAccount = makeGetAccount(); - -interface DraftStatusesFetchSuccessAction { - type: typeof DRAFT_STATUSES_FETCH_SUCCESS; - statuses: Array; -} - -const fetchDraftStatuses = () => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const accountUrl = getAccount(state, state.me as string)!.url; - - return KVStore.getItem>(`drafts:${accountUrl}`).then((statuses) => { - if (statuses) { - dispatch({ - type: DRAFT_STATUSES_FETCH_SUCCESS, - statuses, - }); - } - }).catch(() => {}); - }; - -interface PersistDraftStatusAction { - type: typeof PERSIST_DRAFT_STATUS; - status: Record; - accountUrl: string; -} - -const saveDraftStatus = (composeId: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const accountUrl = getAccount(state, state.me as string)!.url; - - const compose = state.compose[composeId]!; - - const draft = { - ...compose, - draft_id: compose.draft_id || crypto.randomUUID(), - }; - - dispatch({ - type: PERSIST_DRAFT_STATUS, - status: draft, - accountUrl, - }); - }; - -interface CancelDraftStatusAction { - type: typeof CANCEL_DRAFT_STATUS; - statusId: string; - accountUrl: string; -} - -const cancelDraftStatus = (statusId: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const accountUrl = getAccount(state, state.me as string)!.url; - - dispatch({ - type: CANCEL_DRAFT_STATUS, - statusId, - accountUrl, - }); - }; - -type DraftStatusesAction = - | DraftStatusesFetchSuccessAction - | PersistDraftStatusAction - | CancelDraftStatusAction - -export { - DRAFT_STATUSES_FETCH_SUCCESS, - PERSIST_DRAFT_STATUS, - CANCEL_DRAFT_STATUS, - fetchDraftStatuses, - saveDraftStatus, - cancelDraftStatus, - type DraftStatusesAction, -}; diff --git a/packages/pl-fe/src/components/dropdown-navigation.tsx b/packages/pl-fe/src/components/dropdown-navigation.tsx index 397b6caba..baf117a79 100644 --- a/packages/pl-fe/src/components/dropdown-navigation.tsx +++ b/packages/pl-fe/src/components/dropdown-navigation.tsx @@ -20,6 +20,7 @@ import { useInstance } from 'pl-fe/hooks/use-instance'; import { useRegistrationStatus } from 'pl-fe/hooks/use-registration-status'; import { useFollowRequestsCount } from 'pl-fe/queries/accounts/use-follow-requests'; import { scheduledStatusesCountQueryOptions } from 'pl-fe/queries/statuses/scheduled-statuses'; +import { useDraftStatusesCountQuery } from 'pl-fe/queries/statuses/use-draft-statuses'; import { useInteractionRequestsCount } from 'pl-fe/queries/statuses/use-interaction-requests'; import { makeGetOtherAccounts } from 'pl-fe/selectors'; import { useSettingsStore } from 'pl-fe/stores/settings'; @@ -107,7 +108,7 @@ const DropdownNavigation: React.FC = React.memo((): JSX.Element | null => { const followRequestsCount = useFollowRequestsCount().data || 0; const interactionRequestsCount = useInteractionRequestsCount().data || 0; const scheduledStatusCount = useInfiniteQuery(authenticatedScheduledStatusesCountQueryOptions).data || 0; - const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length); + const { data: draftCount = 0 } = useDraftStatusesCountQuery(); // const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen); const touchStart = useRef(0); diff --git a/packages/pl-fe/src/components/modal-root.tsx b/packages/pl-fe/src/components/modal-root.tsx index 3179f1891..f49d3e546 100644 --- a/packages/pl-fe/src/components/modal-root.tsx +++ b/packages/pl-fe/src/components/modal-root.tsx @@ -4,9 +4,9 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { cancelReplyCompose } from 'pl-fe/actions/compose'; -import { saveDraftStatus } from 'pl-fe/actions/draft-statuses'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { usePrevious } from 'pl-fe/hooks/use-previous'; +import { usePersistDraftStatus } from 'pl-fe/queries/statuses/use-draft-statuses'; import { useModalsStore } from 'pl-fe/stores/modals'; import type { ModalType } from 'pl-fe/features/ui/components/modal-root'; @@ -38,6 +38,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) const history = useHistory(); const dispatch = useAppDispatch(); + const persistDraftStatus = usePersistDraftStatus(); const { openModal } = useModalsStore(); const [revealed, setRevealed] = useState(!!children); @@ -85,7 +86,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) }, secondary: intl.formatMessage(messages.saveDraft), onSecondary: isEditing ? undefined : () => { - dispatch(saveDraftStatus('compose-modal')); + persistDraftStatus('compose-modal'); onClose('COMPOSE'); dispatch(cancelReplyCompose()); }, diff --git a/packages/pl-fe/src/components/sidebar-navigation.tsx b/packages/pl-fe/src/components/sidebar-navigation.tsx index 6576140bd..b7ca67b2e 100644 --- a/packages/pl-fe/src/components/sidebar-navigation.tsx +++ b/packages/pl-fe/src/components/sidebar-navigation.tsx @@ -17,6 +17,7 @@ import { useFollowRequestsCount } from 'pl-fe/queries/accounts/use-follow-reques import { usePendingUsersCount } from 'pl-fe/queries/admin/use-accounts'; import { usePendingReportsCount } from 'pl-fe/queries/admin/use-reports'; import { scheduledStatusesCountQueryOptions } from 'pl-fe/queries/statuses/scheduled-statuses'; +import { useDraftStatusesCountQuery } from 'pl-fe/queries/statuses/use-draft-statuses'; import { useInteractionRequestsCount } from 'pl-fe/queries/statuses/use-interaction-requests'; import { useModalsStore } from 'pl-fe/stores/modals'; import sourceCode from 'pl-fe/utils/code'; @@ -72,8 +73,8 @@ const SidebarNavigation: React.FC = React.memo(({ shrink }) const { data: awaitingApprovalCount = 0 } = usePendingUsersCount(); const { data: pendingReportsCount = 0 } = usePendingReportsCount(); const dashboardCount = pendingReportsCount + awaitingApprovalCount; - const scheduledStatusCount = useInfiniteQuery(authenticatedScheduledStatusesCountQueryOptions).data || 0; - const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length); + const { data: scheduledStatusCount = 0 } = useInfiniteQuery(authenticatedScheduledStatusesCountQueryOptions); + const { data: draftCount = 0 } = useDraftStatusesCountQuery(); const restrictUnauth = instance.pleroma.metadata.restrict_unauthenticated; diff --git a/packages/pl-fe/src/features/compose/components/compose-form.tsx b/packages/pl-fe/src/features/compose/components/compose-form.tsx index 84d8273f4..fb51733fd 100644 --- a/packages/pl-fe/src/features/compose/components/compose-form.tsx +++ b/packages/pl-fe/src/features/compose/components/compose-form.tsx @@ -16,7 +16,6 @@ import { resetCompose, changeComposeRedactingOverwrite, } from 'pl-fe/actions/compose'; -import { saveDraftStatus } from 'pl-fe/actions/draft-statuses'; import DropdownMenu from 'pl-fe/components/dropdown-menu'; import List, { ListItem } from 'pl-fe/components/list'; import HStack from 'pl-fe/components/ui/hstack'; @@ -31,6 +30,7 @@ import { useCompose } from 'pl-fe/hooks/use-compose'; import { useDraggedFiles } from 'pl-fe/hooks/use-dragged-files'; import { useFeatures } from 'pl-fe/hooks/use-features'; import { useInstance } from 'pl-fe/hooks/use-instance'; +import { usePersistDraftStatus } from 'pl-fe/queries/statuses/use-draft-statuses'; import { useModalsStore } from 'pl-fe/stores/modals'; import toast from 'pl-fe/toast'; @@ -154,6 +154,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab const compose = useCompose(id); const maxTootChars = configuration.statuses.max_characters; const features = useFeatures(); + const persistDraftStatus = usePersistDraftStatus(); const { spoiler_text: spoilerText, @@ -229,7 +230,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab const handleSaveDraft = (e?: React.FormEvent) => { e?.preventDefault(); - dispatch(saveDraftStatus(id)); + persistDraftStatus(id); closeModal('COMPOSE'); dispatch(resetCompose(id)); editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); diff --git a/packages/pl-fe/src/features/draft-statuses/builder.tsx b/packages/pl-fe/src/features/draft-statuses/builder.tsx index 17cca3e6c..c86b34105 100644 --- a/packages/pl-fe/src/features/draft-statuses/builder.tsx +++ b/packages/pl-fe/src/features/draft-statuses/builder.tsx @@ -4,7 +4,7 @@ import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { normalizeStatus } from 'pl-fe/normalizers/status'; -import type { DraftStatus } from 'pl-fe/reducers/draft-statuses'; +import type { DraftStatus } from 'pl-fe/queries/statuses/use-draft-statuses'; import type { RootState } from 'pl-fe/store'; const buildPoll = (draftStatus: DraftStatus) => { diff --git a/packages/pl-fe/src/features/draft-statuses/components/draft-status-action-bar.tsx b/packages/pl-fe/src/features/draft-statuses/components/draft-status-action-bar.tsx index 0f1ce92e1..dc49e9ddc 100644 --- a/packages/pl-fe/src/features/draft-statuses/components/draft-status-action-bar.tsx +++ b/packages/pl-fe/src/features/draft-statuses/components/draft-status-action-bar.tsx @@ -2,16 +2,16 @@ import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { setComposeToStatus } from 'pl-fe/actions/compose'; -import { cancelDraftStatus } from 'pl-fe/actions/draft-statuses'; import { fetchStatus } from 'pl-fe/actions/statuses'; import Button from 'pl-fe/components/ui/button'; import HStack from 'pl-fe/components/ui/hstack'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; +import { useCancelDraftStatus } from 'pl-fe/queries/statuses/use-draft-statuses'; import { useModalsStore } from 'pl-fe/stores/modals'; import { useSettingsStore } from 'pl-fe/stores/settings'; import type { Status as StatusEntity } from 'pl-fe/normalizers/status'; -import type { DraftStatus } from 'pl-fe/reducers/draft-statuses'; +import type { DraftStatus } from 'pl-fe/queries/statuses/use-draft-statuses'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.draft_status_delete.confirm', defaultMessage: 'Discard' }, @@ -30,17 +30,18 @@ const DraftStatusActionBar: React.FC = ({ source, status const { openModal } = useModalsStore(); const { settings } = useSettingsStore(); const dispatch = useAppDispatch(); + const cancelDraftStatus = useCancelDraftStatus(); const handleCancelClick = () => { const deleteModal = settings.deleteModal; if (!deleteModal) { - dispatch(cancelDraftStatus(source.draft_id)); + cancelDraftStatus(source.draft_id); } else { openModal('CONFIRM', { heading: intl.formatMessage(messages.deleteHeading), message: intl.formatMessage(messages.deleteMessage), confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => dispatch(cancelDraftStatus(source.draft_id)), + onConfirm: () => cancelDraftStatus(source.draft_id), }); } }; diff --git a/packages/pl-fe/src/features/draft-statuses/components/draft-status.tsx b/packages/pl-fe/src/features/draft-statuses/components/draft-status.tsx index 25a61db0a..0f401a6f8 100644 --- a/packages/pl-fe/src/features/draft-statuses/components/draft-status.tsx +++ b/packages/pl-fe/src/features/draft-statuses/components/draft-status.tsx @@ -17,7 +17,7 @@ import { buildStatus } from '../builder'; import DraftStatusActionBar from './draft-status-action-bar'; import type { Status as StatusEntity } from 'pl-fe/normalizers/status'; -import type { DraftStatus as DraftStatusType } from 'pl-fe/reducers/draft-statuses'; +import type { DraftStatus as DraftStatusType } from 'pl-fe/queries/statuses/use-draft-statuses'; interface IDraftStatus { draftStatus: DraftStatusType; diff --git a/packages/pl-fe/src/features/ui/index.tsx b/packages/pl-fe/src/features/ui/index.tsx index 03184b6a5..b77791e57 100644 --- a/packages/pl-fe/src/features/ui/index.tsx +++ b/packages/pl-fe/src/features/ui/index.tsx @@ -3,7 +3,6 @@ import React, { Suspense, lazy, useEffect, useRef } from 'react'; import { Redirect, Switch, useHistory, useLocation } from 'react-router-dom'; import { fetchConfig } from 'pl-fe/actions/admin'; -import { fetchDraftStatuses } from 'pl-fe/actions/draft-statuses'; import { fetchFilters } from 'pl-fe/actions/filters'; import { fetchMarker } from 'pl-fe/actions/markers'; import { expandNotifications } from 'pl-fe/actions/notifications'; @@ -414,8 +413,6 @@ const UI: React.FC = React.memo(({ children }) => { prefetchCustomEmojis(client); - dispatch(fetchDraftStatuses()); - dispatch(fetchHomeTimeline()); dispatch(expandNotifications()) diff --git a/packages/pl-fe/src/modals/compose-modal.tsx b/packages/pl-fe/src/modals/compose-modal.tsx index cfae161f6..ff3e1b5f3 100644 --- a/packages/pl-fe/src/modals/compose-modal.tsx +++ b/packages/pl-fe/src/modals/compose-modal.tsx @@ -3,13 +3,13 @@ import React, { useRef } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { cancelReplyCompose, uploadCompose } from 'pl-fe/actions/compose'; -import { saveDraftStatus } from 'pl-fe/actions/draft-statuses'; import { checkComposeContent } from 'pl-fe/components/modal-root'; import Modal from 'pl-fe/components/ui/modal'; import { ComposeForm } from 'pl-fe/features/ui/util/async-components'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useCompose } from 'pl-fe/hooks/use-compose'; import { useDraggedFiles } from 'pl-fe/hooks/use-dragged-files'; +import { usePersistDraftStatus } from 'pl-fe/queries/statuses/use-draft-statuses'; import { useModalsStore } from 'pl-fe/stores/modals'; import type { BaseModalProps } from 'pl-fe/features/ui/components/modal-root'; @@ -30,6 +30,7 @@ const ComposeModal: React.FC = ({ onClose, c const node = useRef(null); const compose = useCompose(composeId); const { openModal } = useModalsStore(); + const persistDraftStatus = usePersistDraftStatus(); const { id: statusId, privacy, in_reply_to: inReplyTo, quote, group_id: groupId } = compose; @@ -57,7 +58,7 @@ const ComposeModal: React.FC = ({ onClose, c }, secondary: intl.formatMessage(messages.saveDraft), onSecondary: statusId ? undefined : () => { - dispatch(saveDraftStatus(composeId)); + persistDraftStatus(composeId); onClose('COMPOSE'); dispatch(cancelReplyCompose()); }, diff --git a/packages/pl-fe/src/pages/status-lists/draft-statuses.tsx b/packages/pl-fe/src/pages/status-lists/draft-statuses.tsx index ad1fec652..224e7b309 100644 --- a/packages/pl-fe/src/pages/status-lists/draft-statuses.tsx +++ b/packages/pl-fe/src/pages/status-lists/draft-statuses.tsx @@ -1,12 +1,10 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { fetchDraftStatuses } from 'pl-fe/actions/draft-statuses'; import ScrollableList from 'pl-fe/components/scrollable-list'; import Column from 'pl-fe/components/ui/column'; import DraftStatus from 'pl-fe/features/draft-statuses/components/draft-status'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; -import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; +import { useDraftStatusesQuery } from 'pl-fe/queries/statuses/use-draft-statuses'; const messages = defineMessages({ heading: { id: 'column.draft_statuses', defaultMessage: 'Drafts' }, @@ -14,13 +12,8 @@ const messages = defineMessages({ const DraftStatusesPage = () => { const intl = useIntl(); - const dispatch = useAppDispatch(); - const drafts = useAppSelector((state) => state.draft_statuses); - - useEffect(() => { - dispatch(fetchDraftStatuses()); - }, []); + const { data: drafts = [] } = useDraftStatusesQuery(data => Object.values(data)); const emptyMessage = ; @@ -31,7 +24,7 @@ const DraftStatusesPage = () => { emptyMessage={emptyMessage} listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800' > - {Object.values(drafts).toReversed().map((draft) => )} + {drafts.toReversed().map((draft) => )} ); diff --git a/packages/pl-fe/src/queries/statuses/use-draft-statuses.ts b/packages/pl-fe/src/queries/statuses/use-draft-statuses.ts new file mode 100644 index 000000000..229070476 --- /dev/null +++ b/packages/pl-fe/src/queries/statuses/use-draft-statuses.ts @@ -0,0 +1,96 @@ +import { type QueryClient, useQuery, useQueryClient } from '@tanstack/react-query'; +import { create } from 'mutative'; +import { mediaAttachmentSchema } from 'pl-api'; +import * as v from 'valibot'; + +import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; +import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; +import { filteredArray } from 'pl-fe/schemas/utils'; +import KVStore from 'pl-fe/storage/kv-store'; +import { APIEntity } from 'pl-fe/types/entities'; + +const draftStatusSchema = v.object({ + content_type: v.fallback(v.string(), 'text/plain'), + draft_id: v.string(), + editorState: v.fallback(v.nullable(v.string()), null), + group_id: v.fallback(v.nullable(v.string()), null), + in_reply_to: v.fallback(v.nullable(v.string()), null), + media_attachments: filteredArray(mediaAttachmentSchema), + poll: v.fallback(v.nullable(v.record(v.string(), v.any())), null), + privacy: v.fallback(v.string(), 'public'), + quote: v.fallback(v.nullable(v.string()), null), + schedule: v.fallback(v.nullable(v.string()), null), + sensitive: v.fallback(v.boolean(), false), + spoiler_text: v.fallback(v.string(), ''), + text: v.fallback(v.string(), ''), +}); + +type DraftStatus = v.InferOutput; + +const getDrafts = async (accountUrl: string) => { + const drafts = await KVStore.getItem>(`drafts:${accountUrl}`) || []; + + return Object.fromEntries(Object.values(drafts) + .map((draft) => v.safeParse(draftStatusSchema, draft).output as DraftStatus) + .filter((draft) => draft) + .map((draft) => [draft.draft_id, draft])); +}; + +const persistDrafts = async (accountUrl: string, drafts: Record) => KVStore.setItem(`drafts:${accountUrl}`, Object.values(drafts)); + +const useDraftStatusesQuery = (select?: (data: Record) => T) => { + const { account } = useOwnAccount(); + + return useQuery({ + queryKey: ['draftStatuses'], + queryFn: () => getDrafts(account!.url), + enabled: !!account, + select, + }); +}; + +const useDraftStatusQuery = (draftStatusId: string) => useDraftStatusesQuery(data => data[draftStatusId]); + +const useDraftStatusesCountQuery = () => useDraftStatusesQuery(data => Object.values(data).length); + +const usePersistDraftStatus = () => { + const { account } = useOwnAccount(); + const dispatch = useAppDispatch(); + const queryClient = useQueryClient(); + + return (composeId: string) => { + dispatch((_, getState) => { + const compose = getState().compose[composeId]!; + + const draft = { + ...compose, + draft_id: compose.draft_id || crypto.randomUUID(), + }; + + const drafts = queryClient.getQueryData>(['draftStatuses']) || {}; + + const newDrafts: Record = create(drafts, (oldDrafts) => { + oldDrafts[draft.draft_id] = v.parse(draftStatusSchema, draft); + }); + return persistDrafts(account!.url, newDrafts).then(() => queryClient.invalidateQueries({ queryKey: ['draftStatuses'] })); + }); + }; +}; + +const cancelDraftStatus = (queryClient: QueryClient, accountUrl: string, draftId: string) => { + const drafts = queryClient.getQueryData>(['draftStatuses']) || {}; + + const newDrafts: Record = create(drafts, (oldDrafts) => { + delete oldDrafts[draftId]; + }); + return persistDrafts(accountUrl, newDrafts).then((drafts) => queryClient.invalidateQueries({ queryKey: ['draftStatuses'] })); +}; + +const useCancelDraftStatus = () => { + const { account } = useOwnAccount(); + const queryClient = useQueryClient(); + + return (draftId: string) => cancelDraftStatus(queryClient, account!.url, draftId); +}; + +export { draftStatusSchema, useDraftStatusesQuery, useDraftStatusQuery, useDraftStatusesCountQuery, usePersistDraftStatus, cancelDraftStatus, useCancelDraftStatus, type DraftStatus }; diff --git a/packages/pl-fe/src/reducers/draft-statuses.ts b/packages/pl-fe/src/reducers/draft-statuses.ts deleted file mode 100644 index 4f71e9543..000000000 --- a/packages/pl-fe/src/reducers/draft-statuses.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { create } from 'mutative'; -import { mediaAttachmentSchema } from 'pl-api'; -import * as v from 'valibot'; - -import { COMPOSE_SUBMIT_SUCCESS, type ComposeAction } from 'pl-fe/actions/compose'; -import { DRAFT_STATUSES_FETCH_SUCCESS, PERSIST_DRAFT_STATUS, CANCEL_DRAFT_STATUS, type DraftStatusesAction } from 'pl-fe/actions/draft-statuses'; -import { filteredArray } from 'pl-fe/schemas/utils'; -import KVStore from 'pl-fe/storage/kv-store'; - -import type { APIEntity } from 'pl-fe/types/entities'; - -const draftStatusSchema = v.object({ - content_type: v.fallback(v.string(), 'text/plain'), - draft_id: v.string(), - editorState: v.fallback(v.nullable(v.string()), null), - group_id: v.fallback(v.nullable(v.string()), null), - in_reply_to: v.fallback(v.nullable(v.string()), null), - media_attachments: filteredArray(mediaAttachmentSchema), - poll: v.fallback(v.nullable(v.record(v.string(), v.any())), null), - privacy: v.fallback(v.string(), 'public'), - quote: v.fallback(v.nullable(v.string()), null), - schedule: v.fallback(v.nullable(v.string()), null), - sensitive: v.fallback(v.boolean(), false), - spoiler: v.fallback(v.boolean(), false), - spoiler_text: v.fallback(v.string(), ''), - text: v.fallback(v.string(), ''), -}); - -type DraftStatus = v.InferOutput; -type State = Record; - -const initialState: State = {}; - -const importStatus = (state: State, status: APIEntity) => { - state[status.draft_id] = v.parse(draftStatusSchema, status); -}; - -const importStatuses = (state: State, statuses: APIEntity[]) => { - Object.values(statuses || {}).forEach(status => importStatus(state, status)); -}; - -const deleteStatus = (state: State, statusId: string) => { - if (statusId) delete state[statusId]; - return state; -}; - -const persistState = (state: State, accountUrl: string) => { - KVStore.setItem(`drafts:${accountUrl}`, state); - return state; -}; - -const scheduled_statuses = (state: State = initialState, action: DraftStatusesAction | ComposeAction) => { - switch (action.type) { - case DRAFT_STATUSES_FETCH_SUCCESS: - return create(state, (draft) => importStatuses(draft, action.statuses)); - case PERSIST_DRAFT_STATUS: - return persistState(create(state, (draft) => importStatus(draft, action.status)), action.accountUrl); - case CANCEL_DRAFT_STATUS: - return persistState(create(state, (draft) => deleteStatus(draft, action.statusId)), action.accountUrl); - case COMPOSE_SUBMIT_SUCCESS: - return action.draftId ? persistState(create(state, (draft) => deleteStatus(draft, action.draftId!)), action.accountUrl) : state; - default: - return state; - } -}; - -export { - type DraftStatus, - scheduled_statuses as default, -}; diff --git a/packages/pl-fe/src/reducers/index.ts b/packages/pl-fe/src/reducers/index.ts index 547609de9..94f22fe7d 100644 --- a/packages/pl-fe/src/reducers/index.ts +++ b/packages/pl-fe/src/reducers/index.ts @@ -10,7 +10,6 @@ import auth from './auth'; import compose from './compose'; import contexts from './contexts'; import conversations from './conversations'; -import draft_statuses from './draft-statuses'; import filters from './filters'; import instance from './instance'; import me from './me'; @@ -33,7 +32,6 @@ const reducers = { compose, contexts, conversations, - draft_statuses, entities, filters, instance,