TelegramPWA/src/components/left/search/ChatMessageResults.tsx
2024-08-29 15:52:14 +02:00

189 lines
5.5 KiB
TypeScript

import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiChat, ApiMessage } from '../../../api/types';
import { LoadMoreDirection } from '../../../types';
import { selectTabState } from '../../../global/selectors';
import { parseSearchResultKey, type SearchResultKey } from '../../../util/keys/searchResultKey';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { throttle } from '../../../util/schedulers';
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import useAppLayout from '../../../hooks/useAppLayout';
import useOldLang from '../../../hooks/useOldLang';
import NothingFound from '../../common/NothingFound';
import InfiniteScroll from '../../ui/InfiniteScroll';
import ChatMessage from './ChatMessage';
import DateSuggest from './DateSuggest';
import LeftSearchResultTopic from './LeftSearchResultTopic';
export type OwnProps = {
searchQuery?: string;
dateSearchQuery?: string;
onReset: () => void;
onSearchDateSelect: (value: Date) => void;
};
type StateProps = {
currentUserId?: string;
foundIds?: SearchResultKey[];
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
chatsById: Record<string, ApiChat>;
fetchingStatus?: { chats?: boolean; messages?: boolean };
foundTopicIds?: number[];
searchChatId?: string;
};
const runThrottled = throttle((cb) => cb(), 500, true);
const ChatMessageResults: FC<OwnProps & StateProps> = ({
searchQuery,
dateSearchQuery,
foundIds,
globalMessagesByChatId,
chatsById,
fetchingStatus,
foundTopicIds,
searchChatId,
onSearchDateSelect,
onReset,
}) => {
const { searchMessagesGlobal, openThread } = getActions();
const lang = useOldLang();
const { isMobile } = useAppLayout();
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchMessagesGlobal({
type: 'text',
});
});
}
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
}, [searchQuery]);
const handleTopicClick = useCallback(
(id: number) => {
if (!searchChatId) return;
openThread({ chatId: searchChatId, threadId: id, shouldReplaceHistory: true });
if (!isMobile) {
onReset();
}
},
[searchChatId, isMobile, onReset],
);
const foundMessages = useMemo(() => {
if (!foundIds || foundIds.length === 0) {
return MEMO_EMPTY_ARRAY;
}
return foundIds
.map((id) => {
const [chatId, messageId] = parseSearchResultKey(id);
return globalMessagesByChatId?.[chatId]?.byId[messageId];
})
.filter(Boolean)
.sort((a, b) => b.date - a.date);
}, [foundIds, globalMessagesByChatId]);
function renderFoundMessage(message: ApiMessage) {
const text = renderMessageSummary(lang, message);
const chat = chatsById[message.chatId];
if (!text || !chat) {
return undefined;
}
return (
<ChatMessage
chatId={message.chatId}
message={message}
searchQuery={searchQuery}
/>
);
}
const nothingFound = fetchingStatus && !fetchingStatus.chats && !fetchingStatus.messages && !foundMessages.length
&& !foundTopicIds?.length;
return (
<div className="LeftSearch">
<InfiniteScroll
className="search-content custom-scroll chat-list"
items={foundMessages}
onLoadMore={handleLoadMore}
noFastList
>
{dateSearchQuery && (
<div className="chat-selection no-scrollbar">
<DateSuggest
searchDate={dateSearchQuery}
onSelect={onSearchDateSelect}
/>
</div>
)}
{nothingFound && (
<NothingFound
text={lang('ChatList.Search.NoResults')}
description={lang('ChatList.Search.NoResultsDescription')}
/>
)}
{Boolean(foundTopicIds?.length) && (
<div className="pb-2">
<h3 className="section-heading topic-search-heading" dir={lang.isRtl ? 'auto' : undefined}>
{lang('Topics')}
</h3>
{foundTopicIds!.map((id) => {
return (
<LeftSearchResultTopic
chatId={searchChatId!}
topicId={id}
onClick={handleTopicClick}
/>
);
})}
</div>
)}
{Boolean(foundMessages.length) && (
<div className="pb-2">
<h3 className="section-heading topic-search-heading" dir={lang.isRtl ? 'auto' : undefined}>
{lang('SearchMessages')}
</h3>
{foundMessages.map(renderFoundMessage)}
</div>
)}
</InfiniteScroll>
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const { byId: chatsById } = global.chats;
const { currentUserId, messages: { byChatId: globalMessagesByChatId } } = global;
const {
fetchingStatus, resultsByType, foundTopicIds, chatId: searchChatId,
} = selectTabState(global).globalSearch;
const { foundIds } = (resultsByType?.text) || {};
return {
currentUserId,
foundIds,
globalMessagesByChatId,
chatsById,
fetchingStatus,
foundTopicIds,
searchChatId,
};
},
)(ChatMessageResults));