Composer: Add Bot Command Menu and Bot Command Tooltip (#1364)
This commit is contained in:
parent
ad36326c83
commit
a185fd9407
@ -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;
|
||||
}
|
||||
|
||||
@ -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[]);
|
||||
}
|
||||
|
||||
@ -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[];
|
||||
}
|
||||
|
||||
@ -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<ApiUser>(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[]) {
|
||||
|
||||
@ -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 || [])],
|
||||
};
|
||||
|
||||
@ -55,7 +55,7 @@ export {
|
||||
} from './twoFaSettings';
|
||||
|
||||
export {
|
||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult,
|
||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
||||
} from './bots';
|
||||
|
||||
export {
|
||||
|
||||
@ -38,3 +38,9 @@ export interface ApiBotInlineSwitchPm {
|
||||
text: string;
|
||||
startParam: string;
|
||||
}
|
||||
|
||||
export interface ApiBotCommand {
|
||||
botId: number;
|
||||
command: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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';
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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';
|
||||
|
||||
@ -546,10 +546,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
&& !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<OwnProps>(
|
||||
isGroupChat: isChatGroup(chat),
|
||||
isCreator: chat.isCreator,
|
||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||
isBot: Boolean(bot),
|
||||
isBot: Boolean(chatBot),
|
||||
messageIds,
|
||||
messagesById,
|
||||
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
||||
|
||||
@ -67,9 +67,7 @@ const AttachmentModal: FC<OwnProps> = ({
|
||||
const lang = useLang();
|
||||
|
||||
const {
|
||||
isMentionTooltipOpen, mentionFilter,
|
||||
closeMentionTooltip, insertMention,
|
||||
mentionFilteredUsers,
|
||||
isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers,
|
||||
} = useMentionTooltip(
|
||||
isOpen,
|
||||
caption,
|
||||
@ -227,7 +225,6 @@ const AttachmentModal: FC<OwnProps> = ({
|
||||
<MentionTooltip
|
||||
isOpen={isMentionTooltipOpen}
|
||||
onClose={closeMentionTooltip}
|
||||
filter={mentionFilter}
|
||||
onInsertUserName={insertMention}
|
||||
filteredUsers={mentionFilteredUsers}
|
||||
usersById={usersById}
|
||||
|
||||
27
src/components/middle/composer/BotCommand.scss
Normal file
27
src/components/middle/composer/BotCommand.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.BotCommand {
|
||||
margin: 0 !important;
|
||||
|
||||
.ListItem-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.multiline-item {
|
||||
padding: 0 1rem;
|
||||
|
||||
.subtitle {
|
||||
padding-top: .25rem;
|
||||
line-height: 1.3125;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-avatar {
|
||||
.multiline-item {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/components/middle/composer/BotCommand.tsx
Normal file
47
src/components/middle/composer/BotCommand.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { FC, memo } from '../../../lib/teact/teact';
|
||||
|
||||
import { ApiBotCommand, ApiUser } from '../../../api/types';
|
||||
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Avatar from '../../common/Avatar';
|
||||
|
||||
import './BotCommand.scss';
|
||||
|
||||
type OwnProps = {
|
||||
botCommand: ApiBotCommand;
|
||||
bot?: ApiUser;
|
||||
withAvatar?: boolean;
|
||||
focus?: boolean;
|
||||
onClick: (botCommand: ApiBotCommand) => void;
|
||||
};
|
||||
|
||||
const BotCommand: FC<OwnProps> = ({
|
||||
withAvatar,
|
||||
focus,
|
||||
botCommand,
|
||||
bot,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={botCommand.command}
|
||||
className={buildClassName('BotCommand chat-item-clickable scroll-item', withAvatar && 'with-avatar')}
|
||||
multiline
|
||||
onClick={() => onClick(botCommand)}
|
||||
focus={focus}
|
||||
>
|
||||
{withAvatar && (
|
||||
<Avatar size="small" user={bot} />
|
||||
)}
|
||||
<div className="content-inner">
|
||||
<span className="title">/{botCommand.command}</span>
|
||||
<span className="subtitle">{renderText(botCommand.description)}</span>
|
||||
</div>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BotCommand);
|
||||
15
src/components/middle/composer/BotCommandMenu.async.tsx
Normal file
15
src/components/middle/composer/BotCommandMenu.async.tsx
Normal file
@ -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<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const BotCommandMenu = useModuleLoader(Bundles.Extra, 'BotCommandMenu', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return BotCommandMenu ? <BotCommandMenu {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(BotCommandMenuAsync);
|
||||
24
src/components/middle/composer/BotCommandMenu.scss
Normal file
24
src/components/middle/composer/BotCommandMenu.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/components/middle/composer/BotCommandMenu.tsx
Normal file
63
src/components/middle/composer/BotCommandMenu.tsx
Normal file
@ -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<GlobalActions, 'sendBotCommand'>;
|
||||
|
||||
const BotCommandMenu: FC<OwnProps & DispatchProps> = ({
|
||||
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 (
|
||||
<Menu
|
||||
isOpen={isOpen}
|
||||
positionX="left"
|
||||
positionY="bottom"
|
||||
onClose={onClose}
|
||||
className="BotCommandMenu"
|
||||
onCloseAnimationEnd={onClose}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
||||
>
|
||||
{botCommands.map((botCommand) => (
|
||||
<BotCommand
|
||||
key={botCommand.command}
|
||||
botCommand={botCommand}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
undefined,
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['sendBotCommand']),
|
||||
)(BotCommandMenu));
|
||||
15
src/components/middle/composer/BotCommandTooltip.async.tsx
Normal file
15
src/components/middle/composer/BotCommandTooltip.async.tsx
Normal file
@ -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<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const BotCommandTooltip = useModuleLoader(Bundles.Extra, 'BotCommandTooltip', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return BotCommandTooltip ? <BotCommandTooltip {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(BotCommandTooltipAsync);
|
||||
11
src/components/middle/composer/BotCommandTooltip.scss
Normal file
11
src/components/middle/composer/BotCommandTooltip.scss
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
106
src/components/middle/composer/BotCommandTooltip.tsx
Normal file
106
src/components/middle/composer/BotCommandTooltip.tsx
Normal file
@ -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<number, ApiUser>;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'sendBotCommand'>;
|
||||
|
||||
const BotCommandTooltip: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
usersById,
|
||||
isOpen,
|
||||
withUsername,
|
||||
botCommands,
|
||||
onClick,
|
||||
onClose,
|
||||
sendBotCommand,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(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 (
|
||||
<div className={className} ref={containerRef}>
|
||||
{renderedCommands && renderedCommands.map((chatBotCommand, index) => (
|
||||
<BotCommand
|
||||
key={`${chatBotCommand.botId}_${chatBotCommand.command}`}
|
||||
botCommand={chatBotCommand}
|
||||
bot={usersById[chatBotCommand.botId]}
|
||||
withAvatar
|
||||
onClick={handleSendCommand}
|
||||
focus={selectedCommandIndex === index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => ({
|
||||
usersById: global.users.byId,
|
||||
}),
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['sendBotCommand']),
|
||||
)(BotCommandTooltip));
|
||||
@ -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 {
|
||||
|
||||
@ -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<string, false | InlineBotSettings>;
|
||||
botCommands?: ApiBotCommand[] | false;
|
||||
chatBotCommands?: ApiBotCommand[];
|
||||
} & Pick<GlobalState, 'connectionState'>;
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
@ -197,6 +205,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
recentEmojis,
|
||||
inlineBots,
|
||||
isInlineBotLoading,
|
||||
botCommands,
|
||||
chatBotCommands,
|
||||
sendMessage,
|
||||
editMessage,
|
||||
saveDraft,
|
||||
@ -259,6 +269,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
||||
|
||||
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<OwnProps & StateProps & DispatchProps> = ({
|
||||
const canShowCustomSendMenu = !shouldSchedule;
|
||||
|
||||
const {
|
||||
isMentionTooltipOpen, mentionFilter,
|
||||
closeMentionTooltip, insertMention,
|
||||
mentionFilteredUsers,
|
||||
isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers,
|
||||
} = useMentionTooltip(
|
||||
!attachments.length,
|
||||
html,
|
||||
@ -313,6 +322,17 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
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<OwnProps & StateProps & DispatchProps> = ({
|
||||
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<OwnProps & StateProps & DispatchProps> = ({
|
||||
});
|
||||
}, [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<OwnProps & StateProps & DispatchProps> = ({
|
||||
setScheduledMessageArgs({ isSilent: true });
|
||||
openCalendar();
|
||||
} else {
|
||||
handleSend(true);
|
||||
void handleSend(true);
|
||||
}
|
||||
}, [handleSend, openCalendar, shouldSchedule]);
|
||||
|
||||
@ -610,7 +647,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
+ (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<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
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<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
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<OwnProps & StateProps & DispatchProps> = ({
|
||||
)}
|
||||
<MentionTooltip
|
||||
isOpen={isMentionTooltipOpen}
|
||||
filter={mentionFilter}
|
||||
onClose={closeMentionTooltip}
|
||||
onInsertUserName={insertMention}
|
||||
filteredUsers={mentionFilteredUsers}
|
||||
@ -817,6 +854,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
loadMore={loadMoreForInlineBot}
|
||||
onClose={closeInlineBotTooltip}
|
||||
/>
|
||||
<BotCommandTooltip
|
||||
isOpen={isBotCommandTooltipOpen}
|
||||
withUsername={Boolean(chatBotCommands)}
|
||||
botCommands={botTooltipCommands}
|
||||
onClick={handleBotCommandSelect}
|
||||
onClose={closeBotCommandTooltip}
|
||||
/>
|
||||
<div id="message-compose">
|
||||
<div className="svg-appendix" ref={appendixRef} />
|
||||
<ComposerEmbeddedMessage />
|
||||
@ -827,6 +871,19 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
disabled={!allowedAttachmentOptions.canAttachEmbedLinks}
|
||||
/>
|
||||
<div className="message-input-wrapper">
|
||||
{isChatWithBot && botCommands !== false && !activeVoiceRecording && !editingMessage && (
|
||||
<ResponsiveHoverButton
|
||||
className={buildClassName('bot-commands', isBotCommandMenuOpen && 'activated')}
|
||||
round
|
||||
faded
|
||||
disabled={botCommands === undefined}
|
||||
color="translucent"
|
||||
onActivate={handleActivateBotCommandMenu}
|
||||
ariaLabel="Open bot command keyboard"
|
||||
>
|
||||
<i className="icon-bot-commands-filled" />
|
||||
</ResponsiveHoverButton>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT ? (
|
||||
<Button
|
||||
className={symbolMenuButtonClassName}
|
||||
@ -842,11 +899,11 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
</Button>
|
||||
) : (
|
||||
<ResponsiveHoverButton
|
||||
className={`${isSymbolMenuOpen ? 'activated' : ''}`}
|
||||
className={isSymbolMenuOpen ? 'activated' : ''}
|
||||
round
|
||||
faded
|
||||
color="translucent"
|
||||
onActivate={openSymbolMenu}
|
||||
onActivate={handleActivateSymbolMenu}
|
||||
ariaLabel="Choose emoji, sticker or GIF"
|
||||
>
|
||||
<i className="icon-smile" />
|
||||
@ -885,7 +942,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
)}
|
||||
{botKeyboardMessageId && !activeVoiceRecording && !editingMessage && (
|
||||
<ResponsiveHoverButton
|
||||
className={`${isBotKeyboardOpen ? 'activated' : ''}`}
|
||||
className={isBotKeyboardOpen ? 'activated' : ''}
|
||||
round
|
||||
faded
|
||||
color="translucent"
|
||||
@ -897,7 +954,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
)}
|
||||
{!activeVoiceRecording && !editingMessage && (
|
||||
<ResponsiveHoverButton
|
||||
className={`${isAttachMenuOpen ? 'activated' : ''}`}
|
||||
className={isAttachMenuOpen ? 'activated' : ''}
|
||||
round
|
||||
faded
|
||||
color="translucent"
|
||||
@ -937,6 +994,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onClose={closeBotKeyboard}
|
||||
/>
|
||||
)}
|
||||
{botCommands && (
|
||||
<BotCommandMenu
|
||||
isOpen={isBotCommandMenuOpen}
|
||||
botCommands={botCommands}
|
||||
onClose={closeBotCommandMenu}
|
||||
/>
|
||||
)}
|
||||
<SymbolMenu
|
||||
isOpen={isSymbolMenuOpen}
|
||||
allowedAttachmentOptions={allowedAttachmentOptions}
|
||||
@ -1006,7 +1070,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
(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<OwnProps>(
|
||||
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, [
|
||||
|
||||
@ -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<string, any>) {
|
||||
const container = containerRef.current!;
|
||||
@ -70,52 +68,27 @@ const EmojiTooltip: FC<OwnProps> = ({
|
||||
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');
|
||||
|
||||
@ -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<GlobalActions, ('sendBotCommand' | 'openChat' | 'sendInlineBotResult')>;
|
||||
type DispatchProps = Pick<GlobalActions, ('startBot' | 'openChat' | 'sendInlineBotResult')>;
|
||||
|
||||
const InlineBotTooltip: FC<OwnProps & DispatchProps> = ({
|
||||
isOpen,
|
||||
@ -54,13 +53,12 @@ const InlineBotTooltip: FC<OwnProps & DispatchProps> = ({
|
||||
loadMore,
|
||||
onClose,
|
||||
openChat,
|
||||
sendBotCommand,
|
||||
startBot,
|
||||
onSelectResult,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(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<OwnProps & DispatchProps> = ({
|
||||
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<OwnProps & DispatchProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
undefined,
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
'sendBotCommand', 'openChat', 'sendInlineBotResult',
|
||||
'startBot', 'openChat', 'sendInlineBotResult',
|
||||
]),
|
||||
)(InlineBotTooltip));
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
isOpen,
|
||||
filter,
|
||||
onClose,
|
||||
onInsertUserName,
|
||||
usersById,
|
||||
@ -37,21 +34,6 @@ const MentionTooltip: FC<OwnProps> = ({
|
||||
const containerRef = useRef<HTMLDivElement>(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<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
}
|
||||
}, [filteredUsers, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedMentionIndex(0);
|
||||
}, [filter]);
|
||||
|
||||
useEffect(() => {
|
||||
setTooltipItemVisible('.chat-item-clickable', selectedMentionIndex, containerRef);
|
||||
}, [selectedMentionIndex]);
|
||||
|
||||
const prevChatMembers = usePrevious(
|
||||
filteredUsers && filteredUsers.length
|
||||
? filteredUsers
|
||||
|
||||
@ -90,6 +90,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.Button.bot-commands ~ & {
|
||||
.is-pointer-env & > .backdrop {
|
||||
left: 3rem;
|
||||
width: 3.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: 0;
|
||||
width: var(--symbol-menu-width);
|
||||
|
||||
67
src/components/middle/composer/hooks/useBotCommandTooltip.ts
Normal file
67
src/components/middle/composer/hooks/useBotCommandTooltip.ts
Normal file
@ -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<ApiBotCommand[] | undefined>();
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -35,7 +35,6 @@ export default function useMentionTooltip(
|
||||
usersById?: Record<number, ApiUser>,
|
||||
) {
|
||||
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
||||
const [currentFilter, setCurrentFilter] = useState('');
|
||||
const [usersToMention, setUsersToMention] = useState<ApiUser[] | undefined>();
|
||||
|
||||
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,
|
||||
|
||||
@ -100,7 +100,6 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
||||
[error.field]: error.message,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}, [error, paymentDispatch]);
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const canUpdate = Boolean(
|
||||
(privacyType === 'public' && username && isUsernameAvailable)
|
||||
|| (privacyType === 'private' && isPublic)
|
||||
|| (privacyType === 'private' && isPublic),
|
||||
);
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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' |
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<string> = Vector<LangPackString>;
|
||||
langpack.getLanguages#42c6978f lang_pack:string = Vector<LangPackLanguage>;
|
||||
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
|
||||
// LAYER 128
|
||||
// LAYER 128
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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";
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user