session-desktop/ts/components/ConversationListItem.tsx

306 lines
7.8 KiB
TypeScript
Raw Normal View History

import React from 'react';
import classNames from 'classnames';
import { isEmpty } from 'lodash';
import { contextMenu } from 'react-contexify';
import { Avatar } from './Avatar';
import { MessageBody } from './conversation/MessageBody';
import { Timestamp } from './conversation/Timestamp';
import { ContactName } from './conversation/ContactName';
2018-11-14 20:10:32 +01:00
import { TypingAnimation } from './conversation/TypingAnimation';
import { LocalizerType } from '../types/Util';
import {
ConversationAvatar,
usingClosedConversationDetails,
} from './session/usingClosedConversationDetails';
2020-10-22 07:34:41 +02:00
import {
ConversationListItemContextMenu,
PropsContextConversationItem,
} from './session/menu/ConversationListItemContextMenu';
import { createPortal } from 'react-dom';
2019-01-14 22:49:58 +01:00
export type PropsData = {
id: string;
phoneNumber: string;
2019-01-14 22:49:58 +01:00
color?: string;
profileName?: string;
name?: string;
2019-01-14 22:49:58 +01:00
type: 'group' | 'direct';
avatarPath?: string;
2019-01-31 02:45:58 +01:00
isMe: boolean;
isPublic?: boolean;
isRss?: boolean;
isClosable?: boolean;
2020-05-14 07:13:06 +02:00
primaryDevice?: string;
lastUpdated: number;
unreadCount: number;
mentionedUs: boolean;
isSelected: boolean;
2018-11-14 20:10:32 +01:00
isTyping: boolean;
lastMessage?: {
status: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
text: string;
2019-07-23 08:15:39 +02:00
isRss: boolean;
};
isBlocked?: boolean;
isOnline?: boolean;
hasNickname?: boolean;
isSecondary?: boolean;
2019-11-22 06:16:43 +01:00
isGroupInvitation?: boolean;
isKickedFromGroup?: boolean;
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
2019-01-14 22:49:58 +01:00
};
2019-01-14 22:49:58 +01:00
type PropsHousekeeping = {
i18n: LocalizerType;
style?: Object;
2019-01-14 22:49:58 +01:00
onClick?: (id: string) => void;
onDeleteMessages?: () => void;
onDeleteContact?: () => void;
onBlockContact?: () => void;
onCopyPublicKey?: () => void;
onUnblockContact?: () => void;
onInviteContacts?: () => void;
onClearNickname?: () => void;
2019-01-14 22:49:58 +01:00
};
2019-01-14 22:49:58 +01:00
type Props = PropsData & PropsHousekeeping;
const Portal = ({ children }: { children: any }) => {
return createPortal(children, document.querySelector('.inbox.index') as Element);
}
class ConversationListItem extends React.PureComponent<Props> {
public constructor(props: Props) {
super(props);
}
public renderAvatar() {
const {
avatarPath,
i18n,
name,
phoneNumber,
profileName,
memberAvatars,
} = this.props;
2020-06-15 10:05:14 +02:00
const iconSize = 36;
2020-09-15 07:07:22 +02:00
const userName = name || profileName || phoneNumber;
return (
<div className="module-conversation-list-item__avatar-container">
<Avatar
avatarPath={avatarPath}
2020-09-15 07:07:22 +02:00
name={userName}
size={iconSize}
memberAvatars={memberAvatars}
2020-09-15 07:07:22 +02:00
pubkey={phoneNumber}
/>
</div>
);
}
public renderHeader() {
const { unreadCount, mentionedUs, i18n, isMe, lastUpdated } = this.props;
let atSymbol = null;
let unreadCountDiv = null;
if (unreadCount > 0) {
atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
unreadCountDiv = (
<p className="module-conversation-list-item__unread-count">
{unreadCount}
</p>
);
}
return (
<div className="module-conversation-list-item__header">
<div
className={classNames(
'module-conversation-list-item__header__name',
unreadCount > 0
? 'module-conversation-list-item__header__name--with-unread'
: null
)}
>
{this.renderUser()}
</div>
{unreadCountDiv}
{atSymbol}
2020-06-16 02:00:37 +02:00
{
<div
className={classNames(
'module-conversation-list-item__header__date',
unreadCount > 0
? 'module-conversation-list-item__header__date--has-unread'
: null
)}
>
2020-06-16 02:00:37 +02:00
{
2020-01-03 01:49:42 +01:00
<Timestamp
timestamp={lastUpdated}
extended={false}
module="module-conversation-list-item__header__timestamp"
i18n={i18n}
/>
2020-06-16 02:00:37 +02:00
}
</div>
2020-06-16 02:00:37 +02:00
}
</div>
);
}
public renderMessage() {
2020-06-16 02:00:37 +02:00
const { lastMessage, isTyping, unreadCount, i18n } = this.props;
2018-11-14 20:10:32 +01:00
if (!lastMessage && !isTyping) {
return null;
}
2019-07-23 08:15:39 +02:00
let text = lastMessage && lastMessage.text ? lastMessage.text : '';
// if coming from Rss feed
if (lastMessage && lastMessage.isRss) {
// strip any HTML
text = text.replace(/<[^>]*>?/gm, '');
}
if (isEmpty(text)) {
return null;
}
return (
<div className="module-conversation-list-item__message">
<div
className={classNames(
'module-conversation-list-item__message__text',
unreadCount > 0
? 'module-conversation-list-item__message__text--has-unread'
: null
)}
>
2018-11-14 20:10:32 +01:00
{isTyping ? (
<TypingAnimation i18n={i18n} />
) : (
<MessageBody
isGroup={true}
text={text}
disableJumbomoji={true}
disableLinks={true}
i18n={i18n}
/>
)}
</div>
2018-11-14 20:10:32 +01:00
{lastMessage && lastMessage.status ? (
<div
className={classNames(
'module-conversation-list-item__message__status-icon',
2020-05-29 08:25:15 +02:00
`module-conversation-list-item__message__status-icon--${lastMessage.status}`
)}
/>
) : null}
</div>
);
}
public render() {
2019-01-16 05:44:13 +01:00
const {
phoneNumber,
2019-01-16 05:44:13 +01:00
unreadCount,
onClick,
id,
2019-01-16 05:44:13 +01:00
isSelected,
isBlocked,
style,
mentionedUs,
2019-01-16 05:44:13 +01:00
} = this.props;
2020-10-22 07:34:41 +02:00
const triggerId = `conversation-item-${phoneNumber}-ctxmenu`;
const key = `conversation-item-${phoneNumber}`;
return (
2020-10-22 07:34:41 +02:00
<div key={key}>
<div
role="button"
onClick={() => {
if (onClick) {
onClick(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
)}
>
{this.renderAvatar()}
<div className="module-conversation-list-item__content">
{this.renderHeader()}
{this.renderMessage()}
</div>
</div>
<Portal>
<ConversationListItemContextMenu {...this.getMenuProps(triggerId)} />
</Portal>
</div>
);
2020-01-07 03:24:44 +01:00
}
2020-10-22 07:34:41 +02:00
private getMenuProps(triggerId: string): PropsContextConversationItem {
return {
triggerId,
...this.props,
};
}
private renderUser() {
const { name, phoneNumber, profileName, isMe, i18n } = this.props;
const shortenedPubkey = window.shortenPubkey(phoneNumber);
const displayedPubkey = profileName ? shortenedPubkey : phoneNumber;
const displayName = isMe ? i18n('noteToSelf') : profileName;
let shouldShowPubkey = false;
if (!name || name.length === 0) {
shouldShowPubkey = true;
}
return (
<div className="module-conversation__user">
<ContactName
phoneNumber={displayedPubkey}
name={name}
profileName={displayName}
module="module-conversation__user"
i18n={window.i18n}
boldProfileName={true}
shouldShowPubkey={shouldShowPubkey}
/>
</div>
);
}
}
export const ConversationListItemWithDetails = usingClosedConversationDetails(
ConversationListItem
);