import React from 'react'; import { getHasIncomingCall, getHasOngoingCall, getNumberOfPinnedConversations, } from '../../../state/selectors/conversations'; import { getFocusedSection } from '../../../state/selectors/section'; import { Item, Submenu } from 'react-contexify'; import { ConversationNotificationSetting, ConversationNotificationSettingType, } from '../../../models/conversation'; import { useDispatch, useSelector } from 'react-redux'; import { changeNickNameModal, updateConfirmModal } from '../../../state/ducks/modalDialog'; import { SectionType } from '../../../state/ducks/section'; import { getConversationController } from '../../../session/conversations'; import { blockConvoById, clearNickNameByConvoId, copyPublicKeyByConvoId, deleteMessagesByConvoIdWithConfirmation, markAllReadByConvoId, setDisappearingMessagesByConvoId, setNotificationForConvoId, showAddModeratorsByConvoId, showInviteContactByConvoId, showLeaveGroupByConvoId, showRemoveModeratorsByConvoId, showUpdateGroupNameByConvoId, unblockConvoById, } from '../../../interactions/conversationInteractions'; import { SessionButtonColor } from '../SessionButton'; import { getTimerOptions } from '../../../state/selectors/timerOptions'; import { CallManager, ToastUtils } from '../../../session/utils'; const maxNumberOfPinnedConversations = 5; function showTimerOptions( isPublic: boolean, isKickedFromGroup: boolean, left: boolean, isBlocked: boolean ): boolean { return !isPublic && !left && !isKickedFromGroup && !isBlocked; } function showNotificationConvo( isKickedFromGroup: boolean, left: boolean, isBlocked: boolean ): boolean { return !left && !isKickedFromGroup && !isBlocked; } function showBlock(isMe: boolean, isPrivate: boolean): boolean { return !isMe && isPrivate; } function showClearNickname(isMe: boolean, hasNickname: boolean, isGroup: boolean): boolean { return !isMe && hasNickname && !isGroup; } function showChangeNickname(isMe: boolean, isGroup: boolean) { return !isMe && !isGroup; } function showDeleteMessages(isPublic: boolean): boolean { return !isPublic; } // we want to show the copyId for open groups and private chats only function showCopyId(isPublic: boolean, isGroup: boolean): boolean { return !isGroup || isPublic; } function showDeleteContact( isMe: boolean, isGroup: boolean, isPublic: boolean, isGroupLeft: boolean, isKickedFromGroup: boolean ): boolean { // you need to have left a closed group first to be able to delete it completely. return (!isMe && !isGroup) || (isGroup && (isGroupLeft || isKickedFromGroup || isPublic)); } function showAddModerators( isAdmin: boolean, isPublic: boolean, isKickedFromGroup: boolean ): boolean { return !isKickedFromGroup && isAdmin && isPublic; } function showRemoveModerators( isAdmin: boolean, isPublic: boolean, isKickedFromGroup: boolean ): boolean { return !isKickedFromGroup && isAdmin && isPublic; } function showUpdateGroupName(isAdmin: boolean, isKickedFromGroup: boolean, left: boolean): boolean { return !isKickedFromGroup && !left && isAdmin; } function showLeaveGroup( isKickedFromGroup: boolean, left: boolean, isGroup: boolean, isPublic: boolean ): boolean { return !isKickedFromGroup && !left && isGroup && !isPublic; } function showInviteContact(isGroup: boolean, isPublic: boolean): boolean { return isGroup && isPublic; } /** Menu items standardized */ export function getInviteContactMenuItem( isGroup: boolean | undefined, isPublic: boolean | undefined, conversationId: string ): JSX.Element | null { if (showInviteContact(Boolean(isGroup), Boolean(isPublic))) { return ( { showInviteContactByConvoId(conversationId); }} > {window.i18n('inviteContacts')} ); } return null; } export interface PinConversationMenuItemProps { conversationId: string; } export const getPinConversationMenuItem = (conversationId: string): JSX.Element | null => { const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message; const nbOfAlreadyPinnedConvos = useSelector(getNumberOfPinnedConversations); if (isMessagesSection && window.lokiFeatureFlags.enablePinConversations) { const conversation = getConversationController().get(conversationId); const isPinned = conversation.isPinned(); const togglePinConversation = async () => { if ((!isPinned && nbOfAlreadyPinnedConvos < maxNumberOfPinnedConversations) || isPinned) { await conversation.setIsPinned(!isPinned); } else { ToastUtils.pushToastWarning( 'pinConversationLimitToast', window.i18n('pinConversationLimitTitle'), window.i18n('pinConversationLimitToastDescription', maxNumberOfPinnedConversations) ); } }; const menuText = isPinned ? window.i18n('unpinConversation') : window.i18n('pinConversation'); return {menuText}; } return null; }; export function getDeleteContactMenuItem( isMe: boolean | undefined, isGroup: boolean | undefined, isPublic: boolean | undefined, isLeft: boolean | undefined, isKickedFromGroup: boolean | undefined, conversationId: string ): JSX.Element | null { const dispatch = useDispatch(); if ( showDeleteContact( Boolean(isMe), Boolean(isGroup), Boolean(isPublic), Boolean(isLeft), Boolean(isKickedFromGroup) ) ) { let menuItemText: string; if (isPublic) { menuItemText = window.i18n('leaveGroup'); } else { menuItemText = window.i18n('delete'); } const onClickClose = () => { dispatch(updateConfirmModal(null)); }; const showConfirmationModal = () => { dispatch( updateConfirmModal({ title: menuItemText, message: isGroup ? window.i18n('leaveGroupConfirmation') : window.i18n('deleteContactConfirmation'), onClickClose, okTheme: SessionButtonColor.Danger, onClickOk: async () => { await getConversationController().deleteContact(conversationId); }, }) ); }; return {menuItemText}; } return null; } export function getLeaveGroupMenuItem( isKickedFromGroup: boolean | undefined, left: boolean | undefined, isGroup: boolean | undefined, isPublic: boolean | undefined, conversationId: string ): JSX.Element | null { if ( showLeaveGroup(Boolean(isKickedFromGroup), Boolean(left), Boolean(isGroup), Boolean(isPublic)) ) { return ( { showLeaveGroupByConvoId(conversationId); }} > {window.i18n('leaveGroup')} ); } return null; } export function getUpdateGroupNameMenuItem( isAdmin: boolean | undefined, isKickedFromGroup: boolean | undefined, left: boolean | undefined, conversationId: string ): JSX.Element | null { if (showUpdateGroupName(Boolean(isAdmin), Boolean(isKickedFromGroup), Boolean(left))) { return ( { await showUpdateGroupNameByConvoId(conversationId); }} > {window.i18n('editGroup')} ); } return null; } export function getRemoveModeratorsMenuItem( isAdmin: boolean | undefined, isPublic: boolean | undefined, isKickedFromGroup: boolean | undefined, conversationId: string ): JSX.Element | null { if (showRemoveModerators(Boolean(isAdmin), Boolean(isPublic), Boolean(isKickedFromGroup))) { return ( { showRemoveModeratorsByConvoId(conversationId); }} > {window.i18n('removeModerators')} ); } return null; } export function getAddModeratorsMenuItem( isAdmin: boolean | undefined, isPublic: boolean | undefined, isKickedFromGroup: boolean | undefined, conversationId: string ): JSX.Element | null { if (showAddModerators(Boolean(isAdmin), Boolean(isPublic), Boolean(isKickedFromGroup))) { return ( { showAddModeratorsByConvoId(conversationId); }} > {window.i18n('addModerators')} ); } return null; } export function getCopyMenuItem( isPublic: boolean | undefined, isGroup: boolean | undefined, conversationId: string ): JSX.Element | null { if (showCopyId(Boolean(isPublic), Boolean(isGroup))) { const copyIdLabel = isPublic ? window.i18n('copyOpenGroupURL') : window.i18n('copySessionID'); return copyPublicKeyByConvoId(conversationId)}>{copyIdLabel}; } return null; } export function getMarkAllReadMenuItem(conversationId: string): JSX.Element | null { return ( markAllReadByConvoId(conversationId)}>{window.i18n('markAllAsRead')} ); } export function getStartCallMenuItem(conversationId: string): JSX.Element | null { if (window?.lokiFeatureFlags.useCallMessage) { const canCall = !(useSelector(getHasIncomingCall) || useSelector(getHasOngoingCall)); return ( { // TODO: either pass param to callRecipient or call different call methods based on item selected. // TODO: one time redux-persisted permission modal? const convo = getConversationController().get(conversationId); if (!canCall) { ToastUtils.pushUnableToCall(); return; } if (convo) { convo.callState = 'connecting'; await convo.commit(); await CallManager.USER_callRecipient(convo.id); } }} > {'Video Call'} ); } return null; } export function getDisappearingMenuItem( isPublic: boolean | undefined, isKickedFromGroup: boolean | undefined, left: boolean | undefined, isBlocked: boolean | undefined, conversationId: string ): JSX.Element | null { const timerOptions = useSelector(getTimerOptions).timerOptions; if ( showTimerOptions( Boolean(isPublic), Boolean(isKickedFromGroup), Boolean(left), Boolean(isBlocked) ) ) { // const isRtlMode = isRtlBody(); return ( // Remove the && false to make context menu work with RTL support {timerOptions.map(item => ( { await setDisappearingMessagesByConvoId(conversationId, item.value); }} > {item.name} ))} ); } return null; } export function getNotificationForConvoMenuItem({ conversationId, currentNotificationSetting, isBlocked, isKickedFromGroup, left, isPrivate, }: { isKickedFromGroup: boolean | undefined; left: boolean | undefined; isBlocked: boolean | undefined; isPrivate: boolean | undefined; currentNotificationSetting: ConversationNotificationSettingType; conversationId: string; }): JSX.Element | null { if (showNotificationConvo(Boolean(isKickedFromGroup), Boolean(left), Boolean(isBlocked))) { // const isRtlMode = isRtlBody();' // exclude mentions_only settings for private chats as this does not make much sense const notificationForConvoOptions = ConversationNotificationSetting.filter(n => isPrivate ? n !== 'mentions_only' : true ).map((n: ConversationNotificationSettingType) => { // this link to the notificationForConvo_all, notificationForConvo_mentions_only, ... return { value: n, name: window.i18n(`notificationForConvo_${n}`) }; }); return ( // Remove the && false to make context menu work with RTL support {(notificationForConvoOptions || []).map(item => { const disabled = item.value === currentNotificationSetting; return ( { await setNotificationForConvoId(conversationId, item.value); }} disabled={disabled} > {item.name} ); })} ); } return null; } export function isRtlBody(): boolean { return ($('body') as any).hasClass('rtl'); } export function getBlockMenuItem( isMe: boolean | undefined, isPrivate: boolean | undefined, isBlocked: boolean | undefined, conversationId: string ): JSX.Element | null { if (showBlock(Boolean(isMe), Boolean(isPrivate))) { const blockTitle = isBlocked ? window.i18n('unblockUser') : window.i18n('blockUser'); const blockHandler = isBlocked ? () => unblockConvoById(conversationId) : () => blockConvoById(conversationId); return {blockTitle}; } return null; } export function getClearNicknameMenuItem( isMe: boolean | undefined, hasNickname: boolean | undefined, isGroup: boolean | undefined, conversationId: string ): JSX.Element | null { if (showClearNickname(Boolean(isMe), Boolean(hasNickname), Boolean(isGroup))) { return ( clearNickNameByConvoId(conversationId)}> {window.i18n('clearNickname')} ); } return null; } export function getChangeNicknameMenuItem( isMe: boolean | undefined, isGroup: boolean | undefined, conversationId: string ): JSX.Element | null { const dispatch = useDispatch(); if (showChangeNickname(Boolean(isMe), Boolean(isGroup))) { return ( { dispatch(changeNickNameModal({ conversationId })); }} > {window.i18n('changeNickname')} ); } return null; } export function getDeleteMessagesMenuItem( isPublic: boolean | undefined, conversationId: string ): JSX.Element | null { if (showDeleteMessages(Boolean(isPublic))) { return ( { deleteMessagesByConvoIdWithConfirmation(conversationId); }} > {window.i18n('deleteMessages')} ); } return null; }