// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback, FunctionComponent, ReactNode } from 'react'; import classNames from 'classnames'; import { BaseConversationListItem, MESSAGE_TEXT_CLASS_NAME, } from './BaseConversationListItem'; import { MessageBody } from '../conversation/MessageBody'; import { ContactName } from '../conversation/ContactName'; import { TypingAnimation } from '../conversation/TypingAnimation'; import { LocalizerType } from '../../types/Util'; import { ConversationType } from '../../state/ducks/conversations'; const MESSAGE_STATUS_ICON_CLASS_NAME = `${MESSAGE_TEXT_CLASS_NAME}__status-icon`; export const MessageStatuses = [ 'sending', 'sent', 'delivered', 'read', 'paused', 'error', 'partial-sent', ] as const; export type MessageStatusType = typeof MessageStatuses[number]; export type PropsData = Pick< ConversationType, | 'acceptedMessageRequest' | 'avatarPath' | 'color' | 'draftPreview' | 'id' | 'isMe' | 'isPinned' | 'isSelected' | 'lastMessage' | 'lastUpdated' | 'markedUnread' | 'muteExpiresAt' | 'name' | 'phoneNumber' | 'profileName' | 'sharedGroupNames' | 'shouldShowDraft' | 'title' | 'type' | 'typingContact' | 'unblurredAvatarPath' | 'unreadCount' >; type PropsHousekeeping = { i18n: LocalizerType; onClick: (id: string) => void; }; export type Props = PropsData & PropsHousekeeping; export const ConversationListItem: FunctionComponent = React.memo( function ConversationListItem({ acceptedMessageRequest, avatarPath, color, draftPreview, i18n, id, isMe, isSelected, lastMessage, lastUpdated, markedUnread, muteExpiresAt, name, onClick, phoneNumber, profileName, sharedGroupNames, shouldShowDraft, title, type, typingContact, unblurredAvatarPath, unreadCount, }) { const headerName = isMe ? ( i18n('noteToSelf') ) : ( ); let messageText: ReactNode = null; let messageStatusIcon: ReactNode = null; if (!acceptedMessageRequest) { messageText = ( {i18n('ConversationListItem--message-request')} ); } else if (typingContact) { messageText = ; } else if (shouldShowDraft && draftPreview) { messageText = ( <> {i18n('ConversationListItem--draft-prefix')} ); } else if (lastMessage?.deletedForEveryone) { messageText = ( {i18n('message--deletedForEveryone')} ); } else if (lastMessage) { messageText = ( ); if (lastMessage.status) { messageStatusIcon = (
); } } const isMuted = Boolean(muteExpiresAt && Date.now() < muteExpiresAt); if (isMuted) { messageText = ( <> {messageText} ); } const onClickItem = useCallback(() => onClick(id), [onClick, id]); return ( ); } ); // This takes `unknown` because, sometimes, values from the database don't match our // types. In the long term, we should fix that. In the short term, this smooths over the // problem. function truncateMessageText(text: unknown): string { if (typeof text !== 'string') { return ''; } return text.replace(/(?:\r?\n)+/g, ' '); }