mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-05 17:31:12 +00:00
Add UI for revoking quote posts (#35689)
This commit is contained in:
parent
c8f263c419
commit
55a98580aa
|
@ -1,4 +1,8 @@
|
|||
import { apiReblog, apiUnreblog } from 'mastodon/api/interactions';
|
||||
import {
|
||||
apiReblog,
|
||||
apiUnreblog,
|
||||
apiRevokeQuote,
|
||||
} from 'mastodon/api/interactions';
|
||||
import type { StatusVisibility } from 'mastodon/models/status';
|
||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||
|
||||
|
@ -33,3 +37,19 @@ export const unreblog = createDataLoadingThunk(
|
|||
return discardLoadData;
|
||||
},
|
||||
);
|
||||
|
||||
export const revokeQuote = createDataLoadingThunk(
|
||||
'status/revoke_quote',
|
||||
({
|
||||
statusId,
|
||||
quotedStatusId,
|
||||
}: {
|
||||
statusId: string;
|
||||
quotedStatusId: string;
|
||||
}) => apiRevokeQuote(quotedStatusId, statusId),
|
||||
(data, { dispatch, discardLoadData }) => {
|
||||
dispatch(importFetchedStatus(data));
|
||||
|
||||
return discardLoadData;
|
||||
},
|
||||
);
|
||||
|
|
|
@ -8,3 +8,8 @@ export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
|
|||
|
||||
export const apiUnreblog = (statusId: string) =>
|
||||
apiRequestPost<Status>(`v1/statuses/${statusId}/unreblog`);
|
||||
|
||||
export const apiRevokeQuote = (quotedStatusId: string, statusId: string) =>
|
||||
apiRequestPost<Status>(
|
||||
`v1/statuses/${quotedStatusId}/quotes/${statusId}/revoke`,
|
||||
);
|
||||
|
|
|
@ -67,21 +67,28 @@ const messages = defineMessages({
|
|||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { status }) => ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
});
|
||||
const mapStateToProps = (state, { status }) => {
|
||||
const quotedStatusId = status.getIn(['quote', 'quoted_status']);
|
||||
return ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null,
|
||||
});
|
||||
};
|
||||
|
||||
class StatusActionBar extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
relationship: ImmutablePropTypes.record,
|
||||
quotedAccountId: ImmutablePropTypes.string,
|
||||
onReply: PropTypes.func,
|
||||
onFavourite: PropTypes.func,
|
||||
onReblog: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onRevokeQuote: PropTypes.func,
|
||||
onDirect: PropTypes.func,
|
||||
onMention: PropTypes.func,
|
||||
onMute: PropTypes.func,
|
||||
|
@ -110,6 +117,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
updateOnProps = [
|
||||
'status',
|
||||
'relationship',
|
||||
'quotedAccountId',
|
||||
'withDismiss',
|
||||
];
|
||||
|
||||
|
@ -190,6 +198,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleRevokeQuoteClick = () => {
|
||||
this.props.onRevokeQuote(this.props.status);
|
||||
}
|
||||
|
||||
handleBlockClick = () => {
|
||||
const { status, relationship, onBlock, onUnblock } = this.props;
|
||||
const account = status.get('account');
|
||||
|
@ -241,7 +253,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||
const { status, relationship, quotedAccountId, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||
const { signedIn, permissions } = this.props.identity;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
|
@ -291,6 +303,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
|
||||
if (quotedAccountId === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
||||
}
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
||||
} else {
|
||||
|
|
|
@ -111,6 +111,10 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onRevokeQuote (status) {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }}));
|
||||
},
|
||||
|
||||
onEdit (status) {
|
||||
dispatch((_, getState) => {
|
||||
let state = getState();
|
||||
|
|
|
@ -61,22 +61,29 @@ const messages = defineMessages({
|
|||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { status }) => ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
});
|
||||
const mapStateToProps = (state, { status }) => {
|
||||
const quotedStatusId = status.getIn(['quote', 'quoted_status']);
|
||||
return ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null,
|
||||
});
|
||||
};
|
||||
|
||||
class ActionBar extends PureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
relationship: ImmutablePropTypes.record,
|
||||
quotedAccountId: ImmutablePropTypes.string,
|
||||
onReply: PropTypes.func.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onFavourite: PropTypes.func.isRequired,
|
||||
onBookmark: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onRevokeQuote: PropTypes.func,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onDirect: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
|
@ -113,6 +120,10 @@ class ActionBar extends PureComponent {
|
|||
this.props.onDelete(this.props.status);
|
||||
};
|
||||
|
||||
handleRevokeQuoteClick = () => {
|
||||
this.props.onRevokeQuote(this.props.status);
|
||||
}
|
||||
|
||||
handleRedraftClick = () => {
|
||||
this.props.onDelete(this.props.status, true);
|
||||
};
|
||||
|
@ -193,7 +204,7 @@ class ActionBar extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { status, relationship, intl } = this.props;
|
||||
const { status, relationship, quotedAccountId, intl } = this.props;
|
||||
const { signedIn, permissions } = this.props.identity;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
|
@ -237,6 +248,10 @@ class ActionBar extends PureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push(null);
|
||||
|
||||
if (quotedAccountId === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true });
|
||||
}
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
||||
} else {
|
||||
|
|
|
@ -259,6 +259,12 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleRevokeQuoteClick = (status) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }}));
|
||||
};
|
||||
|
||||
handleEditClick = (status) => {
|
||||
const { dispatch, askReplyConfirmation } = this.props;
|
||||
|
||||
|
@ -635,6 +641,7 @@ class Status extends ImmutablePureComponent {
|
|||
onReblog={this.handleReblogClick}
|
||||
onBookmark={this.handleBookmarkClick}
|
||||
onDelete={this.handleDeleteClick}
|
||||
onRevokeQuote={this.handleRevokeQuoteClick}
|
||||
onEdit={this.handleEditClick}
|
||||
onDirect={this.handleDirectClick}
|
||||
onMention={this.handleMentionClick}
|
||||
|
|
|
@ -10,3 +10,4 @@ export { ConfirmClearNotificationsModal } from './clear_notifications';
|
|||
export { ConfirmLogOutModal } from './log_out';
|
||||
export { ConfirmFollowToListModal } from './follow_to_list';
|
||||
export { ConfirmMissingAltTextModal } from './missing_alt_text';
|
||||
export { ConfirmRevokeQuoteModal } from './revoke_quote';
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { revokeQuote } from 'mastodon/actions/interactions_typed';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
revokeQuoteTitle: {
|
||||
id: 'confirmations.revoke_quote.title',
|
||||
defaultMessage: 'Remove post?',
|
||||
},
|
||||
revokeQuoteMessage: {
|
||||
id: 'confirmations.revoke_quote.message',
|
||||
defaultMessage: 'This action cannot be undone.',
|
||||
},
|
||||
revokeQuoteConfirm: {
|
||||
id: 'confirmations.revoke_quote.confirm',
|
||||
defaultMessage: 'Remove post',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmRevokeQuoteModal: React.FC<
|
||||
{
|
||||
statusId: string;
|
||||
quotedStatusId: string;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({ statusId, quotedStatusId, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
void dispatch(revokeQuote({ quotedStatusId, statusId }));
|
||||
}, [dispatch, statusId, quotedStatusId]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.revokeQuoteTitle)}
|
||||
message={intl.formatMessage(messages.revokeQuoteMessage)}
|
||||
confirm={intl.formatMessage(messages.revokeQuoteConfirm)}
|
||||
onConfirm={onConfirm}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -37,6 +37,7 @@ import {
|
|||
ConfirmLogOutModal,
|
||||
ConfirmFollowToListModal,
|
||||
ConfirmMissingAltTextModal,
|
||||
ConfirmRevokeQuoteModal,
|
||||
} from './confirmation_modals';
|
||||
import { ImageModal } from './image_modal';
|
||||
import MediaModal from './media_modal';
|
||||
|
@ -59,6 +60,7 @@ export const MODAL_COMPONENTS = {
|
|||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||
'MUTE': MuteModal,
|
||||
'BLOCK': BlockModal,
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
|
|
|
@ -245,6 +245,9 @@
|
|||
"confirmations.remove_from_followers.confirm": "Remove follower",
|
||||
"confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?",
|
||||
"confirmations.remove_from_followers.title": "Remove follower?",
|
||||
"confirmations.revoke_quote.confirm": "Remove post",
|
||||
"confirmations.revoke_quote.message": "This action cannot be undone.",
|
||||
"confirmations.revoke_quote.title": "Remove post?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||
"confirmations.unfollow.title": "Unfollow user?",
|
||||
|
@ -896,6 +899,7 @@
|
|||
"status.reply": "Reply",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.report": "Report @{name}",
|
||||
"status.revoke_quote": "Remove my post from @{name}’s post",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less_all": "Show less for all",
|
||||
|
|
Loading…
Reference in New Issue
Block a user