Search: Display popular bot apps (#4864)
This commit is contained in:
parent
9daa5f1a19
commit
a4b33eb43a
@ -60,6 +60,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
||||||
|
bot, botActiveUsers, botInlinePlaceholder, botAttachMenu, botCanEdit,
|
||||||
} = mtpUser;
|
} = mtpUser;
|
||||||
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
||||||
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
||||||
@ -80,7 +81,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
type: userType,
|
type: userType,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
canEditBot: Boolean(mtpUser.botCanEdit),
|
canEditBot: botCanEdit,
|
||||||
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
|
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
|
||||||
...(usernames && { usernames }),
|
...(usernames && { usernames }),
|
||||||
phoneNumber: mtpUser.phone || '',
|
phoneNumber: mtpUser.phone || '',
|
||||||
@ -92,8 +93,9 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
areStoriesHidden: Boolean(mtpUser.storiesHidden),
|
areStoriesHidden: Boolean(mtpUser.storiesHidden),
|
||||||
maxStoryId: storiesMaxId,
|
maxStoryId: storiesMaxId,
|
||||||
hasStories: Boolean(storiesMaxId) && !storiesUnavailable,
|
hasStories: Boolean(storiesMaxId) && !storiesUnavailable,
|
||||||
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
...(bot && botInlinePlaceholder && { botPlaceholder: botInlinePlaceholder }),
|
||||||
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachBot: mtpUser.botAttachMenu }),
|
...(bot && botAttachMenu && { isAttachBot: botAttachMenu }),
|
||||||
|
botActiveUsers,
|
||||||
color: mtpUser.color && buildApiPeerColor(mtpUser.color),
|
color: mtpUser.color && buildApiPeerColor(mtpUser.color),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,6 +82,24 @@ export async function fetchTopInlineBots() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchTopBotApps() {
|
||||||
|
const topPeers = await invokeRequest(new GramJs.contacts.GetTopPeers({
|
||||||
|
botsApp: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!(topPeers instanceof GramJs.contacts.TopPeers)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = topPeers.users.map(buildApiUser).filter(Boolean);
|
||||||
|
const ids = users.map(({ id }) => id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ids,
|
||||||
|
users,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchInlineBot({ username }: { username: string }) {
|
export async function fetchInlineBot({ username }: { username: string }) {
|
||||||
const resolvedPeer = await invokeRequest(new GramJs.contacts.ResolveUsername({ username }));
|
const resolvedPeer = await invokeRequest(new GramJs.contacts.ResolveUsername({ username }));
|
||||||
|
|
||||||
@ -582,3 +600,30 @@ export function setBotInfo({
|
|||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchPopularAppBots({
|
||||||
|
offset = '', limit,
|
||||||
|
}: {
|
||||||
|
offset?: string;
|
||||||
|
limit?: number;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.bots.GetPopularAppBots({
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEntitiesToLocalDb(result.users);
|
||||||
|
|
||||||
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
|
const chats = result.users.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
|
|
||||||
|
return {
|
||||||
|
users,
|
||||||
|
chats,
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
answerCallbackButton, setBotInfo, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults,
|
answerCallbackButton, setBotInfo, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults,
|
||||||
sendInlineBotResult, startBot,
|
sendInlineBotResult, startBot, fetchPopularAppBots, fetchTopBotApps,
|
||||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
||||||
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
||||||
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export interface ApiUser {
|
|||||||
maxStoryId?: number;
|
maxStoryId?: number;
|
||||||
color?: ApiPeerColor;
|
color?: ApiPeerColor;
|
||||||
canEditBot?: boolean;
|
canEditBot?: boolean;
|
||||||
|
botActiveUsers?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiUserFullInfo {
|
export interface ApiUserFullInfo {
|
||||||
|
|||||||
@ -462,6 +462,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bot-menu-text {
|
.bot-menu-text {
|
||||||
|
--emoji-size: 1rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@ -29,8 +29,6 @@
|
|||||||
|
|
||||||
.TabList {
|
.TabList {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding-left: 0.5625rem;
|
|
||||||
padding-bottom: 1px;
|
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
@ -45,15 +43,6 @@
|
|||||||
|
|
||||||
.Tab {
|
.Tab {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
padding-left: 0.625rem;
|
|
||||||
padding-right: 0.625rem;
|
|
||||||
|
|
||||||
/* stylelint-disable-next-line */
|
|
||||||
> span {
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .Transition {
|
> .Transition {
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { AudioOrigin, LoadMoreDirection } from '../../../types';
|
|||||||
|
|
||||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||||
import { getIsDownloading, getMessageDownloadableMedia } from '../../../global/helpers';
|
import { getIsDownloading, getMessageDownloadableMedia } from '../../../global/helpers';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
|
||||||
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
||||||
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||||
@ -58,7 +57,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 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
|
||||||
}, [currentType, searchMessagesGlobal, searchQuery]);
|
}, [currentType, searchMessagesGlobal, searchQuery]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
@ -89,36 +88,35 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const media = getMessageDownloadableMedia(message)!;
|
const media = getMessageDownloadableMedia(message)!;
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className="ListItem small-icon"
|
|
||||||
key={message.id}
|
|
||||||
>
|
|
||||||
{shouldDrawDateDivider && (
|
{shouldDrawDateDivider && (
|
||||||
<p
|
<p
|
||||||
className={buildClassName(
|
className="section-heading"
|
||||||
'section-heading',
|
key={message.date}
|
||||||
isFirst && 'section-heading-first',
|
|
||||||
!isFirst && 'section-heading-with-border',
|
|
||||||
)}
|
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
>
|
>
|
||||||
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<Audio
|
<div
|
||||||
|
className="ListItem small-icon"
|
||||||
key={message.id}
|
key={message.id}
|
||||||
theme={theme}
|
>
|
||||||
message={message}
|
<Audio
|
||||||
origin={AudioOrigin.Search}
|
key={message.id}
|
||||||
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
theme={theme}
|
||||||
date={message.date}
|
message={message}
|
||||||
className="scroll-item"
|
origin={AudioOrigin.Search}
|
||||||
onPlay={handlePlayAudio}
|
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
||||||
onDateClick={handleMessageFocus}
|
date={message.date}
|
||||||
canDownload={!chatsById[message.chatId]?.isProtected && !message.isProtected}
|
className="scroll-item"
|
||||||
isDownloading={getIsDownloading(activeDownloads, media)}
|
onPlay={handlePlayAudio}
|
||||||
/>
|
onDateClick={handleMessageFocus}
|
||||||
</div>
|
canDownload={!chatsById[message.chatId]?.isProtected && !message.isProtected}
|
||||||
|
isDownloading={getIsDownloading(activeDownloads, media)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -126,7 +124,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
|||||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="LeftSearch">
|
<div className="LeftSearch--content">
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className="search-content documents-list custom-scroll"
|
className="search-content documents-list custom-scroll"
|
||||||
items={foundMessages}
|
items={foundMessages}
|
||||||
|
|||||||
153
src/components/left/search/BotAppResults.tsx
Normal file
153
src/components/left/search/BotAppResults.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
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 { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
|
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||||
|
import { filterUsersByName } from '../../../global/helpers';
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
|
import { throttle } from '../../../util/schedulers';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||||
|
|
||||||
|
import NothingFound from '../../common/NothingFound';
|
||||||
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
|
import Link from '../../ui/Link';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
import LeftSearchResultChat from './LeftSearchResultChat';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
searchQuery?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
isSynced?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
foundIds?: string[];
|
||||||
|
recentBotIds?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const LESS_LIST_ITEMS_AMOUNT = 5;
|
||||||
|
const runThrottled = throttle((cb) => cb(), 500, true);
|
||||||
|
|
||||||
|
const BotAppResults: FC<OwnProps & StateProps> = ({
|
||||||
|
searchQuery,
|
||||||
|
isSynced,
|
||||||
|
isLoading,
|
||||||
|
foundIds,
|
||||||
|
recentBotIds,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
searchPopularBotApps,
|
||||||
|
openChatWithInfo,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const lang = useOldLang();
|
||||||
|
|
||||||
|
const [shouldShowMoreMine, setShouldShowMoreMine] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const filteredFoundIds = useMemo(() => {
|
||||||
|
if (!foundIds) return [];
|
||||||
|
const recentSet = new Set(recentBotIds);
|
||||||
|
const withoutRecent = foundIds.filter((id) => !recentSet.has(id));
|
||||||
|
|
||||||
|
const usersById = getGlobal().users.byId;
|
||||||
|
return filterUsersByName(withoutRecent, usersById, searchQuery);
|
||||||
|
}, [foundIds, recentBotIds, searchQuery]);
|
||||||
|
|
||||||
|
const handleChatClick = useLastCallback((id: string) => {
|
||||||
|
openChatWithInfo({ id, shouldReplaceHistory: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||||
|
if (!isSynced) return;
|
||||||
|
if (direction === LoadMoreDirection.Backwards) {
|
||||||
|
runThrottled(() => {
|
||||||
|
searchPopularBotApps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isSynced]);
|
||||||
|
|
||||||
|
const handleToggleShowMoreMine = useLastCallback(() => {
|
||||||
|
setShouldShowMoreMine((prev) => !prev);
|
||||||
|
});
|
||||||
|
|
||||||
|
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="LeftSearch--content">
|
||||||
|
<InfiniteScroll
|
||||||
|
className="search-content custom-scroll"
|
||||||
|
items={filteredFoundIds}
|
||||||
|
onLoadMore={handleLoadMore}
|
||||||
|
noFastList
|
||||||
|
>
|
||||||
|
{!canRenderContents && <Loading />}
|
||||||
|
{canRenderContents && !filteredFoundIds?.length && (
|
||||||
|
<NothingFound
|
||||||
|
text={lang('ChatList.Search.NoResults')}
|
||||||
|
description={lang('ChatList.Search.NoResultsDescription')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{canRenderContents && !searchQuery && recentBotIds?.length && (
|
||||||
|
<div className="search-section">
|
||||||
|
<h3 className="section-heading">
|
||||||
|
{recentBotIds.length > LESS_LIST_ITEMS_AMOUNT && (
|
||||||
|
<Link className="Link" onClick={handleToggleShowMoreMine}>
|
||||||
|
{lang(shouldShowMoreMine ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{lang('SearchAppsMine')}
|
||||||
|
</h3>
|
||||||
|
{recentBotIds.map((id, index) => {
|
||||||
|
if (!shouldShowMoreMine && index >= LESS_LIST_ITEMS_AMOUNT) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LeftSearchResultChat
|
||||||
|
chatId={id}
|
||||||
|
onClick={handleChatClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{canRenderContents && filteredFoundIds?.length && (
|
||||||
|
<div className="search-section">
|
||||||
|
<h3 className="section-heading">{lang('SearchAppsPopular')}</h3>
|
||||||
|
{filteredFoundIds.map((id) => {
|
||||||
|
return (
|
||||||
|
<LeftSearchResultChat
|
||||||
|
chatId={id}
|
||||||
|
onClick={handleChatClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</InfiniteScroll>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>((global) => {
|
||||||
|
const globalSearch = selectTabState(global).globalSearch;
|
||||||
|
const foundIds = globalSearch.popularBotApps?.peerIds;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSynced: global.isSynced,
|
||||||
|
isLoading: !foundIds && globalSearch.fetchingStatus?.botApps,
|
||||||
|
foundIds,
|
||||||
|
recentBotIds: global.topBotApps.userIds,
|
||||||
|
};
|
||||||
|
})(BotAppResults));
|
||||||
@ -115,7 +115,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
|||||||
&& !foundTopicIds?.length;
|
&& !foundTopicIds?.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="LeftSearch">
|
<div className="LeftSearch--content">
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className="search-content custom-scroll chat-list"
|
className="search-content custom-scroll chat-list"
|
||||||
items={foundMessages}
|
items={foundMessages}
|
||||||
|
|||||||
@ -238,7 +238,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className="LeftSearch custom-scroll"
|
className="LeftSearch--content custom-scroll"
|
||||||
items={foundMessages}
|
items={foundMessages}
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
// To prevent scroll jumps caused by delayed local results rendering
|
// To prevent scroll jumps caused by delayed local results rendering
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { LoadMoreDirection } from '../../../types';
|
|||||||
|
|
||||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||||
import { getIsDownloading, getMessageDocument } from '../../../global/helpers';
|
import { getIsDownloading, getMessageDocument } from '../../../global/helpers';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
|
||||||
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
||||||
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||||
@ -69,7 +68,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 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]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
@ -95,36 +94,35 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
|||||||
const shouldDrawDateDivider = isFirst
|
const shouldDrawDateDivider = isFirst
|
||||||
|| toYearMonth(message.date) !== toYearMonth(foundMessages[index - 1].date);
|
|| toYearMonth(message.date) !== toYearMonth(foundMessages[index - 1].date);
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className="ListItem small-icon"
|
|
||||||
key={message.id}
|
|
||||||
>
|
|
||||||
{shouldDrawDateDivider && (
|
{shouldDrawDateDivider && (
|
||||||
<p
|
<p
|
||||||
className={buildClassName(
|
className="section-heading"
|
||||||
'section-heading',
|
|
||||||
isFirst && 'section-heading-first',
|
|
||||||
!isFirst && 'section-heading-with-border',
|
|
||||||
)}
|
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
|
key={message.date}
|
||||||
>
|
>
|
||||||
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<Document
|
<div
|
||||||
document={getMessageDocument(message)!}
|
className="ListItem small-icon"
|
||||||
message={message}
|
key={message.id}
|
||||||
withDate
|
>
|
||||||
datetime={message.date}
|
<Document
|
||||||
smaller
|
document={getMessageDocument(message)!}
|
||||||
sender={getSenderName(lang, message, chatsById, usersById)}
|
message={message}
|
||||||
className="scroll-item"
|
withDate
|
||||||
isDownloading={getIsDownloading(activeDownloads, message.content.document!)}
|
datetime={message.date}
|
||||||
shouldWarnAboutSvg={shouldWarnAboutSvg}
|
smaller
|
||||||
observeIntersection={observeIntersectionForMedia}
|
sender={getSenderName(lang, message, chatsById, usersById)}
|
||||||
onDateClick={handleMessageFocus}
|
className="scroll-item"
|
||||||
/>
|
isDownloading={getIsDownloading(activeDownloads, message.content.document!)}
|
||||||
</div>
|
shouldWarnAboutSvg={shouldWarnAboutSvg}
|
||||||
|
observeIntersection={observeIntersectionForMedia}
|
||||||
|
onDateClick={handleMessageFocus}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -132,7 +130,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
|||||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="LeftSearch">
|
<div ref={containerRef} className="LeftSearch--content">
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className="search-content documents-list custom-scroll"
|
className="search-content documents-list custom-scroll"
|
||||||
items={foundMessages}
|
items={foundMessages}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.LeftSearch {
|
.LeftSearch {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -10,23 +12,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.TabList {
|
.TabList {
|
||||||
padding-bottom: 1px;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--content {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--media {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.documents-list {
|
.documents-list {
|
||||||
padding: 0 1.25rem 1.25rem;
|
padding-bottom: 1.25rem;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
|
||||||
.ListItem {
|
.ListItem {
|
||||||
padding: 0.625rem 0;
|
padding-inline: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ListItem + .ListItem {
|
||||||
|
padding-block: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-heading {
|
.section-heading {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.625rem;
|
||||||
padding-left: 1.25rem;
|
padding-left: 1.75rem;
|
||||||
margin: 0 0 0.5rem -1.25rem !important;
|
margin: 0 0 1rem -1.25rem !important;
|
||||||
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
@ -36,16 +51,6 @@
|
|||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-with-border::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
width: calc(100% + 1.25rem);
|
|
||||||
height: 1px;
|
|
||||||
background: var(--color-borders);
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[dir="rtl"],
|
&[dir="rtl"],
|
||||||
&[dir="auto"] {
|
&[dir="auto"] {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
@ -68,9 +73,8 @@
|
|||||||
.LeftSearch .search-section .section-heading,
|
.LeftSearch .search-section .section-heading,
|
||||||
.RecentContacts .search-section .section-heading {
|
.RecentContacts .search-section .section-heading {
|
||||||
margin-left: -0.5rem !important;
|
margin-left: -0.5rem !important;
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.125rem;
|
||||||
width: calc(100% + 0.625rem);
|
width: calc(100% + 0.625rem);
|
||||||
box-shadow: 0 -1px 0 0 var(--color-borders);
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
display: none;
|
display: none;
|
||||||
@ -104,6 +108,9 @@
|
|||||||
.media-list {
|
.media-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||||
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-gap: 0.25rem;
|
grid-gap: 0.25rem;
|
||||||
@ -177,27 +184,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.ListItem {
|
|
||||||
margin: 0 -0.125rem 0 -0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-section {
|
.search-section {
|
||||||
padding: 0 0.5rem 0.5rem 0.5rem;
|
padding: 0 0.125rem 0 1rem;
|
||||||
|
|
||||||
.section-heading {
|
.section-heading {
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0.625rem !important;
|
||||||
padding-top: 0.875rem;
|
|
||||||
|
|
||||||
.Link {
|
.Link {
|
||||||
float: right;
|
float: right;
|
||||||
color: var(--color-links);
|
color: var(--color-links);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-right: 1rem;
|
margin-right: 1.5rem;
|
||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -222,12 +222,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-selection {
|
.chat-selection {
|
||||||
padding-block: 0.5rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
box-shadow: inset 0 -1px 0 0 var(--color-borders);
|
box-shadow: inset 0 -1px 0 0 var(--color-borders);
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import useOldLang from '../../../hooks/useOldLang';
|
|||||||
import TabList from '../../ui/TabList';
|
import TabList from '../../ui/TabList';
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import AudioResults from './AudioResults';
|
import AudioResults from './AudioResults';
|
||||||
|
import BotAppResults from './BotAppResults';
|
||||||
import ChatMessageResults from './ChatMessageResults';
|
import ChatMessageResults from './ChatMessageResults';
|
||||||
import ChatResults from './ChatResults';
|
import ChatResults from './ChatResults';
|
||||||
import FileResults from './FileResults';
|
import FileResults from './FileResults';
|
||||||
@ -43,6 +44,7 @@ type StateProps = {
|
|||||||
const TABS = [
|
const TABS = [
|
||||||
{ type: GlobalSearchContent.ChatList, title: 'SearchAllChatsShort' },
|
{ type: GlobalSearchContent.ChatList, title: 'SearchAllChatsShort' },
|
||||||
{ type: GlobalSearchContent.ChannelList, title: 'ChannelsTab' },
|
{ type: GlobalSearchContent.ChannelList, title: 'ChannelsTab' },
|
||||||
|
{ type: GlobalSearchContent.BotApps, title: 'AppsTab' },
|
||||||
{ type: GlobalSearchContent.Media, title: 'SharedMediaTab2' },
|
{ type: GlobalSearchContent.Media, title: 'SharedMediaTab2' },
|
||||||
{ type: GlobalSearchContent.Links, title: 'SharedLinksTab2' },
|
{ type: GlobalSearchContent.Links, title: 'SharedLinksTab2' },
|
||||||
{ type: GlobalSearchContent.Files, title: 'SharedFilesTab2' },
|
{ type: GlobalSearchContent.Files, title: 'SharedFilesTab2' },
|
||||||
@ -52,7 +54,7 @@ const TABS = [
|
|||||||
|
|
||||||
const CHAT_TABS = [
|
const CHAT_TABS = [
|
||||||
{ type: GlobalSearchContent.ChatList, title: 'All Messages' },
|
{ type: GlobalSearchContent.ChatList, title: 'All Messages' },
|
||||||
...TABS.slice(2), // Skip ChatList and ChannelList, replaced with All Messages
|
...TABS.slice(3), // Skip ChatList, ChannelList and BotApps, replaced with All Messages
|
||||||
];
|
];
|
||||||
|
|
||||||
const LeftSearch: FC<OwnProps & StateProps> = ({
|
const LeftSearch: FC<OwnProps & StateProps> = ({
|
||||||
@ -146,6 +148,13 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
|||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case GlobalSearchContent.BotApps:
|
||||||
|
return (
|
||||||
|
<BotAppResults
|
||||||
|
key="botApps"
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
/>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { withGlobal } from '../../../global';
|
|||||||
import type { ApiChat, ApiUser } from '../../../api/types';
|
import type { ApiChat, ApiUser } from '../../../api/types';
|
||||||
import { StoryViewerOrigin } from '../../../types';
|
import { StoryViewerOrigin } from '../../../types';
|
||||||
|
|
||||||
import { getPrivateChatUserId, isUserId, selectIsChatMuted } from '../../../global/helpers';
|
import { isUserId, selectIsChatMuted } from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat, selectIsChatPinned, selectNotifyExceptions,
|
selectChat, selectIsChatPinned, selectNotifyExceptions,
|
||||||
selectNotifySettings, selectUser,
|
selectNotifySettings, selectUser,
|
||||||
@ -76,10 +76,6 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const buttonRef = useSelectWithEnter(handleClick);
|
const buttonRef = useSelectWithEnter(handleClick);
|
||||||
|
|
||||||
if (!chat) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
className="chat-item-clickable search-result"
|
className="chat-item-clickable search-result"
|
||||||
@ -92,14 +88,14 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
|||||||
userId={chatId}
|
userId={chatId}
|
||||||
withUsername={withUsername}
|
withUsername={withUsername}
|
||||||
withStory
|
withStory
|
||||||
avatarSize="large"
|
avatarSize="medium"
|
||||||
storyViewerOrigin={StoryViewerOrigin.SearchResult}
|
storyViewerOrigin={StoryViewerOrigin.SearchResult}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<GroupChatInfo
|
<GroupChatInfo
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
withUsername={withUsername}
|
withUsername={withUsername}
|
||||||
avatarSize="large"
|
avatarSize="medium"
|
||||||
withStory
|
withStory
|
||||||
storyViewerOrigin={StoryViewerOrigin.SearchResult}
|
storyViewerOrigin={StoryViewerOrigin.SearchResult}
|
||||||
/>
|
/>
|
||||||
@ -127,8 +123,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const privateChatUserId = chat && getPrivateChatUserId(chat);
|
const user = selectUser(global, chatId);
|
||||||
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
|
||||||
const isPinned = selectIsChatPinned(global, chatId);
|
const isPinned = selectIsChatPinned(global, chatId);
|
||||||
const isMuted = chat
|
const isMuted = chat
|
||||||
? selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
? selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import type { StateProps } from './helpers/createMapStateToProps';
|
|||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
|
||||||
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
||||||
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||||
@ -92,32 +91,31 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
|||||||
const shouldDrawDateDivider = isFirst
|
const shouldDrawDateDivider = isFirst
|
||||||
|| toYearMonth(message.date) !== toYearMonth(foundMessages[index - 1].date);
|
|| toYearMonth(message.date) !== toYearMonth(foundMessages[index - 1].date);
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className="ListItem small-icon"
|
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
|
||||||
key={message.id}
|
|
||||||
>
|
|
||||||
{shouldDrawDateDivider && (
|
{shouldDrawDateDivider && (
|
||||||
<p
|
<p
|
||||||
className={buildClassName(
|
className="section-heading"
|
||||||
'section-heading',
|
key={message.date}
|
||||||
isFirst && 'section-heading-first',
|
|
||||||
!isFirst && 'section-heading-with-border',
|
|
||||||
)}
|
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
>
|
>
|
||||||
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<WebLink
|
<div
|
||||||
|
className="ListItem small-icon"
|
||||||
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
>
|
||||||
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
<WebLink
|
||||||
isProtected={isChatProtected || message.isProtected}
|
key={message.id}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
message={message}
|
||||||
onMessageClick={handleMessageFocus}
|
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
||||||
/>
|
isProtected={isChatProtected || message.isProtected}
|
||||||
</div>
|
observeIntersection={observeIntersectionForMedia}
|
||||||
|
onMessageClick={handleMessageFocus}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -125,7 +123,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
|||||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="LeftSearch">
|
<div ref={containerRef} className="LeftSearch--content">
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className="search-content documents-list custom-scroll"
|
className="search-content documents-list custom-scroll"
|
||||||
items={foundMessages}
|
items={foundMessages}
|
||||||
|
|||||||
@ -123,7 +123,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="LeftSearch">
|
<div ref={containerRef} className="LeftSearch--content LeftSearch--media">
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className={classNames}
|
className={classNames}
|
||||||
items={foundMessages}
|
items={foundMessages}
|
||||||
|
|||||||
@ -77,16 +77,10 @@
|
|||||||
.recent-chats-header {
|
.recent-chats-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.Button {
|
.clear-recent-chats {
|
||||||
margin-left: auto;
|
position: absolute;
|
||||||
}
|
inset-inline-end: 0.5rem;
|
||||||
|
|
||||||
&[dir="rtl"] {
|
|
||||||
.Button {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,10 +105,11 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
|
|||||||
{lang('Recent')}
|
{lang('Recent')}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
className="clear-recent-chats"
|
||||||
round
|
round
|
||||||
size="smaller"
|
size="smaller"
|
||||||
color="translucent"
|
color="translucent"
|
||||||
ariaLabel="Clear recent chats"
|
ariaLabel={lang('Clear')}
|
||||||
onClick={handleClearRecentlyFoundChats}
|
onClick={handleClearRecentlyFoundChats}
|
||||||
isRtl={lang.isRtl}
|
isRtl={lang.isRtl}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -259,6 +259,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
loadQuickReplies,
|
loadQuickReplies,
|
||||||
loadStarStatus,
|
loadStarStatus,
|
||||||
loadAvailableEffects,
|
loadAvailableEffects,
|
||||||
|
loadTopBotApps,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
if (DEBUG && !DEBUG_isLogged) {
|
if (DEBUG && !DEBUG_isLogged) {
|
||||||
@ -341,6 +342,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
loadGenericEmojiEffects();
|
loadGenericEmojiEffects();
|
||||||
loadSavedReactionTags();
|
loadSavedReactionTags();
|
||||||
loadAuthorizations();
|
loadAuthorizations();
|
||||||
|
loadTopBotApps();
|
||||||
}
|
}
|
||||||
}, [isMasterTab, isSynced]);
|
}, [isMasterTab, isSynced]);
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ const BotMenuButton: FC<OwnProps> = ({
|
|||||||
ariaLabel="Open bot command keyboard"
|
ariaLabel="Open bot command keyboard"
|
||||||
>
|
>
|
||||||
<i className={buildClassName('bot-menu-icon', 'icon', 'icon-webapp', isOpen && 'open')} />
|
<i className={buildClassName('bot-menu-icon', 'icon', 'icon-webapp', isOpen && 'open')} />
|
||||||
<span ref={textRef} className="bot-menu-text">{text}</span>
|
<span ref={textRef} className="bot-menu-text">{renderText(text)}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -46,18 +46,6 @@
|
|||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
top: -1px;
|
top: -1px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
.Tab {
|
|
||||||
padding: 1rem 0.75rem;
|
|
||||||
|
|
||||||
&_inner {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.platform {
|
|
||||||
bottom: -1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Transition {
|
.Transition {
|
||||||
|
|||||||
@ -225,7 +225,7 @@ function getNodes(origin: StoryViewerOrigin, userId: string) {
|
|||||||
containerSelector = '#LeftColumn .chat-list';
|
containerSelector = '#LeftColumn .chat-list';
|
||||||
break;
|
break;
|
||||||
case StoryViewerOrigin.SearchResult:
|
case StoryViewerOrigin.SearchResult:
|
||||||
containerSelector = '#LeftColumn .LeftSearch';
|
containerSelector = '#LeftColumn .LeftSearch--container';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -223,7 +223,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ListItem-button {
|
.ListItem-button {
|
||||||
padding: 0.5625rem;
|
padding: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.contact-list-item {
|
&.contact-list-item {
|
||||||
@ -237,7 +237,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Avatar {
|
.Avatar {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
@ -270,6 +270,7 @@
|
|||||||
.typing-status {
|
.typing-status {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
text-align: initial;
|
text-align: initial;
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
width: auto;
|
width: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.625rem 0.25rem;
|
padding: 0.625rem 1.125rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
border-top-left-radius: var(--border-radius-messages-small);
|
border-top-left-radius: var(--border-radius-messages-small);
|
||||||
@ -44,7 +44,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> span {
|
.Tab_inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -85,7 +85,7 @@
|
|||||||
|
|
||||||
.platform {
|
.platform {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: calc(-0.625rem - 1px);
|
bottom: -0.625rem;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
|
|||||||
@ -5,13 +5,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
font-size: 0.875rem;
|
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
box-shadow: 0 2px 2px var(--color-light-shadow);
|
box-shadow: 0 2px 2px var(--color-light-shadow);
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
|
||||||
|
padding-inline: 0.5rem;
|
||||||
|
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
scrollbar-color: rgba(0, 0, 0, 0);
|
scrollbar-color: rgba(0, 0, 0, 0);
|
||||||
|
|
||||||
|
|||||||
@ -261,6 +261,32 @@ addActionHandler('loadTopInlineBots', async (global): Promise<void> => {
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadTopBotApps', async (global): Promise<void> => {
|
||||||
|
const { lastRequestedAt } = global.topBotApps;
|
||||||
|
if (lastRequestedAt && getServerTime() - lastRequestedAt < TOP_PEERS_REQUEST_COOLDOWN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await callApi('fetchTopBotApps');
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ids, users } = result;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
topBotApps: {
|
||||||
|
...global.topBotApps,
|
||||||
|
userIds: ids,
|
||||||
|
lastRequestedAt: getServerTime(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('queryInlineBot', async (global, actions, payload): Promise<void> => {
|
addActionHandler('queryInlineBot', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
chatId, username, query, offset,
|
chatId, username, query, offset,
|
||||||
|
|||||||
@ -112,6 +112,38 @@ addActionHandler('searchMessagesGlobal', (global, actions, payload): ActionRetur
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('searchPopularBotApps', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
const popularBotApps = selectTabState(global, tabId).globalSearch.popularBotApps;
|
||||||
|
const offset = popularBotApps?.nextOffset;
|
||||||
|
if (popularBotApps?.peerIds && !offset) return; // Already fetched all
|
||||||
|
|
||||||
|
global = updateGlobalSearchFetchingStatus(global, { botApps: true }, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const result = await callApi('fetchPopularAppBots', { offset });
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
if (!result) {
|
||||||
|
global = updateGlobalSearchFetchingStatus(global, { botApps: false }, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = addChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
|
const peerIds = result.users.map(({ id }) => id);
|
||||||
|
global = updateGlobalSearch(global, {
|
||||||
|
popularBotApps: {
|
||||||
|
peerIds: [...(popularBotApps?.peerIds || []), ...peerIds],
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
global = updateGlobalSearchFetchingStatus(global, { botApps: false }, tabId);
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
|
import { GlobalSearchContent } from '../../../types';
|
||||||
|
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { addActionHandler } from '../../index';
|
import { addActionHandler } from '../../index';
|
||||||
@ -8,14 +9,17 @@ import { selectTabState } from '../../selectors';
|
|||||||
const MAX_RECENTLY_FOUND_IDS = 10;
|
const MAX_RECENTLY_FOUND_IDS = 10;
|
||||||
|
|
||||||
addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionReturnType => {
|
||||||
const { query, tabId = getCurrentTabId() } = payload!;
|
const { query, tabId = getCurrentTabId() } = payload;
|
||||||
const { chatId } = selectTabState(global, tabId).globalSearch;
|
const { chatId, currentContent } = selectTabState(global, tabId).globalSearch;
|
||||||
|
|
||||||
|
const fetchingStatus = query && currentContent !== GlobalSearchContent.BotApps
|
||||||
|
? { chats: !chatId, messages: true } : undefined;
|
||||||
|
|
||||||
return updateGlobalSearch(global, {
|
return updateGlobalSearch(global, {
|
||||||
globalResults: {},
|
globalResults: {},
|
||||||
localResults: {},
|
localResults: {},
|
||||||
resultsByType: undefined,
|
resultsByType: undefined,
|
||||||
...(query ? { fetchingStatus: { chats: !chatId, messages: true } } : { fetchingStatus: undefined }),
|
fetchingStatus,
|
||||||
query,
|
query,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -300,6 +300,7 @@ function reduceGlobal<T extends GlobalState>(global: T) {
|
|||||||
'contactList',
|
'contactList',
|
||||||
'topPeers',
|
'topPeers',
|
||||||
'topInlineBots',
|
'topInlineBots',
|
||||||
|
'topBotApps',
|
||||||
'recentEmojis',
|
'recentEmojis',
|
||||||
'recentCustomEmojis',
|
'recentCustomEmojis',
|
||||||
'push',
|
'push',
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export function getRequestInputInvoice<T extends GlobalState>(
|
|||||||
const {
|
const {
|
||||||
userId, stars, amount, currency,
|
userId, stars, amount, currency,
|
||||||
} = inputInvoice;
|
} = inputInvoice;
|
||||||
const user = selectUser(global, userId!);
|
const user = selectUser(global, userId);
|
||||||
|
|
||||||
if (!user) return undefined;
|
if (!user) return undefined;
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,9 @@ export function getUserStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (user.type && user.type === 'userTypeBot') {
|
if (user.type && user.type === 'userTypeBot') {
|
||||||
|
if (user.botActiveUsers) {
|
||||||
|
return lang('BotUsers', user.botActiveUsers, 'i');
|
||||||
|
}
|
||||||
return lang('Bot');
|
return lang('Bot');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -213,6 +213,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
topPeers: {},
|
topPeers: {},
|
||||||
|
|
||||||
topInlineBots: {},
|
topInlineBots: {},
|
||||||
|
topBotApps: {},
|
||||||
|
|
||||||
activeSessions: {
|
activeSessions: {
|
||||||
byHash: {},
|
byHash: {},
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateGlobalSearchFetchingStatus<T extends GlobalState>(
|
export function updateGlobalSearchFetchingStatus<T extends GlobalState>(
|
||||||
global: T, newState: { chats?: boolean; messages?: boolean },
|
global: T, newState: { chats?: boolean; messages?: boolean; botApps?: boolean },
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
return updateGlobalSearch(global, {
|
return updateGlobalSearch(global, {
|
||||||
|
|||||||
@ -376,6 +376,7 @@ export type TabState = {
|
|||||||
fetchingStatus?: {
|
fetchingStatus?: {
|
||||||
chats?: boolean;
|
chats?: boolean;
|
||||||
messages?: boolean;
|
messages?: boolean;
|
||||||
|
botApps?: boolean;
|
||||||
};
|
};
|
||||||
isClosing?: boolean;
|
isClosing?: boolean;
|
||||||
localResults?: {
|
localResults?: {
|
||||||
@ -384,6 +385,10 @@ export type TabState = {
|
|||||||
globalResults?: {
|
globalResults?: {
|
||||||
peerIds?: string[];
|
peerIds?: string[];
|
||||||
};
|
};
|
||||||
|
popularBotApps?: {
|
||||||
|
peerIds: string[];
|
||||||
|
nextOffset?: string;
|
||||||
|
};
|
||||||
resultsByType?: Partial<Record<ApiGlobalMessageSearchType, {
|
resultsByType?: Partial<Record<ApiGlobalMessageSearchType, {
|
||||||
totalCount?: number;
|
totalCount?: number;
|
||||||
nextOffsetId?: number;
|
nextOffsetId?: number;
|
||||||
@ -1120,6 +1125,11 @@ export type GlobalState = {
|
|||||||
lastRequestedAt?: number;
|
lastRequestedAt?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
topBotApps: {
|
||||||
|
userIds?: string[];
|
||||||
|
lastRequestedAt?: number;
|
||||||
|
};
|
||||||
|
|
||||||
activeSessions: {
|
activeSessions: {
|
||||||
byHash: Record<string, ApiSession>;
|
byHash: Record<string, ApiSession>;
|
||||||
orderedHashes: string[];
|
orderedHashes: string[];
|
||||||
@ -1491,6 +1501,7 @@ export interface ActionPayloads {
|
|||||||
searchMessagesGlobal: {
|
searchMessagesGlobal: {
|
||||||
type: ApiGlobalMessageSearchType;
|
type: ApiGlobalMessageSearchType;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
searchPopularBotApps: WithTabId | undefined;
|
||||||
addRecentlyFoundChatId: {
|
addRecentlyFoundChatId: {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
@ -2800,6 +2811,7 @@ export interface ActionPayloads {
|
|||||||
chatId?: string;
|
chatId?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
loadTopInlineBots: undefined;
|
loadTopInlineBots: undefined;
|
||||||
|
loadTopBotApps: undefined;
|
||||||
queryInlineBot: {
|
queryInlineBot: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
|||||||
@ -1617,6 +1617,7 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla
|
|||||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||||
|
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||||
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||||
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||||
|
|||||||
@ -228,6 +228,7 @@
|
|||||||
"bots.canSendMessage",
|
"bots.canSendMessage",
|
||||||
"bots.allowSendMessage",
|
"bots.allowSendMessage",
|
||||||
"bots.invokeWebViewCustomMethod",
|
"bots.invokeWebViewCustomMethod",
|
||||||
|
"bots.getPopularAppBots",
|
||||||
"bots.setBotInfo",
|
"bots.setBotInfo",
|
||||||
"payments.getPaymentForm",
|
"payments.getPaymentForm",
|
||||||
"payments.getPaymentReceipt",
|
"payments.getPaymentReceipt",
|
||||||
|
|||||||
@ -291,6 +291,7 @@ export enum LeftColumnContent {
|
|||||||
export enum GlobalSearchContent {
|
export enum GlobalSearchContent {
|
||||||
ChatList,
|
ChatList,
|
||||||
ChannelList,
|
ChannelList,
|
||||||
|
BotApps,
|
||||||
Media,
|
Media,
|
||||||
Links,
|
Links,
|
||||||
Files,
|
Files,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user