// Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { FunctionComponent, ReactNode } from 'react'; import React, { useCallback } from 'react'; import { noop } from 'lodash'; import { ContactName } from '../conversation/ContactName'; import type { BodyRangesForDisplayType } from '../../types/BodyRange'; import { processBodyRangesForSearchResult } from '../../types/BodyRange'; import type { LocalizerType, ThemeType } from '../../types/Util'; import { BaseConversationListItem } from './BaseConversationListItem'; import type { ConversationType, ShowConversationType, } from '../../state/ducks/conversations'; import type { PreferredBadgeSelectorType } from '../../state/selectors/badges'; import { Intl } from '../Intl'; import { MessageTextRenderer, RenderLocation, } from '../conversation/MessageTextRenderer'; const EMPTY_OBJECT = Object.freeze(Object.create(null)); export type PropsDataType = { isSelected?: boolean; isSearchingInConversation?: boolean; id: string; conversationId: string; sentAt?: number; snippet: string; body: string; bodyRanges: BodyRangesForDisplayType; from: Pick< ConversationType, | 'acceptedMessageRequest' | 'avatarPath' | 'badges' | 'color' | 'isMe' | 'phoneNumber' | 'profileName' | 'sharedGroupNames' | 'title' | 'type' | 'unblurredAvatarPath' >; to: Pick< ConversationType, 'isMe' | 'phoneNumber' | 'profileName' | 'title' | 'type' >; }; type PropsHousekeepingType = { getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; showConversation: ShowConversationType; theme: ThemeType; }; export type PropsType = PropsDataType & PropsHousekeepingType; const renderPerson = ( i18n: LocalizerType, person: Readonly<{ isMe?: boolean; title: string; }> ): ReactNode => person.isMe ? i18n('icu:you') : <ContactName title={person.title} />; export const MessageSearchResult: FunctionComponent<PropsType> = React.memo( function MessageSearchResult({ body, bodyRanges, conversationId, from, getPreferredBadge, i18n, id, sentAt, showConversation, snippet, theme, to, }) { const onClickItem = useCallback(() => { showConversation({ conversationId, messageId: id }); }, [showConversation, conversationId, id]); if (!from || !to) { // Note: mapStateToProps() may return null if the message is not found. return <div />; } const isNoteToSelf = from.isMe && to.isMe; let headerName: ReactNode; if (isNoteToSelf) { headerName = i18n('icu:noteToSelf'); } else if (from.isMe) { if (to.type === 'group') { headerName = ( <span> <Intl i18n={i18n} id="icu:searchResultHeader--you-to-group" components={{ receiverGroup: renderPerson(i18n, to), }} /> </span> ); } else { headerName = ( <span> <Intl i18n={i18n} id="icu:searchResultHeader--you-to-receiver" components={{ receiverContact: renderPerson(i18n, to), }} /> </span> ); } } else { // eslint-disable-next-line no-lonely-if if (to.type === 'group') { headerName = ( <span> <Intl i18n={i18n} id="icu:searchResultHeader--sender-to-group" components={{ sender: renderPerson(i18n, from), receiverGroup: renderPerson(i18n, to), }} /> </span> ); } else { headerName = ( <span> <Intl i18n={i18n} id="icu:searchResultHeader--sender-to-you" components={{ sender: renderPerson(i18n, from), }} /> </span> ); } } const { cleanedSnippet, bodyRanges: displayBodyRanges } = processBodyRangesForSearchResult({ snippet, body, bodyRanges }); const messageText = ( <MessageTextRenderer messageText={cleanedSnippet} bodyRanges={displayBodyRanges} direction={undefined} disableLinks emojiSizeClass={undefined} i18n={i18n} isSpoilerExpanded={EMPTY_OBJECT} onMentionTrigger={noop} renderLocation={RenderLocation.SearchResult} textLength={cleanedSnippet.length} /> ); return ( <BaseConversationListItem acceptedMessageRequest={from.acceptedMessageRequest} avatarPath={from.avatarPath} badge={getPreferredBadge(from.badges)} color={from.color} conversationType="direct" headerDate={sentAt} headerName={headerName} i18n={i18n} id={id} isNoteToSelf={isNoteToSelf} isMe={from.isMe} isSelected={false} messageText={messageText} onClick={onClickItem} phoneNumber={from.phoneNumber} profileName={from.profileName} sharedGroupNames={from.sharedGroupNames} theme={theme} title={from.title} unblurredAvatarPath={from.unblurredAvatarPath} /> ); } );