From 461b192f37f6b423a077f3de9a97d8901f3284cf Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 16 Jun 2023 07:17:37 +0200 Subject: [PATCH] fix: mod and admin actions on message context menu for communities --- .../message-content/MessageContextMenu.tsx | 40 ++++++++++--------- ts/components/dialog/ReactListModal.tsx | 9 +++-- ts/hooks/useParamSelector.ts | 5 --- ts/models/conversation.ts | 5 --- ts/state/ducks/conversations.ts | 1 - ts/state/ducks/sogsRoomInfo.tsx | 3 +- ts/state/selectors/conversations.ts | 9 ++--- ts/state/selectors/selectedConversation.ts | 21 +++++++++- 8 files changed, 53 insertions(+), 40 deletions(-) diff --git a/ts/components/conversation/message/message-content/MessageContextMenu.tsx b/ts/components/conversation/message/message-content/MessageContextMenu.tsx index 8a1c31248..cbdd7534e 100644 --- a/ts/components/conversation/message/message-content/MessageContextMenu.tsx +++ b/ts/components/conversation/message/message-content/MessageContextMenu.tsx @@ -24,6 +24,13 @@ import { } from '../../../../state/ducks/conversations'; import { StateType } from '../../../../state/reducer'; import { getMessageContextMenuProps } from '../../../../state/selectors/conversations'; +import { + useSelectedConversationKey, + useSelectedIsBlocked, + useSelectedIsPublic, + useSelectedWeAreAdmin, + useSelectedWeAreModerator, +} from '../../../../state/selectors/selectedConversation'; import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil'; import { Reactions } from '../../../../util/reactions'; import { SessionContextMenuContainer } from '../../../SessionContextMenuContainer'; @@ -34,18 +41,13 @@ export type MessageContextMenuSelectorProps = Pick< MessageRenderingProps, | 'attachments' | 'sender' - | 'convoId' | 'direction' | 'status' | 'isDeletable' - | 'isPublic' - | 'isOpenGroupV2' - | 'weAreAdmin' | 'isSenderAdmin' | 'text' | 'serverTimestamp' | 'timestamp' - | 'isBlocked' | 'isDeletableForEveryone' >; @@ -80,27 +82,31 @@ export const MessageContextMenu = (props: Props) => { const dispatch = useDispatch(); const { hideAll } = useContextMenu(); + const isSelectedBlocked = useSelectedIsBlocked(); + const convoId = useSelectedConversationKey(); + const isPublic = useSelectedIsPublic(); + const weAreModerator = useSelectedWeAreModerator(); + const weAreAdmin = useSelectedWeAreAdmin(); + + const showAdminActions = weAreAdmin || weAreModerator; + const selected = useSelector((state: StateType) => getMessageContextMenuProps(state, messageId)); - if (!selected) { + if (!selected || !convoId) { return null; } const { attachments, sender, - convoId, direction, status, isDeletable, isDeletableForEveryone, - isPublic, - weAreAdmin, isSenderAdmin, text, serverTimestamp, timestamp, - isBlocked, } = selected; const isOutgoing = direction === 'outgoing'; @@ -157,12 +163,12 @@ export const MessageContextMenu = (props: Props) => { }, [sender, convoId]); const onReply = useCallback(() => { - if (isBlocked) { + if (isSelectedBlocked) { pushUnblockToSend(); return; } void replyToMessage(messageId); - }, [isBlocked, messageId]); + }, [isSelectedBlocked, messageId]); const saveAttachment = useCallback( (e: any) => { @@ -330,14 +336,12 @@ export const MessageContextMenu = (props: Props) => { {unsendMessageText} ) : null} - {weAreAdmin && isPublic ? {window.i18n('banUser')} : null} - {weAreAdmin && isPublic ? ( - {window.i18n('unbanUser')} - ) : null} - {weAreAdmin && isPublic && !isSenderAdmin ? ( + {showAdminActions ? {window.i18n('banUser')} : null} + {showAdminActions ? {window.i18n('unbanUser')} : null} + {showAdminActions && !isSenderAdmin ? ( {window.i18n('addAsModerator')} ) : null} - {weAreAdmin && isPublic && isSenderAdmin ? ( + {showAdminActions && isSenderAdmin ? ( {window.i18n('removeFromModerators')} ) : null} diff --git a/ts/components/dialog/ReactListModal.tsx b/ts/components/dialog/ReactListModal.tsx index 274ab52de..bcebfa660 100644 --- a/ts/components/dialog/ReactListModal.tsx +++ b/ts/components/dialog/ReactListModal.tsx @@ -3,7 +3,7 @@ import React, { ReactElement, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { Data } from '../../data/data'; -import { useMessageReactsPropsById, useWeAreModerator } from '../../hooks/useParamSelector'; +import { useMessageReactsPropsById } from '../../hooks/useParamSelector'; import { isUsAnySogsFromCache } from '../../session/apis/open_group_api/sogsv3/knownBlindedkeys'; import { UserUtils } from '../../session/utils'; import { @@ -11,7 +11,10 @@ import { updateReactListModal, updateUserDetailsModal, } from '../../state/ducks/modalDialog'; -import { useSelectedIsPublic } from '../../state/selectors/selectedConversation'; +import { + useSelectedIsPublic, + useSelectedWeAreModerator, +} from '../../state/selectors/selectedConversation'; import { SortedReactionList } from '../../types/Reaction'; import { nativeEmojiData } from '../../util/emoji'; import { Reactions } from '../../util/reactions'; @@ -229,7 +232,7 @@ export const ReactListModal = (props: Props): ReactElement => { const msgProps = useMessageReactsPropsById(messageId); const isPublic = useSelectedIsPublic(); - const weAreModerator = useWeAreModerator(msgProps?.convoId); + const weAreModerator = useSelectedWeAreModerator(); const me = UserUtils.getOurPubKeyStrFromCache(); // tslint:disable: cyclomatic-complexity diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index df837c162..6a414522b 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -146,11 +146,6 @@ export function useWeAreAdmin(convoId?: string) { return Boolean(convoProps && convoProps.weAreAdmin); } -export function useWeAreModerator(convoId?: string) { - const convoProps = useConversationPropsById(convoId); - return Boolean(convoProps && (convoProps.weAreAdmin || convoProps.weAreModerator)); -} - export function useExpireTimer(convoId?: string) { const convoProps = useConversationPropsById(convoId); return convoProps && convoProps.expireTimer; diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 81d0425e6..6443020b0 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -269,7 +269,6 @@ export class ConversationModel extends Backbone.Model { const avatarPath = this.getAvatarPath(); const isPrivate = this.isPrivate(); const weAreAdmin = this.isAdmin(ourNumber); - const weAreModerator = this.isModerator(ourNumber); // only used for sogs const currentNotificationSetting = this.get('triggerNotificationsFor'); const priorityFromDb = this.get('priority'); @@ -310,10 +309,6 @@ export class ConversationModel extends Backbone.Model { toRet.weAreAdmin = true; } - if (weAreModerator) { - toRet.weAreModerator = true; - } - if (isPublic) { toRet.isPublic = true; } diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 8d25b4173..6d01f18a4 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -237,7 +237,6 @@ export interface ReduxConversationType { isPublic?: boolean; isPrivate?: boolean; // !isPrivate means isGroup (group or community) weAreAdmin?: boolean; - weAreModerator?: boolean; unreadCount?: number; mentionedUs?: boolean; isSelected?: boolean; diff --git a/ts/state/ducks/sogsRoomInfo.tsx b/ts/state/ducks/sogsRoomInfo.tsx index 32612ae79..84e9959c3 100644 --- a/ts/state/ducks/sogsRoomInfo.tsx +++ b/ts/state/ducks/sogsRoomInfo.tsx @@ -86,10 +86,11 @@ function setCanWriteOutsideRedux(convoId: string, canWrite: boolean) { } /** + * Update the redux slice for that community's moderators list + * if we are a moderator that room and the room is blinded, this update needs to contain our unblinded pubkey, NOT the blinded one. * * @param convoId the convoId of the room to set the moderators * @param moderators the updated list of moderators - * Note: if we are a moderator that room and the room is blinded, this update needs to contain our unblinded pubkey, NOT the blinded one */ function setModeratorsOutsideRedux(convoId: string, moderators: Array) { const currentMods = getModeratorsOutsideRedux(convoId); diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 1d3716f15..0829ee743 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -888,6 +888,10 @@ export const useMessageIsDeleted = (messageId: string): boolean => { return props?.propsForMessage.isDeleted || false; }; +/** + * TODO probably not something which should be memoized with createSelector as we rememoize it for each message (and override the previous one). Not sure what is the right way to do a lookup. But maybe something like having the messages as a record and do a simple lookup to grab the details. + * And the the sorting would be done in a memoized selector + */ export const getMessageContextMenuProps = createSelector(getMessagePropsByMessageId, (props): | MessageContextMenuSelectorProps | undefined => { @@ -898,18 +902,13 @@ export const getMessageContextMenuProps = createSelector(getMessagePropsByMessag const msgProps: MessageContextMenuSelectorProps = pick(props.propsForMessage, [ 'attachments', 'sender', - 'convoId', 'direction', 'status', 'isDeletable', - 'isPublic', - 'isOpenGroupV2', - 'weAreAdmin', 'isSenderAdmin', 'text', 'serverTimestamp', 'timestamp', - 'isBlocked', 'isDeletableForEveryone', ]); diff --git a/ts/state/selectors/selectedConversation.ts b/ts/state/selectors/selectedConversation.ts index 759ab8cbf..a1e186d9c 100644 --- a/ts/state/selectors/selectedConversation.ts +++ b/ts/state/selectors/selectedConversation.ts @@ -1,9 +1,11 @@ +import { isString } from 'lodash'; import { useSelector } from 'react-redux'; import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes'; +import { PubKey } from '../../session/types'; +import { UserUtils } from '../../session/utils'; import { ReduxConversationType } from '../ducks/conversations'; import { StateType } from '../reducer'; -import { getCanWrite, getSubscriberCount } from './sogsRoomInfo'; -import { PubKey } from '../../session/types'; +import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo'; /** * Returns the formatted text for notification setting. @@ -257,3 +259,18 @@ export function useSelectedNicknameOrProfileNameOrShortenedPubkey() { export function useSelectedWeAreAdmin() { return useSelector((state: StateType) => getSelectedConversation(state)?.weAreAdmin || false); } + +/** + * Only for communities. + * @returns true if the selected convo is a community and we are one of the moderators + */ +export function useSelectedWeAreModerator() { + // TODO might be something to memoize let's see + const isPublic = useSelectedIsPublic(); + const selectedConvoKey = useSelectedConversationKey(); + const us = UserUtils.getOurPubKeyStrFromCache(); + const mods = useSelector((state: StateType) => getModerators(state, selectedConvoKey)); + + const weAreModerator = mods.includes(us); + return isPublic && isString(selectedConvoKey) && weAreModerator; +}