diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 29e44d3e0..398b699bb 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -19,12 +19,18 @@ import { } from './session/menu/ConversationListItemContextMenu'; import { createPortal } from 'react-dom'; import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageStatus'; -import { DefaultTheme, withTheme } from 'styled-components'; +import { DefaultTheme, useTheme, withTheme } from 'styled-components'; import { PubKey } from '../session/types'; -import { ConversationType, openConversationExternal } from '../state/ducks/conversations'; +import { + ConversationType, + LastMessageType, + openConversationExternal, +} from '../state/ducks/conversations'; import { SessionIcon, SessionIconSize, SessionIconType } from './session/icon'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { SectionType } from './session/ActionsPanel'; +import { getTheme } from '../state/selectors/theme'; +import { getFocusedSection } from '../state/selectors/section'; export interface ConversationListItemProps extends ConversationType { index?: number; // used to force a refresh when one conversation is removed on top of the list @@ -42,197 +48,274 @@ const Portal = ({ children }: { children: any }) => { return createPortal(children, document.querySelector('.inbox.index') as Element); }; -class ConversationListItem extends React.PureComponent { - public constructor(props: Props) { - super(props); - } +const ConversationListItem = (props: Props) => { + const { + phoneNumber, + unreadCount, + id, + isSelected, + isBlocked, + style, + mentionedUs, + avatarPath, + name, + profileName, + activeAt, + isMe, + isPinned, + isTyping, + lastMessage, + memberAvatars, + } = props; + const triggerId: string = `conversation-item-${phoneNumber}-ctxmenu`; + const key: string = `conversation-item-${phoneNumber}`; - public renderAvatar() { - const { avatarPath, name, phoneNumber, profileName, memberAvatars } = this.props; + const dispatch = useDispatch(); - const userName = name || profileName || phoneNumber; + const menuProps: PropsContextConversationItem = { + ...props, + triggerId, + }; - return ( -
- +
{ + dispatch(openConversationExternal(id)); + }} + onContextMenu={(e: any) => { + contextMenu.show({ + id: triggerId, + event: e, + }); + }} + style={style} + className={classNames( + 'module-conversation-list-item', + unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, + 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 + )} + > + +
+ + +
- ); - } + + + +
+ ); +}; - public renderHeader() { - const { unreadCount, mentionedUs, activeAt, isPinned } = this.props; - - let atSymbol = null; - let unreadCountDiv = null; - if (unreadCount > 0) { - atSymbol = mentionedUs ?

@

: null; - unreadCountDiv =

{unreadCount}

; - } - - const isMessagesSection = - window.inboxStore?.getState().section.focusedSection === SectionType.Message; - - const pinIcon = - isMessagesSection && isPinned ? ( - - ) : null; +export interface ConversationListItemAvatarProps { + avatarPath?: string; + name?: string; + profileName?: string; + phoneNumber?: string; + memberAvatars?: Array; +} - return ( -
-
0 ? 'module-conversation-list-item__header__name--with-unread' : null - )} - > - {this.renderUser()} -
+export const ConversationListItemAvatar = (props: ConversationListItemAvatarProps) => { + const { avatarPath, name, phoneNumber, profileName, memberAvatars } = props; + const userName = name || profileName || phoneNumber; - {pinIcon} - {unreadCountDiv} - {atSymbol} - { -
0 ? 'module-conversation-list-item__header__date--has-unread' : null - )} - > - { - - } -
- } -
- ); - } + return ( +
+ +
+ ); +}; - public renderMessage() { - const { lastMessage, isTyping, unreadCount } = this.props; +export interface ConversationListItemHeaderProps { + unreadCount: number; + mentionedUs: boolean; + activeAt?: number; + isPinned?: boolean; - if (!lastMessage && !isTyping) { - return null; - } - const text = lastMessage && lastMessage.text ? lastMessage.text : ''; + name?: string; + phoneNumber: string; + profileName?: string; + isMe: boolean; +} - if (isEmpty(text)) { - return null; - } +export const ConversationListItemHeader = (props: ConversationListItemHeaderProps) => { + const { + unreadCount, + mentionedUs, + activeAt, + isPinned, + name, + phoneNumber, + profileName, + isMe, + } = props; - return ( -
-
0 ? 'module-conversation-list-item__message__text--has-unread' : null - )} - > - {isTyping ? ( - - ) : ( - - )} -
- {lastMessage && lastMessage.status ? ( - - ) : null} -
- ); + const theme = useTheme(); + + let atSymbol = null; + let unreadCountDiv = null; + if (unreadCount > 0) { + atSymbol = mentionedUs ?

@

: null; + unreadCountDiv =

{unreadCount}

; } - public render() { - const { phoneNumber, unreadCount, id, isSelected, isBlocked, style, mentionedUs } = this.props; - const triggerId = `conversation-item-${phoneNumber}-ctxmenu`; - const key = `conversation-item-${phoneNumber}`; + const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message; + + const pinIcon = + isMessagesSection && isPinned ? ( + + ) : null; - return ( -
+ return ( +
+
0 ? 'module-conversation-list-item__header__name--with-unread' : null + )} + > + +
+ + {pinIcon} + {unreadCountDiv} + {atSymbol} + {
{ - window.inboxStore?.dispatch(openConversationExternal(id)); - }} - onContextMenu={(e: any) => { - contextMenu.show({ - id: triggerId, - event: e, - }); - }} - style={style} className={classNames( - 'module-conversation-list-item', - unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, - 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 + 'module-conversation-list-item__header__date', + unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null )} > - {this.renderAvatar()} -
- {this.renderHeader()} - {this.renderMessage()} -
+ { + + }
- - - -
- ); + } +
+ ); +}; + +export interface ConversationListMessageProps { + lastMessage: LastMessageType; + isTyping: boolean; + unreadCount: number; +} + +export const ConversationListItemMessage = (props: any) => { + const { lastMessage, isTyping, unreadCount } = props; + const theme = useTheme(); + + if (!lastMessage && !isTyping) { + return null; } + const text = lastMessage && lastMessage.text ? lastMessage.text : ''; - private getMenuProps(triggerId: string): PropsContextConversationItem { - return { - triggerId, - ...this.props, - }; + if (isEmpty(text)) { + return null; } - private renderUser() { - const { name, phoneNumber, profileName, isMe } = this.props; + return ( +
+
0 ? 'module-conversation-list-item__message__text--has-unread' : null + )} + > + {isTyping ? ( + + ) : ( + + )} +
+ {lastMessage && lastMessage.status ? ( + + ) : null} +
+ ); +}; - const shortenedPubkey = PubKey.shorten(phoneNumber); +export interface ConversationListItemUserProps { + name?: string; + phoneNumber: string; + profileName?: string; + isMe: boolean; +} - const displayedPubkey = profileName ? shortenedPubkey : phoneNumber; - const displayName = isMe ? window.i18n('noteToSelf') : profileName; +export const ConversationListItemUser = (props: ConversationListItemUserProps) => { + const { name, phoneNumber, profileName, isMe } = props; - let shouldShowPubkey = false; - if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) { - shouldShowPubkey = true; - } + const shortenedPubkey = PubKey.shorten(phoneNumber); - return ( -
- -
- ); + const displayedPubkey = profileName ? shortenedPubkey : phoneNumber; + const displayName = isMe ? window.i18n('noteToSelf') : profileName; + + let shouldShowPubkey = false; + if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) { + shouldShowPubkey = true; } -} + + return ( +
+ +
+ ); +}; export const ConversationListItemWithDetails = usingClosedConversationDetails( withTheme(ConversationListItem) diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx index 1fc0102d4..f31dc6a52 100644 --- a/ts/components/session/menu/Menu.tsx +++ b/ts/components/session/menu/Menu.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { NotificationForConvoOption, TimerOption } from '../../conversation/ConversationHeader'; import { Item, Submenu } from 'react-contexify'; import { ConversationNotificationSettingType } from '../../../models/conversation'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { changeNickNameModal, updateConfirmModal } from '../../../state/ducks/modalDialog'; import { getConversationController } from '../../../session/conversations'; import {