import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback, useMemo, useRef, useState, } from '../../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../../global'; import type { ApiChat, ApiMessage } from '../../../api/types'; import { LoadMoreDirection } from '../../../types'; import { selectTabState } from '../../../global/selectors'; import { unique } from '../../../util/iteratees'; import { sortChatIds, filterUsersByName, } from '../../../global/helpers'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { throttle } from '../../../util/schedulers'; import { renderMessageSummary } from '../../common/helpers/renderMessageText'; import useLang from '../../../hooks/useLang'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useAppLayout from '../../../hooks/useAppLayout'; import InfiniteScroll from '../../ui/InfiniteScroll'; import LeftSearchResultChat from './LeftSearchResultChat'; import RecentContacts from './RecentContacts'; import ChatMessage from './ChatMessage'; import DateSuggest from './DateSuggest'; import Link from '../../ui/Link'; import NothingFound from '../../common/NothingFound'; import PickerSelectedItem from '../../common/PickerSelectedItem'; export type OwnProps = { searchQuery?: string; dateSearchQuery?: string; searchDate?: number; onReset: () => void; onSearchDateSelect: (value: Date) => void; }; type StateProps = { currentUserId?: string; localContactIds?: string[]; localChatIds?: string[]; localUserIds?: string[]; globalChatIds?: string[]; globalUserIds?: string[]; foundIds?: string[]; globalMessagesByChatId?: Record }>; chatsById: Record; fetchingStatus?: { chats?: boolean; messages?: boolean }; lastSyncTime?: number; }; const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4; const LESS_LIST_ITEMS_AMOUNT = 5; const runThrottled = throttle((cb) => cb(), 500, false); const ChatResults: FC = ({ searchQuery, searchDate, dateSearchQuery, currentUserId, localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds, foundIds, globalMessagesByChatId, chatsById, fetchingStatus, lastSyncTime, onReset, onSearchDateSelect, }) => { const { openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId, } = getActions(); // eslint-disable-next-line no-null/no-null const chatSelectionRef = useRef(null); useHorizontalScroll(chatSelectionRef.current, undefined, true); const lang = useLang(); const { isMobile } = useAppLayout(); const [shouldShowMoreLocal, setShouldShowMoreLocal] = useState(false); const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState(false); const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { if (lastSyncTime && direction === LoadMoreDirection.Backwards) { runThrottled(() => { searchMessagesGlobal({ type: 'text', }); }); } // eslint-disable-next-line react-hooks/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading }, [lastSyncTime, searchMessagesGlobal, searchQuery]); const handleChatClick = useCallback( (id: string) => { openChat({ id, shouldReplaceHistory: true }); if (id !== currentUserId) { addRecentlyFoundChatId({ id }); } if (!isMobile) { onReset(); } }, [openChat, currentUserId, isMobile, addRecentlyFoundChatId, onReset], ); const handlePickerItemClick = useCallback((id: string) => { setGlobalSearchChatId({ id }); }, [setGlobalSearchChatId]); const localResults = useMemo(() => { if (!searchQuery || (searchQuery.startsWith('@') && searchQuery.length < 2)) { return MEMO_EMPTY_ARRAY; } const contactIdsWithMe = [ ...(currentUserId ? [currentUserId] : []), ...(localContactIds || []), ]; // No need for expensive global updates on users, so we avoid them const usersById = getGlobal().users.byId; const foundContactIds = filterUsersByName( contactIdsWithMe, usersById, searchQuery, currentUserId, lang('SavedMessages'), ); return [ ...sortChatIds(unique([ ...(foundContactIds || []), ...(localChatIds || []), ...(localUserIds || []), ]), chatsById, undefined, currentUserId ? [currentUserId] : undefined), ]; }, [searchQuery, currentUserId, localContactIds, lang, localChatIds, localUserIds, chatsById]); const globalResults = useMemo(() => { if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) { return MEMO_EMPTY_ARRAY; } return sortChatIds( unique([...globalChatIds, ...globalUserIds]), chatsById, true, ); }, [chatsById, globalChatIds, globalUserIds, searchQuery]); const foundMessages = useMemo(() => { if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) { return MEMO_EMPTY_ARRAY; } return foundIds .map((id) => { const [chatId, messageId] = id.split('_'); return globalMessagesByChatId?.[chatId]?.byId[Number(messageId)]; }) .filter(Boolean) .sort((a, b) => b.date - a.date); }, [foundIds, globalMessagesByChatId, searchQuery, searchDate]); const handleClickShowMoreLocal = useCallback(() => { setShouldShowMoreLocal(!shouldShowMoreLocal); }, [shouldShowMoreLocal]); const handleClickShowMoreGlobal = useCallback(() => { setShouldShowMoreGlobal(!shouldShowMoreGlobal); }, [shouldShowMoreGlobal]); function renderFoundMessage(message: ApiMessage) { const text = renderMessageSummary(lang, message); const chat = chatsById[message.chatId]; if (!text || !chat) { return undefined; } return ( ); } const nothingFound = fetchingStatus && !fetchingStatus.chats && !fetchingStatus.messages && !localResults.length && !globalResults.length && !foundMessages.length; if (!searchQuery && !searchDate) { return ; } return ( {dateSearchQuery && (
)} {nothingFound && ( )} {Boolean(localResults.length) && (
{localResults.map((id) => ( ))}
)} {Boolean(localResults.length) && (

{localResults.length > LESS_LIST_ITEMS_AMOUNT && ( {lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')} )} {lang('DialogList.SearchSectionDialogs')}

{localResults.map((id, index) => { if (!shouldShowMoreLocal && index >= LESS_LIST_ITEMS_AMOUNT) { return undefined; } return ( ); })}
)} {Boolean(globalResults.length) && (

{globalResults.length > LESS_LIST_ITEMS_AMOUNT && ( {lang(shouldShowMoreGlobal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')} )} {lang('DialogList.SearchSectionGlobal')}

{globalResults.map((id, index) => { if (!shouldShowMoreGlobal && index >= LESS_LIST_ITEMS_AMOUNT) { return undefined; } return ( ); })}
)} {Boolean(foundMessages.length) && (

{lang('SearchMessages')}

{foundMessages.map(renderFoundMessage)}
)}
); }; export default memo(withGlobal( (global): StateProps => { const { byId: chatsById } = global.chats; const { userIds: localContactIds } = global.contactList || {}; if (!localContactIds) { return { chatsById, }; } const { currentUserId, messages, lastSyncTime, } = global; const { fetchingStatus, globalResults, localResults, resultsByType, } = selectTabState(global).globalSearch; const { chatIds: globalChatIds, userIds: globalUserIds } = globalResults || {}; const { chatIds: localChatIds, userIds: localUserIds } = localResults || {}; const { byChatId: globalMessagesByChatId } = messages; const foundIds = resultsByType?.text?.foundIds; return { currentUserId, localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds, foundIds, globalMessagesByChatId, chatsById, fetchingStatus, lastSyncTime, }; }, )(ChatResults));