Search: Add sender filter (#6578)
This commit is contained in:
parent
3eb69cefb1
commit
224670674a
@ -1476,6 +1476,7 @@ export async function searchMessagesInChat({
|
|||||||
offsetId,
|
offsetId,
|
||||||
addOffset,
|
addOffset,
|
||||||
limit,
|
limit,
|
||||||
|
fromPeer,
|
||||||
}: {
|
}: {
|
||||||
peer: ApiPeer;
|
peer: ApiPeer;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
@ -1488,6 +1489,7 @@ export async function searchMessagesInChat({
|
|||||||
limit: number;
|
limit: number;
|
||||||
minDate?: number;
|
minDate?: number;
|
||||||
maxDate?: number;
|
maxDate?: number;
|
||||||
|
fromPeer?: ApiPeer;
|
||||||
}): Promise<SearchResults | undefined> {
|
}): Promise<SearchResults | undefined> {
|
||||||
let filter;
|
let filter;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -1519,6 +1521,7 @@ export async function searchMessagesInChat({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inputPeer = buildInputPeer(peer.id, peer.accessHash);
|
const inputPeer = buildInputPeer(peer.id, peer.accessHash);
|
||||||
|
const inputFromPeer = fromPeer ? buildInputPeer(fromPeer.id, fromPeer.accessHash) : undefined;
|
||||||
|
|
||||||
const result = await invokeRequest(new GramJs.messages.Search({
|
const result = await invokeRequest(new GramJs.messages.Search({
|
||||||
peer: isSavedDialog ? new GramJs.InputPeerSelf() : inputPeer,
|
peer: isSavedDialog ? new GramJs.InputPeerSelf() : inputPeer,
|
||||||
@ -1527,6 +1530,7 @@ export async function searchMessagesInChat({
|
|||||||
topMsgId: threadId !== MAIN_THREAD_ID && !isSavedDialog ? Number(threadId) : undefined,
|
topMsgId: threadId !== MAIN_THREAD_ID && !isSavedDialog ? Number(threadId) : undefined,
|
||||||
filter,
|
filter,
|
||||||
q: query,
|
q: query,
|
||||||
|
fromId: inputFromPeer,
|
||||||
minDate: minDate ?? DEFAULT_PRIMITIVES.INT,
|
minDate: minDate ?? DEFAULT_PRIMITIVES.INT,
|
||||||
maxDate: maxDate ?? DEFAULT_PRIMITIVES.INT,
|
maxDate: maxDate ?? DEFAULT_PRIMITIVES.INT,
|
||||||
maxId: DEFAULT_PRIMITIVES.INT,
|
maxId: DEFAULT_PRIMITIVES.INT,
|
||||||
|
|||||||
@ -1711,6 +1711,7 @@
|
|||||||
"GroupChatsSearchContext" = "Group Chats";
|
"GroupChatsSearchContext" = "Group Chats";
|
||||||
"ChannelsSearchContext" = "Channels";
|
"ChannelsSearchContext" = "Channels";
|
||||||
"SearchContextCaption" = "From {type}";
|
"SearchContextCaption" = "From {type}";
|
||||||
|
"SearchFilterFrom" = "From:";
|
||||||
"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";
|
||||||
|
|||||||
@ -19,10 +19,12 @@ import {
|
|||||||
getMainUsername,
|
getMainUsername,
|
||||||
isAnonymousForwardsChat,
|
isAnonymousForwardsChat,
|
||||||
isAnonymousOwnMessage,
|
isAnonymousOwnMessage,
|
||||||
|
isChatChannel,
|
||||||
isSystemBot,
|
isSystemBot,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { isApiPeerUser } from '../../../global/helpers/peers';
|
import { isApiPeerUser } from '../../../global/helpers/peers';
|
||||||
import {
|
import {
|
||||||
|
selectChat,
|
||||||
selectForwardedSender,
|
selectForwardedSender,
|
||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectSender,
|
selectSender,
|
||||||
@ -58,6 +60,7 @@ type StateProps = {
|
|||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
isRepliesChat?: boolean;
|
isRepliesChat?: boolean;
|
||||||
isAnonymousForwards?: boolean;
|
isAnonymousForwards?: boolean;
|
||||||
|
isChannel?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
||||||
@ -72,9 +75,10 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isRepliesChat,
|
isRepliesChat,
|
||||||
isAnonymousForwards,
|
isAnonymousForwards,
|
||||||
|
isChannel,
|
||||||
canPost,
|
canPost,
|
||||||
}) => {
|
}) => {
|
||||||
const { openChat, updateInsertingPeerIdMention } = getActions();
|
const { openChat, updateInsertingPeerIdMention, openMiddleSearch } = getActions();
|
||||||
|
|
||||||
const { forwardInfo } = message;
|
const { forwardInfo } = message;
|
||||||
|
|
||||||
@ -115,6 +119,14 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSearchMessages = useLastCallback(() => {
|
||||||
|
if (!avatarPeer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openMiddleSearch({ fromPeerId: avatarPeer.id });
|
||||||
|
});
|
||||||
|
|
||||||
const handleAvatarClick = useLastCallback(() => {
|
const handleAvatarClick = useLastCallback(() => {
|
||||||
handleOpenChat();
|
handleOpenChat();
|
||||||
});
|
});
|
||||||
@ -142,7 +154,8 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
|
|
||||||
const canMention = canPost && avatarPeer && (isAvatarPeerUser || Boolean(getMainUsername(avatarPeer)));
|
const canMention = canPost && avatarPeer && (isAvatarPeerUser || Boolean(getMainUsername(avatarPeer)));
|
||||||
const shouldRenderContextMenu = Boolean(contextMenuAnchor) && (isAvatarPeerUser || canMention);
|
const canSearch = !isChannel;
|
||||||
|
const shouldRenderContextMenu = Boolean(contextMenuAnchor) && (isAvatarPeerUser || canMention || canSearch);
|
||||||
|
|
||||||
function renderContextMenu() {
|
function renderContextMenu() {
|
||||||
return (
|
return (
|
||||||
@ -176,6 +189,14 @@ const SenderGroupContainer: FC<OwnProps & StateProps> = ({
|
|||||||
{lang('ContextMenuItemMention')}
|
{lang('ContextMenuItemMention')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{canSearch && (
|
||||||
|
<MenuItem
|
||||||
|
icon="search"
|
||||||
|
onClick={handleSearchMessages}
|
||||||
|
>
|
||||||
|
{lang('Search')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
@ -221,6 +242,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
} = ownProps;
|
} = ownProps;
|
||||||
const { chatId } = message;
|
const { chatId } = message;
|
||||||
|
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
const isSystemBotChat = isSystemBot(chatId);
|
const isSystemBotChat = isSystemBot(chatId);
|
||||||
const isAnonymousForwards = isAnonymousForwardsChat(chatId);
|
const isAnonymousForwards = isAnonymousForwardsChat(chatId);
|
||||||
@ -237,6 +259,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isRepliesChat: isSystemBotChat,
|
isRepliesChat: isSystemBotChat,
|
||||||
isAnonymousForwards,
|
isAnonymousForwards,
|
||||||
|
isChannel: chat && isChatChannel(chat),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(SenderGroupContainer));
|
)(SenderGroupContainer));
|
||||||
|
|||||||
@ -202,6 +202,21 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fromTag {
|
||||||
|
|
||||||
|
--color-chat-hover: var(--color-borders);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
margin-inline-start: 0.5rem;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.searchTypes {
|
.searchTypes {
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -235,6 +250,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
||||||
@ -306,3 +327,9 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.memberItem {
|
||||||
|
:global(.ListItem-button) {
|
||||||
|
padding: 0.375rem 0.75rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import type {
|
|||||||
import { ANONYMOUS_USER_ID } from '../../../config';
|
import { ANONYMOUS_USER_ID } from '../../../config';
|
||||||
import { requestMeasure, requestMutation, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestMutation, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
getIsSavedDialog, getReactionKey, isSameReaction, isSystemBot,
|
getIsSavedDialog, getReactionKey, isChatGroup, isSameReaction, isSystemBot,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
@ -27,6 +27,7 @@ import {
|
|||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectMonoforumChannel,
|
selectMonoforumChannel,
|
||||||
|
selectPeer,
|
||||||
selectSender,
|
selectSender,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
@ -50,11 +51,13 @@ import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation'
|
|||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
import usePeerSearch, { prepareChatMemberSearch } from '../../../hooks/usePeerSearch';
|
||||||
|
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
import PeerChip from '../../common/PeerChip';
|
import PeerChip from '../../common/PeerChip';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
import SearchInput from '../../ui/SearchInput';
|
import SearchInput from '../../ui/SearchInput';
|
||||||
import SavedTagButton from '../message/reactions/SavedTagButton';
|
import SavedTagButton from '../message/reactions/SavedTagButton';
|
||||||
import MiddleSearchResult from './MiddleSearchResult';
|
import MiddleSearchResult from './MiddleSearchResult';
|
||||||
@ -82,6 +85,8 @@ type StateProps = {
|
|||||||
isHashtagQuery?: boolean;
|
isHashtagQuery?: boolean;
|
||||||
searchType?: MiddleSearchType;
|
searchType?: MiddleSearchType;
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
|
fromPeerId?: string;
|
||||||
|
isGroupChat?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHANNELS_PEER: CustomPeer = {
|
const CHANNELS_PEER: CustomPeer = {
|
||||||
@ -92,6 +97,8 @@ const CHANNELS_PEER: CustomPeer = {
|
|||||||
const FOCUSED_SEARCH_TRIGGER_OFFSET = 5;
|
const FOCUSED_SEARCH_TRIGGER_OFFSET = 5;
|
||||||
const HIDE_TIMEOUT = 200;
|
const HIDE_TIMEOUT = 200;
|
||||||
const RESULT_ITEM_CLASS_NAME = 'MiddleSearchResult';
|
const RESULT_ITEM_CLASS_NAME = 'MiddleSearchResult';
|
||||||
|
const FROM_FILTER_PREFIX = 'from:';
|
||||||
|
const INLINE_MEMBER_COUNT = 5;
|
||||||
|
|
||||||
const runDebouncedForSearch = debounce((cb) => cb(), 200, false);
|
const runDebouncedForSearch = debounce((cb) => cb(), 200, false);
|
||||||
|
|
||||||
@ -113,6 +120,8 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
isHashtagQuery,
|
isHashtagQuery,
|
||||||
searchType = 'chat',
|
searchType = 'chat',
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
fromPeerId,
|
||||||
|
isGroupChat,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
updateMiddleSearch,
|
updateMiddleSearch,
|
||||||
@ -146,6 +155,28 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
const [isFocused, markFocused, markBlurred] = useFlag();
|
const [isFocused, markFocused, markBlurred] = useFlag();
|
||||||
const [isViewAsList, setIsViewAsList] = useState(true);
|
const [isViewAsList, setIsViewAsList] = useState(true);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isFromFilterMode, setIsFromFilterMode] = useState(Boolean(fromPeerId));
|
||||||
|
const [memberSearchQuery, setMemberSearchQuery] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fromPeerId) {
|
||||||
|
setIsFromFilterMode(true);
|
||||||
|
focusInput();
|
||||||
|
}
|
||||||
|
}, [fromPeerId]);
|
||||||
|
|
||||||
|
const memberSearchFn = useMemo(
|
||||||
|
() => (chat && isGroupChat ? prepareChatMemberSearch(chat) : undefined),
|
||||||
|
[chat, isGroupChat],
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasMemberDropdown = isFromFilterMode && !fromPeerId;
|
||||||
|
|
||||||
|
const { result: memberSearchResults, isLoading: isMemberSearchLoading } = usePeerSearch({
|
||||||
|
query: hasMemberDropdown ? (memberSearchQuery || ' ') : query,
|
||||||
|
queryFn: memberSearchFn,
|
||||||
|
isDisabled: !isGroupChat || Boolean(fromPeerId) || (!isFromFilterMode && (!query || isHashtagQuery)),
|
||||||
|
});
|
||||||
|
|
||||||
const handleClickOutside = useLastCallback((event: MouseEvent) => {
|
const handleClickOutside = useLastCallback((event: MouseEvent) => {
|
||||||
if (maybeLongPressActiveRef.current) return;
|
if (maybeLongPressActiveRef.current) return;
|
||||||
@ -155,17 +186,11 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
useClickOutside([ref], handleClickOutside);
|
useClickOutside([ref], handleClickOutside);
|
||||||
|
|
||||||
const hasResultsContainer = Boolean((query && foundIds) || isHashtagQuery);
|
const hasResultsContainer = Boolean(((query || fromPeerId) && foundIds) || isHashtagQuery);
|
||||||
const isOnlyHash = isHashtagQuery && !query;
|
const isOnlyHash = isHashtagQuery && !query;
|
||||||
const areResultsEmpty = Boolean(query && foundIds && !foundIds.length && !isLoading && !isOnlyHash);
|
|
||||||
const hasResultsPlaceholder = areResultsEmpty || isOnlyHash;
|
|
||||||
const isNonFocusedDropdownForced = searchType === 'myChats' || searchType === 'channels';
|
const isNonFocusedDropdownForced = searchType === 'myChats' || searchType === 'channels';
|
||||||
const hasResultsDropdown = isActive && (isViewAsList || !isMobile) && (isFocused || isNonFocusedDropdownForced)
|
const hasMemberResults = !isHashtagQuery && Boolean(memberSearchResults?.length);
|
||||||
&& Boolean(
|
const hasQueryData = Boolean((query && !isOnlyHash) || savedTag || fromPeerId);
|
||||||
hasResultsContainer || hasResultsPlaceholder || savedTags,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasQueryData = Boolean((query && !isOnlyHash) || savedTag);
|
|
||||||
const hasNavigationButtons = searchType === 'chat' && Boolean(foundIds?.length);
|
const hasNavigationButtons = searchType === 'chat' && Boolean(foundIds?.length);
|
||||||
|
|
||||||
const handleClose = useLastCallback(() => {
|
const handleClose = useLastCallback(() => {
|
||||||
@ -236,6 +261,8 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
setIsViewAsList(true);
|
setIsViewAsList(true);
|
||||||
setFocusedIndex(0);
|
setFocusedIndex(0);
|
||||||
setQuery('');
|
setQuery('');
|
||||||
|
setIsFromFilterMode(false);
|
||||||
|
setMemberSearchQuery('');
|
||||||
hiddenTimerRef.current = window.setTimeout(() => setIsFullyHidden(true), HIDE_TIMEOUT);
|
hiddenTimerRef.current = window.setTimeout(() => setIsFullyHidden(true), HIDE_TIMEOUT);
|
||||||
} else {
|
} else {
|
||||||
setIsFullyHidden(false);
|
setIsFullyHidden(false);
|
||||||
@ -274,13 +301,15 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
}, [isHistoryCalendarOpen, isActive]);
|
}, [isHistoryCalendarOpen, isActive]);
|
||||||
|
|
||||||
const handleReset = useLastCallback(() => {
|
const handleReset = useLastCallback(() => {
|
||||||
if (!query?.length && !savedTag) {
|
if (!query?.length && !savedTag && !fromPeerId && !isFromFilterMode) {
|
||||||
handleClose();
|
handleClose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setQuery('');
|
setQuery('');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setIsFromFilterMode(false);
|
||||||
|
setMemberSearchQuery('');
|
||||||
resetMiddleSearch();
|
resetMiddleSearch();
|
||||||
focusInput();
|
focusInput();
|
||||||
});
|
});
|
||||||
@ -321,9 +350,23 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isGroupChat && newQuery.toLowerCase().startsWith(FROM_FILTER_PREFIX) && !fromPeerId && !isFromFilterMode) {
|
||||||
|
setIsFromFilterMode(true);
|
||||||
|
const memberQuery = newQuery.slice(FROM_FILTER_PREFIX.length).trim();
|
||||||
|
setMemberSearchQuery(memberQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When in from filter mode (selecting a member), update member search query
|
||||||
|
if (isFromFilterMode && !fromPeerId) {
|
||||||
|
setMemberSearchQuery(newQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal query update (including when fromPeerId is set - query is separate)
|
||||||
setQuery(newQuery);
|
setQuery(newQuery);
|
||||||
|
|
||||||
if (!newQuery) {
|
if (!newQuery && !fromPeerId) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
resetMiddleSearch();
|
resetMiddleSearch();
|
||||||
shouldCancelSearchRef.current = true;
|
shouldCancelSearchRef.current = true;
|
||||||
@ -331,10 +374,10 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (query) {
|
if (isActive && (query || fromPeerId)) {
|
||||||
handleSearch();
|
handleSearch();
|
||||||
}
|
}
|
||||||
}, [query]);
|
}, [isActive, query, fromPeerId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(Boolean(fetchingQuery));
|
setIsLoading(Boolean(fetchingQuery));
|
||||||
@ -391,8 +434,16 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const [viewportIds, getMore, viewportOffset = 0] = useInfiniteScroll(handleSearch, foundIds);
|
const [viewportIds, getMore, viewportOffset = 0] = useInfiniteScroll(handleSearch, foundIds);
|
||||||
|
|
||||||
|
const [viewportMemberIds, getMoreMembers] = useInfiniteScroll(
|
||||||
|
undefined,
|
||||||
|
hasMemberDropdown ? memberSearchResults : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const displayedMembers = (hasMemberResults
|
||||||
|
&& (hasMemberDropdown ? viewportMemberIds : memberSearchResults.slice(0, INLINE_MEMBER_COUNT))) || undefined;
|
||||||
|
|
||||||
const viewportResults = useMemo(() => {
|
const viewportResults = useMemo(() => {
|
||||||
if ((!query && !savedTag) || !viewportIds?.length) {
|
if ((!query && !savedTag && !fromPeerId) || !viewportIds?.length) {
|
||||||
return MEMO_EMPTY_ARRAY;
|
return MEMO_EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
@ -418,7 +469,16 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
senderPeer,
|
senderPeer,
|
||||||
};
|
};
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
}, [query, savedTag, viewportIds, isSavedMessages]);
|
}, [query, savedTag, fromPeerId, viewportIds, isSavedMessages]);
|
||||||
|
|
||||||
|
const areResultsEmpty = Boolean(
|
||||||
|
(query || fromPeerId) && !isLoading && !viewportResults.length && !isOnlyHash && !hasMemberResults,
|
||||||
|
);
|
||||||
|
const hasResultsPlaceholder = areResultsEmpty || isOnlyHash;
|
||||||
|
const hasResultsDropdown = isActive && (isViewAsList || !isMobile) && (isFocused || isNonFocusedDropdownForced)
|
||||||
|
&& Boolean(
|
||||||
|
hasResultsContainer || hasResultsPlaceholder || savedTags || hasMemberResults,
|
||||||
|
);
|
||||||
|
|
||||||
const handleMessageClick = useLastCallback((message: ApiMessage) => {
|
const handleMessageClick = useLastCallback((message: ApiMessage) => {
|
||||||
const searchResultKey = getSearchResultKey(message);
|
const searchResultKey = getSearchResultKey(message);
|
||||||
@ -444,13 +504,33 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
markFocused();
|
markFocused();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleKeyDown = useKeyboardListNavigation(containerRef, hasResultsContainer, (index) => {
|
const handleKeyDown = useKeyboardListNavigation(
|
||||||
const foundResult = viewportResults?.[index === -1 ? 0 : index];
|
containerRef,
|
||||||
if (foundResult) {
|
hasResultsContainer || hasMemberResults,
|
||||||
handleMessageClick(foundResult.message);
|
(index) => {
|
||||||
setFocusedIndex(index + viewportOffset);
|
const actualIndex = index === -1 ? 0 : index;
|
||||||
}
|
const membersCount = displayedMembers?.length || 0;
|
||||||
}, `.${RESULT_ITEM_CLASS_NAME}`, true);
|
|
||||||
|
// Member selection
|
||||||
|
if (actualIndex < membersCount && displayedMembers) {
|
||||||
|
const memberId = displayedMembers[actualIndex];
|
||||||
|
if (memberId) {
|
||||||
|
handleSelectFromMember(memberId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message selection
|
||||||
|
const messageIndex = actualIndex - membersCount;
|
||||||
|
const foundResult = viewportResults?.[messageIndex];
|
||||||
|
if (foundResult) {
|
||||||
|
handleMessageClick(foundResult.message);
|
||||||
|
setFocusedIndex(messageIndex + viewportOffset);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
`.${RESULT_ITEM_CLASS_NAME}`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
const updateSearchParams = useLastCallback((update: Partial<MiddleSearchParams>) => {
|
const updateSearchParams = useLastCallback((update: Partial<MiddleSearchParams>) => {
|
||||||
updateMiddleSearch({ chatId: chat!.id, threadId, update });
|
updateMiddleSearch({ chatId: chat!.id, threadId, update });
|
||||||
@ -474,6 +554,18 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleDeleteTag = useLastCallback(() => {
|
const handleDeleteTag = useLastCallback(() => {
|
||||||
|
if (fromPeerId) {
|
||||||
|
updateSearchParams({ fromPeerId: undefined });
|
||||||
|
setMemberSearchQuery('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFromFilterMode) {
|
||||||
|
setIsFromFilterMode(false);
|
||||||
|
setMemberSearchQuery('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isHashtagQuery) {
|
if (isHashtagQuery) {
|
||||||
updateSearchParams({ isHashtag: false });
|
updateSearchParams({ isHashtag: false });
|
||||||
return;
|
return;
|
||||||
@ -489,6 +581,19 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
setIsViewAsList(true);
|
setIsViewAsList(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleFromFilterClick = useLastCallback(() => {
|
||||||
|
setIsFromFilterMode(true);
|
||||||
|
setMemberSearchQuery('');
|
||||||
|
focusInput();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectFromMember = useLastCallback((peerId: string) => {
|
||||||
|
updateMiddleSearch({ chatId: chat!.id, threadId, update: { fromPeerId: peerId } });
|
||||||
|
setMemberSearchQuery('');
|
||||||
|
setQuery('');
|
||||||
|
focusInput();
|
||||||
|
});
|
||||||
|
|
||||||
const handleFocusOlder = useLastCallback(() => {
|
const handleFocusOlder = useLastCallback(() => {
|
||||||
if (searchType !== 'chat') return;
|
if (searchType !== 'chat') return;
|
||||||
markBlurred();
|
markBlurred();
|
||||||
@ -551,11 +656,25 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderMemberItem(peerId: string) {
|
||||||
|
const peer = selectPeer(getGlobal(), peerId);
|
||||||
|
if (!peer) return undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MiddleSearchResult
|
||||||
|
key={`member-${peerId}`}
|
||||||
|
className={RESULT_ITEM_CLASS_NAME}
|
||||||
|
peer={peer}
|
||||||
|
onClick={handleSelectFromMember}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function renderDropdown() {
|
function renderDropdown() {
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.dropdown, !hasResultsDropdown && styles.dropdownHidden)}>
|
<div className={buildClassName(styles.dropdown, !hasResultsDropdown && styles.dropdownHidden)}>
|
||||||
{!isMobile && <div className={styles.separator} />}
|
{!isMobile && <div className={styles.separator} />}
|
||||||
{hasSavedTags && !isHashtagQuery && (
|
{hasSavedTags && !isHashtagQuery && !hasMemberDropdown && (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
styles.savedTags,
|
styles.savedTags,
|
||||||
@ -580,7 +699,7 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isHashtagQuery && (
|
{isHashtagQuery && !hasMemberDropdown && (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(styles.searchTypes, 'no-scrollbar')}
|
className={buildClassName(styles.searchTypes, 'no-scrollbar')}
|
||||||
>
|
>
|
||||||
@ -589,16 +708,21 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
{renderTypeTag('channels')}
|
{renderTypeTag('channels')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasResultsContainer && (
|
{Boolean(hasResultsContainer || hasMemberResults) && (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={buildClassName(styles.results, 'custom-scroll')}
|
className={buildClassName(styles.results, 'custom-scroll')}
|
||||||
items={viewportResults}
|
items={hasMemberDropdown ? displayedMembers : viewportResults}
|
||||||
itemSelector={`.${RESULT_ITEM_CLASS_NAME}`}
|
itemSelector={`.${RESULT_ITEM_CLASS_NAME}`}
|
||||||
preloadBackwards={0}
|
preloadBackwards={0}
|
||||||
onLoadMore={getMore}
|
onLoadMore={hasMemberDropdown ? getMoreMembers : getMore}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
|
{isMemberSearchLoading && hasMemberDropdown && <Loading />}
|
||||||
|
{displayedMembers?.map(renderMemberItem)}
|
||||||
|
{hasMemberDropdown && !isMemberSearchLoading && !memberSearchResults?.length && (
|
||||||
|
<span className={styles.placeholder}>{oldLang('NoResult')}</span>
|
||||||
|
)}
|
||||||
{areResultsEmpty && (
|
{areResultsEmpty && (
|
||||||
<span key="nothing" className={styles.placeholder}>
|
<span key="nothing" className={styles.placeholder}>
|
||||||
{oldLang('NoResultFoundFor', query)}
|
{oldLang('NoResultFoundFor', query)}
|
||||||
@ -609,7 +733,7 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
{oldLang('HashtagSearchPlaceholder')}
|
{oldLang('HashtagSearchPlaceholder')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{viewportResults?.map(({
|
{!hasMemberDropdown && viewportResults?.map(({
|
||||||
message, senderPeer, messageChat, searchResultKey,
|
message, senderPeer, messageChat, searchResultKey,
|
||||||
}, i) => (
|
}, i) => (
|
||||||
<MiddleSearchResult
|
<MiddleSearchResult
|
||||||
@ -618,7 +742,7 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
className={RESULT_ITEM_CLASS_NAME}
|
className={RESULT_ITEM_CLASS_NAME}
|
||||||
query={query}
|
query={query}
|
||||||
message={message}
|
message={message}
|
||||||
senderPeer={senderPeer}
|
peer={senderPeer}
|
||||||
messageChat={messageChat}
|
messageChat={messageChat}
|
||||||
shouldShowChat={isHashtagQuery}
|
shouldShowChat={isHashtagQuery}
|
||||||
isActive={focusedIndex - viewportOffset === i}
|
isActive={focusedIndex - viewportOffset === i}
|
||||||
@ -654,7 +778,7 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
<SearchInput
|
<SearchInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={query}
|
value={isFromFilterMode && !fromPeerId ? memberSearchQuery : query}
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
styles.input,
|
styles.input,
|
||||||
hasResultsDropdown && styles.withDropdown,
|
hasResultsDropdown && styles.withDropdown,
|
||||||
@ -687,11 +811,27 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isHashtagQuery && <div className={styles.hash}>#</div>}
|
{isHashtagQuery && <div className={styles.hash}>#</div>}
|
||||||
|
{(isFromFilterMode || fromPeerId) && (
|
||||||
|
<div className={styles.fromTag}>
|
||||||
|
{lang('SearchFilterFrom')}
|
||||||
|
{fromPeerId && <PeerChip peerId={fromPeerId} forceShowSelf canClose onClick={handleDeleteTag} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isMobile && renderDropdown()}
|
{!isMobile && renderDropdown()}
|
||||||
</SearchInput>
|
</SearchInput>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<div className={styles.icons}>
|
<div className={styles.icons}>
|
||||||
|
{isGroupChat && !isFromFilterMode && !fromPeerId && (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
size="smaller"
|
||||||
|
color="translucent"
|
||||||
|
onClick={handleFromFilterClick}
|
||||||
|
ariaLabel={oldLang('FilterByUser')}
|
||||||
|
iconName="user"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
size="smaller"
|
size="smaller"
|
||||||
@ -706,6 +846,16 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
|||||||
{isMobile && renderDropdown()}
|
{isMobile && renderDropdown()}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
|
{isGroupChat && !isFromFilterMode && !fromPeerId && (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
size="smaller"
|
||||||
|
color="translucent"
|
||||||
|
onClick={handleFromFilterClick}
|
||||||
|
ariaLabel={oldLang('FilterByUser')}
|
||||||
|
iconName="user"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
size="smaller"
|
size="smaller"
|
||||||
@ -779,7 +929,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
requestedQuery, savedTag, results, fetchingQuery, isHashtag, type,
|
requestedQuery, savedTag, results, fetchingQuery, isHashtag, type, fromPeerId,
|
||||||
} = selectCurrentMiddleSearch(global) || {};
|
} = selectCurrentMiddleSearch(global) || {};
|
||||||
const { totalCount, foundIds, query: lastSearchQuery } = results || {};
|
const { totalCount, foundIds, query: lastSearchQuery } = results || {};
|
||||||
|
|
||||||
@ -808,6 +958,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
currentUserId,
|
currentUserId,
|
||||||
searchType: type,
|
searchType: type,
|
||||||
lastSearchQuery,
|
lastSearchQuery,
|
||||||
|
fromPeerId,
|
||||||
|
isGroupChat: isChatGroup(chat),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MiddleSearch));
|
)(MiddleSearch));
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { memo } from '../../../lib/teact/teact';
|
|||||||
|
|
||||||
import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types';
|
import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types';
|
||||||
|
|
||||||
import { getMessageSenderName } from '../../../global/helpers/peers';
|
import { getGroupStatus, getMainUsername } from '../../../global/helpers';
|
||||||
|
import { getMessageSenderName, isApiPeerChat } from '../../../global/helpers/peers';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
@ -18,13 +19,13 @@ import styles from './MiddleSearchResult.module.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
message: ApiMessage;
|
message?: ApiMessage;
|
||||||
senderPeer?: ApiPeer;
|
peer?: ApiPeer;
|
||||||
messageChat?: ApiChat;
|
messageChat?: ApiChat;
|
||||||
shouldShowChat?: boolean;
|
shouldShowChat?: boolean;
|
||||||
query?: string;
|
query?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick: (message: ApiMessage) => void;
|
onClick: (clickArg: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TRUNCATE_LENGTH = 200;
|
const TRUNCATE_LENGTH = 200;
|
||||||
@ -32,7 +33,7 @@ const TRUNCATE_LENGTH = 200;
|
|||||||
const MiddleSearchResult = ({
|
const MiddleSearchResult = ({
|
||||||
isActive,
|
isActive,
|
||||||
message,
|
message,
|
||||||
senderPeer,
|
peer,
|
||||||
messageChat,
|
messageChat,
|
||||||
shouldShowChat,
|
shouldShowChat,
|
||||||
query,
|
query,
|
||||||
@ -40,11 +41,47 @@ const MiddleSearchResult = ({
|
|||||||
onClick,
|
onClick,
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
if (peer && !message) {
|
||||||
|
const username = getMainUsername(peer);
|
||||||
|
|
||||||
|
const handlePeerClick = () => {
|
||||||
|
onClick(peer.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={buildClassName(styles.root, isActive && styles.active, className)}
|
||||||
|
onClick={handlePeerClick}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
className={styles.avatar}
|
||||||
|
peer={peer}
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
<div className={styles.info}>
|
||||||
|
<div className={styles.topRow}>
|
||||||
|
<FullNameTitle peer={peer} withEmojiStatus />
|
||||||
|
</div>
|
||||||
|
{(isApiPeerChat(peer) || username) && (
|
||||||
|
<div className={styles.subtitle} dir="auto">
|
||||||
|
{username
|
||||||
|
? `@${username}`
|
||||||
|
: getGroupStatus(lang, peer as ApiChat)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message) return undefined;
|
||||||
|
|
||||||
const hiddenForwardTitle = message.forwardInfo?.hiddenUserName;
|
const hiddenForwardTitle = message.forwardInfo?.hiddenUserName;
|
||||||
|
const senderPeer = shouldShowChat ? messageChat : peer;
|
||||||
const peer = shouldShowChat ? messageChat : senderPeer;
|
const senderName = shouldShowChat && peer ? getMessageSenderName(lang, message.chatId, peer) : undefined;
|
||||||
|
|
||||||
const senderName = shouldShowChat && senderPeer ? getMessageSenderName(lang, message.chatId, senderPeer) : undefined;
|
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
onClick(message);
|
onClick(message);
|
||||||
@ -59,13 +96,13 @@ const MiddleSearchResult = ({
|
|||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
peer={peer}
|
peer={senderPeer}
|
||||||
text={hiddenForwardTitle}
|
text={hiddenForwardTitle}
|
||||||
size="medium"
|
size="medium"
|
||||||
/>
|
/>
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<div className={styles.topRow}>
|
<div className={styles.topRow}>
|
||||||
{(peer && <FullNameTitle peer={peer} withEmojiStatus />) || hiddenForwardTitle}
|
{(senderPeer && <FullNameTitle peer={senderPeer} withEmojiStatus />) || hiddenForwardTitle}
|
||||||
<LastMessageMeta className={styles.meta} message={message} />
|
<LastMessageMeta className={styles.meta} message={message} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.subtitle} dir="auto">
|
<div className={styles.subtitle} dir="auto">
|
||||||
|
|||||||
@ -64,16 +64,17 @@ addActionHandler('performMiddleSearch', async (global, actions, payload): Promis
|
|||||||
currentSearch = selectCurrentMiddleSearch(global, tabId)!;
|
currentSearch = selectCurrentMiddleSearch(global, tabId)!;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
results, savedTag, type, isHashtag,
|
results, savedTag, type, isHashtag, fromPeerId,
|
||||||
} = currentSearch;
|
} = currentSearch;
|
||||||
const shouldReuseParams = results?.query === query;
|
const shouldReuseParams = results?.query === query;
|
||||||
|
const fromPeer = fromPeerId ? selectPeer(global, fromPeerId) : undefined;
|
||||||
|
|
||||||
const offsetId = shouldReuseParams ? results?.nextOffsetId : undefined;
|
const offsetId = shouldReuseParams ? results?.nextOffsetId : undefined;
|
||||||
const offsetRate = shouldReuseParams ? results?.nextOffsetRate : undefined;
|
const offsetRate = shouldReuseParams ? results?.nextOffsetRate : undefined;
|
||||||
const offsetPeerId = shouldReuseParams ? results?.nextOffsetPeerId : undefined;
|
const offsetPeerId = shouldReuseParams ? results?.nextOffsetPeerId : undefined;
|
||||||
const offsetPeer = shouldReuseParams && offsetPeerId ? selectChat(global, offsetPeerId) : undefined;
|
const offsetPeer = shouldReuseParams && offsetPeerId ? selectChat(global, offsetPeerId) : undefined;
|
||||||
|
|
||||||
const shouldHaveQuery = isHashtag || !savedTag;
|
const shouldHaveQuery = isHashtag || (!savedTag && !fromPeerId);
|
||||||
if (shouldHaveQuery && !query) {
|
if (shouldHaveQuery && !query) {
|
||||||
global = updateMiddleSearch(global, realChatId, threadId, {
|
global = updateMiddleSearch(global, realChatId, threadId, {
|
||||||
fetchingQuery: undefined,
|
fetchingQuery: undefined,
|
||||||
@ -98,6 +99,7 @@ addActionHandler('performMiddleSearch', async (global, actions, payload): Promis
|
|||||||
offsetId,
|
offsetId,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
savedTag,
|
savedTag,
|
||||||
|
fromPeer,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,13 +12,13 @@ import {
|
|||||||
import { selectCurrentMessageList } from '../../selectors';
|
import { selectCurrentMessageList } from '../../selectors';
|
||||||
|
|
||||||
addActionHandler('openMiddleSearch', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openMiddleSearch', (global, actions, payload): ActionReturnType => {
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
const { fromPeerId, tabId = getCurrentTabId() } = payload || {};
|
||||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
if (!chatId || !threadId) {
|
if (!chatId || !threadId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateMiddleSearch(global, chatId, threadId, {}, tabId);
|
return updateMiddleSearch(global, chatId, threadId, { fromPeerId }, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('closeMiddleSearch', (global, actions, payload): ActionReturnType => {
|
addActionHandler('closeMiddleSearch', (global, actions, payload): ActionReturnType => {
|
||||||
|
|||||||
@ -77,7 +77,11 @@ export function updateMiddleSearch<T extends GlobalState>(
|
|||||||
updated.type = 'chat';
|
updated.type = 'chat';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSearch && (currentSearch.type !== updated.type || currentSearch.savedTag !== updated.savedTag)) {
|
if (currentSearch && (
|
||||||
|
currentSearch.type !== updated.type
|
||||||
|
|| currentSearch.savedTag !== updated.savedTag
|
||||||
|
|| currentSearch.fromPeerId !== updated.fromPeerId
|
||||||
|
)) {
|
||||||
updated.results = undefined;
|
updated.results = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -312,7 +312,9 @@ export interface ActionPayloads {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Message search
|
// Message search
|
||||||
openMiddleSearch: WithTabId | undefined;
|
openMiddleSearch: {
|
||||||
|
fromPeerId?: string;
|
||||||
|
} & WithTabId | undefined;
|
||||||
closeMiddleSearch: WithTabId | undefined;
|
closeMiddleSearch: WithTabId | undefined;
|
||||||
updateMiddleSearch: {
|
updateMiddleSearch: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { useState } from '../lib/teact/teact';
|
import { useState } from '../lib/teact/teact';
|
||||||
|
import { getGlobal } from '../global';
|
||||||
|
|
||||||
import type { ApiChat } from '../api/types';
|
import type { ApiChat } from '../api/types';
|
||||||
|
|
||||||
|
import { isChatBasicGroup, isChatSuperGroup } from '../global/helpers';
|
||||||
|
import { filterPeersByQuery } from '../global/helpers/peers';
|
||||||
|
import { selectChatFullInfo } from '../global/selectors';
|
||||||
import { callApi } from '../api/gramjs';
|
import { callApi } from '../api/gramjs';
|
||||||
import useAsync from './useAsync';
|
import useAsync from './useAsync';
|
||||||
import useDebouncedMemo from './useDebouncedMemo';
|
import useDebouncedMemo from './useDebouncedMemo';
|
||||||
@ -20,13 +24,40 @@ export async function peerGlobalSearch(query: string) {
|
|||||||
|
|
||||||
export function prepareChatMemberSearch(chat: ApiChat) {
|
export function prepareChatMemberSearch(chat: ApiChat) {
|
||||||
return async (query: string) => {
|
return async (query: string) => {
|
||||||
|
const trimmedQuery = query.trim();
|
||||||
|
|
||||||
|
// For basic groups, filter from cached members in fullInfo
|
||||||
|
if (isChatBasicGroup(chat)) {
|
||||||
|
const global = getGlobal();
|
||||||
|
const fullInfo = selectChatFullInfo(global, chat.id);
|
||||||
|
const memberIds = fullInfo?.members?.map((m) => m.userId) || [];
|
||||||
|
|
||||||
|
if (!trimmedQuery) {
|
||||||
|
return memberIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterPeersByQuery({ ids: memberIds, query: trimmedQuery, type: 'user' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// For supergroups/channels, use API
|
||||||
const searchResult = await callApi('fetchMembers', {
|
const searchResult = await callApi('fetchMembers', {
|
||||||
chat,
|
chat,
|
||||||
memberFilter: 'search',
|
memberFilter: trimmedQuery ? 'search' : 'recent',
|
||||||
query,
|
query: trimmedQuery,
|
||||||
});
|
});
|
||||||
|
|
||||||
return searchResult?.members?.map((member) => member.userId) || [];
|
const memberIds = searchResult?.members?.map((member) => member.userId) || [];
|
||||||
|
|
||||||
|
if (!isChatSuperGroup(chat)) {
|
||||||
|
return memberIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trimmedQuery) {
|
||||||
|
return [...memberIds, chat.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatMatches = filterPeersByQuery({ ids: [chat.id], query: trimmedQuery, type: 'chat' });
|
||||||
|
return [...memberIds, ...chatMatches];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -411,6 +411,7 @@ export type MiddleSearchParams = {
|
|||||||
requestedQuery?: string;
|
requestedQuery?: string;
|
||||||
savedTag?: ApiReaction;
|
savedTag?: ApiReaction;
|
||||||
isHashtag?: boolean;
|
isHashtag?: boolean;
|
||||||
|
fromPeerId?: string;
|
||||||
fetchingQuery?: string;
|
fetchingQuery?: string;
|
||||||
type: MiddleSearchType;
|
type: MiddleSearchType;
|
||||||
results?: MiddleSearchResults;
|
results?: MiddleSearchResults;
|
||||||
|
|||||||
1
src/types/language.d.ts
vendored
1
src/types/language.d.ts
vendored
@ -1398,6 +1398,7 @@ export interface LangPair {
|
|||||||
'PrivateChatsSearchContext': undefined;
|
'PrivateChatsSearchContext': undefined;
|
||||||
'GroupChatsSearchContext': undefined;
|
'GroupChatsSearchContext': undefined;
|
||||||
'ChannelsSearchContext': undefined;
|
'ChannelsSearchContext': undefined;
|
||||||
|
'SearchFilterFrom': undefined;
|
||||||
'FolderLinkSubtitleNew': undefined;
|
'FolderLinkSubtitleNew': undefined;
|
||||||
'FolderLinkSubtitleAlready': undefined;
|
'FolderLinkSubtitleAlready': undefined;
|
||||||
'FolderLinkAddFolder': undefined;
|
'FolderLinkAddFolder': undefined;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user