Comment Moderation - time based bans
## 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).
This commit is contained in:
parent
049fb2878e
commit
a05ccdd44f
11 changed files with 393 additions and 88 deletions
38
flow-typed/Comment.js
vendored
38
flow-typed/Comment.js
vendored
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
@ -42,7 +42,7 @@ function ChannelBlockButton(props: Props) {
|
|||
if (isBlocked) {
|
||||
doCommentModUnBlock(uri, false);
|
||||
} else {
|
||||
doCommentModBlock(uri, false);
|
||||
doCommentModBlock(uri, undefined, false);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -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) => ({
|
||||
|
@ -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) => ({
|
||||
|
@ -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)),
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -98,7 +77,7 @@ function CommentMenuList(props: Props) {
|
|||
}
|
||||
|
||||
function handleCommentBlock() {
|
||||
commentModBlock(authorUri);
|
||||
openModal(MODALS.BLOCK_CHANNEL, { contentUri: uri, commenterUri: authorUri });
|
||||
}
|
||||
|
||||
function handleCommentMute() {
|
||||
|
@ -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>}
|
||||
|
@ -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} />
|
||||
|
|
|
@ -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';
|
||||
|
|
25
ui/modal/modalBlockChannel/index.js
Normal file
25
ui/modal/modalBlockChannel/index.js
Normal file
|
@ -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);
|
253
ui/modal/modalBlockChannel/view.jsx
Normal file
253
ui/modal/modalBlockChannel/view.jsx
Normal file
|
@ -0,0 +1,253 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Button from 'component/button';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
import Card from 'component/common/card';
|
||||
import { FormField } from 'component/common/form';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import { Modal } from 'modal/modal';
|
||||
|
||||
const TAB = {
|
||||
PERSONAL: 'personal',
|
||||
MODERATOR: 'moderator',
|
||||
ADMIN: 'admin',
|
||||
};
|
||||
|
||||
const BLOCK = {
|
||||
PERMANENT: 'permanent',
|
||||
TIMEOUT: 'timeout',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
contentUri: string,
|
||||
commenterUri: string,
|
||||
// --- select ---
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
contentClaim: ?Claim,
|
||||
moderationDelegatorsById: { [string]: { global: boolean, delegators: { name: string, claimId: string } } },
|
||||
// --- perform ---
|
||||
closeModal: () => void,
|
||||
commentModBlock: (string, ?number) => void,
|
||||
commentModBlockAsAdmin: (string, string, ?number) => void,
|
||||
commentModBlockAsModerator: (string, string, string, ?number) => void,
|
||||
};
|
||||
|
||||
export default function ModalBlockChannel(props: Props) {
|
||||
const {
|
||||
commenterUri,
|
||||
activeChannelClaim,
|
||||
contentClaim,
|
||||
moderationDelegatorsById,
|
||||
closeModal,
|
||||
commentModBlock,
|
||||
commentModBlockAsAdmin,
|
||||
commentModBlockAsModerator,
|
||||
} = props;
|
||||
|
||||
const contentChannelClaim = !contentClaim
|
||||
? null
|
||||
: contentClaim.value_type === 'channel'
|
||||
? contentClaim
|
||||
: contentClaim.signing_channel && contentClaim.is_channel_signature_valid
|
||||
? contentClaim.signing_channel
|
||||
: null;
|
||||
|
||||
const activeModeratorInfo = activeChannelClaim && moderationDelegatorsById[activeChannelClaim.claim_id];
|
||||
const activeChannelIsAdmin = activeChannelClaim && activeModeratorInfo && activeModeratorInfo.global;
|
||||
const activeChannelIsModerator =
|
||||
activeChannelClaim &&
|
||||
contentChannelClaim &&
|
||||
activeModeratorInfo &&
|
||||
Object.values(activeModeratorInfo.delegators).includes(contentChannelClaim.claim_id);
|
||||
|
||||
const [tab, setTab] = usePersistedState('ModalBlockChannel:tab', TAB.PERSONAL);
|
||||
const [blockType, setBlockType] = usePersistedState('ModalBlockChannel:blockType', BLOCK.PERMANENT);
|
||||
const [timeoutHrs, setTimeoutHrs] = usePersistedState('ModalBlockChannel:timeoutHrs', 1);
|
||||
const [timeoutHrsError, setTimeoutHrsError] = React.useState('');
|
||||
|
||||
const personalIsTheOnlyTab = !activeChannelIsModerator && !activeChannelIsAdmin;
|
||||
const blockButtonDisabled = blockType === BLOCK.TIMEOUT && (timeoutHrs === 0 || !Number.isInteger(timeoutHrs));
|
||||
|
||||
// **************************************************************************
|
||||
// **************************************************************************
|
||||
|
||||
// Check 'tab' validity on mount.
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
personalIsTheOnlyTab ||
|
||||
(tab === TAB.MODERATOR && !activeChannelIsModerator) ||
|
||||
(tab === TAB.ADMIN && !activeChannelIsAdmin)
|
||||
) {
|
||||
setTab(TAB.PERSONAL);
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// 'timeoutHrs' sanity check.
|
||||
React.useEffect(() => {
|
||||
if (Number.isInteger(timeoutHrs) && timeoutHrs > 0) {
|
||||
if (timeoutHrsError) {
|
||||
setTimeoutHrsError('');
|
||||
}
|
||||
} else {
|
||||
if (!timeoutHrsError) {
|
||||
setTimeoutHrsError('Invalid duration.');
|
||||
}
|
||||
}
|
||||
}, [timeoutHrs, timeoutHrsError]);
|
||||
|
||||
// **************************************************************************
|
||||
// **************************************************************************
|
||||
|
||||
function getTabElem(value, label) {
|
||||
return (
|
||||
<Button
|
||||
key={value}
|
||||
label={__(label)}
|
||||
button="alt"
|
||||
onClick={() => setTab(value)}
|
||||
className={classnames('button-toggle', { 'button-toggle--active': tab === value })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getTabHelperElem(tab) {
|
||||
switch (tab) {
|
||||
case TAB.PERSONAL:
|
||||
return null;
|
||||
case TAB.MODERATOR:
|
||||
return (
|
||||
<p className="help">
|
||||
{__('Block this channel on behalf of %creator%', {
|
||||
creator: contentChannelClaim ? contentChannelClaim.name : __('creator'),
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
case TAB.ADMIN:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getBlockTypeElem(value, label) {
|
||||
return (
|
||||
<FormField
|
||||
type="radio"
|
||||
name={value}
|
||||
key={value}
|
||||
label={__(label)}
|
||||
checked={blockType === value}
|
||||
onChange={() => setBlockType(value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getTimeoutDurationElem() {
|
||||
return (
|
||||
<FormField
|
||||
name="time_out_hrs"
|
||||
label={__('Hours')}
|
||||
className="form-field--price-amount"
|
||||
max="1000"
|
||||
min="1"
|
||||
step="1"
|
||||
type="number"
|
||||
placeholder="1"
|
||||
value={timeoutHrs}
|
||||
onChange={(e) => setTimeoutHrs(parseInt(e.target.value))}
|
||||
error={timeoutHrsError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getCommenterPreview(uri) {
|
||||
return (
|
||||
<div className="content__non-clickable">
|
||||
<ClaimPreview uri={uri} hideMenu hideActions type="small" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getActiveChannelElem() {
|
||||
return activeChannelClaim ? (
|
||||
<div className="block-modal--active-channel">
|
||||
<ChannelThumbnail xsmall noLazyLoad uri={activeChannelClaim.permanent_url} />
|
||||
<div className="block-modal--active-channel-label">
|
||||
{__('Interacting as %channelName%', { channelName: activeChannelClaim.name })}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
function handleBlock() {
|
||||
const duration = blockType === BLOCK.TIMEOUT && timeoutHrs ? timeoutHrs : undefined;
|
||||
|
||||
switch (tab) {
|
||||
case TAB.PERSONAL:
|
||||
commentModBlock(commenterUri, duration);
|
||||
break;
|
||||
|
||||
case TAB.MODERATOR:
|
||||
if (activeChannelClaim && contentChannelClaim) {
|
||||
commentModBlockAsModerator(commenterUri, contentChannelClaim.claim_id, activeChannelClaim.claim_id, duration);
|
||||
}
|
||||
break;
|
||||
|
||||
case TAB.ADMIN:
|
||||
if (activeChannelClaim) {
|
||||
commentModBlockAsAdmin(commenterUri, activeChannelClaim.claim_id, duration);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
closeModal();
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// **************************************************************************
|
||||
|
||||
return (
|
||||
<Modal isOpen type="card" onAborted={closeModal}>
|
||||
<Card
|
||||
title={__('Block Channel')}
|
||||
subtitle={getCommenterPreview(commenterUri)}
|
||||
actions={
|
||||
<>
|
||||
{!personalIsTheOnlyTab && (
|
||||
<div className="section__actions">
|
||||
<div className="section">
|
||||
<label>{__('Block list')}</label>
|
||||
<div className="block-modal--values">
|
||||
{getTabElem(TAB.PERSONAL, 'Personal')}
|
||||
{activeChannelIsModerator && getTabElem(TAB.MODERATOR, 'Moderator')}
|
||||
{activeChannelIsAdmin && getTabElem(TAB.ADMIN, 'Global Admin')}
|
||||
{getTabHelperElem(tab)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="section section--vertical-compact">
|
||||
<label>{__('Duration')}</label>
|
||||
<div className="block-modal--values">
|
||||
<fieldset>
|
||||
{getBlockTypeElem(BLOCK.PERMANENT, 'Permanent')}
|
||||
{getBlockTypeElem(BLOCK.TIMEOUT, 'Timeout')}
|
||||
</fieldset>
|
||||
{blockType === BLOCK.TIMEOUT && getTimeoutDurationElem()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="block-modal--finalize">
|
||||
<div className="section__actions">
|
||||
<Button button="primary" label={__('Block')} onClick={handleBlock} disabled={blockButtonDisabled} />
|
||||
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
||||
{getActiveChannelElem()}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -8,6 +8,7 @@ import LoadingBarOneOff from 'component/loadingBarOneOff';
|
|||
const ModalAffirmPurchase = lazyImport(() => import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */));
|
||||
const ModalAutoGenerateThumbnail = lazyImport(() => import('modal/modalAutoGenerateThumbnail' /* webpackChunkName: "modalAutoGenerateThumbnail" */));
|
||||
const ModalAutoUpdateDownloaded = lazyImport(() => import('modal/modalAutoUpdateDownloaded' /* webpackChunkName: "modalAutoUpdateDownloaded" */));
|
||||
const ModalBlockChannel = lazyImport(() => import('modal/modalBlockChannel' /* webpackChunkName: "modalBlockChannel" */));
|
||||
const ModalClaimCollectionAdd = lazyImport(() => import('modal/modalClaimCollectionAdd' /* webpackChunkName: "modalClaimCollectionAdd" */));
|
||||
const ModalCommentAcknowledgement = lazyImport(() => import('modal/modalCommentAcknowledgement' /* webpackChunkName: "modalCommentAcknowledgement" */));
|
||||
const ModalConfirmAge = lazyImport(() => import('modal/modalConfirmAge' /* webpackChunkName: "modalConfirmAge" */));
|
||||
|
@ -149,6 +150,8 @@ function ModalRouter(props: Props) {
|
|||
return ModalMassTipsUnlock;
|
||||
case MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS:
|
||||
return ModalRemoveBtcSwapAddress;
|
||||
case MODALS.BLOCK_CHANNEL:
|
||||
return ModalBlockChannel;
|
||||
case MODALS.COLLECTION_ADD:
|
||||
return ModalClaimCollectionAdd;
|
||||
case MODALS.COLLECTION_DELETE:
|
||||
|
|
|
@ -738,6 +738,7 @@ function doCommentModToggleBlock(
|
|||
creatorId: string,
|
||||
blockerIds: Array<string>, // [] = use all my channels
|
||||
blockLevel: string,
|
||||
timeoutHours?: number,
|
||||
showLink: boolean = false
|
||||
) {
|
||||
return async (dispatch: Dispatch, getState: GetState) => {
|
||||
|
@ -844,6 +845,7 @@ function doCommentModToggleBlock(
|
|||
block_all: unblock ? undefined : blockLevel === BLOCK_LEVEL.ADMIN,
|
||||
global_un_block: unblock ? blockLevel === BLOCK_LEVEL.ADMIN : undefined,
|
||||
...sharedModBlockParams,
|
||||
time_out_hrs: unblock ? undefined : timeoutHours,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
@ -920,12 +922,13 @@ function doCommentModToggleBlock(
|
|||
* Blocks the commenter for all channels that I own.
|
||||
*
|
||||
* @param commenterUri
|
||||
* @param timeoutHours
|
||||
* @param showLink
|
||||
* @returns {function(Dispatch): *}
|
||||
*/
|
||||
export function doCommentModBlock(commenterUri: string, showLink: boolean = true) {
|
||||
export function doCommentModBlock(commenterUri: string, timeoutHours?: number, showLink: boolean = true) {
|
||||
return (dispatch: Dispatch) => {
|
||||
return dispatch(doCommentModToggleBlock(false, commenterUri, '', [], BLOCK_LEVEL.SELF, showLink));
|
||||
return dispatch(doCommentModToggleBlock(false, commenterUri, '', [], BLOCK_LEVEL.SELF, timeoutHours, showLink));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -934,11 +937,14 @@ export function doCommentModBlock(commenterUri: string, showLink: boolean = true
|
|||
*
|
||||
* @param commenterUri
|
||||
* @param blockerId
|
||||
* @param timeoutHours
|
||||
* @returns {function(Dispatch): *}
|
||||
*/
|
||||
export function doCommentModBlockAsAdmin(commenterUri: string, blockerId: string) {
|
||||
export function doCommentModBlockAsAdmin(commenterUri: string, blockerId: string, timeoutHours?: number) {
|
||||
return (dispatch: Dispatch) => {
|
||||
return dispatch(doCommentModToggleBlock(false, commenterUri, '', blockerId ? [blockerId] : [], BLOCK_LEVEL.ADMIN));
|
||||
return dispatch(
|
||||
doCommentModToggleBlock(false, commenterUri, '', blockerId ? [blockerId] : [], BLOCK_LEVEL.ADMIN, timeoutHours)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -949,12 +955,25 @@ export function doCommentModBlockAsAdmin(commenterUri: string, blockerId: string
|
|||
* @param commenterUri
|
||||
* @param creatorId
|
||||
* @param blockerId
|
||||
* @param timeoutHours
|
||||
* @returns {function(Dispatch): *}
|
||||
*/
|
||||
export function doCommentModBlockAsModerator(commenterUri: string, creatorId: string, blockerId: string) {
|
||||
export function doCommentModBlockAsModerator(
|
||||
commenterUri: string,
|
||||
creatorId: string,
|
||||
blockerId: string,
|
||||
timeoutHours?: number
|
||||
) {
|
||||
return (dispatch: Dispatch) => {
|
||||
return dispatch(
|
||||
doCommentModToggleBlock(false, commenterUri, creatorId, blockerId ? [blockerId] : [], BLOCK_LEVEL.MODERATOR)
|
||||
doCommentModToggleBlock(
|
||||
false,
|
||||
commenterUri,
|
||||
creatorId,
|
||||
blockerId ? [blockerId] : [],
|
||||
BLOCK_LEVEL.MODERATOR,
|
||||
timeoutHours
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -968,7 +987,7 @@ export function doCommentModBlockAsModerator(commenterUri: string, creatorId: st
|
|||
*/
|
||||
export function doCommentModUnBlock(commenterUri: string, showLink: boolean = true) {
|
||||
return (dispatch: Dispatch) => {
|
||||
return dispatch(doCommentModToggleBlock(true, commenterUri, '', [], BLOCK_LEVEL.SELF, showLink));
|
||||
return dispatch(doCommentModToggleBlock(true, commenterUri, '', [], BLOCK_LEVEL.SELF, undefined, showLink));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,3 +16,43 @@
|
|||
margin-top: var(--spacing-xxs);
|
||||
}
|
||||
}
|
||||
|
||||
.block-modal--values {
|
||||
margin-left: var(--spacing-s);
|
||||
|
||||
.help {
|
||||
font-style: italic;
|
||||
font-size: var(--font-xsmall);
|
||||
}
|
||||
}
|
||||
|
||||
.block-modal--finalize {
|
||||
margin-top: var(--spacing-l);
|
||||
}
|
||||
|
||||
.block-modal--active-channel {
|
||||
padding: var(--spacing-xs);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.channel-thumbnail {
|
||||
margin-right: var(--spacing-xs);
|
||||
height: 1.8rem;
|
||||
width: 1.8rem;
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
border-left: 1px solid var(--color-border);
|
||||
padding-left: var(--spacing-m);
|
||||
margin-left: calc(var(--spacing-l) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.block-modal--active-channel-label {
|
||||
@extend .help;
|
||||
font-size: var(--font-xxsmall);
|
||||
margin-top: 0;
|
||||
max-width: 10rem;
|
||||
white-space: pre-line;
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue