import React from 'react'; import { Item } from 'react-contexify'; import { useDispatch, useSelector } from 'react-redux'; import { useAvatarPath, useConversationUsername, useHasNickname, useIsBlinded, useIsBlocked, useIsIncomingRequest, useIsKickedFromGroup, useIsLeft, useIsMe, useIsPrivate, useIsPrivateAndFriend, useIsPublic, useWeAreAdmin, } from '../../hooks/useParamSelector'; import { approveConvoAndSendResponse, blockConvoById, clearNickNameByConvoId, copyPublicKeyByConvoId, declineConversationWithConfirm, deleteAllMessagesByConvoIdWithConfirmation, markAllReadByConvoId, showAddModeratorsByConvoId, showBanUserByConvoId, showInviteContactByConvoId, showLeaveGroupByConvoId, showRemoveModeratorsByConvoId, showUnbanUserByConvoId, showUpdateGroupNameByConvoId, unblockConvoById, } from '../../interactions/conversationInteractions'; import { getConversationController } from '../../session/conversations'; import { changeNickNameModal, updateConfirmModal, updateUserDetailsModal, } from '../../state/ducks/modalDialog'; import { getIsMessageSection } from '../../state/selectors/section'; import { useSelectedConversationKey } from '../../state/selectors/selectedConversation'; import { SessionButtonColor } from '../basic/SessionButton'; import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext'; import { PubKey } from '../../session/types'; function showDeleteContact( isGroup: boolean, isPublic: boolean, isGroupLeft: boolean, isKickedFromGroup: boolean, isRequest: boolean ): boolean { // you need to have left a closed group first to be able to delete it completely. return (!isGroup && !isRequest) || (isGroup && (isGroupLeft || isKickedFromGroup || isPublic)); } function showUpdateGroupName( weAreAdmin: boolean, isKickedFromGroup: boolean, left: boolean ): boolean { return !isKickedFromGroup && !left && weAreAdmin; } function showLeaveGroup( isKickedFromGroup: boolean, left: boolean, isGroup: boolean, isPublic: boolean ): boolean { return !isKickedFromGroup && !left && isGroup && !isPublic; } function showInviteContact(isPublic: boolean): boolean { return isPublic; } /** Menu items standardized */ export const InviteContactMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); if (showInviteContact(isPublic)) { return ( { showInviteContactByConvoId(convoId); }} > {window.i18n('inviteContacts')} ); } return null; }; export const MarkConversationUnreadMenuItem = (): JSX.Element | null => { const conversationId = useConvoIdFromContext(); const isMessagesSection = useSelector(getIsMessageSection); const isPrivate = useIsPrivate(conversationId); const isPrivateAndFriend = useIsPrivateAndFriend(conversationId); if (isMessagesSection && (!isPrivate || (isPrivate && isPrivateAndFriend))) { const conversation = getConversationController().get(conversationId); const markUnread = async () => { await conversation?.markAsUnread(true); }; return {window.i18n('markUnread')}; } return null; }; export const DeleteContactMenuItem = () => { const dispatch = useDispatch(); const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); const isLeft = useIsLeft(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId); const isPrivate = useIsPrivate(convoId); const isRequest = useIsIncomingRequest(convoId); if (showDeleteContact(!isPrivate, isPublic, isLeft, isKickedFromGroup, isRequest)) { let menuItemText: string; if (isPublic) { menuItemText = window.i18n('leaveGroup'); } else { menuItemText = isPrivate ? window.i18n('editMenuDeleteContact') : window.i18n('editMenuDeleteGroup'); } const onClickClose = () => { dispatch(updateConfirmModal(null)); }; const showConfirmationModal = () => { dispatch( updateConfirmModal({ title: menuItemText, message: isPrivate ? window.i18n('deleteContactConfirmation') : window.i18n('leaveGroupConfirmation'), onClickClose, okTheme: SessionButtonColor.Danger, onClickOk: async () => { await getConversationController().deleteContact(convoId, false); }, }) ); }; return {menuItemText}; } return null; }; export const LeaveGroupMenuItem = () => { const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); const isLeft = useIsLeft(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId); const isPrivate = useIsPrivate(convoId); if (showLeaveGroup(isKickedFromGroup, isLeft, !isPrivate, isPublic)) { return ( { showLeaveGroupByConvoId(convoId); }} > {window.i18n('leaveGroup')} ); } return null; }; export const ShowUserDetailsMenuItem = () => { const dispatch = useDispatch(); const convoId = useConvoIdFromContext(); const isPrivate = useIsPrivate(convoId); const avatarPath = useAvatarPath(convoId); const userName = useConversationUsername(convoId) || convoId; const isBlinded = useIsBlinded(convoId); if (isPrivate && !isBlinded) { return ( { dispatch( updateUserDetailsModal({ conversationId: convoId, userName, authorAvatarPath: avatarPath, }) ); }} > {window.i18n('showUserDetails')} ); } return null; }; export const UpdateGroupNameMenuItem = () => { const convoId = useConvoIdFromContext(); const left = useIsLeft(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId); const weAreAdmin = useWeAreAdmin(convoId); if (showUpdateGroupName(weAreAdmin, isKickedFromGroup, left)) { return ( { await showUpdateGroupNameByConvoId(convoId); }} > {window.i18n('editGroup')} ); } return null; }; export const RemoveModeratorsMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId); const weAreAdmin = useWeAreAdmin(convoId); if (!isKickedFromGroup && weAreAdmin && isPublic) { return ( { showRemoveModeratorsByConvoId(convoId); }} > {window.i18n('removeModerators')} ); } return null; }; export const AddModeratorsMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId); const weAreAdmin = useWeAreAdmin(convoId); if (!isKickedFromGroup && weAreAdmin && isPublic) { return ( { showAddModeratorsByConvoId(convoId); }} > {window.i18n('addModerators')} ); } return null; }; export const UnbanMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId); const weAreAdmin = useWeAreAdmin(convoId); if (isPublic && !isKickedFromGroup && weAreAdmin) { return ( { showUnbanUserByConvoId(convoId); }} > {window.i18n('unbanUser')} ); } return null; }; export const BanMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId); const weAreAdmin = useWeAreAdmin(convoId); if (isPublic && !isKickedFromGroup && weAreAdmin) { return ( { showBanUserByConvoId(convoId); }} > {window.i18n('banUser')} ); } return null; }; export const CopyMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isPublic = useIsPublic(convoId); const isPrivate = useIsPrivate(convoId); const isBlinded = useIsBlinded(convoId); // we want to show the copyId for open groups and private chats only if ((isPrivate && !isBlinded) || isPublic) { const copyIdLabel = isPublic ? window.i18n('copyOpenGroupURL') : window.i18n('copySessionID'); return ( { copyPublicKeyByConvoId(convoId); }} > {copyIdLabel} ); } return null; }; export const MarkAllReadMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isIncomingRequest = useIsIncomingRequest(convoId); if (!isIncomingRequest && !PubKey.hasBlindedPrefix(convoId)) { return ( markAllReadByConvoId(convoId)}>{window.i18n('markAllAsRead')} ); } else { return null; } }; export function isRtlBody(): boolean { const body = document.getElementsByTagName('body').item(0); return body?.classList.contains('rtl') || false; } export const BlockMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isMe = useIsMe(convoId); const isBlocked = useIsBlocked(convoId); const isPrivate = useIsPrivate(convoId); const isIncomingRequest = useIsIncomingRequest(convoId); if (!isMe && isPrivate && !isIncomingRequest && !PubKey.hasBlindedPrefix(convoId)) { const blockTitle = isBlocked ? window.i18n('unblock') : window.i18n('block'); const blockHandler = isBlocked ? () => unblockConvoById(convoId) : () => blockConvoById(convoId); return {blockTitle}; } return null; }; export const ClearNicknameMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isMe = useIsMe(convoId); const hasNickname = useHasNickname(convoId); const isPrivate = useIsPrivate(convoId); const isPrivateAndFriend = useIsPrivateAndFriend(convoId); if (isMe || !hasNickname || !isPrivate || !isPrivateAndFriend) { return null; } return ( clearNickNameByConvoId(convoId)}>{window.i18n('clearNickname')} ); }; export const ChangeNicknameMenuItem = () => { const convoId = useConvoIdFromContext(); const isMe = useIsMe(convoId); const isPrivate = useIsPrivate(convoId); const isPrivateAndFriend = useIsPrivateAndFriend(convoId); const dispatch = useDispatch(); if (isMe || !isPrivate || !isPrivateAndFriend) { return null; } return ( { dispatch(changeNickNameModal({ conversationId: convoId })); }} > {window.i18n('changeNickname')} ); }; export const DeleteMessagesMenuItem = () => { const convoId = useConvoIdFromContext(); if (!convoId) { return null; } return ( { deleteAllMessagesByConvoIdWithConfirmation(convoId); }} > {window.i18n('deleteMessages')} ); }; export const AcceptMsgRequestMenuItem = () => { const convoId = useConvoIdFromContext(); const isRequest = useIsIncomingRequest(convoId); const convo = getConversationController().get(convoId); const isPrivate = useIsPrivate(convoId); if (isRequest && isPrivate) { return ( { await convo.setDidApproveMe(true); await convo.addOutgoingApprovalMessage(Date.now()); await approveConvoAndSendResponse(convoId, true); }} > {window.i18n('accept')} ); } return null; }; export const DeclineMsgRequestMenuItem = () => { const convoId = useConvoIdFromContext(); const isRequest = useIsIncomingRequest(convoId); const isPrivate = useIsPrivate(convoId); const selected = useSelectedConversationKey(); if (isPrivate && isRequest) { return ( { declineConversationWithConfirm({ conversationId: convoId, syncToDevices: true, blockContact: false, currentlySelectedConvo: selected || undefined, }); }} > {window.i18n('decline')} ); } return null; }; export const DeclineAndBlockMsgRequestMenuItem = () => { const convoId = useConvoIdFromContext(); const isRequest = useIsIncomingRequest(convoId); const selected = useSelectedConversationKey(); const isPrivate = useIsPrivate(convoId); if (isRequest && isPrivate) { return ( { declineConversationWithConfirm({ conversationId: convoId, syncToDevices: true, blockContact: true, currentlySelectedConvo: selected || undefined, }); }} > {window.i18n('block')} ); } return null; };