205 lines
5.2 KiB
TypeScript
205 lines
5.2 KiB
TypeScript
// 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}
|
|
/>
|
|
);
|
|
}
|
|
);
|