mirror of
https://github.com/LBRYFoundation/lbry-desktop.git
synced 2025-09-19 00:39:47 +00:00
#6872 Comment Moderation - time based bans
This commit is contained in:
commit
03d56d1445
18 changed files with 651 additions and 128 deletions
41
flow-typed/Comment.js
vendored
41
flow-typed/Comment.js
vendored
|
@ -60,6 +60,9 @@ declare type CommentsState = {
|
||||||
fetchingModerationDelegators: boolean,
|
fetchingModerationDelegators: boolean,
|
||||||
blockingByUri: {},
|
blockingByUri: {},
|
||||||
unBlockingByUri: {},
|
unBlockingByUri: {},
|
||||||
|
personalTimeoutMap: { [uri: string]: { blockedAt: string, bannedFor: number, banRemaining: number } },
|
||||||
|
adminTimeoutMap: { [uri: string]: { blockedAt: string, bannedFor: number, banRemaining: number } },
|
||||||
|
moderatorTimeoutMap: { [uri: string]: { blockedAt: string, bannedFor: number, banRemaining: number } },
|
||||||
togglingForDelegatorMap: {[string]: Array<string>}, // {"blockedUri": ["delegatorUri1", ""delegatorUri2", ...]}
|
togglingForDelegatorMap: {[string]: Array<string>}, // {"blockedUri": ["delegatorUri1", ""delegatorUri2", ...]}
|
||||||
settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings
|
settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings
|
||||||
fetchingSettings: boolean,
|
fetchingSettings: boolean,
|
||||||
|
@ -189,7 +192,43 @@ declare type SuperListResponse = {
|
||||||
has_hidden_comments: boolean,
|
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?: 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 = {
|
declare type ModerationAddDelegateParams = {
|
||||||
mod_channel_id: string,
|
mod_channel_id: string,
|
||||||
|
|
|
@ -54,7 +54,9 @@
|
||||||
"electron-notarize": "^1.0.0",
|
"electron-notarize": "^1.0.0",
|
||||||
"electron-updater": "^4.2.4",
|
"electron-updater": "^4.2.4",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"humanize-duration": "^3.27.0",
|
||||||
"if-env": "^1.0.4",
|
"if-env": "^1.0.4",
|
||||||
|
"parse-duration": "^1.0.0",
|
||||||
"react-datetime-picker": "^3.2.1",
|
"react-datetime-picker": "^3.2.1",
|
||||||
"react-plastic": "^1.1.1",
|
"react-plastic": "^1.1.1",
|
||||||
"react-top-loading-bar": "^2.0.1",
|
"react-top-loading-bar": "^2.0.1",
|
||||||
|
|
|
@ -1744,6 +1744,12 @@
|
||||||
"Moderator Block": "Moderator Block",
|
"Moderator Block": "Moderator Block",
|
||||||
"Block this channel on behalf of %creator%": "Block this channel on behalf of %creator%",
|
"Block this channel on behalf of %creator%": "Block this channel on behalf of %creator%",
|
||||||
"creator": "creator",
|
"creator": "creator",
|
||||||
|
"Enter the timeout duration. Examples: %examples%": "Enter the timeout duration. Examples: %examples%",
|
||||||
|
"Wow, banned for more than 100 years?": "Wow, banned for more than 100 years?",
|
||||||
|
"Invalid duration.": "Invalid duration.",
|
||||||
|
"Permanent": "Permanent",
|
||||||
|
"Timeout --[time-based ban instead of permanent]--": "Timeout",
|
||||||
|
"(Remaining: %duration%) --[timeout ban duration]--": "(Remaining: %duration%)",
|
||||||
"Create a channel to change this setting.": "Create a channel to change this setting.",
|
"Create a channel to change this setting.": "Create a channel to change this setting.",
|
||||||
"Invalid channel URL \"%url%\"": "Invalid channel URL \"%url%\"",
|
"Invalid channel URL \"%url%\"": "Invalid channel URL \"%url%\"",
|
||||||
"Delegation": "Delegation",
|
"Delegation": "Delegation",
|
||||||
|
|
|
@ -14,7 +14,7 @@ const Comments = {
|
||||||
|
|
||||||
moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params),
|
moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params),
|
||||||
moderation_unblock: (params: ModerationBlockParams) => fetchCommentsApi('moderation.UnBlock', 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_add_delegate: (params: ModerationAddDelegateParams) => fetchCommentsApi('moderation.AddDelegate', params),
|
||||||
moderation_remove_delegate: (params: ModerationRemoveDelegateParams) =>
|
moderation_remove_delegate: (params: ModerationRemoveDelegateParams) =>
|
||||||
fetchCommentsApi('moderation.RemoveDelegate', params),
|
fetchCommentsApi('moderation.RemoveDelegate', params),
|
||||||
|
|
|
@ -12,7 +12,7 @@ type Props = {
|
||||||
isBlockingOrUnBlocking: boolean,
|
isBlockingOrUnBlocking: boolean,
|
||||||
isToggling: boolean,
|
isToggling: boolean,
|
||||||
doCommentModUnBlock: (string, boolean) => void,
|
doCommentModUnBlock: (string, boolean) => void,
|
||||||
doCommentModBlock: (string, boolean) => void,
|
doCommentModBlock: (string, ?Number, boolean) => void,
|
||||||
doCommentModUnBlockAsAdmin: (string, string) => void,
|
doCommentModUnBlockAsAdmin: (string, string) => void,
|
||||||
doCommentModBlockAsAdmin: (string, string) => void,
|
doCommentModBlockAsAdmin: (string, string) => void,
|
||||||
doCommentModUnBlockAsModerator: (string, string, string) => void,
|
doCommentModUnBlockAsModerator: (string, string, string) => void,
|
||||||
|
@ -42,7 +42,7 @@ function ChannelBlockButton(props: Props) {
|
||||||
if (isBlocked) {
|
if (isBlocked) {
|
||||||
doCommentModUnBlock(uri, false);
|
doCommentModUnBlock(uri, false);
|
||||||
} else {
|
} else {
|
||||||
doCommentModBlock(uri, false);
|
doCommentModBlock(uri, undefined, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import {
|
import { doCommentPin, doCommentModAddDelegate } from 'redux/actions/comments';
|
||||||
doCommentPin,
|
|
||||||
doCommentModBlock,
|
|
||||||
doCommentModBlockAsAdmin,
|
|
||||||
doCommentModBlockAsModerator,
|
|
||||||
doCommentModAddDelegate,
|
|
||||||
} from 'redux/actions/comments';
|
|
||||||
import { doChannelMute } from 'redux/actions/blocked';
|
import { doChannelMute } from 'redux/actions/blocked';
|
||||||
// import { doSetActiveChannel } from 'redux/actions/app';
|
// import { doSetActiveChannel } from 'redux/actions/app';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
import { selectPlayingUri } from 'redux/selectors/content';
|
import { selectPlayingUri } from 'redux/selectors/content';
|
||||||
import { selectModerationDelegatorsById } from 'redux/selectors/comments';
|
|
||||||
import CommentMenuList from './view';
|
import CommentMenuList from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -22,7 +16,6 @@ const select = (state, props) => ({
|
||||||
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
playingUri: selectPlayingUri(state),
|
playingUri: selectPlayingUri(state),
|
||||||
moderationDelegatorsById: selectModerationDelegatorsById(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
@ -31,10 +24,6 @@ const perform = (dispatch) => ({
|
||||||
muteChannel: (channelUri) => dispatch(doChannelMute(channelUri)),
|
muteChannel: (channelUri) => dispatch(doChannelMute(channelUri)),
|
||||||
pinComment: (commentId, claimId, remove) => dispatch(doCommentPin(commentId, claimId, remove)),
|
pinComment: (commentId, claimId, remove) => dispatch(doCommentPin(commentId, claimId, remove)),
|
||||||
// setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)),
|
// 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) =>
|
commentModAddDelegate: (modChanId, modChanName, creatorChannelClaim) =>
|
||||||
dispatch(doCommentModAddDelegate(modChanId, modChanName, creatorChannelClaim, true)),
|
dispatch(doCommentModAddDelegate(modChanId, modChanName, creatorChannelClaim, true)),
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Icon from 'component/common/icon';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
uri: ?string,
|
||||||
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
||||||
commentId: string, // sha256 digest identifying the comment
|
commentId: string, // sha256 digest identifying the comment
|
||||||
isTopLevel: boolean,
|
isTopLevel: boolean,
|
||||||
|
@ -23,21 +24,18 @@ type Props = {
|
||||||
contentChannelPermanentUrl: any,
|
contentChannelPermanentUrl: any,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: ?PlayingUri,
|
||||||
moderationDelegatorsById: { [string]: { global: boolean, delegators: { name: string, claimId: string } } },
|
|
||||||
// --- perform ---
|
// --- perform ---
|
||||||
openModal: (id: string, {}) => void,
|
openModal: (id: string, {}) => void,
|
||||||
clearPlayingUri: () => void,
|
clearPlayingUri: () => void,
|
||||||
muteChannel: (string) => void,
|
muteChannel: (string) => void,
|
||||||
pinComment: (string, string, boolean) => Promise<any>,
|
pinComment: (string, string, boolean) => Promise<any>,
|
||||||
commentModBlock: (string) => void,
|
|
||||||
commentModBlockAsAdmin: (string, string) => void,
|
|
||||||
commentModBlockAsModerator: (string, string, string) => void,
|
|
||||||
commentModAddDelegate: (string, string, ChannelClaim) => void,
|
commentModAddDelegate: (string, string, ChannelClaim) => void,
|
||||||
setQuickReply: (any) => void,
|
setQuickReply: (any) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function CommentMenuList(props: Props) {
|
function CommentMenuList(props: Props) {
|
||||||
const {
|
const {
|
||||||
|
uri,
|
||||||
claim,
|
claim,
|
||||||
authorUri,
|
authorUri,
|
||||||
commentIsMine,
|
commentIsMine,
|
||||||
|
@ -50,35 +48,16 @@ function CommentMenuList(props: Props) {
|
||||||
isTopLevel,
|
isTopLevel,
|
||||||
isPinned,
|
isPinned,
|
||||||
handleEditComment,
|
handleEditComment,
|
||||||
commentModBlock,
|
|
||||||
commentModBlockAsAdmin,
|
|
||||||
commentModBlockAsModerator,
|
|
||||||
commentModAddDelegate,
|
commentModAddDelegate,
|
||||||
playingUri,
|
playingUri,
|
||||||
disableEdit,
|
disableEdit,
|
||||||
disableRemove,
|
disableRemove,
|
||||||
moderationDelegatorsById,
|
|
||||||
openModal,
|
openModal,
|
||||||
supportAmount,
|
supportAmount,
|
||||||
setQuickReply,
|
setQuickReply,
|
||||||
} = props;
|
} = 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 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) {
|
function handlePinComment(commentId, claimId, remove) {
|
||||||
pinComment(commentId, claimId, remove);
|
pinComment(commentId, claimId, remove);
|
||||||
|
@ -98,7 +77,7 @@ function CommentMenuList(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommentBlock() {
|
function handleCommentBlock() {
|
||||||
commentModBlock(authorUri);
|
openModal(MODALS.BLOCK_CHANNEL, { contentUri: uri, commenterUri: authorUri });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommentMute() {
|
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 (
|
return (
|
||||||
<MenuList className="menu__list">
|
<MenuList className="menu__list">
|
||||||
{activeChannelIsCreator && <div className="comment__menu-title">{__('Creator tools')}</div>}
|
{activeChannelIsCreator && <div className="comment__menu-title">{__('Creator tools')}</div>}
|
||||||
|
@ -197,34 +164,6 @@ function CommentMenuList(props: Props) {
|
||||||
</MenuItem>
|
</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 && (
|
{activeChannelClaim && (
|
||||||
<div className="comment__menu-active">
|
<div className="comment__menu-active">
|
||||||
<ChannelThumbnail xsmall noLazyLoad uri={activeChannelClaim.permanent_url} />
|
<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 MOBILE_SEARCH = 'mobile_search';
|
||||||
export const VIEW_IMAGE = 'view_image';
|
export const VIEW_IMAGE = 'view_image';
|
||||||
export const CONFIRM_REMOVE_BTC_SWAP_ADDRESS = 'confirm_remove_btc_swap_address';
|
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_ADD = 'collection_add';
|
||||||
export const COLLECTION_DELETE = 'collection_delete';
|
export const COLLECTION_DELETE = 'collection_delete';
|
||||||
export const CONFIRM_REMOVE_CARD = 'CONFIRM_REMOVE_CARD';
|
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);
|
304
ui/modal/modalBlockChannel/view.jsx
Normal file
304
ui/modal/modalBlockChannel/view.jsx
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import parseDuration from 'parse-duration';
|
||||||
|
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 Icon from 'component/common/icon';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
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 [timeoutInput, setTimeoutInput] = usePersistedState('ModalBlockChannel:timeoutInput', '10m');
|
||||||
|
const [timeoutInputErr, setTimeoutInputErr] = React.useState('');
|
||||||
|
const [timeoutSec, setTimeoutSec] = React.useState(-1);
|
||||||
|
|
||||||
|
const isPersonalTheOnlyTab = !activeChannelIsModerator && !activeChannelIsAdmin;
|
||||||
|
const isTimeoutAvail = contentClaim && contentClaim.is_my_output;
|
||||||
|
const blockButtonDisabled = blockType === BLOCK.TIMEOUT && timeoutSec < 1;
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// Check settings validity on mount.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (
|
||||||
|
isPersonalTheOnlyTab ||
|
||||||
|
(tab === TAB.MODERATOR && !activeChannelIsModerator) ||
|
||||||
|
(tab === TAB.ADMIN && !activeChannelIsAdmin)
|
||||||
|
) {
|
||||||
|
setTab(TAB.PERSONAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTimeoutAvail && blockType === BLOCK.TIMEOUT) {
|
||||||
|
setBlockType(BLOCK.PERMANENT);
|
||||||
|
}
|
||||||
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
// 'timeoutInput' to 'timeoutSec' conversion.
|
||||||
|
React.useEffect(() => {
|
||||||
|
const setInvalid = (errMsg: string) => {
|
||||||
|
if (timeoutSec !== -1) {
|
||||||
|
setTimeoutSec(-1);
|
||||||
|
}
|
||||||
|
if (!timeoutInputErr) {
|
||||||
|
setTimeoutInputErr(errMsg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setValid = (seconds) => {
|
||||||
|
if (seconds !== timeoutSec) {
|
||||||
|
setTimeoutSec(seconds);
|
||||||
|
}
|
||||||
|
if (timeoutInputErr) {
|
||||||
|
setTimeoutInputErr('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ONE_HUNDRED_YEARS_IN_SECONDS = 3154000000;
|
||||||
|
const seconds = parseDuration(timeoutInput, 's');
|
||||||
|
|
||||||
|
if (Number.isInteger(seconds) && seconds > 0) {
|
||||||
|
if (seconds > ONE_HUNDRED_YEARS_IN_SECONDS) {
|
||||||
|
setInvalid('Wow, banned for more than 100 years?');
|
||||||
|
} else {
|
||||||
|
setValid(seconds);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setInvalid('Invalid duration.');
|
||||||
|
}
|
||||||
|
}, [timeoutInput, timeoutInputErr, timeoutSec]);
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
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, disabled = false, disabledLabel = '') {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
type="radio"
|
||||||
|
name={value}
|
||||||
|
key={value}
|
||||||
|
label={disabled && disabledLabel ? __(disabledLabel) : __(label)}
|
||||||
|
disabled={disabled}
|
||||||
|
checked={blockType === value}
|
||||||
|
onChange={() => setBlockType(value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimeoutDurationElem() {
|
||||||
|
const examples = '\n- 30s\n- 10m\n- 1h\n- 2d\n- 3mo\n- 1y';
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
name="time_out"
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
{__('Duration')}
|
||||||
|
<Icon
|
||||||
|
customTooltipText={__('Enter the timeout duration. Examples: %examples%', { examples })}
|
||||||
|
className="icon--help"
|
||||||
|
icon={ICONS.HELP}
|
||||||
|
tooltip
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
type="text"
|
||||||
|
placeholder="30s, 10m, 1h, 2d, 3mo, 1y"
|
||||||
|
value={timeoutInput}
|
||||||
|
onChange={(e) => setTimeoutInput(e.target.value)}
|
||||||
|
error={timeoutInputErr}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 && timeoutSec > 0 ? timeoutSec : 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
if (isPersonalTheOnlyTab && !isTimeoutAvail) {
|
||||||
|
// There's only 1 option. Just execute it and don't show the modal.
|
||||||
|
commentModBlock(commenterUri);
|
||||||
|
closeModal();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen type="card" onAborted={closeModal}>
|
||||||
|
<Card
|
||||||
|
title={__('Block Channel')}
|
||||||
|
subtitle={getCommenterPreview(commenterUri)}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
{!isPersonalTheOnlyTab && (
|
||||||
|
<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 --[time-based ban instead of permanent]--',
|
||||||
|
!isTimeoutAvail,
|
||||||
|
'Timeout (only available on content that you own)'
|
||||||
|
)}
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,32 +5,71 @@ import { lazyImport } from 'util/lazyImport';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import LoadingBarOneOff from 'component/loadingBarOneOff';
|
import LoadingBarOneOff from 'component/loadingBarOneOff';
|
||||||
|
|
||||||
const ModalAffirmPurchase = lazyImport(() => import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */));
|
const ModalAffirmPurchase = lazyImport(() =>
|
||||||
const ModalAutoGenerateThumbnail = lazyImport(() => import('modal/modalAutoGenerateThumbnail' /* webpackChunkName: "modalAutoGenerateThumbnail" */));
|
import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */)
|
||||||
const ModalAutoUpdateDownloaded = lazyImport(() => import('modal/modalAutoUpdateDownloaded' /* webpackChunkName: "modalAutoUpdateDownloaded" */));
|
);
|
||||||
const ModalClaimCollectionAdd = lazyImport(() => import('modal/modalClaimCollectionAdd' /* webpackChunkName: "modalClaimCollectionAdd" */));
|
const ModalAutoGenerateThumbnail = lazyImport(() =>
|
||||||
const ModalCommentAcknowledgement = lazyImport(() => import('modal/modalCommentAcknowledgement' /* webpackChunkName: "modalCommentAcknowledgement" */));
|
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" */));
|
const ModalConfirmAge = lazyImport(() => import('modal/modalConfirmAge' /* webpackChunkName: "modalConfirmAge" */));
|
||||||
const ModalConfirmThumbnailUpload = lazyImport(() => import('modal/modalConfirmThumbnailUpload' /* webpackChunkName: "modalConfirmThumbnailUpload" */));
|
const ModalConfirmThumbnailUpload = lazyImport(() =>
|
||||||
const ModalConfirmTransaction = lazyImport(() => import('modal/modalConfirmTransaction' /* webpackChunkName: "modalConfirmTransaction" */));
|
import('modal/modalConfirmThumbnailUpload' /* webpackChunkName: "modalConfirmThumbnailUpload" */)
|
||||||
const ModalDeleteCollection = lazyImport(() => import('modal/modalRemoveCollection' /* webpackChunkName: "modalRemoveCollection" */));
|
);
|
||||||
|
const ModalConfirmTransaction = lazyImport(() =>
|
||||||
|
import('modal/modalConfirmTransaction' /* webpackChunkName: "modalConfirmTransaction" */)
|
||||||
|
);
|
||||||
|
const ModalDeleteCollection = lazyImport(() =>
|
||||||
|
import('modal/modalRemoveCollection' /* webpackChunkName: "modalRemoveCollection" */)
|
||||||
|
);
|
||||||
const ModalDownloading = lazyImport(() => import('modal/modalDownloading' /* webpackChunkName: "modalDownloading" */));
|
const ModalDownloading = lazyImport(() => import('modal/modalDownloading' /* webpackChunkName: "modalDownloading" */));
|
||||||
const ModalError = lazyImport(() => import('modal/modalError' /* webpackChunkName: "modalError" */));
|
const ModalError = lazyImport(() => import('modal/modalError' /* webpackChunkName: "modalError" */));
|
||||||
const ModalFileSelection = lazyImport(() => import('modal/modalFileSelection' /* webpackChunkName: "modalFileSelection" */));
|
const ModalFileSelection = lazyImport(() =>
|
||||||
|
import('modal/modalFileSelection' /* webpackChunkName: "modalFileSelection" */)
|
||||||
|
);
|
||||||
const ModalFileTimeout = lazyImport(() => import('modal/modalFileTimeout' /* webpackChunkName: "modalFileTimeout" */));
|
const ModalFileTimeout = lazyImport(() => import('modal/modalFileTimeout' /* webpackChunkName: "modalFileTimeout" */));
|
||||||
const ModalFirstReward = lazyImport(() => import('modal/modalFirstReward' /* webpackChunkName: "modalFirstReward" */));
|
const ModalFirstReward = lazyImport(() => import('modal/modalFirstReward' /* webpackChunkName: "modalFirstReward" */));
|
||||||
const ModalFirstSubscription = lazyImport(() => import('modal/modalFirstSubscription' /* webpackChunkName: "modalFirstSubscription" */));
|
const ModalFirstSubscription = lazyImport(() =>
|
||||||
|
import('modal/modalFirstSubscription' /* webpackChunkName: "modalFirstSubscription" */)
|
||||||
|
);
|
||||||
const ModalImageUpload = lazyImport(() => import('modal/modalImageUpload' /* webpackChunkName: "modalImageUpload" */));
|
const ModalImageUpload = lazyImport(() => import('modal/modalImageUpload' /* webpackChunkName: "modalImageUpload" */));
|
||||||
const ModalMassTipsUnlock = lazyImport(() => import('modal/modalMassTipUnlock' /* webpackChunkName: "modalMassTipUnlock" */));
|
const ModalMassTipsUnlock = lazyImport(() =>
|
||||||
const ModalMobileSearch = lazyImport(() => import('modal/modalMobileSearch' /* webpackChunkName: "modalMobileSearch" */));
|
import('modal/modalMassTipUnlock' /* webpackChunkName: "modalMassTipUnlock" */)
|
||||||
const ModalOpenExternalResource = lazyImport(() => import('modal/modalOpenExternalResource' /* webpackChunkName: "modalOpenExternalResource" */));
|
);
|
||||||
const ModalPasswordUnsave = lazyImport(() => import('modal/modalPasswordUnsave' /* webpackChunkName: "modalPasswordUnsave" */));
|
const ModalMobileSearch = lazyImport(() =>
|
||||||
const ModalPhoneCollection = lazyImport(() => import('modal/modalPhoneCollection' /* webpackChunkName: "modalPhoneCollection" */));
|
import('modal/modalMobileSearch' /* webpackChunkName: "modalMobileSearch" */)
|
||||||
|
);
|
||||||
|
const ModalOpenExternalResource = lazyImport(() =>
|
||||||
|
import('modal/modalOpenExternalResource' /* webpackChunkName: "modalOpenExternalResource" */)
|
||||||
|
);
|
||||||
|
const ModalPasswordUnsave = lazyImport(() =>
|
||||||
|
import('modal/modalPasswordUnsave' /* webpackChunkName: "modalPasswordUnsave" */)
|
||||||
|
);
|
||||||
|
const ModalPhoneCollection = lazyImport(() =>
|
||||||
|
import('modal/modalPhoneCollection' /* webpackChunkName: "modalPhoneCollection" */)
|
||||||
|
);
|
||||||
const ModalPublish = lazyImport(() => import('modal/modalPublish' /* webpackChunkName: "modalPublish" */));
|
const ModalPublish = lazyImport(() => import('modal/modalPublish' /* webpackChunkName: "modalPublish" */));
|
||||||
const ModalPublishPreview = lazyImport(() => import('modal/modalPublishPreview' /* webpackChunkName: "modalPublishPreview" */));
|
const ModalPublishPreview = lazyImport(() =>
|
||||||
const ModalRemoveBtcSwapAddress = lazyImport(() => import('modal/modalRemoveBtcSwapAddress' /* webpackChunkName: "modalRemoveBtcSwapAddress" */));
|
import('modal/modalPublishPreview' /* webpackChunkName: "modalPublishPreview" */)
|
||||||
|
);
|
||||||
|
const ModalRemoveBtcSwapAddress = lazyImport(() =>
|
||||||
|
import('modal/modalRemoveBtcSwapAddress' /* webpackChunkName: "modalRemoveBtcSwapAddress" */)
|
||||||
|
);
|
||||||
const ModalRemoveCard = lazyImport(() => import('modal/modalRemoveCard' /* webpackChunkName: "modalRemoveCard" */));
|
const ModalRemoveCard = lazyImport(() => import('modal/modalRemoveCard' /* webpackChunkName: "modalRemoveCard" */));
|
||||||
const ModalRemoveComment = lazyImport(() => import('modal/modalRemoveComment' /* webpackChunkName: "modalRemoveComment" */));
|
const ModalRemoveComment = lazyImport(() =>
|
||||||
|
import('modal/modalRemoveComment' /* webpackChunkName: "modalRemoveComment" */)
|
||||||
|
);
|
||||||
const ModalRemoveFile = lazyImport(() => import('modal/modalRemoveFile' /* webpackChunkName: "modalRemoveFile" */));
|
const ModalRemoveFile = lazyImport(() => import('modal/modalRemoveFile' /* webpackChunkName: "modalRemoveFile" */));
|
||||||
const ModalRevokeClaim = lazyImport(() => import('modal/modalRevokeClaim' /* webpackChunkName: "modalRevokeClaim" */));
|
const ModalRevokeClaim = lazyImport(() => import('modal/modalRevokeClaim' /* webpackChunkName: "modalRevokeClaim" */));
|
||||||
const ModalRewardCode = lazyImport(() => import('modal/modalRewardCode' /* webpackChunkName: "modalRewardCode" */));
|
const ModalRewardCode = lazyImport(() => import('modal/modalRewardCode' /* webpackChunkName: "modalRewardCode" */));
|
||||||
|
@ -38,15 +77,27 @@ const ModalSendTip = lazyImport(() => import('modal/modalSendTip' /* webpackChun
|
||||||
const ModalSetReferrer = lazyImport(() => import('modal/modalSetReferrer' /* webpackChunkName: "modalSetReferrer" */));
|
const ModalSetReferrer = lazyImport(() => import('modal/modalSetReferrer' /* webpackChunkName: "modalSetReferrer" */));
|
||||||
const ModalSignOut = lazyImport(() => import('modal/modalSignOut' /* webpackChunkName: "modalSignOut" */));
|
const ModalSignOut = lazyImport(() => import('modal/modalSignOut' /* webpackChunkName: "modalSignOut" */));
|
||||||
const ModalSocialShare = lazyImport(() => import('modal/modalSocialShare' /* webpackChunkName: "modalSocialShare" */));
|
const ModalSocialShare = lazyImport(() => import('modal/modalSocialShare' /* webpackChunkName: "modalSocialShare" */));
|
||||||
const ModalSupportsLiquidate = lazyImport(() => import('modal/modalSupportsLiquidate' /* webpackChunkName: "modalSupportsLiquidate" */));
|
const ModalSupportsLiquidate = lazyImport(() =>
|
||||||
|
import('modal/modalSupportsLiquidate' /* webpackChunkName: "modalSupportsLiquidate" */)
|
||||||
|
);
|
||||||
const ModalSyncEnable = lazyImport(() => import('modal/modalSyncEnable' /* webpackChunkName: "modalSyncEnable" */));
|
const ModalSyncEnable = lazyImport(() => import('modal/modalSyncEnable' /* webpackChunkName: "modalSyncEnable" */));
|
||||||
const ModalTransactionFailed = lazyImport(() => import('modal/modalTransactionFailed' /* webpackChunkName: "modalTransactionFailed" */));
|
const ModalTransactionFailed = lazyImport(() =>
|
||||||
|
import('modal/modalTransactionFailed' /* webpackChunkName: "modalTransactionFailed" */)
|
||||||
|
);
|
||||||
const ModalUpgrade = lazyImport(() => import('modal/modalUpgrade' /* webpackChunkName: "modalUpgrade" */));
|
const ModalUpgrade = lazyImport(() => import('modal/modalUpgrade' /* webpackChunkName: "modalUpgrade" */));
|
||||||
const ModalViewImage = lazyImport(() => import('modal/modalViewImage' /* webpackChunkName: "modalViewImage" */));
|
const ModalViewImage = lazyImport(() => import('modal/modalViewImage' /* webpackChunkName: "modalViewImage" */));
|
||||||
const ModalWalletDecrypt = lazyImport(() => import('modal/modalWalletDecrypt' /* webpackChunkName: "modalWalletDecrypt" */));
|
const ModalWalletDecrypt = lazyImport(() =>
|
||||||
const ModalWalletEncrypt = lazyImport(() => import('modal/modalWalletEncrypt' /* webpackChunkName: "modalWalletEncrypt" */));
|
import('modal/modalWalletDecrypt' /* webpackChunkName: "modalWalletDecrypt" */)
|
||||||
const ModalWalletUnlock = lazyImport(() => import('modal/modalWalletUnlock' /* webpackChunkName: "modalWalletUnlock" */));
|
);
|
||||||
const ModalYoutubeWelcome = lazyImport(() => import('modal/modalYoutubeWelcome' /* webpackChunkName: "modalYoutubeWelcome" */));
|
const ModalWalletEncrypt = lazyImport(() =>
|
||||||
|
import('modal/modalWalletEncrypt' /* webpackChunkName: "modalWalletEncrypt" */)
|
||||||
|
);
|
||||||
|
const ModalWalletUnlock = lazyImport(() =>
|
||||||
|
import('modal/modalWalletUnlock' /* webpackChunkName: "modalWalletUnlock" */)
|
||||||
|
);
|
||||||
|
const ModalYoutubeWelcome = lazyImport(() =>
|
||||||
|
import('modal/modalYoutubeWelcome' /* webpackChunkName: "modalYoutubeWelcome" */)
|
||||||
|
);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modal: { id: string, modalProps: {} },
|
modal: { id: string, modalProps: {} },
|
||||||
|
@ -149,6 +200,8 @@ function ModalRouter(props: Props) {
|
||||||
return ModalMassTipsUnlock;
|
return ModalMassTipsUnlock;
|
||||||
case MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS:
|
case MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS:
|
||||||
return ModalRemoveBtcSwapAddress;
|
return ModalRemoveBtcSwapAddress;
|
||||||
|
case MODALS.BLOCK_CHANNEL:
|
||||||
|
return ModalBlockChannel;
|
||||||
case MODALS.COLLECTION_ADD:
|
case MODALS.COLLECTION_ADD:
|
||||||
return ModalClaimCollectionAdd;
|
return ModalClaimCollectionAdd;
|
||||||
case MODALS.COLLECTION_DELETE:
|
case MODALS.COLLECTION_DELETE:
|
||||||
|
|
|
@ -8,6 +8,9 @@ import {
|
||||||
selectModeratorBlockListDelegatorsMap,
|
selectModeratorBlockListDelegatorsMap,
|
||||||
selectFetchingModerationBlockList,
|
selectFetchingModerationBlockList,
|
||||||
selectModerationDelegatorsById,
|
selectModerationDelegatorsById,
|
||||||
|
selectAdminTimeoutMap,
|
||||||
|
selectModeratorTimeoutMap,
|
||||||
|
selectPersonalTimeoutMap,
|
||||||
} from 'redux/selectors/comments';
|
} from 'redux/selectors/comments';
|
||||||
import { selectMyChannelClaims } from 'lbry-redux';
|
import { selectMyChannelClaims } from 'lbry-redux';
|
||||||
import ListBlocked from './view';
|
import ListBlocked from './view';
|
||||||
|
@ -17,6 +20,9 @@ const select = (state) => ({
|
||||||
personalBlockList: selectModerationBlockList(state),
|
personalBlockList: selectModerationBlockList(state),
|
||||||
adminBlockList: selectAdminBlockList(state),
|
adminBlockList: selectAdminBlockList(state),
|
||||||
moderatorBlockList: selectModeratorBlockList(state),
|
moderatorBlockList: selectModeratorBlockList(state),
|
||||||
|
personalTimeoutMap: selectPersonalTimeoutMap(state),
|
||||||
|
adminTimeoutMap: selectAdminTimeoutMap(state),
|
||||||
|
moderatorTimeoutMap: selectModeratorTimeoutMap(state),
|
||||||
moderatorBlockListDelegatorsMap: selectModeratorBlockListDelegatorsMap(state),
|
moderatorBlockListDelegatorsMap: selectModeratorBlockListDelegatorsMap(state),
|
||||||
delegatorsById: selectModerationDelegatorsById(state),
|
delegatorsById: selectModerationDelegatorsById(state),
|
||||||
myChannelClaims: selectMyChannelClaims(state),
|
myChannelClaims: selectMyChannelClaims(state),
|
||||||
|
|
|
@ -3,6 +3,8 @@ import * as ICONS from 'constants/icons';
|
||||||
import { BLOCK_LEVEL } from 'constants/comment';
|
import { BLOCK_LEVEL } from 'constants/comment';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import moment from 'moment';
|
||||||
|
import humanizeDuration from 'humanize-duration';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
@ -25,6 +27,9 @@ type Props = {
|
||||||
personalBlockList: ?Array<string>,
|
personalBlockList: ?Array<string>,
|
||||||
adminBlockList: ?Array<string>,
|
adminBlockList: ?Array<string>,
|
||||||
moderatorBlockList: ?Array<string>,
|
moderatorBlockList: ?Array<string>,
|
||||||
|
personalTimeoutMap: { [uri: string]: { blockedAt: string, bannedFor: number, banRemaining: number } },
|
||||||
|
adminTimeoutMap: { [uri: string]: { blockedAt: string, bannedFor: number, banRemaining: number } },
|
||||||
|
moderatorTimeoutMap: { [uri: string]: { blockedAt: string, bannedFor: number, banRemaining: number } },
|
||||||
moderatorBlockListDelegatorsMap: { [string]: Array<string> },
|
moderatorBlockListDelegatorsMap: { [string]: Array<string> },
|
||||||
fetchingModerationBlockList: boolean,
|
fetchingModerationBlockList: boolean,
|
||||||
fetchModBlockedList: () => void,
|
fetchModBlockedList: () => void,
|
||||||
|
@ -39,6 +44,9 @@ function ListBlocked(props: Props) {
|
||||||
personalBlockList,
|
personalBlockList,
|
||||||
adminBlockList,
|
adminBlockList,
|
||||||
moderatorBlockList,
|
moderatorBlockList,
|
||||||
|
personalTimeoutMap,
|
||||||
|
adminTimeoutMap,
|
||||||
|
moderatorTimeoutMap,
|
||||||
moderatorBlockListDelegatorsMap,
|
moderatorBlockListDelegatorsMap,
|
||||||
fetchingModerationBlockList,
|
fetchingModerationBlockList,
|
||||||
fetchModBlockedList,
|
fetchModBlockedList,
|
||||||
|
@ -97,17 +105,45 @@ function ListBlocked(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getButtons(view, uri) {
|
function getButtons(view, uri) {
|
||||||
|
const getDurationStr = (durationNs) => {
|
||||||
|
const NANO_TO_MS = 1000000;
|
||||||
|
return humanizeDuration(durationNs / NANO_TO_MS, { round: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBanInfoElem = (timeoutInfo) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="help">
|
||||||
|
<blockquote>
|
||||||
|
{moment(timeoutInfo.blockedAt).format('MMMM Do, YYYY @ HH:mm')}
|
||||||
|
<br />
|
||||||
|
{getDurationStr(timeoutInfo.bannedFor)}{' '}
|
||||||
|
{__('(Remaining: %duration%) --[timeout ban duration]--', {
|
||||||
|
duration: getDurationStr(timeoutInfo.banRemaining),
|
||||||
|
})}
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
switch (view) {
|
switch (view) {
|
||||||
case VIEW.BLOCKED:
|
case VIEW.BLOCKED:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChannelBlockButton uri={uri} />
|
<ChannelBlockButton uri={uri} />
|
||||||
<ChannelMuteButton uri={uri} />
|
<ChannelMuteButton uri={uri} />
|
||||||
|
{personalTimeoutMap[uri] && getBanInfoElem(personalTimeoutMap[uri])}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
case VIEW.ADMIN:
|
case VIEW.ADMIN:
|
||||||
return <ChannelBlockButton uri={uri} blockLevel={BLOCK_LEVEL.ADMIN} />;
|
return (
|
||||||
|
<>
|
||||||
|
<ChannelBlockButton uri={uri} blockLevel={BLOCK_LEVEL.ADMIN} />
|
||||||
|
{adminTimeoutMap[uri] && getBanInfoElem(adminTimeoutMap[uri])}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
case VIEW.MODERATOR:
|
case VIEW.MODERATOR:
|
||||||
const delegatorUrisForBlockedUri = localModeratorListDelegatorsMap && localModeratorListDelegatorsMap[uri];
|
const delegatorUrisForBlockedUri = localModeratorListDelegatorsMap && localModeratorListDelegatorsMap[uri];
|
||||||
|
@ -121,6 +157,7 @@ function ListBlocked(props: Props) {
|
||||||
<ClaimPreview uri={delegatorUri} hideMenu hideActions type="small" />
|
<ClaimPreview uri={delegatorUri} hideMenu hideActions type="small" />
|
||||||
</ul>
|
</ul>
|
||||||
<ChannelBlockButton uri={uri} blockLevel={BLOCK_LEVEL.MODERATOR} creatorUri={delegatorUri} />
|
<ChannelBlockButton uri={uri} blockLevel={BLOCK_LEVEL.MODERATOR} creatorUri={delegatorUri} />
|
||||||
|
{moderatorTimeoutMap[uri] && getBanInfoElem(moderatorTimeoutMap[uri])}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -738,6 +738,7 @@ function doCommentModToggleBlock(
|
||||||
creatorId: string,
|
creatorId: string,
|
||||||
blockerIds: Array<string>, // [] = use all my channels
|
blockerIds: Array<string>, // [] = use all my channels
|
||||||
blockLevel: string,
|
blockLevel: string,
|
||||||
|
timeoutSec?: number,
|
||||||
showLink: boolean = false
|
showLink: boolean = false
|
||||||
) {
|
) {
|
||||||
return async (dispatch: Dispatch, getState: GetState) => {
|
return async (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
@ -844,6 +845,7 @@ function doCommentModToggleBlock(
|
||||||
block_all: unblock ? undefined : blockLevel === BLOCK_LEVEL.ADMIN,
|
block_all: unblock ? undefined : blockLevel === BLOCK_LEVEL.ADMIN,
|
||||||
global_un_block: unblock ? blockLevel === BLOCK_LEVEL.ADMIN : undefined,
|
global_un_block: unblock ? blockLevel === BLOCK_LEVEL.ADMIN : undefined,
|
||||||
...sharedModBlockParams,
|
...sharedModBlockParams,
|
||||||
|
time_out: unblock ? undefined : timeoutSec,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -920,12 +922,13 @@ function doCommentModToggleBlock(
|
||||||
* Blocks the commenter for all channels that I own.
|
* Blocks the commenter for all channels that I own.
|
||||||
*
|
*
|
||||||
* @param commenterUri
|
* @param commenterUri
|
||||||
|
* @param timeoutHours
|
||||||
* @param showLink
|
* @param showLink
|
||||||
* @returns {function(Dispatch): *}
|
* @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: 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 commenterUri
|
||||||
* @param blockerId
|
* @param blockerId
|
||||||
|
* @param timeoutHours
|
||||||
* @returns {function(Dispatch): *}
|
* @returns {function(Dispatch): *}
|
||||||
*/
|
*/
|
||||||
export function doCommentModBlockAsAdmin(commenterUri: string, blockerId: string) {
|
export function doCommentModBlockAsAdmin(commenterUri: string, blockerId: string, timeoutHours?: number) {
|
||||||
return (dispatch: Dispatch) => {
|
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 commenterUri
|
||||||
* @param creatorId
|
* @param creatorId
|
||||||
* @param blockerId
|
* @param blockerId
|
||||||
|
* @param timeoutHours
|
||||||
* @returns {function(Dispatch): *}
|
* @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: Dispatch) => {
|
||||||
return 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) {
|
export function doCommentModUnBlock(commenterUri: string, showLink: boolean = true) {
|
||||||
return (dispatch: Dispatch) => {
|
return (dispatch: Dispatch) => {
|
||||||
return dispatch(doCommentModToggleBlock(true, commenterUri, '', [], BLOCK_LEVEL.SELF, showLink));
|
return dispatch(doCommentModToggleBlock(true, commenterUri, '', [], BLOCK_LEVEL.SELF, undefined, showLink));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1035,13 +1054,20 @@ export function doFetchModBlockedList() {
|
||||||
let moderatorBlockList = [];
|
let moderatorBlockList = [];
|
||||||
let moderatorBlockListDelegatorsMap = {};
|
let moderatorBlockListDelegatorsMap = {};
|
||||||
|
|
||||||
|
// These should just be part of the block list above, but it is
|
||||||
|
// separated for now because there are too many clients that we need
|
||||||
|
// to update.
|
||||||
|
const personalTimeoutMap = {};
|
||||||
|
const adminTimeoutMap = {};
|
||||||
|
const moderatorTimeoutMap = {};
|
||||||
|
|
||||||
const blockListsPerChannel = res.map((r) => r.value);
|
const blockListsPerChannel = res.map((r) => r.value);
|
||||||
blockListsPerChannel
|
blockListsPerChannel
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return 1;
|
return 1;
|
||||||
})
|
})
|
||||||
.forEach((channelBlockLists) => {
|
.forEach((channelBlockLists) => {
|
||||||
const storeList = (fetchedList, blockedList, blockedByMap) => {
|
const storeList = (fetchedList, blockedList, timeoutMap, blockedByMap) => {
|
||||||
if (fetchedList) {
|
if (fetchedList) {
|
||||||
fetchedList.forEach((blockedChannel) => {
|
fetchedList.forEach((blockedChannel) => {
|
||||||
if (blockedChannel.blocked_channel_name) {
|
if (blockedChannel.blocked_channel_name) {
|
||||||
|
@ -1052,6 +1078,14 @@ export function doFetchModBlockedList() {
|
||||||
|
|
||||||
if (!blockedList.find((blockedChannel) => isURIEqual(blockedChannel.channelUri, channelUri))) {
|
if (!blockedList.find((blockedChannel) => isURIEqual(blockedChannel.channelUri, channelUri))) {
|
||||||
blockedList.push({ channelUri, blockedAt: blockedChannel.blocked_at });
|
blockedList.push({ channelUri, blockedAt: blockedChannel.blocked_at });
|
||||||
|
|
||||||
|
if (blockedChannel.banned_for) {
|
||||||
|
timeoutMap[channelUri] = {
|
||||||
|
blockedAt: blockedChannel.blocked_at,
|
||||||
|
bannedFor: blockedChannel.banned_for,
|
||||||
|
banRemaining: blockedChannel.ban_remaining,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockedByMap !== undefined) {
|
if (blockedByMap !== undefined) {
|
||||||
|
@ -1077,9 +1111,14 @@ export function doFetchModBlockedList() {
|
||||||
const globally_blocked_channels = channelBlockLists && channelBlockLists.globally_blocked_channels;
|
const globally_blocked_channels = channelBlockLists && channelBlockLists.globally_blocked_channels;
|
||||||
const delegated_blocked_channels = channelBlockLists && channelBlockLists.delegated_blocked_channels;
|
const delegated_blocked_channels = channelBlockLists && channelBlockLists.delegated_blocked_channels;
|
||||||
|
|
||||||
storeList(blocked_channels, personalBlockList);
|
storeList(blocked_channels, personalBlockList, personalTimeoutMap);
|
||||||
storeList(globally_blocked_channels, adminBlockList);
|
storeList(globally_blocked_channels, adminBlockList, adminTimeoutMap);
|
||||||
storeList(delegated_blocked_channels, moderatorBlockList, moderatorBlockListDelegatorsMap);
|
storeList(
|
||||||
|
delegated_blocked_channels,
|
||||||
|
moderatorBlockList,
|
||||||
|
moderatorTimeoutMap,
|
||||||
|
moderatorBlockListDelegatorsMap
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -1104,6 +1143,9 @@ export function doFetchModBlockedList() {
|
||||||
.map((blockedChannel) => blockedChannel.channelUri)
|
.map((blockedChannel) => blockedChannel.channelUri)
|
||||||
: null,
|
: null,
|
||||||
moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap,
|
moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap,
|
||||||
|
personalTimeoutMap,
|
||||||
|
adminTimeoutMap,
|
||||||
|
moderatorTimeoutMap,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
@ -40,6 +40,9 @@ const defaultState: CommentsState = {
|
||||||
fetchingModerationDelegators: false,
|
fetchingModerationDelegators: false,
|
||||||
blockingByUri: {},
|
blockingByUri: {},
|
||||||
unBlockingByUri: {},
|
unBlockingByUri: {},
|
||||||
|
personalTimeoutMap: {},
|
||||||
|
adminTimeoutMap: {},
|
||||||
|
moderatorTimeoutMap: {},
|
||||||
togglingForDelegatorMap: {},
|
togglingForDelegatorMap: {},
|
||||||
settingsByChannelId: {}, // ChannelId -> PerChannelSettings
|
settingsByChannelId: {}, // ChannelId -> PerChannelSettings
|
||||||
fetchingSettings: false,
|
fetchingSettings: false,
|
||||||
|
@ -671,14 +674,25 @@ export default handleActions(
|
||||||
fetchingModerationBlockList: true,
|
fetchingModerationBlockList: true,
|
||||||
}),
|
}),
|
||||||
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED]: (state: CommentsState, action: any) => {
|
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED]: (state: CommentsState, action: any) => {
|
||||||
const { personalBlockList, adminBlockList, moderatorBlockList, moderatorBlockListDelegatorsMap } = action.data;
|
const {
|
||||||
|
personalBlockList,
|
||||||
|
adminBlockList,
|
||||||
|
moderatorBlockList,
|
||||||
|
moderatorBlockListDelegatorsMap,
|
||||||
|
personalTimeoutMap,
|
||||||
|
adminTimeoutMap,
|
||||||
|
moderatorTimeoutMap,
|
||||||
|
} = action.data;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
moderationBlockList: personalBlockList,
|
moderationBlockList: personalBlockList,
|
||||||
adminBlockList: adminBlockList,
|
adminBlockList: adminBlockList,
|
||||||
moderatorBlockList: moderatorBlockList,
|
moderatorBlockList: moderatorBlockList,
|
||||||
moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap,
|
moderatorBlockListDelegatorsMap,
|
||||||
|
personalTimeoutMap,
|
||||||
|
adminTimeoutMap,
|
||||||
|
moderatorTimeoutMap,
|
||||||
fetchingModerationBlockList: false,
|
fetchingModerationBlockList: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -45,6 +45,10 @@ export const selectModeratorBlockList = createSelector(selectState, (state) =>
|
||||||
state.moderatorBlockList ? state.moderatorBlockList.reverse() : []
|
state.moderatorBlockList ? state.moderatorBlockList.reverse() : []
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectPersonalTimeoutMap = createSelector(selectState, (state) => state.personalTimeoutMap);
|
||||||
|
export const selectAdminTimeoutMap = createSelector(selectState, (state) => state.adminTimeoutMap);
|
||||||
|
export const selectModeratorTimeoutMap = createSelector(selectState, (state) => state.moderatorTimeoutMap);
|
||||||
|
|
||||||
export const selectModeratorBlockListDelegatorsMap = createSelector(
|
export const selectModeratorBlockListDelegatorsMap = createSelector(
|
||||||
selectState,
|
selectState,
|
||||||
(state) => state.moderatorBlockListDelegatorsMap
|
(state) => state.moderatorBlockListDelegatorsMap
|
||||||
|
@ -210,7 +214,7 @@ export const makeSelectCommentsForUri = (uri: string) =>
|
||||||
(state, byClaimId, byUri) => {
|
(state, byClaimId, byUri) => {
|
||||||
const claimId = byUri[uri];
|
const claimId = byUri[uri];
|
||||||
const comments = byClaimId && byClaimId[claimId];
|
const comments = byClaimId && byClaimId[claimId];
|
||||||
return makeSelectFilteredComments(comments)(state);
|
return makeSelectFilteredComments(comments, claimId)(state);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -222,7 +226,7 @@ export const makeSelectTopLevelCommentsForUri = (uri: string) =>
|
||||||
(state, byClaimId, byUri) => {
|
(state, byClaimId, byUri) => {
|
||||||
const claimId = byUri[uri];
|
const claimId = byUri[uri];
|
||||||
const comments = byClaimId && byClaimId[claimId];
|
const comments = byClaimId && byClaimId[claimId];
|
||||||
return makeSelectFilteredComments(comments)(state);
|
return makeSelectFilteredComments(comments, claimId)(state);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -258,7 +262,13 @@ export const makeSelectRepliesForParentId = (id: string) =>
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const makeSelectFilteredComments = (comments: Array<Comment>) =>
|
/**
|
||||||
|
* makeSelectFilteredComments
|
||||||
|
*
|
||||||
|
* @param comments List of comments to filter.
|
||||||
|
* @param claimId The claim that `comments` reside in.
|
||||||
|
*/
|
||||||
|
const makeSelectFilteredComments = (comments: Array<Comment>, claimId?: string) =>
|
||||||
createSelector(
|
createSelector(
|
||||||
selectClaimsById,
|
selectClaimsById,
|
||||||
selectMyActiveClaims,
|
selectMyActiveClaims,
|
||||||
|
@ -311,12 +321,18 @@ const makeSelectFilteredComments = (comments: Array<Comment>) =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (claimId) {
|
||||||
|
const claimIdIsMine = myClaims && myClaims.size > 0 && myClaims.has(claimId);
|
||||||
|
if (!claimIdIsMine) {
|
||||||
return !(
|
return !(
|
||||||
mutedChannels.includes(comment.channel_url) ||
|
|
||||||
personalBlockList.includes(comment.channel_url) ||
|
personalBlockList.includes(comment.channel_url) ||
|
||||||
adminBlockList.includes(comment.channel_url) ||
|
adminBlockList.includes(comment.channel_url) ||
|
||||||
moderatorBlockList.includes(comment.channel_url)
|
moderatorBlockList.includes(comment.channel_url)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !mutedChannels.includes(comment.channel_url);
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,3 +16,43 @@
|
||||||
margin-top: var(--spacing-xxs);
|
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);
|
||||||
|
}
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -8558,6 +8558,11 @@ https-proxy-agent@^4.0.0:
|
||||||
agent-base "5"
|
agent-base "5"
|
||||||
debug "4"
|
debug "4"
|
||||||
|
|
||||||
|
humanize-duration@^3.27.0:
|
||||||
|
version "3.27.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.27.0.tgz#3f781b7cf8022ad587f76b9839b60bc2b29636b2"
|
||||||
|
integrity sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==
|
||||||
|
|
||||||
humanize-plus@^1.8.1:
|
humanize-plus@^1.8.1:
|
||||||
version "1.8.2"
|
version "1.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
|
resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
|
||||||
|
@ -11916,6 +11921,11 @@ parse-asn1@^5.0.0:
|
||||||
pbkdf2 "^3.0.3"
|
pbkdf2 "^3.0.3"
|
||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
|
|
||||||
|
parse-duration@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-1.0.0.tgz#8605651745f61088f6fb14045c887526c291858c"
|
||||||
|
integrity sha512-X4kUkCTHU1N/kEbwK9FpUJ0UZQa90VzeczfS704frR30gljxDG0pSziws06XlK+CGRSo/1wtG1mFIdBFQTMQNw==
|
||||||
|
|
||||||
parse-entities@^1.0.2, parse-entities@^1.1.0:
|
parse-entities@^1.0.2, parse-entities@^1.1.0:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50"
|
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50"
|
||||||
|
|
Loading…
Add table
Reference in a new issue