import React from 'react'; import { Avatar } from '../Avatar'; import { Colors, LocalizerType } from '../../types/Util'; import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu'; import { SessionIconButton, SessionIconSize, SessionIconType, } from '../session/icon'; import { SessionButton, SessionButtonColor, SessionButtonType, } from '../session/SessionButton'; import * as Menu from '../../session/utils/Menu'; import { usingClosedConversationDetails } from '../session/usingClosedConversationDetails'; export interface TimerOption { name: string; value: number; } interface Props { id: string; name?: string; phoneNumber: string; profileName?: string; avatarPath?: string; isVerified: boolean; isMe: boolean; isClosable?: boolean; isGroup: boolean; isPrivate: boolean; isArchived: boolean; isPublic: boolean; isRss: boolean; amMod: boolean; // We might not always have the full list of members, // e.g. for open groups where we could have thousands // of members. We'll keep this for now (for closed chats) members: Array; // not equal members.length (see above) subscriberCount?: number; expirationSettingName?: string; showBackButton: boolean; timerOptions: Array; hasNickname?: boolean; isBlocked: boolean; isOnline?: boolean; selectedMessages: any; isKickedFromGroup: boolean; onSetDisappearingMessages: (seconds: number) => void; onDeleteMessages: () => void; onDeleteContact: () => void; onResetSession: () => void; onCloseOverlay: () => void; onDeleteSelectedMessages: () => void; onArchive: () => void; onMoveToInbox: () => void; onShowSafetyNumber: () => void; onShowAllMedia: () => void; onShowGroupMembers: () => void; onGoBack: () => void; onBlockUser: () => void; onUnblockUser: () => void; onClearNickname: () => void; onChangeNickname: () => void; onCopyPublicKey: () => void; onLeaveGroup: () => void; onAddModerators: () => void; onRemoveModerators: () => void; onInviteContacts: () => void; onAvatarClick?: (userPubKey: string) => void; onUpdateGroupName: () => void; i18n: LocalizerType; closedMemberConversations?: any; // this is added by usingClosedConversationDetails } class ConversationHeader extends React.Component { public showMenuBound: (event: React.MouseEvent) => void; public onAvatarClickBound: (userPubKey: string) => void; public menuTriggerRef: React.RefObject; public constructor(props: Props) { super(props); this.menuTriggerRef = React.createRef(); this.showMenuBound = this.showMenu.bind(this); this.onAvatarClickBound = this.onAvatarClick.bind(this); } public showMenu(event: React.MouseEvent) { if (this.menuTriggerRef.current) { this.menuTriggerRef.current.handleContextClick(event); } } public renderBackButton() { const { onGoBack, showBackButton } = this.props; if (!showBackButton) { return null; } return (
); } public renderTitle() { const { phoneNumber, i18n, profileName, isGroup, isPublic, isRss, members, subscriberCount, isMe, isKickedFromGroup, name, } = this.props; if (isMe) { return (
{i18n('noteToSelf')}
); } const memberCount: number = (() => { if (!isGroup || isRss) { return 0; } if (isPublic) { return subscriberCount || 0; } else { return members.length; } })(); let text = ''; if (isGroup && memberCount > 0) { const count = String(memberCount); text = i18n('members', [count]); } const textEl = text === '' || isKickedFromGroup ? null : ( {text} ); let title; if (profileName) { title = `${profileName} ${window.shortenPubkey(phoneNumber)}`; } else { if (name) { title = `${name}`; } else { title = `User ${window.shortenPubkey(phoneNumber)}`; } } return (
{title} {textEl}
); } public renderAvatar() { const { avatarPath, closedMemberConversations, name, phoneNumber, profileName, } = this.props; const userName = name || profileName || phoneNumber; return ( { this.onAvatarClickBound(phoneNumber); }} closedMemberConversations={closedMemberConversations} pubkey={phoneNumber} /> ); } public renderExpirationLength() { const { expirationSettingName } = this.props; if (!expirationSettingName) { return null; } return (
{expirationSettingName}
); } public renderSearch() { return (
); } public renderOptions(triggerId: string) { const { showBackButton } = this.props; if (showBackButton) { return null; } return ( ); } public renderMenu(triggerId: string) { const { i18n, isMe, isClosable, isPublic, isRss, isGroup, isKickedFromGroup, amMod, onDeleteMessages, onDeleteContact, onCopyPublicKey, onLeaveGroup, onAddModerators, onRemoveModerators, onInviteContacts, onUpdateGroupName, } = this.props; return ( {this.renderPublicMenuItems()} {Menu.getCopyMenuItem(isPublic, isRss, isGroup, onCopyPublicKey, i18n)} {Menu.getDeleteMessagesMenuItem(isPublic, onDeleteMessages, i18n)} {Menu.getAddModeratorsMenuItem( amMod, isKickedFromGroup, onAddModerators, i18n )} {Menu.getRemoveModeratorsMenuItem( amMod, isKickedFromGroup, onRemoveModerators, i18n )} {Menu.getUpdateGroupNameMenuItem( amMod, isKickedFromGroup, onUpdateGroupName, i18n )} {Menu.getLeaveGroupMenuItem( isKickedFromGroup, isGroup, isPublic, isRss, onLeaveGroup, i18n )} {/* TODO: add delete group */} {Menu.getInviteContactMenuItem( isGroup, isPublic, onInviteContacts, i18n )} {Menu.getDeleteContactMenuItem( isMe, isClosable, isGroup, isPublic, isRss, onDeleteContact, i18n )} ); } public renderSelectionOverlay() { const { onDeleteSelectedMessages, onCloseOverlay, isPublic, i18n, } = this.props; const isServerDeletable = isPublic; const deleteMessageButtonText = i18n( isServerDeletable ? 'deleteForEveryone' : 'delete' ); return (
); } public render() { const { id, isKickedFromGroup } = this.props; const triggerId = `conversation-header-${id}`; return ( <> {this.renderSelectionOverlay()}
{this.renderBackButton()}
{this.renderOptions(triggerId)} {this.renderTitle()} {/* This might be redundant as we show the title in the title: */} {/*isPrivateGroup ? this.renderMemberCount() : null*/}
{!isKickedFromGroup && this.renderExpirationLength()} {!this.props.isRss && this.renderAvatar()} {this.renderMenu(triggerId)}
); } public onAvatarClick(userPubKey: string) { if (this.props.onAvatarClick) { this.props.onAvatarClick(userPubKey); } } public highlightMessageSearch() { // This is a temporary fix. In future we want to search // messages in the current conversation ($('.session-search-input input') as any).focus(); } // tslint:disable-next-line: cyclomatic-complexity private renderPublicMenuItems() { const { i18n, isBlocked, isMe, isGroup, isPrivate, isKickedFromGroup, isPublic, isRss, onResetSession, onSetDisappearingMessages, onShowGroupMembers, onShowSafetyNumber, timerOptions, onBlockUser, onUnblockUser, } = this.props; const disappearingMessagesMenuItem = Menu.getDisappearingMenuItem( isPublic, isRss, isKickedFromGroup, isBlocked, timerOptions, onSetDisappearingMessages, i18n ); const showMembersMenuItem = Menu.getShowMemberMenuItem( isPublic, isRss, isGroup, onShowGroupMembers, i18n ); const showSafetyNumberMenuItem = Menu.getShowSafetyNumberMenuItem( isPublic, isRss, isGroup, isMe, onShowSafetyNumber, i18n ); const resetSessionMenuItem = Menu.getResetSessionMenuItem( isPublic, isRss, isGroup, isBlocked, onResetSession, i18n ); const blockHandlerMenuItem = Menu.getBlockMenuItem( isMe, isPrivate, isBlocked, onBlockUser, onUnblockUser, i18n ); return ( {disappearingMessagesMenuItem} {showMembersMenuItem} {showSafetyNumberMenuItem} {resetSessionMenuItem} {blockHandlerMenuItem} ); } } export const ConversationHeaderWithDetails = usingClosedConversationDetails( ConversationHeader );