Blocklist Page: show the timeout ban duration
- 'humanize-duration' is used because 'moment''s humanizer sucks.
This commit is contained in:
parent
663376e970
commit
0c1554e453
9 changed files with 101 additions and 7 deletions
3
flow-typed/Comment.js
vendored
3
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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue