Global Search: Support search public posts (#6114)
This commit is contained in:
parent
6204fadc0f
commit
09323114d5
10
CLAUDE.md
10
CLAUDE.md
@ -51,6 +51,16 @@ You are an expert in TypeScript, JavaScript, HTML, SCSS and Teact with deep expe
|
||||
style={{ transform: `translateX(${value}%)` }}
|
||||
style={{ '--custom-prop': value } as React.CSSProperties}
|
||||
```
|
||||
- **IMPORTANT: Font weights in CSS** - Always use existing CSS variables for font-weight. Never use numeric values or custom values.
|
||||
```scss
|
||||
// ✅ CORRECT
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-bold);
|
||||
|
||||
// ❌ WRONG
|
||||
font-weight: 600;
|
||||
font-weight: bold;
|
||||
```
|
||||
|
||||
- **Localization & Text Rules:**
|
||||
- **ALWAYS** use `lang()` for all text content - never hardcode strings.
|
||||
|
||||
@ -22,6 +22,7 @@ import type {
|
||||
ApiPreparedInlineMessage,
|
||||
ApiQuickReply,
|
||||
ApiReplyInfo,
|
||||
ApiSearchPostsFlood,
|
||||
ApiSponsoredMessage,
|
||||
ApiSticker,
|
||||
ApiStory,
|
||||
@ -820,3 +821,17 @@ export function buildPreparedInlineMessage(
|
||||
cacheTime: result.cacheTime,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiSearchPostsFlood(
|
||||
searchFlood: GramJs.SearchPostsFlood,
|
||||
query?: string,
|
||||
): ApiSearchPostsFlood {
|
||||
return {
|
||||
query,
|
||||
queryIsFree: searchFlood.queryIsFree,
|
||||
totalDaily: searchFlood.totalDaily,
|
||||
remains: searchFlood.remains,
|
||||
waitTill: searchFlood.waitTill,
|
||||
starsAmount: searchFlood.starsAmount.toJSNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -549,7 +549,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
||||
const {
|
||||
date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
|
||||
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages,
|
||||
stargiftResale,
|
||||
stargiftResale, postsSearch,
|
||||
} = transaction;
|
||||
|
||||
if (photo) {
|
||||
@ -588,6 +588,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
||||
isGiftUpgrade: stargiftUpgrade,
|
||||
isGiftResale: stargiftResale,
|
||||
paidMessages,
|
||||
isPostsSearch: postsSearch,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ import type {
|
||||
ApiPeer,
|
||||
ApiPoll,
|
||||
ApiReaction,
|
||||
ApiSearchPostsFlood,
|
||||
ApiSendMessageAction,
|
||||
ApiTodoItem,
|
||||
ApiUser,
|
||||
@ -66,6 +67,7 @@ import {
|
||||
buildApiMessage,
|
||||
buildApiQuickReply,
|
||||
buildApiReportResult,
|
||||
buildApiSearchPostsFlood,
|
||||
buildApiSponsoredMessage,
|
||||
buildApiThreadInfo,
|
||||
buildLocalForwardedMessage,
|
||||
@ -128,6 +130,7 @@ type SearchResults = {
|
||||
nextOffsetRate?: number;
|
||||
nextOffsetPeerId?: string;
|
||||
nextOffsetId?: number;
|
||||
searchFlood?: ApiSearchPostsFlood;
|
||||
};
|
||||
|
||||
export async function fetchMessages({
|
||||
@ -1537,6 +1540,16 @@ export async function searchMessagesGlobal({
|
||||
minDate?: number;
|
||||
maxDate?: number;
|
||||
}): Promise<SearchResults | undefined> {
|
||||
if (type === 'publicPosts') {
|
||||
return searchPublicPosts({
|
||||
query,
|
||||
offsetRate,
|
||||
offsetPeer,
|
||||
offsetId,
|
||||
limit,
|
||||
});
|
||||
}
|
||||
|
||||
let filter;
|
||||
switch (type) {
|
||||
case 'media':
|
||||
@ -1613,22 +1626,32 @@ export async function searchMessagesGlobal({
|
||||
};
|
||||
}
|
||||
|
||||
export async function searchHashtagPosts({
|
||||
hashtag, offsetRate, offsetPeer, offsetId, limit,
|
||||
export async function searchPublicPosts({
|
||||
hashtag, query, offsetRate, offsetPeer, offsetId, limit,
|
||||
}: {
|
||||
hashtag: string;
|
||||
hashtag?: string;
|
||||
query?: string;
|
||||
offsetRate?: number;
|
||||
offsetPeer?: ApiPeer;
|
||||
offsetId?: number;
|
||||
limit?: number;
|
||||
}): Promise<SearchResults | undefined> {
|
||||
const peer = (offsetPeer && buildInputPeer(offsetPeer.id, offsetPeer.accessHash)) || new GramJs.InputPeerEmpty();
|
||||
|
||||
const resultFlood = await checkSearchPostsFlood(query);
|
||||
|
||||
if (!resultFlood) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = await invokeRequest(new GramJs.channels.SearchPosts({
|
||||
hashtag,
|
||||
query,
|
||||
offsetRate: offsetRate ?? DEFAULT_PRIMITIVES.INT,
|
||||
offsetId: offsetId ?? DEFAULT_PRIMITIVES.INT,
|
||||
offsetPeer: peer,
|
||||
limit: limit ?? DEFAULT_PRIMITIVES.INT,
|
||||
allowPaidStars: BigInt(resultFlood.starsAmount),
|
||||
}));
|
||||
|
||||
if (!result || result instanceof GramJs.messages.MessagesNotModified) {
|
||||
@ -1650,6 +1673,10 @@ export async function searchHashtagPosts({
|
||||
const nextOffsetRate = 'nextRate' in result && result.nextRate ? result.nextRate : undefined;
|
||||
const nextOffsetId = lastMessage?.id;
|
||||
|
||||
const searchFlood = result instanceof GramJs.messages.MessagesSlice && result.searchFlood
|
||||
? buildApiSearchPostsFlood(result.searchFlood, query)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
messages,
|
||||
userStatusesById,
|
||||
@ -1657,9 +1684,20 @@ export async function searchHashtagPosts({
|
||||
nextOffsetRate,
|
||||
nextOffsetPeerId,
|
||||
nextOffsetId,
|
||||
searchFlood,
|
||||
};
|
||||
}
|
||||
|
||||
export async function checkSearchPostsFlood(query?: string) {
|
||||
const result = await invokeRequest(new GramJs.channels.CheckSearchPostsFlood({ query }));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildApiSearchPostsFlood(result, query);
|
||||
}
|
||||
|
||||
export async function fetchWebPagePreview({
|
||||
text,
|
||||
}: {
|
||||
|
||||
@ -919,7 +919,8 @@ export type ApiTranscription = {
|
||||
};
|
||||
|
||||
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto';
|
||||
export type ApiGlobalMessageSearchType = 'text' | 'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice';
|
||||
export type ApiGlobalMessageSearchType = 'text' |
|
||||
'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'publicPosts';
|
||||
export type ApiMessageSearchContext = 'all' | 'users' | 'groups' | 'channels';
|
||||
|
||||
export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse'
|
||||
@ -992,6 +993,15 @@ export type ApiPreparedInlineMessage = {
|
||||
cacheTime: number;
|
||||
};
|
||||
|
||||
export type ApiSearchPostsFlood = {
|
||||
query?: string;
|
||||
queryIsFree?: boolean;
|
||||
totalDaily: number;
|
||||
remains: number;
|
||||
waitTill?: number;
|
||||
starsAmount: number;
|
||||
};
|
||||
|
||||
export const MAIN_THREAD_ID = -1;
|
||||
|
||||
// `Symbol` can not be transferred from worker
|
||||
|
||||
@ -239,6 +239,7 @@ export interface ApiStarsTransaction {
|
||||
isGiftUpgrade?: true;
|
||||
isGiftResale?: true;
|
||||
paidMessages?: number;
|
||||
isPostsSearch?: true;
|
||||
}
|
||||
|
||||
export interface ApiStarsSubscription {
|
||||
|
||||
@ -1639,6 +1639,7 @@
|
||||
"SearchTabMusic" = "Music";
|
||||
"SearchTabVoice" = "Voice";
|
||||
"SearchTabMessages" = "Messages";
|
||||
"SearchTabPublicPosts" = "Posts";
|
||||
"StarsTransactionsAll" = "All Transactions";
|
||||
"StarsTransactionsIncoming" = "Incoming";
|
||||
"StarsTransactionsOutgoing" = "Outgoing";
|
||||
@ -1657,6 +1658,7 @@
|
||||
"ProfileTabSharedGroups" = "Groups";
|
||||
"ProfileTabSimilarChannels" = "Similar Channels";
|
||||
"ProfileTabSimilarBots" = "Similar Bots";
|
||||
"ProfileTabPublicPosts" = "Public Posts";
|
||||
"ActionUnsupportedTitle" = "Action not supported yet";
|
||||
"ActionUnsupportedDescription" = "Please use one of our apps to complete this action.";
|
||||
"LocationPermissionText" = "**{name}** requests access to set your **location**. You will be able to revoke this access in the profile page of **{name}**.";
|
||||
@ -2171,4 +2173,21 @@
|
||||
"PriceChanged" = "Price Changed";
|
||||
"PayNewPrice" = "Pay New Price";
|
||||
"PriceChangedText" = "The price has already changed from **{originalAmount}** to **{newAmount}**. Do you want to pay the new price?";
|
||||
"GlobalSearch" = "Global Search";
|
||||
"DescriptionPublicPostsSearch" = "Type a keyword to search for posts from public channels.";
|
||||
"ButtonSearchPublicPosts" = "Search {query}";
|
||||
"RemainingPublicPostsSearch_one" = "{count} free search remaining today.";
|
||||
"RemainingPublicPostsSearch_other" = "{count} free searches remaining today.";
|
||||
"PublicPosts" = "Public Posts";
|
||||
"PublicPostsLimitReached" = "Limit Reached";
|
||||
"HintPublicPostsSearchQuota_one" = "You can make up to {count} search query per day.";
|
||||
"HintPublicPostsSearchQuota_other" = "You can make up to {count} search queries per day.";
|
||||
"PublicPostsSearchForStars" = "Search for {stars}";
|
||||
"UnlockTimerPublicPostsSearch" = "free search unlocks in {time}";
|
||||
"PublicPostsPremiumFeatureDescription" = "Type a keyword to search for posts from public channels.";
|
||||
"PublicPostsPremiumFeatureSubtitle" = "Global search is a Premium feature.";
|
||||
"PublicPostsSubscribeToPremium" = "Subscribe to Premium";
|
||||
"NotificationPaidExtraSearch" = "{stars} spent on extra search.";
|
||||
"PostsSearchTransaction" = "Posts Search";
|
||||
|
||||
|
||||
|
||||
1
src/assets/tgs-previews/DuckNothingFound.svg
Normal file
1
src/assets/tgs-previews/DuckNothingFound.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.7 KiB |
1
src/assets/tgs-previews/Search.svg
Normal file
1
src/assets/tgs-previews/Search.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/tgs/DuckNothingFound.tgs
Normal file
BIN
src/assets/tgs/DuckNothingFound.tgs
Normal file
Binary file not shown.
BIN
src/assets/tgs/Search.tgs
Normal file
BIN
src/assets/tgs/Search.tgs
Normal file
Binary file not shown.
@ -4,10 +4,15 @@
|
||||
justify-content: center;
|
||||
color: var(--color-text-meta);
|
||||
|
||||
&.with-description {
|
||||
&.with-description,
|
||||
&.with-sticker {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sticker {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.AnimatedSticker {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@ -2,26 +2,45 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import { memo } from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { LOCAL_TGS_PREVIEW_URLS, LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
||||
|
||||
import AnimatedIconWithPreview from './AnimatedIconWithPreview';
|
||||
|
||||
import './NothingFound.scss';
|
||||
|
||||
interface OwnProps {
|
||||
text?: string;
|
||||
description?: string;
|
||||
withSticker?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_TEXT = 'Nothing found.';
|
||||
|
||||
const NothingFound: FC<OwnProps> = ({ text = DEFAULT_TEXT, description }) => {
|
||||
const NothingFound: FC<OwnProps> = ({ text = DEFAULT_TEXT, description, withSticker }) => {
|
||||
const lang = useOldLang();
|
||||
const { transitionClassNames } = useShowTransitionDeprecated(true);
|
||||
|
||||
return (
|
||||
<div className={buildClassName('NothingFound', transitionClassNames, description && 'with-description')}>
|
||||
<div className={buildClassName(
|
||||
'NothingFound',
|
||||
transitionClassNames,
|
||||
description && 'with-description',
|
||||
withSticker && 'with-sticker')}
|
||||
>
|
||||
{withSticker && (
|
||||
<AnimatedIconWithPreview
|
||||
className="sticker"
|
||||
size={120}
|
||||
tgsUrl={LOCAL_TGS_URLS.DuckNothingFound}
|
||||
previewUrl={LOCAL_TGS_PREVIEW_URLS.DuckNothingFound}
|
||||
nonInteractive
|
||||
noLoop={false}
|
||||
/>
|
||||
)}
|
||||
{text}
|
||||
{description && <p className="description">{renderText(lang(description), ['br'])}</p>}
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,7 @@ import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
||||
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
||||
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
||||
import Diamond from '../../../assets/tgs/Diamond.tgs';
|
||||
import DuckNothingFound from '../../../assets/tgs/DuckNothingFound.tgs';
|
||||
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
||||
import Fragment from '../../../assets/tgs/general/Fragment.tgs';
|
||||
import Mention from '../../../assets/tgs/general/Mention.tgs';
|
||||
@ -22,6 +23,7 @@ import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs
|
||||
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
||||
import ReadTime from '../../../assets/tgs/ReadTime.tgs';
|
||||
import Report from '../../../assets/tgs/Report.tgs';
|
||||
import Search from '../../../assets/tgs/Search.tgs';
|
||||
import SearchingDuck from '../../../assets/tgs/SearchingDuck.tgs';
|
||||
import Congratulations from '../../../assets/tgs/settings/Congratulations.tgs';
|
||||
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.tgs';
|
||||
@ -33,6 +35,13 @@ import Lock from '../../../assets/tgs/settings/Lock.tgs';
|
||||
import StarReaction from '../../../assets/tgs/stars/StarReaction.tgs';
|
||||
import StarReactionEffect from '../../../assets/tgs/stars/StarReactionEffect.tgs';
|
||||
import Unlock from '../../../assets/tgs/Unlock.tgs';
|
||||
import DuckNothingFoundPreview from '../../../assets/tgs-previews/DuckNothingFound.svg';
|
||||
import SearchPreview from '../../../assets/tgs-previews/Search.svg';
|
||||
|
||||
export const LOCAL_TGS_PREVIEW_URLS = {
|
||||
DuckNothingFound: DuckNothingFoundPreview,
|
||||
Search: SearchPreview,
|
||||
};
|
||||
|
||||
export const LOCAL_TGS_URLS = {
|
||||
MonkeyIdle,
|
||||
@ -70,4 +79,6 @@ export const LOCAL_TGS_URLS = {
|
||||
SearchingDuck,
|
||||
BannedDuck,
|
||||
Diamond,
|
||||
Search,
|
||||
DuckNothingFound,
|
||||
};
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV, LAYERS_ANIMATION_NAME,
|
||||
} from '../../util/browser/windowEnvironment';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import { debounce } from '../../util/schedulers';
|
||||
import { captureControlledSwipe } from '../../util/swipeController';
|
||||
|
||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||
@ -110,6 +111,10 @@ function LeftColumn({
|
||||
const [contactsFilter, setContactsFilter] = useState<string>('');
|
||||
const [foldersState, foldersDispatch] = useFoldersReducer();
|
||||
|
||||
const debouncedSetGlobalSearchQuery = useMemo(() => debounce((query: string) => {
|
||||
setGlobalSearchQuery({ query });
|
||||
}, 200, false, true), [setGlobalSearchQuery]);
|
||||
|
||||
// Used to reset child components in background.
|
||||
const [lastResetTime, setLastResetTime] = useState<number>(0);
|
||||
|
||||
@ -375,7 +380,7 @@ function LeftColumn({
|
||||
openLeftColumnContent({ contentKey: LeftColumnContent.GlobalSearch });
|
||||
|
||||
if (query !== searchQuery) {
|
||||
setGlobalSearchQuery({ query });
|
||||
debouncedSetGlobalSearchQuery(query);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -114,6 +114,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
setGlobalSearchChatId,
|
||||
lockScreen,
|
||||
openSettingsScreen,
|
||||
searchMessagesGlobal,
|
||||
} = getActions();
|
||||
|
||||
const oldLang = useOldLang();
|
||||
@ -193,6 +194,15 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
lockScreen();
|
||||
});
|
||||
|
||||
const handleSearchEnter = useLastCallback(() => {
|
||||
if (searchQuery && content === LeftColumnContent.GlobalSearch) {
|
||||
searchMessagesGlobal({
|
||||
type: 'publicPosts',
|
||||
shouldResetResultsByType: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const isSearchRelevant = Boolean(globalSearchChatId)
|
||||
|| content === LeftColumnContent.GlobalSearch
|
||||
|| content === LeftColumnContent.Contacts;
|
||||
@ -295,6 +305,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
onReset={onReset}
|
||||
onFocus={handleSearchFocus}
|
||||
onSpinnerClick={connectionStatusPosition === 'minimized' ? toggleConnectionStatus : undefined}
|
||||
onEnter={handleSearchEnter}
|
||||
>
|
||||
{searchContent}
|
||||
<StoryToggler
|
||||
@ -344,7 +355,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
isLoading: fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false,
|
||||
isLoading: fetchingStatus ? Boolean(fetchingStatus.chats
|
||||
|| fetchingStatus.messages || fetchingStatus.publicPosts) : false,
|
||||
globalSearchChatId: chatId,
|
||||
searchDate: minDate,
|
||||
theme: selectTheme(global),
|
||||
|
||||
@ -134,6 +134,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
withSticker
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
|
||||
@ -89,6 +89,7 @@ const BotAppResults: FC<OwnProps & StateProps> = ({
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && !filteredFoundIds?.length && (
|
||||
<NothingFound
|
||||
withSticker
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
|
||||
@ -132,6 +132,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{nothingFound && (
|
||||
<NothingFound
|
||||
withSticker
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
|
||||
@ -374,6 +374,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
{nothingFound && (
|
||||
<NothingFound
|
||||
withSticker
|
||||
text={oldLang('ChatList.Search.NoResults')}
|
||||
description={oldLang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
|
||||
@ -139,6 +139,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
withSticker
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@ -27,6 +28,7 @@ import ChatResults from './ChatResults';
|
||||
import FileResults from './FileResults';
|
||||
import LinkResults from './LinkResults';
|
||||
import MediaResults from './MediaResults';
|
||||
import PublicPostsResults from './PublicPostsResults';
|
||||
|
||||
import './LeftSearch.scss';
|
||||
|
||||
@ -51,6 +53,7 @@ const TABS: TabInfo[] = [
|
||||
{ type: GlobalSearchContent.ChatList, key: 'SearchTabChats' },
|
||||
{ type: GlobalSearchContent.ChannelList, key: 'SearchTabChannels' },
|
||||
{ type: GlobalSearchContent.BotApps, key: 'SearchTabApps' },
|
||||
{ type: GlobalSearchContent.PublicPosts, key: 'SearchTabPublicPosts' },
|
||||
{ type: GlobalSearchContent.Media, key: 'SearchTabMedia' },
|
||||
{ type: GlobalSearchContent.Links, key: 'SearchTabLinks' },
|
||||
{ type: GlobalSearchContent.Files, key: 'SearchTabFiles' },
|
||||
@ -74,12 +77,19 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
setGlobalSearchContent,
|
||||
setGlobalSearchDate,
|
||||
checkSearchPostsFlood,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const [activeTab, setActiveTab] = useState(currentContent);
|
||||
const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
checkSearchPostsFlood({});
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
const tabs = useMemo(() => {
|
||||
const arr = chatId ? CHAT_TABS : TABS;
|
||||
return arr.map((tab) => ({
|
||||
@ -166,6 +176,13 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
);
|
||||
case GlobalSearchContent.PublicPosts:
|
||||
return (
|
||||
<PublicPostsResults
|
||||
key="publicPosts"
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -132,6 +132,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
withSticker
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
|
||||
@ -133,6 +133,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
withSticker
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
|
||||
163
src/components/left/search/PublicPostsResults.tsx
Normal file
163
src/components/left/search/PublicPostsResults.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
import { getGlobal } from '../../../global';
|
||||
|
||||
import type { ApiMessage, ApiSearchPostsFlood } from '../../../api/types';
|
||||
import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import { parseSearchResultKey, type SearchResultKey } from '../../../util/keys/searchResultKey';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Transition from '../../ui/Transition';
|
||||
import ChatMessage from './ChatMessage';
|
||||
import PublicPostsSearchLauncher from './PublicPostsSearchLauncher.tsx';
|
||||
|
||||
export type OwnProps = {
|
||||
searchQuery?: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
foundIds?: SearchResultKey[];
|
||||
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
|
||||
searchFlood?: ApiSearchPostsFlood;
|
||||
shouldShowSearchLauncher?: boolean;
|
||||
isNothingFound?: boolean;
|
||||
};
|
||||
|
||||
const runThrottled = throttle((cb) => cb(), 500, true);
|
||||
|
||||
const PublicPostsResults = ({
|
||||
searchQuery,
|
||||
foundIds,
|
||||
globalMessagesByChatId,
|
||||
searchFlood,
|
||||
shouldShowSearchLauncher,
|
||||
isNothingFound,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { searchMessagesGlobal } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const handleSearch = useLastCallback(() => {
|
||||
if (!searchQuery) return;
|
||||
|
||||
searchMessagesGlobal({
|
||||
type: 'publicPosts',
|
||||
shouldResetResultsByType: true,
|
||||
});
|
||||
});
|
||||
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (direction === LoadMoreDirection.Backwards) {
|
||||
runThrottled(() => {
|
||||
searchMessagesGlobal({
|
||||
type: 'publicPosts',
|
||||
});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const foundMessages = useMemo(() => {
|
||||
if (!foundIds || foundIds.length === 0) {
|
||||
return MEMO_EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
return foundIds
|
||||
.map((id) => {
|
||||
const [chatId, messageId] = parseSearchResultKey(id);
|
||||
return globalMessagesByChatId?.[chatId]?.byId[messageId];
|
||||
})
|
||||
.filter(Boolean);
|
||||
}, [foundIds, globalMessagesByChatId]);
|
||||
|
||||
function renderFoundMessage(message: ApiMessage) {
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
|
||||
const text = renderMessageSummary(oldLang, message);
|
||||
const chat = chatsById[message.chatId];
|
||||
|
||||
if (!text || !chat) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatMessage
|
||||
key={`${message.chatId}-${message.id}`}
|
||||
chatId={message.chatId}
|
||||
message={message}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'}
|
||||
activeKey={shouldShowSearchLauncher ? 0 : 1}
|
||||
>
|
||||
{shouldShowSearchLauncher ? (
|
||||
<PublicPostsSearchLauncher
|
||||
searchQuery={searchQuery}
|
||||
searchFlood={searchFlood}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
) : (
|
||||
<div className="LeftSearch--content">
|
||||
<InfiniteScroll
|
||||
key={searchQuery}
|
||||
className="search-content custom-scroll chat-list"
|
||||
items={foundMessages}
|
||||
onLoadMore={handleLoadMore}
|
||||
noFastList
|
||||
>
|
||||
{isNothingFound && (
|
||||
<NothingFound
|
||||
text={oldLang('ChatList.Search.NoResults')}
|
||||
description={oldLang('ChatList.Search.NoResultsDescription')}
|
||||
withSticker
|
||||
/>
|
||||
)}
|
||||
{Boolean(foundMessages.length) && (
|
||||
<div className="pb-2">
|
||||
<h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>
|
||||
{lang('PublicPosts')}
|
||||
</h3>
|
||||
{foundMessages.map(renderFoundMessage)}
|
||||
</div>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { messages: { byChatId: globalMessagesByChatId } } = global;
|
||||
const { resultsByType, searchFlood } = selectTabState(global).globalSearch;
|
||||
|
||||
const publicPostsResult = resultsByType?.publicPosts;
|
||||
const { foundIds } = publicPostsResult || {};
|
||||
const shouldShowSearchLauncher = !publicPostsResult;
|
||||
const isNothingFound = publicPostsResult && !foundIds?.length;
|
||||
|
||||
return {
|
||||
foundIds,
|
||||
globalMessagesByChatId,
|
||||
searchFlood,
|
||||
shouldShowSearchLauncher,
|
||||
isNothingFound,
|
||||
};
|
||||
},
|
||||
)(PublicPostsResults));
|
||||
129
src/components/left/search/PublicPostsSearchLauncher.module.scss
Normal file
129
src/components/left/search/PublicPostsSearchLauncher.module.scss
Normal file
@ -0,0 +1,129 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
height: 100%;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
max-width: 20rem;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.searchButtonContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sticker {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.3125rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.remainingSearches {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.searchQuery {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
|
||||
max-width: 10rem;
|
||||
margin-inline-start: 0.25rem;
|
||||
|
||||
color: var(--color-white);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: bottom;
|
||||
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.limitTitle {
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.limitDescription {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.375rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.paidSearchButton {
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.freeSearchUnlock {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.premiumTitle {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.premiumDescription {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.subscribePremiumButton {
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.premiumSubtitle {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
237
src/components/left/search/PublicPostsSearchLauncher.tsx
Normal file
237
src/components/left/search/PublicPostsSearchLauncher.tsx
Normal file
@ -0,0 +1,237 @@
|
||||
import { memo, useEffect } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiSearchPostsFlood } from '../../../api/types';
|
||||
|
||||
import {
|
||||
PUBLIC_POSTS_SEARCH_DEFAULT_STARS_AMOUNT,
|
||||
PUBLIC_POSTS_SEARCH_DEFAULT_TOTAL_DAILY,
|
||||
} from '../../../config';
|
||||
import { selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { LOCAL_TGS_PREVIEW_URLS, LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
|
||||
import { useTransitionActiveKey } from '../../../hooks/animations/useTransitionActiveKey';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import TextTimer from '../../ui/TextTimer';
|
||||
import Transition from '../../ui/Transition';
|
||||
|
||||
import styles from './PublicPostsSearchLauncher.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
searchQuery?: string;
|
||||
searchFlood?: ApiSearchPostsFlood;
|
||||
onSearch: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
isCurrentUserPremium?: boolean;
|
||||
starsBalance: number;
|
||||
};
|
||||
|
||||
const WAIT_DELAY = 2;
|
||||
|
||||
const PublicPostsSearchLauncher = ({
|
||||
searchQuery,
|
||||
searchFlood,
|
||||
onSearch,
|
||||
isCurrentUserPremium,
|
||||
starsBalance,
|
||||
}: OwnProps & StateProps) => {
|
||||
const lang = useLang();
|
||||
const queryIsFree = searchFlood?.queryIsFree;
|
||||
const queryFromFlood = searchFlood?.query;
|
||||
|
||||
const searchButtonActiveKey = useTransitionActiveKey([searchQuery?.slice(0, 18).trimEnd()]);
|
||||
|
||||
const handleSearchClick = useLastCallback(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (queryIsFree && searchQuery && queryFromFlood === searchQuery) {
|
||||
onSearch();
|
||||
}
|
||||
}, [queryIsFree, searchQuery, queryFromFlood, onSearch]);
|
||||
|
||||
const handlePaidSearchClick = useLastCallback(() => {
|
||||
const starsAmount = searchFlood?.starsAmount || 0;
|
||||
const currentBalance = starsBalance;
|
||||
|
||||
if (currentBalance < starsAmount) {
|
||||
openStarsBalanceModal({
|
||||
topup: {
|
||||
balanceNeeded: starsAmount,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
onSearch();
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
checkSearchPostsFlood,
|
||||
openPremiumModal,
|
||||
openStarsBalanceModal,
|
||||
} = getActions();
|
||||
|
||||
const onCheckFlood = useLastCallback(() => {
|
||||
checkSearchPostsFlood({});
|
||||
});
|
||||
|
||||
const handleSubscribePremiumClick = useLastCallback(() => {
|
||||
openPremiumModal();
|
||||
});
|
||||
|
||||
const renderLimitReached = () => {
|
||||
const waitTill = searchFlood?.waitTill;
|
||||
const starsAmount = searchFlood?.starsAmount || PUBLIC_POSTS_SEARCH_DEFAULT_STARS_AMOUNT;
|
||||
const totalDaily = searchFlood?.totalDaily || PUBLIC_POSTS_SEARCH_DEFAULT_TOTAL_DAILY;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<AnimatedIconWithPreview
|
||||
className={styles.sticker}
|
||||
size={120}
|
||||
tgsUrl={LOCAL_TGS_URLS.Search}
|
||||
previewUrl={LOCAL_TGS_PREVIEW_URLS.Search}
|
||||
nonInteractive
|
||||
noLoop={false}
|
||||
/>
|
||||
<div className={styles.limitTitle}>
|
||||
{lang('PublicPostsLimitReached')}
|
||||
</div>
|
||||
<div className={styles.limitDescription}>
|
||||
{lang('HintPublicPostsSearchQuota', { count: totalDaily }, { pluralValue: totalDaily })}
|
||||
</div>
|
||||
<Button
|
||||
className={styles.paidSearchButton}
|
||||
color="primary"
|
||||
size="smaller"
|
||||
disabled={!searchQuery}
|
||||
noForcedUpperCase
|
||||
onClick={handlePaidSearchClick}
|
||||
>
|
||||
{lang('PublicPostsSearchForStars', {
|
||||
stars: formatStarsAsIcon(lang, starsAmount, { asFont: true }),
|
||||
}, { withNodes: true })}
|
||||
</Button>
|
||||
{Boolean(waitTill) && (
|
||||
<div className={styles.freeSearchUnlock}>
|
||||
<TextTimer
|
||||
langKey="UnlockTimerPublicPostsSearch"
|
||||
endsAt={waitTill + WAIT_DELAY}
|
||||
onEnd={onCheckFlood}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSearchButton = () => {
|
||||
const remainingSearches = searchFlood?.remains || 0;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<AnimatedIconWithPreview
|
||||
className={styles.sticker}
|
||||
size={120}
|
||||
tgsUrl={LOCAL_TGS_URLS.Search}
|
||||
previewUrl={LOCAL_TGS_PREVIEW_URLS.Search}
|
||||
nonInteractive
|
||||
noLoop={false}
|
||||
/>
|
||||
<div className={styles.title}>
|
||||
{lang('GlobalSearch')}
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
{lang('DescriptionPublicPostsSearch')}
|
||||
</div>
|
||||
<Button
|
||||
className={styles.searchButton}
|
||||
color="primary"
|
||||
size="smaller"
|
||||
noForcedUpperCase
|
||||
disabled={!searchQuery}
|
||||
onClick={handleSearchClick}
|
||||
>
|
||||
<Transition
|
||||
name="fade"
|
||||
activeKey={searchButtonActiveKey}
|
||||
>
|
||||
<div className={styles.searchButtonContent}>
|
||||
<Icon name="search" className={styles.searchIcon} />
|
||||
{lang('ButtonSearchPublicPosts', {
|
||||
query: searchQuery ? <span className={styles.searchQuery}>{searchQuery}</span> : '',
|
||||
}, { withNodes: true })}
|
||||
{searchQuery && <Icon name="next" className={styles.nextIcon} />}
|
||||
</div>
|
||||
</Transition>
|
||||
</Button>
|
||||
<div className={styles.remainingSearches}>
|
||||
{lang('RemainingPublicPostsSearch', { count: remainingSearches }, { pluralValue: remainingSearches })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderPremiumRequired = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.premiumTitle}>
|
||||
{lang('GlobalSearch')}
|
||||
</div>
|
||||
<div className={styles.premiumDescription}>
|
||||
{lang('PublicPostsPremiumFeatureDescription')}
|
||||
</div>
|
||||
<Button
|
||||
className={styles.subscribePremiumButton}
|
||||
color="primary"
|
||||
size="smaller"
|
||||
noForcedUpperCase
|
||||
onClick={handleSubscribePremiumClick}
|
||||
>
|
||||
{lang('PublicPostsSubscribeToPremium')}
|
||||
</Button>
|
||||
<div className={styles.premiumSubtitle}>
|
||||
{lang('PublicPostsPremiumFeatureSubtitle')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (!isCurrentUserPremium) {
|
||||
return renderPremiumRequired();
|
||||
}
|
||||
|
||||
const serverTime = getServerTime();
|
||||
const shouldRenderPaidScreen = searchFlood?.remains === 0
|
||||
|| (searchFlood?.waitTill && searchFlood.waitTill > serverTime);
|
||||
|
||||
return (
|
||||
<Transition
|
||||
name="fade"
|
||||
activeKey={shouldRenderPaidScreen ? 0 : 1}
|
||||
>
|
||||
{shouldRenderPaidScreen ? renderLimitReached() : renderSearchButton()}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => ({
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
starsBalance: global.stars?.balance?.amount || 0,
|
||||
}))(PublicPostsSearchLauncher));
|
||||
@ -2,7 +2,7 @@ import type { ApiStarsAmount, ApiStarsTransaction, ApiTypeCurrencyAmount } from
|
||||
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
||||
import { buildStarsTransactionCustomPeer } from '../../../../global/helpers/payments';
|
||||
import { buildStarsTransactionCustomPeer, shouldUseCustomPeer } from '../../../../global/helpers/payments';
|
||||
import {
|
||||
type LangFn,
|
||||
} from '../../../../util/localization';
|
||||
@ -25,6 +25,9 @@ export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transactio
|
||||
? lang('StarGiftSaleTransaction')
|
||||
: lang('StarGiftPurchaseTransaction');
|
||||
}
|
||||
if (transaction.isPostsSearch) {
|
||||
return lang('PostsSearchTransaction');
|
||||
}
|
||||
|
||||
if (transaction.starRefCommision) {
|
||||
return oldLang('StarTransactionCommission', formatPercent(transaction.starRefCommision));
|
||||
@ -45,8 +48,8 @@ export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transactio
|
||||
return isNegativeAmount(transaction.amount) ? oldLang('Gift2TransactionSent') : oldLang('Gift2ConvertedTitle');
|
||||
}
|
||||
|
||||
const customPeer = (transaction.peer && transaction.peer.type !== 'peer'
|
||||
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
||||
const customPeer = (transaction.peer && shouldUseCustomPeer(transaction)
|
||||
&& buildStarsTransactionCustomPeer(transaction)) || undefined;
|
||||
|
||||
if (customPeer) return customPeer.title || oldLang(customPeer.titleKey!);
|
||||
|
||||
|
||||
@ -9,7 +9,9 @@ import type { GlobalState } from '../../../../global/types';
|
||||
import type { CustomPeer } from '../../../../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
||||
import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments';
|
||||
import { buildStarsTransactionCustomPeer,
|
||||
formatStarsTransactionAmount,
|
||||
shouldUseCustomPeer } from '../../../../global/helpers/payments';
|
||||
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||
import { selectPeer } from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
@ -71,14 +73,11 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
||||
let status: string | undefined;
|
||||
let avatarPeer: ApiPeer | CustomPeer | undefined;
|
||||
|
||||
if (transaction.peer.type === 'peer') {
|
||||
if (!shouldUseCustomPeer(transaction)) {
|
||||
description = peer && getPeerTitle(oldLang, peer);
|
||||
avatarPeer = peer || CUSTOM_PEER_PREMIUM;
|
||||
} else {
|
||||
const customPeer = buildStarsTransactionCustomPeer(
|
||||
transaction.peer,
|
||||
transaction.amount.currency === TON_CURRENCY_CODE,
|
||||
);
|
||||
const customPeer = buildStarsTransactionCustomPeer(transaction);
|
||||
title = customPeer.title || oldLang(customPeer.titleKey!);
|
||||
description = oldLang(customPeer.subtitleKey!);
|
||||
avatarPeer = customPeer;
|
||||
@ -92,6 +91,11 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
||||
description = lang('GiftUnique', { title: transaction.starGift.title, number: transaction.starGift.number });
|
||||
}
|
||||
|
||||
if (transaction.isPostsSearch) {
|
||||
title = getTransactionTitle(oldLang, lang, transaction);
|
||||
description = undefined;
|
||||
}
|
||||
|
||||
if (transaction.photo) {
|
||||
avatarPeer = undefined;
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import { getMessageLink } from '../../../../global/helpers';
|
||||
import {
|
||||
buildStarsTransactionCustomPeer,
|
||||
formatStarsTransactionAmount,
|
||||
shouldUseCustomPeer,
|
||||
} from '../../../../global/helpers/payments';
|
||||
import {
|
||||
selectCanPlayAnimatedEmojis,
|
||||
@ -97,8 +98,8 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const giftAttributes = isUniqueGift ? getGiftAttributes(gift) : undefined;
|
||||
|
||||
const customPeer = (transaction.peer && transaction.peer.type !== 'peer'
|
||||
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
||||
const customPeer = (transaction.peer && shouldUseCustomPeer(transaction)
|
||||
&& buildStarsTransactionCustomPeer(transaction)) || undefined;
|
||||
|
||||
const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined;
|
||||
const toName = transaction.peer && oldLang(getStarsPeerTitleKey(transaction.peer));
|
||||
@ -123,8 +124,8 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
|| (isGiftUpgrade && starGift?.type === 'starGiftUnique' ? starGift.title : undefined)
|
||||
|| (media ? mediaText : undefined);
|
||||
|
||||
const shouldDisplayAvatar = !media && !sticker;
|
||||
const avatarPeer = !photo ? (peer || customPeer) : undefined;
|
||||
const shouldDisplayAvatar = !media && !sticker && !transaction.isPostsSearch;
|
||||
const avatarPeer = !photo ? ((!shouldUseCustomPeer(transaction) && peer) || customPeer) : undefined;
|
||||
|
||||
const uniqueGiftHeader = isUniqueGift && (
|
||||
<div className={buildClassName(styles.header, styles.uniqueGift)}>
|
||||
@ -161,7 +162,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
{shouldDisplayAvatar && (
|
||||
<Avatar peer={avatarPeer} webPhoto={photo} size="giant" />
|
||||
)}
|
||||
{!sticker && (
|
||||
{!sticker && !transaction.isPostsSearch && (
|
||||
<img
|
||||
className={buildClassName(styles.starsBackground)}
|
||||
src={StarsBackground}
|
||||
@ -236,10 +237,12 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
peerLabel = oldLang('Stars.Transaction.Via');
|
||||
}
|
||||
|
||||
tableData.push([
|
||||
peerLabel,
|
||||
peerId ? { chatId: peerId } : toName || '',
|
||||
]);
|
||||
if (!transaction.isPostsSearch) {
|
||||
tableData.push([
|
||||
peerLabel,
|
||||
peerId ? { chatId: peerId } : toName || '',
|
||||
]);
|
||||
}
|
||||
|
||||
if (transaction.starRefCommision && transaction.paidMessages) {
|
||||
tableData.push([
|
||||
@ -329,8 +332,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const currencyAmount = modal?.transaction.amount;
|
||||
const starsGiftSticker = modal?.transaction.isGift
|
||||
&& currencyAmount?.currency === STARS_CURRENCY_CODE ? selectGiftStickerForStars(global, currencyAmount?.amount)
|
||||
: selectGiftStickerForTon(global, currencyAmount?.amount);
|
||||
? (currencyAmount?.currency === STARS_CURRENCY_CODE ? selectGiftStickerForStars(global, currencyAmount?.amount)
|
||||
: selectGiftStickerForTon(global, currencyAmount?.amount)) : undefined;
|
||||
|
||||
return {
|
||||
peer,
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
background-size: cover;
|
||||
outline: none !important;
|
||||
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
transition: background-color 0.15s, color 0.15s, opacity 0.15s;
|
||||
|
||||
// @optimization
|
||||
&:active,
|
||||
|
||||
@ -49,6 +49,7 @@ type OwnProps = {
|
||||
onUpClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onDownClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onSpinnerClick?: NoneToVoidFunction;
|
||||
onEnter?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const SearchInput: FC<OwnProps> = ({
|
||||
@ -80,6 +81,7 @@ const SearchInput: FC<OwnProps> = ({
|
||||
onUpClick,
|
||||
onDownClick,
|
||||
onSpinnerClick,
|
||||
onEnter,
|
||||
}) => {
|
||||
let inputRef = useRef<HTMLInputElement>();
|
||||
if (ref) {
|
||||
@ -125,8 +127,22 @@ const SearchInput: FC<OwnProps> = ({
|
||||
}
|
||||
|
||||
const handleKeyDown = useLastCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!resultsItemSelector) return;
|
||||
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
||||
if (e.key === 'Enter') {
|
||||
if (onEnter) {
|
||||
e.preventDefault();
|
||||
onEnter();
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultsItemSelector) {
|
||||
const element = document.querySelector(resultsItemSelector) as HTMLElement;
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resultsItemSelector && e.key === 'ArrowDown') {
|
||||
const element = document.querySelector(resultsItemSelector) as HTMLElement;
|
||||
if (element) {
|
||||
element.focus();
|
||||
|
||||
@ -5,8 +5,11 @@ import { getServerTime } from '../../util/serverTime';
|
||||
|
||||
import useInterval from '../../hooks/schedulers/useInterval';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import AnimatedCounter from '../common/AnimatedCounter';
|
||||
|
||||
type OwnProps = {
|
||||
langKey: string;
|
||||
endsAt: number;
|
||||
@ -16,7 +19,8 @@ type OwnProps = {
|
||||
const UPDATE_FREQUENCY = 500; // Sometimes second gets skipped if using 1000
|
||||
|
||||
const TextTimer: FC<OwnProps> = ({ langKey, endsAt, onEnd }) => {
|
||||
const lang = useOldLang();
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const serverTime = getServerTime();
|
||||
@ -32,11 +36,33 @@ const TextTimer: FC<OwnProps> = ({ langKey, endsAt, onEnd }) => {
|
||||
if (!isActive) return undefined;
|
||||
|
||||
const timeLeft = endsAt - serverTime;
|
||||
const formattedTime = formatMediaDuration(timeLeft);
|
||||
const time = formatMediaDuration(timeLeft);
|
||||
|
||||
const timeParts = time.split(':');
|
||||
const timeCounter = (
|
||||
<span style="font-variant-numeric: tabular-nums;">
|
||||
{timeParts.map((part, index) => (
|
||||
<>
|
||||
{index > 0 && ':'}
|
||||
<AnimatedCounter key={index} text={part} />
|
||||
</>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
|
||||
const isTypedKey = langKey === 'UnlockTimerPublicPostsSearch';
|
||||
|
||||
if (isTypedKey) {
|
||||
return (
|
||||
<span>
|
||||
{lang(langKey, { time: timeCounter }, { withNodes: true })}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{lang(langKey, formattedTime)}
|
||||
{oldLang(langKey, time)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@ -110,6 +110,10 @@ export const TODO_ITEMS_LIMIT = 30;
|
||||
export const TODO_TITLE_LENGTH_LIMIT = 32;
|
||||
export const TODO_ITEM_LENGTH_LIMIT = 64;
|
||||
|
||||
// Public Posts Search defaults
|
||||
export const PUBLIC_POSTS_SEARCH_DEFAULT_STARS_AMOUNT = 10;
|
||||
export const PUBLIC_POSTS_SEARCH_DEFAULT_TOTAL_DAILY = 2;
|
||||
|
||||
// Suggested Posts defaults
|
||||
export const STARS_SUGGESTED_POST_AMOUNT_MAX = 100000;
|
||||
export const STARS_SUGGESTED_POST_AMOUNT_MIN = 5;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiMessageSearchContext, ApiPeer, ApiTopic,
|
||||
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiMessageSearchContext, ApiPeer, ApiSearchPostsFlood, ApiTopic,
|
||||
ApiUserStatus,
|
||||
} from '../../../api/types';
|
||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
||||
@ -9,6 +11,8 @@ import { timestampPlusDay } from '../../../util/dates/dateFormat';
|
||||
import { isDeepLink, tryParseDeepLink } from '../../../util/deepLinkParser';
|
||||
import { toChannelId } from '../../../util/entities/ids';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { getTranslationFn } from '../../../util/localization';
|
||||
import { formatStarsAsText } from '../../../util/localization/format';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { isChatChannel, isChatGroup } from '../../helpers/chats';
|
||||
@ -158,6 +162,23 @@ addActionHandler('searchPopularBotApps', async (global, actions, payload): Promi
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('checkSearchPostsFlood', async (global, actions, payload): Promise<void> => {
|
||||
const { query, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const result = await callApi('checkSearchPostsFlood', query);
|
||||
|
||||
global = getGlobal();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = updateGlobalSearch(global, {
|
||||
searchFlood: result,
|
||||
}, tabId);
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
query?: string;
|
||||
type: ApiGlobalMessageSearchType;
|
||||
@ -175,6 +196,12 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
query = '', type, context, offsetRate, offsetId, offsetPeer,
|
||||
peer, maxDate, minDate, shouldResetResultsByType, tabId = getCurrentTabId(),
|
||||
} = params;
|
||||
|
||||
if (type === 'publicPosts') {
|
||||
global = updateGlobalSearchFetchingStatus(global, { publicPosts: true }, tabId);
|
||||
setGlobal(global);
|
||||
}
|
||||
|
||||
let result: {
|
||||
messages: ApiMessage[];
|
||||
userStatusesById?: Record<number, ApiUserStatus>;
|
||||
@ -184,10 +211,13 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
nextOffsetRate?: number;
|
||||
nextOffsetId?: number;
|
||||
nextOffsetPeerId?: string;
|
||||
searchFlood?: ApiSearchPostsFlood;
|
||||
} | undefined;
|
||||
|
||||
let messageLink: ApiMessage | undefined;
|
||||
|
||||
const previousSearchFlood = selectTabState(global, tabId).globalSearch.searchFlood;
|
||||
|
||||
if (peer) {
|
||||
const inChatResultRequest = callApi('searchMessagesInChat', {
|
||||
peer,
|
||||
@ -256,7 +286,7 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
}
|
||||
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
|
||||
if (!result || (query !== '' && query !== currentSearchQuery)) {
|
||||
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
||||
global = updateGlobalSearchFetchingStatus(global, { messages: false, publicPosts: false }, tabId);
|
||||
setGlobal(global);
|
||||
return;
|
||||
}
|
||||
@ -269,6 +299,8 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
messages, userStatusesById, totalCount, nextOffsetRate, nextOffsetId, nextOffsetPeerId,
|
||||
} = result;
|
||||
|
||||
const searchFlood = result.searchFlood || previousSearchFlood;
|
||||
|
||||
if (userStatusesById) {
|
||||
global = addUserStatuses(global, userStatusesById);
|
||||
}
|
||||
@ -285,6 +317,7 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
nextOffsetRate,
|
||||
nextOffsetId,
|
||||
nextOffsetPeerId,
|
||||
searchFlood,
|
||||
tabId,
|
||||
);
|
||||
|
||||
@ -298,6 +331,20 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
}, tabId);
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
if (type === 'publicPosts' && searchFlood && !searchFlood.queryIsFree && !offsetId
|
||||
&& previousSearchFlood?.remains === 0) {
|
||||
const lang = getTranslationFn();
|
||||
getActions().showNotification({
|
||||
icon: 'star',
|
||||
message: {
|
||||
key: 'NotificationPaidExtraSearch',
|
||||
variables: {
|
||||
stars: formatStarsAsText(lang, searchFlood.starsAmount),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessageByPublicLink(global: GlobalState, link: { username: string; messageId: number }) {
|
||||
|
||||
@ -113,7 +113,7 @@ addActionHandler('performMiddleSearch', async (global, actions, payload): Promis
|
||||
}
|
||||
|
||||
if (type === 'channels') {
|
||||
result = await callApi('searchHashtagPosts', {
|
||||
result = await callApi('searchPublicPosts', {
|
||||
hashtag: query!,
|
||||
limit: MESSAGE_SEARCH_SLICE,
|
||||
offsetId,
|
||||
|
||||
@ -12,9 +12,12 @@ addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionRetur
|
||||
const { query, tabId = getCurrentTabId() } = payload;
|
||||
const { chatId, currentContent } = selectTabState(global, tabId).globalSearch;
|
||||
|
||||
const fetchingStatus = query && currentContent !== GlobalSearchContent.BotApps
|
||||
const fetchingStatus = query
|
||||
&& currentContent !== GlobalSearchContent.BotApps && currentContent !== GlobalSearchContent.PublicPosts
|
||||
? { chats: !chatId, messages: true } : undefined;
|
||||
|
||||
actions.checkSearchPostsFlood({ query, tabId });
|
||||
|
||||
return updateGlobalSearch(global, {
|
||||
globalResults: {},
|
||||
localResults: {},
|
||||
|
||||
@ -6,8 +6,6 @@ import type {
|
||||
ApiRequestInputSavedStarGift,
|
||||
ApiStarsAmount,
|
||||
ApiStarsTransaction,
|
||||
ApiStarsTransactionPeer,
|
||||
ApiStarsTransactionPeerPeer,
|
||||
ApiTypeCurrencyAmount,
|
||||
} from '../../api/types';
|
||||
import type { CustomPeer } from '../../types';
|
||||
@ -259,10 +257,25 @@ export function getRequestInputSavedStarGift<T extends GlobalState>(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function shouldUseCustomPeer(transaction: ApiStarsTransaction) {
|
||||
return transaction.peer.type !== 'peer' || Boolean(transaction.isPostsSearch);
|
||||
}
|
||||
|
||||
export function buildStarsTransactionCustomPeer(
|
||||
peer: Exclude<ApiStarsTransactionPeer, ApiStarsTransactionPeerPeer>,
|
||||
isForTon?: boolean,
|
||||
transaction: ApiStarsTransaction,
|
||||
): CustomPeer {
|
||||
const { peer } = transaction;
|
||||
const isForTon = transaction.amount.currency === TON_CURRENCY_CODE;
|
||||
|
||||
if (transaction.isPostsSearch) {
|
||||
return {
|
||||
avatarIcon: 'search',
|
||||
isCustomPeer: true,
|
||||
title: '',
|
||||
peerColorId: 5,
|
||||
};
|
||||
}
|
||||
|
||||
if (peer.type === 'appStore') {
|
||||
return {
|
||||
avatarIcon: 'star',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ApiGlobalMessageSearchType, ApiMessage } from '../../api/types';
|
||||
import type { ApiGlobalMessageSearchType, ApiMessage, ApiSearchPostsFlood } from '../../api/types';
|
||||
import type { GlobalSearchContent } from '../../types';
|
||||
import type { GlobalState, TabArgs, TabState } from '../types';
|
||||
|
||||
@ -37,6 +37,7 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
||||
nextOffsetRate?: number,
|
||||
nextOffsetId?: number,
|
||||
nextOffsetPeerId?: string,
|
||||
searchFlood?: ApiSearchPostsFlood,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
const { resultsByType } = selectTabState(global, tabId).globalSearch || {};
|
||||
@ -52,8 +53,12 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
||||
(newId) => foundIdsForType.includes(getSearchResultKey(newFoundMessagesById[newId])),
|
||||
)
|
||||
) {
|
||||
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
||||
global = updateGlobalSearchFetchingStatus(global, {
|
||||
messages: false,
|
||||
publicPosts: false,
|
||||
}, tabId);
|
||||
return updateGlobalSearch(global, {
|
||||
searchFlood,
|
||||
resultsByType: {
|
||||
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
||||
[type]: {
|
||||
@ -74,9 +79,13 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
||||
const foundIds = Array.prototype.concat(prevFoundIds, newFoundIds);
|
||||
const foundOrPrevFoundIds = areSortedArraysEqual(prevFoundIds, foundIds) ? prevFoundIds : foundIds;
|
||||
|
||||
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
||||
global = updateGlobalSearchFetchingStatus(global, {
|
||||
messages: false,
|
||||
publicPosts: false,
|
||||
}, tabId);
|
||||
|
||||
return updateGlobalSearch(global, {
|
||||
searchFlood,
|
||||
resultsByType: {
|
||||
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
||||
[type]: {
|
||||
@ -91,7 +100,7 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
||||
}
|
||||
|
||||
export function updateGlobalSearchFetchingStatus<T extends GlobalState>(
|
||||
global: T, newState: { chats?: boolean; messages?: boolean; botApps?: boolean },
|
||||
global: T, newState: { chats?: boolean; messages?: boolean; botApps?: boolean; publicPosts?: boolean },
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
return updateGlobalSearch(global, {
|
||||
|
||||
@ -418,6 +418,9 @@ export interface ActionPayloads {
|
||||
shouldCheckFetchingMessagesStatus?: boolean;
|
||||
} & WithTabId;
|
||||
searchPopularBotApps: WithTabId | undefined;
|
||||
checkSearchPostsFlood: {
|
||||
query?: string;
|
||||
} & WithTabId;
|
||||
addRecentlyFoundChatId: {
|
||||
id: string;
|
||||
};
|
||||
|
||||
@ -36,6 +36,7 @@ import type {
|
||||
ApiReceiptRegular,
|
||||
ApiSavedGifts,
|
||||
ApiSavedStarGift,
|
||||
ApiSearchPostsFlood,
|
||||
ApiSponsoredPeer,
|
||||
ApiStarGift,
|
||||
ApiStarGiftAttribute,
|
||||
@ -249,10 +250,12 @@ export type TabState = {
|
||||
chatId?: string;
|
||||
foundTopicIds?: number[];
|
||||
sponsoredPeer?: ApiSponsoredPeer;
|
||||
searchFlood?: ApiSearchPostsFlood;
|
||||
fetchingStatus?: {
|
||||
chats?: boolean;
|
||||
messages?: boolean;
|
||||
botApps?: boolean;
|
||||
publicPosts?: boolean;
|
||||
};
|
||||
isClosing?: boolean;
|
||||
localResults?: {
|
||||
|
||||
2
src/lib/gramjs/tl/api.d.ts
vendored
2
src/lib/gramjs/tl/api.d.ts
vendored
@ -16515,6 +16515,7 @@ namespace Api {
|
||||
stargiftUpgrade?: true;
|
||||
businessTransfer?: true;
|
||||
stargiftResale?: true;
|
||||
postsSearch?: true;
|
||||
id: string;
|
||||
amount: Api.TypeStarsAmount;
|
||||
date: int;
|
||||
@ -16548,6 +16549,7 @@ namespace Api {
|
||||
stargiftUpgrade?: true;
|
||||
businessTransfer?: true;
|
||||
stargiftResale?: true;
|
||||
postsSearch?: true;
|
||||
id: string;
|
||||
amount: Api.TypeStarsAmount;
|
||||
date: int;
|
||||
|
||||
@ -1368,7 +1368,7 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer;
|
||||
starsTransactionPeerAds#60682812 = StarsTransactionPeer;
|
||||
starsTransactionPeerAPI#f9677aad = StarsTransactionPeer;
|
||||
starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption;
|
||||
starsTransaction#13659eb0 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true id:string amount:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int ads_proceeds_from_date:flags.23?int ads_proceeds_to_date:flags.23?int = StarsTransaction;
|
||||
starsTransaction#13659eb0 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true posts_search:flags.24?true id:string amount:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int ads_proceeds_from_date:flags.23?int ads_proceeds_to_date:flags.23?int = StarsTransaction;
|
||||
payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector<StarsSubscription> subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.StarsStatus;
|
||||
foundStory#e87acbc0 peer:Peer story:StoryItem = FoundStory;
|
||||
stories.foundStories#e2de7737 flags:# count:int stories:Vector<FoundStory> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = stories.FoundStories;
|
||||
@ -1751,6 +1751,7 @@ channels.getChannelRecommendations#25a71742 flags:# channel:flags.0?InputChannel
|
||||
channels.searchPosts#f2c4f24d flags:# hashtag:flags.0?string query:flags.1?string offset_rate:int offset_peer:InputPeer offset_id:int limit:int allow_paid_stars:flags.2?long = messages.Messages;
|
||||
channels.updatePaidMessagesPrice#4b12327b flags:# broadcast_messages_allowed:flags.0?true channel:InputChannel send_paid_messages_stars:long = Updates;
|
||||
channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.checkSearchPostsFlood#22567115 flags:# query:flags.0?string = SearchPostsFlood;
|
||||
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
|
||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||
|
||||
@ -284,6 +284,7 @@
|
||||
"channels.searchPosts",
|
||||
"channels.reportSpam",
|
||||
"channels.updatePaidMessagesPrice",
|
||||
"channels.checkSearchPostsFlood",
|
||||
"channels.toggleAutotranslation",
|
||||
"bots.getBotRecommendations",
|
||||
"bots.canSendMessage",
|
||||
|
||||
@ -1853,7 +1853,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer;
|
||||
|
||||
starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption;
|
||||
|
||||
starsTransaction#13659eb0 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true id:string amount:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int ads_proceeds_from_date:flags.23?int ads_proceeds_to_date:flags.23?int = StarsTransaction;
|
||||
starsTransaction#13659eb0 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true posts_search:flags.24?true id:string amount:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int ads_proceeds_from_date:flags.23?int ads_proceeds_to_date:flags.23?int = StarsTransaction;
|
||||
|
||||
payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector<StarsSubscription> subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.StarsStatus;
|
||||
|
||||
|
||||
@ -313,6 +313,7 @@ export enum GlobalSearchContent {
|
||||
ChatList,
|
||||
ChannelList,
|
||||
BotApps,
|
||||
PublicPosts,
|
||||
Media,
|
||||
Links,
|
||||
Files,
|
||||
|
||||
28
src/types/language.d.ts
vendored
28
src/types/language.d.ts
vendored
@ -1333,6 +1333,7 @@ export interface LangPair {
|
||||
'SearchTabMusic': undefined;
|
||||
'SearchTabVoice': undefined;
|
||||
'SearchTabMessages': undefined;
|
||||
'SearchTabPublicPosts': undefined;
|
||||
'StarsTransactionsAll': undefined;
|
||||
'StarsTransactionsIncoming': undefined;
|
||||
'StarsTransactionsOutgoing': undefined;
|
||||
@ -1351,6 +1352,7 @@ export interface LangPair {
|
||||
'ProfileTabSharedGroups': undefined;
|
||||
'ProfileTabSimilarChannels': undefined;
|
||||
'ProfileTabSimilarBots': undefined;
|
||||
'ProfileTabPublicPosts': undefined;
|
||||
'ActionUnsupportedTitle': undefined;
|
||||
'ActionUnsupportedDescription': undefined;
|
||||
'UnlockMoreSimilarBots': undefined;
|
||||
@ -1620,6 +1622,14 @@ export interface LangPair {
|
||||
'LabelPayInTON': undefined;
|
||||
'PriceChanged': undefined;
|
||||
'PayNewPrice': undefined;
|
||||
'GlobalSearch': undefined;
|
||||
'DescriptionPublicPostsSearch': undefined;
|
||||
'PublicPosts': undefined;
|
||||
'PublicPostsLimitReached': undefined;
|
||||
'PublicPostsPremiumFeatureDescription': undefined;
|
||||
'PublicPostsPremiumFeatureSubtitle': undefined;
|
||||
'PublicPostsSubscribeToPremium': undefined;
|
||||
'PostsSearchTransaction': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairWithVariables<V = LangVariable> {
|
||||
@ -2816,6 +2826,18 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'originalAmount': V;
|
||||
'newAmount': V;
|
||||
};
|
||||
'ButtonSearchPublicPosts': {
|
||||
'query': V;
|
||||
};
|
||||
'PublicPostsSearchForStars': {
|
||||
'stars': V;
|
||||
};
|
||||
'UnlockTimerPublicPostsSearch': {
|
||||
'time': V;
|
||||
};
|
||||
'NotificationPaidExtraSearch': {
|
||||
'stars': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
@ -3137,6 +3159,12 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
||||
'TextAgeVerificationModal': {
|
||||
'count': V;
|
||||
};
|
||||
'RemainingPublicPostsSearch': {
|
||||
'count': V;
|
||||
};
|
||||
'HintPublicPostsSearchQuota': {
|
||||
'count': V;
|
||||
};
|
||||
}
|
||||
export type RegularLangKey = keyof LangPair;
|
||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user