// Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { FunctionComponent, ReactNode } from 'react'; import React, { useCallback } from 'react'; import { escapeRegExp } from 'lodash'; import { MessageBodyHighlight } from './MessageBodyHighlight'; import { ContactName } from '../conversation/ContactName'; import { assertDev } from '../../util/assert'; import type { HydratedBodyRangesType, 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'; export type PropsDataType = { isSelected?: boolean; isSearchingInConversation?: boolean; id: string; conversationId: string; sentAt?: number; snippet: string; body: string; bodyRanges: HydratedBodyRangesType; 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') : ; // This function exists because bodyRanges tells us the character position // where the at-mention starts at according to the full body text. The snippet // we get back is a portion of the text and we don't know where it starts. This // function will find the relevant bodyRanges that apply to the snippet and // then update the proper start position of each body range. function getFilteredBodyRanges( snippet: string, body: string, bodyRanges: HydratedBodyRangesType ): HydratedBodyRangesType { if (!bodyRanges.length) { return []; } // Find where the snippet starts in the full text const stripped = snippet .replace(/<>/g, '') .replace(/<>/g, '') .replace(/^.../, '') .replace(/...$/, ''); const rx = new RegExp(escapeRegExp(stripped)); const match = rx.exec(body); assertDev(Boolean(match), `No match found for "${snippet}" inside "${body}"`); const delta = match ? match.index + snippet.length : 0; // Filters out the @mentions that are present inside the snippet const filteredBodyRanges = bodyRanges.filter(bodyRange => { return bodyRange.start < delta; }); const snippetBodyRanges = []; const MENTIONS_REGEX = /\uFFFC/g; let bodyRangeMatch = MENTIONS_REGEX.exec(snippet); let i = 0; // Find the start position within the snippet so these can later be // encoded and rendered correctly. while (bodyRangeMatch) { const bodyRange = filteredBodyRanges[i]; if (bodyRange) { snippetBodyRanges.push({ ...bodyRange, start: bodyRangeMatch.index, }); } else { assertDev( false, `Body range does not exist? Count: ${i}, Length: ${filteredBodyRanges.length}` ); } bodyRangeMatch = MENTIONS_REGEX.exec(snippet); i += 1; } return snippetBodyRanges; } export const MessageSearchResult: FunctionComponent = 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) { return
; } const isNoteToSelf = from.isMe && to.isMe; let headerName: ReactNode; if (isNoteToSelf) { headerName = i18n('icu:noteToSelf'); } else if (from.isMe) { if (to.type === 'group') { headerName = ( ); } else { headerName = ( ); } } else { // eslint-disable-next-line no-lonely-if if (to.type === 'group') { headerName = ( ); } else { headerName = ( ); } } const snippetBodyRanges = getFilteredBodyRanges(snippet, body, bodyRanges); const messageText = ( ); return ( ); } );