diff --git a/src/api/gramjs/apiBuilders/bots.ts b/src/api/gramjs/apiBuilders/bots.ts index 692c45ae5..d86141fec 100644 --- a/src/api/gramjs/apiBuilders/bots.ts +++ b/src/api/gramjs/apiBuilders/bots.ts @@ -1,6 +1,6 @@ import { Api as GramJs } from '../../../lib/gramjs'; import { - ApiBotInlineMediaResult, ApiBotInlineResult, ApiInlineResultType, ApiWebDocument, + ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm, ApiInlineResultType, ApiWebDocument, } from '../../types'; import { pick } from '../../../util/iteratees'; @@ -46,6 +46,10 @@ export function buildApiBotInlineMediaResult( }; } +export function buildBotSwitchPm(switchPm?: GramJs.InlineBotSwitchPM) { + return switchPm ? pick(switchPm, ['text', 'startParam']) as ApiBotInlineSwitchPm : undefined; +} + function buildApiWebDocument(document?: GramJs.TypeWebDocument): ApiWebDocument | undefined { return document ? pick(document, ['url', 'mimeType']) : undefined; } diff --git a/src/api/gramjs/apiBuilders/chats.ts b/src/api/gramjs/apiBuilders/chats.ts index 8b219ded5..366ff0dc7 100644 --- a/src/api/gramjs/apiBuilders/chats.ts +++ b/src/api/gramjs/apiBuilders/chats.ts @@ -3,6 +3,7 @@ import { ApiChat, ApiChatAdminRights, ApiChatBannedRights, + ApiBotCommand, ApiChatFolder, ApiChatMember, ApiRestrictionReason, @@ -376,3 +377,14 @@ export function buildApiChatFolderFromSuggested({ description, }; } + +export function buildApiChatBotCommands(botInfos: GramJs.BotInfo[]) { + return botInfos.reduce((botCommands, botInfo) => { + botCommands = botCommands.concat(botInfo.commands.map((mtpCommand) => ({ + botId: botInfo.userId, + ...omitVirtualClassFields(mtpCommand), + }))); + + return botCommands; + }, [] as ApiBotCommand[]); +} diff --git a/src/api/gramjs/apiBuilders/users.ts b/src/api/gramjs/apiBuilders/users.ts index 691f0c870..bd304df82 100644 --- a/src/api/gramjs/apiBuilders/users.ts +++ b/src/api/gramjs/apiBuilders/users.ts @@ -1,5 +1,7 @@ import { Api as GramJs } from '../../../lib/gramjs'; -import { ApiUser, ApiUserStatus, ApiUserType } from '../../types'; +import { + ApiBotCommand, ApiUser, ApiUserStatus, ApiUserType, +} from '../../types'; export function buildApiUserFromFull(mtpUserFull: GramJs.UserFull): ApiUser { const { @@ -14,6 +16,7 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.UserFull): ApiUser { pinnedMessageId: pinnedMsgId, isBlocked: Boolean(blocked), ...(botInfo && { botDescription: botInfo.description }), + ...(botInfo && botInfo.commands.length && { botCommands: buildApiBotCommands(mtpUserFull.user.id, botInfo) }), }, }; } @@ -74,3 +77,11 @@ export function buildApiUserStatus(mtpStatus?: GramJs.TypeUserStatus): ApiUserSt return { type: 'userStatusLastMonth' }; } } + +function buildApiBotCommands(botId: number, botInfo: GramJs.BotInfo) { + return botInfo.commands.map(({ command, description }) => ({ + botId, + command, + description, + })) as ApiBotCommand[]; +} diff --git a/src/api/gramjs/methods/bots.ts b/src/api/gramjs/methods/bots.ts index 3938d67e7..ee31d2963 100644 --- a/src/api/gramjs/methods/bots.ts +++ b/src/api/gramjs/methods/bots.ts @@ -1,15 +1,14 @@ import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; -import { ApiBotInlineSwitchPm, ApiChat, ApiUser } from '../../types'; +import { ApiChat, ApiUser } from '../../types'; import localDb from '../localDb'; import { invokeRequest } from './client'; import { buildInputPeer, calculateResultHash, generateRandomBigInt } from '../gramjsBuilders'; import { buildApiUser } from '../apiBuilders/users'; -import { buildApiBotInlineMediaResult, buildApiBotInlineResult } from '../apiBuilders/bots'; +import { buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm } from '../apiBuilders/bots'; import { buildApiChatFromPreview } from '../apiBuilders/chats'; -import { pick } from '../../../util/iteratees'; export function init() { } @@ -92,7 +91,7 @@ export async function fetchInlineBotResults({ isGallery: Boolean(result.gallery), help: bot.botPlaceholder, nextOffset: getInlineBotResultsNextOffset(bot.username, result.nextOffset), - switchPm: buildSwitchPm(result.switchPm), + switchPm: buildBotSwitchPm(result.switchPm), users: result.users.map(buildApiUser).filter(Boolean as any), results: processInlineBotResult(String(result.queryId), result.results), }; @@ -118,8 +117,20 @@ export async function sendInlineBotResult({ }), true); } -function buildSwitchPm(switchPm?: GramJs.InlineBotSwitchPM) { - return switchPm ? pick(switchPm, ['text', 'startParam']) as ApiBotInlineSwitchPm : undefined; +export async function startBot({ + bot, startParam, +}: { + bot: ApiUser; + startParam?: string; +}) { + const randomId = generateRandomBigInt(); + + await invokeRequest(new GramJs.messages.StartBot({ + bot: buildInputPeer(bot.id, bot.accessHash), + peer: buildInputPeer(bot.id, bot.accessHash), + randomId, + startParam, + }), true); } function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) { diff --git a/src/api/gramjs/methods/chats.ts b/src/api/gramjs/methods/chats.ts index 2d4f963c5..e54330e66 100644 --- a/src/api/gramjs/methods/chats.ts +++ b/src/api/gramjs/methods/chats.ts @@ -22,6 +22,7 @@ import { getApiChatIdFromMtpPeer, buildApiChatFolder, buildApiChatFolderFromSuggested, + buildApiChatBotCommands, } from '../apiBuilders/chats'; import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages'; import { buildApiUser } from '../apiBuilders/users'; @@ -317,10 +318,12 @@ async function getFullChatInfo(chatId: number): Promise<{ about, participants, exportedInvite, + botInfo, } = result.fullChat; const members = buildChatMembers(participants); const adminMembers = members ? members.filter(({ isAdmin, isOwner }) => isAdmin || isOwner) : undefined; + const botCommands = botInfo ? buildApiChatBotCommands(botInfo) : undefined; return { fullInfo: { @@ -328,6 +331,7 @@ async function getFullChatInfo(chatId: number): Promise<{ members, adminMembers, canViewMembers: true, + botCommands, ...(exportedInvite && { inviteLink: exportedInvite.link, }), @@ -361,6 +365,7 @@ async function getFullChannelInfo( linkedChatId, hiddenPrehistory, call, + botInfo, } = result.fullChat; const inviteLink = exportedInvite instanceof GramJs.ChatInviteExported @@ -374,6 +379,7 @@ async function getFullChannelInfo( const { members: adminMembers, users: adminUsers } = ( canViewParticipants && adminRights && await fetchMembers(id, accessHash, 'admin') ) || {}; + const botCommands = botInfo ? buildApiChatBotCommands(botInfo) : undefined; return { fullInfo: { @@ -395,6 +401,7 @@ async function getFullChannelInfo( adminMembers, groupCallId: call ? call.id.toString() : undefined, linkedChatId: linkedChatId ? getApiChatIdFromMtpPeer({ chatId: linkedChatId } as GramJs.TypePeer) : undefined, + botCommands, }, users: [...(users || []), ...(bannedUsers || []), ...(adminUsers || [])], }; diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index c56ee47c7..7b4ced51d 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -55,7 +55,7 @@ export { } from './twoFaSettings'; export { - answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, + answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot, } from './bots'; export { diff --git a/src/api/types/bots.ts b/src/api/types/bots.ts index 3b6a8ac4e..2ac6cf358 100644 --- a/src/api/types/bots.ts +++ b/src/api/types/bots.ts @@ -38,3 +38,9 @@ export interface ApiBotInlineSwitchPm { text: string; startParam: string; } + +export interface ApiBotCommand { + botId: number; + command: string; + description: string; +} diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts index 6643e573d..f662de452 100644 --- a/src/api/types/chats.ts +++ b/src/api/types/chats.ts @@ -1,4 +1,5 @@ import { ApiMessage, ApiPhoto } from './messages'; +import { ApiBotCommand } from './bots'; type ApiChatType = ( 'chatTypePrivate' | 'chatTypeSecret' | @@ -96,6 +97,7 @@ export interface ApiChatFullInfo { maxMessageId?: number; }; linkedChatId?: number; + botCommands?: ApiBotCommand[]; } export interface ApiChatMember { diff --git a/src/api/types/users.ts b/src/api/types/users.ts index b06128d45..4f9c15d3f 100644 --- a/src/api/types/users.ts +++ b/src/api/types/users.ts @@ -1,4 +1,5 @@ import { ApiPhoto } from './messages'; +import { ApiBotCommand } from './bots'; export interface ApiUser { id: number; @@ -28,6 +29,7 @@ export interface ApiUserFullInfo { commonChatsCount?: number; botDescription?: string; pinnedMessageId?: number; + botCommands?: ApiBotCommand[]; } export type ApiUserType = 'userTypeBot' | 'userTypeRegular' | 'userTypeDeleted' | 'userTypeUnknown'; diff --git a/src/assets/fonts/icomoon.woff b/src/assets/fonts/icomoon.woff index 9c9fd54c8..1c87e0b48 100755 Binary files a/src/assets/fonts/icomoon.woff and b/src/assets/fonts/icomoon.woff differ diff --git a/src/assets/fonts/icomoon.woff2 b/src/assets/fonts/icomoon.woff2 index ad4766f4c..c8420c5c5 100644 Binary files a/src/assets/fonts/icomoon.woff2 and b/src/assets/fonts/icomoon.woff2 differ diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index 617187af4..9f85bb9a0 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -29,6 +29,8 @@ export { default as AttachmentModal } from '../components/middle/composer/Attach export { default as PollModal } from '../components/middle/composer/PollModal'; export { default as SymbolMenu } from '../components/middle/composer/SymbolMenu'; export { default as AttachMenu } from '../components/middle/composer/AttachMenu'; +export { default as BotCommandTooltip } from '../components/middle/composer/BotCommandTooltip'; +export { default as BotCommandMenu } from '../components/middle/composer/BotCommandMenu'; export { default as MentionTooltip } from '../components/middle/composer/MentionTooltip'; export { default as StickerTooltip } from '../components/middle/composer/StickerTooltip'; export { default as CustomSendMenu } from '../components/middle/composer/CustomSendMenu'; diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 264fb2bf8..600656bdd 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -546,10 +546,9 @@ export default memo(withGlobal( && !messageIds && !chat.unreadCount && !focusingId && lastMessage && !lastMessage.groupedId ); - const bot = selectChatBot(global, chatId); + const chatBot = selectChatBot(global, chatId)!; let botDescription: string | undefined; if (selectIsChatBotNotStarted(global, chatId)) { - const chatBot = selectChatBot(global, chatId)!; if (chatBot.fullInfo) { botDescription = chatBot.fullInfo.botDescription || 'NoMessages'; } else { @@ -565,7 +564,7 @@ export default memo(withGlobal( isGroupChat: isChatGroup(chat), isCreator: chat.isCreator, isChatWithSelf: selectIsChatWithSelf(global, chatId), - isBot: Boolean(bot), + isBot: Boolean(chatBot), messageIds, messagesById, firstUnreadId: selectFirstUnreadId(global, chatId, threadId), diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index 738bd13d5..4a439e8c9 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -67,9 +67,7 @@ const AttachmentModal: FC = ({ const lang = useLang(); const { - isMentionTooltipOpen, mentionFilter, - closeMentionTooltip, insertMention, - mentionFilteredUsers, + isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers, } = useMentionTooltip( isOpen, caption, @@ -227,7 +225,6 @@ const AttachmentModal: FC = ({ void; +}; + +const BotCommand: FC = ({ + withAvatar, + focus, + botCommand, + bot, + onClick, +}) => { + return ( + onClick(botCommand)} + focus={focus} + > + {withAvatar && ( + + )} +
+ /{botCommand.command} + {renderText(botCommand.description)} +
+
+ ); +}; + +export default memo(BotCommand); diff --git a/src/components/middle/composer/BotCommandMenu.async.tsx b/src/components/middle/composer/BotCommandMenu.async.tsx new file mode 100644 index 000000000..dcc980429 --- /dev/null +++ b/src/components/middle/composer/BotCommandMenu.async.tsx @@ -0,0 +1,15 @@ +import React, { FC, memo } from '../../../lib/teact/teact'; +import { OwnProps } from './BotCommandMenu'; +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +const BotCommandMenuAsync: FC = (props) => { + const { isOpen } = props; + const BotCommandMenu = useModuleLoader(Bundles.Extra, 'BotCommandMenu', !isOpen); + + // eslint-disable-next-line react/jsx-props-no-spreading + return BotCommandMenu ? : undefined; +}; + +export default memo(BotCommandMenuAsync); diff --git a/src/components/middle/composer/BotCommandMenu.scss b/src/components/middle/composer/BotCommandMenu.scss new file mode 100644 index 000000000..e2fc16edb --- /dev/null +++ b/src/components/middle/composer/BotCommandMenu.scss @@ -0,0 +1,24 @@ +.BotCommandMenu { + .menu-container { + width: calc(100% - 4rem); + max-width: 20rem; + max-height: 40vh; + overflow: auto; + flex-direction: column; + + @media (max-width: 600px) { + width: calc(100% - 3rem); + } + } + + .is-pointer-env & { + > .backdrop { + position: absolute; + top: -1rem; + left: 0; + right: auto; + width: 3.5rem; + height: 4.5rem; + } + } +} diff --git a/src/components/middle/composer/BotCommandMenu.tsx b/src/components/middle/composer/BotCommandMenu.tsx new file mode 100644 index 000000000..5bf96c3ee --- /dev/null +++ b/src/components/middle/composer/BotCommandMenu.tsx @@ -0,0 +1,63 @@ +import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { GlobalActions } from '../../../global/types'; +import { ApiBotCommand } from '../../../api/types'; + +import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../util/environment'; +import { pick } from '../../../util/iteratees'; +import useMouseInside from '../../../hooks/useMouseInside'; + +import Menu from '../../ui/Menu'; +import BotCommand from './BotCommand'; + +import './BotCommandMenu.scss'; + +export type OwnProps = { + isOpen: boolean; + botCommands: ApiBotCommand[]; + onClose: NoneToVoidFunction; +}; + +type DispatchProps = Pick; + +const BotCommandMenu: FC = ({ + isOpen, botCommands, onClose, sendBotCommand, +}) => { + const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, IS_SINGLE_COLUMN_LAYOUT); + + const handleClick = useCallback((botCommand: ApiBotCommand) => { + sendBotCommand({ + command: `/${botCommand.command}`, + botId: botCommand.botId, + }); + onClose(); + }, [onClose, sendBotCommand]); + + return ( + + {botCommands.map((botCommand) => ( + + ))} + + ); +}; + +export default memo(withGlobal( + undefined, + (setGlobal, actions): DispatchProps => pick(actions, ['sendBotCommand']), +)(BotCommandMenu)); diff --git a/src/components/middle/composer/BotCommandTooltip.async.tsx b/src/components/middle/composer/BotCommandTooltip.async.tsx new file mode 100644 index 000000000..d6b41c73f --- /dev/null +++ b/src/components/middle/composer/BotCommandTooltip.async.tsx @@ -0,0 +1,15 @@ +import React, { FC, memo } from '../../../lib/teact/teact'; +import { OwnProps } from './BotCommandTooltip'; +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +const BotCommandTooltipAsync: FC = (props) => { + const { isOpen } = props; + const BotCommandTooltip = useModuleLoader(Bundles.Extra, 'BotCommandTooltip', !isOpen); + + // eslint-disable-next-line react/jsx-props-no-spreading + return BotCommandTooltip ? : undefined; +}; + +export default memo(BotCommandTooltipAsync); diff --git a/src/components/middle/composer/BotCommandTooltip.scss b/src/components/middle/composer/BotCommandTooltip.scss new file mode 100644 index 000000000..8c65d4006 --- /dev/null +++ b/src/components/middle/composer/BotCommandTooltip.scss @@ -0,0 +1,11 @@ +.BotCommandTooltip { + width: calc(100% - 4rem); + max-width: 26rem; + flex-direction: column; + + z-index: -1; + + @media (max-width: 600px) { + width: calc(100% - 3rem); + } +} diff --git a/src/components/middle/composer/BotCommandTooltip.tsx b/src/components/middle/composer/BotCommandTooltip.tsx new file mode 100644 index 000000000..78250db70 --- /dev/null +++ b/src/components/middle/composer/BotCommandTooltip.tsx @@ -0,0 +1,106 @@ +import React, { + FC, useCallback, useEffect, useRef, memo, +} from '../../../lib/teact/teact'; +import { withGlobal } from '../../../lib/teact/teactn'; + +import { ApiBotCommand, ApiUser } from '../../../api/types'; +import { GlobalActions } from '../../../global/types'; + +import { pick } from '../../../util/iteratees'; +import buildClassName from '../../../util/buildClassName'; +import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; +import useShowTransition from '../../../hooks/useShowTransition'; +import usePrevious from '../../../hooks/usePrevious'; +import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; + +import BotCommand from './BotCommand'; + +import './BotCommandTooltip.scss'; + +export type OwnProps = { + isOpen: boolean; + withUsername?: boolean; + botCommands?: ApiBotCommand[]; + onClick: NoneToVoidFunction; + onClose: NoneToVoidFunction; +}; + +type StateProps = { + usersById: Record; +}; + +type DispatchProps = Pick; + +const BotCommandTooltip: FC = ({ + usersById, + isOpen, + withUsername, + botCommands, + onClick, + onClose, + sendBotCommand, +}) => { + // eslint-disable-next-line no-null/no-null + const containerRef = useRef(null); + const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); + + const handleSendCommand = useCallback(({ botId, command }: ApiBotCommand) => { + const bot = usersById[botId]; + sendBotCommand({ + command: `/${command}${withUsername && bot ? `@${bot.username}` : ''}`, + botId, + }); + onClick(); + }, [onClick, sendBotCommand, usersById, withUsername]); + + const selectedCommandIndex = useKeyboardNavigation({ + isActive: isOpen, + items: botCommands, + onSelect: handleSendCommand, + onClose, + }); + + useEffect(() => { + if (botCommands && !botCommands.length) { + onClose(); + } + }, [botCommands, onClose]); + + useEffect(() => { + setTooltipItemVisible('.chat-item-clickable', selectedCommandIndex, containerRef); + }, [selectedCommandIndex]); + + const prevCommands = usePrevious(botCommands && botCommands.length ? botCommands : undefined, shouldRender); + const renderedCommands = botCommands && !botCommands.length ? prevCommands : botCommands; + + if (!shouldRender || (renderedCommands && !renderedCommands.length)) { + return undefined; + } + + const className = buildClassName( + 'BotCommandTooltip composer-tooltip custom-scroll', + transitionClassNames, + ); + + return ( +
+ {renderedCommands && renderedCommands.map((chatBotCommand, index) => ( + + ))} +
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => ({ + usersById: global.users.byId, + }), + (setGlobal, actions): DispatchProps => pick(actions, ['sendBotCommand']), +)(BotCommandTooltip)); diff --git a/src/components/middle/composer/Composer.scss b/src/components/middle/composer/Composer.scss index a5c1612b4..f72c62c4b 100644 --- a/src/components/middle/composer/Composer.scss +++ b/src/components/middle/composer/Composer.scss @@ -192,13 +192,14 @@ > .Spinner { align-self: center; --spinner-size: 1.5rem; + margin-right: -.5rem; } > .Button { flex-shrink: 0; background: none !important; - width: 3.375rem; - height: 3.375rem; + width: 3.5rem; + height: 3.5rem; margin: 0; padding: 0.625rem; align-self: flex-end; @@ -210,7 +211,17 @@ } + .Button { - margin-left: -.25rem; + margin-left: -.625rem; + } + + &.bot-commands { + color: var(--color-primary) !important; + padding-right: 0; + + // SymbolMenu button should be accessible if BotCommandsMenu opened + body.is-touch-env &.activated + .Button.mobile-symbol-menu-button { + z-index: calc(var(--z-menu-backdrop) + 1); + } } &.scheduled-button i::after { diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 67b1748ae..979c1c4b9 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -17,14 +17,16 @@ import { ApiChatMember, ApiUser, MAIN_THREAD_ID, + ApiBotCommand, } from '../../../api/types'; import { InlineBotSettings } from '../../../types'; -import { BASE_EMOJI_KEYWORD_LANG, EDITABLE_INPUT_ID, SCHEDULED_WHEN_ONLINE } from '../../../config'; +import { + BASE_EMOJI_KEYWORD_LANG, EDITABLE_INPUT_ID, REPLIES_USER_ID, SCHEDULED_WHEN_ONLINE, +} from '../../../config'; import { IS_VOICE_RECORDING_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT, IS_IOS } from '../../../util/environment'; import { selectChat, - selectIsChatWithBot, selectIsRightColumnShown, selectIsInSelectMode, selectNewestMessageWithBotKeyboardButtons, @@ -32,6 +34,7 @@ import { selectScheduledIds, selectEditingMessage, selectIsChatWithSelf, + selectChatBot, selectChatUser, selectChatMessage, } from '../../../modules/selectors'; @@ -50,8 +53,10 @@ import insertHtmlInSelection from '../../../util/insertHtmlInSelection'; import deleteLastCharacterOutsideSelection from '../../../util/deleteLastCharacterOutsideSelection'; import { pick } from '../../../util/iteratees'; import buildClassName from '../../../util/buildClassName'; +import windowSize from '../../../util/windowSize'; import { isSelectionInsideInput } from './helpers/selection'; import applyIosAutoCapitalizationFix from './helpers/applyIosAutoCapitalizationFix'; +import { getServerTime } from '../../../util/serverTime'; import useFlag from '../../../hooks/useFlag'; import useVoiceRecording from './hooks/useVoiceRecording'; @@ -65,8 +70,7 @@ import useMentionTooltip from './hooks/useMentionTooltip'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import useLang from '../../../hooks/useLang'; import useInlineBotTooltip from './hooks/useInlineBotTooltip'; -import windowSize from '../../../util/windowSize'; -import { getServerTime } from '../../../util/serverTime'; +import useBotCommandTooltip from './hooks/useBotCommandTooltip'; import DeleteMessageModal from '../../common/DeleteMessageModal.async'; import Button from '../../ui/Button'; @@ -79,10 +83,12 @@ import MentionTooltip from './MentionTooltip.async'; import CustomSendMenu from './CustomSendMenu.async'; import StickerTooltip from './StickerTooltip.async'; import EmojiTooltip from './EmojiTooltip.async'; +import BotCommandTooltip from './BotCommandTooltip.async'; import BotKeyboardMenu from './BotKeyboardMenu'; import MessageInput from './MessageInput'; import ComposerEmbeddedMessage from './ComposerEmbeddedMessage'; import AttachmentModal from './AttachmentModal.async'; +import BotCommandMenu from './BotCommandMenu.async'; import PollModal from './PollModal.async'; import DropArea, { DropAreaState } from './DropArea.async'; import WebPagePreview from './WebPagePreview'; @@ -133,6 +139,8 @@ type StateProps = { topInlineBotIds?: number[]; isInlineBotLoading: boolean; inlineBots?: Record; + botCommands?: ApiBotCommand[] | false; + chatBotCommands?: ApiBotCommand[]; } & Pick; type DispatchProps = Pick = ({ recentEmojis, inlineBots, isInlineBotLoading, + botCommands, + chatBotCommands, sendMessage, editMessage, saveDraft, @@ -259,6 +269,7 @@ const Composer: FC = ({ const [attachments, setAttachments] = useState([]); const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag(); + const [isBotCommandMenuOpen, openBotCommandMenu, closeBotCommandMenu] = useFlag(); const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag(); const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(); @@ -283,9 +294,7 @@ const Composer: FC = ({ const canShowCustomSendMenu = !shouldSchedule; const { - isMentionTooltipOpen, mentionFilter, - closeMentionTooltip, insertMention, - mentionFilteredUsers, + isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers, } = useMentionTooltip( !attachments.length, html, @@ -313,6 +322,17 @@ const Composer: FC = ({ inlineBots, ); + const { + isOpen: isBotCommandTooltipOpen, + close: closeBotCommandTooltip, + filteredBotCommands: botTooltipCommands, + } = useBotCommandTooltip( + Boolean((botCommands && botCommands.length) || (chatBotCommands && chatBotCommands.length)), + html, + botCommands, + chatBotCommands, + ); + const { isContextMenuOpen: isCustomSendMenuOpen, handleContextMenu, @@ -532,6 +552,16 @@ const Composer: FC = ({ resetComposer, stopRecordingVoice, showDialog, slowMode, isAdmin, sendMessage, forwardMessages, lang, ]); + const handleActivateBotCommandMenu = useCallback(() => { + closeSymbolMenu(); + openBotCommandMenu(); + }, [closeSymbolMenu, openBotCommandMenu]); + + const handleActivateSymbolMenu = useCallback(() => { + closeBotCommandMenu(); + openSymbolMenu(); + }, [closeBotCommandMenu, openSymbolMenu]); + const handleStickerSelect = useCallback((sticker: ApiSticker, shouldPreserveInput = false) => { sticker = { ...sticker, @@ -582,6 +612,13 @@ const Composer: FC = ({ }); }, [chatId, clearDraft, connectionState, resetComposer, sendInlineBotResult]); + const handleBotCommandSelect = useCallback(() => { + clearDraft({ chatId, localOnly: true }); + requestAnimationFrame(() => { + resetComposer(); + }); + }, [chatId, clearDraft, resetComposer]); + const handlePollSend = useCallback((poll: ApiNewPoll) => { if (shouldSchedule) { setScheduledMessageArgs({ poll }); @@ -598,7 +635,7 @@ const Composer: FC = ({ setScheduledMessageArgs({ isSilent: true }); openCalendar(); } else { - handleSend(true); + void handleSend(true); } }, [handleSend, openCalendar, shouldSchedule]); @@ -610,7 +647,7 @@ const Composer: FC = ({ + (isWhenOnline ? 0 : serverTimeOffset); if (!scheduledMessageArgs || Object.keys(restArgs).length === 0) { - handleSend(!!isSilent, scheduledAt); + void handleSend(!!isSilent, scheduledAt); } else { sendMessage({ ...scheduledMessageArgs, @@ -652,9 +689,10 @@ const Composer: FC = ({ messageInput.blur(); setTimeout(() => { + closeBotCommandMenu(); openSymbolMenu(); }, MOBILE_KEYBOARD_HIDE_DELAY_MS); - }, [openSymbolMenu]); + }, [openSymbolMenu, closeBotCommandMenu]); const handleAllScheduledClick = useCallback(() => { openChat({ id: chatId, threadId, type: 'scheduled' }); @@ -687,14 +725,14 @@ const Composer: FC = ({ } openCalendar(); } else { - handleSend(); + void handleSend(); requestAnimationFrame(() => { resetComposer(); }); } break; case MainButtonState.Record: - startRecordingVoice(); + void startRecordingVoice(); break; case MainButtonState.Edit: handleEditComplete(); @@ -800,7 +838,6 @@ const Composer: FC = ({ )} = ({ loadMore={loadMoreForInlineBot} onClose={closeInlineBotTooltip} /> +
@@ -827,6 +871,19 @@ const Composer: FC = ({ disabled={!allowedAttachmentOptions.canAttachEmbedLinks} />
+ {isChatWithBot && botCommands !== false && !activeVoiceRecording && !editingMessage && ( + + + + )} {IS_SINGLE_COLUMN_LAYOUT ? ( ) : ( @@ -885,7 +942,7 @@ const Composer: FC = ({ )} {botKeyboardMessageId && !activeVoiceRecording && !editingMessage && ( = ({ )} {!activeVoiceRecording && !editingMessage && ( = ({ onClose={closeBotKeyboard} /> )} + {botCommands && ( + + )} ( (global, { chatId, threadId, messageListType }): StateProps => { const chat = selectChat(global, chatId); const chatUser = chat && selectChatUser(global, chat); - const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined; + const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined; + const isChatWithBot = Boolean(chatBot); const isChatWithSelf = selectIsChatWithSelf(global, chatId); const messageWithActualBotKeyboard = isChatWithBot && selectNewestMessageWithBotKeyboardButtons(global, chatId); const scheduledIds = selectScheduledIds(global, chatId); @@ -1055,6 +1120,8 @@ export default memo(withGlobal( serverTimeOffset: global.serverTimeOffset, inlineBots: global.inlineBots.byUsername, isInlineBotLoading: global.inlineBots.isLoading, + chatBotCommands: chat && chat.fullInfo && chat.fullInfo.botCommands, + botCommands: chatBot && chatBot.fullInfo ? (chatBot.fullInfo.botCommands || false) : undefined, }; }, (setGlobal, actions): DispatchProps => pick(actions, [ diff --git a/src/components/middle/composer/EmojiTooltip.tsx b/src/components/middle/composer/EmojiTooltip.tsx index 86656eab7..d54f93bda 100644 --- a/src/components/middle/composer/EmojiTooltip.tsx +++ b/src/components/middle/composer/EmojiTooltip.tsx @@ -1,16 +1,15 @@ import React, { - FC, memo, useCallback, useEffect, useRef, useState, + FC, memo, useCallback, useEffect, useRef, } from '../../../lib/teact/teact'; import { IS_TOUCH_ENV } from '../../../util/environment'; import buildClassName from '../../../util/buildClassName'; -import cycleRestrict from '../../../util/cycleRestrict'; -import captureKeyboardListeners from '../../../util/captureKeyboardListeners'; import findInViewport from '../../../util/findInViewport'; import isFullyVisible from '../../../util/isFullyVisible'; import fastSmoothScrollHorizontal from '../../../util/fastSmoothScrollHorizontal'; import useShowTransition from '../../../hooks/useShowTransition'; import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation'; +import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; import Loading from '../../ui/Loading'; import EmojiButton from './EmojiButton'; @@ -20,7 +19,6 @@ import './EmojiTooltip.scss'; const VIEWPORT_MARGIN = 8; const EMOJI_BUTTON_WIDTH = 44; const CLOSE_DURATION = 350; -const NO_EMOJI_SELECTED_INDEX = -1; function setItemVisible(index: number, containerRef: Record) { const container = containerRef.current!; @@ -70,52 +68,27 @@ const EmojiTooltip: FC = ({ const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); const listEmojis: Emoji[] = usePrevDuringAnimation(emojis.length ? emojis : undefined, CLOSE_DURATION) || []; - const [selectedIndex, setSelectedIndex] = useState(NO_EMOJI_SELECTED_INDEX); - - useEffect(() => { - setSelectedIndex(0); - }, [emojis]); - - useEffect(() => { - setItemVisible(selectedIndex, containerRef); - }, [selectedIndex]); - - const getSelectedIndex = useCallback((newIndex: number) => { - if (!emojis.length) { - return NO_EMOJI_SELECTED_INDEX; - } - - const emojisCount = emojis.length; - return cycleRestrict(emojisCount, newIndex); - }, [emojis]); - - const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => { - e.preventDefault(); - setSelectedIndex((index) => (getSelectedIndex(index + value))); - }, [setSelectedIndex, getSelectedIndex]); - - const handleSelectEmoji = useCallback((e: KeyboardEvent) => { - if (emojis.length && selectedIndex > NO_EMOJI_SELECTED_INDEX) { - const emoji = emojis[selectedIndex]; - if (emoji) { - e.preventDefault(); - onEmojiSelect(emoji.native); - addRecentEmoji({ emoji: emoji.id }); - } - } - }, [addRecentEmoji, emojis, onEmojiSelect, selectedIndex]); + const handleSelectEmoji = useCallback((emoji: Emoji) => { + onEmojiSelect(emoji.native); + addRecentEmoji({ emoji: emoji.id }); + }, [addRecentEmoji, onEmojiSelect]); const handleClick = useCallback((native: string, id: string) => { onEmojiSelect(native); addRecentEmoji({ emoji: id }); }, [addRecentEmoji, onEmojiSelect]); - useEffect(() => (isOpen ? captureKeyboardListeners({ - onEsc: onClose, - onLeft: (e: KeyboardEvent) => handleArrowKey(-1, e), - onRight: (e: KeyboardEvent) => handleArrowKey(1, e), - onEnter: handleSelectEmoji, - }) : undefined), [handleArrowKey, handleSelectEmoji, isOpen, onClose]); + const selectedIndex = useKeyboardNavigation({ + isActive: isOpen, + isHorizontal: true, + items: emojis, + onSelect: handleSelectEmoji, + onClose, + }); + + useEffect(() => { + setItemVisible(selectedIndex, containerRef); + }, [selectedIndex]); const handleMouseEnter = () => { document.body.classList.add('no-select'); diff --git a/src/components/middle/composer/InlineBotTooltip.tsx b/src/components/middle/composer/InlineBotTooltip.tsx index 669feab3d..f7b7a985f 100644 --- a/src/components/middle/composer/InlineBotTooltip.tsx +++ b/src/components/middle/composer/InlineBotTooltip.tsx @@ -1,5 +1,5 @@ import React, { - FC, memo, useCallback, useEffect, useRef, useState, + FC, memo, useCallback, useEffect, useRef, } from '../../../lib/teact/teact'; import { withGlobal } from '../../../lib/teact/teactn'; @@ -11,13 +11,12 @@ import { LoadMoreDirection } from '../../../types'; import { IS_TOUCH_ENV } from '../../../util/environment'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; import buildClassName from '../../../util/buildClassName'; -import captureKeyboardListeners from '../../../util/captureKeyboardListeners'; -import cycleRestrict from '../../../util/cycleRestrict'; import useShowTransition from '../../../hooks/useShowTransition'; import { throttle } from '../../../util/schedulers'; import { pick } from '../../../util/iteratees'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import usePrevious from '../../../hooks/usePrevious'; +import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; import MediaResult from './inlineResults/MediaResult'; import ArticleResult from './inlineResults/ArticleResult'; @@ -43,7 +42,7 @@ export type OwnProps = { onClose: NoneToVoidFunction; }; -type DispatchProps = Pick; +type DispatchProps = Pick; const InlineBotTooltip: FC = ({ isOpen, @@ -54,13 +53,12 @@ const InlineBotTooltip: FC = ({ loadMore, onClose, openChat, - sendBotCommand, + startBot, onSelectResult, }) => { // eslint-disable-next-line no-null/no-null const containerRef = useRef(null); const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); - const [selectedIndex, setSelectedIndex] = useState(-1); const { observe: observeIntersection, } = useIntersectionObserver({ @@ -69,58 +67,29 @@ const InlineBotTooltip: FC = ({ isDisabled: !isOpen, }); - useEffect(() => { - setSelectedIndex(isGallery ? -1 : 0); - }, [inlineBotResults, isGallery]); - - useEffect(() => { - setTooltipItemVisible('.chat-item-clickable', selectedIndex, containerRef); - }, [selectedIndex]); - - const getSelectedIndex = useCallback((newIndex: number) => { - if (!inlineBotResults || !inlineBotResults.length) { - return -1; - } - - return cycleRestrict(inlineBotResults.length, newIndex); - }, [inlineBotResults]); - - const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => { - if (isGallery) { - return; - } - - e.preventDefault(); - setSelectedIndex((index) => (getSelectedIndex(index + value))); - }, [isGallery, getSelectedIndex]); - - const handleSelectInlineBotResult = useCallback((e: KeyboardEvent) => { - if (inlineBotResults && inlineBotResults.length && selectedIndex > -1) { - const inlineResult = inlineBotResults[selectedIndex]; - if (inlineResult) { - e.preventDefault(); - onSelectResult(inlineResult); - } - } - }, [inlineBotResults, onSelectResult, selectedIndex]); - const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { if (direction === LoadMoreDirection.Backwards) { runThrottled(loadMore); } }, [loadMore]); - useEffect(() => (isOpen ? captureKeyboardListeners({ - onEsc: onClose, - onUp: (e: KeyboardEvent) => handleArrowKey(-1, e), - onDown: (e: KeyboardEvent) => handleArrowKey(1, e), - onEnter: handleSelectInlineBotResult, - }) : undefined), [handleArrowKey, handleSelectInlineBotResult, isGallery, isOpen, onClose]); + const selectedIndex = useKeyboardNavigation({ + isActive: isOpen, + shouldRemoveSelectionOnReset: isGallery, + noArrowNavigation: isGallery, + items: inlineBotResults, + onSelect: onSelectResult, + onClose, + }); + + useEffect(() => { + setTooltipItemVisible('.chat-item-clickable', selectedIndex, containerRef); + }, [selectedIndex]); const handleSendPm = useCallback(() => { openChat({ id: botId }); - sendBotCommand({ chatId: botId, command: `/start ${switchPm!.startParam}` }); - }, [botId, openChat, sendBotCommand, switchPm]); + startBot({ botId, param: switchPm!.startParam }); + }, [botId, openChat, startBot, switchPm]); const prevInlineBotResults = usePrevious( inlineBotResults && inlineBotResults.length @@ -230,6 +199,6 @@ const InlineBotTooltip: FC = ({ export default memo(withGlobal( undefined, (setGlobal, actions): DispatchProps => pick(actions, [ - 'sendBotCommand', 'openChat', 'sendInlineBotResult', + 'startBot', 'openChat', 'sendInlineBotResult', ]), )(InlineBotTooltip)); diff --git a/src/components/middle/composer/MentionTooltip.tsx b/src/components/middle/composer/MentionTooltip.tsx index ba900cb4a..aecadfced 100644 --- a/src/components/middle/composer/MentionTooltip.tsx +++ b/src/components/middle/composer/MentionTooltip.tsx @@ -1,5 +1,5 @@ import React, { - FC, useCallback, useEffect, useState, useRef, memo, + FC, useCallback, useEffect, useRef, memo, } from '../../../lib/teact/teact'; import usePrevious from '../../../hooks/usePrevious'; @@ -7,9 +7,8 @@ import { ApiUser } from '../../../api/types'; import useShowTransition from '../../../hooks/useShowTransition'; import buildClassName from '../../../util/buildClassName'; -import captureKeyboardListeners from '../../../util/captureKeyboardListeners'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; -import cycleRestrict from '../../../util/cycleRestrict'; +import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; import ListItem from '../../ui/ListItem'; import PrivateChatInfo from '../../common/PrivateChatInfo'; @@ -18,7 +17,6 @@ import './MentionTooltip.scss'; export type OwnProps = { isOpen: boolean; - filter: string; onClose: () => void; onInsertUserName: (user: ApiUser, forceFocus?: boolean) => void; filteredUsers?: ApiUser[]; @@ -27,7 +25,6 @@ export type OwnProps = { const MentionTooltip: FC = ({ isOpen, - filter, onClose, onInsertUserName, usersById, @@ -37,21 +34,6 @@ const MentionTooltip: FC = ({ const containerRef = useRef(null); const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false); - const getSelectedIndex = useCallback((newIndex: number) => { - if (!filteredUsers) { - return -1; - } - const membersCount = filteredUsers!.length; - return cycleRestrict(membersCount, newIndex); - }, [filteredUsers]); - - const [selectedMentionIndex, setSelectedMentionIndex] = useState(-1); - - const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => { - e.preventDefault(); - setSelectedMentionIndex((index) => (getSelectedIndex(index + value))); - }, [setSelectedMentionIndex, getSelectedIndex]); - const handleUserSelect = useCallback((userId: number, forceFocus = false) => { const user = usersById && usersById[userId]; if (!user) { @@ -61,23 +43,21 @@ const MentionTooltip: FC = ({ onInsertUserName(user, forceFocus); }, [usersById, onInsertUserName]); - const handleSelectMention = useCallback((e: KeyboardEvent) => { - if (filteredUsers && filteredUsers.length && selectedMentionIndex > -1) { - const member = filteredUsers[selectedMentionIndex]; - if (member) { - e.preventDefault(); - handleUserSelect(member.id, true); - } - } - }, [filteredUsers, selectedMentionIndex, handleUserSelect]); + const handleSelectMention = useCallback((member: ApiUser) => { + handleUserSelect(member.id, true); + }, [handleUserSelect]); - useEffect(() => (isOpen ? captureKeyboardListeners({ - onEsc: onClose, - onUp: (e: KeyboardEvent) => handleArrowKey(-1, e), - onDown: (e: KeyboardEvent) => handleArrowKey(1, e), - onEnter: handleSelectMention, - onTab: handleSelectMention, - }) : undefined), [isOpen, onClose, handleArrowKey, handleSelectMention]); + const selectedMentionIndex = useKeyboardNavigation({ + isActive: isOpen, + items: filteredUsers, + onSelect: handleSelectMention, + shouldSelectOnTab: true, + onClose, + }); + + useEffect(() => { + setTooltipItemVisible('.chat-item-clickable', selectedMentionIndex, containerRef); + }, [selectedMentionIndex]); useEffect(() => { if (filteredUsers && !filteredUsers.length) { @@ -85,14 +65,6 @@ const MentionTooltip: FC = ({ } }, [filteredUsers, onClose]); - useEffect(() => { - setSelectedMentionIndex(0); - }, [filter]); - - useEffect(() => { - setTooltipItemVisible('.chat-item-clickable', selectedMentionIndex, containerRef); - }, [selectedMentionIndex]); - const prevChatMembers = usePrevious( filteredUsers && filteredUsers.length ? filteredUsers diff --git a/src/components/middle/composer/SymbolMenu.scss b/src/components/middle/composer/SymbolMenu.scss index 866e100f7..4ff91541d 100644 --- a/src/components/middle/composer/SymbolMenu.scss +++ b/src/components/middle/composer/SymbolMenu.scss @@ -90,6 +90,13 @@ } } + .Button.bot-commands ~ & { + .is-pointer-env & > .backdrop { + left: 3rem; + width: 3.25rem; + } + } + .bubble { padding: 0; width: var(--symbol-menu-width); diff --git a/src/components/middle/composer/hooks/useBotCommandTooltip.ts b/src/components/middle/composer/hooks/useBotCommandTooltip.ts new file mode 100644 index 000000000..ad1de4842 --- /dev/null +++ b/src/components/middle/composer/hooks/useBotCommandTooltip.ts @@ -0,0 +1,67 @@ +import { + useCallback, useEffect, useState, +} from '../../../../lib/teact/teact'; + +import { ApiBotCommand } from '../../../../api/types'; + +import { throttle } from '../../../../util/schedulers'; +import useFlag from '../../../../hooks/useFlag'; + +const runThrottled = throttle((cb) => cb(), 500, true); +const RE_COMMAND = /[\w\d_-]*/i; + +export default function useBotCommandTooltip( + isAllowed: boolean, + html: string, + botCommands?: ApiBotCommand[] | false, + chatBotCommands?: ApiBotCommand[], +) { + const [isOpen, markIsOpen, unmarkIsOpen] = useFlag(); + const [filteredBotCommands, setFilteredBotCommands] = useState(); + + const getFilteredCommands = useCallback((filter) => { + if (!botCommands && !chatBotCommands) { + setFilteredBotCommands(undefined); + + return; + } + + runThrottled(() => { + const nextFilteredBotCommands = (botCommands || chatBotCommands || []) + .filter(({ command }) => !filter || command.includes(filter)); + setFilteredBotCommands( + nextFilteredBotCommands && nextFilteredBotCommands.length ? nextFilteredBotCommands : undefined, + ); + }); + }, [botCommands, chatBotCommands]); + + useEffect(() => { + if (!isAllowed || !html.length) { + unmarkIsOpen(); + return; + } + + const shouldShowCommands = html.startsWith('/'); + + if (shouldShowCommands) { + const filter = html.substr(1).match(RE_COMMAND); + getFilteredCommands(filter ? filter[0] : ''); + } else { + unmarkIsOpen(); + } + }, [getFilteredCommands, html, isAllowed, unmarkIsOpen]); + + useEffect(() => { + if (filteredBotCommands && filteredBotCommands.length) { + markIsOpen(); + } else { + unmarkIsOpen(); + } + }, [filteredBotCommands, markIsOpen, unmarkIsOpen]); + + return { + isOpen, + close: unmarkIsOpen, + filteredBotCommands, + }; +} diff --git a/src/components/middle/composer/hooks/useKeyboardNavigation.ts b/src/components/middle/composer/hooks/useKeyboardNavigation.ts new file mode 100644 index 000000000..ce1d59c48 --- /dev/null +++ b/src/components/middle/composer/hooks/useKeyboardNavigation.ts @@ -0,0 +1,66 @@ +import { useCallback, useEffect, useState } from '../../../../lib/teact/teact'; +import captureKeyboardListeners from '../../../../util/captureKeyboardListeners'; +import cycleRestrict from '../../../../util/cycleRestrict'; + +export function useKeyboardNavigation({ + isActive, + isHorizontal, + shouldRemoveSelectionOnReset, + noArrowNavigation, + items, + shouldSelectOnTab, + onSelect, + onClose, +}: { + isActive: boolean; + isHorizontal?: boolean; + shouldRemoveSelectionOnReset?: boolean; + noArrowNavigation?: boolean; + items?: any[]; + shouldSelectOnTab?: boolean; + onSelect: AnyToVoidFunction; + onClose: NoneToVoidFunction; +}) { + const [selectedItemIndex, setSelectedItemIndex] = useState(-1); + + const getSelectedIndex = useCallback((newIndex: number) => { + if (!items) { + return -1; + } + + return cycleRestrict(items.length, newIndex); + }, [items]); + + const handleArrowKey = useCallback((value: number, e: KeyboardEvent) => { + e.preventDefault(); + setSelectedItemIndex((index) => (getSelectedIndex(index + value))); + }, [setSelectedItemIndex, getSelectedIndex]); + + const handleItemSelect = useCallback((e: KeyboardEvent) => { + if (items && items.length && selectedItemIndex > -1) { + const item = items[selectedItemIndex]; + if (item) { + e.preventDefault(); + onSelect(item); + } + } + }, [items, onSelect, selectedItemIndex]); + + useEffect(() => { + setSelectedItemIndex(shouldRemoveSelectionOnReset ? -1 : 0); + }, [items, shouldRemoveSelectionOnReset]); + + useEffect(() => (isActive ? captureKeyboardListeners({ + onEsc: onClose, + onUp: noArrowNavigation || isHorizontal ? undefined : (e: KeyboardEvent) => handleArrowKey(-1, e), + onDown: noArrowNavigation || isHorizontal ? undefined : (e: KeyboardEvent) => handleArrowKey(1, e), + onLeft: noArrowNavigation || !isHorizontal ? undefined : (e: KeyboardEvent) => handleArrowKey(-1, e), + onRight: noArrowNavigation || !isHorizontal ? undefined : (e: KeyboardEvent) => handleArrowKey(1, e), + onTab: shouldSelectOnTab ? handleItemSelect : undefined, + onEnter: handleItemSelect, + }) : undefined), [ + noArrowNavigation, handleArrowKey, handleItemSelect, isActive, isHorizontal, onClose, shouldSelectOnTab, + ]); + + return selectedItemIndex; +} diff --git a/src/components/middle/composer/hooks/useMentionTooltip.ts b/src/components/middle/composer/hooks/useMentionTooltip.ts index 5ef53f7c1..31663b1aa 100644 --- a/src/components/middle/composer/hooks/useMentionTooltip.ts +++ b/src/components/middle/composer/hooks/useMentionTooltip.ts @@ -35,7 +35,6 @@ export default function useMentionTooltip( usersById?: Record, ) { const [isOpen, markIsOpen, unmarkIsOpen] = useFlag(); - const [currentFilter, setCurrentFilter] = useState(''); const [usersToMention, setUsersToMention] = useState(); const topInlineBots = useMemo(() => { @@ -77,7 +76,6 @@ export default function useMentionTooltip( if (usernameFilter) { const filter = usernameFilter ? usernameFilter.substr(1) : ''; - setCurrentFilter(filter); getFilteredUsers(filter, canSuggestInlineBots(html)); } else { unmarkIsOpen(); @@ -121,7 +119,6 @@ export default function useMentionTooltip( return { isMentionTooltipOpen: isOpen, - mentionFilter: currentFilter, closeMentionTooltip: unmarkIsOpen, insertMention, mentionFilteredUsers: usersToMention, diff --git a/src/components/payment/PaymentModal.tsx b/src/components/payment/PaymentModal.tsx index 45e0fdda7..d79fef6b5 100644 --- a/src/components/payment/PaymentModal.tsx +++ b/src/components/payment/PaymentModal.tsx @@ -100,7 +100,6 @@ const Invoice: FC = ({ [error.field]: error.message, }, }); - return; } }, [error, paymentDispatch]); diff --git a/src/components/right/management/ManageChatPrivacyType.tsx b/src/components/right/management/ManageChatPrivacyType.tsx index eccfda0ee..097fb63d9 100644 --- a/src/components/right/management/ManageChatPrivacyType.tsx +++ b/src/components/right/management/ManageChatPrivacyType.tsx @@ -62,7 +62,7 @@ const ManageChatPrivacyType: FC = ({ const canUpdate = Boolean( (privacyType === 'public' && username && isUsernameAvailable) - || (privacyType === 'private' && isPublic) + || (privacyType === 'private' && isPublic), ); useHistoryBack(isActive, onClose); diff --git a/src/config.ts b/src/config.ts index 5d5ee7b42..d98348311 100644 --- a/src/config.ts +++ b/src/config.ts @@ -133,6 +133,7 @@ export const RE_TME_INVITE_LINK = /^(?:https?:\/\/)?(?:t\.me\/joinchat\/)([\d\w_ // MTProto constants export const SERVICE_NOTIFICATIONS_USER_ID = 777000; +export const REPLIES_USER_ID = 1271266957; export const ALL_FOLDER_ID = 0; export const ARCHIVED_FOLDER_ID = 1; export const DELETED_COMMENTS_CHANNEL_ID = 777; diff --git a/src/global/types.ts b/src/global/types.ts index e410fe5e3..945c74b13 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -500,7 +500,7 @@ export type ActionTypes = ( 'openStickerSetShortName' | // bots 'clickInlineButton' | 'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' | - 'resetInlineBot' | 'restartBot' | + 'resetInlineBot' | 'restartBot' | 'startBot' | // misc 'openMediaViewer' | 'closeMediaViewer' | 'openAudioPlayer' | 'closeAudioPlayer' | 'openPollModal' | 'closePollModal' | 'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' | 'setDeviceToken' | diff --git a/src/hooks/useMouseInside.ts b/src/hooks/useMouseInside.ts index 1d2ee7229..32107f454 100644 --- a/src/hooks/useMouseInside.ts +++ b/src/hooks/useMouseInside.ts @@ -8,7 +8,13 @@ let closeTimeout: number | undefined; export default function useMouseInside( isOpen: boolean, onClose: NoneToVoidFunction, menuCloseTimeout = MENU_CLOSE_TIMEOUT, isDisabled = false, ) { - const isMouseInside = useRef(false); + const isMouseInside = useRef(true); + + useEffect(() => { + if (isOpen) { + isMouseInside.current = true; + } + }, [isOpen]); useEffect(() => { if (closeTimeout) { diff --git a/src/lib/gramjs/client/auth.ts b/src/lib/gramjs/client/auth.ts index 065ac2511..e37a064cb 100644 --- a/src/lib/gramjs/client/auth.ts +++ b/src/lib/gramjs/client/auth.ts @@ -50,7 +50,6 @@ export async function authFlow( client._log.info('Signed in successfully as', utils.getDisplayName(me)); } - export async function checkAuthorization(client: TelegramClient) { try { await client.invoke(new Api.updates.GetState()); diff --git a/src/lib/gramjs/tl/apiTl.js b/src/lib/gramjs/tl/apiTl.js index 90ec48117..21b3848a0 100644 --- a/src/lib/gramjs/tl/apiTl.js +++ b/src/lib/gramjs/tl/apiTl.js @@ -1000,6 +1000,7 @@ messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; +messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; diff --git a/src/lib/gramjs/tl/static/api.reduced.tl b/src/lib/gramjs/tl/static/api.reduced.tl index d3e109281..7af017bd8 100644 --- a/src/lib/gramjs/tl/static/api.reduced.tl +++ b/src/lib/gramjs/tl/static/api.reduced.tl @@ -1001,6 +1001,7 @@ messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; +messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; @@ -1086,4 +1087,4 @@ langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDiffer langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; langpack.getLanguages#42c6978f lang_pack:string = Vector; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; -// LAYER 128 \ No newline at end of file +// LAYER 128 diff --git a/src/modules/actions/api/bots.ts b/src/modules/actions/api/bots.ts index fdb1ea689..1341603f3 100644 --- a/src/modules/actions/api/bots.ts +++ b/src/modules/actions/api/bots.ts @@ -200,7 +200,7 @@ addReducer('sendInlineBotResult', (global, actions, payload) => { }); }); -addReducer('resetInlineBot', ((global, actions, payload) => { +addReducer('resetInlineBot', (global, actions, payload) => { const { username } = payload; let inlineBotData = global.inlineBots.byUsername[username]; @@ -219,7 +219,23 @@ addReducer('resetInlineBot', ((global, actions, payload) => { }; setGlobal(replaceInlineBotSettings(global, username, inlineBotData)); -})); +}); + +addReducer('startBot', (global, actions, payload) => { + const { botId, param } = payload; + + const bot = selectUser(global, botId); + if (!bot) { + return; + } + + (async () => { + await callApi('startBot', { + bot, + startParam: param, + }); + })(); +}); async function searchInlineBot({ username, diff --git a/src/modules/actions/apiUpdaters/initial.ts b/src/modules/actions/apiUpdaters/initial.ts index f2e53147a..d4f45a8d5 100644 --- a/src/modules/actions/apiUpdaters/initial.ts +++ b/src/modules/actions/apiUpdaters/initial.ts @@ -57,7 +57,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { onUpdateCurrentUser(update); break; - case 'error': + case 'error': { if (update.error.message === 'SESSION_REVOKED') { actions.signOut(); } @@ -70,6 +70,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => { } break; + } } }); diff --git a/src/styles/Telegram T.json b/src/styles/Telegram T.json index f30da8388..cc0bc7ed8 100644 --- a/src/styles/Telegram T.json +++ b/src/styles/Telegram T.json @@ -2,18 +2,34 @@ "metadata": { "name": "Telegram T", "lastOpened": 0, - "created": 1617759787079 + "created": 1628131266337 }, "iconSets": [ { "selection": [ + { + "order": 667, + "id": 34, + "name": "bot-commands-filled", + "prevSize": 32, + "code": 59775, + "tempChar": "" + }, + { + "order": 664, + "id": 33, + "name": "reply-filled", + "prevSize": 32, + "code": 59776, + "tempChar": "" + }, { "order": 656, "id": 32, "name": "bug", "prevSize": 32, "code": 59774, - "tempChar": "" + "tempChar": "" }, { "order": 619, @@ -21,7 +37,7 @@ "name": "data", "prevSize": 32, "code": 59773, - "tempChar": "" + "tempChar": "" }, { "order": 622, @@ -29,7 +45,7 @@ "name": "darkmode", "prevSize": 32, "code": 59769, - "tempChar": "" + "tempChar": "" }, { "order": 623, @@ -37,7 +53,7 @@ "name": "animations", "prevSize": 32, "code": 59770, - "tempChar": "" + "tempChar": "" }, { "order": 626, @@ -45,7 +61,7 @@ "name": "enter", "prevSize": 32, "code": 59771, - "tempChar": "" + "tempChar": "" }, { "order": 627, @@ -53,7 +69,7 @@ "name": "fontsize", "prevSize": 32, "code": 59772, - "tempChar": "" + "tempChar": "" }, { "order": 630, @@ -61,7 +77,7 @@ "name": "permissions", "prevSize": 32, "code": 59766, - "tempChar": "" + "tempChar": "" }, { "order": 631, @@ -69,7 +85,7 @@ "name": "card", "prevSize": 32, "code": 59767, - "tempChar": "" + "tempChar": "" }, { "order": 634, @@ -77,15 +93,15 @@ "name": "truck", "prevSize": 32, "code": 59768, - "tempChar": "" + "tempChar": "" }, { - "order": 635, + "order": 663, "id": 23, "name": "share-filled", "prevSize": 32, "code": 59738, - "tempChar": "" + "tempChar": "" }, { "order": 638, @@ -93,7 +109,7 @@ "name": "bold", "prevSize": 32, "code": 59745, - "tempChar": "" + "tempChar": "" }, { "order": 639, @@ -101,7 +117,7 @@ "name": "bot-command", "prevSize": 32, "code": 59746, - "tempChar": "" + "tempChar": "" }, { "order": 642, @@ -109,7 +125,7 @@ "name": "calendar-filter", "prevSize": 32, "code": 59747, - "tempChar": "" + "tempChar": "" }, { "order": 643, @@ -117,7 +133,7 @@ "name": "comments", "prevSize": 32, "code": 59748, - "tempChar": "" + "tempChar": "" }, { "order": 645, @@ -125,7 +141,7 @@ "name": "comments-sticker", "prevSize": 32, "code": 59749, - "tempChar": "" + "tempChar": "" }, { "order": 646, @@ -133,7 +149,7 @@ "name": "arrow-down", "prevSize": 32, "code": 59750, - "tempChar": "" + "tempChar": "" }, { "order": 647, @@ -141,7 +157,7 @@ "name": "email", "prevSize": 32, "code": 59751, - "tempChar": "" + "tempChar": "" }, { "order": 648, @@ -149,7 +165,7 @@ "name": "italic", "prevSize": 32, "code": 59752, - "tempChar": "" + "tempChar": "" }, { "order": 620, @@ -157,7 +173,7 @@ "name": "link", "prevSize": 32, "code": 59753, - "tempChar": "" + "tempChar": "" }, { "order": 621, @@ -165,7 +181,7 @@ "name": "mention", "prevSize": 32, "code": 59754, - "tempChar": "" + "tempChar": "" }, { "order": 624, @@ -173,7 +189,7 @@ "name": "monospace", "prevSize": 32, "code": 59755, - "tempChar": "" + "tempChar": "" }, { "order": 625, @@ -181,7 +197,7 @@ "name": "next", "prevSize": 32, "code": 59756, - "tempChar": "" + "tempChar": "" }, { "order": 628, @@ -189,7 +205,7 @@ "name": "password-off", "prevSize": 32, "code": 59757, - "tempChar": "" + "tempChar": "" }, { "order": 629, @@ -197,7 +213,7 @@ "name": "pin-list", "prevSize": 32, "code": 59758, - "tempChar": "" + "tempChar": "" }, { "order": 632, @@ -205,7 +221,7 @@ "name": "previous", "prevSize": 32, "code": 59759, - "tempChar": "" + "tempChar": "" }, { "order": 633, @@ -213,7 +229,7 @@ "name": "replace", "prevSize": 32, "code": 59760, - "tempChar": "" + "tempChar": "" }, { "order": 636, @@ -221,7 +237,7 @@ "name": "schedule", "prevSize": 32, "code": 59761, - "tempChar": "" + "tempChar": "" }, { "order": 637, @@ -229,7 +245,7 @@ "name": "strikethrough", "prevSize": 32, "code": 59762, - "tempChar": "" + "tempChar": "" }, { "order": 640, @@ -237,7 +253,7 @@ "name": "underlined", "prevSize": 32, "code": 59763, - "tempChar": "" + "tempChar": "" }, { "order": 641, @@ -245,7 +261,7 @@ "name": "zoom-in", "prevSize": 32, "code": 59764, - "tempChar": "" + "tempChar": "" }, { "order": 649, @@ -253,20 +269,50 @@ "name": "zoom-out", "prevSize": 32, "code": 59765, - "tempChar": "" + "tempChar": "" } ], "id": 2, "metadata": { "name": "Untitled Set", "importSize": { - "width": 768, - "height": 768 + "width": 22, + "height": 22 } }, "height": 1024, "prevSize": 32, "icons": [ + { + "id": 34, + "paths": [ + "M698.182 0c179.944 0 325.818 145.874 325.818 325.818v372.364c0 179.944-145.874 325.818-325.818 325.818h-372.364c-179.944 0-325.818-145.874-325.818-325.818v-372.364c0-179.944 145.874-325.818 325.818-325.818zM751.377 651.636l-483.406 0.313c-19.842 2.689-35.243 22.362-35.243 46.232 0 25.706 17.862 46.545 39.896 46.545l483.406-0.313c19.842-2.689 35.243-22.362 35.243-46.232 0-25.706-17.862-46.545-39.896-46.545zM751.377 465.455l-483.406 0.313c-19.842 2.689-35.243 22.362-35.243 46.232 0 25.706 17.862 46.545 39.896 46.545l483.406-0.313c19.842-2.689 35.243-22.362 35.243-46.232 0-25.706-17.862-46.545-39.896-46.545zM751.377 279.273l-483.406 0.313c-19.842 2.689-35.243 22.362-35.243 46.232 0 25.706 17.862 46.545 39.896 46.545l483.406-0.313c19.842-2.689 35.243-22.362 35.243-46.232 0-25.706-17.862-46.545-39.896-46.545z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "bot-commands-filled" + ] + }, + { + "id": 33, + "paths": [ + "M499.091 204.599c0-25.135-20.376-45.511-45.511-45.511-11.451 0-22.48 4.316-30.889 12.088l-332.624 307.401c-18.459 17.060-19.595 45.853-1.952 64.919l1.952 1.929 332.624 307.401c18.459 17.060 47.253 15.925 64.313-2.535 7.771-8.409 12.088-19.439 12.088-30.889v-140.747c171.605 4.143 288.836 58.904 354.051 163.311l6.231 10.368c8.549 14.833 27.505 19.928 42.34 11.379 9.467-5.457 15.363-15.495 15.517-26.421 4.404-311.701-132.133-477.967-404.411-490.876l-13.727-0.517v-141.297l-0.003-0.003z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "reply-filled" + ] + }, { "id": 32, "paths": [ @@ -2289,7 +2335,7 @@ "name": "select", "prevSize": 32, "code": 59744, - "tempChar": "" + "tempChar": "" }, { "order": 480, @@ -2297,7 +2343,7 @@ "name": "folder", "prevSize": 32, "code": 59667, - "tempChar": "" + "tempChar": "" }, { "order": 481, @@ -2305,7 +2351,7 @@ "name": "bots", "prevSize": 32, "code": 59669, - "tempChar": "" + "tempChar": "" }, { "order": 482, @@ -2313,7 +2359,7 @@ "name": "calendar", "prevSize": 32, "code": 59670, - "tempChar": "" + "tempChar": "" }, { "order": 483, @@ -2321,7 +2367,7 @@ "name": "cloud-download", "prevSize": 32, "code": 59671, - "tempChar": "" + "tempChar": "" }, { "order": 484, @@ -2329,7 +2375,7 @@ "name": "colorize", "prevSize": 32, "code": 59672, - "tempChar": "" + "tempChar": "" }, { "order": 651, @@ -2337,7 +2383,7 @@ "name": "forward", "prevSize": 32, "code": 59687, - "tempChar": "" + "tempChar": "" }, { "order": 650, @@ -2345,7 +2391,7 @@ "name": "reply", "prevSize": 32, "code": 59719, - "tempChar": "" + "tempChar": "" }, { "order": 487, @@ -2353,7 +2399,7 @@ "name": "help", "prevSize": 32, "code": 59690, - "tempChar": "" + "tempChar": "" }, { "order": 488, @@ -2361,7 +2407,7 @@ "name": "info", "prevSize": 32, "code": 59691, - "tempChar": "" + "tempChar": "" }, { "order": 489, @@ -2369,7 +2415,7 @@ "name": "info-filled", "prevSize": 32, "code": 59675, - "tempChar": "" + "tempChar": "" }, { "order": 490, @@ -2377,7 +2423,7 @@ "name": "delete-filled", "prevSize": 32, "code": 59676, - "tempChar": "" + "tempChar": "" }, { "order": 491, @@ -2385,7 +2431,7 @@ "name": "delete", "prevSize": 32, "code": 59677, - "tempChar": "" + "tempChar": "" }, { "order": 492, @@ -2393,7 +2439,7 @@ "name": "edit", "prevSize": 32, "code": 59683, - "tempChar": "" + "tempChar": "" }, { "order": 493, @@ -2401,7 +2447,7 @@ "name": "new-chat-filled", "prevSize": 32, "code": 59705, - "tempChar": "" + "tempChar": "" }, { "order": 494, @@ -2409,7 +2455,7 @@ "name": "send", "prevSize": 32, "code": 59722, - "tempChar": "" + "tempChar": "" }, { "order": 495, @@ -2417,7 +2463,7 @@ "name": "send-outline", "prevSize": 32, "code": 59723, - "tempChar": "" + "tempChar": "" }, { "order": 496, @@ -2425,7 +2471,7 @@ "name": "add-user-filled", "prevSize": 32, "code": 59652, - "tempChar": "" + "tempChar": "" }, { "order": 497, @@ -2433,7 +2479,7 @@ "name": "add-user", "prevSize": 32, "code": 59653, - "tempChar": "" + "tempChar": "" }, { "order": 498, @@ -2441,7 +2487,7 @@ "name": "delete-user", "prevSize": 32, "code": 59678, - "tempChar": "" + "tempChar": "" }, { "order": 499, @@ -2449,7 +2495,7 @@ "name": "microphone", "prevSize": 32, "code": 59701, - "tempChar": "" + "tempChar": "" }, { "order": 500, @@ -2457,7 +2503,7 @@ "name": "microphone-alt", "prevSize": 32, "code": 59707, - "tempChar": "" + "tempChar": "" }, { "order": 501, @@ -2465,7 +2511,7 @@ "name": "poll", "prevSize": 32, "code": 59704, - "tempChar": "" + "tempChar": "" }, { "order": 502, @@ -2473,7 +2519,7 @@ "name": "revote", "prevSize": 32, "code": 59706, - "tempChar": "" + "tempChar": "" }, { "order": 503, @@ -2481,7 +2527,7 @@ "name": "photo", "prevSize": 32, "code": 59712, - "tempChar": "" + "tempChar": "" }, { "order": 504, @@ -2489,7 +2535,7 @@ "name": "document", "prevSize": 32, "code": 59679, - "tempChar": "" + "tempChar": "" }, { "order": 505, @@ -2497,7 +2543,7 @@ "name": "camera", "prevSize": 32, "code": 59662, - "tempChar": "" + "tempChar": "" }, { "order": 506, @@ -2505,7 +2551,7 @@ "name": "camera-add", "prevSize": 32, "code": 59663, - "tempChar": "" + "tempChar": "" }, { "order": 507, @@ -2513,7 +2559,7 @@ "name": "logout", "prevSize": 32, "code": 59698, - "tempChar": "" + "tempChar": "" }, { "order": 508, @@ -2521,7 +2567,7 @@ "name": "saved-messages", "prevSize": 32, "code": 59720, - "tempChar": "" + "tempChar": "" }, { "order": 509, @@ -2529,7 +2575,7 @@ "name": "settings", "prevSize": 32, "code": 59726, - "tempChar": "" + "tempChar": "" }, { "order": 652, @@ -2537,7 +2583,7 @@ "name": "phone", "prevSize": 32, "code": 59711, - "tempChar": "" + "tempChar": "" }, { "order": 653, @@ -2545,7 +2591,7 @@ "name": "attach", "prevSize": 32, "code": 59657, - "tempChar": "" + "tempChar": "" }, { "order": 512, @@ -2553,7 +2599,7 @@ "name": "copy", "prevSize": 32, "code": 59674, - "tempChar": "" + "tempChar": "" }, { "order": 513, @@ -2561,7 +2607,7 @@ "name": "channel", "prevSize": 32, "code": 59665, - "tempChar": "" + "tempChar": "" }, { "order": 514, @@ -2569,7 +2615,7 @@ "name": "group", "prevSize": 32, "code": 59689, - "tempChar": "" + "tempChar": "" }, { "order": 515, @@ -2577,7 +2623,7 @@ "name": "user", "prevSize": 32, "code": 59737, - "tempChar": "" + "tempChar": "" }, { "order": 516, @@ -2585,7 +2631,7 @@ "name": "non-contacts", "prevSize": 32, "code": 59688, - "tempChar": "" + "tempChar": "" }, { "order": 517, @@ -2593,7 +2639,7 @@ "name": "active-sessions", "prevSize": 32, "code": 59650, - "tempChar": "" + "tempChar": "" }, { "order": 518, @@ -2601,7 +2647,7 @@ "name": "admin", "prevSize": 32, "code": 59654, - "tempChar": "" + "tempChar": "" }, { "order": 519, @@ -2609,7 +2655,7 @@ "name": "download", "prevSize": 32, "code": 59681, - "tempChar": "" + "tempChar": "" }, { "order": 520, @@ -2617,7 +2663,7 @@ "name": "location", "prevSize": 32, "code": 59696, - "tempChar": "" + "tempChar": "" }, { "order": 521, @@ -2625,7 +2671,7 @@ "name": "stop", "prevSize": 32, "code": 59730, - "tempChar": "" + "tempChar": "" }, { "order": 523, @@ -2633,7 +2679,7 @@ "name": "archive", "prevSize": 32, "code": 59656, - "tempChar": "" + "tempChar": "" }, { "order": 524, @@ -2641,7 +2687,7 @@ "name": "unarchive", "prevSize": 32, "code": 59731, - "tempChar": "" + "tempChar": "" }, { "order": 525, @@ -2649,7 +2695,7 @@ "name": "readchats", "prevSize": 32, "code": 59699, - "tempChar": "" + "tempChar": "" }, { "order": 526, @@ -2657,7 +2703,7 @@ "name": "unread", "prevSize": 32, "code": 59735, - "tempChar": "" + "tempChar": "" }, { "order": 654, @@ -2665,7 +2711,7 @@ "name": "message", "prevSize": 32, "code": 59700, - "tempChar": "" + "tempChar": "" }, { "order": 659, @@ -2673,7 +2719,7 @@ "name": "lock", "prevSize": 32, "code": 59697, - "tempChar": "" + "tempChar": "" }, { "order": 529, @@ -2681,7 +2727,7 @@ "name": "unlock", "prevSize": 32, "code": 59732, - "tempChar": "" + "tempChar": "" }, { "order": 530, @@ -2689,7 +2735,7 @@ "name": "mute", "prevSize": 32, "code": 59703, - "tempChar": "" + "tempChar": "" }, { "order": 531, @@ -2697,7 +2743,7 @@ "name": "unmute", "prevSize": 32, "code": 59733, - "tempChar": "" + "tempChar": "" }, { "order": 532, @@ -2705,7 +2751,7 @@ "name": "pin", "prevSize": 32, "code": 59713, - "tempChar": "" + "tempChar": "" }, { "order": 533, @@ -2713,7 +2759,7 @@ "name": "unpin", "prevSize": 32, "code": 59734, - "tempChar": "" + "tempChar": "" }, { "order": 534, @@ -2721,7 +2767,7 @@ "name": "smallscreen", "prevSize": 32, "code": 59742, - "tempChar": "" + "tempChar": "" }, { "order": 535, @@ -2729,7 +2775,7 @@ "name": "fullscreen", "prevSize": 32, "code": 59743, - "tempChar": "" + "tempChar": "" }, { "order": 536, @@ -2737,7 +2783,7 @@ "name": "large-pause", "prevSize": 32, "code": 59694, - "tempChar": "" + "tempChar": "" }, { "order": 537, @@ -2745,7 +2791,7 @@ "name": "large-play", "prevSize": 32, "code": 59695, - "tempChar": "" + "tempChar": "" }, { "order": 538, @@ -2753,7 +2799,7 @@ "name": "pause", "prevSize": 32, "code": 59709, - "tempChar": "" + "tempChar": "" }, { "order": 539, @@ -2761,7 +2807,7 @@ "name": "play", "prevSize": 32, "code": 59715, - "tempChar": "" + "tempChar": "" }, { "order": 540, @@ -2769,7 +2815,7 @@ "name": "channelviews", "prevSize": 32, "code": 59666, - "tempChar": "" + "tempChar": "" }, { "order": 541, @@ -2777,7 +2823,7 @@ "name": "message-succeeded", "prevSize": 32, "code": 59648, - "tempChar": "" + "tempChar": "" }, { "order": 657, @@ -2785,7 +2831,7 @@ "name": "message-read", "prevSize": 32, "code": 59649, - "tempChar": "" + "tempChar": "" }, { "order": 543, @@ -2793,7 +2839,7 @@ "name": "message-pending", "prevSize": 32, "code": 59724, - "tempChar": "" + "tempChar": "" }, { "order": 544, @@ -2801,7 +2847,7 @@ "name": "message-failed", "prevSize": 32, "code": 59725, - "tempChar": "" + "tempChar": "" }, { "order": 545, @@ -2809,7 +2855,7 @@ "name": "favorite", "prevSize": 32, "code": 59710, - "tempChar": "" + "tempChar": "" }, { "order": 546, @@ -2817,7 +2863,7 @@ "name": "keyboard", "prevSize": 32, "code": 59716, - "tempChar": "" + "tempChar": "" }, { "order": 547, @@ -2825,7 +2871,7 @@ "name": "delete-left", "prevSize": 32, "code": 59717, - "tempChar": "" + "tempChar": "" }, { "order": 548, @@ -2833,7 +2879,7 @@ "name": "recent", "prevSize": 32, "code": 59718, - "tempChar": "" + "tempChar": "" }, { "order": 549, @@ -2841,7 +2887,7 @@ "name": "gifs", "prevSize": 32, "code": 59727, - "tempChar": "" + "tempChar": "" }, { "order": 550, @@ -2849,7 +2895,7 @@ "name": "stickers", "prevSize": 32, "code": 59739, - "tempChar": "" + "tempChar": "" }, { "order": 551, @@ -2857,7 +2903,7 @@ "name": "smile", "prevSize": 32, "code": 59728, - "tempChar": "" + "tempChar": "" }, { "order": 552, @@ -2865,7 +2911,7 @@ "name": "animals", "prevSize": 32, "code": 59655, - "tempChar": "" + "tempChar": "" }, { "order": 553, @@ -2873,7 +2919,7 @@ "name": "eats", "prevSize": 32, "code": 59682, - "tempChar": "" + "tempChar": "" }, { "order": 554, @@ -2881,7 +2927,7 @@ "name": "sport", "prevSize": 32, "code": 59729, - "tempChar": "" + "tempChar": "" }, { "order": 555, @@ -2889,7 +2935,7 @@ "name": "car", "prevSize": 32, "code": 59664, - "tempChar": "" + "tempChar": "" }, { "order": 556, @@ -2897,7 +2943,7 @@ "name": "lamp", "prevSize": 32, "code": 59692, - "tempChar": "" + "tempChar": "" }, { "order": 557, @@ -2905,7 +2951,7 @@ "name": "language", "prevSize": 32, "code": 59693, - "tempChar": "" + "tempChar": "" }, { "order": 558, @@ -2913,7 +2959,7 @@ "name": "flag", "prevSize": 32, "code": 59686, - "tempChar": "" + "tempChar": "" }, { "order": 559, @@ -2921,7 +2967,7 @@ "name": "more", "prevSize": 32, "code": 59702, - "tempChar": "" + "tempChar": "" }, { "order": 560, @@ -2929,7 +2975,7 @@ "name": "search", "prevSize": 32, "code": 59721, - "tempChar": "" + "tempChar": "" }, { "order": 561, @@ -2937,7 +2983,7 @@ "name": "remove", "prevSize": 32, "code": 59740, - "tempChar": "" + "tempChar": "" }, { "order": 562, @@ -2945,7 +2991,7 @@ "name": "add", "prevSize": 32, "code": 59651, - "tempChar": "" + "tempChar": "" }, { "order": 563, @@ -2953,7 +2999,7 @@ "name": "check", "prevSize": 32, "code": 59668, - "tempChar": "" + "tempChar": "" }, { "order": 564, @@ -2961,7 +3007,7 @@ "name": "close", "prevSize": 32, "code": 59673, - "tempChar": "" + "tempChar": "" }, { "order": 610, @@ -2969,7 +3015,7 @@ "name": "arrow-left", "prevSize": 32, "code": 59661, - "tempChar": "" + "tempChar": "" }, { "order": 566, @@ -2977,7 +3023,7 @@ "name": "arrow-right", "prevSize": 32, "code": 59708, - "tempChar": "" + "tempChar": "" }, { "order": 567, @@ -2985,7 +3031,7 @@ "name": "down", "prevSize": 32, "code": 59680, - "tempChar": "" + "tempChar": "" }, { "order": 568, @@ -2993,7 +3039,7 @@ "name": "up", "prevSize": 32, "code": 59736, - "tempChar": "" + "tempChar": "" }, { "order": 569, @@ -3001,7 +3047,7 @@ "name": "eye-closed", "prevSize": 32, "code": 59685, - "tempChar": "" + "tempChar": "" }, { "order": 570, @@ -3009,7 +3055,7 @@ "name": "eye", "prevSize": 32, "code": 59684, - "tempChar": "" + "tempChar": "" }, { "order": 571, @@ -3017,7 +3063,7 @@ "name": "muted-chat", "prevSize": 32, "code": 59741, - "tempChar": "" + "tempChar": "" }, { "order": 572, @@ -3025,7 +3071,7 @@ "name": "avatar-archived-chats", "prevSize": 32, "code": 59658, - "tempChar": "" + "tempChar": "" }, { "order": 573, @@ -3033,7 +3079,7 @@ "name": "avatar-deleted-account", "prevSize": 32, "code": 59659, - "tempChar": "" + "tempChar": "" }, { "order": 574, @@ -3041,7 +3087,7 @@ "name": "avatar-saved-messages", "prevSize": 32, "code": 59660, - "tempChar": "" + "tempChar": "" }, { "order": 575, @@ -3049,7 +3095,7 @@ "name": "pinned-chat", "prevSize": 32, "code": 59714, - "tempChar": "" + "tempChar": "" } ], "prevSize": 32, diff --git a/src/styles/icons.scss b/src/styles/icons.scss index c9dbd538b..8b217394b 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -1,7 +1,7 @@ @font-face { font-family: 'icomoon'; - src: url('../assets/fonts/icomoon.woff2?7tsg0v') format('woff2'), - url('../assets/fonts/icomoon.woff?7tsg0v') format('woff'); + src: url('../assets/fonts/icomoon.woff2?n9djnk') format('woff2'), + url('../assets/fonts/icomoon.woff?n9djnk') format('woff'); font-weight: normal; font-style: normal; font-display: block; @@ -32,6 +32,12 @@ } } +.icon-bot-commands-filled:before { + content: "\e97f"; +} +.icon-reply-filled:before { + content: "\e980"; +} .icon-bug:before { content: "\e97e"; } diff --git a/src/util/getReadableErrorText.ts b/src/util/getReadableErrorText.ts index a430499f5..0f8a06172 100644 --- a/src/util/getReadableErrorText.ts +++ b/src/util/getReadableErrorText.ts @@ -120,5 +120,5 @@ export default function getReadableErrorText(error: ApiError) { } export function getShippingError(error: ApiError): ApiFieldError | undefined { - return SHIPPING_ERRORS[error.message]; + return SHIPPING_ERRORS[error.message]; }