Add ability for owners to delete statuses from Group
This commit is contained in:
parent
b608095e84
commit
4bc92f3c27
5 changed files with 44 additions and 94 deletions
|
@ -4,7 +4,6 @@ import api, { getLinks } from '../api';
|
|||
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { importFetchedGroups, importFetchedAccounts } from './importer';
|
||||
import { deleteFromTimelines } from './timelines';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { GroupRole } from 'soapbox/reducers/group-memberships';
|
||||
|
@ -35,10 +34,6 @@ const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST';
|
|||
const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS';
|
||||
const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL';
|
||||
|
||||
const GROUP_DELETE_STATUS_REQUEST = 'GROUP_DELETE_STATUS_REQUEST';
|
||||
const GROUP_DELETE_STATUS_SUCCESS = 'GROUP_DELETE_STATUS_SUCCESS';
|
||||
const GROUP_DELETE_STATUS_FAIL = 'GROUP_DELETE_STATUS_FAIL';
|
||||
|
||||
const GROUP_KICK_REQUEST = 'GROUP_KICK_REQUEST';
|
||||
const GROUP_KICK_SUCCESS = 'GROUP_KICK_SUCCESS';
|
||||
const GROUP_KICK_FAIL = 'GROUP_KICK_FAIL';
|
||||
|
@ -206,36 +201,6 @@ const fetchGroupRelationshipsFail = (error: AxiosError) => ({
|
|||
skipNotFound: true,
|
||||
});
|
||||
|
||||
const groupDeleteStatus = (groupId: string, statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(groupDeleteStatusRequest(groupId, statusId));
|
||||
|
||||
return api(getState).delete(`/api/v1/groups/${groupId}/statuses/${statusId}`)
|
||||
.then(() => {
|
||||
dispatch(deleteFromTimelines(statusId));
|
||||
dispatch(groupDeleteStatusSuccess(groupId, statusId));
|
||||
}).catch(err => dispatch(groupDeleteStatusFail(groupId, statusId, err)));
|
||||
};
|
||||
|
||||
const groupDeleteStatusRequest = (groupId: string, statusId: string) => ({
|
||||
type: GROUP_DELETE_STATUS_REQUEST,
|
||||
groupId,
|
||||
statusId,
|
||||
});
|
||||
|
||||
const groupDeleteStatusSuccess = (groupId: string, statusId: string) => ({
|
||||
type: GROUP_DELETE_STATUS_SUCCESS,
|
||||
groupId,
|
||||
statusId,
|
||||
});
|
||||
|
||||
const groupDeleteStatusFail = (groupId: string, statusId: string, error: AxiosError) => ({
|
||||
type: GROUP_DELETE_STATUS_SUCCESS,
|
||||
groupId,
|
||||
statusId,
|
||||
error,
|
||||
});
|
||||
|
||||
const groupKick = (groupId: string, accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(groupKickRequest(groupId, accountId));
|
||||
|
@ -677,9 +642,6 @@ export {
|
|||
GROUP_RELATIONSHIPS_FETCH_REQUEST,
|
||||
GROUP_RELATIONSHIPS_FETCH_SUCCESS,
|
||||
GROUP_RELATIONSHIPS_FETCH_FAIL,
|
||||
GROUP_DELETE_STATUS_REQUEST,
|
||||
GROUP_DELETE_STATUS_SUCCESS,
|
||||
GROUP_DELETE_STATUS_FAIL,
|
||||
GROUP_KICK_REQUEST,
|
||||
GROUP_KICK_SUCCESS,
|
||||
GROUP_KICK_FAIL,
|
||||
|
@ -735,10 +697,6 @@ export {
|
|||
fetchGroupRelationshipsRequest,
|
||||
fetchGroupRelationshipsSuccess,
|
||||
fetchGroupRelationshipsFail,
|
||||
groupDeleteStatus,
|
||||
groupDeleteStatusRequest,
|
||||
groupDeleteStatusSuccess,
|
||||
groupDeleteStatusFail,
|
||||
groupKick,
|
||||
groupKickRequest,
|
||||
groupKickSuccess,
|
||||
|
|
20
app/soapbox/api/hooks/groups/useDeleteGroupStatus.ts
Normal file
20
app/soapbox/api/hooks/groups/useDeleteGroupStatus.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useDeleteEntity } from 'soapbox/entity-store/hooks';
|
||||
import { useApi } from 'soapbox/hooks';
|
||||
|
||||
import type { Group } from 'soapbox/schemas';
|
||||
|
||||
function useDeleteGroupStatus(group: Group, statusId: string) {
|
||||
const api = useApi();
|
||||
const { deleteEntity, isSubmitting } = useDeleteEntity(
|
||||
Entities.STATUSES,
|
||||
() => api.delete(`/api/v1/groups/${group.id}/statuses/${statusId}`),
|
||||
);
|
||||
|
||||
return {
|
||||
mutate: deleteEntity,
|
||||
isSubmitting,
|
||||
};
|
||||
}
|
||||
|
||||
export { useDeleteGroupStatus };
|
|
@ -94,7 +94,7 @@ const DropdownMenuItem = ({ index, item, onClick }: IDropdownMenuItem) => {
|
|||
>
|
||||
{item.icon && <Icon src={item.icon} className='mr-3 h-5 w-5 flex-none rtl:ml-3 rtl:mr-0' />}
|
||||
|
||||
<span className='truncate'>{item.text}</span>
|
||||
<span className='truncate font-medium'>{item.text}</span>
|
||||
|
||||
{item.count ? (
|
||||
<span className='ml-auto h-5 w-5 flex-none'>
|
||||
|
|
|
@ -7,18 +7,20 @@ import { blockAccount } from 'soapbox/actions/accounts';
|
|||
import { launchChat } from 'soapbox/actions/chats';
|
||||
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
|
||||
import { editEvent } from 'soapbox/actions/events';
|
||||
import { groupBlock, groupDeleteStatus, groupKick } from 'soapbox/actions/groups';
|
||||
import { toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog } from 'soapbox/actions/interactions';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { deleteStatus, editStatus, toggleMuteStatus } from 'soapbox/actions/statuses';
|
||||
import { deleteFromTimelines } from 'soapbox/actions/timelines';
|
||||
import { useDeleteGroupStatus } from 'soapbox/api/hooks/groups/useDeleteGroupStatus';
|
||||
import DropdownMenu from 'soapbox/components/dropdown-menu';
|
||||
import StatusActionButton from 'soapbox/components/status-action-button';
|
||||
import StatusReactionWrapper from 'soapbox/components/status-reaction-wrapper';
|
||||
import { HStack } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLocal, isRemote } from 'soapbox/utils/accounts';
|
||||
import copy from 'soapbox/utils/copy';
|
||||
|
@ -87,16 +89,7 @@ const messages = defineMessages({
|
|||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||
replies_disabled_group: { id: 'status.disabled_replies.group_membership', defaultMessage: 'Only group members can reply' },
|
||||
groupModDelete: { id: 'status.group_mod_delete', defaultMessage: 'Delete post from group' },
|
||||
groupModKick: { id: 'status.group_mod_kick', defaultMessage: 'Kick @{name} from group' },
|
||||
groupModBlock: { id: 'status.group_mod_block', defaultMessage: 'Block @{name} from group' },
|
||||
deleteFromGroupHeading: { id: 'confirmations.delete_from_group.heading', defaultMessage: 'Delete from group' },
|
||||
deleteFromGroupMessage: { id: 'confirmations.delete_from_group.message', defaultMessage: 'Are you sure you want to delete @{name}\'s post?' },
|
||||
kickFromGroupHeading: { id: 'confirmations.kick_from_group.heading', defaultMessage: 'Kick group member' },
|
||||
kickFromGroupMessage: { id: 'confirmations.kick_from_group.message', defaultMessage: 'Are you sure you want to kick @{name} from this group?' },
|
||||
kickFromGroupConfirm: { id: 'confirmations.kick_from_group.confirm', defaultMessage: 'Kick' },
|
||||
blockFromGroupHeading: { id: 'confirmations.block_from_group.heading', defaultMessage: 'Block group member' },
|
||||
blockFromGroupMessage: { id: 'confirmations.block_from_group.message', defaultMessage: 'Are you sure you want to block @{name} from interacting with this group?' },
|
||||
blockFromGroupConfirm: { id: 'confirmations.block_from_group.confirm', defaultMessage: 'Block' },
|
||||
});
|
||||
|
||||
interface IStatusActionBar {
|
||||
|
@ -121,6 +114,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
const features = useFeatures();
|
||||
const settings = useSettings();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const deleteGroupStatus = useDeleteGroupStatus(status?.group as Group, status.id);
|
||||
|
||||
const { allowedEmoji } = soapboxConfig;
|
||||
|
||||
|
@ -315,29 +309,13 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
heading: intl.formatMessage(messages.deleteHeading),
|
||||
message: intl.formatMessage(messages.deleteFromGroupMessage, { name: <strong className='break-words'>{account.username}</strong> }),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => dispatch(groupDeleteStatus((status.group as Group).id, status.id)),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleKickFromGroup: React.EventHandler<React.MouseEvent> = () => {
|
||||
const account = status.account as Account;
|
||||
|
||||
dispatch(openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.kickFromGroupHeading),
|
||||
message: intl.formatMessage(messages.kickFromGroupMessage, { name: <strong className='break-words'>{account.username}</strong> }),
|
||||
confirm: intl.formatMessage(messages.kickFromGroupConfirm),
|
||||
onConfirm: () => dispatch(groupKick((status.group as Group).id, account.id)),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleBlockFromGroup: React.EventHandler<React.MouseEvent> = () => {
|
||||
const account = status.account as Account;
|
||||
|
||||
dispatch(openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.blockFromGroupHeading),
|
||||
message: intl.formatMessage(messages.blockFromGroupMessage, { name: <strong className='break-words'>{account.username}</strong> }),
|
||||
confirm: intl.formatMessage(messages.blockFromGroupConfirm),
|
||||
onConfirm: () => dispatch(groupBlock((status.group as Group).id, account.id)),
|
||||
onConfirm: () => {
|
||||
deleteGroupStatus.mutate(status.id, {
|
||||
onSuccess() {
|
||||
dispatch(deleteFromTimelines(status.id));
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -362,7 +340,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
menu.push({
|
||||
text: intl.formatMessage(messages.copy),
|
||||
action: handleCopy,
|
||||
icon: require('@tabler/icons/link.svg'),
|
||||
icon: require('@tabler/icons/clipboard-copy.svg'),
|
||||
});
|
||||
|
||||
if (features.embeds && isLocal(account)) {
|
||||
|
@ -466,7 +444,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
menu.push({
|
||||
text: intl.formatMessage(messages.mute, { name: username }),
|
||||
action: handleMuteClick,
|
||||
icon: require('@tabler/icons/circle-x.svg'),
|
||||
icon: require('@tabler/icons/volume-3.svg'),
|
||||
});
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.block, { name: username }),
|
||||
|
@ -480,23 +458,17 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
});
|
||||
}
|
||||
|
||||
if (status.group && groupRelationship?.role && ['admin', 'moderator'].includes(groupRelationship.role)) {
|
||||
if (status.group &&
|
||||
groupRelationship?.role &&
|
||||
[GroupRoles.OWNER].includes(groupRelationship.role) &&
|
||||
!ownAccount
|
||||
) {
|
||||
menu.push(null);
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.groupModDelete),
|
||||
action: handleDeleteFromGroup,
|
||||
icon: require('@tabler/icons/trash.svg'),
|
||||
});
|
||||
// TODO: figure out when an account is not in the group anymore
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.groupModKick, { name: account.get('username') }),
|
||||
action: handleKickFromGroup,
|
||||
icon: require('@tabler/icons/user-minus.svg'),
|
||||
});
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.groupModBlock, { name: account.get('username') }),
|
||||
action: handleBlockFromGroup,
|
||||
icon: require('@tabler/icons/ban.svg'),
|
||||
destructive: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1440,7 +1440,7 @@
|
|||
"status.cancel_reblog_private": "Un-repost",
|
||||
"status.cannot_reblog": "This post cannot be reposted",
|
||||
"status.chat": "Chat with @{name}",
|
||||
"status.copy": "Copy link to post",
|
||||
"status.copy": "Copy Link to Post",
|
||||
"status.delete": "Delete",
|
||||
"status.detailed_status": "Detailed conversation view",
|
||||
"status.direct": "Direct message @{name}",
|
||||
|
@ -1462,8 +1462,8 @@
|
|||
"status.load_more": "Load more",
|
||||
"status.mention": "Mention @{name}",
|
||||
"status.more": "More",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this post",
|
||||
"status.mute_conversation": "Mute Conversation",
|
||||
"status.open": "Show Post Details",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned post",
|
||||
"status.quote": "Quote post",
|
||||
|
@ -1499,7 +1499,7 @@
|
|||
"status.translated_from_with": "Translated from {lang} using {provider}",
|
||||
"status.unbookmark": "Remove bookmark",
|
||||
"status.unbookmarked": "Bookmark removed.",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unmute_conversation": "Unmute Conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status_list.queue_label": "Click to see {count} new {count, plural, one {post} other {posts}}",
|
||||
"statuses.quote_tombstone": "Post is unavailable.",
|
||||
|
|
Loading…
Reference in a new issue