Implement editing collection settings and deleting collections (#37658)
This commit is contained in:
@@ -9,7 +9,7 @@ import type {
|
||||
ApiWrappedCollectionJSON,
|
||||
ApiCollectionWithAccountsJSON,
|
||||
ApiCreateCollectionPayload,
|
||||
ApiPatchCollectionPayload,
|
||||
ApiUpdateCollectionPayload,
|
||||
ApiCollectionsJSON,
|
||||
} from '../api_types/collections';
|
||||
|
||||
@@ -19,7 +19,7 @@ export const apiCreateCollection = (collection: ApiCreateCollectionPayload) =>
|
||||
export const apiUpdateCollection = ({
|
||||
id,
|
||||
...collection
|
||||
}: ApiPatchCollectionPayload) =>
|
||||
}: ApiUpdateCollectionPayload) =>
|
||||
apiRequestPut<ApiWrappedCollectionJSON>(
|
||||
`v1_alpha/collections/${id}`,
|
||||
collection,
|
||||
@@ -29,7 +29,7 @@ export const apiDeleteCollection = (collectionId: string) =>
|
||||
apiRequestDelete(`v1_alpha/collections/${collectionId}`);
|
||||
|
||||
export const apiGetCollection = (collectionId: string) =>
|
||||
apiRequestGet<ApiCollectionWithAccountsJSON[]>(
|
||||
apiRequestGet<ApiCollectionWithAccountsJSON>(
|
||||
`v1_alpha/collections/${collectionId}`,
|
||||
);
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ type CommonPayloadFields = Pick<
|
||||
tag_name?: string;
|
||||
};
|
||||
|
||||
export interface ApiPatchCollectionPayload extends Partial<CommonPayloadFields> {
|
||||
export interface ApiUpdateCollectionPayload extends Partial<CommonPayloadFields> {
|
||||
id: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import type {
|
||||
ApiCollectionJSON,
|
||||
ApiCreateCollectionPayload,
|
||||
ApiUpdateCollectionPayload,
|
||||
} from 'mastodon/api_types/collections';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
@@ -18,7 +19,11 @@ import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { TextAreaField, ToggleField } from 'mastodon/components/form_fields';
|
||||
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { createCollection } from 'mastodon/reducers/slices/collections';
|
||||
import {
|
||||
createCollection,
|
||||
fetchCollection,
|
||||
updateCollection,
|
||||
} from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -83,16 +88,18 @@ const CollectionSettings: React.FC<{
|
||||
e.preventDefault();
|
||||
|
||||
if (id) {
|
||||
// void dispatch(
|
||||
// updateList({
|
||||
// id,
|
||||
// title,
|
||||
// exclusive,
|
||||
// replies_policy: repliesPolicy,
|
||||
// }),
|
||||
// ).then(() => {
|
||||
// return '';
|
||||
// });
|
||||
const payload: ApiUpdateCollectionPayload = {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
tag_name: topic,
|
||||
discoverable,
|
||||
sensitive,
|
||||
};
|
||||
|
||||
void dispatch(updateCollection({ payload })).then(() => {
|
||||
history.push(`/collections`);
|
||||
});
|
||||
} else {
|
||||
const payload: ApiCreateCollectionPayload = {
|
||||
name,
|
||||
@@ -103,6 +110,7 @@ const CollectionSettings: React.FC<{
|
||||
if (topic) {
|
||||
payload.tag_name = topic;
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
createCollection({
|
||||
payload,
|
||||
@@ -114,8 +122,6 @@ const CollectionSettings: React.FC<{
|
||||
);
|
||||
history.push(`/collections`);
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -198,7 +204,7 @@ const CollectionSettings: React.FC<{
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='collections.mark_as_sensitive_hint'
|
||||
defaultMessage="Hides the collection's description and accounts behind a content warning. The title will still be visible."
|
||||
defaultMessage="Hides the collection's description and accounts behind a content warning. The collection name will still be visible."
|
||||
/>
|
||||
}
|
||||
checked={sensitive}
|
||||
@@ -232,9 +238,9 @@ export const CollectionEditorPage: React.FC<{
|
||||
const isLoading = isEditMode && !collection;
|
||||
|
||||
useEffect(() => {
|
||||
// if (id) {
|
||||
// dispatch(fetchCollection(id));
|
||||
// }
|
||||
if (id) {
|
||||
void dispatch(fetchCollection({ collectionId: id }));
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
|
||||
const pageTitle = intl.formatMessage(id ? messages.edit : messages.create);
|
||||
|
||||
@@ -48,13 +48,14 @@ const ListItem: React.FC<{
|
||||
const handleDeleteClick = useCallback(() => {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'CONFIRM_DELETE_LIST',
|
||||
modalType: 'CONFIRM_DELETE_COLLECTION',
|
||||
modalProps: {
|
||||
listId: id,
|
||||
name,
|
||||
id,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}, [dispatch, id]);
|
||||
}, [dispatch, id, name]);
|
||||
|
||||
const menu = useMemo(
|
||||
() => [
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import { deleteCollection } from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteListTitle: {
|
||||
id: 'confirmations.delete_collection.title',
|
||||
defaultMessage: 'Delete "{name}"?',
|
||||
},
|
||||
deleteListMessage: {
|
||||
id: 'confirmations.delete_collection.message',
|
||||
defaultMessage: 'This action cannot be undone.',
|
||||
},
|
||||
deleteListConfirm: {
|
||||
id: 'confirmations.delete_collection.confirm',
|
||||
defaultMessage: 'Delete',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmDeleteCollectionModal: React.FC<
|
||||
{
|
||||
id: string;
|
||||
name: string;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({ id, name, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
void dispatch(deleteCollection({ collectionId: id }));
|
||||
history.push('/collections');
|
||||
}, [dispatch, history, id]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.deleteListTitle, {
|
||||
name,
|
||||
})}
|
||||
message={intl.formatMessage(messages.deleteListMessage)}
|
||||
confirm={intl.formatMessage(messages.deleteListConfirm)}
|
||||
onConfirm={onConfirm}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
export { ConfirmationModal } from './confirmation_modal';
|
||||
export { ConfirmDeleteStatusModal } from './delete_status';
|
||||
export { ConfirmDeleteListModal } from './delete_list';
|
||||
export { ConfirmDeleteCollectionModal } from './delete_collection';
|
||||
export {
|
||||
ConfirmReplyModal,
|
||||
ConfirmEditStatusModal,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
ConfirmationModal,
|
||||
ConfirmDeleteStatusModal,
|
||||
ConfirmDeleteListModal,
|
||||
ConfirmDeleteCollectionModal,
|
||||
ConfirmReplyModal,
|
||||
ConfirmEditStatusModal,
|
||||
ConfirmUnblockModal,
|
||||
@@ -57,6 +58,7 @@ export const MODAL_COMPONENTS = {
|
||||
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
|
||||
'CONFIRM_DELETE_STATUS': () => Promise.resolve({ default: ConfirmDeleteStatusModal }),
|
||||
'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }),
|
||||
'CONFIRM_DELETE_COLLECTION': () => Promise.resolve({ default: ConfirmDeleteCollectionModal }),
|
||||
'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }),
|
||||
'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }),
|
||||
'CONFIRM_UNBLOCK': () => Promise.resolve({ default: ConfirmUnblockModal }),
|
||||
|
||||
@@ -221,7 +221,7 @@
|
||||
"collections.description_length_hint": "100 characters limit",
|
||||
"collections.error_loading_collections": "There was an error when trying to load your collections.",
|
||||
"collections.mark_as_sensitive": "Mark as sensitive",
|
||||
"collections.mark_as_sensitive_hint": "Hides the collection's description and accounts behind a content warning. The title will still be visible.",
|
||||
"collections.mark_as_sensitive_hint": "Hides the collection's description and accounts behind a content warning. The collection name will still be visible.",
|
||||
"collections.name_length_hint": "100 characters limit",
|
||||
"collections.no_collections_yet": "No collections yet.",
|
||||
"collections.topic_hint": "Add a hashtag that helps others understand the main topic of this collection.",
|
||||
@@ -291,6 +291,9 @@
|
||||
"confirmations.delete.confirm": "Delete",
|
||||
"confirmations.delete.message": "Are you sure you want to delete this post?",
|
||||
"confirmations.delete.title": "Delete post?",
|
||||
"confirmations.delete_collection.confirm": "Delete",
|
||||
"confirmations.delete_collection.message": "This action cannot be undone.",
|
||||
"confirmations.delete_collection.title": "Delete \"{name}\"?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.delete_list.title": "Delete list?",
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { importFetchedAccounts } from '@/mastodon/actions/importer';
|
||||
import {
|
||||
apiCreateCollection,
|
||||
apiGetAccountCollections,
|
||||
// apiGetCollection,
|
||||
apiUpdateCollection,
|
||||
apiGetCollection,
|
||||
apiDeleteCollection,
|
||||
} from '@/mastodon/api/collections';
|
||||
import type {
|
||||
ApiCollectionJSON,
|
||||
ApiCreateCollectionPayload,
|
||||
ApiUpdateCollectionPayload,
|
||||
} from '@/mastodon/api_types/collections';
|
||||
import {
|
||||
createAppSelector,
|
||||
@@ -59,10 +63,11 @@ const collectionSlice = createSlice({
|
||||
};
|
||||
});
|
||||
|
||||
builder.addCase(fetchAccountCollections.fulfilled, (state, actions) => {
|
||||
const { collections } = actions.payload;
|
||||
builder.addCase(fetchAccountCollections.fulfilled, (state, action) => {
|
||||
const { collections } = action.payload;
|
||||
|
||||
const collectionsMap: Record<string, ApiCollectionJSON> = {};
|
||||
const collectionsMap: Record<string, ApiCollectionJSON> =
|
||||
state.collections;
|
||||
const collectionIds: string[] = [];
|
||||
|
||||
collections.forEach((collection) => {
|
||||
@@ -72,12 +77,40 @@ const collectionSlice = createSlice({
|
||||
});
|
||||
|
||||
state.collections = collectionsMap;
|
||||
state.accountCollections[actions.meta.arg.accountId] = {
|
||||
state.accountCollections[action.meta.arg.accountId] = {
|
||||
collectionIds,
|
||||
status: 'idle',
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetching a single collection
|
||||
*/
|
||||
|
||||
builder.addCase(fetchCollection.fulfilled, (state, action) => {
|
||||
const { collection } = action.payload;
|
||||
state.collections[collection.id] = collection;
|
||||
});
|
||||
|
||||
/**
|
||||
* Updating a collection
|
||||
*/
|
||||
|
||||
builder.addCase(updateCollection.fulfilled, (state, action) => {
|
||||
const { collection } = action.payload;
|
||||
state.collections[collection.id] = collection;
|
||||
});
|
||||
|
||||
/**
|
||||
* Deleting a collection
|
||||
*/
|
||||
|
||||
builder.addCase(deleteCollection.fulfilled, (state, action) => {
|
||||
const { collectionId } = action.meta.arg;
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete state.collections[collectionId];
|
||||
});
|
||||
|
||||
/**
|
||||
* Creating a collection
|
||||
*/
|
||||
@@ -86,6 +119,7 @@ const collectionSlice = createSlice({
|
||||
const { collection } = actions.payload;
|
||||
|
||||
state.collections[collection.id] = collection;
|
||||
|
||||
if (state.accountCollections[collection.account_id]) {
|
||||
state.accountCollections[collection.account_id]?.collectionIds.unshift(
|
||||
collection.id,
|
||||
@@ -105,13 +139,17 @@ export const fetchAccountCollections = createDataLoadingThunk(
|
||||
({ accountId }: { accountId: string }) => apiGetAccountCollections(accountId),
|
||||
);
|
||||
|
||||
// To be added soon…
|
||||
//
|
||||
// export const fetchCollection = createDataLoadingThunk(
|
||||
// `${collectionSlice.name}/fetchCollection`,
|
||||
// ({ collectionId }: { collectionId: string }) =>
|
||||
// apiGetCollection(collectionId),
|
||||
// );
|
||||
export const fetchCollection = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/fetchCollection`,
|
||||
({ collectionId }: { collectionId: string }) =>
|
||||
apiGetCollection(collectionId),
|
||||
(payload, { dispatch }) => {
|
||||
if (payload.accounts.length > 0) {
|
||||
dispatch(importFetchedAccounts(payload.accounts));
|
||||
}
|
||||
return payload;
|
||||
},
|
||||
);
|
||||
|
||||
export const createCollection = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/createCollection`,
|
||||
@@ -119,6 +157,18 @@ export const createCollection = createDataLoadingThunk(
|
||||
apiCreateCollection(payload),
|
||||
);
|
||||
|
||||
export const updateCollection = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/updateCollection`,
|
||||
({ payload }: { payload: ApiUpdateCollectionPayload }) =>
|
||||
apiUpdateCollection(payload),
|
||||
);
|
||||
|
||||
export const deleteCollection = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/deleteCollection`,
|
||||
({ collectionId }: { collectionId: string }) =>
|
||||
apiDeleteCollection(collectionId),
|
||||
);
|
||||
|
||||
export const collections = collectionSlice.reducer;
|
||||
|
||||
/**
|
||||
@@ -136,7 +186,7 @@ export const selectMyCollections = createAppSelector(
|
||||
(state) => state.collections.accountCollections,
|
||||
(state) => state.collections.collections,
|
||||
],
|
||||
(me, collectionsByAccountId, collectionsById) => {
|
||||
(me, collectionsByAccountId, collectionsMap) => {
|
||||
const myCollectionsQuery = collectionsByAccountId[me];
|
||||
|
||||
if (!myCollectionsQuery) {
|
||||
@@ -151,7 +201,7 @@ export const selectMyCollections = createAppSelector(
|
||||
return {
|
||||
status,
|
||||
collections: collectionIds
|
||||
.map((id) => collectionsById[id])
|
||||
.map((id) => collectionsMap[id])
|
||||
.filter((c) => !!c),
|
||||
} satisfies AccountCollectionQuery;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user