Translation: Add Cocoon info (#6646)

This commit is contained in:
zubiden 2026-02-22 23:43:03 +01:00 committed by Alexander Zinchuk
parent 6605a90606
commit baff436709
29 changed files with 352 additions and 43 deletions

View File

@ -84,6 +84,12 @@ declare module '*.jpg' {
const url: string;
export default url;
}
declare module '*.webp' {
const url: string;
export default url;
}
declare module '*.svg' {
const url: string;
export default url;

BIN
src/assets/cocoon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -2608,6 +2608,23 @@
"SettingsDataClearMediaCache" = "Clear Media Cache";
"SettingsDataClearMediaCacheDescription" = "Deletes locally cached media for this account";
"SettingsDataClearMediaDone" = "Media cache cleared";
"TranslateMenuCocoon" = "Translations are powered by 🥚 **Cocoon**. {link}";
"TranslateMenuCocoonLinkText" = "How does it work?";
"CocoonTitle" = "Cocoon";
"CocoonDescription" = "Cocoon (**Co**nfidential **Co**mpute **O**pen **N**etwork)\nhandles AI tasks safely and efficiently.";
"CocoonFeature1Title" = "Private";
"CocoonFeature1Text" = "No third party can access any data, such as translations, inside **{username}**.";
"CocoonFeature1Username" = "@cocoon";
"CocoonFeature1UsernameLink" = "https://t.me/cocoon";
"CocoonFeature2Title" = "Efficient";
"CocoonFeature2Text" = "Cocoon has allowed Telegram to reduce translation cost by 6x.";
"CocoonFeature3Title" = "For Everyone";
"CocoonFeature3Text" = "Any developer can use Cocoon for AI features. Learn more at **{link}**.";
"CocoonFeature3LinkText" = "cocoon.org";
"CocoonFeature3Link" = "https://cocoon.org";
"CocoonFooterText" = "Want to integrate Cocoon into your projects?\nReach out **{link}**";
"CocoonFooterLinkText" = "directly to @cocoon";
"CocoonFooterLink" = "https://t.me/cocoon?direct";
"ChatListAuctionTitle_one" = "Active Auction";
"ChatListAuctionTitle_other" = "{count} Active Auctions";
"ChatListAuctionWinning" = "You are winning";

View File

@ -108,3 +108,4 @@ export { default as ProfileRatingModal } from '../components/modals/profileRatin
export { default as QuickPreviewModal } from '../components/modals/quickPreview/QuickPreviewModal';
export { default as StealthModeModal } from '../components/modals/storyStealthMode/StealthModeModal';
export { default as QuickChatPickerModal } from '../components/modals/quickChatPicker/QuickChatPickerModal';
export { default as CocoonModal } from '../components/modals/cocoon/CocoonModal';

View File

@ -17,6 +17,8 @@ const DEFAULT_PARTICLE_PARAMS = {
centerShift: [0, -36] as const,
};
const MIN_BURST_INTERVAL = 8;
const InteractiveSparkles = ({
color = 'purple',
centerShift = DEFAULT_PARTICLE_PARAMS.centerShift,
@ -25,6 +27,7 @@ const InteractiveSparkles = ({
onRequestAnimation,
}: OwnProps) => {
const canvasRef = useRef<HTMLCanvasElement>();
const lastBurstTimeRef = useRef<number>(0);
useLayoutEffect(() => {
if (isDisabled) return undefined;
@ -41,6 +44,11 @@ const InteractiveSparkles = ({
const animate = () => {
if (isDisabled) return;
const now = Date.now();
if (now - lastBurstTimeRef.current < MIN_BURST_INTERVAL) return;
lastBurstTimeRef.current = now;
setupParticles(canvasRef.current!, {
color: PARTICLE_COLORS[`${color}Gradient`],
centerShift,

View File

@ -8,6 +8,7 @@ import type { IAnchorPosition, MessageListType, ThreadId } from '../../types';
import { MAIN_THREAD_ID } from '../../api/types';
import { ManagementScreens } from '../../types';
import { COCOON_EMOJI_ID } from '../../config';
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
import {
getHasAdminRight,
@ -38,11 +39,14 @@ import { isUserId } from '../../util/entities/ids';
import focusNoScroll from '../../util/focusNoScroll';
import { useHotkeys } from '../../hooks/useHotkeys';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
import CustomEmoji from '../common/CustomEmoji';
import Button from '../ui/Button';
import DropdownMenu from '../ui/DropdownMenu';
import Link from '../ui/Link';
import MenuItem from '../ui/MenuItem';
import MenuSeparator from '../ui/MenuSeparator';
import HeaderMenuContainer from './HeaderMenuContainer.async';
@ -142,9 +146,12 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
unblockUser,
setViewForumAsMessages,
openFrozenAccountModal,
openCocoonModal,
} = getActions();
const menuButtonRef = useRef<HTMLButtonElement>();
const lang = useOldLang();
const oldLang = useOldLang();
const lang = useLang();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [menuAnchor, setMenuAnchor] = useState<IAnchorPosition | undefined>(undefined);
@ -166,7 +173,8 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
joinChannel({ chatId });
if (shouldSendJoinRequest) {
showNotification({
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
message: isChannel ? oldLang('RequestToJoinChannelSentDescription')
: oldLang('RequestToJoinGroupSentDescription'),
});
}
});
@ -239,21 +247,21 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
const getTextWithLanguage = useCallback((langKey: string, langCode: string) => {
const simplified = langCode.split('-')[0];
const translationKey = `TranslateLanguage${simplified.toUpperCase()}`;
const name = lang(translationKey);
const name = oldLang(translationKey);
if (name !== translationKey) {
return lang(langKey, name);
return oldLang(langKey, name);
}
const translatedNames = new Intl.DisplayNames([language], { type: 'language' });
const translatedName = translatedNames.of(langCode)!;
return lang(`${langKey}Other`, translatedName);
}, [language, lang]);
return oldLang(`${langKey}Other`, translatedName);
}, [language, oldLang]);
const buttonText = useMemo(() => {
if (isTranslating) return lang('ShowOriginalButton');
if (isTranslating) return oldLang('ShowOriginalButton');
return getTextWithLanguage('TranslateToButton', translationLanguage);
}, [translationLanguage, getTextWithLanguage, isTranslating, lang]);
}, [translationLanguage, getTextWithLanguage, isTranslating, oldLang]);
const doNotTranslateText = useMemo(() => {
if (!detectedChatLanguage) return undefined;
@ -270,6 +278,10 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
openChatLanguageModal({ chatId });
});
const handleCocoonClick = useLastCallback(() => {
openCocoonModal();
});
const handleDoNotTranslate = useLastCallback(() => {
if (!detectedChatLanguage) return;
@ -294,11 +306,11 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
size="smaller"
className={isOpen ? 'active' : ''}
onClick={onTrigger}
ariaLabel={lang('TranslateMessage')}
ariaLabel={oldLang('TranslateMessage')}
iconName="language"
/>
);
}, [isRightColumnShown, lang]);
}, [isRightColumnShown, oldLang]);
return (
<div className="HeaderActions">
@ -312,12 +324,28 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
{buttonText}
</MenuItem>
<MenuItem icon="replace" onClick={handleChangeLanguage}>
{lang('Chat.Translate.Menu.To')}
{oldLang('Chat.Translate.Menu.To')}
</MenuItem>
<MenuSeparator />
{detectedChatLanguage
&& <MenuItem icon="hand-stop" onClick={handleDoNotTranslate}>{doNotTranslateText}</MenuItem>}
<MenuItem icon="close-circle" onClick={handleHide}>{lang('Hide')}</MenuItem>
<MenuItem icon="close-circle" onClick={handleHide}>{oldLang('Hide')}</MenuItem>
<MenuSeparator />
<MenuItem withWrap onClick={handleCocoonClick}>
{lang('TranslateMenuCocoon', {
link: (
<Link isPrimary onClick={(e) => e.preventDefault()}>
{lang('TranslateMenuCocoonLinkText')}
</Link>
),
}, {
withNodes: true,
withMarkdown: true,
specialReplacement: {
'🥚': <CustomEmoji documentId={COCOON_EMOJI_ID} />,
},
})}
</MenuItem>
</DropdownMenu>
)}
{!isMobile && (
@ -329,7 +357,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
fluid
onClick={handleSubscribeClick}
>
{lang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
{oldLang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
</Button>
)}
{canExpandActions && shouldSendJoinRequest && (
@ -339,7 +367,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
fluid
onClick={handleSubscribeClick}
>
{lang('ChannelJoinRequest')}
{oldLang('ChannelJoinRequest')}
</Button>
)}
{canExpandActions && canStartBot && (
@ -349,7 +377,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
fluid
onClick={handleStartBot}
>
{lang('BotStart')}
{oldLang('BotStart')}
</Button>
)}
{canExpandActions && canRestartBot && (
@ -359,7 +387,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
fluid
onClick={handleRestartBot}
>
{lang('BotRestart')}
{oldLang('BotRestart')}
</Button>
)}
{canExpandActions && canUnblock && (
@ -369,7 +397,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
fluid
onClick={handleUnblock}
>
{lang('Unblock')}
{oldLang('Unblock')}
</Button>
)}
{canSearch && (
@ -379,7 +407,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
color="translucent"
size="smaller"
onClick={handleSearchClick}
ariaLabel={lang('Conversation.SearchPlaceholder')}
ariaLabel={oldLang('Conversation.SearchPlaceholder')}
iconName="search"
/>
)}
@ -404,7 +432,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
size="smaller"
iconName="user"
onClick={handleJoinRequestsClick}
ariaLabel={isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}
ariaLabel={isChannel ? oldLang('SubscribeRequests') : oldLang('MemberRequests')}
>
<div className="badge">{pendingJoinRequests}</div>
</Button>

View File

@ -583,13 +583,13 @@
&:not(.custom-shape) .text-content {
.emoji {
width: calc(1.25 * var(--message-text-size, 1rem));
height: calc(1.25 * var(--message-text-size, 1rem));
background-size: calc(1.25 * var(--message-text-size, 1rem));
width: 1.25em;
height: 1.25em;
background-size: 1.25em;
}
.custom-emoji {
--custom-emoji-size: max(calc(1.25 * var(--message-text-size, 1rem)), 20px);
--custom-emoji-size: max(1.25em, 20px);
width: var(--custom-emoji-size);
height: var(--custom-emoji-size);

View File

@ -15,6 +15,7 @@ import BirthdaySetupModal from './birthday/BirthdaySetupModal.async';
import BoostModal from './boost/BoostModal.async';
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
import ChatlistModal from './chatlist/ChatlistModal.async';
import CocoonModal from './cocoon/CocoonModal.async';
import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async';
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
@ -130,7 +131,8 @@ type ModalKey = keyof Pick<TabState,
'storyStealthModal' |
'isPasskeyModalOpen' |
'birthdaySetupModal' |
'isQuickChatPickerOpen'
'isQuickChatPickerOpen' |
'isCocoonModalOpen'
>;
type StateProps = {
@ -207,6 +209,7 @@ const MODALS: ModalRegistry = {
isPasskeyModalOpen: PasskeyModal,
birthdaySetupModal: BirthdaySetupModal,
isQuickChatPickerOpen: QuickChatPickerModal,
isCocoonModalOpen: CocoonModal,
};
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;

View File

@ -0,0 +1,14 @@
import type { OwnProps } from './CocoonModal';
import { Bundles } from '../../../util/moduleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const CocoonModalAsync = (props: OwnProps) => {
const { modal } = props;
const CocoonModal = useModuleLoader(Bundles.Extra, 'CocoonModal', !modal);
return CocoonModal ? <CocoonModal {...props} /> : undefined;
};
export default CocoonModalAsync;

View File

@ -0,0 +1,26 @@
.header {
position: relative;
margin-top: -1rem;
margin-inline: -1rem;
}
.footer {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.footerText {
font-size: 0.9375rem;
color: var(--color-text-secondary);
text-align: center;
text-wrap: balance;
}
.egg {
transition: transform 0.25s ease-out;
&:hover {
transform: scale(1.1);
}
}

View File

@ -0,0 +1,108 @@
import { memo, useMemo } from '@teact';
import { getActions } from '../../../global';
import type { TabState } from '../../../global/types';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import SafeLink from '../../common/SafeLink';
import Button from '../../ui/Button';
import Link from '../../ui/Link';
import ParticlesHeader from '../common/ParticlesHeader';
import TableAboutModal, { type TableAboutData } from '../common/TableAboutModal';
import styles from './CocoonModal.module.scss';
export type OwnProps = {
modal: TabState['isCocoonModalOpen'];
};
const CocoonModal = ({ modal }: OwnProps) => {
const { closeCocoonModal, openTelegramLink } = getActions();
const isOpen = Boolean(modal);
const lang = useLang();
const handleClose = useLastCallback(() => {
closeCocoonModal();
});
const openLinkAndClose = useLastCallback((url: string) => {
openTelegramLink({ url });
handleClose();
});
const listItemData = useMemo(() => {
const feature1Description = lang('CocoonFeature1Text', {
username: (
<Link
isPrimary
onClick={() => {
openLinkAndClose(lang('CocoonFeature1UsernameLink'));
}}
>
{lang('CocoonFeature1Username')}
</Link>
),
}, { withNodes: true, withMarkdown: true });
const feature3Description = lang('CocoonFeature3Text', {
link: <SafeLink url={lang('CocoonFeature3Link')} text={lang('CocoonFeature3LinkText')} />,
}, {
withNodes: true,
withMarkdown: true,
});
return [
['lock', lang('CocoonFeature1Title'), feature1Description],
['stats', lang('CocoonFeature2Title'), lang('CocoonFeature2Text')],
['gift', lang('CocoonFeature3Title'), feature3Description],
] satisfies TableAboutData;
}, [lang]);
const header = useMemo(() => {
return (
<ParticlesHeader
className={styles.header}
modelClassName={styles.egg}
model="ai-egg"
color="purple"
title={lang('CocoonTitle')}
description={lang('CocoonDescription', undefined, { withNodes: true, withMarkdown: true })}
/>
);
}, [lang]);
const footer = useMemo(() => {
return (
<div className={styles.footer}>
<span className={styles.footerText}>
{lang('CocoonFooterText', {
link: (
<Link isPrimary onClick={() => openLinkAndClose(lang('CocoonFooterLink'))}>
{lang('CocoonFooterLinkText')}
</Link>
),
}, { withNodes: true, withMarkdown: true })}
</span>
<Button iconName="understood" onClick={handleClose}>
{lang('ButtonUnderstood')}
</Button>
</div>
);
}, [lang]);
return (
<TableAboutModal
isOpen={isOpen}
onClose={closeCocoonModal}
listItemData={listItemData}
withSeparator
header={header}
footer={footer}
/>
);
};
export default memo(CocoonModal);

View File

@ -5,6 +5,29 @@
padding-top: 9rem;
}
.ai-egg {
padding-top: 10rem;
background-color: #061029;
background-image: radial-gradient(circle at center, #FFFFFF08 0%, #00000000 50%);
.title {
margin-bottom: 0;
font-size: 2.5rem;
color: transparent;
background-image: linear-gradient(90deg, #67c9ff 0%, #cb56ff 100%);
background-clip: text;
}
.description {
color: #b8c9ef;
:global(b) {
color: white;
}
}
}
.particles {
position: absolute;
top: 0;
@ -41,3 +64,10 @@
transform: scale(1.1);
}
}
.cocoon {
position: absolute;
z-index: 1;
top: 1.75rem;
height: 8.125rem;
}

View File

@ -15,14 +15,17 @@ import SwayingStar from './SwayingStar.tsx';
import styles from './ParticlesHeader.module.scss';
import Cocoon from '../../../assets/cocoon.webp';
interface OwnProps {
model: 'swaying-star' | 'speeding-diamond' | 'sticker';
model: 'swaying-star' | 'speeding-diamond' | 'ai-egg' | 'sticker';
sticker?: ApiSticker;
color: 'purple' | 'gold' | 'blue';
title: TeactNode;
description: TeactNode;
isDisabled?: boolean;
className?: string;
modelClassName?: string;
}
const GIFT_STICKER_SIZE = 8 * REM;
@ -39,6 +42,7 @@ function ParticlesHeader({
description,
isDisabled,
className,
modelClassName,
}: OwnProps) {
const stickerRef = useRef<HTMLDivElement>();
const triggerSparklesRef = useRef<(() => void) | undefined>();
@ -52,7 +56,7 @@ function ParticlesHeader({
});
return (
<div className={buildClassName(styles.root, className)}>
<div className={buildClassName(styles.root, styles[model], className)}>
<InteractiveSparkles
color={color}
centerShift={PARTICLE_PARAMS.centerShift}
@ -63,16 +67,27 @@ function ParticlesHeader({
{model === 'swaying-star' ? (
<SwayingStar
className={modelClassName}
color={color as 'purple' | 'gold'}
centerShift={PARTICLE_PARAMS.centerShift}
onMouseMove={handleMouseMove}
/>
) : model === 'ai-egg' ? (
<img
src={Cocoon}
alt=""
role="presentation"
aria-hidden="true"
className={buildClassName(styles.cocoon, modelClassName)}
draggable={false}
onMouseMove={handleMouseMove}
/>
) : model === 'speeding-diamond' ? (
<SpeedingDiamond onMouseMove={handleMouseMove} />
<SpeedingDiamond className={modelClassName} onMouseMove={handleMouseMove} />
) : model === 'sticker' && sticker && (
<div
ref={stickerRef}
className={styles.stickerWrapper}
className={buildClassName(styles.stickerWrapper, modelClassName)}
style={`width: ${GIFT_STICKER_SIZE}px; height: ${GIFT_STICKER_SIZE}px`}
onMouseMove={handleMouseMove}
>

View File

@ -2,6 +2,7 @@ import { memo, useState } from '@teact';
import { requestMutation } from '../../../lib/fasterdom/fasterdom.ts';
import { animateSingle } from '../../../util/animation.ts';
import buildClassName from '../../../util/buildClassName';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets.ts';
import useLastCallback from '../../../hooks/useLastCallback.ts';
@ -13,6 +14,7 @@ import styles from './SpeedingDiamond.module.scss';
import diamondPreviewUrl from '../../../assets/diamond.png';
interface OwnProps {
className?: string;
onMouseMove: NoneToVoidFunction;
}
@ -24,7 +26,7 @@ const SLOWDOWN_DURATION = 1500;
let slowdownTimeout: number | undefined;
let isAnimating = true;
function SpeedingDiamond({ onMouseMove }: OwnProps) {
function SpeedingDiamond({ className, onMouseMove }: OwnProps) {
const [speed, setSpeed] = useState(MIN_SPEED);
const handleMouseMove = useLastCallback(() => {
@ -58,7 +60,7 @@ function SpeedingDiamond({ onMouseMove }: OwnProps) {
});
return (
<div className={styles.root}>
<div className={buildClassName(styles.root, className)}>
<div
className={styles.diamond}
onMouseMove={handleMouseMove}

View File

@ -8,6 +8,7 @@ import useLastCallback from '../../../hooks/useLastCallback.ts';
import styles from './SwayingStar.module.scss';
interface OwnProps {
className?: string;
color: 'purple' | 'gold';
centerShift: readonly [number, number];
onMouseMove: NoneToVoidFunction;
@ -16,6 +17,7 @@ interface OwnProps {
const INTERACTIVE_RADIUS = 50;
function SwayingStar({
className,
color,
centerShift,
onMouseMove,
@ -48,7 +50,7 @@ function SwayingStar({
return (
<div
className={styles.root}
className={buildClassName(styles.root, className)}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>

View File

@ -75,8 +75,10 @@
align-items: center;
}
.listItem {
padding-bottom: 0.5rem;
.listItems {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.listItemTitle {
@ -86,5 +88,5 @@
.separator {
width: calc(100% + 2rem); // Hack to cover modal paddings
margin-block: 1rem;
margin-bottom: 0.5rem;
}

View File

@ -63,7 +63,7 @@ const TableAboutModal = ({
</div>
)}
{header}
<div>
<div className={styles.listItems}>
{listItemData?.map(([icon, title, subtitle]) => {
return (
<ListItem

View File

@ -1,6 +1,3 @@
import type { FC } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
import useLastCallback from '../../hooks/useLastCallback';
@ -16,9 +13,9 @@ type OwnProps = {
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
};
const Link: FC<OwnProps> = ({
const Link = ({
children, isPrimary, className, isRtl, withMultilineFix, onClick,
}) => {
}: OwnProps) => {
const handleClick = useLastCallback((e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
onClick!(e);

View File

@ -161,11 +161,20 @@
font-weight: var(--font-weight-semibold);
}
a:hover {
text-decoration: none;
}
&.wrap {
display: block;
white-space: normal;
}
&.text-only {
padding-inline: 0.5rem;
line-height: 1.375;
}
&.menu-custom-emoji-sets {
margin: 0 0.25rem;
padding: 0.5rem 0.75rem;

View File

@ -91,6 +91,7 @@ const MenuItem = (props: MenuItemProps) => {
destructive && 'destructive',
!isTouchScreen && 'compact',
withWrap && 'wrap',
!icon && !customIcon && 'text-only',
);
const content = (

View File

@ -345,6 +345,7 @@ export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must
export const VERIFICATION_CODES_USER_ID = '489000';
export const ANONYMOUS_USER_ID = '2666000';
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
export const COCOON_EMOJI_ID = '5197252827247841976';
export const LOCAL_MESSAGES_LIMIT = 1e6; // 1M
export const CHANNEL_ID_BASE = 10n ** 12n;
export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif';

View File

@ -961,3 +961,12 @@ addActionHandler('openQuickChatPicker', (global, actions, payload): ActionReturn
});
addTabStateResetterAction('closeQuickChatPicker', 'isQuickChatPickerOpen');
addActionHandler('openCocoonModal', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
return updateTabState(global, {
isCocoonModalOpen: true,
}, tabId);
});
addTabStateResetterAction('closeCocoonModal', 'isCocoonModalOpen');

View File

@ -2996,6 +2996,9 @@ export interface ActionPayloads {
chatId: string;
messageId: number;
};
openCocoonModal: WithTabId | undefined;
closeCocoonModal: WithTabId | undefined;
}
export interface RequiredActionPayloads {

View File

@ -975,4 +975,6 @@ export type TabState = {
isWaitingForStarGiftTransfer?: true;
insertingPeerIdMention?: string;
shouldSaveAttachmentsCompression?: boolean;
isCocoonModalOpen?: boolean;
};

View File

@ -1872,6 +1872,7 @@ payments.getStarsStatus#4ea9b3bf flags:# ton:flags.0?true peer:InputPeer = payme
payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true ton:flags.4?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus;
payments.sendStarsForm#7998c914 form_id:long invoice:InputInvoice = payments.PaymentResult;
payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates;
payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true ton:flags.1?true peer:InputPeer = payments.StarsRevenueStats;
payments.getStarsTransactionsByID#2dca16b8 flags:# ton:flags.0?true peer:InputPeer id:Vector<InputStarsTransaction> = payments.StarsStatus;
payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector<StarsGiftOption>;
payments.getStarsSubscriptions#32512c5 flags:# missing_balance:flags.0?true peer:InputPeer offset:string = payments.StarsStatus;

View File

@ -331,6 +331,7 @@
"payments.getStarsTransactions",
"payments.getStarsTransactionsByID",
"payments.getStarsSubscriptions",
"payments.getStarsRevenueStats",
"payments.changeStarsSubscription",
"payments.fulfillStarsSubscription",
"payments.sendStarsForm",

View File

@ -1916,6 +1916,19 @@ export interface LangPair {
'SettingsDataClearMediaCache': undefined;
'SettingsDataClearMediaCacheDescription': undefined;
'SettingsDataClearMediaDone': undefined;
'TranslateMenuCocoonLinkText': undefined;
'CocoonTitle': undefined;
'CocoonDescription': undefined;
'CocoonFeature1Title': undefined;
'CocoonFeature1Username': undefined;
'CocoonFeature1UsernameLink': undefined;
'CocoonFeature2Title': undefined;
'CocoonFeature2Text': undefined;
'CocoonFeature3Title': undefined;
'CocoonFeature3LinkText': undefined;
'CocoonFeature3Link': undefined;
'CocoonFooterLinkText': undefined;
'CocoonFooterLink': undefined;
'ChatListAuctionWinning': undefined;
'ChatListAuctionOutbid': undefined;
'ChatListAuctionView': undefined;
@ -3360,6 +3373,18 @@ export interface LangPairWithVariables<V = LangVariable> {
'status': V;
'onlineCount': V;
};
'TranslateMenuCocoon': {
'link': V;
};
'CocoonFeature1Text': {
'username': V;
};
'CocoonFeature3Text': {
'link': V;
};
'CocoonFooterText': {
'link': V;
};
'ChatListAuctionMixed': {
'winCount': V;
'outbidCount': V;

View File

@ -1,5 +1,3 @@
import type React from '../lib/teact/teact';
const stopEvent = (e: React.UIEvent | Event | React.FormEvent) => {
e.stopPropagation();
e.preventDefault();

View File

@ -164,7 +164,7 @@ export default function createConfig(
],
},
{
test: /\.(woff(2)?|ttf|eot|svg|png|jpg|tgs)(\?v=\d+\.\d+\.\d+)?$/,
test: /\.(woff(2)?|ttf|eot|svg|png|jpg|tgs|webp)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset/resource',
},
{