frontend-rw #1
6 changed files with 74 additions and 48 deletions
|
@ -3,27 +3,40 @@ import { makeGetAccount } from 'pl-fe/selectors';
|
||||||
import KVStore from 'pl-fe/storage/kv-store';
|
import KVStore from 'pl-fe/storage/kv-store';
|
||||||
|
|
||||||
import type { AppDispatch, RootState } from 'pl-fe/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';
|
|
||||||
|
|
||||||
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS';
|
const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const;
|
||||||
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS';
|
|
||||||
|
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS' as const;
|
||||||
|
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS' as const;
|
||||||
|
|
||||||
const getAccount = makeGetAccount();
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
interface DraftStatusesFetchSuccessAction {
|
||||||
|
type: typeof DRAFT_STATUSES_FETCH_SUCCESS;
|
||||||
|
statuses: Array<APIEntity>;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchDraftStatuses = () =>
|
const fetchDraftStatuses = () =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const accountUrl = getAccount(state, state.me as string)!.url;
|
const accountUrl = getAccount(state, state.me as string)!.url;
|
||||||
|
|
||||||
return KVStore.getItem(`drafts:${accountUrl}`).then((statuses) => {
|
return KVStore.getItem<Array<APIEntity>>(`drafts:${accountUrl}`).then((statuses) => {
|
||||||
dispatch({
|
dispatch<DraftStatusesFetchSuccessAction>({
|
||||||
type: DRAFT_STATUSES_FETCH_SUCCESS,
|
type: DRAFT_STATUSES_FETCH_SUCCESS,
|
||||||
statuses,
|
statuses,
|
||||||
});
|
});
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface PersistDraftStatusAction {
|
||||||
|
type: typeof PERSIST_DRAFT_STATUS;
|
||||||
|
status: Record<string, any>;
|
||||||
|
accountUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
const saveDraftStatus = (composeId: string) =>
|
const saveDraftStatus = (composeId: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -36,25 +49,36 @@ const saveDraftStatus = (composeId: string) =>
|
||||||
draft_id: compose.draft_id || crypto.randomUUID(),
|
draft_id: compose.draft_id || crypto.randomUUID(),
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch({
|
dispatch<PersistDraftStatusAction>({
|
||||||
type: PERSIST_DRAFT_STATUS,
|
type: PERSIST_DRAFT_STATUS,
|
||||||
status: draft,
|
status: draft,
|
||||||
accountUrl,
|
accountUrl,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface CancelDraftStatusAction {
|
||||||
|
type: typeof CANCEL_DRAFT_STATUS;
|
||||||
|
statusId: string;
|
||||||
|
accountUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
const cancelDraftStatus = (statusId: string) =>
|
const cancelDraftStatus = (statusId: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const accountUrl = getAccount(state, state.me as string)!.url;
|
const accountUrl = getAccount(state, state.me as string)!.url;
|
||||||
|
|
||||||
dispatch({
|
dispatch<CancelDraftStatusAction>({
|
||||||
type: CANCEL_DRAFT_STATUS,
|
type: CANCEL_DRAFT_STATUS,
|
||||||
statusId,
|
statusId,
|
||||||
accountUrl,
|
accountUrl,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DraftStatusesAction =
|
||||||
|
| DraftStatusesFetchSuccessAction
|
||||||
|
| PersistDraftStatusAction
|
||||||
|
| CancelDraftStatusAction
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DRAFT_STATUSES_FETCH_SUCCESS,
|
DRAFT_STATUSES_FETCH_SUCCESS,
|
||||||
PERSIST_DRAFT_STATUS,
|
PERSIST_DRAFT_STATUS,
|
||||||
|
@ -62,4 +86,5 @@ export {
|
||||||
fetchDraftStatuses,
|
fetchDraftStatuses,
|
||||||
saveDraftStatus,
|
saveDraftStatus,
|
||||||
cancelDraftStatus,
|
cancelDraftStatus,
|
||||||
|
type DraftStatusesAction,
|
||||||
};
|
};
|
||||||
|
|
|
@ -99,7 +99,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
|
||||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
|
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
|
||||||
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
|
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
|
||||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||||
const draftCount = useAppSelector((state) => state.draft_statuses.size);
|
const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length);
|
||||||
// const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
// const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
|
||||||
const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen);
|
const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen);
|
||||||
const touchStart = useRef(0);
|
const touchStart = useRef(0);
|
||||||
|
|
|
@ -52,7 +52,7 @@ const SidebarNavigation = () => {
|
||||||
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
|
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
|
||||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length);
|
const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length);
|
||||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||||
const draftCount = useAppSelector((state) => state.draft_statuses.size);
|
const draftCount = useAppSelector((state) => Object.keys(state.draft_statuses).length);
|
||||||
|
|
||||||
const restrictUnauth = instance.pleroma.metadata.restrict_unauthenticated;
|
const restrictUnauth = instance.pleroma.metadata.restrict_unauthenticated;
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,11 @@ import type { DraftStatus } from 'pl-fe/reducers/draft-statuses';
|
||||||
import type { RootState } from 'pl-fe/store';
|
import type { RootState } from 'pl-fe/store';
|
||||||
|
|
||||||
const buildPoll = (draftStatus: DraftStatus) => {
|
const buildPoll = (draftStatus: DraftStatus) => {
|
||||||
if (draftStatus.hasIn(['poll', 'options'])) {
|
if (draftStatus.poll?.options) {
|
||||||
return {
|
return {
|
||||||
...draftStatus.poll!.toJS(),
|
...draftStatus.poll,
|
||||||
id: `${draftStatus.draft_id}-poll`,
|
id: `${draftStatus.draft_id}-poll`,
|
||||||
options: draftStatus.poll!.get('options').map((title: string) => ({ title })).toArray(),
|
options: draftStatus.poll.options.map((title: string) => ({ title })).toArray(),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -31,7 +31,7 @@ const DraftStatuses = () => {
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
|
listClassName='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
|
||||||
>
|
>
|
||||||
{drafts.toOrderedSet().reverse().map((draft) => <DraftStatus key={draft.draft_id} draftStatus={draft} />)}
|
{Object.values(drafts).toReversed().map((draft) => <DraftStatus key={draft.draft_id} draftStatus={draft} />)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,63 +1,64 @@
|
||||||
import { List as ImmutableList, Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord, fromJS } from 'immutable';
|
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 { COMPOSE_SUBMIT_SUCCESS, type ComposeAction } from 'pl-fe/actions/compose';
|
||||||
import { DRAFT_STATUSES_FETCH_SUCCESS, PERSIST_DRAFT_STATUS, CANCEL_DRAFT_STATUS } from 'pl-fe/actions/draft-statuses';
|
import { DRAFT_STATUSES_FETCH_SUCCESS, PERSIST_DRAFT_STATUS, CANCEL_DRAFT_STATUS, DraftStatusesAction } from 'pl-fe/actions/draft-statuses';
|
||||||
|
import { filteredArray } from 'pl-fe/schemas/utils';
|
||||||
import KVStore from 'pl-fe/storage/kv-store';
|
import KVStore from 'pl-fe/storage/kv-store';
|
||||||
|
|
||||||
import type { MediaAttachment } from 'pl-api';
|
|
||||||
import type { StatusVisibility } from 'pl-fe/normalizers/status';
|
|
||||||
import type { APIEntity } from 'pl-fe/types/entities';
|
import type { APIEntity } from 'pl-fe/types/entities';
|
||||||
import type { AnyAction } from 'redux';
|
|
||||||
|
|
||||||
const DraftStatusRecord = ImmutableRecord({
|
const draftStatusSchema = v.object({
|
||||||
content_type: 'text/plain',
|
content_type: v.fallback(v.string(), 'text/plain'),
|
||||||
draft_id: '',
|
draft_id: v.string(),
|
||||||
editorState: null as string | null,
|
editorState: v.fallback(v.nullable(v.string()), null),
|
||||||
group_id: null as string | null,
|
group_id: v.fallback(v.nullable(v.string()), null),
|
||||||
in_reply_to: null as string | null,
|
in_reply_to: v.fallback(v.nullable(v.string()), null),
|
||||||
media_attachments: ImmutableList<MediaAttachment>(),
|
media_attachments: filteredArray(mediaAttachmentSchema),
|
||||||
poll: null as ImmutableMap<string, any> | null,
|
poll: v.fallback(v.nullable(v.record(v.string(), v.any())), null),
|
||||||
privacy: 'public' as StatusVisibility,
|
privacy: v.fallback(v.string(), 'public'),
|
||||||
quote: null as string | null,
|
quote: v.fallback(v.nullable(v.string()), null),
|
||||||
schedule: null as Date | null,
|
schedule: v.fallback(v.nullable(v.string()), null),
|
||||||
sensitive: false,
|
sensitive: v.fallback(v.boolean(), false),
|
||||||
spoiler: false,
|
spoiler: v.fallback(v.boolean(), false),
|
||||||
spoiler_text: '',
|
spoiler_text: v.fallback(v.string(), ''),
|
||||||
text: '',
|
text: v.fallback(v.string(), ''),
|
||||||
to: ImmutableOrderedSet<string>(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type DraftStatus = ReturnType<typeof DraftStatusRecord>;
|
type DraftStatus = v.InferOutput<typeof draftStatusSchema>;
|
||||||
type State = ImmutableMap<string, DraftStatus>;
|
type State = Record<string, DraftStatus>;
|
||||||
|
|
||||||
const initialState: State = ImmutableMap();
|
const initialState: State = {};
|
||||||
|
|
||||||
const importStatus = (state: State, status: APIEntity) =>
|
const importStatus = (state: State, status: APIEntity) => {
|
||||||
state.set(status.draft_id, DraftStatusRecord(ImmutableMap(fromJS(status))));
|
state[status.draft_id] = v.parse(draftStatusSchema, status);
|
||||||
|
};
|
||||||
|
|
||||||
const importStatuses = (state: State, statuses: APIEntity[]) =>
|
const importStatuses = (state: State, statuses: APIEntity[]) => {
|
||||||
state.withMutations(mutable => Object.values(statuses || {}).forEach(status => importStatus(mutable, status)));
|
Object.values(statuses || {}).forEach(status => importStatus(state, status));
|
||||||
|
};
|
||||||
|
|
||||||
const deleteStatus = (state: State, statusId: string) => {
|
const deleteStatus = (state: State, statusId: string) => {
|
||||||
if (statusId) return state.delete(statusId);
|
if (statusId) delete state[statusId];
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const persistState = (state: State, accountUrl: string) => {
|
const persistState = (state: State, accountUrl: string) => {
|
||||||
KVStore.setItem(`drafts:${accountUrl}`, state.toJS());
|
KVStore.setItem(`drafts:${accountUrl}`, state);
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheduled_statuses = (state: State = initialState, action: AnyAction | ComposeAction) => {
|
const scheduled_statuses = (state: State = initialState, action: DraftStatusesAction | ComposeAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case DRAFT_STATUSES_FETCH_SUCCESS:
|
case DRAFT_STATUSES_FETCH_SUCCESS:
|
||||||
return importStatuses(state, action.statuses);
|
return create(state, (draft) => importStatuses(draft, action.statuses));
|
||||||
case PERSIST_DRAFT_STATUS:
|
case PERSIST_DRAFT_STATUS:
|
||||||
return persistState(importStatus(state, action.status), action.accountUrl);
|
return persistState(create(state, (draft) => importStatus(draft, action.status)), action.accountUrl);
|
||||||
case CANCEL_DRAFT_STATUS:
|
case CANCEL_DRAFT_STATUS:
|
||||||
return persistState(deleteStatus(state, action.statusId), action.accountUrl);
|
return persistState(create(state, (draft) => deleteStatus(draft, action.statusId)), action.accountUrl);
|
||||||
case COMPOSE_SUBMIT_SUCCESS:
|
case COMPOSE_SUBMIT_SUCCESS:
|
||||||
return persistState(deleteStatus(state, action.draftId), action.accountUrl);
|
return action.draftId ? persistState(create(state, (draft) => deleteStatus(draft, action.draftId!)), action.accountUrl) : state;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue