Skip to content

Commit 9b5a22b

Browse files
keikarimiko
andauthored
Track my delegated mods better (#3397)
* Track my delegates better * Appstrings * Use Remove icon for removal * Coderabbit --------- Co-authored-by: miko <sauce47@posteo.net>
1 parent ca2217a commit 9b5a22b

File tree

8 files changed

+205
-15
lines changed

8 files changed

+205
-15
lines changed

static/app-strings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2983,6 +2983,10 @@
29832983
"Creator only": "Creator only",
29842984
"Would you like to enable them? Homepage recommendations placement can be configured from the homepage customization.": "Would you like to enable them? Homepage recommendations placement can be configured from the homepage customization.",
29852985
"Homepage recommendations available": "Homepage recommendations available",
2986+
"Remove this user as a moderator of your channel.": "Remove this user as a moderator of your channel.",
2987+
"Remove this user as a moderator of %channel%.": "Remove this user as a moderator of %channel%.",
2988+
"Remove as moderator": "Remove as moderator",
2989+
"Removed %user% from moderators of %myChannel%": "Removed %user% from moderators of %myChannel%",
29862990

29872991
"--end--": "--end--"
29882992
}

ui/component/app/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ import {
3232
} from 'redux/actions/settings';
3333
import { doSyncLoop } from 'redux/actions/sync';
3434
import { doSignIn, doSetIncognito, doSetAssignedLbrynetServer, doOpenModal } from 'redux/actions/app';
35-
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
35+
import {
36+
doFetchModBlockedList,
37+
doFetchCommentModAmIList,
38+
doCommentModListDelegatesForMyChannels,
39+
} from 'redux/actions/comments';
3640
import App from './view';
3741

3842
const select = (state) => ({
@@ -68,6 +72,7 @@ const perform = {
6872
setIncognito: doSetIncognito,
6973
fetchModBlockedList: doFetchModBlockedList,
7074
fetchModAmIList: doFetchCommentModAmIList,
75+
fetchDelegatesForMyChannels: doCommentModListDelegatesForMyChannels,
7176
doOpenAnnouncements,
7277
doSetLastViewedAnnouncement,
7378
doSetDefaultChannel,

ui/component/app/view.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type Props = {
8989
setIncognito: (boolean) => void,
9090
fetchModBlockedList: () => void,
9191
fetchModAmIList: () => void,
92+
fetchDelegatesForMyChannels: () => void,
9293
homepageFetched: boolean,
9394
defaultChannelClaim: ?any,
9495
nagsShown: boolean,
@@ -134,6 +135,7 @@ function App(props: Props) {
134135
setIncognito,
135136
fetchModBlockedList,
136137
fetchModAmIList,
138+
fetchDelegatesForMyChannels,
137139
defaultChannelClaim,
138140
announcement,
139141
homepageOrder,
@@ -357,6 +359,7 @@ function App(props: Props) {
357359
if (hasMyChannels) {
358360
fetchModBlockedList();
359361
fetchModAmIList();
362+
fetchDelegatesForMyChannels();
360363
}
361364
// eslint-disable-next-line react-hooks/exhaustive-deps
362365
}, [hasMyChannels, hasNoChannels, setIncognito]);

ui/component/commentMenuList/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { connect } from 'react-redux';
22
import { doChannelMute } from 'redux/actions/blocked';
3-
import { doCommentPin, doCommentModAddDelegate } from 'redux/actions/comments';
3+
import { doCommentPin, doCommentModAddDelegate, doCommentModRemoveDelegate } from 'redux/actions/comments';
44
import { doOpenModal, doSetActiveChannel } from 'redux/actions/app';
55
import { doClearPlayingUri } from 'redux/actions/content';
66
import { doToast } from 'redux/actions/notifications';
77
import { selectClaimIsMine, selectClaimForUri } from 'redux/selectors/claims';
88
import { selectActiveChannelClaim } from 'redux/selectors/app';
9-
import { selectModerationDelegatorsById } from 'redux/selectors/comments';
9+
import { selectModerationDelegatorsById, selectModerationDelegatesById } from 'redux/selectors/comments';
1010
import { selectPlayingUri } from 'redux/selectors/content';
1111
import { selectUserVerifiedEmail } from 'redux/selectors/user';
1212
import CommentMenuList from './view';
@@ -25,6 +25,7 @@ const select = (state, props) => {
2525
channelIsMine: selectClaimIsMine(state, authorClaim),
2626
playingUri: selectPlayingUri(state),
2727
moderationDelegatorsById: selectModerationDelegatorsById(state),
28+
moderationDelegatesById: selectModerationDelegatesById(state),
2829
authorCanonicalUri,
2930
authorId,
3031
};
@@ -38,6 +39,8 @@ const perform = (dispatch) => ({
3839
pinComment: (commentId, claimId, remove) => dispatch(doCommentPin(commentId, claimId, remove)),
3940
commentModAddDelegate: (modChanId, modChanName, creatorChannelClaim) =>
4041
dispatch(doCommentModAddDelegate(modChanId, modChanName, creatorChannelClaim, true)),
42+
commentModRemoveDelegate: (modChanId, modChanName, creatorChannelClaim) =>
43+
dispatch(doCommentModRemoveDelegate(modChanId, modChanName, creatorChannelClaim, true)),
4144
doSetActiveChannel: (authorId) => dispatch(doSetActiveChannel(authorId)),
4245
});
4346

ui/component/commentMenuList/view.jsx

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type Props = {
3636
activeChannelClaim: ?ChannelClaim,
3737
playingUri: PlayingUri,
3838
moderationDelegatorsById: { [string]: { global: boolean, delegators: { name: string, claimId: string } } },
39+
moderationDelegatesById: { [?string]: ?Array<Object> },
3940
authorCanonicalUri: ?string,
4041
authorId: string,
4142
// --- perform ---
@@ -47,6 +48,7 @@ type Props = {
4748
doSetActiveChannel: (string) => void,
4849
pinComment: (string, string, boolean) => Promise<any>,
4950
commentModAddDelegate: (string, string, ChannelClaim) => void,
51+
commentModRemoveDelegate: (string, string, ChannelClaim) => void,
5052
setQuickReply: (any) => void,
5153
handleDismissPin?: () => void,
5254
};
@@ -66,6 +68,7 @@ function CommentMenuList(props: Props) {
6668
isPinned,
6769
playingUri,
6870
moderationDelegatorsById,
71+
moderationDelegatesById,
6972
authorCanonicalUri,
7073
isAuthenticated,
7174
disableEdit,
@@ -81,6 +84,7 @@ function CommentMenuList(props: Props) {
8184
doSetActiveChannel,
8285
pinComment,
8386
commentModAddDelegate,
87+
commentModRemoveDelegate,
8488
setQuickReply,
8589
handleDismissPin,
8690
} = props;
@@ -95,6 +99,8 @@ function CommentMenuList(props: Props) {
9599
const contentChannelClaim = getChannelFromClaim(claim);
96100
const contentChannelPermanentUrl = contentChannelClaim && contentChannelClaim.permanent_url;
97101

102+
const delegates = moderationDelegatesById[contentChannelClaim?.claim_id];
103+
98104
const activeModeratorInfo = activeChannelClaim && moderationDelegatorsById[activeChannelClaim.claim_id];
99105
const activeChannelIsCreator = activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl;
100106
const activeChannelIsAdmin = activeChannelClaim && activeModeratorInfo && activeModeratorInfo.global;
@@ -103,6 +109,10 @@ function CommentMenuList(props: Props) {
103109
contentChannelClaim &&
104110
activeModeratorInfo &&
105111
Object.values(activeModeratorInfo.delegators).includes(contentChannelClaim.claim_id);
112+
const authorIsModerator =
113+
claimIsMine && // only check for own claims
114+
Array.isArray(delegates) &&
115+
delegates.some((delegate) => delegate.channelId === authorId);
106116

107117
function handleDeleteComment() {
108118
if (playingUri.source === 'comment') {
@@ -126,6 +136,13 @@ function CommentMenuList(props: Props) {
126136
}
127137
}
128138

139+
function removeModerator() {
140+
if (activeChannelClaim && authorUri) {
141+
const { channelName, channelClaimId } = parseURI(authorUri);
142+
if (channelName && channelClaimId) commentModRemoveDelegate(channelClaimId, channelName, activeChannelClaim);
143+
}
144+
}
145+
129146
function getBlockOptionElem() {
130147
const isPersonalBlockTheOnlyOption = !activeChannelIsModerator && !activeChannelIsAdmin;
131148
const isTimeoutBlockAvailable = claimIsMine || activeChannelIsModerator;
@@ -256,17 +273,32 @@ function CommentMenuList(props: Props) {
256273
{__('Dismiss Pin')}
257274
</MenuItem>
258275
)}
259-
{/* todo: filter out already active mods (bug with activeModeratorInfo?) */}
260-
{activeChannelIsCreator && activeChannelClaim && activeChannelClaim.permanent_url !== authorUri && (
261-
<MenuItem className="comment__menu-option" onSelect={assignAsModerator}>
276+
{activeChannelIsCreator &&
277+
activeChannelClaim &&
278+
activeChannelClaim.permanent_url !== authorUri &&
279+
!authorIsModerator && (
280+
<MenuItem className="comment__menu-option" onSelect={assignAsModerator}>
281+
<div className="menu__link">
282+
<Icon aria-hidden icon={ICONS.ADD} />
283+
{__('Add as moderator')}
284+
</div>
285+
<span className="comment__menu-help">
286+
{activeChannelClaim
287+
? __('Assign this user to moderate %channel%.', { channel: activeChannelClaim.name })
288+
: __('Assign this user to moderate your channel.')}
289+
</span>
290+
</MenuItem>
291+
)}
292+
{activeChannelIsCreator && authorIsModerator && (
293+
<MenuItem className="comment__menu-option" onSelect={removeModerator}>
262294
<div className="menu__link">
263-
<Icon aria-hidden icon={ICONS.ADD} />
264-
{__('Add as moderator')}
295+
<Icon aria-hidden icon={ICONS.REMOVE} />
296+
{__('Remove as moderator')}
265297
</div>
266298
<span className="comment__menu-help">
267299
{activeChannelClaim
268-
? __('Assign this user to moderate %channel%.', { channel: activeChannelClaim.name })
269-
: __('Assign this user to moderate your channel.')}
300+
? __('Remove this user as a moderator of %channel%.', { channel: activeChannelClaim.name })
301+
: __('Remove this user as a moderator of your channel.')}
270302
</span>
271303
</MenuItem>
272304
)}

ui/constants/action_types.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,8 @@ export const COMMENT_PIN_STARTED = 'COMMENT_PIN_STARTED';
553553
export const COMMENT_PIN_COMPLETED = 'COMMENT_PIN_COMPLETED';
554554
export const COMMENT_PIN_FAILED = 'COMMENT_PIN_FAILED';
555555
export const COMMENT_MARK_AS_REMOVED = 'COMMENT_MARK_AS_REMOVED';
556+
export const ADD_MODERATOR_COMPLETED = 'ADD_MODERATOR_COMPLETED';
557+
export const REMOVE_MODERATOR_COMPLETED = 'REMOVE_MODERATOR_COMPLETED';
556558
export const COMMENT_MODERATION_BLOCK_LIST_STARTED = 'COMMENT_MODERATION_BLOCK_LIST_STARTED';
557559
export const COMMENT_MODERATION_BLOCK_LIST_COMPLETED = 'COMMENT_MODERATION_BLOCK_LIST_COMPLETED';
558560
export const COMMENT_MODERATION_BLOCK_LIST_FAILED = 'COMMENT_MODERATION_BLOCK_LIST_FAILED';
@@ -568,6 +570,12 @@ export const COMMENT_FETCH_MODERATION_DELEGATES_COMPLETED = 'COMMENT_FETCH_MODER
568570
export const COMMENT_MODERATION_AM_I_LIST_STARTED = 'COMMENT_MODERATION_AM_I_LIST_STARTED';
569571
export const COMMENT_MODERATION_AM_I_LIST_FAILED = 'COMMENT_MODERATION_AM_I_LIST_FAILED';
570572
export const COMMENT_MODERATION_AM_I_LIST_COMPLETED = 'COMMENT_MODERATION_AM_I_LIST_COMPLETED';
573+
export const COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_STARTED =
574+
'COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_STARTED';
575+
export const COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_FAILED =
576+
'COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_FAILED';
577+
export const COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_COMPLETED =
578+
'COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_COMPLETED';
571579
export const COMMENT_FETCH_SETTINGS_STARTED = 'COMMENT_FETCH_SETTINGS_STARTED';
572580
export const COMMENT_FETCH_SETTINGS_FAILED = 'COMMENT_FETCH_SETTINGS_FAILED';
573581
export const COMMENT_FETCH_SETTINGS_COMPLETED = 'COMMENT_FETCH_SETTINGS_COMPLETED';

ui/redux/actions/comments.js

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,7 +1712,11 @@ export function doCommentModAddDelegate(
17121712
channel_name: creatorChannelClaim.name,
17131713
...signature,
17141714
})
1715-
.then(() => {
1715+
.then((res) => {
1716+
dispatch({
1717+
type: ACTIONS.ADD_MODERATOR_COMPLETED,
1718+
data: { newDelegates: res?.Delegates, creatorChannelId: creatorChannelClaim.claim_id },
1719+
});
17161720
if (showToast) {
17171721
dispatch(
17181722
doToast({
@@ -1735,7 +1739,8 @@ export function doCommentModAddDelegate(
17351739
export function doCommentModRemoveDelegate(
17361740
modChannelId: string,
17371741
modChannelName: string,
1738-
creatorChannelClaim: ChannelClaim
1742+
creatorChannelClaim: ChannelClaim,
1743+
showToast: boolean = false
17391744
) {
17401745
return async (dispatch: Dispatch, getState: GetState) => {
17411746
const signature = await ChannelSign.sign(creatorChannelClaim.claim_id, creatorChannelClaim.name, false);
@@ -1750,9 +1755,28 @@ export function doCommentModRemoveDelegate(
17501755
channel_id: creatorChannelClaim.claim_id,
17511756
channel_name: creatorChannelClaim.name,
17521757
...signature,
1753-
}).catch((err) => {
1754-
dispatch(doToast({ message: err.message, isError: true }));
1755-
});
1758+
})
1759+
.then(() => {
1760+
dispatch({
1761+
type: ACTIONS.REMOVE_MODERATOR_COMPLETED,
1762+
data: { removedDelegateId: modChannelId, creatorChannelId: creatorChannelClaim.claim_id },
1763+
});
1764+
if (showToast) {
1765+
dispatch(
1766+
doToast({
1767+
message: __('Removed %user% from moderators of %myChannel%', {
1768+
user: modChannelName,
1769+
myChannel: creatorChannelClaim.name,
1770+
}),
1771+
linkText: __('Manage'),
1772+
linkTarget: `/${PAGES.SETTINGS_CREATOR}`,
1773+
})
1774+
);
1775+
}
1776+
})
1777+
.catch((err) => {
1778+
dispatch(doToast({ message: err.message, isError: true }));
1779+
});
17561780
};
17571781
}
17581782

@@ -1788,6 +1812,60 @@ export function doCommentModListDelegates(channelClaim: ChannelClaim) {
17881812
};
17891813
}
17901814

1815+
export function doCommentModListDelegatesForMyChannels() {
1816+
return async (dispatch: Dispatch, getState: GetState) => {
1817+
const state = getState();
1818+
const myChannels = selectMyChannelClaims(state);
1819+
if (!myChannels) {
1820+
dispatch({ type: ACTIONS.COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_FAILED });
1821+
return;
1822+
}
1823+
1824+
dispatch({ type: ACTIONS.COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_STARTED });
1825+
1826+
let channelSignatures = [];
1827+
1828+
return Promise.all(myChannels.map((channel) => channelSignName(channel.claim_id, channel.name, true)))
1829+
.then((response) => {
1830+
channelSignatures = response;
1831+
// $FlowFixMe
1832+
return Promise.allSettled(
1833+
channelSignatures
1834+
.filter((x) => x !== undefined && x !== null)
1835+
.map((signatureData) =>
1836+
Comments.moderation_list_delegates({
1837+
channel_name: signatureData.name,
1838+
channel_id: signatureData.claim_id,
1839+
signature: signatureData.signature,
1840+
signing_ts: signatureData.signing_ts,
1841+
}).then((value) => ({ signatureData, value }))
1842+
)
1843+
)
1844+
.then((results) => {
1845+
const delegatesById = {};
1846+
1847+
results.forEach((result) => {
1848+
if (result.status === PROMISE_FULFILLED) {
1849+
const { signatureData, value } = result.value;
1850+
delegatesById[signatureData.claim_id] = value.Delegates;
1851+
}
1852+
});
1853+
dispatch({
1854+
type: ACTIONS.COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_COMPLETED,
1855+
data: delegatesById,
1856+
});
1857+
})
1858+
.catch((err) => {
1859+
devToast(dispatch, `Fetch delegates for my channels: ${err}`);
1860+
dispatch({ type: ACTIONS.COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_FAILED });
1861+
});
1862+
})
1863+
.catch(() => {
1864+
dispatch({ type: ACTIONS.COMMENT_MODERATION_DELEGATES_FOR_MY_CHANNELS_FAILED });
1865+
});
1866+
};
1867+
}
1868+
17911869
export function doFetchCommentModAmIList(channelClaim: ChannelClaim) {
17921870
return async (dispatch: Dispatch, getState: GetState) => {
17931871
const state = getState();

0 commit comments

Comments
 (0)