signal-desktop/ts/components/ConversationListItem.tsx

284 lines
7.3 KiB
TypeScript
Raw Normal View History

2020-10-30 20:34:04 +00:00
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
2020-09-12 00:46:52 +00:00
import React, { CSSProperties } from 'react';
import classNames from 'classnames';
import { isNumber } from 'lodash';
import { Avatar } from './Avatar';
import { MessageBody } from './conversation/MessageBody';
import { Timestamp } from './conversation/Timestamp';
import { ContactName } from './conversation/ContactName';
2018-11-14 19:10:32 +00:00
import { TypingAnimation } from './conversation/TypingAnimation';
2019-11-19 23:03:00 +00:00
import { cleanId } from './_util';
2018-11-14 19:10:32 +00:00
import { LocalizerType } from '../types/Util';
import { ColorType } from '../types/Colors';
export const MessageStatuses = [
'sending',
'sent',
'delivered',
'read',
'error',
'partial-sent',
] as const;
export type MessageStatusType = typeof MessageStatuses[number];
2019-01-14 21:49:58 +00:00
export type PropsData = {
id: string;
2020-07-24 01:35:32 +00:00
phoneNumber?: string;
color?: ColorType;
profileName?: string;
2020-07-24 01:35:32 +00:00
title: string;
name?: string;
2019-01-14 21:49:58 +00:00
type: 'group' | 'direct';
avatarPath?: string;
isMe?: boolean;
2020-08-27 19:45:08 +00:00
muteExpiresAt?: number;
2020-11-20 17:30:45 +00:00
lastUpdated?: number;
unreadCount?: number;
2020-11-20 17:30:45 +00:00
markedUnread?: boolean;
isSelected?: boolean;
acceptedMessageRequest?: boolean;
2019-08-07 00:40:25 +00:00
draftPreview?: string;
shouldShowDraft?: boolean;
2020-09-12 00:46:52 +00:00
typingContact?: unknown;
lastMessage?: {
status: MessageStatusType;
text: string;
2020-04-29 21:24:12 +00:00
deletedForEveryone?: boolean;
};
isPinned?: boolean;
2019-01-14 21:49:58 +00:00
};
2019-01-14 21:49:58 +00:00
type PropsHousekeeping = {
i18n: LocalizerType;
2020-09-12 00:46:52 +00:00
style?: CSSProperties;
2019-01-14 21:49:58 +00:00
onClick?: (id: string) => void;
};
export type Props = PropsData & PropsHousekeeping;
2019-01-14 21:49:58 +00:00
export class ConversationListItem extends React.PureComponent<Props> {
2020-09-12 00:46:52 +00:00
public renderAvatar(): JSX.Element {
const {
avatarPath,
color,
2019-01-14 21:49:58 +00:00
type,
i18n,
2019-01-31 01:45:58 +00:00
isMe,
name,
phoneNumber,
profileName,
2020-07-24 01:35:32 +00:00
title,
} = this.props;
return (
<div className="module-conversation-list-item__avatar-container">
<Avatar
avatarPath={avatarPath}
color={color}
2019-01-31 01:45:58 +00:00
noteToSelf={isMe}
2019-01-14 21:49:58 +00:00
conversationType={type}
i18n={i18n}
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
2020-07-24 01:35:32 +00:00
title={title}
2019-10-04 18:06:17 +00:00
size={52}
/>
{this.renderUnread()}
</div>
);
}
isUnread(): boolean {
const { markedUnread, unreadCount } = this.props;
2020-11-20 17:30:45 +00:00
return Boolean((isNumber(unreadCount) && unreadCount > 0) || markedUnread);
}
2020-09-12 00:46:52 +00:00
public renderUnread(): JSX.Element | null {
const { unreadCount } = this.props;
if (this.isUnread()) {
return (
<div className="module-conversation-list-item__unread-count">
{unreadCount || ''}
</div>
);
}
return null;
}
2020-09-12 00:46:52 +00:00
public renderHeader(): JSX.Element {
const {
i18n,
2019-01-31 01:45:58 +00:00
isMe,
lastUpdated,
name,
phoneNumber,
profileName,
2020-07-24 01:35:32 +00:00
title,
} = this.props;
return (
<div className="module-conversation-list-item__header">
<div
className={classNames(
'module-conversation-list-item__header__name',
this.isUnread()
? 'module-conversation-list-item__header__name--with-unread'
: null
)}
>
2019-01-31 01:45:58 +00:00
{isMe ? (
i18n('noteToSelf')
) : (
<ContactName
phoneNumber={phoneNumber}
name={name}
profileName={profileName}
2020-07-24 01:35:32 +00:00
title={title}
i18n={i18n}
2019-01-31 01:45:58 +00:00
/>
)}
</div>
<div
className={classNames(
'module-conversation-list-item__header__date',
this.isUnread()
? 'module-conversation-list-item__header__date--has-unread'
: null
)}
>
<Timestamp
timestamp={lastUpdated}
extended={false}
module="module-conversation-list-item__header__timestamp"
withUnread={this.isUnread()}
i18n={i18n}
/>
</div>
</div>
);
}
2020-09-12 00:46:52 +00:00
public renderMessage(): JSX.Element | null {
2019-08-07 00:40:25 +00:00
const {
draftPreview,
i18n,
acceptedMessageRequest,
2019-08-07 00:40:25 +00:00
lastMessage,
2020-08-27 19:45:08 +00:00
muteExpiresAt,
2019-08-07 00:40:25 +00:00
shouldShowDraft,
typingContact,
} = this.props;
if (!lastMessage && !typingContact) {
return null;
}
const messageBody = lastMessage ? lastMessage.text : '';
const showingDraft = shouldShowDraft && draftPreview;
2020-04-29 21:24:12 +00:00
const deletedForEveryone = Boolean(
lastMessage && lastMessage.deletedForEveryone
);
2020-09-12 00:46:52 +00:00
/* eslint-disable no-nested-ternary */
return (
<div className="module-conversation-list-item__message">
2020-01-07 01:47:40 +00:00
<div
dir="auto"
className={classNames(
'module-conversation-list-item__message__text',
this.isUnread()
? 'module-conversation-list-item__message__text--has-unread'
: null
)}
>
{muteExpiresAt && Date.now() < muteExpiresAt && (
2020-08-27 19:45:08 +00:00
<span className="module-conversation-list-item__muted" />
)}
{!acceptedMessageRequest ? (
2020-08-07 00:50:54 +00:00
<span className="module-conversation-list-item__message-request">
{i18n('ConversationListItem--message-request')}
</span>
) : typingContact ? (
2018-11-14 19:10:32 +00:00
<TypingAnimation i18n={i18n} />
) : (
<>
{showingDraft ? (
<>
<span className="module-conversation-list-item__message__draft-prefix">
{i18n('ConversationListItem--draft-prefix')}
</span>
<MessageBody
text={(draftPreview || '').split('\n')[0]}
disableJumbomoji
disableLinks
i18n={i18n}
/>
</>
2020-04-29 21:24:12 +00:00
) : deletedForEveryone ? (
<span className="module-conversation-list-item__message__deleted-for-everyone">
{i18n('message--deletedForEveryone')}
</span>
2020-10-30 17:56:03 +00:00
) : (
<MessageBody
text={(messageBody || '').split('\n')[0]}
2020-10-30 17:56:03 +00:00
disableJumbomoji
disableLinks
i18n={i18n}
/>
)}
</>
2018-11-14 19:10:32 +00:00
)}
</div>
{!showingDraft && lastMessage && lastMessage.status ? (
<div
className={classNames(
'module-conversation-list-item__message__status-icon',
2020-01-08 17:44:54 +00:00
`module-conversation-list-item__message__status-icon--${lastMessage.status}`
)}
/>
) : null}
</div>
);
}
2020-09-12 00:46:52 +00:00
/* eslint-enable no-nested-ternary */
2020-09-12 00:46:52 +00:00
public render(): JSX.Element {
const { id, isSelected, onClick, style } = this.props;
return (
2019-11-07 21:36:16 +00:00
<button
2020-09-12 00:46:52 +00:00
type="button"
2019-01-14 21:49:58 +00:00
onClick={() => {
if (onClick) {
onClick(id);
}
}}
style={style}
className={classNames(
'module-conversation-list-item',
this.isUnread() ? 'module-conversation-list-item--has-unread' : null,
isSelected ? 'module-conversation-list-item--is-selected' : null
)}
2019-11-07 21:36:16 +00:00
data-id={cleanId(id)}
>
{this.renderAvatar()}
<div className="module-conversation-list-item__content">
{this.renderHeader()}
{this.renderMessage()}
</div>
2019-11-07 21:36:16 +00:00
</button>
);
}
}