You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/components/ConversationListItem.tsx

372 lines
9.8 KiB
TypeScript

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

import React from 'react';
import classNames from 'classnames';
import { isEmpty } from 'lodash';
import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu';
import { Portal } from 'react-portal';
import { Avatar } from './Avatar';
import { MessageBody } from './conversation/MessageBody';
import { Timestamp } from './conversation/Timestamp';
import { ContactName } from './conversation/ContactName';
import { TypingAnimation } from './conversation/TypingAnimation';
import { Colors, LocalizerType } from '../types/Util';
import { SessionButton, SessionButtonColor } from './session/SessionButton';
export type PropsData = {
id: string;
phoneNumber: string;
color?: string;
profileName?: string;
name?: string;
type: 'group' | 'direct';
avatarPath?: string;
isMe: boolean;
isPublic?: boolean;
isClosable?: boolean;
lastUpdated: number;
unreadCount: number;
mentionedUs: boolean;
isSelected: boolean;
isTyping: boolean;
lastMessage?: {
status: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
text: string;
isRss: boolean;
};
isPendingFriendRequest?: boolean;
hasReceivedFriendRequest?: boolean;
hasSentFriendRequest?: boolean;
isBlocked?: boolean;
isOnline?: boolean;
hasNickname?: boolean;
isFriendItem?: boolean;
isSecondary?: boolean;
isGroupInvitation?: boolean;
};
type PropsHousekeeping = {
i18n: LocalizerType;
style?: Object;
onClick?: (id: string) => void;
onDeleteMessages?: () => void;
onDeleteContact?: () => void;
onBlockContact?: () => void;
onChangeNickname?: () => void;
onClearNickname?: () => void;
onCopyPublicKey?: () => void;
onUnblockContact?: () => void;
acceptFriendRequest?: () => void;
declineFriendRequest?: () => void;
};
type Props = PropsData & PropsHousekeeping;
export class ConversationListItem extends React.PureComponent<Props> {
public renderAvatar() {
const {
avatarPath,
color,
type,
i18n,
isMe,
name,
phoneNumber,
profileName,
isOnline,
isPendingFriendRequest,
} = this.props;
let borderColor = undefined;
if (!isPendingFriendRequest) {
borderColor = isOnline ? Colors.ONLINE : Colors.OFFLINE;
}
const iconSize = isPendingFriendRequest ? 28 : 48;
return (
<div className="module-conversation-list-item__avatar-container">
<Avatar
avatarPath={avatarPath}
color={color}
noteToSelf={isMe}
conversationType={type}
i18n={i18n}
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
size={iconSize}
borderColor={borderColor}
/>
</div>
);
}
public renderUnread() {
const { unreadCount, mentionedUs } = this.props;
if (unreadCount > 0) {
const atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
return (
<div>
<p className="module-conversation-list-item__unread-count">
{unreadCount}
</p>
{atSymbol}
</div>
);
}
return null;
}
public renderHeader() {
const { unreadCount, i18n, isMe, lastUpdated, isFriendItem, hasReceivedFriendRequest } = this.props;
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
)}
>
{isMe ? i18n('noteToSelf') : this.renderUser()}
</div>
{hasReceivedFriendRequest || this.renderUnread()}
{!isFriendItem && (
<div
className={classNames(
'module-conversation-list-item__header__date',
unreadCount > 0
? 'module-conversation-list-item__header__date--has-unread'
: null
)}
>
{!hasReceivedFriendRequest && (<Timestamp
timestamp={lastUpdated}
extended={false}
module="module-conversation-list-item__header__timestamp"
i18n={i18n}
/>)
}
</div>
)}
</div>
);
}
public renderContextMenu(triggerId: string) {
const {
i18n,
isBlocked,
isMe,
isClosable,
isPublic,
hasNickname,
onDeleteContact,
onDeleteMessages,
onBlockContact,
onChangeNickname,
onClearNickname,
onCopyPublicKey,
onUnblockContact,
} = this.props;
const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
const blockHandler = isBlocked ? onUnblockContact : onBlockContact;
return (
<ContextMenu id={triggerId}>
{!isPublic && !isMe ? (
<MenuItem onClick={blockHandler}>{blockTitle}</MenuItem>
) : null}
{!isPublic && !isMe ? (
<MenuItem onClick={onChangeNickname}>
{i18n('changeNickname')}
</MenuItem>
) : null}
{!isPublic && !isMe && hasNickname ? (
<MenuItem onClick={onClearNickname}>{i18n('clearNickname')}</MenuItem>
) : null}
{!isPublic ? (
<MenuItem onClick={onCopyPublicKey}>{i18n('copyPublicKey')}</MenuItem>
) : null}
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
{!isMe && isClosable ? (
!isPublic ? (
<MenuItem onClick={onDeleteContact}>
{i18n('deleteContact')}
</MenuItem>
) : (
<MenuItem onClick={onDeleteContact}>
{i18n('deletePublicChannel')}
</MenuItem>
)
) : null}
</ContextMenu>
);
}
public renderMessage() {
const {
lastMessage,
isTyping,
unreadCount,
i18n,
isFriendItem,
isPendingFriendRequest,
} = this.props;
if (isFriendItem) {
return null;
}
if (!lastMessage && !isTyping) {
return null;
}
let text = lastMessage && lastMessage.text ? lastMessage.text : '';
// if coming from Rss feed
if (lastMessage && lastMessage.isRss) {
// strip any HTML
text = text.replace(/<[^>]*>?/gm, '');
}
if (isPendingFriendRequest) {
text = text.replace('Friend Request: ', '');
}
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
)}
>
{isTyping ? (
<TypingAnimation i18n={i18n} />
) : (
<MessageBody
text={text}
disableJumbomoji={true}
disableLinks={true}
i18n={i18n}
/>
)}
</div>
{lastMessage && lastMessage.status ? (
<div
className={classNames(
'module-conversation-list-item__message__status-icon',
`module-conversation-list-item__message__status-icon--${
lastMessage.status
}`
)}
/>
) : null}
</div>
);
}
public renderFriendRequestButtons() {
const { acceptFriendRequest, declineFriendRequest } = this.props;
return (
<div className="module-conversation-list-item__buttons">
<SessionButton text={window.i18n('decline')} buttonColor={SessionButtonColor.None} onClick={declineFriendRequest}/>
<SessionButton text={window.i18n('accept')} onClick={acceptFriendRequest}/>
</div>
);
}
public render() {
const {
phoneNumber,
unreadCount,
onClick,
id,
isSelected,
hasReceivedFriendRequest,
isBlocked,
style,
mentionedUs,
} = this.props;
const triggerId = `${phoneNumber}-ctxmenu-${Date.now()}`;
return (
<div>
<ContextMenuTrigger id={triggerId}>
<div
role="button"
onClick={() => {
if (onClick) {
onClick(id);
}
}}
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,
hasReceivedFriendRequest
? 'module-conversation-list-item--has-friend-request'
: null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
{this.renderAvatar()}
<div className="module-conversation-list-item__content">
{this.renderHeader()}
{this.renderMessage()}
</div>
{hasReceivedFriendRequest && this.renderFriendRequestButtons()}
</div>
</ContextMenuTrigger>
<Portal>{this.renderContextMenu(triggerId)}</Portal>
</div>
);
}
private renderUser() {
const { name, phoneNumber, profileName } = this.props;
const shortenedPubkey = window.shortenPubkey(phoneNumber);
const displayedPubkey = profileName ? shortenedPubkey : phoneNumber;
return (
<div className="module-conversation__user">
<ContactName
phoneNumber={displayedPubkey}
name={name}
profileName={profileName}
module="module-conversation__user"
i18n={window.i18n}
boldProfileName={true}
/>
</div>
);
}
}