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={{ transform: `translateX(${value}%)` }}
|
||||||
style={{ '--custom-prop': value } as React.CSSProperties}
|
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:**
|
- **Localization & Text Rules:**
|
||||||
- **ALWAYS** use `lang()` for all text content - never hardcode strings.
|
- **ALWAYS** use `lang()` for all text content - never hardcode strings.
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import type {
|
|||||||
ApiPreparedInlineMessage,
|
ApiPreparedInlineMessage,
|
||||||
ApiQuickReply,
|
ApiQuickReply,
|
||||||
ApiReplyInfo,
|
ApiReplyInfo,
|
||||||
|
ApiSearchPostsFlood,
|
||||||
ApiSponsoredMessage,
|
ApiSponsoredMessage,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiStory,
|
ApiStory,
|
||||||
@ -820,3 +821,17 @@ export function buildPreparedInlineMessage(
|
|||||||
cacheTime: result.cacheTime,
|
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 {
|
const {
|
||||||
date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
|
date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
|
||||||
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages,
|
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages,
|
||||||
stargiftResale,
|
stargiftResale, postsSearch,
|
||||||
} = transaction;
|
} = transaction;
|
||||||
|
|
||||||
if (photo) {
|
if (photo) {
|
||||||
@ -588,6 +588,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
|||||||
isGiftUpgrade: stargiftUpgrade,
|
isGiftUpgrade: stargiftUpgrade,
|
||||||
isGiftResale: stargiftResale,
|
isGiftResale: stargiftResale,
|
||||||
paidMessages,
|
paidMessages,
|
||||||
|
isPostsSearch: postsSearch,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import type {
|
|||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPoll,
|
ApiPoll,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
|
ApiSearchPostsFlood,
|
||||||
ApiSendMessageAction,
|
ApiSendMessageAction,
|
||||||
ApiTodoItem,
|
ApiTodoItem,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
@ -66,6 +67,7 @@ import {
|
|||||||
buildApiMessage,
|
buildApiMessage,
|
||||||
buildApiQuickReply,
|
buildApiQuickReply,
|
||||||
buildApiReportResult,
|
buildApiReportResult,
|
||||||
|
buildApiSearchPostsFlood,
|
||||||
buildApiSponsoredMessage,
|
buildApiSponsoredMessage,
|
||||||
buildApiThreadInfo,
|
buildApiThreadInfo,
|
||||||
buildLocalForwardedMessage,
|
buildLocalForwardedMessage,
|
||||||
@ -128,6 +130,7 @@ type SearchResults = {
|
|||||||
nextOffsetRate?: number;
|
nextOffsetRate?: number;
|
||||||
nextOffsetPeerId?: string;
|
nextOffsetPeerId?: string;
|
||||||
nextOffsetId?: number;
|
nextOffsetId?: number;
|
||||||
|
searchFlood?: ApiSearchPostsFlood;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchMessages({
|
export async function fetchMessages({
|
||||||
@ -1537,6 +1540,16 @@ export async function searchMessagesGlobal({
|
|||||||
minDate?: number;
|
minDate?: number;
|
||||||
maxDate?: number;
|
maxDate?: number;
|
||||||
}): Promise<SearchResults | undefined> {
|
}): Promise<SearchResults | undefined> {
|
||||||
|
if (type === 'publicPosts') {
|
||||||
|
return searchPublicPosts({
|
||||||
|
query,
|
||||||
|
offsetRate,
|
||||||
|
offsetPeer,
|
||||||
|
offsetId,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let filter;
|
let filter;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'media':
|
case 'media':
|
||||||
@ -1613,22 +1626,32 @@ export async function searchMessagesGlobal({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchHashtagPosts({
|
export async function searchPublicPosts({
|
||||||
hashtag, offsetRate, offsetPeer, offsetId, limit,
|
hashtag, query, offsetRate, offsetPeer, offsetId, limit,
|
||||||
}: {
|
}: {
|
||||||
hashtag: string;
|
hashtag?: string;
|
||||||
|
query?: string;
|
||||||
offsetRate?: number;
|
offsetRate?: number;
|
||||||
offsetPeer?: ApiPeer;
|
offsetPeer?: ApiPeer;
|
||||||
offsetId?: number;
|
offsetId?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}): Promise<SearchResults | undefined> {
|
}): Promise<SearchResults | undefined> {
|
||||||
const peer = (offsetPeer && buildInputPeer(offsetPeer.id, offsetPeer.accessHash)) || new GramJs.InputPeerEmpty();
|
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({
|
const result = await invokeRequest(new GramJs.channels.SearchPosts({
|
||||||
hashtag,
|
hashtag,
|
||||||
|
query,
|
||||||
offsetRate: offsetRate ?? DEFAULT_PRIMITIVES.INT,
|
offsetRate: offsetRate ?? DEFAULT_PRIMITIVES.INT,
|
||||||
offsetId: offsetId ?? DEFAULT_PRIMITIVES.INT,
|
offsetId: offsetId ?? DEFAULT_PRIMITIVES.INT,
|
||||||
offsetPeer: peer,
|
offsetPeer: peer,
|
||||||
limit: limit ?? DEFAULT_PRIMITIVES.INT,
|
limit: limit ?? DEFAULT_PRIMITIVES.INT,
|
||||||
|
allowPaidStars: BigInt(resultFlood.starsAmount),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!result || result instanceof GramJs.messages.MessagesNotModified) {
|
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 nextOffsetRate = 'nextRate' in result && result.nextRate ? result.nextRate : undefined;
|
||||||
const nextOffsetId = lastMessage?.id;
|
const nextOffsetId = lastMessage?.id;
|
||||||
|
|
||||||
|
const searchFlood = result instanceof GramJs.messages.MessagesSlice && result.searchFlood
|
||||||
|
? buildApiSearchPostsFlood(result.searchFlood, query)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
userStatusesById,
|
userStatusesById,
|
||||||
@ -1657,9 +1684,20 @@ export async function searchHashtagPosts({
|
|||||||
nextOffsetRate,
|
nextOffsetRate,
|
||||||
nextOffsetPeerId,
|
nextOffsetPeerId,
|
||||||
nextOffsetId,
|
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({
|
export async function fetchWebPagePreview({
|
||||||
text,
|
text,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@ -919,7 +919,8 @@ export type ApiTranscription = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto';
|
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto';
|
||||||
export type ApiGlobalMessageSearchType = 'text' | 'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice';
|
export type ApiGlobalMessageSearchType = 'text' |
|
||||||
|
'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'publicPosts';
|
||||||
export type ApiMessageSearchContext = 'all' | 'users' | 'groups' | 'channels';
|
export type ApiMessageSearchContext = 'all' | 'users' | 'groups' | 'channels';
|
||||||
|
|
||||||
export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse'
|
export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse'
|
||||||
@ -992,6 +993,15 @@ export type ApiPreparedInlineMessage = {
|
|||||||
cacheTime: number;
|
cacheTime: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiSearchPostsFlood = {
|
||||||
|
query?: string;
|
||||||
|
queryIsFree?: boolean;
|
||||||
|
totalDaily: number;
|
||||||
|
remains: number;
|
||||||
|
waitTill?: number;
|
||||||
|
starsAmount: number;
|
||||||
|
};
|
||||||
|
|
||||||
export const MAIN_THREAD_ID = -1;
|
export const MAIN_THREAD_ID = -1;
|
||||||
|
|
||||||
// `Symbol` can not be transferred from worker
|
// `Symbol` can not be transferred from worker
|
||||||
|
|||||||
@ -239,6 +239,7 @@ export interface ApiStarsTransaction {
|
|||||||
isGiftUpgrade?: true;
|
isGiftUpgrade?: true;
|
||||||
isGiftResale?: true;
|
isGiftResale?: true;
|
||||||
paidMessages?: number;
|
paidMessages?: number;
|
||||||
|
isPostsSearch?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiStarsSubscription {
|
export interface ApiStarsSubscription {
|
||||||
|
|||||||
@ -1639,6 +1639,7 @@
|
|||||||
"SearchTabMusic" = "Music";
|
"SearchTabMusic" = "Music";
|
||||||
"SearchTabVoice" = "Voice";
|
"SearchTabVoice" = "Voice";
|
||||||
"SearchTabMessages" = "Messages";
|
"SearchTabMessages" = "Messages";
|
||||||
|
"SearchTabPublicPosts" = "Posts";
|
||||||
"StarsTransactionsAll" = "All Transactions";
|
"StarsTransactionsAll" = "All Transactions";
|
||||||
"StarsTransactionsIncoming" = "Incoming";
|
"StarsTransactionsIncoming" = "Incoming";
|
||||||
"StarsTransactionsOutgoing" = "Outgoing";
|
"StarsTransactionsOutgoing" = "Outgoing";
|
||||||
@ -1657,6 +1658,7 @@
|
|||||||
"ProfileTabSharedGroups" = "Groups";
|
"ProfileTabSharedGroups" = "Groups";
|
||||||
"ProfileTabSimilarChannels" = "Similar Channels";
|
"ProfileTabSimilarChannels" = "Similar Channels";
|
||||||
"ProfileTabSimilarBots" = "Similar Bots";
|
"ProfileTabSimilarBots" = "Similar Bots";
|
||||||
|
"ProfileTabPublicPosts" = "Public Posts";
|
||||||
"ActionUnsupportedTitle" = "Action not supported yet";
|
"ActionUnsupportedTitle" = "Action not supported yet";
|
||||||
"ActionUnsupportedDescription" = "Please use one of our apps to complete this action.";
|
"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}**.";
|
"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";
|
"PriceChanged" = "Price Changed";
|
||||||
"PayNewPrice" = "Pay New Price";
|
"PayNewPrice" = "Pay New Price";
|
||||||
"PriceChangedText" = "The price has already changed from **{originalAmount}** to **{newAmount}**. Do you want to pay the 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;
|
justify-content: center;
|
||||||
color: var(--color-text-meta);
|
color: var(--color-text-meta);
|
||||||
|
|
||||||
&.with-description {
|
&.with-description,
|
||||||
|
&.with-sticker {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sticker {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.AnimatedSticker {
|
.AnimatedSticker {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,26 +2,45 @@ import type { FC } from '../../lib/teact/teact';
|
|||||||
import { memo } from '../../lib/teact/teact';
|
import { memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import { LOCAL_TGS_PREVIEW_URLS, LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
|
||||||
|
|
||||||
|
import AnimatedIconWithPreview from './AnimatedIconWithPreview';
|
||||||
|
|
||||||
import './NothingFound.scss';
|
import './NothingFound.scss';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
text?: string;
|
text?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
withSticker?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TEXT = 'Nothing found.';
|
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 lang = useOldLang();
|
||||||
const { transitionClassNames } = useShowTransitionDeprecated(true);
|
const { transitionClassNames } = useShowTransitionDeprecated(true);
|
||||||
|
|
||||||
return (
|
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}
|
{text}
|
||||||
{description && <p className="description">{renderText(lang(description), ['br'])}</p>}
|
{description && <p className="description">{renderText(lang(description), ['br'])}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
|||||||
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
||||||
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
||||||
import Diamond from '../../../assets/tgs/Diamond.tgs';
|
import Diamond from '../../../assets/tgs/Diamond.tgs';
|
||||||
|
import DuckNothingFound from '../../../assets/tgs/DuckNothingFound.tgs';
|
||||||
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
||||||
import Fragment from '../../../assets/tgs/general/Fragment.tgs';
|
import Fragment from '../../../assets/tgs/general/Fragment.tgs';
|
||||||
import Mention from '../../../assets/tgs/general/Mention.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 MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
||||||
import ReadTime from '../../../assets/tgs/ReadTime.tgs';
|
import ReadTime from '../../../assets/tgs/ReadTime.tgs';
|
||||||
import Report from '../../../assets/tgs/Report.tgs';
|
import Report from '../../../assets/tgs/Report.tgs';
|
||||||
|
import Search from '../../../assets/tgs/Search.tgs';
|
||||||
import SearchingDuck from '../../../assets/tgs/SearchingDuck.tgs';
|
import SearchingDuck from '../../../assets/tgs/SearchingDuck.tgs';
|
||||||
import Congratulations from '../../../assets/tgs/settings/Congratulations.tgs';
|
import Congratulations from '../../../assets/tgs/settings/Congratulations.tgs';
|
||||||
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.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 StarReaction from '../../../assets/tgs/stars/StarReaction.tgs';
|
||||||
import StarReactionEffect from '../../../assets/tgs/stars/StarReactionEffect.tgs';
|
import StarReactionEffect from '../../../assets/tgs/stars/StarReactionEffect.tgs';
|
||||||
import Unlock from '../../../assets/tgs/Unlock.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 = {
|
export const LOCAL_TGS_URLS = {
|
||||||
MonkeyIdle,
|
MonkeyIdle,
|
||||||
@ -70,4 +79,6 @@ export const LOCAL_TGS_URLS = {
|
|||||||
SearchingDuck,
|
SearchingDuck,
|
||||||
BannedDuck,
|
BannedDuck,
|
||||||
Diamond,
|
Diamond,
|
||||||
|
Search,
|
||||||
|
DuckNothingFound,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV, LAYERS_ANIMATION_NAME,
|
IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV, LAYERS_ANIMATION_NAME,
|
||||||
} from '../../util/browser/windowEnvironment';
|
} from '../../util/browser/windowEnvironment';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
|
import { debounce } from '../../util/schedulers';
|
||||||
import { captureControlledSwipe } from '../../util/swipeController';
|
import { captureControlledSwipe } from '../../util/swipeController';
|
||||||
|
|
||||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||||
@ -110,6 +111,10 @@ function LeftColumn({
|
|||||||
const [contactsFilter, setContactsFilter] = useState<string>('');
|
const [contactsFilter, setContactsFilter] = useState<string>('');
|
||||||
const [foldersState, foldersDispatch] = useFoldersReducer();
|
const [foldersState, foldersDispatch] = useFoldersReducer();
|
||||||
|
|
||||||
|
const debouncedSetGlobalSearchQuery = useMemo(() => debounce((query: string) => {
|
||||||
|
setGlobalSearchQuery({ query });
|
||||||
|
}, 200, false, true), [setGlobalSearchQuery]);
|
||||||
|
|
||||||
// Used to reset child components in background.
|
// Used to reset child components in background.
|
||||||
const [lastResetTime, setLastResetTime] = useState<number>(0);
|
const [lastResetTime, setLastResetTime] = useState<number>(0);
|
||||||
|
|
||||||
@ -375,7 +380,7 @@ function LeftColumn({
|
|||||||
openLeftColumnContent({ contentKey: LeftColumnContent.GlobalSearch });
|
openLeftColumnContent({ contentKey: LeftColumnContent.GlobalSearch });
|
||||||
|
|
||||||
if (query !== searchQuery) {
|
if (query !== searchQuery) {
|
||||||
setGlobalSearchQuery({ query });
|
debouncedSetGlobalSearchQuery(query);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -114,6 +114,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
setGlobalSearchChatId,
|
setGlobalSearchChatId,
|
||||||
lockScreen,
|
lockScreen,
|
||||||
openSettingsScreen,
|
openSettingsScreen,
|
||||||
|
searchMessagesGlobal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
@ -193,6 +194,15 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
lockScreen();
|
lockScreen();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSearchEnter = useLastCallback(() => {
|
||||||
|
if (searchQuery && content === LeftColumnContent.GlobalSearch) {
|
||||||
|
searchMessagesGlobal({
|
||||||
|
type: 'publicPosts',
|
||||||
|
shouldResetResultsByType: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const isSearchRelevant = Boolean(globalSearchChatId)
|
const isSearchRelevant = Boolean(globalSearchChatId)
|
||||||
|| content === LeftColumnContent.GlobalSearch
|
|| content === LeftColumnContent.GlobalSearch
|
||||||
|| content === LeftColumnContent.Contacts;
|
|| content === LeftColumnContent.Contacts;
|
||||||
@ -295,6 +305,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
onReset={onReset}
|
onReset={onReset}
|
||||||
onFocus={handleSearchFocus}
|
onFocus={handleSearchFocus}
|
||||||
onSpinnerClick={connectionStatusPosition === 'minimized' ? toggleConnectionStatus : undefined}
|
onSpinnerClick={connectionStatusPosition === 'minimized' ? toggleConnectionStatus : undefined}
|
||||||
|
onEnter={handleSearchEnter}
|
||||||
>
|
>
|
||||||
{searchContent}
|
{searchContent}
|
||||||
<StoryToggler
|
<StoryToggler
|
||||||
@ -344,7 +355,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
searchQuery,
|
searchQuery,
|
||||||
isLoading: fetchingStatus ? Boolean(fetchingStatus.chats || fetchingStatus.messages) : false,
|
isLoading: fetchingStatus ? Boolean(fetchingStatus.chats
|
||||||
|
|| fetchingStatus.messages || fetchingStatus.publicPosts) : false,
|
||||||
globalSearchChatId: chatId,
|
globalSearchChatId: chatId,
|
||||||
searchDate: minDate,
|
searchDate: minDate,
|
||||||
theme: selectTheme(global),
|
theme: selectTheme(global),
|
||||||
|
|||||||
@ -134,6 +134,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
|||||||
{!canRenderContents && <Loading />}
|
{!canRenderContents && <Loading />}
|
||||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
|
withSticker
|
||||||
text={lang('ChatList.Search.NoResults')}
|
text={lang('ChatList.Search.NoResults')}
|
||||||
description={lang('ChatList.Search.NoResultsDescription')}
|
description={lang('ChatList.Search.NoResultsDescription')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -89,6 +89,7 @@ const BotAppResults: FC<OwnProps & StateProps> = ({
|
|||||||
{!canRenderContents && <Loading />}
|
{!canRenderContents && <Loading />}
|
||||||
{canRenderContents && !filteredFoundIds?.length && (
|
{canRenderContents && !filteredFoundIds?.length && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
|
withSticker
|
||||||
text={lang('ChatList.Search.NoResults')}
|
text={lang('ChatList.Search.NoResults')}
|
||||||
description={lang('ChatList.Search.NoResultsDescription')}
|
description={lang('ChatList.Search.NoResultsDescription')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -132,6 +132,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{nothingFound && (
|
{nothingFound && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
|
withSticker
|
||||||
text={lang('ChatList.Search.NoResults')}
|
text={lang('ChatList.Search.NoResults')}
|
||||||
description={lang('ChatList.Search.NoResultsDescription')}
|
description={lang('ChatList.Search.NoResultsDescription')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -374,6 +374,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{nothingFound && (
|
{nothingFound && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
|
withSticker
|
||||||
text={oldLang('ChatList.Search.NoResults')}
|
text={oldLang('ChatList.Search.NoResults')}
|
||||||
description={oldLang('ChatList.Search.NoResultsDescription')}
|
description={oldLang('ChatList.Search.NoResultsDescription')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -139,6 +139,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
|||||||
{!canRenderContents && <Loading />}
|
{!canRenderContents && <Loading />}
|
||||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
|
withSticker
|
||||||
text={lang('ChatList.Search.NoResults')}
|
text={lang('ChatList.Search.NoResults')}
|
||||||
description={lang('ChatList.Search.NoResultsDescription')}
|
description={lang('ChatList.Search.NoResultsDescription')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@ -27,6 +28,7 @@ import ChatResults from './ChatResults';
|
|||||||
import FileResults from './FileResults';
|
import FileResults from './FileResults';
|
||||||
import LinkResults from './LinkResults';
|
import LinkResults from './LinkResults';
|
||||||
import MediaResults from './MediaResults';
|
import MediaResults from './MediaResults';
|
||||||
|
import PublicPostsResults from './PublicPostsResults';
|
||||||
|
|
||||||
import './LeftSearch.scss';
|
import './LeftSearch.scss';
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ const TABS: TabInfo[] = [
|
|||||||
{ type: GlobalSearchContent.ChatList, key: 'SearchTabChats' },
|
{ type: GlobalSearchContent.ChatList, key: 'SearchTabChats' },
|
||||||
{ type: GlobalSearchContent.ChannelList, key: 'SearchTabChannels' },
|
{ type: GlobalSearchContent.ChannelList, key: 'SearchTabChannels' },
|
||||||
{ type: GlobalSearchContent.BotApps, key: 'SearchTabApps' },
|
{ type: GlobalSearchContent.BotApps, key: 'SearchTabApps' },
|
||||||
|
{ type: GlobalSearchContent.PublicPosts, key: 'SearchTabPublicPosts' },
|
||||||
{ type: GlobalSearchContent.Media, key: 'SearchTabMedia' },
|
{ type: GlobalSearchContent.Media, key: 'SearchTabMedia' },
|
||||||
{ type: GlobalSearchContent.Links, key: 'SearchTabLinks' },
|
{ type: GlobalSearchContent.Links, key: 'SearchTabLinks' },
|
||||||
{ type: GlobalSearchContent.Files, key: 'SearchTabFiles' },
|
{ type: GlobalSearchContent.Files, key: 'SearchTabFiles' },
|
||||||
@ -74,12 +77,19 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
|||||||
const {
|
const {
|
||||||
setGlobalSearchContent,
|
setGlobalSearchContent,
|
||||||
setGlobalSearchDate,
|
setGlobalSearchDate,
|
||||||
|
checkSearchPostsFlood,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const [activeTab, setActiveTab] = useState(currentContent);
|
const [activeTab, setActiveTab] = useState(currentContent);
|
||||||
const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]);
|
const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isActive) {
|
||||||
|
checkSearchPostsFlood({});
|
||||||
|
}
|
||||||
|
}, [isActive]);
|
||||||
|
|
||||||
const tabs = useMemo(() => {
|
const tabs = useMemo(() => {
|
||||||
const arr = chatId ? CHAT_TABS : TABS;
|
const arr = chatId ? CHAT_TABS : TABS;
|
||||||
return arr.map((tab) => ({
|
return arr.map((tab) => ({
|
||||||
@ -166,6 +176,13 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
|||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case GlobalSearchContent.PublicPosts:
|
||||||
|
return (
|
||||||
|
<PublicPostsResults
|
||||||
|
key="publicPosts"
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
/>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,6 +132,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
|||||||
{!canRenderContents && <Loading />}
|
{!canRenderContents && <Loading />}
|
||||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
|
withSticker
|
||||||
text={lang('ChatList.Search.NoResults')}
|
text={lang('ChatList.Search.NoResults')}
|
||||||
description={lang('ChatList.Search.NoResultsDescription')}
|
description={lang('ChatList.Search.NoResultsDescription')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -133,6 +133,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
|
|||||||
{!canRenderContents && <Loading />}
|
{!canRenderContents && <Loading />}
|
||||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||||
<NothingFound
|
<NothingFound
|
||||||
|
withSticker
|
||||||
text={lang('ChatList.Search.NoResults')}
|
text={lang('ChatList.Search.NoResults')}
|
||||||
description={lang('ChatList.Search.NoResultsDescription')}
|
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 type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
||||||
import { buildStarsTransactionCustomPeer } from '../../../../global/helpers/payments';
|
import { buildStarsTransactionCustomPeer, shouldUseCustomPeer } from '../../../../global/helpers/payments';
|
||||||
import {
|
import {
|
||||||
type LangFn,
|
type LangFn,
|
||||||
} from '../../../../util/localization';
|
} from '../../../../util/localization';
|
||||||
@ -25,6 +25,9 @@ export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transactio
|
|||||||
? lang('StarGiftSaleTransaction')
|
? lang('StarGiftSaleTransaction')
|
||||||
: lang('StarGiftPurchaseTransaction');
|
: lang('StarGiftPurchaseTransaction');
|
||||||
}
|
}
|
||||||
|
if (transaction.isPostsSearch) {
|
||||||
|
return lang('PostsSearchTransaction');
|
||||||
|
}
|
||||||
|
|
||||||
if (transaction.starRefCommision) {
|
if (transaction.starRefCommision) {
|
||||||
return oldLang('StarTransactionCommission', formatPercent(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');
|
return isNegativeAmount(transaction.amount) ? oldLang('Gift2TransactionSent') : oldLang('Gift2ConvertedTitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
const customPeer = (transaction.peer && transaction.peer.type !== 'peer'
|
const customPeer = (transaction.peer && shouldUseCustomPeer(transaction)
|
||||||
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
&& buildStarsTransactionCustomPeer(transaction)) || undefined;
|
||||||
|
|
||||||
if (customPeer) return customPeer.title || oldLang(customPeer.titleKey!);
|
if (customPeer) return customPeer.title || oldLang(customPeer.titleKey!);
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,9 @@ import type { GlobalState } from '../../../../global/types';
|
|||||||
import type { CustomPeer } from '../../../../types';
|
import type { CustomPeer } from '../../../../types';
|
||||||
|
|
||||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
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 { getPeerTitle } from '../../../../global/helpers/peers';
|
||||||
import { selectPeer } from '../../../../global/selectors';
|
import { selectPeer } from '../../../../global/selectors';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
@ -71,14 +73,11 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
|||||||
let status: string | undefined;
|
let status: string | undefined;
|
||||||
let avatarPeer: ApiPeer | CustomPeer | undefined;
|
let avatarPeer: ApiPeer | CustomPeer | undefined;
|
||||||
|
|
||||||
if (transaction.peer.type === 'peer') {
|
if (!shouldUseCustomPeer(transaction)) {
|
||||||
description = peer && getPeerTitle(oldLang, peer);
|
description = peer && getPeerTitle(oldLang, peer);
|
||||||
avatarPeer = peer || CUSTOM_PEER_PREMIUM;
|
avatarPeer = peer || CUSTOM_PEER_PREMIUM;
|
||||||
} else {
|
} else {
|
||||||
const customPeer = buildStarsTransactionCustomPeer(
|
const customPeer = buildStarsTransactionCustomPeer(transaction);
|
||||||
transaction.peer,
|
|
||||||
transaction.amount.currency === TON_CURRENCY_CODE,
|
|
||||||
);
|
|
||||||
title = customPeer.title || oldLang(customPeer.titleKey!);
|
title = customPeer.title || oldLang(customPeer.titleKey!);
|
||||||
description = oldLang(customPeer.subtitleKey!);
|
description = oldLang(customPeer.subtitleKey!);
|
||||||
avatarPeer = customPeer;
|
avatarPeer = customPeer;
|
||||||
@ -92,6 +91,11 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
|||||||
description = lang('GiftUnique', { title: transaction.starGift.title, number: transaction.starGift.number });
|
description = lang('GiftUnique', { title: transaction.starGift.title, number: transaction.starGift.number });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transaction.isPostsSearch) {
|
||||||
|
title = getTransactionTitle(oldLang, lang, transaction);
|
||||||
|
description = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (transaction.photo) {
|
if (transaction.photo) {
|
||||||
avatarPeer = undefined;
|
avatarPeer = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { getMessageLink } from '../../../../global/helpers';
|
|||||||
import {
|
import {
|
||||||
buildStarsTransactionCustomPeer,
|
buildStarsTransactionCustomPeer,
|
||||||
formatStarsTransactionAmount,
|
formatStarsTransactionAmount,
|
||||||
|
shouldUseCustomPeer,
|
||||||
} from '../../../../global/helpers/payments';
|
} from '../../../../global/helpers/payments';
|
||||||
import {
|
import {
|
||||||
selectCanPlayAnimatedEmojis,
|
selectCanPlayAnimatedEmojis,
|
||||||
@ -97,8 +98,8 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const giftAttributes = isUniqueGift ? getGiftAttributes(gift) : undefined;
|
const giftAttributes = isUniqueGift ? getGiftAttributes(gift) : undefined;
|
||||||
|
|
||||||
const customPeer = (transaction.peer && transaction.peer.type !== 'peer'
|
const customPeer = (transaction.peer && shouldUseCustomPeer(transaction)
|
||||||
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
&& buildStarsTransactionCustomPeer(transaction)) || undefined;
|
||||||
|
|
||||||
const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined;
|
const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined;
|
||||||
const toName = transaction.peer && oldLang(getStarsPeerTitleKey(transaction.peer));
|
const toName = transaction.peer && oldLang(getStarsPeerTitleKey(transaction.peer));
|
||||||
@ -123,8 +124,8 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
|| (isGiftUpgrade && starGift?.type === 'starGiftUnique' ? starGift.title : undefined)
|
|| (isGiftUpgrade && starGift?.type === 'starGiftUnique' ? starGift.title : undefined)
|
||||||
|| (media ? mediaText : undefined);
|
|| (media ? mediaText : undefined);
|
||||||
|
|
||||||
const shouldDisplayAvatar = !media && !sticker;
|
const shouldDisplayAvatar = !media && !sticker && !transaction.isPostsSearch;
|
||||||
const avatarPeer = !photo ? (peer || customPeer) : undefined;
|
const avatarPeer = !photo ? ((!shouldUseCustomPeer(transaction) && peer) || customPeer) : undefined;
|
||||||
|
|
||||||
const uniqueGiftHeader = isUniqueGift && (
|
const uniqueGiftHeader = isUniqueGift && (
|
||||||
<div className={buildClassName(styles.header, styles.uniqueGift)}>
|
<div className={buildClassName(styles.header, styles.uniqueGift)}>
|
||||||
@ -161,7 +162,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
{shouldDisplayAvatar && (
|
{shouldDisplayAvatar && (
|
||||||
<Avatar peer={avatarPeer} webPhoto={photo} size="giant" />
|
<Avatar peer={avatarPeer} webPhoto={photo} size="giant" />
|
||||||
)}
|
)}
|
||||||
{!sticker && (
|
{!sticker && !transaction.isPostsSearch && (
|
||||||
<img
|
<img
|
||||||
className={buildClassName(styles.starsBackground)}
|
className={buildClassName(styles.starsBackground)}
|
||||||
src={StarsBackground}
|
src={StarsBackground}
|
||||||
@ -236,10 +237,12 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
peerLabel = oldLang('Stars.Transaction.Via');
|
peerLabel = oldLang('Stars.Transaction.Via');
|
||||||
}
|
}
|
||||||
|
|
||||||
tableData.push([
|
if (!transaction.isPostsSearch) {
|
||||||
peerLabel,
|
tableData.push([
|
||||||
peerId ? { chatId: peerId } : toName || '',
|
peerLabel,
|
||||||
]);
|
peerId ? { chatId: peerId } : toName || '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (transaction.starRefCommision && transaction.paidMessages) {
|
if (transaction.starRefCommision && transaction.paidMessages) {
|
||||||
tableData.push([
|
tableData.push([
|
||||||
@ -329,8 +332,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const currencyAmount = modal?.transaction.amount;
|
const currencyAmount = modal?.transaction.amount;
|
||||||
const starsGiftSticker = modal?.transaction.isGift
|
const starsGiftSticker = modal?.transaction.isGift
|
||||||
&& currencyAmount?.currency === STARS_CURRENCY_CODE ? selectGiftStickerForStars(global, currencyAmount?.amount)
|
? (currencyAmount?.currency === STARS_CURRENCY_CODE ? selectGiftStickerForStars(global, currencyAmount?.amount)
|
||||||
: selectGiftStickerForTon(global, currencyAmount?.amount);
|
: selectGiftStickerForTon(global, currencyAmount?.amount)) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peer,
|
peer,
|
||||||
|
|||||||
@ -53,7 +53,7 @@
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
|
|
||||||
transition: background-color 0.15s, color 0.15s;
|
transition: background-color 0.15s, color 0.15s, opacity 0.15s;
|
||||||
|
|
||||||
// @optimization
|
// @optimization
|
||||||
&:active,
|
&:active,
|
||||||
|
|||||||
@ -49,6 +49,7 @@ type OwnProps = {
|
|||||||
onUpClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
onUpClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onDownClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
onDownClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onSpinnerClick?: NoneToVoidFunction;
|
onSpinnerClick?: NoneToVoidFunction;
|
||||||
|
onEnter?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SearchInput: FC<OwnProps> = ({
|
const SearchInput: FC<OwnProps> = ({
|
||||||
@ -80,6 +81,7 @@ const SearchInput: FC<OwnProps> = ({
|
|||||||
onUpClick,
|
onUpClick,
|
||||||
onDownClick,
|
onDownClick,
|
||||||
onSpinnerClick,
|
onSpinnerClick,
|
||||||
|
onEnter,
|
||||||
}) => {
|
}) => {
|
||||||
let inputRef = useRef<HTMLInputElement>();
|
let inputRef = useRef<HTMLInputElement>();
|
||||||
if (ref) {
|
if (ref) {
|
||||||
@ -125,8 +127,22 @@ const SearchInput: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = useLastCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = useLastCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (!resultsItemSelector) return;
|
if (e.key === 'Enter') {
|
||||||
if (e.key === 'ArrowDown' || 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;
|
const element = document.querySelector(resultsItemSelector) as HTMLElement;
|
||||||
if (element) {
|
if (element) {
|
||||||
element.focus();
|
element.focus();
|
||||||
|
|||||||
@ -5,8 +5,11 @@ import { getServerTime } from '../../util/serverTime';
|
|||||||
|
|
||||||
import useInterval from '../../hooks/schedulers/useInterval';
|
import useInterval from '../../hooks/schedulers/useInterval';
|
||||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import AnimatedCounter from '../common/AnimatedCounter';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
langKey: string;
|
langKey: string;
|
||||||
endsAt: number;
|
endsAt: number;
|
||||||
@ -16,7 +19,8 @@ type OwnProps = {
|
|||||||
const UPDATE_FREQUENCY = 500; // Sometimes second gets skipped if using 1000
|
const UPDATE_FREQUENCY = 500; // Sometimes second gets skipped if using 1000
|
||||||
|
|
||||||
const TextTimer: FC<OwnProps> = ({ langKey, endsAt, onEnd }) => {
|
const TextTimer: FC<OwnProps> = ({ langKey, endsAt, onEnd }) => {
|
||||||
const lang = useOldLang();
|
const lang = useLang();
|
||||||
|
const oldLang = useOldLang();
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
const serverTime = getServerTime();
|
const serverTime = getServerTime();
|
||||||
@ -32,11 +36,33 @@ const TextTimer: FC<OwnProps> = ({ langKey, endsAt, onEnd }) => {
|
|||||||
if (!isActive) return undefined;
|
if (!isActive) return undefined;
|
||||||
|
|
||||||
const timeLeft = endsAt - serverTime;
|
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 (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{lang(langKey, formattedTime)}
|
{oldLang(langKey, time)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -110,6 +110,10 @@ export const TODO_ITEMS_LIMIT = 30;
|
|||||||
export const TODO_TITLE_LENGTH_LIMIT = 32;
|
export const TODO_TITLE_LENGTH_LIMIT = 32;
|
||||||
export const TODO_ITEM_LENGTH_LIMIT = 64;
|
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
|
// Suggested Posts defaults
|
||||||
export const STARS_SUGGESTED_POST_AMOUNT_MAX = 100000;
|
export const STARS_SUGGESTED_POST_AMOUNT_MAX = 100000;
|
||||||
export const STARS_SUGGESTED_POST_AMOUNT_MIN = 5;
|
export const STARS_SUGGESTED_POST_AMOUNT_MIN = 5;
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiMessageSearchContext, ApiPeer, ApiTopic,
|
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiMessageSearchContext, ApiPeer, ApiSearchPostsFlood, ApiTopic,
|
||||||
ApiUserStatus,
|
ApiUserStatus,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../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 { isDeepLink, tryParseDeepLink } from '../../../util/deepLinkParser';
|
||||||
import { toChannelId } from '../../../util/entities/ids';
|
import { toChannelId } from '../../../util/entities/ids';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
|
import { getTranslationFn } from '../../../util/localization';
|
||||||
|
import { formatStarsAsText } from '../../../util/localization/format';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { isChatChannel, isChatGroup } from '../../helpers/chats';
|
import { isChatChannel, isChatGroup } from '../../helpers/chats';
|
||||||
@ -158,6 +162,23 @@ addActionHandler('searchPopularBotApps', async (global, actions, payload): Promi
|
|||||||
setGlobal(global);
|
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: {
|
async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||||
query?: string;
|
query?: string;
|
||||||
type: ApiGlobalMessageSearchType;
|
type: ApiGlobalMessageSearchType;
|
||||||
@ -175,6 +196,12 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
query = '', type, context, offsetRate, offsetId, offsetPeer,
|
query = '', type, context, offsetRate, offsetId, offsetPeer,
|
||||||
peer, maxDate, minDate, shouldResetResultsByType, tabId = getCurrentTabId(),
|
peer, maxDate, minDate, shouldResetResultsByType, tabId = getCurrentTabId(),
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
|
if (type === 'publicPosts') {
|
||||||
|
global = updateGlobalSearchFetchingStatus(global, { publicPosts: true }, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
|
||||||
let result: {
|
let result: {
|
||||||
messages: ApiMessage[];
|
messages: ApiMessage[];
|
||||||
userStatusesById?: Record<number, ApiUserStatus>;
|
userStatusesById?: Record<number, ApiUserStatus>;
|
||||||
@ -184,10 +211,13 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
nextOffsetRate?: number;
|
nextOffsetRate?: number;
|
||||||
nextOffsetId?: number;
|
nextOffsetId?: number;
|
||||||
nextOffsetPeerId?: string;
|
nextOffsetPeerId?: string;
|
||||||
|
searchFlood?: ApiSearchPostsFlood;
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
|
||||||
let messageLink: ApiMessage | undefined;
|
let messageLink: ApiMessage | undefined;
|
||||||
|
|
||||||
|
const previousSearchFlood = selectTabState(global, tabId).globalSearch.searchFlood;
|
||||||
|
|
||||||
if (peer) {
|
if (peer) {
|
||||||
const inChatResultRequest = callApi('searchMessagesInChat', {
|
const inChatResultRequest = callApi('searchMessagesInChat', {
|
||||||
peer,
|
peer,
|
||||||
@ -256,7 +286,7 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
}
|
}
|
||||||
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
|
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
|
||||||
if (!result || (query !== '' && query !== currentSearchQuery)) {
|
if (!result || (query !== '' && query !== currentSearchQuery)) {
|
||||||
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
global = updateGlobalSearchFetchingStatus(global, { messages: false, publicPosts: false }, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -269,6 +299,8 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
messages, userStatusesById, totalCount, nextOffsetRate, nextOffsetId, nextOffsetPeerId,
|
messages, userStatusesById, totalCount, nextOffsetRate, nextOffsetId, nextOffsetPeerId,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
|
const searchFlood = result.searchFlood || previousSearchFlood;
|
||||||
|
|
||||||
if (userStatusesById) {
|
if (userStatusesById) {
|
||||||
global = addUserStatuses(global, userStatusesById);
|
global = addUserStatuses(global, userStatusesById);
|
||||||
}
|
}
|
||||||
@ -285,6 +317,7 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
nextOffsetRate,
|
nextOffsetRate,
|
||||||
nextOffsetId,
|
nextOffsetId,
|
||||||
nextOffsetPeerId,
|
nextOffsetPeerId,
|
||||||
|
searchFlood,
|
||||||
tabId,
|
tabId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -298,6 +331,20 @@ async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
|
|
||||||
setGlobal(global);
|
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 }) {
|
async function getMessageByPublicLink(global: GlobalState, link: { username: string; messageId: number }) {
|
||||||
|
|||||||
@ -113,7 +113,7 @@ addActionHandler('performMiddleSearch', async (global, actions, payload): Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'channels') {
|
if (type === 'channels') {
|
||||||
result = await callApi('searchHashtagPosts', {
|
result = await callApi('searchPublicPosts', {
|
||||||
hashtag: query!,
|
hashtag: query!,
|
||||||
limit: MESSAGE_SEARCH_SLICE,
|
limit: MESSAGE_SEARCH_SLICE,
|
||||||
offsetId,
|
offsetId,
|
||||||
|
|||||||
@ -12,9 +12,12 @@ addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionRetur
|
|||||||
const { query, tabId = getCurrentTabId() } = payload;
|
const { query, tabId = getCurrentTabId() } = payload;
|
||||||
const { chatId, currentContent } = selectTabState(global, tabId).globalSearch;
|
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;
|
? { chats: !chatId, messages: true } : undefined;
|
||||||
|
|
||||||
|
actions.checkSearchPostsFlood({ query, tabId });
|
||||||
|
|
||||||
return updateGlobalSearch(global, {
|
return updateGlobalSearch(global, {
|
||||||
globalResults: {},
|
globalResults: {},
|
||||||
localResults: {},
|
localResults: {},
|
||||||
|
|||||||
@ -6,8 +6,6 @@ import type {
|
|||||||
ApiRequestInputSavedStarGift,
|
ApiRequestInputSavedStarGift,
|
||||||
ApiStarsAmount,
|
ApiStarsAmount,
|
||||||
ApiStarsTransaction,
|
ApiStarsTransaction,
|
||||||
ApiStarsTransactionPeer,
|
|
||||||
ApiStarsTransactionPeerPeer,
|
|
||||||
ApiTypeCurrencyAmount,
|
ApiTypeCurrencyAmount,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { CustomPeer } from '../../types';
|
import type { CustomPeer } from '../../types';
|
||||||
@ -259,10 +257,25 @@ export function getRequestInputSavedStarGift<T extends GlobalState>(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldUseCustomPeer(transaction: ApiStarsTransaction) {
|
||||||
|
return transaction.peer.type !== 'peer' || Boolean(transaction.isPostsSearch);
|
||||||
|
}
|
||||||
|
|
||||||
export function buildStarsTransactionCustomPeer(
|
export function buildStarsTransactionCustomPeer(
|
||||||
peer: Exclude<ApiStarsTransactionPeer, ApiStarsTransactionPeerPeer>,
|
transaction: ApiStarsTransaction,
|
||||||
isForTon?: boolean,
|
|
||||||
): CustomPeer {
|
): 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') {
|
if (peer.type === 'appStore') {
|
||||||
return {
|
return {
|
||||||
avatarIcon: 'star',
|
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 { GlobalSearchContent } from '../../types';
|
||||||
import type { GlobalState, TabArgs, TabState } from '../types';
|
import type { GlobalState, TabArgs, TabState } from '../types';
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
|||||||
nextOffsetRate?: number,
|
nextOffsetRate?: number,
|
||||||
nextOffsetId?: number,
|
nextOffsetId?: number,
|
||||||
nextOffsetPeerId?: string,
|
nextOffsetPeerId?: string,
|
||||||
|
searchFlood?: ApiSearchPostsFlood,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
const { resultsByType } = selectTabState(global, tabId).globalSearch || {};
|
const { resultsByType } = selectTabState(global, tabId).globalSearch || {};
|
||||||
@ -52,8 +53,12 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
|||||||
(newId) => foundIdsForType.includes(getSearchResultKey(newFoundMessagesById[newId])),
|
(newId) => foundIdsForType.includes(getSearchResultKey(newFoundMessagesById[newId])),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
global = updateGlobalSearchFetchingStatus(global, {
|
||||||
|
messages: false,
|
||||||
|
publicPosts: false,
|
||||||
|
}, tabId);
|
||||||
return updateGlobalSearch(global, {
|
return updateGlobalSearch(global, {
|
||||||
|
searchFlood,
|
||||||
resultsByType: {
|
resultsByType: {
|
||||||
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
||||||
[type]: {
|
[type]: {
|
||||||
@ -74,9 +79,13 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
|||||||
const foundIds = Array.prototype.concat(prevFoundIds, newFoundIds);
|
const foundIds = Array.prototype.concat(prevFoundIds, newFoundIds);
|
||||||
const foundOrPrevFoundIds = areSortedArraysEqual(prevFoundIds, foundIds) ? prevFoundIds : foundIds;
|
const foundOrPrevFoundIds = areSortedArraysEqual(prevFoundIds, foundIds) ? prevFoundIds : foundIds;
|
||||||
|
|
||||||
global = updateGlobalSearchFetchingStatus(global, { messages: false }, tabId);
|
global = updateGlobalSearchFetchingStatus(global, {
|
||||||
|
messages: false,
|
||||||
|
publicPosts: false,
|
||||||
|
}, tabId);
|
||||||
|
|
||||||
return updateGlobalSearch(global, {
|
return updateGlobalSearch(global, {
|
||||||
|
searchFlood,
|
||||||
resultsByType: {
|
resultsByType: {
|
||||||
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
...(selectTabState(global, tabId).globalSearch || {}).resultsByType,
|
||||||
[type]: {
|
[type]: {
|
||||||
@ -91,7 +100,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; botApps?: boolean },
|
global: T, newState: { chats?: boolean; messages?: boolean; botApps?: boolean; publicPosts?: boolean },
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
return updateGlobalSearch(global, {
|
return updateGlobalSearch(global, {
|
||||||
|
|||||||
@ -418,6 +418,9 @@ export interface ActionPayloads {
|
|||||||
shouldCheckFetchingMessagesStatus?: boolean;
|
shouldCheckFetchingMessagesStatus?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
searchPopularBotApps: WithTabId | undefined;
|
searchPopularBotApps: WithTabId | undefined;
|
||||||
|
checkSearchPostsFlood: {
|
||||||
|
query?: string;
|
||||||
|
} & WithTabId;
|
||||||
addRecentlyFoundChatId: {
|
addRecentlyFoundChatId: {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import type {
|
|||||||
ApiReceiptRegular,
|
ApiReceiptRegular,
|
||||||
ApiSavedGifts,
|
ApiSavedGifts,
|
||||||
ApiSavedStarGift,
|
ApiSavedStarGift,
|
||||||
|
ApiSearchPostsFlood,
|
||||||
ApiSponsoredPeer,
|
ApiSponsoredPeer,
|
||||||
ApiStarGift,
|
ApiStarGift,
|
||||||
ApiStarGiftAttribute,
|
ApiStarGiftAttribute,
|
||||||
@ -249,10 +250,12 @@ export type TabState = {
|
|||||||
chatId?: string;
|
chatId?: string;
|
||||||
foundTopicIds?: number[];
|
foundTopicIds?: number[];
|
||||||
sponsoredPeer?: ApiSponsoredPeer;
|
sponsoredPeer?: ApiSponsoredPeer;
|
||||||
|
searchFlood?: ApiSearchPostsFlood;
|
||||||
fetchingStatus?: {
|
fetchingStatus?: {
|
||||||
chats?: boolean;
|
chats?: boolean;
|
||||||
messages?: boolean;
|
messages?: boolean;
|
||||||
botApps?: boolean;
|
botApps?: boolean;
|
||||||
|
publicPosts?: boolean;
|
||||||
};
|
};
|
||||||
isClosing?: boolean;
|
isClosing?: boolean;
|
||||||
localResults?: {
|
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;
|
stargiftUpgrade?: true;
|
||||||
businessTransfer?: true;
|
businessTransfer?: true;
|
||||||
stargiftResale?: true;
|
stargiftResale?: true;
|
||||||
|
postsSearch?: true;
|
||||||
id: string;
|
id: string;
|
||||||
amount: Api.TypeStarsAmount;
|
amount: Api.TypeStarsAmount;
|
||||||
date: int;
|
date: int;
|
||||||
@ -16548,6 +16549,7 @@ namespace Api {
|
|||||||
stargiftUpgrade?: true;
|
stargiftUpgrade?: true;
|
||||||
businessTransfer?: true;
|
businessTransfer?: true;
|
||||||
stargiftResale?: true;
|
stargiftResale?: true;
|
||||||
|
postsSearch?: true;
|
||||||
id: string;
|
id: string;
|
||||||
amount: Api.TypeStarsAmount;
|
amount: Api.TypeStarsAmount;
|
||||||
date: int;
|
date: int;
|
||||||
|
|||||||
@ -1368,7 +1368,7 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer;
|
|||||||
starsTransactionPeerAds#60682812 = StarsTransactionPeer;
|
starsTransactionPeerAds#60682812 = StarsTransactionPeer;
|
||||||
starsTransactionPeerAPI#f9677aad = StarsTransactionPeer;
|
starsTransactionPeerAPI#f9677aad = StarsTransactionPeer;
|
||||||
starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption;
|
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;
|
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;
|
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;
|
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.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.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.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.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.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||||
|
|||||||
@ -284,6 +284,7 @@
|
|||||||
"channels.searchPosts",
|
"channels.searchPosts",
|
||||||
"channels.reportSpam",
|
"channels.reportSpam",
|
||||||
"channels.updatePaidMessagesPrice",
|
"channels.updatePaidMessagesPrice",
|
||||||
|
"channels.checkSearchPostsFlood",
|
||||||
"channels.toggleAutotranslation",
|
"channels.toggleAutotranslation",
|
||||||
"bots.getBotRecommendations",
|
"bots.getBotRecommendations",
|
||||||
"bots.canSendMessage",
|
"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;
|
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;
|
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,
|
ChatList,
|
||||||
ChannelList,
|
ChannelList,
|
||||||
BotApps,
|
BotApps,
|
||||||
|
PublicPosts,
|
||||||
Media,
|
Media,
|
||||||
Links,
|
Links,
|
||||||
Files,
|
Files,
|
||||||
|
|||||||
28
src/types/language.d.ts
vendored
28
src/types/language.d.ts
vendored
@ -1333,6 +1333,7 @@ export interface LangPair {
|
|||||||
'SearchTabMusic': undefined;
|
'SearchTabMusic': undefined;
|
||||||
'SearchTabVoice': undefined;
|
'SearchTabVoice': undefined;
|
||||||
'SearchTabMessages': undefined;
|
'SearchTabMessages': undefined;
|
||||||
|
'SearchTabPublicPosts': undefined;
|
||||||
'StarsTransactionsAll': undefined;
|
'StarsTransactionsAll': undefined;
|
||||||
'StarsTransactionsIncoming': undefined;
|
'StarsTransactionsIncoming': undefined;
|
||||||
'StarsTransactionsOutgoing': undefined;
|
'StarsTransactionsOutgoing': undefined;
|
||||||
@ -1351,6 +1352,7 @@ export interface LangPair {
|
|||||||
'ProfileTabSharedGroups': undefined;
|
'ProfileTabSharedGroups': undefined;
|
||||||
'ProfileTabSimilarChannels': undefined;
|
'ProfileTabSimilarChannels': undefined;
|
||||||
'ProfileTabSimilarBots': undefined;
|
'ProfileTabSimilarBots': undefined;
|
||||||
|
'ProfileTabPublicPosts': undefined;
|
||||||
'ActionUnsupportedTitle': undefined;
|
'ActionUnsupportedTitle': undefined;
|
||||||
'ActionUnsupportedDescription': undefined;
|
'ActionUnsupportedDescription': undefined;
|
||||||
'UnlockMoreSimilarBots': undefined;
|
'UnlockMoreSimilarBots': undefined;
|
||||||
@ -1620,6 +1622,14 @@ export interface LangPair {
|
|||||||
'LabelPayInTON': undefined;
|
'LabelPayInTON': undefined;
|
||||||
'PriceChanged': undefined;
|
'PriceChanged': undefined;
|
||||||
'PayNewPrice': undefined;
|
'PayNewPrice': undefined;
|
||||||
|
'GlobalSearch': undefined;
|
||||||
|
'DescriptionPublicPostsSearch': undefined;
|
||||||
|
'PublicPosts': undefined;
|
||||||
|
'PublicPostsLimitReached': undefined;
|
||||||
|
'PublicPostsPremiumFeatureDescription': undefined;
|
||||||
|
'PublicPostsPremiumFeatureSubtitle': undefined;
|
||||||
|
'PublicPostsSubscribeToPremium': undefined;
|
||||||
|
'PostsSearchTransaction': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V = LangVariable> {
|
export interface LangPairWithVariables<V = LangVariable> {
|
||||||
@ -2816,6 +2826,18 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'originalAmount': V;
|
'originalAmount': V;
|
||||||
'newAmount': V;
|
'newAmount': V;
|
||||||
};
|
};
|
||||||
|
'ButtonSearchPublicPosts': {
|
||||||
|
'query': V;
|
||||||
|
};
|
||||||
|
'PublicPostsSearchForStars': {
|
||||||
|
'stars': V;
|
||||||
|
};
|
||||||
|
'UnlockTimerPublicPostsSearch': {
|
||||||
|
'time': V;
|
||||||
|
};
|
||||||
|
'NotificationPaidExtraSearch': {
|
||||||
|
'stars': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairPlural {
|
export interface LangPairPlural {
|
||||||
@ -3137,6 +3159,12 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
|||||||
'TextAgeVerificationModal': {
|
'TextAgeVerificationModal': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'RemainingPublicPostsSearch': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'HintPublicPostsSearchQuota': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type RegularLangKey = keyof LangPair;
|
export type RegularLangKey = keyof LangPair;
|
||||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user