diff --git a/flow-typed/Comment.js b/flow-typed/Comment.js index 822810b26..f62993f9e 100644 --- a/flow-typed/Comment.js +++ b/flow-typed/Comment.js @@ -60,6 +60,9 @@ declare type CommentsState = { fetchingModerationDelegators: boolean, blockingByUri: {}, 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}, // {"blockedUri": ["delegatorUri1", ""delegatorUri2", ...]} settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings fetchingSettings: boolean, diff --git a/package.json b/package.json index 04c3ec4a8..f9caeed7a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "electron-notarize": "^1.0.0", "electron-updater": "^4.2.4", "express": "^4.17.1", + "humanize-duration": "^3.27.0", "if-env": "^1.0.4", "parse-duration": "^1.0.0", "react-datetime-picker": "^3.2.1", diff --git a/static/app-strings.json b/static/app-strings.json index 4f9e45e9d..381022242 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -1749,6 +1749,7 @@ "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.", "Invalid channel URL \"%url%\"": "Invalid channel URL \"%url%\"", "Delegation": "Delegation", diff --git a/ui/page/listBlocked/index.js b/ui/page/listBlocked/index.js index e3e1d5c55..178b11aa9 100644 --- a/ui/page/listBlocked/index.js +++ b/ui/page/listBlocked/index.js @@ -8,6 +8,9 @@ import { selectModeratorBlockListDelegatorsMap, selectFetchingModerationBlockList, selectModerationDelegatorsById, + selectAdminTimeoutMap, + selectModeratorTimeoutMap, + selectPersonalTimeoutMap, } from 'redux/selectors/comments'; import { selectMyChannelClaims } from 'lbry-redux'; import ListBlocked from './view'; @@ -17,6 +20,9 @@ const select = (state) => ({ personalBlockList: selectModerationBlockList(state), adminBlockList: selectAdminBlockList(state), moderatorBlockList: selectModeratorBlockList(state), + personalTimeoutMap: selectPersonalTimeoutMap(state), + adminTimeoutMap: selectAdminTimeoutMap(state), + moderatorTimeoutMap: selectModeratorTimeoutMap(state), moderatorBlockListDelegatorsMap: selectModeratorBlockListDelegatorsMap(state), delegatorsById: selectModerationDelegatorsById(state), myChannelClaims: selectMyChannelClaims(state), diff --git a/ui/page/listBlocked/view.jsx b/ui/page/listBlocked/view.jsx index 85f355947..8036a8f1f 100644 --- a/ui/page/listBlocked/view.jsx +++ b/ui/page/listBlocked/view.jsx @@ -3,6 +3,8 @@ import * as ICONS from 'constants/icons'; import { BLOCK_LEVEL } from 'constants/comment'; import React from 'react'; import classnames from 'classnames'; +import moment from 'moment'; +import humanizeDuration from 'humanize-duration'; import ClaimList from 'component/claimList'; import ClaimPreview from 'component/claimPreview'; import Page from 'component/page'; @@ -25,6 +27,9 @@ type Props = { personalBlockList: ?Array, adminBlockList: ?Array, moderatorBlockList: ?Array, + 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 }, fetchingModerationBlockList: boolean, fetchModBlockedList: () => void, @@ -39,6 +44,9 @@ function ListBlocked(props: Props) { personalBlockList, adminBlockList, moderatorBlockList, + personalTimeoutMap, + adminTimeoutMap, + moderatorTimeoutMap, moderatorBlockListDelegatorsMap, fetchingModerationBlockList, fetchModBlockedList, @@ -97,17 +105,45 @@ function ListBlocked(props: Props) { } function getButtons(view, uri) { + const getDurationStr = (durationNs) => { + const NANO_TO_MS = 1000000; + return humanizeDuration(durationNs / NANO_TO_MS, { round: true }); + }; + + const getBanInfoElem = (timeoutInfo) => { + return ( +
+
+
+ {moment(timeoutInfo.blockedAt).format('MMMM Do, YYYY @ HH:mm')} +
+ {getDurationStr(timeoutInfo.bannedFor)}{' '} + {__('(Remaining: %duration%) --[timeout ban duration]--', { + duration: getDurationStr(timeoutInfo.banRemaining), + })} +
+
+
+ ); + }; + switch (view) { case VIEW.BLOCKED: return ( <> + {personalTimeoutMap[uri] && getBanInfoElem(personalTimeoutMap[uri])} ); case VIEW.ADMIN: - return ; + return ( + <> + + {adminTimeoutMap[uri] && getBanInfoElem(adminTimeoutMap[uri])} + + ); case VIEW.MODERATOR: const delegatorUrisForBlockedUri = localModeratorListDelegatorsMap && localModeratorListDelegatorsMap[uri]; @@ -121,6 +157,7 @@ function ListBlocked(props: Props) { + {moderatorTimeoutMap[uri] && getBanInfoElem(moderatorTimeoutMap[uri])} ); })} diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index dd97f8301..dc85ba754 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -1054,13 +1054,20 @@ export function doFetchModBlockedList() { let moderatorBlockList = []; 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); blockListsPerChannel .sort((a, b) => { return 1; }) .forEach((channelBlockLists) => { - const storeList = (fetchedList, blockedList, blockedByMap) => { + const storeList = (fetchedList, blockedList, timeoutMap, blockedByMap) => { if (fetchedList) { fetchedList.forEach((blockedChannel) => { if (blockedChannel.blocked_channel_name) { @@ -1071,6 +1078,14 @@ export function doFetchModBlockedList() { if (!blockedList.find((blockedChannel) => isURIEqual(blockedChannel.channelUri, channelUri))) { 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) { @@ -1096,9 +1111,14 @@ export function doFetchModBlockedList() { const globally_blocked_channels = channelBlockLists && channelBlockLists.globally_blocked_channels; const delegated_blocked_channels = channelBlockLists && channelBlockLists.delegated_blocked_channels; - storeList(blocked_channels, personalBlockList); - storeList(globally_blocked_channels, adminBlockList); - storeList(delegated_blocked_channels, moderatorBlockList, moderatorBlockListDelegatorsMap); + storeList(blocked_channels, personalBlockList, personalTimeoutMap); + storeList(globally_blocked_channels, adminBlockList, adminTimeoutMap); + storeList( + delegated_blocked_channels, + moderatorBlockList, + moderatorTimeoutMap, + moderatorBlockListDelegatorsMap + ); }); dispatch({ @@ -1123,6 +1143,9 @@ export function doFetchModBlockedList() { .map((blockedChannel) => blockedChannel.channelUri) : null, moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap, + personalTimeoutMap, + adminTimeoutMap, + moderatorTimeoutMap, }, }); }) diff --git a/ui/redux/reducers/comments.js b/ui/redux/reducers/comments.js index 815025698..04c0bed8d 100644 --- a/ui/redux/reducers/comments.js +++ b/ui/redux/reducers/comments.js @@ -40,6 +40,9 @@ const defaultState: CommentsState = { fetchingModerationDelegators: false, blockingByUri: {}, unBlockingByUri: {}, + personalTimeoutMap: {}, + adminTimeoutMap: {}, + moderatorTimeoutMap: {}, togglingForDelegatorMap: {}, settingsByChannelId: {}, // ChannelId -> PerChannelSettings fetchingSettings: false, @@ -671,14 +674,25 @@ export default handleActions( fetchingModerationBlockList: true, }), [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 { ...state, moderationBlockList: personalBlockList, adminBlockList: adminBlockList, moderatorBlockList: moderatorBlockList, - moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap, + moderatorBlockListDelegatorsMap, + personalTimeoutMap, + adminTimeoutMap, + moderatorTimeoutMap, fetchingModerationBlockList: false, }; }, diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index e4c21ff54..bc930d9cd 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -45,6 +45,10 @@ export const selectModeratorBlockList = createSelector(selectState, (state) => 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( selectState, (state) => state.moderatorBlockListDelegatorsMap diff --git a/yarn.lock b/yarn.lock index 9b1e9da25..c65da973f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8558,6 +8558,11 @@ https-proxy-agent@^4.0.0: agent-base "5" 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: version "1.8.2" resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"