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