fix: mod and admin actions on message context menu for communities

pull/2793/head
Audric Ackermann 2 years ago
parent d970887caa
commit 461b192f37

@ -24,6 +24,13 @@ import {
} from '../../../../state/ducks/conversations'; } from '../../../../state/ducks/conversations';
import { StateType } from '../../../../state/reducer'; import { StateType } from '../../../../state/reducer';
import { getMessageContextMenuProps } from '../../../../state/selectors/conversations'; import { getMessageContextMenuProps } from '../../../../state/selectors/conversations';
import {
useSelectedConversationKey,
useSelectedIsBlocked,
useSelectedIsPublic,
useSelectedWeAreAdmin,
useSelectedWeAreModerator,
} from '../../../../state/selectors/selectedConversation';
import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil'; import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil';
import { Reactions } from '../../../../util/reactions'; import { Reactions } from '../../../../util/reactions';
import { SessionContextMenuContainer } from '../../../SessionContextMenuContainer'; import { SessionContextMenuContainer } from '../../../SessionContextMenuContainer';
@ -34,18 +41,13 @@ export type MessageContextMenuSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
| 'attachments' | 'attachments'
| 'sender' | 'sender'
| 'convoId'
| 'direction' | 'direction'
| 'status' | 'status'
| 'isDeletable' | 'isDeletable'
| 'isPublic'
| 'isOpenGroupV2'
| 'weAreAdmin'
| 'isSenderAdmin' | 'isSenderAdmin'
| 'text' | 'text'
| 'serverTimestamp' | 'serverTimestamp'
| 'timestamp' | 'timestamp'
| 'isBlocked'
| 'isDeletableForEveryone' | 'isDeletableForEveryone'
>; >;
@ -80,27 +82,31 @@ export const MessageContextMenu = (props: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { hideAll } = useContextMenu(); 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)); const selected = useSelector((state: StateType) => getMessageContextMenuProps(state, messageId));
if (!selected) { if (!selected || !convoId) {
return null; return null;
} }
const { const {
attachments, attachments,
sender, sender,
convoId,
direction, direction,
status, status,
isDeletable, isDeletable,
isDeletableForEveryone, isDeletableForEveryone,
isPublic,
weAreAdmin,
isSenderAdmin, isSenderAdmin,
text, text,
serverTimestamp, serverTimestamp,
timestamp, timestamp,
isBlocked,
} = selected; } = selected;
const isOutgoing = direction === 'outgoing'; const isOutgoing = direction === 'outgoing';
@ -157,12 +163,12 @@ export const MessageContextMenu = (props: Props) => {
}, [sender, convoId]); }, [sender, convoId]);
const onReply = useCallback(() => { const onReply = useCallback(() => {
if (isBlocked) { if (isSelectedBlocked) {
pushUnblockToSend(); pushUnblockToSend();
return; return;
} }
void replyToMessage(messageId); void replyToMessage(messageId);
}, [isBlocked, messageId]); }, [isSelectedBlocked, messageId]);
const saveAttachment = useCallback( const saveAttachment = useCallback(
(e: any) => { (e: any) => {
@ -330,14 +336,12 @@ export const MessageContextMenu = (props: Props) => {
<Item onClick={onDeleteForEveryone}>{unsendMessageText}</Item> <Item onClick={onDeleteForEveryone}>{unsendMessageText}</Item>
</> </>
) : null} ) : null}
{weAreAdmin && isPublic ? <Item onClick={onBan}>{window.i18n('banUser')}</Item> : null} {showAdminActions ? <Item onClick={onBan}>{window.i18n('banUser')}</Item> : null}
{weAreAdmin && isPublic ? ( {showAdminActions ? <Item onClick={onUnban}>{window.i18n('unbanUser')}</Item> : null}
<Item onClick={onUnban}>{window.i18n('unbanUser')}</Item> {showAdminActions && !isSenderAdmin ? (
) : null}
{weAreAdmin && isPublic && !isSenderAdmin ? (
<Item onClick={addModerator}>{window.i18n('addAsModerator')}</Item> <Item onClick={addModerator}>{window.i18n('addAsModerator')}</Item>
) : null} ) : null}
{weAreAdmin && isPublic && isSenderAdmin ? ( {showAdminActions && isSenderAdmin ? (
<Item onClick={removeModerator}>{window.i18n('removeFromModerators')}</Item> <Item onClick={removeModerator}>{window.i18n('removeFromModerators')}</Item>
) : null} ) : null}
</Menu> </Menu>

@ -3,7 +3,7 @@ import React, { ReactElement, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { Data } from '../../data/data'; 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 { isUsAnySogsFromCache } from '../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { UserUtils } from '../../session/utils'; import { UserUtils } from '../../session/utils';
import { import {
@ -11,7 +11,10 @@ import {
updateReactListModal, updateReactListModal,
updateUserDetailsModal, updateUserDetailsModal,
} from '../../state/ducks/modalDialog'; } from '../../state/ducks/modalDialog';
import { useSelectedIsPublic } from '../../state/selectors/selectedConversation'; import {
useSelectedIsPublic,
useSelectedWeAreModerator,
} from '../../state/selectors/selectedConversation';
import { SortedReactionList } from '../../types/Reaction'; import { SortedReactionList } from '../../types/Reaction';
import { nativeEmojiData } from '../../util/emoji'; import { nativeEmojiData } from '../../util/emoji';
import { Reactions } from '../../util/reactions'; import { Reactions } from '../../util/reactions';
@ -229,7 +232,7 @@ export const ReactListModal = (props: Props): ReactElement => {
const msgProps = useMessageReactsPropsById(messageId); const msgProps = useMessageReactsPropsById(messageId);
const isPublic = useSelectedIsPublic(); const isPublic = useSelectedIsPublic();
const weAreModerator = useWeAreModerator(msgProps?.convoId); const weAreModerator = useSelectedWeAreModerator();
const me = UserUtils.getOurPubKeyStrFromCache(); const me = UserUtils.getOurPubKeyStrFromCache();
// tslint:disable: cyclomatic-complexity // tslint:disable: cyclomatic-complexity

@ -146,11 +146,6 @@ export function useWeAreAdmin(convoId?: string) {
return Boolean(convoProps && convoProps.weAreAdmin); 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) { export function useExpireTimer(convoId?: string) {
const convoProps = useConversationPropsById(convoId); const convoProps = useConversationPropsById(convoId);
return convoProps && convoProps.expireTimer; return convoProps && convoProps.expireTimer;

@ -269,7 +269,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const avatarPath = this.getAvatarPath(); const avatarPath = this.getAvatarPath();
const isPrivate = this.isPrivate(); const isPrivate = this.isPrivate();
const weAreAdmin = this.isAdmin(ourNumber); const weAreAdmin = this.isAdmin(ourNumber);
const weAreModerator = this.isModerator(ourNumber); // only used for sogs
const currentNotificationSetting = this.get('triggerNotificationsFor'); const currentNotificationSetting = this.get('triggerNotificationsFor');
const priorityFromDb = this.get('priority'); const priorityFromDb = this.get('priority');
@ -310,10 +309,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
toRet.weAreAdmin = true; toRet.weAreAdmin = true;
} }
if (weAreModerator) {
toRet.weAreModerator = true;
}
if (isPublic) { if (isPublic) {
toRet.isPublic = true; toRet.isPublic = true;
} }

@ -237,7 +237,6 @@ export interface ReduxConversationType {
isPublic?: boolean; isPublic?: boolean;
isPrivate?: boolean; // !isPrivate means isGroup (group or community) isPrivate?: boolean; // !isPrivate means isGroup (group or community)
weAreAdmin?: boolean; weAreAdmin?: boolean;
weAreModerator?: boolean;
unreadCount?: number; unreadCount?: number;
mentionedUs?: boolean; mentionedUs?: boolean;
isSelected?: boolean; isSelected?: boolean;

@ -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 convoId the convoId of the room to set the moderators
* @param moderators the updated list of 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<string>) { function setModeratorsOutsideRedux(convoId: string, moderators: Array<string>) {
const currentMods = getModeratorsOutsideRedux(convoId); const currentMods = getModeratorsOutsideRedux(convoId);

@ -888,6 +888,10 @@ export const useMessageIsDeleted = (messageId: string): boolean => {
return props?.propsForMessage.isDeleted || false; 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<id, message> 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): export const getMessageContextMenuProps = createSelector(getMessagePropsByMessageId, (props):
| MessageContextMenuSelectorProps | MessageContextMenuSelectorProps
| undefined => { | undefined => {
@ -898,18 +902,13 @@ export const getMessageContextMenuProps = createSelector(getMessagePropsByMessag
const msgProps: MessageContextMenuSelectorProps = pick(props.propsForMessage, [ const msgProps: MessageContextMenuSelectorProps = pick(props.propsForMessage, [
'attachments', 'attachments',
'sender', 'sender',
'convoId',
'direction', 'direction',
'status', 'status',
'isDeletable', 'isDeletable',
'isPublic',
'isOpenGroupV2',
'weAreAdmin',
'isSenderAdmin', 'isSenderAdmin',
'text', 'text',
'serverTimestamp', 'serverTimestamp',
'timestamp', 'timestamp',
'isBlocked',
'isDeletableForEveryone', 'isDeletableForEveryone',
]); ]);

@ -1,9 +1,11 @@
import { isString } from 'lodash';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes'; import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes';
import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils';
import { ReduxConversationType } from '../ducks/conversations'; import { ReduxConversationType } from '../ducks/conversations';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getCanWrite, getSubscriberCount } from './sogsRoomInfo'; import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo';
import { PubKey } from '../../session/types';
/** /**
* Returns the formatted text for notification setting. * Returns the formatted text for notification setting.
@ -257,3 +259,18 @@ export function useSelectedNicknameOrProfileNameOrShortenedPubkey() {
export function useSelectedWeAreAdmin() { export function useSelectedWeAreAdmin() {
return useSelector((state: StateType) => getSelectedConversation(state)?.weAreAdmin || false); 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;
}

Loading…
Cancel
Save