import React, { useCallback } from 'react'; import classNames from 'classnames'; import { isEmpty } from 'lodash'; import { contextMenu } from 'react-contexify'; import { Avatar, AvatarSize } from './Avatar'; import { MessageBody } from './conversation/MessageBody'; import { Timestamp } from './conversation/Timestamp'; import { ContactName } from './conversation/ContactName'; import { TypingAnimation } from './conversation/TypingAnimation'; import { MemoConversationListItemContextMenu } from './session/menu/ConversationListItemContextMenu'; import { createPortal } from 'react-dom'; import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageStatus'; import styled from 'styled-components'; import { PubKey } from '../session/types'; import { LastMessageType, openConversationWithMessages, ReduxConversationType, } from '../state/ducks/conversations'; import _ from 'underscore'; import { SessionIcon, SessionIconButton } from './session/icon'; import { useDispatch, useSelector } from 'react-redux'; import { SectionType } from '../state/ducks/section'; import { getFocusedSection } from '../state/selectors/section'; import { ConversationNotificationSettingType } from '../models/conversation'; import { forceSyncConfigurationNowIfNeeded } from '../session/utils/syncUtils'; import { updateUserDetailsModal } from '../state/ducks/modalDialog'; import { approveConversation, blockConvoById } from '../interactions/conversationInteractions'; import { useAvatarPath, useConversationUsername, useIsMe } from '../hooks/useParamSelector'; // tslint:disable-next-line: no-empty-interface export interface ConversationListItemProps extends ReduxConversationType {} export const StyledConversationListItemIconWrapper = styled.div` svg { margin: 0px 2px; } display: flex; flex-direction: row; `; type PropsHousekeeping = { style?: Object; isMessageRequest?: boolean; }; // tslint:disable: use-simple-attributes type Props = ConversationListItemProps & PropsHousekeeping; const Portal = ({ children }: { children: any }) => { return createPortal(children, document.querySelector('.inbox.index') as Element); }; const HeaderItem = (props: { unreadCount: number; mentionedUs: boolean; activeAt?: number; conversationId: string; isPinned: boolean; currentNotificationSetting: ConversationNotificationSettingType; }) => { const { unreadCount, mentionedUs, activeAt, isPinned, conversationId, currentNotificationSetting, } = props; let atSymbol = null; let unreadCountDiv = null; if (unreadCount > 0) { atSymbol = mentionedUs ?

@

: null; unreadCountDiv =

{unreadCount}

; } const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message; const pinIcon = isMessagesSection && isPinned ? ( ) : null; const NotificationSettingIcon = () => { if (!isMessagesSection) { return null; } switch (currentNotificationSetting) { case 'all': return null; case 'disabled': return ( ); case 'mentions_only': return ( ); default: return null; } }; return (
0 ? 'module-conversation-list-item__header__name--with-unread' : null )} >
{pinIcon} {unreadCountDiv} {atSymbol}
0 ? 'module-conversation-list-item__header__date--has-unread' : null )} >
); }; const UserItem = (props: { conversationId: string }) => { const { conversationId } = props; const shortenedPubkey = PubKey.shorten(conversationId); const isMe = useIsMe(conversationId); const username = useConversationUsername(conversationId); const displayedPubkey = username ? shortenedPubkey : conversationId; const displayName = isMe ? window.i18n('noteToSelf') : username; let shouldShowPubkey = false; if ((!username || username.length === 0) && (!displayName || displayName.length === 0)) { shouldShowPubkey = true; } return (
); }; const MessageItem = (props: { lastMessage?: LastMessageType; isTyping: boolean; unreadCount: number; isMessageRequest: boolean; conversationId: string; }) => { const { lastMessage, isTyping, unreadCount } = props; if (!lastMessage && !isTyping) { return null; } const text = lastMessage?.text || ''; if (isEmpty(text)) { return null; } return (
0 ? 'module-conversation-list-item__message__text--has-unread' : null )} > {isTyping ? ( ) : ( )}
{lastMessage && lastMessage.status && !props.isMessageRequest ? ( ) : null}
); }; const AvatarItem = (props: { conversationId: string; isPrivate: boolean }) => { const { isPrivate, conversationId } = props; const userName = useConversationUsername(conversationId); const avatarPath = useAvatarPath(conversationId); const dispatch = useDispatch(); return (
{ if (isPrivate) { dispatch( updateUserDetailsModal({ conversationId: conversationId, userName: userName || '', authorAvatarPath: avatarPath, }) ); } }} />
); }; const RejectMessageRequestButton = ({ conversationId }: { conversationId: string }) => { /** * Removes conversation from requests list, * adds ID to block list, syncs the block with linked devices. */ const handleConversationBlock = async () => { await blockConvoById(conversationId); await forceSyncConfigurationNowIfNeeded(); }; return ( ); }; const ApproveMessageRequestButton = ({ conversationId }: { conversationId: string }) => { return ( { await approveConversation(conversationId); }} backgroundColor="var(--color-accent)" iconColor="var(--color-foreground-primary)" iconPadding="var(--margins-xs)" borderRadius="2px" /> ); }; const MessageRequestButtons = ({ conversationId, isMessageRequest, }: { conversationId: string; isMessageRequest: boolean; }) => { if (!isMessageRequest) { return null; } return ( <> ); }; // tslint:disable: max-func-body-length const ConversationListItem = (props: Props) => { const { activeAt, unreadCount, id: conversationId, isSelected, isBlocked, style, mentionedUs, isMe, isPinned, isTyping, lastMessage, hasNickname, isKickedFromGroup, left, type, isPublic, avatarPath, isPrivate, currentNotificationSetting, isMessageRequest, } = props; const triggerId = `conversation-item-${conversationId}-ctxmenu`; const key = `conversation-item-${conversationId}`; const openConvo = useCallback( async (e: React.MouseEvent) => { // mousedown is invoked sooner than onClick, but for both right and left click if (e.button === 0) { await openConversationWithMessages({ conversationKey: conversationId }); } }, [conversationId] ); return (
{ e.stopPropagation(); e.preventDefault(); }} onContextMenu={(e: any) => { contextMenu.show({ id: triggerId, event: e, }); }} style={style} className={classNames( 'module-conversation-list-item', unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, unreadCount && unreadCount > 0 && mentionedUs ? 'module-conversation-list-item--mentioned-us' : null, isSelected ? 'module-conversation-list-item--is-selected' : null, isBlocked ? 'module-conversation-list-item--is-blocked' : null )} >
); }; export const MemoConversationListItemWithDetails = React.memo(ConversationListItem, _.isEqual);