Blocklist Page: show the timeout ban duration

- 'humanize-duration' is used because 'moment''s humanizer sucks.
This commit is contained in:
infinite-persistence 2021-08-20 15:18:54 +08:00
parent 663376e970
commit 0c1554e453
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
9 changed files with 101 additions and 7 deletions

View file

@ -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,

View file

@ -54,6 +54,7 @@
"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", "parse-duration": "^1.0.0",
"react-datetime-picker": "^3.2.1", "react-datetime-picker": "^3.2.1",

View file

@ -1749,6 +1749,7 @@
"Invalid duration.": "Invalid duration.", "Invalid duration.": "Invalid duration.",
"Permanent": "Permanent", "Permanent": "Permanent",
"Timeout --[time-based ban instead of permanent]--": "Timeout", "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",

View file

@ -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),

View file

@ -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>
); );
})} })}

View file

@ -1054,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) {
@ -1071,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) {
@ -1096,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({
@ -1123,6 +1143,9 @@ export function doFetchModBlockedList() {
.map((blockedChannel) => blockedChannel.channelUri) .map((blockedChannel) => blockedChannel.channelUri)
: null, : null,
moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap, moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap,
personalTimeoutMap,
adminTimeoutMap,
moderatorTimeoutMap,
}, },
}); });
}) })

View file

@ -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,
}; };
}, },

View file

@ -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

View file

@ -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"