Global Search: Support filters (#5386)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
This commit is contained in:
parent
3b486614df
commit
d0ad84217d
@ -12,6 +12,7 @@ import type {
|
|||||||
ApiInputReplyInfo,
|
ApiInputReplyInfo,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
|
ApiMessageSearchContext,
|
||||||
ApiMessageSearchType,
|
ApiMessageSearchType,
|
||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
ApiOnProgress,
|
ApiOnProgress,
|
||||||
@ -1256,7 +1257,7 @@ export async function searchMessagesInChat({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function searchMessagesGlobal({
|
export async function searchMessagesGlobal({
|
||||||
query, offsetRate = 0, offsetPeer, offsetId, limit, type = 'text', minDate, maxDate,
|
query, offsetRate = 0, offsetPeer, offsetId, limit, type = 'text', minDate, maxDate, context = 'all',
|
||||||
}: {
|
}: {
|
||||||
query: string;
|
query: string;
|
||||||
offsetRate?: number;
|
offsetRate?: number;
|
||||||
@ -1264,6 +1265,7 @@ export async function searchMessagesGlobal({
|
|||||||
offsetId?: number;
|
offsetId?: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
type?: ApiGlobalMessageSearchType;
|
type?: ApiGlobalMessageSearchType;
|
||||||
|
context?: ApiMessageSearchContext;
|
||||||
minDate?: number;
|
minDate?: number;
|
||||||
maxDate?: number;
|
maxDate?: number;
|
||||||
}): Promise<SearchResults | undefined> {
|
}): Promise<SearchResults | undefined> {
|
||||||
@ -1301,7 +1303,9 @@ export async function searchMessagesGlobal({
|
|||||||
offsetRate,
|
offsetRate,
|
||||||
offsetPeer: peer,
|
offsetPeer: peer,
|
||||||
offsetId,
|
offsetId,
|
||||||
broadcastsOnly: type === 'channels' || undefined,
|
broadcastsOnly: type === 'channels' || context === 'channels' || undefined,
|
||||||
|
groupsOnly: context === 'groups' || undefined,
|
||||||
|
usersOnly: context === 'users' || undefined,
|
||||||
limit,
|
limit,
|
||||||
filter,
|
filter,
|
||||||
minDate,
|
minDate,
|
||||||
|
|||||||
@ -1008,6 +1008,7 @@ export type ApiTranscription = {
|
|||||||
|
|
||||||
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto';
|
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto';
|
||||||
export type ApiGlobalMessageSearchType = 'text' | 'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice';
|
export type ApiGlobalMessageSearchType = 'text' | 'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice';
|
||||||
|
export type ApiMessageSearchContext = 'all' | 'users' | 'groups' | 'channels';
|
||||||
|
|
||||||
export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse'
|
export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse'
|
||||||
| 'copyright' | 'geoIrrelevant' | 'fake' | 'illegalDrugs' | 'personalDetails' | 'other';
|
| 'copyright' | 'geoIrrelevant' | 'fake' | 'illegalDrugs' | 'personalDetails' | 'other';
|
||||||
|
|||||||
@ -1406,6 +1406,11 @@
|
|||||||
"StarsSubscribeBotText_one" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** star per month?"
|
"StarsSubscribeBotText_one" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** star per month?"
|
||||||
"StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?"
|
"StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?"
|
||||||
"StarsSubscribeBotButtonMonth" = "Subscribe for {amount} / month";
|
"StarsSubscribeBotButtonMonth" = "Subscribe for {amount} / month";
|
||||||
|
"AllChatsSearchContext" = "All Chats";
|
||||||
|
"PrivateChatsSearchContext" = "Private Chats";
|
||||||
|
"GroupChatsSearchContext" = "Group Chats";
|
||||||
|
"ChannelsSearchContext" = "Channels";
|
||||||
|
"SearchContextCaption" = "From {type}";
|
||||||
"FolderLinkTitleDescription" = "Anyone with this link can add {folder} folder and {chats} selected below.";
|
"FolderLinkTitleDescription" = "Anyone with this link can add {folder} folder and {chats} selected below.";
|
||||||
"FolderLinkTitleDescriptionChats_one" = "the chat";
|
"FolderLinkTitleDescriptionChats_one" = "the chat";
|
||||||
"FolderLinkTitleDescriptionChats_other" = "the {count} chats";
|
"FolderLinkTitleDescriptionChats_other" = "the {count} chats";
|
||||||
|
|||||||
44
src/components/left/search/ChatResults.scss
Normal file
44
src/components/left/search/ChatResults.scss
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
.iconPlaceholder {
|
||||||
|
width: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatResultsContextMenu {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.75rem;
|
||||||
|
top: 2.5rem !important;
|
||||||
|
|
||||||
|
.bubble {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropDownLink {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.Loading {
|
||||||
|
height: 1rem !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
|
||||||
|
.Spinner {
|
||||||
|
--spinner-size: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconContainer {
|
||||||
|
width: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-inline-start: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconContainerSlide {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuOwner {
|
||||||
|
position: relative;
|
||||||
|
min-height: 20rem;
|
||||||
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useMemo, useRef, useState,
|
memo, useCallback, useEffect,
|
||||||
|
useMemo, useRef, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage } from '../../../api/types';
|
import type { ApiMessage, ApiMessageSearchContext } from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import { ALL_FOLDER_ID, GLOBAL_SUGGESTED_CHANNELS_ID } from '../../../config';
|
import { ALL_FOLDER_ID, GLOBAL_SUGGESTED_CHANNELS_ID } from '../../../config';
|
||||||
@ -14,6 +15,7 @@ import {
|
|||||||
isChatChannel,
|
isChatChannel,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { selectSimilarChannelIds, selectTabState } from '../../../global/selectors';
|
import { selectSimilarChannelIds, selectTabState } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { getOrderedIds } from '../../../util/folderManager';
|
import { getOrderedIds } from '../../../util/folderManager';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
import { parseSearchResultKey, type SearchResultKey } from '../../../util/keys/searchResultKey';
|
import { parseSearchResultKey, type SearchResultKey } from '../../../util/keys/searchResultKey';
|
||||||
@ -23,19 +25,29 @@ import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
|||||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||||
|
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useEffectOnce from '../../../hooks/useEffectOnce';
|
import useEffectOnce from '../../../hooks/useEffectOnce';
|
||||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
import NothingFound from '../../common/NothingFound';
|
import NothingFound from '../../common/NothingFound';
|
||||||
import PeerChip from '../../common/PeerChip';
|
import PeerChip from '../../common/PeerChip';
|
||||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
import Link from '../../ui/Link';
|
import Link from '../../ui/Link';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
import Menu from '../../ui/Menu';
|
||||||
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
import Transition from '../../ui/Transition';
|
||||||
import ChatMessage from './ChatMessage';
|
import ChatMessage from './ChatMessage';
|
||||||
import DateSuggest from './DateSuggest';
|
import DateSuggest from './DateSuggest';
|
||||||
import LeftSearchResultChat from './LeftSearchResultChat';
|
import LeftSearchResultChat from './LeftSearchResultChat';
|
||||||
import RecentContacts from './RecentContacts';
|
import RecentContacts from './RecentContacts';
|
||||||
|
|
||||||
|
import './ChatResults.scss';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
searchQuery?: string;
|
searchQuery?: string;
|
||||||
dateSearchQuery?: string;
|
dateSearchQuery?: string;
|
||||||
@ -78,17 +90,22 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
onSearchDateSelect,
|
onSearchDateSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId, loadChannelRecommendations,
|
openChat, addRecentlyFoundChatId, searchMessagesGlobal,
|
||||||
|
setGlobalSearchChatId, loadChannelRecommendations,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const chatSelectionRef = useRef<HTMLDivElement>(null);
|
const chatSelectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const [shouldShowMoreLocal, setShouldShowMoreLocal] = useState<boolean>(false);
|
const [shouldShowMoreLocal, setShouldShowMoreLocal] = useState<boolean>(false);
|
||||||
const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false);
|
const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false);
|
||||||
|
const [searchContext, setSearchContext] = useState<ApiMessageSearchContext>('all');
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
if (isChannelList) loadChannelRecommendations({});
|
if (isChannelList) loadChannelRecommendations({});
|
||||||
@ -99,11 +116,12 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
searchMessagesGlobal({
|
searchMessagesGlobal({
|
||||||
type: isChannelList ? 'channels' : 'text',
|
type: isChannelList ? 'channels' : 'text',
|
||||||
|
context: searchContext,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
|
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
|
||||||
}, [searchQuery]);
|
}, [searchQuery, searchContext]);
|
||||||
|
|
||||||
const handleChatClick = useCallback(
|
const handleChatClick = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
@ -124,6 +142,79 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
setGlobalSearchChatId({ id });
|
setGlobalSearchChatId({ id });
|
||||||
}, [setGlobalSearchChatId]);
|
}, [setGlobalSearchChatId]);
|
||||||
|
|
||||||
|
function getSearchContextCaption(context: ApiMessageSearchContext) {
|
||||||
|
if (context === 'users') return lang('PrivateChatsSearchContext');
|
||||||
|
if (context === 'groups') return lang('GroupChatsSearchContext');
|
||||||
|
if (context === 'channels') return lang('ChannelsSearchContext');
|
||||||
|
return lang('AllChatsSearchContext');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
isContextMenuOpen, contextMenuAnchor, handleContextMenu,
|
||||||
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(ref);
|
||||||
|
|
||||||
|
const getRootElement = useLastCallback(() => ref.current!);
|
||||||
|
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.chatResultsContextMenu .bubble'));
|
||||||
|
const getTriggerElement = useLastCallback(() => ref.current!.querySelector('.menuTrigger'));
|
||||||
|
|
||||||
|
const handleClickContext = useLastCallback((e: React.MouseEvent): void => {
|
||||||
|
handleContextMenu(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemPlaceholderClass = buildClassName('icon', 'iconPlaceholder');
|
||||||
|
|
||||||
|
function renderContextMenu() {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
isOpen={isContextMenuOpen}
|
||||||
|
anchor={contextMenuAnchor}
|
||||||
|
getTriggerElement={getTriggerElement}
|
||||||
|
getRootElement={getRootElement}
|
||||||
|
getMenuElement={getMenuElement}
|
||||||
|
className="chatResultsContextMenu"
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
autoClose
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
icon={searchContext === 'all' ? 'check' : undefined}
|
||||||
|
customIcon={searchContext !== 'all' ? <i className={itemPlaceholderClass} /> : undefined}
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => setSearchContext('all')}
|
||||||
|
>
|
||||||
|
{getSearchContextCaption('all')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={searchContext === 'users' ? 'check' : undefined}
|
||||||
|
customIcon={searchContext !== 'users' ? <i className={itemPlaceholderClass} /> : undefined}
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => setSearchContext('users')}
|
||||||
|
>
|
||||||
|
{getSearchContextCaption('users')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={searchContext === 'groups' ? 'check' : undefined}
|
||||||
|
customIcon={searchContext !== 'groups' ? <i className={itemPlaceholderClass} /> : undefined}
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => setSearchContext('groups')}
|
||||||
|
>
|
||||||
|
{getSearchContextCaption('groups')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={searchContext === 'channels' ? 'check' : undefined}
|
||||||
|
customIcon={searchContext !== 'channels' ? <i className={itemPlaceholderClass} /> : undefined}
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => setSearchContext('channels')}
|
||||||
|
>
|
||||||
|
{getSearchContextCaption('channels')}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const localResults = useMemo(() => {
|
const localResults = useMemo(() => {
|
||||||
if (!isChannelList && (!searchQuery || (searchQuery.startsWith('@') && searchQuery.length < 2))) {
|
if (!isChannelList && (!searchQuery || (searchQuery.startsWith('@') && searchQuery.length < 2))) {
|
||||||
return MEMO_EMPTY_ARRAY;
|
return MEMO_EMPTY_ARRAY;
|
||||||
@ -139,7 +230,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
const chat = chatsById[id];
|
const chat = chatsById[id];
|
||||||
return chat && isChatChannel(chat);
|
return chat && isChatChannel(chat);
|
||||||
});
|
});
|
||||||
const localChatIds = filterChatsByName(lang, filteredChatIds, chatsById, searchQuery, currentUserId);
|
const localChatIds = filterChatsByName(oldLang, filteredChatIds, chatsById, searchQuery, currentUserId);
|
||||||
|
|
||||||
if (isChannelList) return localChatIds;
|
if (isChannelList) return localChatIds;
|
||||||
|
|
||||||
@ -149,7 +240,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const localContactIds = filterUsersByName(
|
const localContactIds = filterUsersByName(
|
||||||
contactIdsWithMe, usersById, searchQuery, currentUserId, lang('SavedMessages'),
|
contactIdsWithMe, usersById, searchQuery, currentUserId, oldLang('SavedMessages'),
|
||||||
);
|
);
|
||||||
|
|
||||||
const localPeerIds = [
|
const localPeerIds = [
|
||||||
@ -161,7 +252,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
...sortChatIds(localPeerIds, undefined, currentUserId ? [currentUserId] : undefined),
|
...sortChatIds(localPeerIds, undefined, currentUserId ? [currentUserId] : undefined),
|
||||||
...sortChatIds(accountPeerIds || []),
|
...sortChatIds(accountPeerIds || []),
|
||||||
]);
|
]);
|
||||||
}, [searchQuery, lang, currentUserId, contactIds, accountPeerIds, isChannelList]);
|
}, [searchQuery, oldLang, currentUserId, contactIds, accountPeerIds, isChannelList]);
|
||||||
|
|
||||||
useHorizontalScroll(chatSelectionRef, !localResults.length || isChannelList, true);
|
useHorizontalScroll(chatSelectionRef, !localResults.length || isChannelList, true);
|
||||||
|
|
||||||
@ -202,6 +293,17 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}, [searchQuery, searchDate, foundIds, isChannelList, globalMessagesByChatId]);
|
}, [searchQuery, searchDate, foundIds, isChannelList, globalMessagesByChatId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!searchQuery) return;
|
||||||
|
searchMessagesGlobal({
|
||||||
|
type: isChannelList ? 'channels' : 'text',
|
||||||
|
context: searchContext,
|
||||||
|
shouldResetResultsByType: true,
|
||||||
|
shouldCheckFetchingMessagesStatus: true,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||||
|
}, [searchContext]);
|
||||||
|
|
||||||
const handleClickShowMoreLocal = useCallback(() => {
|
const handleClickShowMoreLocal = useCallback(() => {
|
||||||
setShouldShowMoreLocal(!shouldShowMoreLocal);
|
setShouldShowMoreLocal(!shouldShowMoreLocal);
|
||||||
}, [shouldShowMoreLocal]);
|
}, [shouldShowMoreLocal]);
|
||||||
@ -213,7 +315,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
function renderFoundMessage(message: ApiMessage) {
|
function renderFoundMessage(message: ApiMessage) {
|
||||||
const chatsById = getGlobal().chats.byId;
|
const chatsById = getGlobal().chats.byId;
|
||||||
|
|
||||||
const text = renderMessageSummary(lang, message);
|
const text = renderMessageSummary(oldLang, message);
|
||||||
const chat = chatsById[message.chatId];
|
const chat = chatsById[message.chatId];
|
||||||
|
|
||||||
if (!text || !chat) {
|
if (!text || !chat) {
|
||||||
@ -229,17 +331,22 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nothingFound = fetchingStatus && !fetchingStatus.chats && !fetchingStatus.messages
|
const actualFoundIds = foundMessages;
|
||||||
&& !localResults.length && !globalResults.length && !foundMessages.length;
|
|
||||||
|
const nothingFound = searchContext === 'all' && fetchingStatus && !fetchingStatus.chats && !fetchingStatus.messages
|
||||||
|
&& !localResults.length && !globalResults.length && !actualFoundIds.length;
|
||||||
|
const isMessagesFetching = fetchingStatus?.messages;
|
||||||
|
|
||||||
if (!searchQuery && !searchDate && !isChannelList) {
|
if (!searchQuery && !searchDate && !isChannelList) {
|
||||||
return <RecentContacts onReset={onReset} />;
|
return <RecentContacts onReset={onReset} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldRenderMessagesSection = searchContext === 'all' ? Boolean(actualFoundIds.length) : true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className="LeftSearch--content custom-scroll"
|
className="LeftSearch--content custom-scroll"
|
||||||
items={foundMessages}
|
items={actualFoundIds}
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
// To prevent scroll jumps caused by delayed local results rendering
|
// To prevent scroll jumps caused by delayed local results rendering
|
||||||
noScrollRestoreOnTop
|
noScrollRestoreOnTop
|
||||||
@ -255,14 +362,14 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{nothingFound && (
|
{nothingFound && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
text={lang('ChatList.Search.NoResults')}
|
text={oldLang('ChatList.Search.NoResults')}
|
||||||
description={lang('ChatList.Search.NoResultsDescription')}
|
description={oldLang('ChatList.Search.NoResultsDescription')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{Boolean(localResults.length) && !isChannelList && (
|
{Boolean(localResults.length) && !isChannelList && (
|
||||||
<div
|
<div
|
||||||
className="chat-selection no-scrollbar"
|
className="chat-selection no-scrollbar"
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={oldLang.isRtl ? 'rtl' : undefined}
|
||||||
ref={chatSelectionRef}
|
ref={chatSelectionRef}
|
||||||
>
|
>
|
||||||
{localResults.map((id) => (
|
{localResults.map((id) => (
|
||||||
@ -277,13 +384,13 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{Boolean(localResults.length) && (
|
{Boolean(localResults.length) && (
|
||||||
<div className="search-section">
|
<div className="search-section">
|
||||||
<h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>
|
<h3 className="section-heading" dir={oldLang.isRtl ? 'auto' : undefined}>
|
||||||
{localResults.length > LESS_LIST_ITEMS_AMOUNT && (
|
{localResults.length > LESS_LIST_ITEMS_AMOUNT && (
|
||||||
<Link className="Link" onClick={handleClickShowMoreLocal}>
|
<Link className="Link" onClick={handleClickShowMoreLocal}>
|
||||||
{lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
{oldLang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{lang(isChannelList ? 'SearchMyChannels' : 'DialogList.SearchSectionDialogs')}
|
{oldLang(isChannelList ? 'SearchMyChannels' : 'DialogList.SearchSectionDialogs')}
|
||||||
</h3>
|
</h3>
|
||||||
{localResults.map((id, index) => {
|
{localResults.map((id, index) => {
|
||||||
if (!shouldShowMoreLocal && index >= LESS_LIST_ITEMS_AMOUNT) {
|
if (!shouldShowMoreLocal && index >= LESS_LIST_ITEMS_AMOUNT) {
|
||||||
@ -301,13 +408,13 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{Boolean(globalResults.length) && (
|
{Boolean(globalResults.length) && (
|
||||||
<div className="search-section">
|
<div className="search-section">
|
||||||
<h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>
|
<h3 className="section-heading" dir={oldLang.isRtl ? 'auto' : undefined}>
|
||||||
{globalResults.length > LESS_LIST_ITEMS_AMOUNT && (
|
{globalResults.length > LESS_LIST_ITEMS_AMOUNT && (
|
||||||
<Link className="Link" onClick={handleClickShowMoreGlobal}>
|
<Link className="Link" onClick={handleClickShowMoreGlobal}>
|
||||||
{lang(shouldShowMoreGlobal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
{oldLang(shouldShowMoreGlobal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{lang('DialogList.SearchSectionGlobal')}
|
{oldLang('DialogList.SearchSectionGlobal')}
|
||||||
</h3>
|
</h3>
|
||||||
{globalResults.map((id, index) => {
|
{globalResults.map((id, index) => {
|
||||||
if (!shouldShowMoreGlobal && index >= LESS_LIST_ITEMS_AMOUNT) {
|
if (!shouldShowMoreGlobal && index >= LESS_LIST_ITEMS_AMOUNT) {
|
||||||
@ -326,8 +433,8 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{Boolean(suggestedChannelIds?.length) && !searchQuery && (
|
{Boolean(suggestedChannelIds?.length) && !searchQuery && (
|
||||||
<div className="search-section">
|
<div className="search-section">
|
||||||
<h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>
|
<h3 className="section-heading" dir={oldLang.isRtl ? 'auto' : undefined}>
|
||||||
{lang('SearchRecommendedChannels')}
|
{oldLang('SearchRecommendedChannels')}
|
||||||
</h3>
|
</h3>
|
||||||
{suggestedChannelIds.map((id) => {
|
{suggestedChannelIds.map((id) => {
|
||||||
return (
|
return (
|
||||||
@ -340,12 +447,35 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{Boolean(foundMessages.length) && (
|
<div className="menuOwner" ref={ref}>
|
||||||
<div className="search-section">
|
{renderContextMenu()}
|
||||||
<h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>{lang('SearchMessages')}</h3>
|
{shouldRenderMessagesSection && (
|
||||||
{foundMessages.map(renderFoundMessage)}
|
<div className="search-section">
|
||||||
</div>
|
<h3 className="section-heading" dir={oldLang.isRtl ? 'auto' : undefined}>
|
||||||
)}
|
<Link className="Link menuTrigger dropDownLink" onClick={handleClickContext}>
|
||||||
|
{lang('SearchContextCaption', {
|
||||||
|
type: getSearchContextCaption(searchContext),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
})}
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
name="fade"
|
||||||
|
shouldCleanup
|
||||||
|
activeKey={Number(isMessagesFetching)}
|
||||||
|
className="iconContainer"
|
||||||
|
slideClassName="iconContainerSlide"
|
||||||
|
>
|
||||||
|
{isMessagesFetching && (<Loading />)}
|
||||||
|
{!isMessagesFetching && <Icon name="down" />}
|
||||||
|
</Transition>
|
||||||
|
</Link>
|
||||||
|
{oldLang('SearchMessages')}
|
||||||
|
</h3>
|
||||||
|
{actualFoundIds.map(renderFoundMessage)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiPeer, ApiTopic,
|
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiMessageSearchContext, ApiPeer, ApiTopic,
|
||||||
ApiUserStatus,
|
ApiUserStatus,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
||||||
@ -86,13 +86,22 @@ addActionHandler('setGlobalSearchDate', (global, actions, payload): ActionReturn
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('searchMessagesGlobal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('searchMessagesGlobal', (global, actions, payload): ActionReturnType => {
|
||||||
const { type, tabId = getCurrentTabId() } = payload;
|
const {
|
||||||
|
type, context, shouldResetResultsByType, shouldCheckFetchingMessagesStatus, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
if (shouldCheckFetchingMessagesStatus) {
|
||||||
|
global = updateGlobalSearchFetchingStatus(global, { messages: true }, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
global = getGlobal();
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query, resultsByType, chatId,
|
query, resultsByType, chatId,
|
||||||
} = selectTabState(global, tabId).globalSearch;
|
} = selectTabState(global, tabId).globalSearch;
|
||||||
const {
|
const {
|
||||||
totalCount, foundIds, nextOffsetId, nextOffsetPeerId, nextOffsetRate,
|
totalCount, foundIds, nextOffsetId, nextOffsetPeerId, nextOffsetRate,
|
||||||
} = resultsByType?.[type] || {};
|
} = (!shouldResetResultsByType && resultsByType?.[type]) || {};
|
||||||
|
|
||||||
// Stop loading if we have all the messages or server returned 0
|
// Stop loading if we have all the messages or server returned 0
|
||||||
if (totalCount !== undefined && (!totalCount || (foundIds && foundIds.length >= totalCount))) {
|
if (totalCount !== undefined && (!totalCount || (foundIds && foundIds.length >= totalCount))) {
|
||||||
@ -105,6 +114,8 @@ addActionHandler('searchMessagesGlobal', (global, actions, payload): ActionRetur
|
|||||||
searchMessagesGlobal(global, {
|
searchMessagesGlobal(global, {
|
||||||
query,
|
query,
|
||||||
type,
|
type,
|
||||||
|
context,
|
||||||
|
shouldResetResultsByType,
|
||||||
offsetRate: nextOffsetRate,
|
offsetRate: nextOffsetRate,
|
||||||
offsetId: nextOffsetId,
|
offsetId: nextOffsetId,
|
||||||
offsetPeer,
|
offsetPeer,
|
||||||
@ -145,6 +156,7 @@ addActionHandler('searchPopularBotApps', async (global, actions, payload): Promi
|
|||||||
async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||||
query?: string;
|
query?: string;
|
||||||
type: ApiGlobalMessageSearchType;
|
type: ApiGlobalMessageSearchType;
|
||||||
|
context?: ApiMessageSearchContext;
|
||||||
offsetRate?: number;
|
offsetRate?: number;
|
||||||
offsetId?: number;
|
offsetId?: number;
|
||||||
offsetPeer?: ApiPeer;
|
offsetPeer?: ApiPeer;
|
||||||
@ -152,9 +164,11 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
maxDate?: number;
|
maxDate?: number;
|
||||||
minDate?: number;
|
minDate?: number;
|
||||||
tabId: TabArgs<T>[0];
|
tabId: TabArgs<T>[0];
|
||||||
|
shouldResetResultsByType?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
query = '', type, offsetRate, offsetId, offsetPeer, peer, maxDate, minDate, tabId = getCurrentTabId(),
|
query = '', type, context, offsetRate, offsetId, offsetPeer,
|
||||||
|
peer, maxDate, minDate, shouldResetResultsByType, tabId = getCurrentTabId(),
|
||||||
} = params;
|
} = params;
|
||||||
let result: {
|
let result: {
|
||||||
messages: ApiMessage[];
|
messages: ApiMessage[];
|
||||||
@ -211,6 +225,7 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
offsetPeer,
|
offsetPeer,
|
||||||
limit: GLOBAL_SEARCH_SLICE,
|
limit: GLOBAL_SEARCH_SLICE,
|
||||||
type,
|
type,
|
||||||
|
context,
|
||||||
maxDate,
|
maxDate,
|
||||||
minDate,
|
minDate,
|
||||||
});
|
});
|
||||||
@ -225,6 +240,15 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
|
if (shouldResetResultsByType) {
|
||||||
|
global = updateGlobalSearch(global, {
|
||||||
|
resultsByType: {
|
||||||
|
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
||||||
|
[type]: undefined,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
}
|
||||||
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
|
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
|
||||||
if (!result || (query !== '' && query !== currentSearchQuery)) {
|
if (!result || (query !== '' && query !== currentSearchQuery)) {
|
||||||
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
||||||
|
|||||||
@ -57,6 +57,7 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
|||||||
resultsByType: {
|
resultsByType: {
|
||||||
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
||||||
[type]: {
|
[type]: {
|
||||||
|
foundIds: foundIdsForType,
|
||||||
totalCount,
|
totalCount,
|
||||||
nextOffsetId,
|
nextOffsetId,
|
||||||
nextOffsetRate,
|
nextOffsetRate,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import type {
|
|||||||
ApiLimitTypeWithModal,
|
ApiLimitTypeWithModal,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
|
ApiMessageSearchContext,
|
||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
ApiNotification,
|
ApiNotification,
|
||||||
ApiPaymentStatus,
|
ApiPaymentStatus,
|
||||||
@ -386,6 +387,9 @@ export interface ActionPayloads {
|
|||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
searchMessagesGlobal: {
|
searchMessagesGlobal: {
|
||||||
type: ApiGlobalMessageSearchType;
|
type: ApiGlobalMessageSearchType;
|
||||||
|
context?: ApiMessageSearchContext;
|
||||||
|
shouldResetResultsByType?: boolean;
|
||||||
|
shouldCheckFetchingMessagesStatus?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
searchPopularBotApps: WithTabId | undefined;
|
searchPopularBotApps: WithTabId | undefined;
|
||||||
addRecentlyFoundChatId: {
|
addRecentlyFoundChatId: {
|
||||||
|
|||||||
7
src/types/language.d.ts
vendored
7
src/types/language.d.ts
vendored
@ -1168,6 +1168,10 @@ export interface LangPair {
|
|||||||
'PrivacyGiftsInfo': undefined;
|
'PrivacyGiftsInfo': undefined;
|
||||||
'PrivacyValueBots': undefined;
|
'PrivacyValueBots': undefined;
|
||||||
'CustomShareGiftsInfo': undefined;
|
'CustomShareGiftsInfo': undefined;
|
||||||
|
'AllChatsSearchContext': undefined;
|
||||||
|
'PrivateChatsSearchContext': undefined;
|
||||||
|
'GroupChatsSearchContext': undefined;
|
||||||
|
'ChannelsSearchContext': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||||
@ -1574,6 +1578,9 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
|||||||
'StarsSubscribeBotButtonMonth': {
|
'StarsSubscribeBotButtonMonth': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
|
'SearchContextCaption': {
|
||||||
|
'type': V;
|
||||||
|
};
|
||||||
'FolderLinkTitleDescription': {
|
'FolderLinkTitleDescription': {
|
||||||
'folder': V;
|
'folder': V;
|
||||||
'chats': V;
|
'chats': V;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user