Skip to content

Commit

Permalink
Comment Moderation - time based bans
Browse files Browse the repository at this point in the history
## Issue
6712 Comment Moderation - time based bans

## Approach
- Consolidated the 3 types of blocking buttons in the comment content menu (i.e. Block, Moderator Block, Admin Block) into 1 regular Block button.
- Show a modal when Block is clicked.
    - Let user choose the blocklist.
    - Let user choose the timeout duration (this PR's impetus).
  • Loading branch information
infinite-persistence committed Sep 2, 2021
1 parent 049fb28 commit a05ccdd
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 88 deletions.
38 changes: 37 additions & 1 deletion flow-typed/Comment.js
Expand Up @@ -189,7 +189,43 @@ declare type SuperListResponse = {
has_hidden_comments: boolean,
};

declare type ModerationBlockParams = {};
declare type ModerationBlockParams = {
// Publisher, Moderator, or Commentron Admin
mod_channel_id: string,
mod_channel_name: string,
// Offender being blocked
blocked_channel_id: string,
blocked_channel_name: string,
// Creator that Moderator is delegated from. Used for delegated moderation
creator_channel_id?: string,
creator_channel_name?: string,
// Blocks identity from comment universally, requires Admin rights on commentron instance
block_all?: boolean,
time_out_hrs?: number,
// If true will delete all comments of the offender, requires Admin rights on commentron for universal delete
delete_all?: boolean,
// The usual signature stuff
signature: string,
signing_ts: string,
};

declare type ModerationBlockResponse = {
deleted_comment_ids: Array<string>,
banned_channel_id: string,
all_blocked: boolean,
banned_from: string,
};

declare type BlockedListArgs = {
// Publisher, Moderator or Commentron Admin
mod_channel_id: string,
mod_channel_name: string,
// Creator that Moderator is delegated from. Used for delegated moderation
creator_channel_id?: string,
creator_channel_name?: string,
signature: string,
signing_ts: string,
};

declare type ModerationAddDelegateParams = {
mod_channel_id: string,
Expand Down
2 changes: 1 addition & 1 deletion ui/comments.js
Expand Up @@ -14,7 +14,7 @@ const Comments = {

moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params),
moderation_unblock: (params: ModerationBlockParams) => fetchCommentsApi('moderation.UnBlock', params),
moderation_block_list: (params: ModerationBlockParams) => fetchCommentsApi('moderation.BlockedList', params),
moderation_block_list: (params: BlockedListArgs) => fetchCommentsApi('moderation.BlockedList', params),
moderation_add_delegate: (params: ModerationAddDelegateParams) => fetchCommentsApi('moderation.AddDelegate', params),
moderation_remove_delegate: (params: ModerationRemoveDelegateParams) =>
fetchCommentsApi('moderation.RemoveDelegate', params),
Expand Down
4 changes: 2 additions & 2 deletions ui/component/channelBlockButton/view.jsx
Expand Up @@ -12,7 +12,7 @@ type Props = {
isBlockingOrUnBlocking: boolean,
isToggling: boolean,
doCommentModUnBlock: (string, boolean) => void,
doCommentModBlock: (string, boolean) => void,
doCommentModBlock: (string, ?Number, boolean) => void,
doCommentModUnBlockAsAdmin: (string, string) => void,
doCommentModBlockAsAdmin: (string, string) => void,
doCommentModUnBlockAsModerator: (string, string, string) => void,
Expand Down Expand Up @@ -42,7 +42,7 @@ function ChannelBlockButton(props: Props) {
if (isBlocked) {
doCommentModUnBlock(uri, false);
} else {
doCommentModBlock(uri, false);
doCommentModBlock(uri, undefined, false);
}
break;

Expand Down
15 changes: 2 additions & 13 deletions ui/component/commentMenuList/index.js
@@ -1,19 +1,13 @@
import { connect } from 'react-redux';
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
import {
doCommentPin,
doCommentModBlock,
doCommentModBlockAsAdmin,
doCommentModBlockAsModerator,
doCommentModAddDelegate,
} from 'redux/actions/comments';
import { doCommentPin, doCommentModAddDelegate } from 'redux/actions/comments';
import { doChannelMute } from 'redux/actions/blocked';
// import { doSetActiveChannel } from 'redux/actions/app';
import { doOpenModal } from 'redux/actions/app';
import { doSetPlayingUri } from 'redux/actions/content';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { selectPlayingUri } from 'redux/selectors/content';
import { selectModerationDelegatorsById } from 'redux/selectors/comments';

import CommentMenuList from './view';

const select = (state, props) => ({
Expand All @@ -22,7 +16,6 @@ const select = (state, props) => ({
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
activeChannelClaim: selectActiveChannelClaim(state),
playingUri: selectPlayingUri(state),
moderationDelegatorsById: selectModerationDelegatorsById(state),
});

const perform = (dispatch) => ({
Expand All @@ -31,10 +24,6 @@ const perform = (dispatch) => ({
muteChannel: (channelUri) => dispatch(doChannelMute(channelUri)),
pinComment: (commentId, claimId, remove) => dispatch(doCommentPin(commentId, claimId, remove)),
// setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)),
commentModBlock: (commenterUri) => dispatch(doCommentModBlock(commenterUri)),
commentModBlockAsAdmin: (commenterUri, blockerId) => dispatch(doCommentModBlockAsAdmin(commenterUri, blockerId)),
commentModBlockAsModerator: (commenterUri, creatorId, blockerId) =>
dispatch(doCommentModBlockAsModerator(commenterUri, creatorId, blockerId)),
commentModAddDelegate: (modChanId, modChanName, creatorChannelClaim) =>
dispatch(doCommentModAddDelegate(modChanId, modChanName, creatorChannelClaim, true)),
});
Expand Down
67 changes: 3 additions & 64 deletions ui/component/commentMenuList/view.jsx
Expand Up @@ -8,6 +8,7 @@ import Icon from 'component/common/icon';
import { parseURI } from 'lbry-redux';

type Props = {
uri: ?string,
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
commentId: string, // sha256 digest identifying the comment
isTopLevel: boolean,
Expand All @@ -23,21 +24,18 @@ type Props = {
contentChannelPermanentUrl: any,
activeChannelClaim: ?ChannelClaim,
playingUri: ?PlayingUri,
moderationDelegatorsById: { [string]: { global: boolean, delegators: { name: string, claimId: string } } },
// --- perform ---
openModal: (id: string, {}) => void,
clearPlayingUri: () => void,
muteChannel: (string) => void,
pinComment: (string, string, boolean) => Promise<any>,
commentModBlock: (string) => void,
commentModBlockAsAdmin: (string, string) => void,
commentModBlockAsModerator: (string, string, string) => void,
commentModAddDelegate: (string, string, ChannelClaim) => void,
setQuickReply: (any) => void,
};

function CommentMenuList(props: Props) {
const {
uri,
claim,
authorUri,
commentIsMine,
Expand All @@ -50,35 +48,16 @@ function CommentMenuList(props: Props) {
isTopLevel,
isPinned,
handleEditComment,
commentModBlock,
commentModBlockAsAdmin,
commentModBlockAsModerator,
commentModAddDelegate,
playingUri,
disableEdit,
disableRemove,
moderationDelegatorsById,
openModal,
supportAmount,
setQuickReply,
} = props;

const contentChannelClaim = !claim
? null
: claim.value_type === 'channel'
? claim
: claim.signing_channel && claim.is_channel_signature_valid
? claim.signing_channel
: null;

const activeModeratorInfo = activeChannelClaim && moderationDelegatorsById[activeChannelClaim.claim_id];
const activeChannelIsCreator = activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl;
const activeChannelIsAdmin = activeChannelClaim && activeModeratorInfo && activeModeratorInfo.global;
const activeChannelIsModerator =
activeChannelClaim &&
contentChannelClaim &&
activeModeratorInfo &&
Object.values(activeModeratorInfo.delegators).includes(contentChannelClaim.claim_id);

function handlePinComment(commentId, claimId, remove) {
pinComment(commentId, claimId, remove);
Expand All @@ -98,7 +77,7 @@ function CommentMenuList(props: Props) {
}

function handleCommentBlock() {
commentModBlock(authorUri);
openModal(MODALS.BLOCK_CHANNEL, { contentUri: uri, commenterUri: authorUri });
}

function handleCommentMute() {
Expand All @@ -112,18 +91,6 @@ function CommentMenuList(props: Props) {
}
}

function blockCommentAsModerator() {
if (activeChannelClaim && contentChannelClaim) {
commentModBlockAsModerator(authorUri, contentChannelClaim.claim_id, activeChannelClaim.claim_id);
}
}

function blockCommentAsAdmin() {
if (activeChannelClaim) {
commentModBlockAsAdmin(authorUri, activeChannelClaim.claim_id);
}
}

return (
<MenuList className="menu__list">
{activeChannelIsCreator && <div className="comment__menu-title">{__('Creator tools')}</div>}
Expand Down Expand Up @@ -197,34 +164,6 @@ function CommentMenuList(props: Props) {
</MenuItem>
)}

{!commentIsMine && (activeChannelIsAdmin || activeChannelIsModerator) && (
<div className="comment__menu-title">{__('Moderator tools')}</div>
)}

{!commentIsMine && activeChannelIsAdmin && (
<MenuItem className="comment__menu-option" onSelect={blockCommentAsAdmin}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.GLOBE} />
{__('Global Block')}
</div>
<span className="comment__menu-help">{__('Block this channel as global admin')}</span>
</MenuItem>
)}

{!commentIsMine && activeChannelIsModerator && (
<MenuItem className="comment__menu-option" onSelect={blockCommentAsModerator}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.BLOCK} />
{__('Moderator Block')}
</div>
<span className="comment__menu-help">
{__('Block this channel on behalf of %creator%', {
creator: contentChannelClaim ? contentChannelClaim.name : __('creator'),
})}
</span>
</MenuItem>
)}

{activeChannelClaim && (
<div className="comment__menu-active">
<ChannelThumbnail xsmall noLazyLoad uri={activeChannelClaim.permanent_url} />
Expand Down
1 change: 1 addition & 0 deletions ui/constants/modal_types.js
Expand Up @@ -43,6 +43,7 @@ export const IMAGE_UPLOAD = 'image_upload';
export const MOBILE_SEARCH = 'mobile_search';
export const VIEW_IMAGE = 'view_image';
export const CONFIRM_REMOVE_BTC_SWAP_ADDRESS = 'confirm_remove_btc_swap_address';
export const BLOCK_CHANNEL = 'block_channel';
export const COLLECTION_ADD = 'collection_add';
export const COLLECTION_DELETE = 'collection_delete';
export const CONFIRM_REMOVE_CARD = 'CONFIRM_REMOVE_CARD';
Expand Down
25 changes: 25 additions & 0 deletions ui/modal/modalBlockChannel/index.js
@@ -0,0 +1,25 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri } from 'lbry-redux';
import { doHideModal } from 'redux/actions/app';
import { doCommentModBlock, doCommentModBlockAsAdmin, doCommentModBlockAsModerator } from 'redux/actions/comments';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { selectModerationDelegatorsById } from 'redux/selectors/comments';

import ModalBlockChannel from './view';

const select = (state, props) => ({
activeChannelClaim: selectActiveChannelClaim(state),
contentClaim: makeSelectClaimForUri(props.contentUri)(state),
moderationDelegatorsById: selectModerationDelegatorsById(state),
});

const perform = (dispatch) => ({
closeModal: () => dispatch(doHideModal()),
commentModBlock: (commenterUri, timeoutHours) => dispatch(doCommentModBlock(commenterUri, timeoutHours)),
commentModBlockAsAdmin: (commenterUri, blockerId, timeoutHours) =>
dispatch(doCommentModBlockAsAdmin(commenterUri, blockerId, timeoutHours)),
commentModBlockAsModerator: (commenterUri, creatorId, blockerId, timeoutHours) =>
dispatch(doCommentModBlockAsModerator(commenterUri, creatorId, blockerId, timeoutHours)),
});

export default connect(select, perform)(ModalBlockChannel);

0 comments on commit a05ccdd

Please sign in to comment.