Allow moderators to delete comment (#235)

## Ticket
223 Add ability for delegated moderators to delete comments

## Changes
- Refactored doCommentAbandon's signature so we don't end up converting between "uri" and "Claim" several times and needing to lookup redux when the client can already provide us the exact values that we need.

- Pass the new moderator fields to the API.

- Remove the need to call 'makeSelectChannelPermUrlForClaimUri' since it's a simple field query when we already have the claim.
This commit is contained in:
infinite-persistence 2021-11-09 01:22:40 +08:00 committed by GitHub
parent 9138e508c6
commit cfd67b1c8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 47 deletions

View file

@ -169,8 +169,10 @@ declare type CommentAbandonParams = {
comment_id: string,
creator_channel_id?: string,
creator_channel_name?: string,
channel_id?: string,
hexdata?: string,
signature?: string,
signing_ts?: string,
mod_channel_id?: string,
mod_channel_name?: string,
};
declare type CommentCreateParams = {

View file

@ -4,11 +4,7 @@ import { doCommentPin, doCommentModAddDelegate } from 'redux/actions/comments';
import { doOpenModal } from 'redux/actions/app';
import { doSetPlayingUri } from 'redux/actions/content';
import { doToast } from 'redux/actions/notifications';
import {
makeSelectChannelPermUrlForClaimUri,
makeSelectClaimIsMine,
makeSelectClaimForUri,
} from 'redux/selectors/claims';
import { makeSelectClaimIsMine, makeSelectClaimForUri } from 'redux/selectors/claims';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { selectModerationDelegatorsById } from 'redux/selectors/comments';
import { selectPlayingUri } from 'redux/selectors/content';
@ -17,7 +13,6 @@ import CommentMenuList from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
activeChannelClaim: selectActiveChannelClaim(state),
playingUri: selectPlayingUri(state),
moderationDelegatorsById: selectModerationDelegatorsById(state),

View file

@ -24,7 +24,6 @@ type Props = {
// --- select ---
claim: ?Claim,
claimIsMine: boolean,
contentChannelPermanentUrl: any,
activeChannelClaim: ?ChannelClaim,
playingUri: ?PlayingUri,
moderationDelegatorsById: { [string]: { global: boolean, delegators: { name: string, claimId: string } } },
@ -47,7 +46,6 @@ function CommentMenuList(props: Props) {
commentIsMine,
commentId,
activeChannelClaim,
contentChannelPermanentUrl,
isTopLevel,
isPinned,
playingUri,
@ -71,6 +69,8 @@ function CommentMenuList(props: Props) {
} = useHistory();
const contentChannelClaim = getChannelFromClaim(claim);
const contentChannelPermanentUrl = contentChannelClaim && contentChannelClaim.permanent_url;
const activeModeratorInfo = activeChannelClaim && moderationDelegatorsById[activeChannelClaim.claim_id];
const activeChannelIsCreator = activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl;
const activeChannelIsAdmin = activeChannelClaim && activeModeratorInfo && activeModeratorInfo.global;
@ -84,10 +84,12 @@ function CommentMenuList(props: Props) {
if (playingUri && playingUri.source === 'comment') {
clearPlayingUri();
}
openModal(MODALS.CONFIRM_REMOVE_COMMENT, {
commentId,
commentIsMine,
contentChannelPermanentUrl,
deleterClaim: activeChannelClaim,
deleterIsModOrAdmin: activeChannelIsAdmin || activeChannelIsModerator,
creatorClaim: commentIsMine ? undefined : contentChannelClaim,
supportAmount,
setQuickReply,
});
@ -201,7 +203,8 @@ function CommentMenuList(props: Props) {
{!disableRemove &&
activeChannelClaim &&
(activeChannelClaim.permanent_url === authorUri ||
(activeChannelIsModerator ||
activeChannelClaim.permanent_url === authorUri ||
activeChannelClaim.permanent_url === contentChannelPermanentUrl) && (
<MenuItem className="comment__menu-option" onSelect={handleDeleteComment}>
<div className="menu__link">

View file

@ -3,9 +3,9 @@ import { doHideModal } from 'redux/actions/app';
import ModalRemoveComment from './view';
import { doCommentAbandon } from 'redux/actions/comments';
const perform = (dispatch) => ({
closeModal: () => dispatch(doHideModal()),
deleteComment: (commentId, creatorChannelUrl) => dispatch(doCommentAbandon(commentId, creatorChannelUrl)),
});
const perform = {
doHideModal,
doCommentAbandon,
};
export default connect(null, perform)(ModalRemoveComment);

View file

@ -6,27 +6,30 @@ import Card from 'component/common/card';
type Props = {
commentId: string, // sha256 digest identifying the comment
commentIsMine: boolean, // if this comment was signed by an owned channel
contentChannelPermanentUrl: any,
closeModal: () => void,
deleteComment: (string, ?string) => void,
deleterClaim: Claim,
deleterIsModOrAdmin?: boolean,
creatorClaim?: Claim,
supportAmount?: any,
setQuickReply: (any) => void,
// --- redux ---
doHideModal: () => void,
doCommentAbandon: (string, Claim, ?boolean, ?Claim) => void,
};
function ModalRemoveComment(props: Props) {
const {
commentId,
commentIsMine,
contentChannelPermanentUrl,
closeModal,
deleteComment,
deleterClaim,
deleterIsModOrAdmin,
creatorClaim,
supportAmount,
setQuickReply,
doHideModal,
doCommentAbandon,
} = props;
return (
<Modal isOpen contentLabel={__('Confirm Comment Deletion')} type="card" onAborted={closeModal}>
<Modal isOpen contentLabel={__('Confirm Comment Deletion')} type="card" onAborted={doHideModal}>
<Card
title={__('Remove Comment')}
body={
@ -46,12 +49,14 @@ function ModalRemoveComment(props: Props) {
button="primary"
label={__('Remove')}
onClick={() => {
closeModal();
deleteComment(commentId, commentIsMine ? undefined : contentChannelPermanentUrl);
if (setQuickReply) setQuickReply(undefined);
doHideModal();
doCommentAbandon(commentId, deleterClaim, deleterIsModOrAdmin, creatorClaim);
if (setQuickReply) {
setQuickReply(undefined);
}
}}
/>
<Button button="link" label={__('Cancel')} onClick={closeModal} />
<Button button="link" label={__('Cancel')} onClick={doHideModal} />
</div>
</>
}

View file

@ -690,33 +690,40 @@ export function doCommentPin(commentId: string, claimId: string, remove: boolean
};
}
export function doCommentAbandon(commentId: string, creatorChannelUri?: string) {
/**
* Deletes a comment in Commentron.
*
* @param commentId The comment ID to delete.
* @param deleterClaim The channel-claim of the person doing the deletion. Defaults to the active channel if not provided.
* @param deleterIsModOrAdmin Is the deleter a mod or admin for the content?
* @param creatorClaim The channel-claim for the content where the comment resides. Not required if the deleter owns the comment (i.e. deleting own comment).
* @returns {function(Dispatch): *}
*/
export function doCommentAbandon(
commentId: string,
deleterClaim?: Claim,
deleterIsModOrAdmin?: boolean,
creatorClaim?: Claim
) {
return async (dispatch: Dispatch, getState: GetState) => {
if (!deleterClaim) {
const state = getState();
const claim = creatorChannelUri ? selectClaimsByUri(state)[creatorChannelUri] : undefined;
const creatorChannelId = claim ? claim.claim_id : null;
const creatorChannelName = claim ? claim.name : null;
const activeChannelClaim = selectActiveChannelClaim(state);
deleterClaim = selectActiveChannelClaim(state);
}
dispatch({
type: ACTIONS.COMMENT_ABANDON_STARTED,
});
let commentIdSignature;
if (activeChannelClaim) {
try {
commentIdSignature = await Lbry.channel_sign({
channel_id: activeChannelClaim.claim_id,
hexdata: toHex(commentId),
});
} catch (e) {}
}
const commentIdSignature = await channelSignData(deleterClaim.claim_id, commentId);
return Comments.comment_abandon({
comment_id: commentId,
...(creatorChannelId ? { creator_channel_id: creatorChannelId } : {}),
...(creatorChannelName ? { creator_channel_name: creatorChannelName } : {}),
creator_channel_id: creatorClaim ? creatorClaim.claim_id : undefined,
creator_channel_name: creatorClaim ? creatorClaim.name : undefined,
...(commentIdSignature || {}),
mod_channel_id: deleterClaim && deleterIsModOrAdmin ? deleterClaim.claim_id : undefined,
mod_channel_name: deleterClaim && deleterIsModOrAdmin ? deleterClaim.name : undefined,
})
.then((result: CommentAbandonResponse) => {
// Comment may not be deleted if the signing channel can't be signed.