Ai Tone Editor: Support custom AI tone editing (#6965)
This commit is contained in:
parent
d82240b06c
commit
ac713094f8
@ -27,7 +27,8 @@ type Limit =
|
|||||||
| 'chatlist_joined_limit'
|
| 'chatlist_joined_limit'
|
||||||
| 'recommended_channels_limit'
|
| 'recommended_channels_limit'
|
||||||
| 'saved_dialogs_pinned_limit'
|
| 'saved_dialogs_pinned_limit'
|
||||||
| 'reactions_user_max';
|
| 'reactions_user_max'
|
||||||
|
| 'aicompose_tone_saved_limit';
|
||||||
type LimitKey = `${Limit}_${LimitType}`;
|
type LimitKey = `${Limit}_${LimitType}`;
|
||||||
type LimitsConfig = Record<LimitKey, number>;
|
type LimitsConfig = Record<LimitKey, number>;
|
||||||
|
|
||||||
@ -135,8 +136,6 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
aicompose_tone_examples_num?: number;
|
aicompose_tone_examples_num?: number;
|
||||||
aicompose_tone_title_length_max?: number;
|
aicompose_tone_title_length_max?: number;
|
||||||
aicompose_tone_prompt_length_max?: number;
|
aicompose_tone_prompt_length_max?: number;
|
||||||
aicompose_tone_saved_limit_default?: number;
|
|
||||||
aicompose_tone_saved_limit_premium?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||||
@ -217,6 +216,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
savedDialogsPinned: getLimit(appConfig, 'saved_dialogs_pinned_limit', 'savedDialogsPinned'),
|
savedDialogsPinned: getLimit(appConfig, 'saved_dialogs_pinned_limit', 'savedDialogsPinned'),
|
||||||
maxReactions: getLimit(appConfig, 'reactions_user_max', 'maxReactions'),
|
maxReactions: getLimit(appConfig, 'reactions_user_max', 'maxReactions'),
|
||||||
moreAccounts: DEFAULT_LIMITS.moreAccounts,
|
moreAccounts: DEFAULT_LIMITS.moreAccounts,
|
||||||
|
aiComposeToneSaved: getLimit(appConfig, 'aicompose_tone_saved_limit', 'aiComposeToneSaved'),
|
||||||
},
|
},
|
||||||
contactNoteLimit: appConfig.contact_note_length_limit,
|
contactNoteLimit: appConfig.contact_note_length_limit,
|
||||||
hash,
|
hash,
|
||||||
@ -284,8 +284,6 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
aiComposeToneExamplesNum: appConfig.aicompose_tone_examples_num,
|
aiComposeToneExamplesNum: appConfig.aicompose_tone_examples_num,
|
||||||
aiComposeToneTitleLengthMax: appConfig.aicompose_tone_title_length_max,
|
aiComposeToneTitleLengthMax: appConfig.aicompose_tone_title_length_max,
|
||||||
aiComposeTonePromptLengthMax: appConfig.aicompose_tone_prompt_length_max,
|
aiComposeTonePromptLengthMax: appConfig.aicompose_tone_prompt_length_max,
|
||||||
aiComposeToneSavedLimitDefault: appConfig.aicompose_tone_saved_limit_default,
|
|
||||||
aiComposeToneSavedLimitPremium: appConfig.aicompose_tone_saved_limit_premium,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1004,6 +1004,10 @@ export function buildWebPage(webPage: GramJs.TypeWebPage): ApiWebPage | undefine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const attributeAiTone = attributes?.find((a): a is GramJs.WebPageAttributeAiComposeTone => (
|
||||||
|
a instanceof GramJs.WebPageAttributeAiComposeTone
|
||||||
|
));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mediaType: 'webpage',
|
mediaType: 'webpage',
|
||||||
webpageType: 'full',
|
webpageType: 'full',
|
||||||
@ -1026,6 +1030,7 @@ export function buildWebPage(webPage: GramJs.TypeWebPage): ApiWebPage | undefine
|
|||||||
gift,
|
gift,
|
||||||
auction,
|
auction,
|
||||||
stickers,
|
stickers,
|
||||||
|
aiComposeToneEmojiId: attributeAiTone?.emojiId.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -64,7 +64,9 @@ import {
|
|||||||
buildApiSponsoredMessageReportResult,
|
buildApiSponsoredMessageReportResult,
|
||||||
buildThreadReadState,
|
buildThreadReadState,
|
||||||
} from '../apiBuilders/chats';
|
} from '../apiBuilders/chats';
|
||||||
import { buildApiAiComposeTone, buildApiComposedMessageWithAI, buildApiFormattedText } from '../apiBuilders/common';
|
import {
|
||||||
|
buildApiAiComposeTone, buildApiAiComposeToneExample, buildApiComposedMessageWithAI, buildApiFormattedText,
|
||||||
|
} from '../apiBuilders/common';
|
||||||
import { buildApiTopicWithState } from '../apiBuilders/forums';
|
import { buildApiTopicWithState } from '../apiBuilders/forums';
|
||||||
import {
|
import {
|
||||||
buildMessageMediaContent, buildMessagePollFromMedia, buildMessageTextContent,
|
buildMessageMediaContent, buildMessagePollFromMedia, buildMessageTextContent,
|
||||||
@ -2853,3 +2855,110 @@ export async function fetchAiComposeTones({
|
|||||||
hash: result.hash.toString(),
|
hash: result.hash.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createAiTone({
|
||||||
|
title,
|
||||||
|
emojiId,
|
||||||
|
prompt,
|
||||||
|
shouldDisplayAuthor,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
emojiId: string;
|
||||||
|
prompt: string;
|
||||||
|
shouldDisplayAuthor?: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.aicompose.CreateTone({
|
||||||
|
title,
|
||||||
|
prompt,
|
||||||
|
emojiId: BigInt(emojiId),
|
||||||
|
displayAuthor: shouldDisplayAuthor || undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
return buildApiAiComposeTone(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAiTone({
|
||||||
|
tone,
|
||||||
|
}: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.aicompose.DeleteTone({
|
||||||
|
tone: buildInputAiComposeTone(tone),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAiTone({
|
||||||
|
tone,
|
||||||
|
title,
|
||||||
|
emojiId,
|
||||||
|
prompt,
|
||||||
|
shouldDisplayAuthor,
|
||||||
|
}: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
title?: string;
|
||||||
|
emojiId?: string;
|
||||||
|
prompt?: string;
|
||||||
|
shouldDisplayAuthor?: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.aicompose.UpdateTone({
|
||||||
|
tone: buildInputAiComposeTone(tone),
|
||||||
|
title,
|
||||||
|
prompt,
|
||||||
|
emojiId: emojiId ? BigInt(emojiId) : undefined,
|
||||||
|
displayAuthor: shouldDisplayAuthor,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
return buildApiAiComposeTone(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchAiTone({
|
||||||
|
tone,
|
||||||
|
}: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.aicompose.GetTone({
|
||||||
|
tone: buildInputAiComposeTone(tone),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result || !('tones' in result)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tones: result.tones.map(buildApiAiComposeTone),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchAiToneExample({
|
||||||
|
tone,
|
||||||
|
num,
|
||||||
|
}: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
num: number;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.aicompose.GetToneExample({
|
||||||
|
tone: buildInputAiComposeTone(tone),
|
||||||
|
num,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
return buildApiAiComposeToneExample(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveAiTone({
|
||||||
|
tone,
|
||||||
|
unsave,
|
||||||
|
}: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
unsave?: boolean;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.aicompose.SaveTone({
|
||||||
|
tone: buildInputAiComposeTone(tone),
|
||||||
|
unsave: Boolean(unsave),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|||||||
@ -426,6 +426,7 @@ export interface ApiWebPageFull {
|
|||||||
gift?: ApiStarGiftUnique;
|
gift?: ApiStarGiftUnique;
|
||||||
auction?: ApiWebPageAuctionData;
|
auction?: ApiWebPageAuctionData;
|
||||||
stickers?: ApiWebPageStickerData;
|
stickers?: ApiWebPageStickerData;
|
||||||
|
aiComposeToneEmojiId?: string;
|
||||||
hasLargeMedia?: boolean;
|
hasLargeMedia?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -337,8 +337,6 @@ export interface ApiAppConfig {
|
|||||||
aiComposeToneExamplesNum?: number;
|
aiComposeToneExamplesNum?: number;
|
||||||
aiComposeToneTitleLengthMax?: number;
|
aiComposeToneTitleLengthMax?: number;
|
||||||
aiComposeTonePromptLengthMax?: number;
|
aiComposeTonePromptLengthMax?: number;
|
||||||
aiComposeToneSavedLimitDefault?: number;
|
|
||||||
aiComposeToneSavedLimitPremium?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiConfig {
|
export interface ApiConfig {
|
||||||
@ -464,15 +462,17 @@ export type ApiLimitType =
|
|||||||
| 'recommendedChannels'
|
| 'recommendedChannels'
|
||||||
| 'savedDialogsPinned'
|
| 'savedDialogsPinned'
|
||||||
| 'maxReactions'
|
| 'maxReactions'
|
||||||
| 'moreAccounts';
|
| 'moreAccounts'
|
||||||
|
| 'aiComposeToneSaved';
|
||||||
|
|
||||||
export type ApiLimitTypeWithModal = Exclude<ApiLimitType, (
|
export type ApiLimitTypeWithModal = Exclude<ApiLimitType, (
|
||||||
'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels' | 'moreAccounts'
|
'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels' | 'moreAccounts'
|
||||||
| 'maxReactions'
|
| 'maxReactions' | 'aiComposeToneSaved'
|
||||||
)>;
|
)>;
|
||||||
|
|
||||||
export type ApiLimitTypeForPromo = Exclude<ApiLimitType,
|
export type ApiLimitTypeForPromo = Exclude<ApiLimitType,
|
||||||
'uploadMaxFileparts' | 'chatlistInvites' | 'chatlistJoined' | 'savedDialogsPinned' | 'maxReactions'
|
'uploadMaxFileparts' | 'chatlistInvites' | 'chatlistJoined' | 'savedDialogsPinned' | 'maxReactions'
|
||||||
|
| 'aiComposeToneSaved'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type ApiPeerNotifySettings = {
|
export type ApiPeerNotifySettings = {
|
||||||
|
|||||||
1
src/assets/font-icons/reload-arrows.svg
Normal file
1
src/assets/font-icons/reload-arrows.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M22.609 22.136a.226.226 0 0 0-.276.04A8.82 8.82 0 0 1 16 24.859c-4.885 0-8.858-3.975-8.858-8.86 0-.237.034-.466.052-.698a16 16 0 0 0-.73-.13c-.258-.03-.516-.06-.774-.081q-.26 0-.501-.066a11 11 0 0 0-.047.976c0 5.988 4.87 10.859 10.858 10.859 3.183 0 6.045-1.382 8.032-3.572.1-.11.07-.289-.059-.363zM24.77 17.184c.19-.075.388-.12.6-.113q.124 0 .246.012.308.033.614.083a1.6 1.6 0 0 1 .329.093c.068.028.135.06.203.087.057-.442.097-.89.097-1.346C26.859 10.012 21.987 5.14 16 5.14a10.82 10.82 0 0 0-8.032 3.572c-.1.11-.07.289.06.363l1.363.788c.09.052.203.035.276-.04A8.82 8.82 0 0 1 16 7.141c4.885 0 8.859 3.974 8.859 8.859 0 .402-.037.796-.09 1.184"/><path d="M6.241 12.481 1.8 17.451a.185.185 0 0 0 .138.308h8.606c.16 0 .245-.189.138-.308zM25.759 19.519l4.441-4.97a.185.185 0 0 0-.138-.308h-8.606c-.16 0-.245.189-.138.308z"/></svg>
|
||||||
|
After Width: | Height: | Size: 897 B |
@ -1910,6 +1910,7 @@
|
|||||||
"ViewButtonStickerset" = "VIEW STICKERS";
|
"ViewButtonStickerset" = "VIEW STICKERS";
|
||||||
"ViewButtonEmojiset" = "VIEW EMOJI";
|
"ViewButtonEmojiset" = "VIEW EMOJI";
|
||||||
"ViewButtonGiftUnique" = "VIEW COLLECTIBLE";
|
"ViewButtonGiftUnique" = "VIEW COLLECTIBLE";
|
||||||
|
"ViewButtonAiStyle" = "VIEW STYLE";
|
||||||
"AuthContinueOnThisLanguage" = "Continue in English";
|
"AuthContinueOnThisLanguage" = "Continue in English";
|
||||||
"Share" = "Share";
|
"Share" = "Share";
|
||||||
"GiftSortByDate" = "Sort by Date";
|
"GiftSortByDate" = "Sort by Date";
|
||||||
@ -2889,6 +2890,32 @@
|
|||||||
"AiMessageEditorApply" = "Apply";
|
"AiMessageEditorApply" = "Apply";
|
||||||
"AiMessageEditorEmojify" = "emojify";
|
"AiMessageEditorEmojify" = "emojify";
|
||||||
"AiMessageEditorTranslation" = "Translation";
|
"AiMessageEditorTranslation" = "Translation";
|
||||||
|
"AiToneEditorNewStyle" = "New Style";
|
||||||
|
"AiToneEditorTitle" = "Create AI Tone";
|
||||||
|
"AiToneEditorNamePlaceholder" = "Style Name (for example: Pirate)";
|
||||||
|
"AiToneEditorPromptPlaceholder" = "Instructions (for example write in bold, nautical tone, light slang, vivid sea imagery, playful swagger)";
|
||||||
|
"AiToneEditorDisplayAuthor" = "Add a link to my account";
|
||||||
|
"AiToneEditorSelectEmoji" = "Select Emoji";
|
||||||
|
"AiToneCreated" = "{title} style created!";
|
||||||
|
"AiToneCreatedHint" = "Press and hold a style to edit or share it.";
|
||||||
|
"AiToneEditStyle" = "Edit Style";
|
||||||
|
"AiToneShareStyle" = "Share Style";
|
||||||
|
"AiToneDeleteStyle" = "Delete Style";
|
||||||
|
"AiToneDeleteStyleConfirmOwn" = "Are you sure you want to delete this style? It will be removed for everyone who installed it.";
|
||||||
|
"AiToneDeleteStyleConfirm" = "Are you sure you want to remove this style?";
|
||||||
|
"AiToneEditorEditTitle" = "Edit AI Style";
|
||||||
|
"AiTonePreviewSubtitle" = "Add this style to instantly rewrite your messages.";
|
||||||
|
"AiTonePreviewBefore" = "Before";
|
||||||
|
"AiTonePreviewAnotherExample" = "Another Example";
|
||||||
|
"AiTonePreviewAfter" = "After";
|
||||||
|
"AiTonePreviewAddStyle" = "Add Style";
|
||||||
|
"AiTonePreviewRemoveStyle" = "Remove Style";
|
||||||
|
"AiTonePreviewStyleAdded" = "Style added";
|
||||||
|
"AiToneLimitReached" = "You have reached the limit of custom styles.";
|
||||||
|
"AiToneLimitReachedPremium" = "You have reached the limit of **{limit}** custom styles.";
|
||||||
|
"AiTonePreviewUsedBy" = "Used by {count} people.";
|
||||||
|
"AiTonePreviewCreatedBy" = "Created by {author}.";
|
||||||
|
"AiTonePreviewUsedByCreatedBy" = "{usedBy} {createdBy}";
|
||||||
"TextShowMore" = "more";
|
"TextShowMore" = "more";
|
||||||
"TextShowLess" = "less";
|
"TextShowLess" = "less";
|
||||||
"AiMessageEditorFrom" = "From";
|
"AiMessageEditorFrom" = "From";
|
||||||
|
|||||||
@ -73,6 +73,10 @@ export { default as ReactionPicker } from '../components/middle/message/reaction
|
|||||||
|
|
||||||
export { default as AiMessageEditorModal }
|
export { default as AiMessageEditorModal }
|
||||||
from '../components/middle/composer/AiMessageEditorModal/AiMessageEditorModal';
|
from '../components/middle/composer/AiMessageEditorModal/AiMessageEditorModal';
|
||||||
|
export { default as AiToneEmojiPickerModal }
|
||||||
|
from '../components/middle/composer/AiMessageEditorModal/AiToneEmojiPickerModal';
|
||||||
|
export { default as AiTonePreviewModal }
|
||||||
|
from '../components/modals/aiTonePreview/AiTonePreviewModal';
|
||||||
|
|
||||||
export { default as AttachmentModal } from '../components/middle/composer/AttachmentModal';
|
export { default as AttachmentModal } from '../components/middle/composer/AttachmentModal';
|
||||||
export { default as PollModal } from '../components/modals/poll/PollModal';
|
export { default as PollModal } from '../components/modals/poll/PollModal';
|
||||||
|
|||||||
@ -68,6 +68,7 @@ type OwnProps = {
|
|||||||
isStatusPicker?: boolean;
|
isStatusPicker?: boolean;
|
||||||
isReactionPicker?: boolean;
|
isReactionPicker?: boolean;
|
||||||
isTranslucent?: boolean;
|
isTranslucent?: boolean;
|
||||||
|
noAddButton?: boolean;
|
||||||
onCustomEmojiSelect: (sticker: ApiSticker) => void;
|
onCustomEmojiSelect: (sticker: ApiSticker) => void;
|
||||||
onReactionSelect?: (reaction: ApiReactionWithPaid) => void;
|
onReactionSelect?: (reaction: ApiReactionWithPaid) => void;
|
||||||
onReactionContext?: (reaction: ApiReactionWithPaid) => void;
|
onReactionContext?: (reaction: ApiReactionWithPaid) => void;
|
||||||
@ -130,6 +131,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
isReactionPicker,
|
isReactionPicker,
|
||||||
isStatusPicker,
|
isStatusPicker,
|
||||||
isTranslucent,
|
isTranslucent,
|
||||||
|
noAddButton,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
withDefaultTopicIcons,
|
withDefaultTopicIcons,
|
||||||
@ -451,6 +453,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
isStatusPicker={isStatusPicker}
|
isStatusPicker={isStatusPicker}
|
||||||
isReactionPicker={isReactionPicker}
|
isReactionPicker={isReactionPicker}
|
||||||
|
noAddButton={noAddButton}
|
||||||
shouldHideHeader={shouldHideHeader}
|
shouldHideHeader={shouldHideHeader}
|
||||||
withDefaultTopicIcon={withDefaultTopicIcons && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
withDefaultTopicIcon={withDefaultTopicIcons && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
||||||
withDefaultStatusIcon={isStatusPicker && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
withDefaultStatusIcon={isStatusPicker && stickerSet.id === RECENT_SYMBOL_SET_ID}
|
||||||
|
|||||||
@ -62,6 +62,7 @@ type OwnProps = {
|
|||||||
isChatStickerSet?: boolean;
|
isChatStickerSet?: boolean;
|
||||||
isTranslucent?: boolean;
|
isTranslucent?: boolean;
|
||||||
noContextMenus?: boolean;
|
noContextMenus?: boolean;
|
||||||
|
noAddButton?: boolean;
|
||||||
forcePlayback?: boolean;
|
forcePlayback?: boolean;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
observeIntersectionForPlayingItems: ObserveFn;
|
observeIntersectionForPlayingItems: ObserveFn;
|
||||||
@ -106,6 +107,7 @@ const StickerSet = ({
|
|||||||
isChatStickerSet,
|
isChatStickerSet,
|
||||||
isTranslucent,
|
isTranslucent,
|
||||||
noContextMenus,
|
noContextMenus,
|
||||||
|
noAddButton,
|
||||||
forcePlayback,
|
forcePlayback,
|
||||||
collectibleStatuses,
|
collectibleStatuses,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
@ -260,7 +262,7 @@ const StickerSet = ({
|
|||||||
const collectibleEmojiIdsSet = useMemo(() => (
|
const collectibleEmojiIdsSet = useMemo(() => (
|
||||||
collectibleStatuses ? new Set(collectibleStatuses.map(({ documentId }) => documentId)) : undefined
|
collectibleStatuses ? new Set(collectibleStatuses.map(({ documentId }) => documentId)) : undefined
|
||||||
), [collectibleStatuses]);
|
), [collectibleStatuses]);
|
||||||
const withAddSetButton = !shouldHideHeader && !isRecent && !isStatusCollectible
|
const withAddSetButton = !noAddButton && !shouldHideHeader && !isRecent && !isStatusCollectible
|
||||||
&& isEmoji && !isPopular && !isChatEmojiSet
|
&& isEmoji && !isPopular && !isChatEmojiSet
|
||||||
&& (!isInstalled || (!isCurrentUserPremium && !isSavedMessages));
|
&& (!isInstalled || (!isCurrentUserPremium && !isSavedMessages));
|
||||||
const addSetButtonText = useMemo(() => {
|
const addSetButtonText = useMemo(() => {
|
||||||
|
|||||||
@ -143,6 +143,11 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stickyFooter {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.withHeader {
|
.withHeader {
|
||||||
mask-image:
|
mask-image:
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
|
|||||||
@ -43,6 +43,10 @@ export type ModalProps = {
|
|||||||
height?: ModalHeight;
|
height?: ModalHeight;
|
||||||
noBackdrop?: boolean;
|
noBackdrop?: boolean;
|
||||||
noLightDismiss?: boolean;
|
noLightDismiss?: boolean;
|
||||||
|
noScrollable?: boolean;
|
||||||
|
noContentInlinePadding?: boolean;
|
||||||
|
keepMounted?: boolean;
|
||||||
|
stickyFooter?: TeactNode;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
noContainment?: boolean;
|
noContainment?: boolean;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
@ -114,10 +118,15 @@ const Modal = ({
|
|||||||
height = 'regular',
|
height = 'regular',
|
||||||
noBackdrop,
|
noBackdrop,
|
||||||
noLightDismiss,
|
noLightDismiss,
|
||||||
|
noScrollable,
|
||||||
|
noContentInlinePadding,
|
||||||
|
keepMounted,
|
||||||
|
stickyFooter,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
noContainment,
|
noContainment,
|
||||||
onClose,
|
onClose,
|
||||||
}: ModalProps) => {
|
}: ModalProps) => {
|
||||||
|
const [hasEverOpened, setHasEverOpened] = useState(Boolean(isOpen));
|
||||||
const [shouldRender, setShouldRender] = useState(Boolean(isOpen));
|
const [shouldRender, setShouldRender] = useState(Boolean(isOpen));
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
const [hasTitle, setHasTitle] = useState(false);
|
const [hasTitle, setHasTitle] = useState(false);
|
||||||
@ -134,11 +143,14 @@ const Modal = ({
|
|||||||
const frozenProps = useFrozenProps({
|
const frozenProps = useFrozenProps({
|
||||||
header,
|
header,
|
||||||
children,
|
children,
|
||||||
|
stickyFooter,
|
||||||
dialogClassName,
|
dialogClassName,
|
||||||
contentClassName,
|
contentClassName,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
noBackdrop,
|
noBackdrop,
|
||||||
|
noScrollable,
|
||||||
|
noContentInlinePadding,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
noContainment,
|
noContainment,
|
||||||
}, !isOpen);
|
}, !isOpen);
|
||||||
@ -193,6 +205,12 @@ const Modal = ({
|
|||||||
titleId,
|
titleId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && !hasEverOpened) {
|
||||||
|
setHasEverOpened(true);
|
||||||
|
}
|
||||||
|
}, [hasEverOpened, isOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
cleanupCloseAnimation();
|
cleanupCloseAnimation();
|
||||||
@ -322,7 +340,7 @@ const Modal = ({
|
|||||||
handleRequestClose();
|
handleRequestClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!shouldRender) {
|
if (!shouldRender && !(keepMounted && hasEverOpened)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +373,8 @@ const Modal = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Surface
|
<Surface
|
||||||
scrollable
|
scrollable={!frozenProps.noScrollable}
|
||||||
|
noPadding={frozenProps.noContentInlinePadding}
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
styles.content,
|
styles.content,
|
||||||
shouldShowHeader && styles.withHeader,
|
shouldShowHeader && styles.withHeader,
|
||||||
@ -366,6 +385,11 @@ const Modal = ({
|
|||||||
{frozenProps.children}
|
{frozenProps.children}
|
||||||
</div>
|
</div>
|
||||||
</Surface>
|
</Surface>
|
||||||
|
{Boolean(frozenProps.stickyFooter) && (
|
||||||
|
<div className={styles.stickyFooter}>
|
||||||
|
{frozenProps.stickyFooter}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</ModalContext.Provider>
|
</ModalContext.Provider>
|
||||||
|
|||||||
9
src/components/gili/templates/CheckboxField.module.scss
Normal file
9
src/components/gili/templates/CheckboxField.module.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@layer ui.templates {
|
||||||
|
.centered {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centeredControl {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import { memo } from '../../../lib/teact/teact';
|
import { memo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
import Control, {
|
import Control, {
|
||||||
ControlDescription,
|
ControlDescription,
|
||||||
ControlLabel,
|
ControlLabel,
|
||||||
@ -7,11 +9,14 @@ import Control, {
|
|||||||
import Interactive from '../layout/Interactive';
|
import Interactive from '../layout/Interactive';
|
||||||
import Checkbox from '../primitives/Checkbox';
|
import Checkbox from '../primitives/Checkbox';
|
||||||
|
|
||||||
|
import styles from './CheckboxField.module.scss';
|
||||||
|
|
||||||
type Props = Omit<React.ComponentProps<typeof Checkbox>, 'className' | 'disabled'> & {
|
type Props = Omit<React.ComponentProps<typeof Checkbox>, 'className' | 'disabled'> & {
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
isCentered?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
controlClassName?: string;
|
controlClassName?: string;
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
@ -24,6 +29,7 @@ const CheckboxField = ({
|
|||||||
description,
|
description,
|
||||||
disabled,
|
disabled,
|
||||||
loading,
|
loading,
|
||||||
|
isCentered,
|
||||||
className,
|
className,
|
||||||
controlClassName,
|
controlClassName,
|
||||||
labelClassName,
|
labelClassName,
|
||||||
@ -38,9 +44,9 @@ const CheckboxField = ({
|
|||||||
ripple={ripple}
|
ripple={ripple}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
className={className}
|
className={buildClassName(className, isCentered && styles.centered)}
|
||||||
>
|
>
|
||||||
<Control className={controlClassName}>
|
<Control className={buildClassName(controlClassName, isCentered && styles.centeredControl)}>
|
||||||
<Checkbox {...checkboxProps} />
|
<Checkbox {...checkboxProps} />
|
||||||
<ControlLabel className={labelClassName}>{label}</ControlLabel>
|
<ControlLabel className={labelClassName}>{label}</ControlLabel>
|
||||||
{description !== undefined ? (
|
{description !== undefined ? (
|
||||||
|
|||||||
@ -64,6 +64,7 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
|
|||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
className="confirm"
|
className="confirm"
|
||||||
title={lang('ShareYouPhoneNumberTitle')}
|
title={lang('ShareYouPhoneNumberTitle')}
|
||||||
|
isNativeDialog
|
||||||
onCloseAnimationEnd={dismissDialog}
|
onCloseAnimationEnd={dismissDialog}
|
||||||
>
|
>
|
||||||
{lang(
|
{lang(
|
||||||
@ -94,6 +95,7 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
|
|||||||
onCloseAnimationEnd={dismissDialog}
|
onCloseAnimationEnd={dismissDialog}
|
||||||
className="error"
|
className="error"
|
||||||
title={title}
|
title={title}
|
||||||
|
isNativeDialog
|
||||||
>
|
>
|
||||||
{renderedText}
|
{renderedText}
|
||||||
<div className="dialog-buttons mt-2">
|
<div className="dialog-buttons mt-2">
|
||||||
|
|||||||
@ -32,7 +32,9 @@
|
|||||||
.resultArea {
|
.resultArea {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultAreaAnimated {
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
transition: height 0.1s;
|
transition: height 0.1s;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type AiEditorResultAreaProps = {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
transitionKey?: number;
|
transitionKey?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
loadingElement?: TeactNode;
|
||||||
children: TeactNode;
|
children: TeactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ export const AiEditorResultArea = memo(({
|
|||||||
isLoading,
|
isLoading,
|
||||||
transitionKey,
|
transitionKey,
|
||||||
className,
|
className,
|
||||||
|
loadingElement,
|
||||||
children,
|
children,
|
||||||
}: AiEditorResultAreaProps) => {
|
}: AiEditorResultAreaProps) => {
|
||||||
const contentRef = useRef<HTMLDivElement>();
|
const contentRef = useRef<HTMLDivElement>();
|
||||||
@ -45,15 +47,16 @@ export const AiEditorResultArea = memo(({
|
|||||||
});
|
});
|
||||||
}, [children, isLoading, transitionKey]);
|
}, [children, isLoading, transitionKey]);
|
||||||
|
|
||||||
const displayHeight = height ?? MIN_HEIGHT;
|
const hasInitialized = height !== undefined;
|
||||||
|
const displayHeight = hasInitialized ? height : (isLoading ? MIN_HEIGHT : undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(styles.resultArea, className)}
|
className={buildClassName(styles.resultArea, hasInitialized && styles.resultAreaAnimated, className)}
|
||||||
style={`height: ${displayHeight}px`}
|
style={displayHeight !== undefined ? `height: ${displayHeight}px` : undefined}
|
||||||
>
|
>
|
||||||
<div className={buildClassName(styles.loadingContainer, !isLoading && styles.hidden)}>
|
<div className={buildClassName(styles.loadingContainer, !isLoading && styles.hidden)}>
|
||||||
<TextLoadingPlaceholder lines={6} />
|
{loadingElement || <TextLoadingPlaceholder lines={6} />}
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
name="fade"
|
name="fade"
|
||||||
|
|||||||
@ -77,6 +77,7 @@
|
|||||||
padding: 0 1rem 1rem;
|
padding: 0 1rem 1rem;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
pointer-events: none;
|
||||||
content: "";
|
content: "";
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -24,6 +24,38 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabListWrapper {
|
||||||
|
position: relative;
|
||||||
|
min-height: 4.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabListSkeleton {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
inset: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 1.25rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 0.625rem 0;
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
background: var(--color-background);
|
||||||
|
|
||||||
|
transition: opacity 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabListSkeletonHidden {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabSkeleton {
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.textLabel,
|
.textLabel,
|
||||||
.resultLabel {
|
.resultLabel {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { memo, useMemo } from '../../../../lib/teact/teact';
|
import { memo, useMemo, useState } from '../../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiAiComposeToneType, ApiComposedMessageWithAI, ApiFormattedText, ApiInputAiComposeTone,
|
ApiAiComposeToneType, ApiComposedMessageWithAI, ApiFormattedText, ApiInputAiComposeTone,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
|
import type { MenuItemContextAction } from '../../../ui/ListItem';
|
||||||
import type { TabWithProperties } from '../../../ui/TabList';
|
import type { TabWithProperties } from '../../../ui/TabList';
|
||||||
|
|
||||||
|
import { TME_LINK_PREFIX } from '../../../../config';
|
||||||
|
import { selectTabState } from '../../../../global/selectors';
|
||||||
import { compareAiTones, getInputTone } from '../../../../util/aiComposeTones';
|
import { compareAiTones, getInputTone } from '../../../../util/aiComposeTones';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
||||||
@ -15,9 +18,12 @@ import useLang from '../../../../hooks/useLang';
|
|||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import CheckboxField from '../../../gili/templates/CheckboxField';
|
import CheckboxField from '../../../gili/templates/CheckboxField';
|
||||||
|
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||||
|
import Skeleton from '../../../ui/placeholder/Skeleton';
|
||||||
import TabList from '../../../ui/TabList';
|
import TabList from '../../../ui/TabList';
|
||||||
import Transition from '../../../ui/Transition';
|
import Transition from '../../../ui/Transition';
|
||||||
import { AiEditorCopyButton, AiEditorErrorMessage, AiEditorResultArea } from './AiEditorShared';
|
import { AiEditorCopyButton, AiEditorErrorMessage, AiEditorResultArea } from './AiEditorShared';
|
||||||
|
import AiToneEditorModal from './AiToneEditorModal';
|
||||||
|
|
||||||
import sharedStyles from './AiEditorShared.module.scss';
|
import sharedStyles from './AiEditorShared.module.scss';
|
||||||
import modalStyles from './AiMessageEditorModal.module.scss';
|
import modalStyles from './AiMessageEditorModal.module.scss';
|
||||||
@ -35,6 +41,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
tones: ApiAiComposeToneType[];
|
tones: ApiAiComposeToneType[];
|
||||||
|
isAiToneEditorOpen?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AiTextStyleEditor = ({
|
const AiTextStyleEditor = ({
|
||||||
@ -46,28 +53,97 @@ const AiTextStyleEditor = ({
|
|||||||
error,
|
error,
|
||||||
isPremium,
|
isPremium,
|
||||||
tones,
|
tones,
|
||||||
|
isAiToneEditorOpen,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
setAiMessageEditorStyleOptions,
|
setAiMessageEditorStyleOptions,
|
||||||
composeWithAiMessageEditor,
|
composeWithAiMessageEditor,
|
||||||
|
openAiToneEditorModal,
|
||||||
|
closeAiMessageEditorModal,
|
||||||
|
deleteAiTone,
|
||||||
|
openChatWithDraft,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
const [toneToDelete, setToneToDelete] = useState<ApiInputAiComposeTone>();
|
||||||
|
const [isCreatorDelete, setIsCreatorDelete] = useState(false);
|
||||||
|
|
||||||
|
const handleConfirmDelete = useLastCallback(() => {
|
||||||
|
if (!toneToDelete) return;
|
||||||
|
deleteAiTone({ tone: toneToDelete });
|
||||||
|
setToneToDelete(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCloseDeleteConfirm = useLastCallback(() => {
|
||||||
|
setToneToDelete(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
const hasResult = Boolean(result?.resultText);
|
const hasResult = Boolean(result?.resultText);
|
||||||
const hasRequest = Boolean(selectedTone) || shouldEmojify;
|
const hasRequest = Boolean(selectedTone) || shouldEmojify;
|
||||||
const shouldShowError = Boolean(error) && hasRequest;
|
const shouldShowError = Boolean(error) && hasRequest;
|
||||||
|
|
||||||
const styleTabs = useMemo((): TabWithProperties[] => tones.map((entry) => ({
|
const buildContextActions = useLastCallback((entry: ApiAiComposeToneType): MenuItemContextAction[] | undefined => {
|
||||||
customEmojiDocumentId: entry.emojiId,
|
if (!('id' in entry)) return undefined;
|
||||||
title: entry.title,
|
|
||||||
})), [tones]);
|
const tone = getInputTone(entry);
|
||||||
|
const actions: MenuItemContextAction[] = [];
|
||||||
|
|
||||||
|
if (entry.isCreator) {
|
||||||
|
actions.push({
|
||||||
|
title: lang('AiToneEditStyle'),
|
||||||
|
icon: 'edit',
|
||||||
|
handler: () => {
|
||||||
|
openAiToneEditorModal({ toneToEdit: entry });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.push({
|
||||||
|
title: lang('AiToneShareStyle'),
|
||||||
|
icon: 'forward',
|
||||||
|
handler: () => {
|
||||||
|
closeAiMessageEditorModal();
|
||||||
|
openChatWithDraft({ text: { text: `${TME_LINK_PREFIX}addstyle/${entry.slug}` } });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
actions.push({
|
||||||
|
title: lang('AiToneDeleteStyle'),
|
||||||
|
icon: 'delete',
|
||||||
|
destructive: true,
|
||||||
|
handler: () => {
|
||||||
|
setToneToDelete(tone);
|
||||||
|
setIsCreatorDelete(Boolean(entry.isCreator));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
});
|
||||||
|
|
||||||
|
const styleTabs = useMemo((): TabWithProperties[] => {
|
||||||
|
const tabs: TabWithProperties[] = tones.map((entry) => ({
|
||||||
|
customEmojiDocumentId: entry.emojiId,
|
||||||
|
title: entry.title,
|
||||||
|
contextActions: buildContextActions(entry),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (tones.length) {
|
||||||
|
tabs.push({ icon: 'add', title: lang('AiToneEditorNewStyle') });
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs;
|
||||||
|
}, [tones, lang, buildContextActions]);
|
||||||
|
|
||||||
const activeStyleIndex = tones.findIndex(
|
const activeStyleIndex = tones.findIndex(
|
||||||
(entry) => compareAiTones(selectedTone, getInputTone(entry)),
|
(entry) => compareAiTones(selectedTone, getInputTone(entry)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStyleSelect = useLastCallback((index: number) => {
|
const handleStyleSelect = useLastCallback((index: number) => {
|
||||||
|
if (index === tones.length) {
|
||||||
|
openAiToneEditorModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const tone = getInputTone(tones[index]);
|
const tone = getInputTone(tones[index]);
|
||||||
setAiMessageEditorStyleOptions({ selectedTone: tone });
|
setAiMessageEditorStyleOptions({ selectedTone: tone });
|
||||||
composeWithAiMessageEditor({ tone, isEmojify: shouldEmojify });
|
composeWithAiMessageEditor({ tone, isEmojify: shouldEmojify });
|
||||||
@ -105,15 +181,26 @@ const AiTextStyleEditor = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(modalStyles.editorBlock, styles.styleBlock)}>
|
<div className={buildClassName(modalStyles.editorBlock, styles.styleBlock)}>
|
||||||
<TabList
|
<div className={styles.tabListWrapper}>
|
||||||
tabs={styleTabs}
|
{styleTabs.length > 0 && (
|
||||||
activeTab={activeStyleIndex}
|
<TabList
|
||||||
onSwitchTab={handleStyleSelect}
|
tabs={styleTabs}
|
||||||
className={styles.tabList}
|
activeTab={activeStyleIndex}
|
||||||
tabClassName={styles.tab}
|
onSwitchTab={handleStyleSelect}
|
||||||
indicatorClassName={styles.tabListIndicator}
|
className={styles.tabList}
|
||||||
itemAlignment="vertical"
|
tabClassName={styles.tab}
|
||||||
/>
|
indicatorClassName={styles.tabListIndicator}
|
||||||
|
itemAlignment="vertical"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={buildClassName(styles.tabListSkeleton, styleTabs.length && styles.tabListSkeletonHidden)}>
|
||||||
|
<Skeleton className={styles.tabSkeleton} variant="round" animation="wave" />
|
||||||
|
<Skeleton className={styles.tabSkeleton} variant="round" animation="wave" />
|
||||||
|
<Skeleton className={styles.tabSkeleton} variant="round" animation="wave" />
|
||||||
|
<Skeleton className={styles.tabSkeleton} variant="round" animation="wave" />
|
||||||
|
<Skeleton className={styles.tabSkeleton} variant="round" animation="wave" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={sharedStyles.separator} />
|
<div className={sharedStyles.separator} />
|
||||||
|
|
||||||
@ -144,6 +231,16 @@ const AiTextStyleEditor = ({
|
|||||||
textToCopy={displayText?.text}
|
textToCopy={displayText?.text}
|
||||||
isHidden={isLoading || shouldShowError || !displayText?.text}
|
isHidden={isLoading || shouldShowError || !displayText?.text}
|
||||||
/>
|
/>
|
||||||
|
<AiToneEditorModal isOpen={Boolean(isAiToneEditorOpen)} />
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={Boolean(toneToDelete)}
|
||||||
|
title={lang('AiToneDeleteStyle')}
|
||||||
|
text={lang(isCreatorDelete ? 'AiToneDeleteStyleConfirmOwn' : 'AiToneDeleteStyleConfirm')}
|
||||||
|
confirmLabel={lang('Delete')}
|
||||||
|
confirmIsDestructive
|
||||||
|
onClose={handleCloseDeleteConfirm}
|
||||||
|
confirmHandler={handleConfirmDelete}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -152,6 +249,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global): Complete<StateProps> => {
|
(global): Complete<StateProps> => {
|
||||||
return {
|
return {
|
||||||
tones: global.aiComposeTones?.tones ?? MEMO_EMPTY_ARRAY,
|
tones: global.aiComposeTones?.tones ?? MEMO_EMPTY_ARRAY,
|
||||||
|
isAiToneEditorOpen: Boolean(selectTabState(global).aiToneEditorModal),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(AiTextStyleEditor));
|
)(AiTextStyleEditor));
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
.emojiRow {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojiButton {
|
||||||
|
--custom-emoji-size: 3rem;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
background: var(--color-background);
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojiPlaceholderIcon {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promptInput {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promptInput :global(.form-control) {
|
||||||
|
min-height: 5.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteButton {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createButton {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
@ -0,0 +1,243 @@
|
|||||||
|
import { memo, useEffect, useMemo, useState } from '../../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import type { ApiAiComposeTone } from '../../../../api/types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../../global/selectors';
|
||||||
|
import { getInputTone } from '../../../../util/aiComposeTones';
|
||||||
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import CustomEmoji from '../../../common/CustomEmoji';
|
||||||
|
import Icon from '../../../common/icons/Icon';
|
||||||
|
import CheckboxField from '../../../gili/templates/CheckboxField';
|
||||||
|
import Button from '../../../ui/Button';
|
||||||
|
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||||
|
import InputText from '../../../ui/InputText';
|
||||||
|
import TextArea from '../../../ui/TextArea';
|
||||||
|
import AiToneEmojiPickerModal from './AiToneEmojiPickerModal.async';
|
||||||
|
import Island from '@gili/layout/Island';
|
||||||
|
import Modal, { ModalCloseButton, ModalHeader, ModalTitle } from '@gili/modal/Modal';
|
||||||
|
|
||||||
|
import styles from './AiToneEditorModal.module.scss';
|
||||||
|
|
||||||
|
const EMOJI_SIZE = 48;
|
||||||
|
const DEFAULT_TITLE_MAX_LENGTH = 12;
|
||||||
|
const DEFAULT_PROMPT_MAX_LENGTH = 1024;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
toneToEdit?: ApiAiComposeTone;
|
||||||
|
titleMaxLength?: number;
|
||||||
|
promptMaxLength?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AiToneEditorModal = ({
|
||||||
|
isOpen,
|
||||||
|
toneToEdit,
|
||||||
|
titleMaxLength = DEFAULT_TITLE_MAX_LENGTH,
|
||||||
|
promptMaxLength = DEFAULT_PROMPT_MAX_LENGTH,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const {
|
||||||
|
closeAiToneEditorModal,
|
||||||
|
createAiTone,
|
||||||
|
updateAiTone,
|
||||||
|
deleteAiTone,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isEditMode = Boolean(toneToEdit);
|
||||||
|
|
||||||
|
const [emojiId, setEmojiId] = useState<string | undefined>();
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [prompt, setPrompt] = useState('');
|
||||||
|
const [shouldDisplayAuthor, setShouldDisplayAuthor] = useState(false);
|
||||||
|
const [isEmojiPickerOpen, openEmojiPicker, closeEmojiPicker] = useFlag();
|
||||||
|
const [isDeleteConfirmOpen, openDeleteConfirm, closeDeleteConfirm] = useFlag();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
if (toneToEdit) {
|
||||||
|
setEmojiId(toneToEdit.emojiId);
|
||||||
|
setTitle(toneToEdit.title);
|
||||||
|
setPrompt(toneToEdit.prompt || '');
|
||||||
|
setShouldDisplayAuthor(Boolean(toneToEdit.authorId));
|
||||||
|
} else {
|
||||||
|
setEmojiId(undefined);
|
||||||
|
setTitle('');
|
||||||
|
setPrompt('');
|
||||||
|
setShouldDisplayAuthor(false);
|
||||||
|
}
|
||||||
|
}, [isOpen, toneToEdit]);
|
||||||
|
|
||||||
|
const canSubmit = Boolean(emojiId && title.trim() && prompt.trim());
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => {
|
||||||
|
closeAiToneEditorModal();
|
||||||
|
closeEmojiPicker();
|
||||||
|
closeDeleteConfirm();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = useLastCallback(() => {
|
||||||
|
if (!canSubmit) return;
|
||||||
|
|
||||||
|
if (isEditMode) {
|
||||||
|
const tone = getInputTone(toneToEdit);
|
||||||
|
updateAiTone({
|
||||||
|
tone,
|
||||||
|
title: title.trim(),
|
||||||
|
emojiId: emojiId!,
|
||||||
|
prompt: prompt.trim(),
|
||||||
|
shouldDisplayAuthor: shouldDisplayAuthor || undefined,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createAiTone({
|
||||||
|
title: title.trim(),
|
||||||
|
emojiId: emojiId!,
|
||||||
|
prompt: prompt.trim(),
|
||||||
|
shouldDisplayAuthor: shouldDisplayAuthor || undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDelete = useLastCallback(() => {
|
||||||
|
if (!toneToEdit) return;
|
||||||
|
const tone = getInputTone(toneToEdit);
|
||||||
|
deleteAiTone({ tone });
|
||||||
|
closeDeleteConfirm();
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTitleChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setTitle(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePromptChange = useLastCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setPrompt(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleEmojiSelect = useLastCallback((emojiDocumentId: string) => {
|
||||||
|
setEmojiId(emojiDocumentId);
|
||||||
|
closeEmojiPicker();
|
||||||
|
});
|
||||||
|
|
||||||
|
const modalTitle = lang(isEditMode ? 'AiToneEditorEditTitle' : 'AiToneEditorTitle');
|
||||||
|
|
||||||
|
const renderHeader = useMemo(() => (
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalTitle>{modalTitle}</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
), [modalTitle]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
header={renderHeader}
|
||||||
|
ariaLabel={modalTitle}
|
||||||
|
width="slim"
|
||||||
|
>
|
||||||
|
<div className={styles.emojiRow}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.emojiButton}
|
||||||
|
onClick={openEmojiPicker}
|
||||||
|
>
|
||||||
|
{emojiId ? (
|
||||||
|
<CustomEmoji documentId={emojiId} size={EMOJI_SIZE} />
|
||||||
|
) : (
|
||||||
|
<Icon name="smile" className={styles.emojiPlaceholderIcon} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Island>
|
||||||
|
<InputText
|
||||||
|
className={styles.input}
|
||||||
|
value={title}
|
||||||
|
onChange={handleTitleChange}
|
||||||
|
placeholder={lang('AiToneEditorNamePlaceholder')}
|
||||||
|
maxLength={titleMaxLength}
|
||||||
|
hasLengthIndicator
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
className={buildClassName(styles.input, styles.promptInput)}
|
||||||
|
value={prompt}
|
||||||
|
onChange={handlePromptChange}
|
||||||
|
placeholder={lang('AiToneEditorPromptPlaceholder')}
|
||||||
|
maxLength={promptMaxLength}
|
||||||
|
hasLengthIndicator
|
||||||
|
noReplaceNewlines
|
||||||
|
/>
|
||||||
|
</Island>
|
||||||
|
|
||||||
|
<Island>
|
||||||
|
<CheckboxField
|
||||||
|
label={lang('AiToneEditorDisplayAuthor')}
|
||||||
|
checked={shouldDisplayAuthor}
|
||||||
|
isRound
|
||||||
|
isCentered
|
||||||
|
onChange={setShouldDisplayAuthor}
|
||||||
|
/>
|
||||||
|
</Island>
|
||||||
|
|
||||||
|
<div className={styles.footer}>
|
||||||
|
{isEditMode && (
|
||||||
|
<Button
|
||||||
|
className={styles.deleteButton}
|
||||||
|
onClick={openDeleteConfirm}
|
||||||
|
color="danger"
|
||||||
|
isText
|
||||||
|
>
|
||||||
|
{lang('AiToneDeleteStyle')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className={styles.createButton}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!canSubmit}
|
||||||
|
>
|
||||||
|
{lang(isEditMode ? 'Save' : 'Create')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<AiToneEmojiPickerModal
|
||||||
|
isOpen={isEmojiPickerOpen}
|
||||||
|
onEmojiSelect={handleEmojiSelect}
|
||||||
|
onClose={closeEmojiPicker}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isDeleteConfirmOpen}
|
||||||
|
title={lang('AiToneDeleteStyle')}
|
||||||
|
text={lang('AiToneDeleteStyleConfirm')}
|
||||||
|
confirmLabel={lang('Delete')}
|
||||||
|
confirmIsDestructive
|
||||||
|
onClose={closeDeleteConfirm}
|
||||||
|
confirmHandler={handleDelete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): Complete<StateProps> => {
|
||||||
|
return {
|
||||||
|
toneToEdit: selectTabState(global).aiToneEditorModal?.toneToEdit,
|
||||||
|
titleMaxLength: global.appConfig.aiComposeToneTitleLengthMax,
|
||||||
|
promptMaxLength: global.appConfig.aiComposeTonePromptLengthMax,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(AiToneEditorModal));
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import type { OwnProps } from './AiToneEmojiPickerModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const AiToneEmojiPickerModalAsync = (props: OwnProps) => {
|
||||||
|
const { isOpen } = props;
|
||||||
|
const AiToneEmojiPickerModal = useModuleLoader(Bundles.Extra, 'AiToneEmojiPickerModal', !isOpen);
|
||||||
|
|
||||||
|
return AiToneEmojiPickerModal ? <AiToneEmojiPickerModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AiToneEmojiPickerModalAsync;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
.content {
|
||||||
|
--modal-content-block-padding: 0rem;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker {
|
||||||
|
height: var(--symbol-menu-height);
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { memo, useMemo } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { ApiSticker } from '../../../../api/types';
|
||||||
|
|
||||||
|
import useLang from '../../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import CustomEmojiPicker from '../../../common/CustomEmojiPicker';
|
||||||
|
import Modal, { ModalCloseButton, ModalHeader, ModalTitle } from '@gili/modal/Modal';
|
||||||
|
|
||||||
|
import styles from './AiToneEmojiPickerModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onEmojiSelect: (emojiId: string) => void;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AiToneEmojiPickerModal = ({
|
||||||
|
isOpen,
|
||||||
|
onEmojiSelect,
|
||||||
|
onClose,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const handleEmojiSelect = useLastCallback((sticker: ApiSticker) => {
|
||||||
|
onEmojiSelect(sticker.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderHeader = useMemo(() => (
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalTitle>{lang('AiToneEditorSelectEmoji')}</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
), [lang]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
header={renderHeader}
|
||||||
|
ariaLabel={lang('AiToneEditorSelectEmoji')}
|
||||||
|
width="slim"
|
||||||
|
noScrollable
|
||||||
|
noContentInlinePadding
|
||||||
|
keepMounted
|
||||||
|
contentClassName={styles.content}
|
||||||
|
>
|
||||||
|
<div className={styles.picker}>
|
||||||
|
<CustomEmojiPicker
|
||||||
|
idPrefix="ai-tone-icon-"
|
||||||
|
loadAndPlay={isOpen}
|
||||||
|
isHidden={!isOpen}
|
||||||
|
noAddButton
|
||||||
|
onCustomEmojiSelect={handleEmojiSelect}
|
||||||
|
onDismiss={onClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(AiToneEmojiPickerModal);
|
||||||
@ -197,6 +197,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--ai-tone-emoji {
|
||||||
|
--custom-emoji-size: 3rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--ai-tone {
|
||||||
|
max-width: 20rem;
|
||||||
|
|
||||||
|
.WebPage--ai-tone-emoji {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 1921px) {
|
@media (min-width: 1921px) {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import useLang from '../../../hooks/useLang';
|
|||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import Audio from '../../common/Audio';
|
import Audio from '../../common/Audio';
|
||||||
|
import CustomEmoji from '../../common/CustomEmoji';
|
||||||
import Document from '../../common/Document';
|
import Document from '../../common/Document';
|
||||||
import EmojiIconBackground from '../../common/embedded/EmojiIconBackground';
|
import EmojiIconBackground from '../../common/embedded/EmojiIconBackground';
|
||||||
import Icon from '../../common/icons/Icon';
|
import Icon from '../../common/icons/Icon';
|
||||||
@ -37,6 +38,7 @@ const MAX_TEXT_LENGTH = 170; // symbols
|
|||||||
const WEBPAGE_STORY_TYPE = 'telegram_story';
|
const WEBPAGE_STORY_TYPE = 'telegram_story';
|
||||||
const WEBPAGE_GIFT_TYPE = 'telegram_nft';
|
const WEBPAGE_GIFT_TYPE = 'telegram_nft';
|
||||||
const WEBPAGE_AUCTION_TYPE = 'telegram_auction';
|
const WEBPAGE_AUCTION_TYPE = 'telegram_auction';
|
||||||
|
const WEBPAGE_AI_TONE_TYPE = 'telegram_aicomposetone';
|
||||||
const STICKER_SIZE = 80;
|
const STICKER_SIZE = 80;
|
||||||
const EMOJI_SIZE = 38;
|
const EMOJI_SIZE = 38;
|
||||||
|
|
||||||
@ -148,6 +150,7 @@ const WebPage = ({
|
|||||||
const isStory = type === WEBPAGE_STORY_TYPE;
|
const isStory = type === WEBPAGE_STORY_TYPE;
|
||||||
const isGift = type === WEBPAGE_GIFT_TYPE;
|
const isGift = type === WEBPAGE_GIFT_TYPE;
|
||||||
const isAuction = type === WEBPAGE_AUCTION_TYPE;
|
const isAuction = type === WEBPAGE_AUCTION_TYPE;
|
||||||
|
const isAiTone = type === WEBPAGE_AI_TONE_TYPE;
|
||||||
const isExpiredStory = story && 'isDeleted' in story;
|
const isExpiredStory = story && 'isDeleted' in story;
|
||||||
|
|
||||||
const resultType = stickers?.isEmoji ? 'telegram_emojiset' : type;
|
const resultType = stickers?.isEmoji ? 'telegram_emojiset' : type;
|
||||||
@ -157,8 +160,9 @@ const WebPage = ({
|
|||||||
const quickButtonIcon = getWebpageButtonIcon(resultType);
|
const quickButtonIcon = getWebpageButtonIcon(resultType);
|
||||||
|
|
||||||
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
|
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
|
||||||
|
const aiToneEmojiId = isAiTone ? webPage.aiComposeToneEmojiId : undefined;
|
||||||
const isArticle = Boolean(truncatedDescription || title || siteName);
|
const isArticle = Boolean(truncatedDescription || title || siteName);
|
||||||
let isSquarePhoto = Boolean(stickers);
|
let isSquarePhoto = Boolean(stickers) || Boolean(aiToneEmojiId);
|
||||||
if (isArticle && webPage?.photo && !webPage.video && !webPage.document) {
|
if (isArticle && webPage?.photo && !webPage.video && !webPage.document) {
|
||||||
isSquarePhoto = getIsSmallPhoto(webPage, mediaSize);
|
isSquarePhoto = getIsSmallPhoto(webPage, mediaSize);
|
||||||
}
|
}
|
||||||
@ -173,6 +177,7 @@ const WebPage = ({
|
|||||||
document && 'with-document',
|
document && 'with-document',
|
||||||
quickButtonTitle && 'with-quick-button',
|
quickButtonTitle && 'with-quick-button',
|
||||||
(isGift || isAuction) && 'with-gift',
|
(isGift || isAuction) && 'with-gift',
|
||||||
|
isAiTone && 'WebPage--ai-tone',
|
||||||
);
|
);
|
||||||
|
|
||||||
function renderQuickButton() {
|
function renderQuickButton() {
|
||||||
@ -323,6 +328,11 @@ const WebPage = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{aiToneEmojiId && (
|
||||||
|
<div className="media-inner square-image WebPage--ai-tone-emoji">
|
||||||
|
<CustomEmoji documentId={aiToneEmojiId} size={STICKER_SIZE} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{quickButtonTitle && renderQuickButton()}
|
{quickButtonTitle && renderQuickButton()}
|
||||||
</PeerColorWrapper>
|
</PeerColorWrapper>
|
||||||
|
|||||||
@ -44,6 +44,8 @@ export function getWebpageButtonLangKey(type?: string, auctionEndDate?: number):
|
|||||||
const isFinished = auctionEndDate !== undefined && auctionEndDate < getServerTime();
|
const isFinished = auctionEndDate !== undefined && auctionEndDate < getServerTime();
|
||||||
return isFinished ? 'PollViewResults' : 'GiftAuctionJoin';
|
return isFinished ? 'PollViewResults' : 'GiftAuctionJoin';
|
||||||
}
|
}
|
||||||
|
case 'telegram_aicomposetone':
|
||||||
|
return 'ViewButtonAiStyle';
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import WebAppsCloseConfirmationModal from '../main/WebAppsCloseConfirmationModal
|
|||||||
import AiMessageEditorModal from '../middle/composer/AiMessageEditorModal/AiMessageEditorModal.async';
|
import AiMessageEditorModal from '../middle/composer/AiMessageEditorModal/AiMessageEditorModal.async';
|
||||||
import AboutAdsModal from './aboutAds/AboutAdsModal.async';
|
import AboutAdsModal from './aboutAds/AboutAdsModal.async';
|
||||||
import AgeVerificationModal from './ageVerification/AgeVerificationModal.async';
|
import AgeVerificationModal from './ageVerification/AgeVerificationModal.async';
|
||||||
|
import AiTonePreviewModal from './aiTonePreview/AiTonePreviewModal.async';
|
||||||
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
||||||
import BirthdaySetupModal from './birthday/BirthdaySetupModal.async';
|
import BirthdaySetupModal from './birthday/BirthdaySetupModal.async';
|
||||||
import BoostModal from './boost/BoostModal.async';
|
import BoostModal from './boost/BoostModal.async';
|
||||||
@ -157,7 +158,8 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'isQuickChatPickerOpen' |
|
'isQuickChatPickerOpen' |
|
||||||
'isCocoonModalOpen' |
|
'isCocoonModalOpen' |
|
||||||
'editRankModal' |
|
'editRankModal' |
|
||||||
'rankModal'
|
'rankModal' |
|
||||||
|
'aiTonePreviewModal'
|
||||||
>;
|
>;
|
||||||
type WrappedModalKey = 'pollModal';
|
type WrappedModalKey = 'pollModal';
|
||||||
type LegacyModalKey = Exclude<ModalKey, WrappedModalKey>;
|
type LegacyModalKey = Exclude<ModalKey, WrappedModalKey>;
|
||||||
@ -293,6 +295,7 @@ const LEGACY_MODALS: LegacyModalRegistry = {
|
|||||||
isCocoonModalOpen: CocoonModal,
|
isCocoonModalOpen: CocoonModal,
|
||||||
editRankModal: EditRankModal,
|
editRankModal: EditRankModal,
|
||||||
rankModal: RankModal,
|
rankModal: RankModal,
|
||||||
|
aiTonePreviewModal: AiTonePreviewModal,
|
||||||
};
|
};
|
||||||
const WRAPPED_MODALS: WrappedModalRegistry = {
|
const WRAPPED_MODALS: WrappedModalRegistry = {
|
||||||
pollModal: PollModal,
|
pollModal: PollModal,
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import type { OwnProps } from './AiTonePreviewModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const AiTonePreviewModalAsync = (props: OwnProps) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const AiTonePreviewModal = useModuleLoader(Bundles.Extra, 'AiTonePreviewModal', !modal);
|
||||||
|
|
||||||
|
return AiTonePreviewModal ? <AiTonePreviewModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AiTonePreviewModalAsync;
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
.modal {
|
||||||
|
--modal-header-height: 1rem;
|
||||||
|
--modal-max-height: min(39.3125rem, 80dvh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojiRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emojiCircle {
|
||||||
|
--custom-emoji-size: 3rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
max-width: 13rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exampleWrapper {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeletonBefore {
|
||||||
|
margin-bottom: 1.0625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeletonAfter {
|
||||||
|
margin-top: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anotherExampleButton {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
min-height: 1.75rem;
|
||||||
|
margin-inline-start: auto;
|
||||||
|
padding: 0.25rem 0.625rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.25rem;
|
||||||
|
color: var(--color-primary);
|
||||||
|
|
||||||
|
background: var(--color-background-secondary);
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.anotherExampleIcon {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
padding: 0 1rem 1rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 1.125rem;
|
||||||
|
|
||||||
|
background: linear-gradient(to top, var(--color-background-secondary) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
277
src/components/modals/aiTonePreview/AiTonePreviewModal.tsx
Normal file
277
src/components/modals/aiTonePreview/AiTonePreviewModal.tsx
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
import { memo, useMemo, useRef, useState } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiUser } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { selectUser } from '../../../global/selectors';
|
||||||
|
import { getInputTone } from '../../../util/aiComposeTones';
|
||||||
|
import calcTextLineHeightAndCount from '../../../util/element/calcTextLineHeightAndCount';
|
||||||
|
import formatUsername from '../../common/helpers/formatUsername';
|
||||||
|
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import CustomEmoji from '../../common/CustomEmoji';
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
|
import { AiEditorResultArea } from '../../middle/composer/AiMessageEditorModal/AiEditorShared';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Link from '../../ui/Link';
|
||||||
|
import TextLoadingPlaceholder from '../../ui/placeholder/TextLoadingPlaceholder';
|
||||||
|
import Modal, { ModalCloseButton, ModalHeader } from '@gili/modal/Modal';
|
||||||
|
|
||||||
|
import sharedStyles from '../../middle/composer/AiMessageEditorModal/AiEditorShared.module.scss';
|
||||||
|
import styles from './AiTonePreviewModal.module.scss';
|
||||||
|
|
||||||
|
const DEFAULT_MAX_EXAMPLES = 3;
|
||||||
|
const DEFAULT_LINES = 4;
|
||||||
|
const EMOJI_SIZE = 48;
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['aiTonePreviewModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
author?: ApiUser;
|
||||||
|
maxExamples?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AiTonePreviewModal = ({ modal, author, maxExamples = DEFAULT_MAX_EXAMPLES }: OwnProps & StateProps) => {
|
||||||
|
const {
|
||||||
|
closeAiTonePreview,
|
||||||
|
saveAiTone,
|
||||||
|
loadAiTonePreviewExample,
|
||||||
|
openChat,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
const tone = modal?.tone;
|
||||||
|
const example = modal?.example;
|
||||||
|
const isAlreadyAdded = modal?.isAlreadyAdded;
|
||||||
|
const slug = modal?.slug;
|
||||||
|
const hasExampleError = modal?.hasExampleError;
|
||||||
|
|
||||||
|
const [exampleNum, setExampleNum] = useState(0);
|
||||||
|
const spinCountRef = useRef(0);
|
||||||
|
const beforeRef = useRef<HTMLDivElement>();
|
||||||
|
const afterRef = useRef<HTMLDivElement>();
|
||||||
|
const prevLinesRef = useRef({ before: DEFAULT_LINES, after: DEFAULT_LINES });
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => {
|
||||||
|
closeAiTonePreview();
|
||||||
|
setExampleNum(0);
|
||||||
|
spinCountRef.current = 0;
|
||||||
|
prevLinesRef.current = { before: DEFAULT_LINES, after: DEFAULT_LINES };
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAdd = useLastCallback(() => {
|
||||||
|
if (!tone) return;
|
||||||
|
saveAiTone({ tone: getInputTone(tone) });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRemove = useLastCallback(() => {
|
||||||
|
if (!tone) return;
|
||||||
|
saveAiTone({ tone: getInputTone(tone), unsave: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAnotherExample = useLastCallback(() => {
|
||||||
|
if (!slug) return;
|
||||||
|
|
||||||
|
if (beforeRef.current && afterRef.current) {
|
||||||
|
prevLinesRef.current = {
|
||||||
|
before: Math.max(DEFAULT_LINES, calcTextLineHeightAndCount(beforeRef.current).totalLines),
|
||||||
|
after: Math.max(DEFAULT_LINES, calcTextLineHeightAndCount(afterRef.current).totalLines),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextNum = (exampleNum + 1) % maxExamples;
|
||||||
|
spinCountRef.current += 1;
|
||||||
|
setExampleNum(nextNum);
|
||||||
|
loadAiTonePreviewExample({
|
||||||
|
tone: { type: 'slug', slug },
|
||||||
|
num: nextNum,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAuthorClick = useLastCallback(() => {
|
||||||
|
if (!tone?.authorId) return;
|
||||||
|
handleClose();
|
||||||
|
openChat({ id: tone.authorId });
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderHeader = useMemo(() => (
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
</ModalHeader>
|
||||||
|
), []);
|
||||||
|
|
||||||
|
function renderFooterInfo() {
|
||||||
|
if (!tone) return undefined;
|
||||||
|
|
||||||
|
const installsCount = tone.installsCount || 0;
|
||||||
|
const authorName = author?.usernames?.[0]?.username;
|
||||||
|
|
||||||
|
if (!installsCount && !authorName) return undefined;
|
||||||
|
|
||||||
|
const authorLink = authorName
|
||||||
|
? <Link isPrimary onClick={handleAuthorClick}>{formatUsername(authorName)}</Link>
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const usedByText = installsCount
|
||||||
|
? lang('AiTonePreviewUsedBy', { count: lang.number(installsCount) })
|
||||||
|
: undefined;
|
||||||
|
const createdByText = authorLink
|
||||||
|
? lang('AiTonePreviewCreatedBy', { author: authorLink }, { withNodes: true })
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (usedByText && createdByText) {
|
||||||
|
return (
|
||||||
|
<div className={styles.info}>
|
||||||
|
{lang('AiTonePreviewUsedByCreatedBy', {
|
||||||
|
usedBy: usedByText,
|
||||||
|
createdBy: createdByText,
|
||||||
|
}, { withNodes: true })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.info}>
|
||||||
|
{usedByText || createdByText}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { before: beforeLines, after: afterLines } = prevLinesRef.current;
|
||||||
|
|
||||||
|
const exampleLoadingElement = useMemo(() => (
|
||||||
|
<>
|
||||||
|
<TextLoadingPlaceholder lines={beforeLines} className={styles.skeletonBefore} />
|
||||||
|
<div className={sharedStyles.separator} />
|
||||||
|
<div className={sharedStyles.labelRow}>
|
||||||
|
<span className={sharedStyles.label}>{lang('AiTonePreviewAfter')}</span>
|
||||||
|
</div>
|
||||||
|
<TextLoadingPlaceholder lines={afterLines} className={styles.skeletonAfter} />
|
||||||
|
</>
|
||||||
|
), [lang, beforeLines, afterLines]);
|
||||||
|
|
||||||
|
function renderExampleContent() {
|
||||||
|
if (hasExampleError) {
|
||||||
|
return (
|
||||||
|
<div className={sharedStyles.errorMessage}>
|
||||||
|
{lang('AiMessageEditorGenericError')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!example) return undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div ref={beforeRef} className={sharedStyles.resultContent}>
|
||||||
|
{renderTextWithEntities(example.from)}
|
||||||
|
</div>
|
||||||
|
<div className={sharedStyles.separator} />
|
||||||
|
<div className={sharedStyles.labelRow}>
|
||||||
|
<span className={sharedStyles.label}>{lang('AiTonePreviewAfter')}</span>
|
||||||
|
</div>
|
||||||
|
<div ref={afterRef} className={sharedStyles.resultContent}>
|
||||||
|
{renderTextWithEntities(example.to)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderFooter = useMemo(() => {
|
||||||
|
if (!tone) return undefined;
|
||||||
|
return (
|
||||||
|
<div className={styles.footer}>
|
||||||
|
{isAlreadyAdded ? (
|
||||||
|
<Button
|
||||||
|
className={styles.addButton}
|
||||||
|
onClick={tone.isCreator ? handleClose : handleRemove}
|
||||||
|
color={tone.isCreator ? undefined : 'danger'}
|
||||||
|
isText={!tone.isCreator}
|
||||||
|
noForcedUpperCase
|
||||||
|
>
|
||||||
|
{lang(tone.isCreator ? 'Done' : 'AiTonePreviewRemoveStyle')}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className={styles.addButton}
|
||||||
|
onClick={handleAdd}
|
||||||
|
>
|
||||||
|
{lang('AiTonePreviewAddStyle')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [tone, isAlreadyAdded, lang, handleClose, handleRemove, handleAdd]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
header={renderHeader}
|
||||||
|
stickyFooter={renderFooter}
|
||||||
|
dialogClassName={styles.modal}
|
||||||
|
width="slim"
|
||||||
|
height="regular"
|
||||||
|
>
|
||||||
|
{tone && (
|
||||||
|
<>
|
||||||
|
{tone.emojiId && (
|
||||||
|
<div className={styles.emojiRow}>
|
||||||
|
<div className={styles.emojiCircle}>
|
||||||
|
<CustomEmoji documentId={tone.emojiId} size={EMOJI_SIZE} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.title}>{tone.title}</div>
|
||||||
|
<div className={styles.subtitle}>{lang('AiTonePreviewSubtitle')}</div>
|
||||||
|
|
||||||
|
<div className={styles.exampleWrapper}>
|
||||||
|
<div className={sharedStyles.labelRow}>
|
||||||
|
<span className={sharedStyles.label}>{lang('AiTonePreviewBefore')}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.anotherExampleButton}
|
||||||
|
onClick={handleAnotherExample}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="reload-arrows"
|
||||||
|
className={styles.anotherExampleIcon}
|
||||||
|
style={`transform: rotate(${spinCountRef.current * 180}deg)`}
|
||||||
|
/>
|
||||||
|
{lang('AiTonePreviewAnotherExample')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<AiEditorResultArea
|
||||||
|
isLoading={!example && !hasExampleError}
|
||||||
|
transitionKey={exampleNum}
|
||||||
|
loadingElement={exampleLoadingElement}
|
||||||
|
>
|
||||||
|
{renderExampleContent()}
|
||||||
|
</AiEditorResultArea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{renderFooterInfo()}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { modal }): Complete<StateProps> => {
|
||||||
|
const authorId = modal?.tone?.authorId;
|
||||||
|
return {
|
||||||
|
author: authorId ? selectUser(global, authorId) : undefined,
|
||||||
|
maxExamples: global.appConfig.aiComposeToneExamplesNum,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(AiTonePreviewModal));
|
||||||
@ -9,6 +9,8 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
|
import AnimatedCounter from '../common/AnimatedCounter';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
ref?: ElementRef<HTMLInputElement>;
|
ref?: ElementRef<HTMLInputElement>;
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -22,6 +24,7 @@ type OwnProps = {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
autoComplete?: string;
|
autoComplete?: string;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
|
hasLengthIndicator?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
@ -51,6 +54,7 @@ const InputText = ({
|
|||||||
autoComplete = 'off',
|
autoComplete = 'off',
|
||||||
inputMode,
|
inputMode,
|
||||||
maxLength,
|
maxLength,
|
||||||
|
hasLengthIndicator,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
title,
|
title,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
@ -109,6 +113,11 @@ const InputText = ({
|
|||||||
{labelText && (
|
{labelText && (
|
||||||
<label htmlFor={id}>{labelText}</label>
|
<label htmlFor={id}>{labelText}</label>
|
||||||
)}
|
)}
|
||||||
|
{hasLengthIndicator && maxLength !== undefined && (
|
||||||
|
<div className="max-length-indicator">
|
||||||
|
<AnimatedCounter text={Math.max(0, maxLength - (value || '').length).toString()} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import AnimatedCounter from '../common/AnimatedCounter';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
ref?: ElementRef<HTMLTextAreaElement>;
|
ref?: ElementRef<HTMLTextAreaElement>;
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -26,6 +28,7 @@ type OwnProps = {
|
|||||||
autoComplete?: string;
|
autoComplete?: string;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
maxLengthIndicator?: string;
|
maxLengthIndicator?: string;
|
||||||
|
hasLengthIndicator?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
|
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
|
||||||
onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void;
|
onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
@ -52,6 +55,7 @@ const TextArea: FC<OwnProps> = ({
|
|||||||
inputMode,
|
inputMode,
|
||||||
maxLength,
|
maxLength,
|
||||||
maxLengthIndicator,
|
maxLengthIndicator,
|
||||||
|
hasLengthIndicator,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
onChange,
|
onChange,
|
||||||
onInput,
|
onInput,
|
||||||
@ -135,8 +139,12 @@ const TextArea: FC<OwnProps> = ({
|
|||||||
{labelText && (
|
{labelText && (
|
||||||
<label htmlFor={id}>{labelText}</label>
|
<label htmlFor={id}>{labelText}</label>
|
||||||
)}
|
)}
|
||||||
{maxLengthIndicator && (
|
{(maxLengthIndicator || (hasLengthIndicator && maxLength !== undefined)) && (
|
||||||
<div className="max-length-indicator">{maxLengthIndicator}</div>
|
<div className="max-length-indicator">
|
||||||
|
<AnimatedCounter
|
||||||
|
text={maxLengthIndicator || Math.max(0, maxLength! - (value || '').length).toString()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ApiInputAiComposeTone } from '../../../api/types';
|
import type { ApiInputAiComposeTone } from '../../../api/types';
|
||||||
|
import type { ActionReturnType, GlobalState } from '../../types';
|
||||||
|
|
||||||
import { compareAiTones, getToneCacheKey } from '../../../util/aiComposeTones';
|
import { compareAiTones, getToneCacheKey } from '../../../util/aiComposeTones';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
@ -6,6 +7,36 @@ import { callApi } from '../../../api/gramjs';
|
|||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import { selectTabState } from '../../selectors';
|
import { selectTabState } from '../../selectors';
|
||||||
|
import { selectCurrentLimit } from '../../selectors/limits';
|
||||||
|
import { selectIsCurrentUserPremium } from '../../selectors/users';
|
||||||
|
|
||||||
|
export function showToneLimitNotification<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
actions: { showNotification: AnyFunction },
|
||||||
|
tabId: number,
|
||||||
|
): boolean {
|
||||||
|
const isPremium = selectIsCurrentUserPremium(global);
|
||||||
|
const limit = selectCurrentLimit(global, 'aiComposeToneSaved');
|
||||||
|
|
||||||
|
const customToneCount = (global.aiComposeTones?.tones || []).filter((t) => 'id' in t).length;
|
||||||
|
if (customToneCount < limit) return false;
|
||||||
|
|
||||||
|
if (isPremium) {
|
||||||
|
actions.showNotification({
|
||||||
|
message: { key: 'AiToneLimitReachedPremium', variables: { limit: limit.toString() } },
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
actions.showNotification({
|
||||||
|
message: { key: 'AiToneLimitReached' },
|
||||||
|
action: { action: 'openPremiumModal', payload: { tabId } },
|
||||||
|
actionText: { key: 'PremiumMore' },
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function buildStyleCacheKey(tone?: ApiInputAiComposeTone, emojify?: boolean) {
|
function buildStyleCacheKey(tone?: ApiInputAiComposeTone, emojify?: boolean) {
|
||||||
return `${tone ? getToneCacheKey(tone) : ''}_${emojify ? '1' : '0'}`;
|
return `${tone ? getToneCacheKey(tone) : ''}_${emojify ? '1' : '0'}`;
|
||||||
@ -148,6 +179,166 @@ addActionHandler('composeWithAiMessageEditor', async (global, actions, payload):
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('createAiTone', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
title, emojiId, prompt, shouldDisplayAuthor,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
if (showToneLimitNotification(global, actions, tabId)) return;
|
||||||
|
|
||||||
|
const result = await callApi('createAiTone', {
|
||||||
|
title, emojiId, prompt, shouldDisplayAuthor,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
actions.closeAiToneEditorModal({ tabId });
|
||||||
|
actions.loadAiComposeTones();
|
||||||
|
actions.showNotification({
|
||||||
|
title: { key: 'AiToneCreated', variables: { title } },
|
||||||
|
message: { key: 'AiToneCreatedHint' },
|
||||||
|
customEmojiIconId: emojiId,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('deleteAiTone', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { tone, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const result = await callApi('deleteAiTone', { tone });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
actions.showNotification({ message: { key: 'ErrorUnspecified' }, tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.loadAiComposeTones();
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('updateAiTone', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
tone, title, emojiId, prompt, shouldDisplayAuthor,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const updatedTone = await callApi('updateAiTone', {
|
||||||
|
tone, title, emojiId, prompt, shouldDisplayAuthor,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updatedTone) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const currentTones = global.aiComposeTones?.tones || [];
|
||||||
|
const updatedTones = 'id' in updatedTone
|
||||||
|
? currentTones.map((t) => ('id' in t && t.id === updatedTone.id ? updatedTone : t))
|
||||||
|
: currentTones;
|
||||||
|
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
aiComposeTones: {
|
||||||
|
...global.aiComposeTones,
|
||||||
|
tones: updatedTones,
|
||||||
|
hash: global.aiComposeTones?.hash || '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
actions.closeAiToneEditorModal({ tabId });
|
||||||
|
actions.loadAiComposeTones();
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('openAiTonePreview', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { slug, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const result = await callApi('fetchAiTone', {
|
||||||
|
tone: { type: 'slug', slug },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result?.tones.length) {
|
||||||
|
actions.showNotification({ message: { key: 'ErrorUnspecified' }, tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tone = result.tones[0];
|
||||||
|
if (!('id' in tone)) return;
|
||||||
|
|
||||||
|
const example = await callApi('fetchAiToneExample', {
|
||||||
|
tone: { type: 'slug', slug },
|
||||||
|
num: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const currentTones = global.aiComposeTones?.tones || [];
|
||||||
|
const isAlreadyAdded = tone.isCreator || currentTones.some((t) => 'id' in t && t.id === tone.id);
|
||||||
|
global = updateTabState(global, {
|
||||||
|
aiTonePreviewModal: {
|
||||||
|
slug,
|
||||||
|
tone,
|
||||||
|
isAlreadyAdded,
|
||||||
|
example,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeAiTonePreview', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
return updateTabState(global, {
|
||||||
|
aiTonePreviewModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('saveAiTone', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { tone, unsave, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
if (!unsave && showToneLimitNotification(global, actions, tabId)) return;
|
||||||
|
|
||||||
|
const result = await callApi('saveAiTone', { tone, unsave });
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
actions.loadAiComposeTones();
|
||||||
|
actions.closeAiTonePreview({ tabId });
|
||||||
|
|
||||||
|
if (!unsave) {
|
||||||
|
actions.showNotification({
|
||||||
|
message: { key: 'AiTonePreviewStyleAdded' },
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadAiTonePreviewExample', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { tone, num, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
// Clear current example to trigger loading state
|
||||||
|
const currentModal = selectTabState(global, tabId).aiTonePreviewModal;
|
||||||
|
if (currentModal) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
aiTonePreviewModal: { ...currentModal, example: undefined, hasExampleError: undefined },
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
const example = await callApi('fetchAiToneExample', { tone, num });
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const previewModal = selectTabState(global, tabId).aiTonePreviewModal;
|
||||||
|
if (!previewModal) return;
|
||||||
|
|
||||||
|
const openModalTone: ApiInputAiComposeTone = { type: 'slug', slug: previewModal.slug };
|
||||||
|
if (!compareAiTones(openModalTone, tone)) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
aiTonePreviewModal: {
|
||||||
|
...previewModal,
|
||||||
|
example,
|
||||||
|
hasExampleError: !example,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadAiComposeTones', async (global): Promise<void> => {
|
addActionHandler('loadAiComposeTones', async (global): Promise<void> => {
|
||||||
const hash = global.aiComposeTones?.hash;
|
const hash = global.aiComposeTones?.hash;
|
||||||
const result = await callApi('fetchAiComposeTones', { hash });
|
const result = await callApi('fetchAiComposeTones', { hash });
|
||||||
|
|||||||
@ -1754,6 +1754,12 @@ addActionHandler('openTelegramLink', async (global, actions, payload): Promise<v
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (part1 === 'addstyle') {
|
||||||
|
if (!part2) return;
|
||||||
|
actions.openAiTonePreview({ slug: part2, tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (part1 === 'share') {
|
if (part1 === 'share') {
|
||||||
const text = formatShareText(params.url, params.text);
|
const text = formatShareText(params.url, params.text);
|
||||||
openChatWithDraft({ text, tabId });
|
openChatWithDraft({ text, tabId });
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { updateTabState } from '../../reducers/tabs';
|
|||||||
import { selectTabState } from '../../selectors';
|
import { selectTabState } from '../../selectors';
|
||||||
import { selectCurrentMessageList } from '../../selectors/messages';
|
import { selectCurrentMessageList } from '../../selectors/messages';
|
||||||
import { selectTranslationLanguage } from '../../selectors/settings';
|
import { selectTranslationLanguage } from '../../selectors/settings';
|
||||||
|
import { showToneLimitNotification } from '../api/ai';
|
||||||
|
|
||||||
addActionHandler('openAiMessageEditorModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openAiMessageEditorModal', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
@ -187,3 +188,23 @@ addActionHandler('clearAiMessageEditorPendingResult', (global, actions, payload)
|
|||||||
aiMessageEditorPendingResult: undefined,
|
aiMessageEditorPendingResult: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openAiToneEditorModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { toneToEdit, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
if (!toneToEdit && showToneLimitNotification(global, actions, tabId)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
aiToneEditorModal: { toneToEdit },
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeAiToneEditorModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
aiToneEditorModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
ApiAiComposeTone,
|
||||||
ApiAttachBot,
|
ApiAttachBot,
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiBirthday,
|
ApiBirthday,
|
||||||
@ -2694,6 +2695,38 @@ export interface ActionPayloads {
|
|||||||
scheduleRepeatPeriod?: number;
|
scheduleRepeatPeriod?: number;
|
||||||
} & WithTabId) | undefined;
|
} & WithTabId) | undefined;
|
||||||
clearAiMessageEditorPendingResult: WithTabId | undefined;
|
clearAiMessageEditorPendingResult: WithTabId | undefined;
|
||||||
|
openAiToneEditorModal: {
|
||||||
|
toneToEdit?: ApiAiComposeTone;
|
||||||
|
} & WithTabId | undefined;
|
||||||
|
closeAiToneEditorModal: WithTabId | undefined;
|
||||||
|
createAiTone: {
|
||||||
|
title: string;
|
||||||
|
emojiId: string;
|
||||||
|
prompt: string;
|
||||||
|
shouldDisplayAuthor?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
|
updateAiTone: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
title?: string;
|
||||||
|
emojiId?: string;
|
||||||
|
prompt?: string;
|
||||||
|
shouldDisplayAuthor?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
|
deleteAiTone: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
} & WithTabId;
|
||||||
|
openAiTonePreview: {
|
||||||
|
slug: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeAiTonePreview: WithTabId | undefined;
|
||||||
|
saveAiTone: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
unsave?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
|
loadAiTonePreviewExample: {
|
||||||
|
tone: ApiInputAiComposeTone;
|
||||||
|
num: number;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
openGiveawayModal: ({
|
openGiveawayModal: ({
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
|
ApiAiComposeTone,
|
||||||
|
ApiAiComposeToneExample,
|
||||||
ApiAttachBot,
|
ApiAttachBot,
|
||||||
ApiBirthday,
|
ApiBirthday,
|
||||||
ApiBoost,
|
ApiBoost,
|
||||||
@ -694,6 +696,18 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
aiToneEditorModal?: {
|
||||||
|
toneToEdit?: ApiAiComposeTone;
|
||||||
|
};
|
||||||
|
|
||||||
|
aiTonePreviewModal?: {
|
||||||
|
slug: string;
|
||||||
|
tone?: ApiAiComposeTone;
|
||||||
|
example?: ApiAiComposeToneExample;
|
||||||
|
isAlreadyAdded?: boolean;
|
||||||
|
hasExampleError?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
aiMessageEditorPendingResult?: {
|
aiMessageEditorPendingResult?: {
|
||||||
text?: ApiFormattedText;
|
text?: ApiFormattedText;
|
||||||
shouldClear?: boolean;
|
shouldClear?: boolean;
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export const DEFAULT_LIMITS: Record<ApiLimitType, readonly [number, number]> = {
|
|||||||
savedDialogsPinned: [5, 100],
|
savedDialogsPinned: [5, 100],
|
||||||
maxReactions: [1, 3],
|
maxReactions: [1, 3],
|
||||||
moreAccounts: [3, MULTIACCOUNT_MAX_SLOTS],
|
moreAccounts: [3, MULTIACCOUNT_MAX_SLOTS],
|
||||||
|
aiComposeToneSaved: [5, 20],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_MAX_MESSAGE_LENGTH = 4096;
|
export const DEFAULT_MAX_MESSAGE_LENGTH = 4096;
|
||||||
@ -58,6 +59,7 @@ export const DEFAULT_APP_CONFIG: ApiAppConfig = {
|
|||||||
savedDialogsPinned: DEFAULT_LIMITS.savedDialogsPinned,
|
savedDialogsPinned: DEFAULT_LIMITS.savedDialogsPinned,
|
||||||
moreAccounts: DEFAULT_LIMITS.moreAccounts,
|
moreAccounts: DEFAULT_LIMITS.moreAccounts,
|
||||||
maxReactions: DEFAULT_LIMITS.maxReactions,
|
maxReactions: DEFAULT_LIMITS.maxReactions,
|
||||||
|
aiComposeToneSaved: DEFAULT_LIMITS.aiComposeToneSaved,
|
||||||
},
|
},
|
||||||
autologinDomains: [
|
autologinDomains: [
|
||||||
'instantview.telegram.org',
|
'instantview.telegram.org',
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url("./icons.woff2?e9a7be346eaac71f831beea8710b79fd") format("woff2"),
|
src: url("./icons.woff2?2ae8509ab058d7ef62dd8b67cc2b2b75") format("woff2"),
|
||||||
url("./icons.woff?e9a7be346eaac71f831beea8710b79fd") format("woff");
|
url("./icons.woff?2ae8509ab058d7ef62dd8b67cc2b2b75") format("woff");
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-char::before {
|
.icon-char::before {
|
||||||
@ -720,324 +720,327 @@ url("./icons.woff?e9a7be346eaac71f831beea8710b79fd") format("woff");
|
|||||||
.icon-reload::before {
|
.icon-reload::before {
|
||||||
content: "\f1e9";
|
content: "\f1e9";
|
||||||
}
|
}
|
||||||
.icon-remove::before {
|
.icon-reload-arrows::before {
|
||||||
content: "\f1ea";
|
content: "\f1ea";
|
||||||
}
|
}
|
||||||
.icon-remove-quote::before {
|
.icon-remove::before {
|
||||||
content: "\f1eb";
|
content: "\f1eb";
|
||||||
}
|
}
|
||||||
.icon-reopen-topic::before {
|
.icon-remove-quote::before {
|
||||||
content: "\f1ec";
|
content: "\f1ec";
|
||||||
}
|
}
|
||||||
.icon-reorder-tabs::before {
|
.icon-reopen-topic::before {
|
||||||
content: "\f1ed";
|
content: "\f1ed";
|
||||||
}
|
}
|
||||||
.icon-replace::before {
|
.icon-reorder-tabs::before {
|
||||||
content: "\f1ee";
|
content: "\f1ee";
|
||||||
}
|
}
|
||||||
.icon-replace-round::before {
|
.icon-replace::before {
|
||||||
content: "\f1ef";
|
content: "\f1ef";
|
||||||
}
|
}
|
||||||
.icon-replies::before {
|
.icon-replace-round::before {
|
||||||
content: "\f1f0";
|
content: "\f1f0";
|
||||||
}
|
}
|
||||||
.icon-reply::before {
|
.icon-replies::before {
|
||||||
content: "\f1f1";
|
content: "\f1f1";
|
||||||
}
|
}
|
||||||
.icon-reply-filled::before {
|
.icon-reply::before {
|
||||||
content: "\f1f2";
|
content: "\f1f2";
|
||||||
}
|
}
|
||||||
.icon-revenue-split::before {
|
.icon-reply-filled::before {
|
||||||
content: "\f1f3";
|
content: "\f1f3";
|
||||||
}
|
}
|
||||||
.icon-revote::before {
|
.icon-revenue-split::before {
|
||||||
content: "\f1f4";
|
content: "\f1f4";
|
||||||
}
|
}
|
||||||
.icon-rotate::before {
|
.icon-revote::before {
|
||||||
content: "\f1f5";
|
content: "\f1f5";
|
||||||
}
|
}
|
||||||
.icon-save-story::before {
|
.icon-rotate::before {
|
||||||
content: "\f1f6";
|
content: "\f1f6";
|
||||||
}
|
}
|
||||||
.icon-saved-messages::before {
|
.icon-save-story::before {
|
||||||
content: "\f1f7";
|
content: "\f1f7";
|
||||||
}
|
}
|
||||||
.icon-schedule::before {
|
.icon-saved-messages::before {
|
||||||
content: "\f1f8";
|
content: "\f1f8";
|
||||||
}
|
}
|
||||||
.icon-scheduled::before {
|
.icon-schedule::before {
|
||||||
content: "\f1f9";
|
content: "\f1f9";
|
||||||
}
|
}
|
||||||
.icon-sd-photo::before {
|
.icon-scheduled::before {
|
||||||
content: "\f1fa";
|
content: "\f1fa";
|
||||||
}
|
}
|
||||||
.icon-search::before {
|
.icon-sd-photo::before {
|
||||||
content: "\f1fb";
|
content: "\f1fb";
|
||||||
}
|
}
|
||||||
.icon-select::before {
|
.icon-search::before {
|
||||||
content: "\f1fc";
|
content: "\f1fc";
|
||||||
}
|
}
|
||||||
.icon-select-filled::before {
|
.icon-select::before {
|
||||||
content: "\f1fd";
|
content: "\f1fd";
|
||||||
}
|
}
|
||||||
.icon-sell::before {
|
.icon-select-filled::before {
|
||||||
content: "\f1fe";
|
content: "\f1fe";
|
||||||
}
|
}
|
||||||
.icon-sell-outline::before {
|
.icon-sell::before {
|
||||||
content: "\f1ff";
|
content: "\f1ff";
|
||||||
}
|
}
|
||||||
.icon-send::before {
|
.icon-sell-outline::before {
|
||||||
content: "\f200";
|
content: "\f200";
|
||||||
}
|
}
|
||||||
.icon-send-outline::before {
|
.icon-send::before {
|
||||||
content: "\f201";
|
content: "\f201";
|
||||||
}
|
}
|
||||||
.icon-settings::before {
|
.icon-send-outline::before {
|
||||||
content: "\f202";
|
content: "\f202";
|
||||||
}
|
}
|
||||||
.icon-settings-filled::before {
|
.icon-settings::before {
|
||||||
content: "\f203";
|
content: "\f203";
|
||||||
}
|
}
|
||||||
.icon-share-filled::before {
|
.icon-settings-filled::before {
|
||||||
content: "\f204";
|
content: "\f204";
|
||||||
}
|
}
|
||||||
.icon-share-screen::before {
|
.icon-share-filled::before {
|
||||||
content: "\f205";
|
content: "\f205";
|
||||||
}
|
}
|
||||||
.icon-share-screen-outlined::before {
|
.icon-share-screen::before {
|
||||||
content: "\f206";
|
content: "\f206";
|
||||||
}
|
}
|
||||||
.icon-share-screen-stop::before {
|
.icon-share-screen-outlined::before {
|
||||||
content: "\f207";
|
content: "\f207";
|
||||||
}
|
}
|
||||||
.icon-show-message::before {
|
.icon-share-screen-stop::before {
|
||||||
content: "\f208";
|
content: "\f208";
|
||||||
}
|
}
|
||||||
.icon-sidebar::before {
|
.icon-show-message::before {
|
||||||
content: "\f209";
|
content: "\f209";
|
||||||
}
|
}
|
||||||
.icon-skip-next::before {
|
.icon-sidebar::before {
|
||||||
content: "\f20a";
|
content: "\f20a";
|
||||||
}
|
}
|
||||||
.icon-skip-previous::before {
|
.icon-skip-next::before {
|
||||||
content: "\f20b";
|
content: "\f20b";
|
||||||
}
|
}
|
||||||
.icon-smallscreen::before {
|
.icon-skip-previous::before {
|
||||||
content: "\f20c";
|
content: "\f20c";
|
||||||
}
|
}
|
||||||
.icon-smile::before {
|
.icon-smallscreen::before {
|
||||||
content: "\f20d";
|
content: "\f20d";
|
||||||
}
|
}
|
||||||
.icon-sort::before {
|
.icon-smile::before {
|
||||||
content: "\f20e";
|
content: "\f20e";
|
||||||
}
|
}
|
||||||
.icon-sort-by-date::before {
|
.icon-sort::before {
|
||||||
content: "\f20f";
|
content: "\f20f";
|
||||||
}
|
}
|
||||||
.icon-sort-by-number::before {
|
.icon-sort-by-date::before {
|
||||||
content: "\f210";
|
content: "\f210";
|
||||||
}
|
}
|
||||||
.icon-sort-by-price::before {
|
.icon-sort-by-number::before {
|
||||||
content: "\f211";
|
content: "\f211";
|
||||||
}
|
}
|
||||||
.icon-speaker::before {
|
.icon-sort-by-price::before {
|
||||||
content: "\f212";
|
content: "\f212";
|
||||||
}
|
}
|
||||||
.icon-speaker-muted-story::before {
|
.icon-speaker::before {
|
||||||
content: "\f213";
|
content: "\f213";
|
||||||
}
|
}
|
||||||
.icon-speaker-outline::before {
|
.icon-speaker-muted-story::before {
|
||||||
content: "\f214";
|
content: "\f214";
|
||||||
}
|
}
|
||||||
.icon-speaker-story::before {
|
.icon-speaker-outline::before {
|
||||||
content: "\f215";
|
content: "\f215";
|
||||||
}
|
}
|
||||||
.icon-spoiler::before {
|
.icon-speaker-story::before {
|
||||||
content: "\f216";
|
content: "\f216";
|
||||||
}
|
}
|
||||||
.icon-spoiler-disable::before {
|
.icon-spoiler::before {
|
||||||
content: "\f217";
|
content: "\f217";
|
||||||
}
|
}
|
||||||
.icon-sport::before {
|
.icon-spoiler-disable::before {
|
||||||
content: "\f218";
|
content: "\f218";
|
||||||
}
|
}
|
||||||
.icon-star::before {
|
.icon-sport::before {
|
||||||
content: "\f219";
|
content: "\f219";
|
||||||
}
|
}
|
||||||
.icon-stars-lock::before {
|
.icon-star::before {
|
||||||
content: "\f21a";
|
content: "\f21a";
|
||||||
}
|
}
|
||||||
.icon-stars-refund::before {
|
.icon-stars-lock::before {
|
||||||
content: "\f21b";
|
content: "\f21b";
|
||||||
}
|
}
|
||||||
.icon-stats::before {
|
.icon-stars-refund::before {
|
||||||
content: "\f21c";
|
content: "\f21c";
|
||||||
}
|
}
|
||||||
.icon-stealth-future::before {
|
.icon-stats::before {
|
||||||
content: "\f21d";
|
content: "\f21d";
|
||||||
}
|
}
|
||||||
.icon-stealth-past::before {
|
.icon-stealth-future::before {
|
||||||
content: "\f21e";
|
content: "\f21e";
|
||||||
}
|
}
|
||||||
.icon-stickers::before {
|
.icon-stealth-past::before {
|
||||||
content: "\f21f";
|
content: "\f21f";
|
||||||
}
|
}
|
||||||
.icon-stop::before {
|
.icon-stickers::before {
|
||||||
content: "\f220";
|
content: "\f220";
|
||||||
}
|
}
|
||||||
.icon-stop-raising-hand::before {
|
.icon-stop::before {
|
||||||
content: "\f221";
|
content: "\f221";
|
||||||
}
|
}
|
||||||
.icon-story-caption::before {
|
.icon-stop-raising-hand::before {
|
||||||
content: "\f222";
|
content: "\f222";
|
||||||
}
|
}
|
||||||
.icon-story-expired::before {
|
.icon-story-caption::before {
|
||||||
content: "\f223";
|
content: "\f223";
|
||||||
}
|
}
|
||||||
.icon-story-priority::before {
|
.icon-story-expired::before {
|
||||||
content: "\f224";
|
content: "\f224";
|
||||||
}
|
}
|
||||||
.icon-story-reply::before {
|
.icon-story-priority::before {
|
||||||
content: "\f225";
|
content: "\f225";
|
||||||
}
|
}
|
||||||
.icon-strikethrough::before {
|
.icon-story-reply::before {
|
||||||
content: "\f226";
|
content: "\f226";
|
||||||
}
|
}
|
||||||
.icon-tag::before {
|
.icon-strikethrough::before {
|
||||||
content: "\f227";
|
content: "\f227";
|
||||||
}
|
}
|
||||||
.icon-tag-add::before {
|
.icon-tag::before {
|
||||||
content: "\f228";
|
content: "\f228";
|
||||||
}
|
}
|
||||||
.icon-tag-crossed::before {
|
.icon-tag-add::before {
|
||||||
content: "\f229";
|
content: "\f229";
|
||||||
}
|
}
|
||||||
.icon-tag-filter::before {
|
.icon-tag-crossed::before {
|
||||||
content: "\f22a";
|
content: "\f22a";
|
||||||
}
|
}
|
||||||
.icon-tag-name::before {
|
.icon-tag-filter::before {
|
||||||
content: "\f22b";
|
content: "\f22b";
|
||||||
}
|
}
|
||||||
.icon-timer::before {
|
.icon-tag-name::before {
|
||||||
content: "\f22c";
|
content: "\f22c";
|
||||||
}
|
}
|
||||||
.icon-timer-filled::before {
|
.icon-timer::before {
|
||||||
content: "\f22d";
|
content: "\f22d";
|
||||||
}
|
}
|
||||||
.icon-toncoin::before {
|
.icon-timer-filled::before {
|
||||||
content: "\f22e";
|
content: "\f22e";
|
||||||
}
|
}
|
||||||
.icon-tone::before {
|
.icon-toncoin::before {
|
||||||
content: "\f22f";
|
content: "\f22f";
|
||||||
}
|
}
|
||||||
.icon-tools::before {
|
.icon-tone::before {
|
||||||
content: "\f230";
|
content: "\f230";
|
||||||
}
|
}
|
||||||
.icon-topic-new::before {
|
.icon-tools::before {
|
||||||
content: "\f231";
|
content: "\f231";
|
||||||
}
|
}
|
||||||
.icon-trade::before {
|
.icon-topic-new::before {
|
||||||
content: "\f232";
|
content: "\f232";
|
||||||
}
|
}
|
||||||
.icon-transcribe::before {
|
.icon-trade::before {
|
||||||
content: "\f233";
|
content: "\f233";
|
||||||
}
|
}
|
||||||
.icon-truck::before {
|
.icon-transcribe::before {
|
||||||
content: "\f234";
|
content: "\f234";
|
||||||
}
|
}
|
||||||
.icon-unarchive::before {
|
.icon-truck::before {
|
||||||
content: "\f235";
|
content: "\f235";
|
||||||
}
|
}
|
||||||
.icon-underlined::before {
|
.icon-unarchive::before {
|
||||||
content: "\f236";
|
content: "\f236";
|
||||||
}
|
}
|
||||||
.icon-understood::before {
|
.icon-underlined::before {
|
||||||
content: "\f237";
|
content: "\f237";
|
||||||
}
|
}
|
||||||
.icon-undo::before {
|
.icon-understood::before {
|
||||||
content: "\f238";
|
content: "\f238";
|
||||||
}
|
}
|
||||||
.icon-unique-profile::before {
|
.icon-undo::before {
|
||||||
content: "\f239";
|
content: "\f239";
|
||||||
}
|
}
|
||||||
.icon-unlist::before {
|
.icon-unique-profile::before {
|
||||||
content: "\f23a";
|
content: "\f23a";
|
||||||
}
|
}
|
||||||
.icon-unlist-outline::before {
|
.icon-unlist::before {
|
||||||
content: "\f23b";
|
content: "\f23b";
|
||||||
}
|
}
|
||||||
.icon-unlock::before {
|
.icon-unlist-outline::before {
|
||||||
content: "\f23c";
|
content: "\f23c";
|
||||||
}
|
}
|
||||||
.icon-unlock-badge::before {
|
.icon-unlock::before {
|
||||||
content: "\f23d";
|
content: "\f23d";
|
||||||
}
|
}
|
||||||
.icon-unmute::before {
|
.icon-unlock-badge::before {
|
||||||
content: "\f23e";
|
content: "\f23e";
|
||||||
}
|
}
|
||||||
.icon-unpin::before {
|
.icon-unmute::before {
|
||||||
content: "\f23f";
|
content: "\f23f";
|
||||||
}
|
}
|
||||||
.icon-unread::before {
|
.icon-unpin::before {
|
||||||
content: "\f240";
|
content: "\f240";
|
||||||
}
|
}
|
||||||
.icon-up::before {
|
.icon-unread::before {
|
||||||
content: "\f241";
|
content: "\f241";
|
||||||
}
|
}
|
||||||
.icon-user::before {
|
.icon-up::before {
|
||||||
content: "\f242";
|
content: "\f242";
|
||||||
}
|
}
|
||||||
.icon-user-filled::before {
|
.icon-user::before {
|
||||||
content: "\f243";
|
content: "\f243";
|
||||||
}
|
}
|
||||||
.icon-user-online::before {
|
.icon-user-filled::before {
|
||||||
content: "\f244";
|
content: "\f244";
|
||||||
}
|
}
|
||||||
.icon-user-stars::before {
|
.icon-user-online::before {
|
||||||
content: "\f245";
|
content: "\f245";
|
||||||
}
|
}
|
||||||
.icon-user-tag::before {
|
.icon-user-stars::before {
|
||||||
content: "\f246";
|
content: "\f246";
|
||||||
}
|
}
|
||||||
.icon-video::before {
|
.icon-user-tag::before {
|
||||||
content: "\f247";
|
content: "\f247";
|
||||||
}
|
}
|
||||||
.icon-video-outlined::before {
|
.icon-video::before {
|
||||||
content: "\f248";
|
content: "\f248";
|
||||||
}
|
}
|
||||||
.icon-video-stop::before {
|
.icon-video-outlined::before {
|
||||||
content: "\f249";
|
content: "\f249";
|
||||||
}
|
}
|
||||||
.icon-view-once::before {
|
.icon-video-stop::before {
|
||||||
content: "\f24a";
|
content: "\f24a";
|
||||||
}
|
}
|
||||||
.icon-voice-chat::before {
|
.icon-view-once::before {
|
||||||
content: "\f24b";
|
content: "\f24b";
|
||||||
}
|
}
|
||||||
.icon-volume-1::before {
|
.icon-voice-chat::before {
|
||||||
content: "\f24c";
|
content: "\f24c";
|
||||||
}
|
}
|
||||||
.icon-volume-2::before {
|
.icon-volume-1::before {
|
||||||
content: "\f24d";
|
content: "\f24d";
|
||||||
}
|
}
|
||||||
.icon-volume-3::before {
|
.icon-volume-2::before {
|
||||||
content: "\f24e";
|
content: "\f24e";
|
||||||
}
|
}
|
||||||
.icon-warning::before {
|
.icon-volume-3::before {
|
||||||
content: "\f24f";
|
content: "\f24f";
|
||||||
}
|
}
|
||||||
.icon-web::before {
|
.icon-warning::before {
|
||||||
content: "\f250";
|
content: "\f250";
|
||||||
}
|
}
|
||||||
.icon-webapp::before {
|
.icon-web::before {
|
||||||
content: "\f251";
|
content: "\f251";
|
||||||
}
|
}
|
||||||
.icon-word-wrap::before {
|
.icon-webapp::before {
|
||||||
content: "\f252";
|
content: "\f252";
|
||||||
}
|
}
|
||||||
.icon-zoom-in::before {
|
.icon-word-wrap::before {
|
||||||
content: "\f253";
|
content: "\f253";
|
||||||
}
|
}
|
||||||
.icon-zoom-out::before {
|
.icon-zoom-in::before {
|
||||||
content: "\f254";
|
content: "\f254";
|
||||||
}
|
}
|
||||||
|
.icon-zoom-out::before {
|
||||||
|
content: "\f255";
|
||||||
|
}
|
||||||
|
|||||||
@ -249,111 +249,112 @@ $icons-map: (
|
|||||||
"redo": "\f1e7",
|
"redo": "\f1e7",
|
||||||
"refund": "\f1e8",
|
"refund": "\f1e8",
|
||||||
"reload": "\f1e9",
|
"reload": "\f1e9",
|
||||||
"remove": "\f1ea",
|
"reload-arrows": "\f1ea",
|
||||||
"remove-quote": "\f1eb",
|
"remove": "\f1eb",
|
||||||
"reopen-topic": "\f1ec",
|
"remove-quote": "\f1ec",
|
||||||
"reorder-tabs": "\f1ed",
|
"reopen-topic": "\f1ed",
|
||||||
"replace": "\f1ee",
|
"reorder-tabs": "\f1ee",
|
||||||
"replace-round": "\f1ef",
|
"replace": "\f1ef",
|
||||||
"replies": "\f1f0",
|
"replace-round": "\f1f0",
|
||||||
"reply": "\f1f1",
|
"replies": "\f1f1",
|
||||||
"reply-filled": "\f1f2",
|
"reply": "\f1f2",
|
||||||
"revenue-split": "\f1f3",
|
"reply-filled": "\f1f3",
|
||||||
"revote": "\f1f4",
|
"revenue-split": "\f1f4",
|
||||||
"rotate": "\f1f5",
|
"revote": "\f1f5",
|
||||||
"save-story": "\f1f6",
|
"rotate": "\f1f6",
|
||||||
"saved-messages": "\f1f7",
|
"save-story": "\f1f7",
|
||||||
"schedule": "\f1f8",
|
"saved-messages": "\f1f8",
|
||||||
"scheduled": "\f1f9",
|
"schedule": "\f1f9",
|
||||||
"sd-photo": "\f1fa",
|
"scheduled": "\f1fa",
|
||||||
"search": "\f1fb",
|
"sd-photo": "\f1fb",
|
||||||
"select": "\f1fc",
|
"search": "\f1fc",
|
||||||
"select-filled": "\f1fd",
|
"select": "\f1fd",
|
||||||
"sell": "\f1fe",
|
"select-filled": "\f1fe",
|
||||||
"sell-outline": "\f1ff",
|
"sell": "\f1ff",
|
||||||
"send": "\f200",
|
"sell-outline": "\f200",
|
||||||
"send-outline": "\f201",
|
"send": "\f201",
|
||||||
"settings": "\f202",
|
"send-outline": "\f202",
|
||||||
"settings-filled": "\f203",
|
"settings": "\f203",
|
||||||
"share-filled": "\f204",
|
"settings-filled": "\f204",
|
||||||
"share-screen": "\f205",
|
"share-filled": "\f205",
|
||||||
"share-screen-outlined": "\f206",
|
"share-screen": "\f206",
|
||||||
"share-screen-stop": "\f207",
|
"share-screen-outlined": "\f207",
|
||||||
"show-message": "\f208",
|
"share-screen-stop": "\f208",
|
||||||
"sidebar": "\f209",
|
"show-message": "\f209",
|
||||||
"skip-next": "\f20a",
|
"sidebar": "\f20a",
|
||||||
"skip-previous": "\f20b",
|
"skip-next": "\f20b",
|
||||||
"smallscreen": "\f20c",
|
"skip-previous": "\f20c",
|
||||||
"smile": "\f20d",
|
"smallscreen": "\f20d",
|
||||||
"sort": "\f20e",
|
"smile": "\f20e",
|
||||||
"sort-by-date": "\f20f",
|
"sort": "\f20f",
|
||||||
"sort-by-number": "\f210",
|
"sort-by-date": "\f210",
|
||||||
"sort-by-price": "\f211",
|
"sort-by-number": "\f211",
|
||||||
"speaker": "\f212",
|
"sort-by-price": "\f212",
|
||||||
"speaker-muted-story": "\f213",
|
"speaker": "\f213",
|
||||||
"speaker-outline": "\f214",
|
"speaker-muted-story": "\f214",
|
||||||
"speaker-story": "\f215",
|
"speaker-outline": "\f215",
|
||||||
"spoiler": "\f216",
|
"speaker-story": "\f216",
|
||||||
"spoiler-disable": "\f217",
|
"spoiler": "\f217",
|
||||||
"sport": "\f218",
|
"spoiler-disable": "\f218",
|
||||||
"star": "\f219",
|
"sport": "\f219",
|
||||||
"stars-lock": "\f21a",
|
"star": "\f21a",
|
||||||
"stars-refund": "\f21b",
|
"stars-lock": "\f21b",
|
||||||
"stats": "\f21c",
|
"stars-refund": "\f21c",
|
||||||
"stealth-future": "\f21d",
|
"stats": "\f21d",
|
||||||
"stealth-past": "\f21e",
|
"stealth-future": "\f21e",
|
||||||
"stickers": "\f21f",
|
"stealth-past": "\f21f",
|
||||||
"stop": "\f220",
|
"stickers": "\f220",
|
||||||
"stop-raising-hand": "\f221",
|
"stop": "\f221",
|
||||||
"story-caption": "\f222",
|
"stop-raising-hand": "\f222",
|
||||||
"story-expired": "\f223",
|
"story-caption": "\f223",
|
||||||
"story-priority": "\f224",
|
"story-expired": "\f224",
|
||||||
"story-reply": "\f225",
|
"story-priority": "\f225",
|
||||||
"strikethrough": "\f226",
|
"story-reply": "\f226",
|
||||||
"tag": "\f227",
|
"strikethrough": "\f227",
|
||||||
"tag-add": "\f228",
|
"tag": "\f228",
|
||||||
"tag-crossed": "\f229",
|
"tag-add": "\f229",
|
||||||
"tag-filter": "\f22a",
|
"tag-crossed": "\f22a",
|
||||||
"tag-name": "\f22b",
|
"tag-filter": "\f22b",
|
||||||
"timer": "\f22c",
|
"tag-name": "\f22c",
|
||||||
"timer-filled": "\f22d",
|
"timer": "\f22d",
|
||||||
"toncoin": "\f22e",
|
"timer-filled": "\f22e",
|
||||||
"tone": "\f22f",
|
"toncoin": "\f22f",
|
||||||
"tools": "\f230",
|
"tone": "\f230",
|
||||||
"topic-new": "\f231",
|
"tools": "\f231",
|
||||||
"trade": "\f232",
|
"topic-new": "\f232",
|
||||||
"transcribe": "\f233",
|
"trade": "\f233",
|
||||||
"truck": "\f234",
|
"transcribe": "\f234",
|
||||||
"unarchive": "\f235",
|
"truck": "\f235",
|
||||||
"underlined": "\f236",
|
"unarchive": "\f236",
|
||||||
"understood": "\f237",
|
"underlined": "\f237",
|
||||||
"undo": "\f238",
|
"understood": "\f238",
|
||||||
"unique-profile": "\f239",
|
"undo": "\f239",
|
||||||
"unlist": "\f23a",
|
"unique-profile": "\f23a",
|
||||||
"unlist-outline": "\f23b",
|
"unlist": "\f23b",
|
||||||
"unlock": "\f23c",
|
"unlist-outline": "\f23c",
|
||||||
"unlock-badge": "\f23d",
|
"unlock": "\f23d",
|
||||||
"unmute": "\f23e",
|
"unlock-badge": "\f23e",
|
||||||
"unpin": "\f23f",
|
"unmute": "\f23f",
|
||||||
"unread": "\f240",
|
"unpin": "\f240",
|
||||||
"up": "\f241",
|
"unread": "\f241",
|
||||||
"user": "\f242",
|
"up": "\f242",
|
||||||
"user-filled": "\f243",
|
"user": "\f243",
|
||||||
"user-online": "\f244",
|
"user-filled": "\f244",
|
||||||
"user-stars": "\f245",
|
"user-online": "\f245",
|
||||||
"user-tag": "\f246",
|
"user-stars": "\f246",
|
||||||
"video": "\f247",
|
"user-tag": "\f247",
|
||||||
"video-outlined": "\f248",
|
"video": "\f248",
|
||||||
"video-stop": "\f249",
|
"video-outlined": "\f249",
|
||||||
"view-once": "\f24a",
|
"video-stop": "\f24a",
|
||||||
"voice-chat": "\f24b",
|
"view-once": "\f24b",
|
||||||
"volume-1": "\f24c",
|
"voice-chat": "\f24c",
|
||||||
"volume-2": "\f24d",
|
"volume-1": "\f24d",
|
||||||
"volume-3": "\f24e",
|
"volume-2": "\f24e",
|
||||||
"warning": "\f24f",
|
"volume-3": "\f24f",
|
||||||
"web": "\f250",
|
"warning": "\f250",
|
||||||
"webapp": "\f251",
|
"web": "\f251",
|
||||||
"word-wrap": "\f252",
|
"webapp": "\f252",
|
||||||
"zoom-in": "\f253",
|
"word-wrap": "\f253",
|
||||||
"zoom-out": "\f254",
|
"zoom-in": "\f254",
|
||||||
|
"zoom-out": "\f255",
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>icons Preview</title>
|
<title>icons Preview</title>
|
||||||
<link rel="stylesheet" href="../icons.css?e9a7be346eaac71f831beea8710b79fd">
|
<link rel="stylesheet" href="../icons.css?2ae8509ab058d7ef62dd8b67cc2b2b75">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<main class="page">
|
<main class="page">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<h1 class="title">icons Preview</h1>
|
<h1 class="title">icons Preview</h1>
|
||||||
<p class="subtitle">340 icons</p>
|
<p class="subtitle">341 icons</p>
|
||||||
</header>
|
</header>
|
||||||
<section class="grid">
|
<section class="grid">
|
||||||
<article class="card">
|
<article class="card">
|
||||||
@ -1261,540 +1261,545 @@
|
|||||||
<div class="name">reload</div>
|
<div class="name">reload</div>
|
||||||
<div class="code">\f1e9</div>
|
<div class="code">\f1e9</div>
|
||||||
</article>
|
</article>
|
||||||
|
<article class="card">
|
||||||
|
<i class="icon icon-reload-arrows"></i>
|
||||||
|
<div class="name">reload-arrows</div>
|
||||||
|
<div class="code">\f1ea</div>
|
||||||
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-remove"></i>
|
<i class="icon icon-remove"></i>
|
||||||
<div class="name">remove</div>
|
<div class="name">remove</div>
|
||||||
<div class="code">\f1ea</div>
|
<div class="code">\f1eb</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-remove-quote"></i>
|
<i class="icon icon-remove-quote"></i>
|
||||||
<div class="name">remove-quote</div>
|
<div class="name">remove-quote</div>
|
||||||
<div class="code">\f1eb</div>
|
<div class="code">\f1ec</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-reopen-topic"></i>
|
<i class="icon icon-reopen-topic"></i>
|
||||||
<div class="name">reopen-topic</div>
|
<div class="name">reopen-topic</div>
|
||||||
<div class="code">\f1ec</div>
|
<div class="code">\f1ed</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-reorder-tabs"></i>
|
<i class="icon icon-reorder-tabs"></i>
|
||||||
<div class="name">reorder-tabs</div>
|
<div class="name">reorder-tabs</div>
|
||||||
<div class="code">\f1ed</div>
|
<div class="code">\f1ee</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-replace"></i>
|
<i class="icon icon-replace"></i>
|
||||||
<div class="name">replace</div>
|
<div class="name">replace</div>
|
||||||
<div class="code">\f1ee</div>
|
<div class="code">\f1ef</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-replace-round"></i>
|
<i class="icon icon-replace-round"></i>
|
||||||
<div class="name">replace-round</div>
|
<div class="name">replace-round</div>
|
||||||
<div class="code">\f1ef</div>
|
<div class="code">\f1f0</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-replies"></i>
|
<i class="icon icon-replies"></i>
|
||||||
<div class="name">replies</div>
|
<div class="name">replies</div>
|
||||||
<div class="code">\f1f0</div>
|
<div class="code">\f1f1</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-reply"></i>
|
<i class="icon icon-reply"></i>
|
||||||
<div class="name">reply</div>
|
<div class="name">reply</div>
|
||||||
<div class="code">\f1f1</div>
|
<div class="code">\f1f2</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-reply-filled"></i>
|
<i class="icon icon-reply-filled"></i>
|
||||||
<div class="name">reply-filled</div>
|
<div class="name">reply-filled</div>
|
||||||
<div class="code">\f1f2</div>
|
<div class="code">\f1f3</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-revenue-split"></i>
|
<i class="icon icon-revenue-split"></i>
|
||||||
<div class="name">revenue-split</div>
|
<div class="name">revenue-split</div>
|
||||||
<div class="code">\f1f3</div>
|
<div class="code">\f1f4</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-revote"></i>
|
<i class="icon icon-revote"></i>
|
||||||
<div class="name">revote</div>
|
<div class="name">revote</div>
|
||||||
<div class="code">\f1f4</div>
|
<div class="code">\f1f5</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-rotate"></i>
|
<i class="icon icon-rotate"></i>
|
||||||
<div class="name">rotate</div>
|
<div class="name">rotate</div>
|
||||||
<div class="code">\f1f5</div>
|
<div class="code">\f1f6</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-save-story"></i>
|
<i class="icon icon-save-story"></i>
|
||||||
<div class="name">save-story</div>
|
<div class="name">save-story</div>
|
||||||
<div class="code">\f1f6</div>
|
<div class="code">\f1f7</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-saved-messages"></i>
|
<i class="icon icon-saved-messages"></i>
|
||||||
<div class="name">saved-messages</div>
|
<div class="name">saved-messages</div>
|
||||||
<div class="code">\f1f7</div>
|
<div class="code">\f1f8</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-schedule"></i>
|
<i class="icon icon-schedule"></i>
|
||||||
<div class="name">schedule</div>
|
<div class="name">schedule</div>
|
||||||
<div class="code">\f1f8</div>
|
<div class="code">\f1f9</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-scheduled"></i>
|
<i class="icon icon-scheduled"></i>
|
||||||
<div class="name">scheduled</div>
|
<div class="name">scheduled</div>
|
||||||
<div class="code">\f1f9</div>
|
<div class="code">\f1fa</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sd-photo"></i>
|
<i class="icon icon-sd-photo"></i>
|
||||||
<div class="name">sd-photo</div>
|
<div class="name">sd-photo</div>
|
||||||
<div class="code">\f1fa</div>
|
<div class="code">\f1fb</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-search"></i>
|
<i class="icon icon-search"></i>
|
||||||
<div class="name">search</div>
|
<div class="name">search</div>
|
||||||
<div class="code">\f1fb</div>
|
<div class="code">\f1fc</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-select"></i>
|
<i class="icon icon-select"></i>
|
||||||
<div class="name">select</div>
|
<div class="name">select</div>
|
||||||
<div class="code">\f1fc</div>
|
<div class="code">\f1fd</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-select-filled"></i>
|
<i class="icon icon-select-filled"></i>
|
||||||
<div class="name">select-filled</div>
|
<div class="name">select-filled</div>
|
||||||
<div class="code">\f1fd</div>
|
<div class="code">\f1fe</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sell"></i>
|
<i class="icon icon-sell"></i>
|
||||||
<div class="name">sell</div>
|
<div class="name">sell</div>
|
||||||
<div class="code">\f1fe</div>
|
<div class="code">\f1ff</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sell-outline"></i>
|
<i class="icon icon-sell-outline"></i>
|
||||||
<div class="name">sell-outline</div>
|
<div class="name">sell-outline</div>
|
||||||
<div class="code">\f1ff</div>
|
<div class="code">\f200</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-send"></i>
|
<i class="icon icon-send"></i>
|
||||||
<div class="name">send</div>
|
<div class="name">send</div>
|
||||||
<div class="code">\f200</div>
|
<div class="code">\f201</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-send-outline"></i>
|
<i class="icon icon-send-outline"></i>
|
||||||
<div class="name">send-outline</div>
|
<div class="name">send-outline</div>
|
||||||
<div class="code">\f201</div>
|
<div class="code">\f202</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-settings"></i>
|
<i class="icon icon-settings"></i>
|
||||||
<div class="name">settings</div>
|
<div class="name">settings</div>
|
||||||
<div class="code">\f202</div>
|
<div class="code">\f203</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-settings-filled"></i>
|
<i class="icon icon-settings-filled"></i>
|
||||||
<div class="name">settings-filled</div>
|
<div class="name">settings-filled</div>
|
||||||
<div class="code">\f203</div>
|
<div class="code">\f204</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-share-filled"></i>
|
<i class="icon icon-share-filled"></i>
|
||||||
<div class="name">share-filled</div>
|
<div class="name">share-filled</div>
|
||||||
<div class="code">\f204</div>
|
<div class="code">\f205</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-share-screen"></i>
|
<i class="icon icon-share-screen"></i>
|
||||||
<div class="name">share-screen</div>
|
<div class="name">share-screen</div>
|
||||||
<div class="code">\f205</div>
|
<div class="code">\f206</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-share-screen-outlined"></i>
|
<i class="icon icon-share-screen-outlined"></i>
|
||||||
<div class="name">share-screen-outlined</div>
|
<div class="name">share-screen-outlined</div>
|
||||||
<div class="code">\f206</div>
|
<div class="code">\f207</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-share-screen-stop"></i>
|
<i class="icon icon-share-screen-stop"></i>
|
||||||
<div class="name">share-screen-stop</div>
|
<div class="name">share-screen-stop</div>
|
||||||
<div class="code">\f207</div>
|
<div class="code">\f208</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-show-message"></i>
|
<i class="icon icon-show-message"></i>
|
||||||
<div class="name">show-message</div>
|
<div class="name">show-message</div>
|
||||||
<div class="code">\f208</div>
|
<div class="code">\f209</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sidebar"></i>
|
<i class="icon icon-sidebar"></i>
|
||||||
<div class="name">sidebar</div>
|
<div class="name">sidebar</div>
|
||||||
<div class="code">\f209</div>
|
<div class="code">\f20a</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-skip-next"></i>
|
<i class="icon icon-skip-next"></i>
|
||||||
<div class="name">skip-next</div>
|
<div class="name">skip-next</div>
|
||||||
<div class="code">\f20a</div>
|
<div class="code">\f20b</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-skip-previous"></i>
|
<i class="icon icon-skip-previous"></i>
|
||||||
<div class="name">skip-previous</div>
|
<div class="name">skip-previous</div>
|
||||||
<div class="code">\f20b</div>
|
<div class="code">\f20c</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-smallscreen"></i>
|
<i class="icon icon-smallscreen"></i>
|
||||||
<div class="name">smallscreen</div>
|
<div class="name">smallscreen</div>
|
||||||
<div class="code">\f20c</div>
|
<div class="code">\f20d</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-smile"></i>
|
<i class="icon icon-smile"></i>
|
||||||
<div class="name">smile</div>
|
<div class="name">smile</div>
|
||||||
<div class="code">\f20d</div>
|
<div class="code">\f20e</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sort"></i>
|
<i class="icon icon-sort"></i>
|
||||||
<div class="name">sort</div>
|
<div class="name">sort</div>
|
||||||
<div class="code">\f20e</div>
|
<div class="code">\f20f</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sort-by-date"></i>
|
<i class="icon icon-sort-by-date"></i>
|
||||||
<div class="name">sort-by-date</div>
|
<div class="name">sort-by-date</div>
|
||||||
<div class="code">\f20f</div>
|
<div class="code">\f210</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sort-by-number"></i>
|
<i class="icon icon-sort-by-number"></i>
|
||||||
<div class="name">sort-by-number</div>
|
<div class="name">sort-by-number</div>
|
||||||
<div class="code">\f210</div>
|
<div class="code">\f211</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sort-by-price"></i>
|
<i class="icon icon-sort-by-price"></i>
|
||||||
<div class="name">sort-by-price</div>
|
<div class="name">sort-by-price</div>
|
||||||
<div class="code">\f211</div>
|
<div class="code">\f212</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-speaker"></i>
|
<i class="icon icon-speaker"></i>
|
||||||
<div class="name">speaker</div>
|
<div class="name">speaker</div>
|
||||||
<div class="code">\f212</div>
|
<div class="code">\f213</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-speaker-muted-story"></i>
|
<i class="icon icon-speaker-muted-story"></i>
|
||||||
<div class="name">speaker-muted-story</div>
|
<div class="name">speaker-muted-story</div>
|
||||||
<div class="code">\f213</div>
|
<div class="code">\f214</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-speaker-outline"></i>
|
<i class="icon icon-speaker-outline"></i>
|
||||||
<div class="name">speaker-outline</div>
|
<div class="name">speaker-outline</div>
|
||||||
<div class="code">\f214</div>
|
<div class="code">\f215</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-speaker-story"></i>
|
<i class="icon icon-speaker-story"></i>
|
||||||
<div class="name">speaker-story</div>
|
<div class="name">speaker-story</div>
|
||||||
<div class="code">\f215</div>
|
<div class="code">\f216</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-spoiler"></i>
|
<i class="icon icon-spoiler"></i>
|
||||||
<div class="name">spoiler</div>
|
<div class="name">spoiler</div>
|
||||||
<div class="code">\f216</div>
|
<div class="code">\f217</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-spoiler-disable"></i>
|
<i class="icon icon-spoiler-disable"></i>
|
||||||
<div class="name">spoiler-disable</div>
|
<div class="name">spoiler-disable</div>
|
||||||
<div class="code">\f217</div>
|
<div class="code">\f218</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-sport"></i>
|
<i class="icon icon-sport"></i>
|
||||||
<div class="name">sport</div>
|
<div class="name">sport</div>
|
||||||
<div class="code">\f218</div>
|
<div class="code">\f219</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-star"></i>
|
<i class="icon icon-star"></i>
|
||||||
<div class="name">star</div>
|
<div class="name">star</div>
|
||||||
<div class="code">\f219</div>
|
<div class="code">\f21a</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stars-lock"></i>
|
<i class="icon icon-stars-lock"></i>
|
||||||
<div class="name">stars-lock</div>
|
<div class="name">stars-lock</div>
|
||||||
<div class="code">\f21a</div>
|
<div class="code">\f21b</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stars-refund"></i>
|
<i class="icon icon-stars-refund"></i>
|
||||||
<div class="name">stars-refund</div>
|
<div class="name">stars-refund</div>
|
||||||
<div class="code">\f21b</div>
|
<div class="code">\f21c</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stats"></i>
|
<i class="icon icon-stats"></i>
|
||||||
<div class="name">stats</div>
|
<div class="name">stats</div>
|
||||||
<div class="code">\f21c</div>
|
<div class="code">\f21d</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stealth-future"></i>
|
<i class="icon icon-stealth-future"></i>
|
||||||
<div class="name">stealth-future</div>
|
<div class="name">stealth-future</div>
|
||||||
<div class="code">\f21d</div>
|
<div class="code">\f21e</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stealth-past"></i>
|
<i class="icon icon-stealth-past"></i>
|
||||||
<div class="name">stealth-past</div>
|
<div class="name">stealth-past</div>
|
||||||
<div class="code">\f21e</div>
|
<div class="code">\f21f</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stickers"></i>
|
<i class="icon icon-stickers"></i>
|
||||||
<div class="name">stickers</div>
|
<div class="name">stickers</div>
|
||||||
<div class="code">\f21f</div>
|
<div class="code">\f220</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stop"></i>
|
<i class="icon icon-stop"></i>
|
||||||
<div class="name">stop</div>
|
<div class="name">stop</div>
|
||||||
<div class="code">\f220</div>
|
<div class="code">\f221</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-stop-raising-hand"></i>
|
<i class="icon icon-stop-raising-hand"></i>
|
||||||
<div class="name">stop-raising-hand</div>
|
<div class="name">stop-raising-hand</div>
|
||||||
<div class="code">\f221</div>
|
<div class="code">\f222</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-story-caption"></i>
|
<i class="icon icon-story-caption"></i>
|
||||||
<div class="name">story-caption</div>
|
<div class="name">story-caption</div>
|
||||||
<div class="code">\f222</div>
|
<div class="code">\f223</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-story-expired"></i>
|
<i class="icon icon-story-expired"></i>
|
||||||
<div class="name">story-expired</div>
|
<div class="name">story-expired</div>
|
||||||
<div class="code">\f223</div>
|
<div class="code">\f224</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-story-priority"></i>
|
<i class="icon icon-story-priority"></i>
|
||||||
<div class="name">story-priority</div>
|
<div class="name">story-priority</div>
|
||||||
<div class="code">\f224</div>
|
<div class="code">\f225</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-story-reply"></i>
|
<i class="icon icon-story-reply"></i>
|
||||||
<div class="name">story-reply</div>
|
<div class="name">story-reply</div>
|
||||||
<div class="code">\f225</div>
|
<div class="code">\f226</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-strikethrough"></i>
|
<i class="icon icon-strikethrough"></i>
|
||||||
<div class="name">strikethrough</div>
|
<div class="name">strikethrough</div>
|
||||||
<div class="code">\f226</div>
|
<div class="code">\f227</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-tag"></i>
|
<i class="icon icon-tag"></i>
|
||||||
<div class="name">tag</div>
|
<div class="name">tag</div>
|
||||||
<div class="code">\f227</div>
|
<div class="code">\f228</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-tag-add"></i>
|
<i class="icon icon-tag-add"></i>
|
||||||
<div class="name">tag-add</div>
|
<div class="name">tag-add</div>
|
||||||
<div class="code">\f228</div>
|
<div class="code">\f229</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-tag-crossed"></i>
|
<i class="icon icon-tag-crossed"></i>
|
||||||
<div class="name">tag-crossed</div>
|
<div class="name">tag-crossed</div>
|
||||||
<div class="code">\f229</div>
|
<div class="code">\f22a</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-tag-filter"></i>
|
<i class="icon icon-tag-filter"></i>
|
||||||
<div class="name">tag-filter</div>
|
<div class="name">tag-filter</div>
|
||||||
<div class="code">\f22a</div>
|
<div class="code">\f22b</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-tag-name"></i>
|
<i class="icon icon-tag-name"></i>
|
||||||
<div class="name">tag-name</div>
|
<div class="name">tag-name</div>
|
||||||
<div class="code">\f22b</div>
|
<div class="code">\f22c</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-timer"></i>
|
<i class="icon icon-timer"></i>
|
||||||
<div class="name">timer</div>
|
<div class="name">timer</div>
|
||||||
<div class="code">\f22c</div>
|
<div class="code">\f22d</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-timer-filled"></i>
|
<i class="icon icon-timer-filled"></i>
|
||||||
<div class="name">timer-filled</div>
|
<div class="name">timer-filled</div>
|
||||||
<div class="code">\f22d</div>
|
<div class="code">\f22e</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-toncoin"></i>
|
<i class="icon icon-toncoin"></i>
|
||||||
<div class="name">toncoin</div>
|
<div class="name">toncoin</div>
|
||||||
<div class="code">\f22e</div>
|
<div class="code">\f22f</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-tone"></i>
|
<i class="icon icon-tone"></i>
|
||||||
<div class="name">tone</div>
|
<div class="name">tone</div>
|
||||||
<div class="code">\f22f</div>
|
<div class="code">\f230</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-tools"></i>
|
<i class="icon icon-tools"></i>
|
||||||
<div class="name">tools</div>
|
<div class="name">tools</div>
|
||||||
<div class="code">\f230</div>
|
<div class="code">\f231</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-topic-new"></i>
|
<i class="icon icon-topic-new"></i>
|
||||||
<div class="name">topic-new</div>
|
<div class="name">topic-new</div>
|
||||||
<div class="code">\f231</div>
|
<div class="code">\f232</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-trade"></i>
|
<i class="icon icon-trade"></i>
|
||||||
<div class="name">trade</div>
|
<div class="name">trade</div>
|
||||||
<div class="code">\f232</div>
|
<div class="code">\f233</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-transcribe"></i>
|
<i class="icon icon-transcribe"></i>
|
||||||
<div class="name">transcribe</div>
|
<div class="name">transcribe</div>
|
||||||
<div class="code">\f233</div>
|
<div class="code">\f234</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-truck"></i>
|
<i class="icon icon-truck"></i>
|
||||||
<div class="name">truck</div>
|
<div class="name">truck</div>
|
||||||
<div class="code">\f234</div>
|
<div class="code">\f235</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unarchive"></i>
|
<i class="icon icon-unarchive"></i>
|
||||||
<div class="name">unarchive</div>
|
<div class="name">unarchive</div>
|
||||||
<div class="code">\f235</div>
|
<div class="code">\f236</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-underlined"></i>
|
<i class="icon icon-underlined"></i>
|
||||||
<div class="name">underlined</div>
|
<div class="name">underlined</div>
|
||||||
<div class="code">\f236</div>
|
<div class="code">\f237</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-understood"></i>
|
<i class="icon icon-understood"></i>
|
||||||
<div class="name">understood</div>
|
<div class="name">understood</div>
|
||||||
<div class="code">\f237</div>
|
<div class="code">\f238</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-undo"></i>
|
<i class="icon icon-undo"></i>
|
||||||
<div class="name">undo</div>
|
<div class="name">undo</div>
|
||||||
<div class="code">\f238</div>
|
<div class="code">\f239</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unique-profile"></i>
|
<i class="icon icon-unique-profile"></i>
|
||||||
<div class="name">unique-profile</div>
|
<div class="name">unique-profile</div>
|
||||||
<div class="code">\f239</div>
|
<div class="code">\f23a</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unlist"></i>
|
<i class="icon icon-unlist"></i>
|
||||||
<div class="name">unlist</div>
|
<div class="name">unlist</div>
|
||||||
<div class="code">\f23a</div>
|
<div class="code">\f23b</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unlist-outline"></i>
|
<i class="icon icon-unlist-outline"></i>
|
||||||
<div class="name">unlist-outline</div>
|
<div class="name">unlist-outline</div>
|
||||||
<div class="code">\f23b</div>
|
<div class="code">\f23c</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unlock"></i>
|
<i class="icon icon-unlock"></i>
|
||||||
<div class="name">unlock</div>
|
<div class="name">unlock</div>
|
||||||
<div class="code">\f23c</div>
|
<div class="code">\f23d</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unlock-badge"></i>
|
<i class="icon icon-unlock-badge"></i>
|
||||||
<div class="name">unlock-badge</div>
|
<div class="name">unlock-badge</div>
|
||||||
<div class="code">\f23d</div>
|
<div class="code">\f23e</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unmute"></i>
|
<i class="icon icon-unmute"></i>
|
||||||
<div class="name">unmute</div>
|
<div class="name">unmute</div>
|
||||||
<div class="code">\f23e</div>
|
<div class="code">\f23f</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unpin"></i>
|
<i class="icon icon-unpin"></i>
|
||||||
<div class="name">unpin</div>
|
<div class="name">unpin</div>
|
||||||
<div class="code">\f23f</div>
|
<div class="code">\f240</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-unread"></i>
|
<i class="icon icon-unread"></i>
|
||||||
<div class="name">unread</div>
|
<div class="name">unread</div>
|
||||||
<div class="code">\f240</div>
|
<div class="code">\f241</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-up"></i>
|
<i class="icon icon-up"></i>
|
||||||
<div class="name">up</div>
|
<div class="name">up</div>
|
||||||
<div class="code">\f241</div>
|
<div class="code">\f242</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-user"></i>
|
<i class="icon icon-user"></i>
|
||||||
<div class="name">user</div>
|
<div class="name">user</div>
|
||||||
<div class="code">\f242</div>
|
<div class="code">\f243</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-user-filled"></i>
|
<i class="icon icon-user-filled"></i>
|
||||||
<div class="name">user-filled</div>
|
<div class="name">user-filled</div>
|
||||||
<div class="code">\f243</div>
|
<div class="code">\f244</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-user-online"></i>
|
<i class="icon icon-user-online"></i>
|
||||||
<div class="name">user-online</div>
|
<div class="name">user-online</div>
|
||||||
<div class="code">\f244</div>
|
<div class="code">\f245</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-user-stars"></i>
|
<i class="icon icon-user-stars"></i>
|
||||||
<div class="name">user-stars</div>
|
<div class="name">user-stars</div>
|
||||||
<div class="code">\f245</div>
|
<div class="code">\f246</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-user-tag"></i>
|
<i class="icon icon-user-tag"></i>
|
||||||
<div class="name">user-tag</div>
|
<div class="name">user-tag</div>
|
||||||
<div class="code">\f246</div>
|
<div class="code">\f247</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-video"></i>
|
<i class="icon icon-video"></i>
|
||||||
<div class="name">video</div>
|
<div class="name">video</div>
|
||||||
<div class="code">\f247</div>
|
<div class="code">\f248</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-video-outlined"></i>
|
<i class="icon icon-video-outlined"></i>
|
||||||
<div class="name">video-outlined</div>
|
<div class="name">video-outlined</div>
|
||||||
<div class="code">\f248</div>
|
<div class="code">\f249</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-video-stop"></i>
|
<i class="icon icon-video-stop"></i>
|
||||||
<div class="name">video-stop</div>
|
<div class="name">video-stop</div>
|
||||||
<div class="code">\f249</div>
|
<div class="code">\f24a</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-view-once"></i>
|
<i class="icon icon-view-once"></i>
|
||||||
<div class="name">view-once</div>
|
<div class="name">view-once</div>
|
||||||
<div class="code">\f24a</div>
|
<div class="code">\f24b</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-voice-chat"></i>
|
<i class="icon icon-voice-chat"></i>
|
||||||
<div class="name">voice-chat</div>
|
<div class="name">voice-chat</div>
|
||||||
<div class="code">\f24b</div>
|
<div class="code">\f24c</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-volume-1"></i>
|
<i class="icon icon-volume-1"></i>
|
||||||
<div class="name">volume-1</div>
|
<div class="name">volume-1</div>
|
||||||
<div class="code">\f24c</div>
|
<div class="code">\f24d</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-volume-2"></i>
|
<i class="icon icon-volume-2"></i>
|
||||||
<div class="name">volume-2</div>
|
<div class="name">volume-2</div>
|
||||||
<div class="code">\f24d</div>
|
<div class="code">\f24e</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-volume-3"></i>
|
<i class="icon icon-volume-3"></i>
|
||||||
<div class="name">volume-3</div>
|
<div class="name">volume-3</div>
|
||||||
<div class="code">\f24e</div>
|
<div class="code">\f24f</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-warning"></i>
|
<i class="icon icon-warning"></i>
|
||||||
<div class="name">warning</div>
|
<div class="name">warning</div>
|
||||||
<div class="code">\f24f</div>
|
<div class="code">\f250</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-web"></i>
|
<i class="icon icon-web"></i>
|
||||||
<div class="name">web</div>
|
<div class="name">web</div>
|
||||||
<div class="code">\f250</div>
|
<div class="code">\f251</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-webapp"></i>
|
<i class="icon icon-webapp"></i>
|
||||||
<div class="name">webapp</div>
|
<div class="name">webapp</div>
|
||||||
<div class="code">\f251</div>
|
<div class="code">\f252</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-word-wrap"></i>
|
<i class="icon icon-word-wrap"></i>
|
||||||
<div class="name">word-wrap</div>
|
<div class="name">word-wrap</div>
|
||||||
<div class="code">\f252</div>
|
<div class="code">\f253</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-zoom-in"></i>
|
<i class="icon icon-zoom-in"></i>
|
||||||
<div class="name">zoom-in</div>
|
<div class="name">zoom-in</div>
|
||||||
<div class="code">\f253</div>
|
<div class="code">\f254</div>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<i class="icon icon-zoom-out"></i>
|
<i class="icon icon-zoom-out"></i>
|
||||||
<div class="name">zoom-out</div>
|
<div class="name">zoom-out</div>
|
||||||
<div class="code">\f254</div>
|
<div class="code">\f255</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -232,6 +232,7 @@ export type FontIconName =
|
|||||||
| 'redo'
|
| 'redo'
|
||||||
| 'refund'
|
| 'refund'
|
||||||
| 'reload'
|
| 'reload'
|
||||||
|
| 'reload-arrows'
|
||||||
| 'remove'
|
| 'remove'
|
||||||
| 'remove-quote'
|
| 'remove-quote'
|
||||||
| 'reopen-topic'
|
| 'reopen-topic'
|
||||||
|
|||||||
38
src/types/language.d.ts
vendored
38
src/types/language.d.ts
vendored
@ -1533,6 +1533,7 @@ export interface LangPair {
|
|||||||
'ViewButtonStickerset': undefined;
|
'ViewButtonStickerset': undefined;
|
||||||
'ViewButtonEmojiset': undefined;
|
'ViewButtonEmojiset': undefined;
|
||||||
'ViewButtonGiftUnique': undefined;
|
'ViewButtonGiftUnique': undefined;
|
||||||
|
'ViewButtonAiStyle': undefined;
|
||||||
'AuthContinueOnThisLanguage': undefined;
|
'AuthContinueOnThisLanguage': undefined;
|
||||||
'Share': undefined;
|
'Share': undefined;
|
||||||
'GiftSortByDate': undefined;
|
'GiftSortByDate': undefined;
|
||||||
@ -2108,6 +2109,27 @@ export interface LangPair {
|
|||||||
'AiMessageEditorApply': undefined;
|
'AiMessageEditorApply': undefined;
|
||||||
'AiMessageEditorEmojify': undefined;
|
'AiMessageEditorEmojify': undefined;
|
||||||
'AiMessageEditorTranslation': undefined;
|
'AiMessageEditorTranslation': undefined;
|
||||||
|
'AiToneEditorNewStyle': undefined;
|
||||||
|
'AiToneEditorTitle': undefined;
|
||||||
|
'AiToneEditorNamePlaceholder': undefined;
|
||||||
|
'AiToneEditorPromptPlaceholder': undefined;
|
||||||
|
'AiToneEditorDisplayAuthor': undefined;
|
||||||
|
'AiToneEditorSelectEmoji': undefined;
|
||||||
|
'AiToneCreatedHint': undefined;
|
||||||
|
'AiToneEditStyle': undefined;
|
||||||
|
'AiToneShareStyle': undefined;
|
||||||
|
'AiToneDeleteStyle': undefined;
|
||||||
|
'AiToneDeleteStyleConfirmOwn': undefined;
|
||||||
|
'AiToneDeleteStyleConfirm': undefined;
|
||||||
|
'AiToneEditorEditTitle': undefined;
|
||||||
|
'AiTonePreviewSubtitle': undefined;
|
||||||
|
'AiTonePreviewBefore': undefined;
|
||||||
|
'AiTonePreviewAnotherExample': undefined;
|
||||||
|
'AiTonePreviewAfter': undefined;
|
||||||
|
'AiTonePreviewAddStyle': undefined;
|
||||||
|
'AiTonePreviewRemoveStyle': undefined;
|
||||||
|
'AiTonePreviewStyleAdded': undefined;
|
||||||
|
'AiToneLimitReached': undefined;
|
||||||
'TextShowMore': undefined;
|
'TextShowMore': undefined;
|
||||||
'TextShowLess': undefined;
|
'TextShowLess': undefined;
|
||||||
'AiMessageEditorFrom': undefined;
|
'AiMessageEditorFrom': undefined;
|
||||||
@ -3741,6 +3763,22 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'AiMessageEditorDailyLimitReached': {
|
'AiMessageEditorDailyLimitReached': {
|
||||||
'link': V;
|
'link': V;
|
||||||
};
|
};
|
||||||
|
'AiToneCreated': {
|
||||||
|
'title': V;
|
||||||
|
};
|
||||||
|
'AiToneLimitReachedPremium': {
|
||||||
|
'limit': V;
|
||||||
|
};
|
||||||
|
'AiTonePreviewUsedBy': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'AiTonePreviewCreatedBy': {
|
||||||
|
'author': V;
|
||||||
|
};
|
||||||
|
'AiTonePreviewUsedByCreatedBy': {
|
||||||
|
'usedBy': V;
|
||||||
|
'createdBy': V;
|
||||||
|
};
|
||||||
'UnofficialSecurityRisk': {
|
'UnofficialSecurityRisk': {
|
||||||
'peer': V;
|
'peer': V;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import { toChannelId } from './entities/ids';
|
|||||||
import { isUsernameValid } from './entities/username';
|
import { isUsernameValid } from './entities/username';
|
||||||
|
|
||||||
export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
||||||
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
'setlanguage' | 'addtheme' | 'addstyle' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' |
|
||||||
'invoice' | 'addlist' | 'boost' | 'giftcode' | 'message' | 'premium_offer' | 'premium_multigift' | 'stars_topup'
|
'msg' | 'msg_url' | 'invoice' | 'addlist' | 'boost' | 'giftcode' | 'message' | 'premium_offer' |
|
||||||
| 'nft' | 'stars' | 'ton' | 'stargift_auction' | 'premium' | 'oauth';
|
'premium_multigift' | 'stars_topup' | 'nft' | 'stars' | 'ton' | 'stargift_auction' | 'premium' | 'oauth';
|
||||||
|
|
||||||
interface PublicMessageLink {
|
interface PublicMessageLink {
|
||||||
type: 'publicMessageLink';
|
type: 'publicMessageLink';
|
||||||
|
|||||||
@ -222,6 +222,12 @@ export const processDeepLink = (url: string, linkContext?: LinkContext): boolean
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'addstyle': {
|
||||||
|
const { set } = params;
|
||||||
|
if (!set) return false;
|
||||||
|
actions.openAiTonePreview({ slug: set });
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'share':
|
case 'share':
|
||||||
case 'msg':
|
case 'msg':
|
||||||
case 'msg_url': {
|
case 'msg_url': {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user