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 { Api as GramJs } from '../../../lib/gramjs';
|
||||||
import {
|
import {
|
||||||
ApiBotInlineMediaResult, ApiBotInlineResult, ApiInlineResultType, ApiWebDocument,
|
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm, ApiInlineResultType, ApiWebDocument,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { pick } from '../../../util/iteratees';
|
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 {
|
function buildApiWebDocument(document?: GramJs.TypeWebDocument): ApiWebDocument | undefined {
|
||||||
return document ? pick(document, ['url', 'mimeType']) : undefined;
|
return document ? pick(document, ['url', 'mimeType']) : undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
ApiChat,
|
ApiChat,
|
||||||
ApiChatAdminRights,
|
ApiChatAdminRights,
|
||||||
ApiChatBannedRights,
|
ApiChatBannedRights,
|
||||||
|
ApiBotCommand,
|
||||||
ApiChatFolder,
|
ApiChatFolder,
|
||||||
ApiChatMember,
|
ApiChatMember,
|
||||||
ApiRestrictionReason,
|
ApiRestrictionReason,
|
||||||
@ -376,3 +377,14 @@ export function buildApiChatFolderFromSuggested({
|
|||||||
description,
|
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 { 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 {
|
export function buildApiUserFromFull(mtpUserFull: GramJs.UserFull): ApiUser {
|
||||||
const {
|
const {
|
||||||
@ -14,6 +16,7 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.UserFull): ApiUser {
|
|||||||
pinnedMessageId: pinnedMsgId,
|
pinnedMessageId: pinnedMsgId,
|
||||||
isBlocked: Boolean(blocked),
|
isBlocked: Boolean(blocked),
|
||||||
...(botInfo && { botDescription: botInfo.description }),
|
...(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' };
|
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 BigInt from 'big-integer';
|
||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import { ApiBotInlineSwitchPm, ApiChat, ApiUser } from '../../types';
|
import { ApiChat, ApiUser } from '../../types';
|
||||||
|
|
||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
import { buildInputPeer, calculateResultHash, generateRandomBigInt } from '../gramjsBuilders';
|
import { buildInputPeer, calculateResultHash, generateRandomBigInt } from '../gramjsBuilders';
|
||||||
import { buildApiUser } from '../apiBuilders/users';
|
import { buildApiUser } from '../apiBuilders/users';
|
||||||
import { buildApiBotInlineMediaResult, buildApiBotInlineResult } from '../apiBuilders/bots';
|
import { buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm } from '../apiBuilders/bots';
|
||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { pick } from '../../../util/iteratees';
|
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
}
|
}
|
||||||
@ -92,7 +91,7 @@ export async function fetchInlineBotResults({
|
|||||||
isGallery: Boolean(result.gallery),
|
isGallery: Boolean(result.gallery),
|
||||||
help: bot.botPlaceholder,
|
help: bot.botPlaceholder,
|
||||||
nextOffset: getInlineBotResultsNextOffset(bot.username, result.nextOffset),
|
nextOffset: getInlineBotResultsNextOffset(bot.username, result.nextOffset),
|
||||||
switchPm: buildSwitchPm(result.switchPm),
|
switchPm: buildBotSwitchPm(result.switchPm),
|
||||||
users: result.users.map(buildApiUser).filter<ApiUser>(Boolean as any),
|
users: result.users.map(buildApiUser).filter<ApiUser>(Boolean as any),
|
||||||
results: processInlineBotResult(String(result.queryId), result.results),
|
results: processInlineBotResult(String(result.queryId), result.results),
|
||||||
};
|
};
|
||||||
@ -118,8 +117,20 @@ export async function sendInlineBotResult({
|
|||||||
}), true);
|
}), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSwitchPm(switchPm?: GramJs.InlineBotSwitchPM) {
|
export async function startBot({
|
||||||
return switchPm ? pick(switchPm, ['text', 'startParam']) as ApiBotInlineSwitchPm : undefined;
|
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[]) {
|
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
getApiChatIdFromMtpPeer,
|
getApiChatIdFromMtpPeer,
|
||||||
buildApiChatFolder,
|
buildApiChatFolder,
|
||||||
buildApiChatFolderFromSuggested,
|
buildApiChatFolderFromSuggested,
|
||||||
|
buildApiChatBotCommands,
|
||||||
} from '../apiBuilders/chats';
|
} from '../apiBuilders/chats';
|
||||||
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
||||||
import { buildApiUser } from '../apiBuilders/users';
|
import { buildApiUser } from '../apiBuilders/users';
|
||||||
@ -317,10 +318,12 @@ async function getFullChatInfo(chatId: number): Promise<{
|
|||||||
about,
|
about,
|
||||||
participants,
|
participants,
|
||||||
exportedInvite,
|
exportedInvite,
|
||||||
|
botInfo,
|
||||||
} = result.fullChat;
|
} = result.fullChat;
|
||||||
|
|
||||||
const members = buildChatMembers(participants);
|
const members = buildChatMembers(participants);
|
||||||
const adminMembers = members ? members.filter(({ isAdmin, isOwner }) => isAdmin || isOwner) : undefined;
|
const adminMembers = members ? members.filter(({ isAdmin, isOwner }) => isAdmin || isOwner) : undefined;
|
||||||
|
const botCommands = botInfo ? buildApiChatBotCommands(botInfo) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fullInfo: {
|
fullInfo: {
|
||||||
@ -328,6 +331,7 @@ async function getFullChatInfo(chatId: number): Promise<{
|
|||||||
members,
|
members,
|
||||||
adminMembers,
|
adminMembers,
|
||||||
canViewMembers: true,
|
canViewMembers: true,
|
||||||
|
botCommands,
|
||||||
...(exportedInvite && {
|
...(exportedInvite && {
|
||||||
inviteLink: exportedInvite.link,
|
inviteLink: exportedInvite.link,
|
||||||
}),
|
}),
|
||||||
@ -361,6 +365,7 @@ async function getFullChannelInfo(
|
|||||||
linkedChatId,
|
linkedChatId,
|
||||||
hiddenPrehistory,
|
hiddenPrehistory,
|
||||||
call,
|
call,
|
||||||
|
botInfo,
|
||||||
} = result.fullChat;
|
} = result.fullChat;
|
||||||
|
|
||||||
const inviteLink = exportedInvite instanceof GramJs.ChatInviteExported
|
const inviteLink = exportedInvite instanceof GramJs.ChatInviteExported
|
||||||
@ -374,6 +379,7 @@ async function getFullChannelInfo(
|
|||||||
const { members: adminMembers, users: adminUsers } = (
|
const { members: adminMembers, users: adminUsers } = (
|
||||||
canViewParticipants && adminRights && await fetchMembers(id, accessHash, 'admin')
|
canViewParticipants && adminRights && await fetchMembers(id, accessHash, 'admin')
|
||||||
) || {};
|
) || {};
|
||||||
|
const botCommands = botInfo ? buildApiChatBotCommands(botInfo) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fullInfo: {
|
fullInfo: {
|
||||||
@ -395,6 +401,7 @@ async function getFullChannelInfo(
|
|||||||
adminMembers,
|
adminMembers,
|
||||||
groupCallId: call ? call.id.toString() : undefined,
|
groupCallId: call ? call.id.toString() : undefined,
|
||||||
linkedChatId: linkedChatId ? getApiChatIdFromMtpPeer({ chatId: linkedChatId } as GramJs.TypePeer) : undefined,
|
linkedChatId: linkedChatId ? getApiChatIdFromMtpPeer({ chatId: linkedChatId } as GramJs.TypePeer) : undefined,
|
||||||
|
botCommands,
|
||||||
},
|
},
|
||||||
users: [...(users || []), ...(bannedUsers || []), ...(adminUsers || [])],
|
users: [...(users || []), ...(bannedUsers || []), ...(adminUsers || [])],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export {
|
|||||||
} from './twoFaSettings';
|
} from './twoFaSettings';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult,
|
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
||||||
} from './bots';
|
} from './bots';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -38,3 +38,9 @@ export interface ApiBotInlineSwitchPm {
|
|||||||
text: string;
|
text: string;
|
||||||
startParam: string;
|
startParam: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiBotCommand {
|
||||||
|
botId: number;
|
||||||
|
command: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ApiMessage, ApiPhoto } from './messages';
|
import { ApiMessage, ApiPhoto } from './messages';
|
||||||
|
import { ApiBotCommand } from './bots';
|
||||||
|
|
||||||
type ApiChatType = (
|
type ApiChatType = (
|
||||||
'chatTypePrivate' | 'chatTypeSecret' |
|
'chatTypePrivate' | 'chatTypeSecret' |
|
||||||
@ -96,6 +97,7 @@ export interface ApiChatFullInfo {
|
|||||||
maxMessageId?: number;
|
maxMessageId?: number;
|
||||||
};
|
};
|
||||||
linkedChatId?: number;
|
linkedChatId?: number;
|
||||||
|
botCommands?: ApiBotCommand[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiChatMember {
|
export interface ApiChatMember {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ApiPhoto } from './messages';
|
import { ApiPhoto } from './messages';
|
||||||
|
import { ApiBotCommand } from './bots';
|
||||||
|
|
||||||
export interface ApiUser {
|
export interface ApiUser {
|
||||||
id: number;
|
id: number;
|
||||||
@ -28,6 +29,7 @@ export interface ApiUserFullInfo {
|
|||||||
commonChatsCount?: number;
|
commonChatsCount?: number;
|
||||||
botDescription?: string;
|
botDescription?: string;
|
||||||
pinnedMessageId?: number;
|
pinnedMessageId?: number;
|
||||||
|
botCommands?: ApiBotCommand[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiUserType = 'userTypeBot' | 'userTypeRegular' | 'userTypeDeleted' | 'userTypeUnknown';
|
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 PollModal } from '../components/middle/composer/PollModal';
|
||||||
export { default as SymbolMenu } from '../components/middle/composer/SymbolMenu';
|
export { default as SymbolMenu } from '../components/middle/composer/SymbolMenu';
|
||||||
export { default as AttachMenu } from '../components/middle/composer/AttachMenu';
|
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 MentionTooltip } from '../components/middle/composer/MentionTooltip';
|
||||||
export { default as StickerTooltip } from '../components/middle/composer/StickerTooltip';
|
export { default as StickerTooltip } from '../components/middle/composer/StickerTooltip';
|
||||||
export { default as CustomSendMenu } from '../components/middle/composer/CustomSendMenu';
|
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
|
&& !messageIds && !chat.unreadCount && !focusingId && lastMessage && !lastMessage.groupedId
|
||||||
);
|
);
|
||||||
|
|
||||||
const bot = selectChatBot(global, chatId);
|
const chatBot = selectChatBot(global, chatId)!;
|
||||||
let botDescription: string | undefined;
|
let botDescription: string | undefined;
|
||||||
if (selectIsChatBotNotStarted(global, chatId)) {
|
if (selectIsChatBotNotStarted(global, chatId)) {
|
||||||
const chatBot = selectChatBot(global, chatId)!;
|
|
||||||
if (chatBot.fullInfo) {
|
if (chatBot.fullInfo) {
|
||||||
botDescription = chatBot.fullInfo.botDescription || 'NoMessages';
|
botDescription = chatBot.fullInfo.botDescription || 'NoMessages';
|
||||||
} else {
|
} else {
|
||||||
@ -565,7 +564,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isGroupChat: isChatGroup(chat),
|
isGroupChat: isChatGroup(chat),
|
||||||
isCreator: chat.isCreator,
|
isCreator: chat.isCreator,
|
||||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||||
isBot: Boolean(bot),
|
isBot: Boolean(chatBot),
|
||||||
messageIds,
|
messageIds,
|
||||||
messagesById,
|
messagesById,
|
||||||
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
||||||
|
|||||||
@ -67,9 +67,7 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isMentionTooltipOpen, mentionFilter,
|
isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers,
|
||||||
closeMentionTooltip, insertMention,
|
|
||||||
mentionFilteredUsers,
|
|
||||||
} = useMentionTooltip(
|
} = useMentionTooltip(
|
||||||
isOpen,
|
isOpen,
|
||||||
caption,
|
caption,
|
||||||
@ -227,7 +225,6 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
<MentionTooltip
|
<MentionTooltip
|
||||||
isOpen={isMentionTooltipOpen}
|
isOpen={isMentionTooltipOpen}
|
||||||
onClose={closeMentionTooltip}
|
onClose={closeMentionTooltip}
|
||||||
filter={mentionFilter}
|
|
||||||
onInsertUserName={insertMention}
|
onInsertUserName={insertMention}
|
||||||
filteredUsers={mentionFilteredUsers}
|
filteredUsers={mentionFilteredUsers}
|
||||||
usersById={usersById}
|
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 {
|
> .Spinner {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
--spinner-size: 1.5rem;
|
--spinner-size: 1.5rem;
|
||||||
|
margin-right: -.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .Button {
|
> .Button {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: none !important;
|
background: none !important;
|
||||||
width: 3.375rem;
|
width: 3.5rem;
|
||||||
height: 3.375rem;
|
height: 3.5rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.625rem;
|
padding: 0.625rem;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
@ -210,7 +211,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
+ .Button {
|
+ .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 {
|
&.scheduled-button i::after {
|
||||||
|
|||||||
@ -17,14 +17,16 @@ import {
|
|||||||
ApiChatMember,
|
ApiChatMember,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
MAIN_THREAD_ID,
|
MAIN_THREAD_ID,
|
||||||
|
ApiBotCommand,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { InlineBotSettings } from '../../../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 { IS_VOICE_RECORDING_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT, IS_IOS } from '../../../util/environment';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
selectIsChatWithBot,
|
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsInSelectMode,
|
selectIsInSelectMode,
|
||||||
selectNewestMessageWithBotKeyboardButtons,
|
selectNewestMessageWithBotKeyboardButtons,
|
||||||
@ -32,6 +34,7 @@ import {
|
|||||||
selectScheduledIds,
|
selectScheduledIds,
|
||||||
selectEditingMessage,
|
selectEditingMessage,
|
||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
|
selectChatBot,
|
||||||
selectChatUser,
|
selectChatUser,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
} from '../../../modules/selectors';
|
} from '../../../modules/selectors';
|
||||||
@ -50,8 +53,10 @@ import insertHtmlInSelection from '../../../util/insertHtmlInSelection';
|
|||||||
import deleteLastCharacterOutsideSelection from '../../../util/deleteLastCharacterOutsideSelection';
|
import deleteLastCharacterOutsideSelection from '../../../util/deleteLastCharacterOutsideSelection';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import windowSize from '../../../util/windowSize';
|
||||||
import { isSelectionInsideInput } from './helpers/selection';
|
import { isSelectionInsideInput } from './helpers/selection';
|
||||||
import applyIosAutoCapitalizationFix from './helpers/applyIosAutoCapitalizationFix';
|
import applyIosAutoCapitalizationFix from './helpers/applyIosAutoCapitalizationFix';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useVoiceRecording from './hooks/useVoiceRecording';
|
import useVoiceRecording from './hooks/useVoiceRecording';
|
||||||
@ -65,8 +70,7 @@ import useMentionTooltip from './hooks/useMentionTooltip';
|
|||||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useInlineBotTooltip from './hooks/useInlineBotTooltip';
|
import useInlineBotTooltip from './hooks/useInlineBotTooltip';
|
||||||
import windowSize from '../../../util/windowSize';
|
import useBotCommandTooltip from './hooks/useBotCommandTooltip';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
|
||||||
|
|
||||||
import DeleteMessageModal from '../../common/DeleteMessageModal.async';
|
import DeleteMessageModal from '../../common/DeleteMessageModal.async';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -79,10 +83,12 @@ import MentionTooltip from './MentionTooltip.async';
|
|||||||
import CustomSendMenu from './CustomSendMenu.async';
|
import CustomSendMenu from './CustomSendMenu.async';
|
||||||
import StickerTooltip from './StickerTooltip.async';
|
import StickerTooltip from './StickerTooltip.async';
|
||||||
import EmojiTooltip from './EmojiTooltip.async';
|
import EmojiTooltip from './EmojiTooltip.async';
|
||||||
|
import BotCommandTooltip from './BotCommandTooltip.async';
|
||||||
import BotKeyboardMenu from './BotKeyboardMenu';
|
import BotKeyboardMenu from './BotKeyboardMenu';
|
||||||
import MessageInput from './MessageInput';
|
import MessageInput from './MessageInput';
|
||||||
import ComposerEmbeddedMessage from './ComposerEmbeddedMessage';
|
import ComposerEmbeddedMessage from './ComposerEmbeddedMessage';
|
||||||
import AttachmentModal from './AttachmentModal.async';
|
import AttachmentModal from './AttachmentModal.async';
|
||||||
|
import BotCommandMenu from './BotCommandMenu.async';
|
||||||
import PollModal from './PollModal.async';
|
import PollModal from './PollModal.async';
|
||||||
import DropArea, { DropAreaState } from './DropArea.async';
|
import DropArea, { DropAreaState } from './DropArea.async';
|
||||||
import WebPagePreview from './WebPagePreview';
|
import WebPagePreview from './WebPagePreview';
|
||||||
@ -133,6 +139,8 @@ type StateProps = {
|
|||||||
topInlineBotIds?: number[];
|
topInlineBotIds?: number[];
|
||||||
isInlineBotLoading: boolean;
|
isInlineBotLoading: boolean;
|
||||||
inlineBots?: Record<string, false | InlineBotSettings>;
|
inlineBots?: Record<string, false | InlineBotSettings>;
|
||||||
|
botCommands?: ApiBotCommand[] | false;
|
||||||
|
chatBotCommands?: ApiBotCommand[];
|
||||||
} & Pick<GlobalState, 'connectionState'>;
|
} & Pick<GlobalState, 'connectionState'>;
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
@ -197,6 +205,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
recentEmojis,
|
recentEmojis,
|
||||||
inlineBots,
|
inlineBots,
|
||||||
isInlineBotLoading,
|
isInlineBotLoading,
|
||||||
|
botCommands,
|
||||||
|
chatBotCommands,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
editMessage,
|
editMessage,
|
||||||
saveDraft,
|
saveDraft,
|
||||||
@ -259,6 +269,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
||||||
|
|
||||||
const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag();
|
const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag();
|
||||||
|
const [isBotCommandMenuOpen, openBotCommandMenu, closeBotCommandMenu] = useFlag();
|
||||||
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
|
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
|
||||||
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
||||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||||
@ -283,9 +294,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const canShowCustomSendMenu = !shouldSchedule;
|
const canShowCustomSendMenu = !shouldSchedule;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isMentionTooltipOpen, mentionFilter,
|
isMentionTooltipOpen, closeMentionTooltip, insertMention, mentionFilteredUsers,
|
||||||
closeMentionTooltip, insertMention,
|
|
||||||
mentionFilteredUsers,
|
|
||||||
} = useMentionTooltip(
|
} = useMentionTooltip(
|
||||||
!attachments.length,
|
!attachments.length,
|
||||||
html,
|
html,
|
||||||
@ -313,6 +322,17 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
inlineBots,
|
inlineBots,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen: isBotCommandTooltipOpen,
|
||||||
|
close: closeBotCommandTooltip,
|
||||||
|
filteredBotCommands: botTooltipCommands,
|
||||||
|
} = useBotCommandTooltip(
|
||||||
|
Boolean((botCommands && botCommands.length) || (chatBotCommands && chatBotCommands.length)),
|
||||||
|
html,
|
||||||
|
botCommands,
|
||||||
|
chatBotCommands,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen: isCustomSendMenuOpen,
|
isContextMenuOpen: isCustomSendMenuOpen,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
@ -532,6 +552,16 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
resetComposer, stopRecordingVoice, showDialog, slowMode, isAdmin, sendMessage, forwardMessages, lang,
|
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) => {
|
const handleStickerSelect = useCallback((sticker: ApiSticker, shouldPreserveInput = false) => {
|
||||||
sticker = {
|
sticker = {
|
||||||
...sticker,
|
...sticker,
|
||||||
@ -582,6 +612,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
});
|
});
|
||||||
}, [chatId, clearDraft, connectionState, resetComposer, sendInlineBotResult]);
|
}, [chatId, clearDraft, connectionState, resetComposer, sendInlineBotResult]);
|
||||||
|
|
||||||
|
const handleBotCommandSelect = useCallback(() => {
|
||||||
|
clearDraft({ chatId, localOnly: true });
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
resetComposer();
|
||||||
|
});
|
||||||
|
}, [chatId, clearDraft, resetComposer]);
|
||||||
|
|
||||||
const handlePollSend = useCallback((poll: ApiNewPoll) => {
|
const handlePollSend = useCallback((poll: ApiNewPoll) => {
|
||||||
if (shouldSchedule) {
|
if (shouldSchedule) {
|
||||||
setScheduledMessageArgs({ poll });
|
setScheduledMessageArgs({ poll });
|
||||||
@ -598,7 +635,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
setScheduledMessageArgs({ isSilent: true });
|
setScheduledMessageArgs({ isSilent: true });
|
||||||
openCalendar();
|
openCalendar();
|
||||||
} else {
|
} else {
|
||||||
handleSend(true);
|
void handleSend(true);
|
||||||
}
|
}
|
||||||
}, [handleSend, openCalendar, shouldSchedule]);
|
}, [handleSend, openCalendar, shouldSchedule]);
|
||||||
|
|
||||||
@ -610,7 +647,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
+ (isWhenOnline ? 0 : serverTimeOffset);
|
+ (isWhenOnline ? 0 : serverTimeOffset);
|
||||||
|
|
||||||
if (!scheduledMessageArgs || Object.keys(restArgs).length === 0) {
|
if (!scheduledMessageArgs || Object.keys(restArgs).length === 0) {
|
||||||
handleSend(!!isSilent, scheduledAt);
|
void handleSend(!!isSilent, scheduledAt);
|
||||||
} else {
|
} else {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
...scheduledMessageArgs,
|
...scheduledMessageArgs,
|
||||||
@ -652,9 +689,10 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
messageInput.blur();
|
messageInput.blur();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
closeBotCommandMenu();
|
||||||
openSymbolMenu();
|
openSymbolMenu();
|
||||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||||
}, [openSymbolMenu]);
|
}, [openSymbolMenu, closeBotCommandMenu]);
|
||||||
|
|
||||||
const handleAllScheduledClick = useCallback(() => {
|
const handleAllScheduledClick = useCallback(() => {
|
||||||
openChat({ id: chatId, threadId, type: 'scheduled' });
|
openChat({ id: chatId, threadId, type: 'scheduled' });
|
||||||
@ -687,14 +725,14 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
openCalendar();
|
openCalendar();
|
||||||
} else {
|
} else {
|
||||||
handleSend();
|
void handleSend();
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MainButtonState.Record:
|
case MainButtonState.Record:
|
||||||
startRecordingVoice();
|
void startRecordingVoice();
|
||||||
break;
|
break;
|
||||||
case MainButtonState.Edit:
|
case MainButtonState.Edit:
|
||||||
handleEditComplete();
|
handleEditComplete();
|
||||||
@ -800,7 +838,6 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
)}
|
)}
|
||||||
<MentionTooltip
|
<MentionTooltip
|
||||||
isOpen={isMentionTooltipOpen}
|
isOpen={isMentionTooltipOpen}
|
||||||
filter={mentionFilter}
|
|
||||||
onClose={closeMentionTooltip}
|
onClose={closeMentionTooltip}
|
||||||
onInsertUserName={insertMention}
|
onInsertUserName={insertMention}
|
||||||
filteredUsers={mentionFilteredUsers}
|
filteredUsers={mentionFilteredUsers}
|
||||||
@ -817,6 +854,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
loadMore={loadMoreForInlineBot}
|
loadMore={loadMoreForInlineBot}
|
||||||
onClose={closeInlineBotTooltip}
|
onClose={closeInlineBotTooltip}
|
||||||
/>
|
/>
|
||||||
|
<BotCommandTooltip
|
||||||
|
isOpen={isBotCommandTooltipOpen}
|
||||||
|
withUsername={Boolean(chatBotCommands)}
|
||||||
|
botCommands={botTooltipCommands}
|
||||||
|
onClick={handleBotCommandSelect}
|
||||||
|
onClose={closeBotCommandTooltip}
|
||||||
|
/>
|
||||||
<div id="message-compose">
|
<div id="message-compose">
|
||||||
<div className="svg-appendix" ref={appendixRef} />
|
<div className="svg-appendix" ref={appendixRef} />
|
||||||
<ComposerEmbeddedMessage />
|
<ComposerEmbeddedMessage />
|
||||||
@ -827,6 +871,19 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
disabled={!allowedAttachmentOptions.canAttachEmbedLinks}
|
disabled={!allowedAttachmentOptions.canAttachEmbedLinks}
|
||||||
/>
|
/>
|
||||||
<div className="message-input-wrapper">
|
<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 ? (
|
{IS_SINGLE_COLUMN_LAYOUT ? (
|
||||||
<Button
|
<Button
|
||||||
className={symbolMenuButtonClassName}
|
className={symbolMenuButtonClassName}
|
||||||
@ -842,11 +899,11 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<ResponsiveHoverButton
|
<ResponsiveHoverButton
|
||||||
className={`${isSymbolMenuOpen ? 'activated' : ''}`}
|
className={isSymbolMenuOpen ? 'activated' : ''}
|
||||||
round
|
round
|
||||||
faded
|
faded
|
||||||
color="translucent"
|
color="translucent"
|
||||||
onActivate={openSymbolMenu}
|
onActivate={handleActivateSymbolMenu}
|
||||||
ariaLabel="Choose emoji, sticker or GIF"
|
ariaLabel="Choose emoji, sticker or GIF"
|
||||||
>
|
>
|
||||||
<i className="icon-smile" />
|
<i className="icon-smile" />
|
||||||
@ -885,7 +942,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
)}
|
)}
|
||||||
{botKeyboardMessageId && !activeVoiceRecording && !editingMessage && (
|
{botKeyboardMessageId && !activeVoiceRecording && !editingMessage && (
|
||||||
<ResponsiveHoverButton
|
<ResponsiveHoverButton
|
||||||
className={`${isBotKeyboardOpen ? 'activated' : ''}`}
|
className={isBotKeyboardOpen ? 'activated' : ''}
|
||||||
round
|
round
|
||||||
faded
|
faded
|
||||||
color="translucent"
|
color="translucent"
|
||||||
@ -897,7 +954,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
)}
|
)}
|
||||||
{!activeVoiceRecording && !editingMessage && (
|
{!activeVoiceRecording && !editingMessage && (
|
||||||
<ResponsiveHoverButton
|
<ResponsiveHoverButton
|
||||||
className={`${isAttachMenuOpen ? 'activated' : ''}`}
|
className={isAttachMenuOpen ? 'activated' : ''}
|
||||||
round
|
round
|
||||||
faded
|
faded
|
||||||
color="translucent"
|
color="translucent"
|
||||||
@ -937,6 +994,13 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
onClose={closeBotKeyboard}
|
onClose={closeBotKeyboard}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{botCommands && (
|
||||||
|
<BotCommandMenu
|
||||||
|
isOpen={isBotCommandMenuOpen}
|
||||||
|
botCommands={botCommands}
|
||||||
|
onClose={closeBotCommandMenu}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SymbolMenu
|
<SymbolMenu
|
||||||
isOpen={isSymbolMenuOpen}
|
isOpen={isSymbolMenuOpen}
|
||||||
allowedAttachmentOptions={allowedAttachmentOptions}
|
allowedAttachmentOptions={allowedAttachmentOptions}
|
||||||
@ -1006,7 +1070,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { chatId, threadId, messageListType }): StateProps => {
|
(global, { chatId, threadId, messageListType }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const chatUser = chat && selectChatUser(global, chat);
|
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 isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
const messageWithActualBotKeyboard = isChatWithBot && selectNewestMessageWithBotKeyboardButtons(global, chatId);
|
const messageWithActualBotKeyboard = isChatWithBot && selectNewestMessageWithBotKeyboardButtons(global, chatId);
|
||||||
const scheduledIds = selectScheduledIds(global, chatId);
|
const scheduledIds = selectScheduledIds(global, chatId);
|
||||||
@ -1055,6 +1120,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
serverTimeOffset: global.serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
inlineBots: global.inlineBots.byUsername,
|
inlineBots: global.inlineBots.byUsername,
|
||||||
isInlineBotLoading: global.inlineBots.isLoading,
|
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, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useEffect, useRef, useState,
|
FC, memo, useCallback, useEffect, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import cycleRestrict from '../../../util/cycleRestrict';
|
|
||||||
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
|
||||||
import findInViewport from '../../../util/findInViewport';
|
import findInViewport from '../../../util/findInViewport';
|
||||||
import isFullyVisible from '../../../util/isFullyVisible';
|
import isFullyVisible from '../../../util/isFullyVisible';
|
||||||
import fastSmoothScrollHorizontal from '../../../util/fastSmoothScrollHorizontal';
|
import fastSmoothScrollHorizontal from '../../../util/fastSmoothScrollHorizontal';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation';
|
import usePrevDuringAnimation from '../../../hooks/usePrevDuringAnimation';
|
||||||
|
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
||||||
|
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import EmojiButton from './EmojiButton';
|
import EmojiButton from './EmojiButton';
|
||||||
@ -20,7 +19,6 @@ import './EmojiTooltip.scss';
|
|||||||
const VIEWPORT_MARGIN = 8;
|
const VIEWPORT_MARGIN = 8;
|
||||||
const EMOJI_BUTTON_WIDTH = 44;
|
const EMOJI_BUTTON_WIDTH = 44;
|
||||||
const CLOSE_DURATION = 350;
|
const CLOSE_DURATION = 350;
|
||||||
const NO_EMOJI_SELECTED_INDEX = -1;
|
|
||||||
|
|
||||||
function setItemVisible(index: number, containerRef: Record<string, any>) {
|
function setItemVisible(index: number, containerRef: Record<string, any>) {
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
@ -70,52 +68,27 @@ const EmojiTooltip: FC<OwnProps> = ({
|
|||||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
||||||
const listEmojis: Emoji[] = usePrevDuringAnimation(emojis.length ? emojis : undefined, CLOSE_DURATION) || [];
|
const listEmojis: Emoji[] = usePrevDuringAnimation(emojis.length ? emojis : undefined, CLOSE_DURATION) || [];
|
||||||
|
|
||||||
const [selectedIndex, setSelectedIndex] = useState(NO_EMOJI_SELECTED_INDEX);
|
const handleSelectEmoji = useCallback((emoji: Emoji) => {
|
||||||
|
onEmojiSelect(emoji.native);
|
||||||
useEffect(() => {
|
addRecentEmoji({ emoji: emoji.id });
|
||||||
setSelectedIndex(0);
|
}, [addRecentEmoji, onEmojiSelect]);
|
||||||
}, [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 handleClick = useCallback((native: string, id: string) => {
|
const handleClick = useCallback((native: string, id: string) => {
|
||||||
onEmojiSelect(native);
|
onEmojiSelect(native);
|
||||||
addRecentEmoji({ emoji: id });
|
addRecentEmoji({ emoji: id });
|
||||||
}, [addRecentEmoji, onEmojiSelect]);
|
}, [addRecentEmoji, onEmojiSelect]);
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureKeyboardListeners({
|
const selectedIndex = useKeyboardNavigation({
|
||||||
onEsc: onClose,
|
isActive: isOpen,
|
||||||
onLeft: (e: KeyboardEvent) => handleArrowKey(-1, e),
|
isHorizontal: true,
|
||||||
onRight: (e: KeyboardEvent) => handleArrowKey(1, e),
|
items: emojis,
|
||||||
onEnter: handleSelectEmoji,
|
onSelect: handleSelectEmoji,
|
||||||
}) : undefined), [handleArrowKey, handleSelectEmoji, isOpen, onClose]);
|
onClose,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setItemVisible(selectedIndex, containerRef);
|
||||||
|
}, [selectedIndex]);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
document.body.classList.add('no-select');
|
document.body.classList.add('no-select');
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useEffect, useRef, useState,
|
FC, memo, useCallback, useEffect, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
@ -11,13 +11,12 @@ import { LoadMoreDirection } from '../../../types';
|
|||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
|
||||||
import cycleRestrict from '../../../util/cycleRestrict';
|
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
|
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
||||||
|
|
||||||
import MediaResult from './inlineResults/MediaResult';
|
import MediaResult from './inlineResults/MediaResult';
|
||||||
import ArticleResult from './inlineResults/ArticleResult';
|
import ArticleResult from './inlineResults/ArticleResult';
|
||||||
@ -43,7 +42,7 @@ export type OwnProps = {
|
|||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, ('sendBotCommand' | 'openChat' | 'sendInlineBotResult')>;
|
type DispatchProps = Pick<GlobalActions, ('startBot' | 'openChat' | 'sendInlineBotResult')>;
|
||||||
|
|
||||||
const InlineBotTooltip: FC<OwnProps & DispatchProps> = ({
|
const InlineBotTooltip: FC<OwnProps & DispatchProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -54,13 +53,12 @@ const InlineBotTooltip: FC<OwnProps & DispatchProps> = ({
|
|||||||
loadMore,
|
loadMore,
|
||||||
onClose,
|
onClose,
|
||||||
openChat,
|
openChat,
|
||||||
sendBotCommand,
|
startBot,
|
||||||
onSelectResult,
|
onSelectResult,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
||||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
|
||||||
const {
|
const {
|
||||||
observe: observeIntersection,
|
observe: observeIntersection,
|
||||||
} = useIntersectionObserver({
|
} = useIntersectionObserver({
|
||||||
@ -69,58 +67,29 @@ const InlineBotTooltip: FC<OwnProps & DispatchProps> = ({
|
|||||||
isDisabled: !isOpen,
|
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 }) => {
|
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||||
if (direction === LoadMoreDirection.Backwards) {
|
if (direction === LoadMoreDirection.Backwards) {
|
||||||
runThrottled(loadMore);
|
runThrottled(loadMore);
|
||||||
}
|
}
|
||||||
}, [loadMore]);
|
}, [loadMore]);
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureKeyboardListeners({
|
const selectedIndex = useKeyboardNavigation({
|
||||||
onEsc: onClose,
|
isActive: isOpen,
|
||||||
onUp: (e: KeyboardEvent) => handleArrowKey(-1, e),
|
shouldRemoveSelectionOnReset: isGallery,
|
||||||
onDown: (e: KeyboardEvent) => handleArrowKey(1, e),
|
noArrowNavigation: isGallery,
|
||||||
onEnter: handleSelectInlineBotResult,
|
items: inlineBotResults,
|
||||||
}) : undefined), [handleArrowKey, handleSelectInlineBotResult, isGallery, isOpen, onClose]);
|
onSelect: onSelectResult,
|
||||||
|
onClose,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTooltipItemVisible('.chat-item-clickable', selectedIndex, containerRef);
|
||||||
|
}, [selectedIndex]);
|
||||||
|
|
||||||
const handleSendPm = useCallback(() => {
|
const handleSendPm = useCallback(() => {
|
||||||
openChat({ id: botId });
|
openChat({ id: botId });
|
||||||
sendBotCommand({ chatId: botId, command: `/start ${switchPm!.startParam}` });
|
startBot({ botId, param: switchPm!.startParam });
|
||||||
}, [botId, openChat, sendBotCommand, switchPm]);
|
}, [botId, openChat, startBot, switchPm]);
|
||||||
|
|
||||||
const prevInlineBotResults = usePrevious(
|
const prevInlineBotResults = usePrevious(
|
||||||
inlineBotResults && inlineBotResults.length
|
inlineBotResults && inlineBotResults.length
|
||||||
@ -230,6 +199,6 @@ const InlineBotTooltip: FC<OwnProps & DispatchProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
undefined,
|
undefined,
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
'sendBotCommand', 'openChat', 'sendInlineBotResult',
|
'startBot', 'openChat', 'sendInlineBotResult',
|
||||||
]),
|
]),
|
||||||
)(InlineBotTooltip));
|
)(InlineBotTooltip));
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useCallback, useEffect, useState, useRef, memo,
|
FC, useCallback, useEffect, useRef, memo,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
|
|
||||||
@ -7,9 +7,8 @@ import { ApiUser } from '../../../api/types';
|
|||||||
|
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
|
||||||
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
||||||
import cycleRestrict from '../../../util/cycleRestrict';
|
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
||||||
|
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||||
@ -18,7 +17,6 @@ import './MentionTooltip.scss';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
filter: string;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onInsertUserName: (user: ApiUser, forceFocus?: boolean) => void;
|
onInsertUserName: (user: ApiUser, forceFocus?: boolean) => void;
|
||||||
filteredUsers?: ApiUser[];
|
filteredUsers?: ApiUser[];
|
||||||
@ -27,7 +25,6 @@ export type OwnProps = {
|
|||||||
|
|
||||||
const MentionTooltip: FC<OwnProps> = ({
|
const MentionTooltip: FC<OwnProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
filter,
|
|
||||||
onClose,
|
onClose,
|
||||||
onInsertUserName,
|
onInsertUserName,
|
||||||
usersById,
|
usersById,
|
||||||
@ -37,21 +34,6 @@ const MentionTooltip: FC<OwnProps> = ({
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
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 handleUserSelect = useCallback((userId: number, forceFocus = false) => {
|
||||||
const user = usersById && usersById[userId];
|
const user = usersById && usersById[userId];
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@ -61,23 +43,21 @@ const MentionTooltip: FC<OwnProps> = ({
|
|||||||
onInsertUserName(user, forceFocus);
|
onInsertUserName(user, forceFocus);
|
||||||
}, [usersById, onInsertUserName]);
|
}, [usersById, onInsertUserName]);
|
||||||
|
|
||||||
const handleSelectMention = useCallback((e: KeyboardEvent) => {
|
const handleSelectMention = useCallback((member: ApiUser) => {
|
||||||
if (filteredUsers && filteredUsers.length && selectedMentionIndex > -1) {
|
handleUserSelect(member.id, true);
|
||||||
const member = filteredUsers[selectedMentionIndex];
|
}, [handleUserSelect]);
|
||||||
if (member) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleUserSelect(member.id, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [filteredUsers, selectedMentionIndex, handleUserSelect]);
|
|
||||||
|
|
||||||
useEffect(() => (isOpen ? captureKeyboardListeners({
|
const selectedMentionIndex = useKeyboardNavigation({
|
||||||
onEsc: onClose,
|
isActive: isOpen,
|
||||||
onUp: (e: KeyboardEvent) => handleArrowKey(-1, e),
|
items: filteredUsers,
|
||||||
onDown: (e: KeyboardEvent) => handleArrowKey(1, e),
|
onSelect: handleSelectMention,
|
||||||
onEnter: handleSelectMention,
|
shouldSelectOnTab: true,
|
||||||
onTab: handleSelectMention,
|
onClose,
|
||||||
}) : undefined), [isOpen, onClose, handleArrowKey, handleSelectMention]);
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTooltipItemVisible('.chat-item-clickable', selectedMentionIndex, containerRef);
|
||||||
|
}, [selectedMentionIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (filteredUsers && !filteredUsers.length) {
|
if (filteredUsers && !filteredUsers.length) {
|
||||||
@ -85,14 +65,6 @@ const MentionTooltip: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [filteredUsers, onClose]);
|
}, [filteredUsers, onClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedMentionIndex(0);
|
|
||||||
}, [filter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTooltipItemVisible('.chat-item-clickable', selectedMentionIndex, containerRef);
|
|
||||||
}, [selectedMentionIndex]);
|
|
||||||
|
|
||||||
const prevChatMembers = usePrevious(
|
const prevChatMembers = usePrevious(
|
||||||
filteredUsers && filteredUsers.length
|
filteredUsers && filteredUsers.length
|
||||||
? filteredUsers
|
? filteredUsers
|
||||||
|
|||||||
@ -90,6 +90,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Button.bot-commands ~ & {
|
||||||
|
.is-pointer-env & > .backdrop {
|
||||||
|
left: 3rem;
|
||||||
|
width: 3.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bubble {
|
.bubble {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: var(--symbol-menu-width);
|
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>,
|
usersById?: Record<number, ApiUser>,
|
||||||
) {
|
) {
|
||||||
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
||||||
const [currentFilter, setCurrentFilter] = useState('');
|
|
||||||
const [usersToMention, setUsersToMention] = useState<ApiUser[] | undefined>();
|
const [usersToMention, setUsersToMention] = useState<ApiUser[] | undefined>();
|
||||||
|
|
||||||
const topInlineBots = useMemo(() => {
|
const topInlineBots = useMemo(() => {
|
||||||
@ -77,7 +76,6 @@ export default function useMentionTooltip(
|
|||||||
|
|
||||||
if (usernameFilter) {
|
if (usernameFilter) {
|
||||||
const filter = usernameFilter ? usernameFilter.substr(1) : '';
|
const filter = usernameFilter ? usernameFilter.substr(1) : '';
|
||||||
setCurrentFilter(filter);
|
|
||||||
getFilteredUsers(filter, canSuggestInlineBots(html));
|
getFilteredUsers(filter, canSuggestInlineBots(html));
|
||||||
} else {
|
} else {
|
||||||
unmarkIsOpen();
|
unmarkIsOpen();
|
||||||
@ -121,7 +119,6 @@ export default function useMentionTooltip(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isMentionTooltipOpen: isOpen,
|
isMentionTooltipOpen: isOpen,
|
||||||
mentionFilter: currentFilter,
|
|
||||||
closeMentionTooltip: unmarkIsOpen,
|
closeMentionTooltip: unmarkIsOpen,
|
||||||
insertMention,
|
insertMention,
|
||||||
mentionFilteredUsers: usersToMention,
|
mentionFilteredUsers: usersToMention,
|
||||||
|
|||||||
@ -100,7 +100,6 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
|||||||
[error.field]: error.message,
|
[error.field]: error.message,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}, [error, paymentDispatch]);
|
}, [error, paymentDispatch]);
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const canUpdate = Boolean(
|
const canUpdate = Boolean(
|
||||||
(privacyType === 'public' && username && isUsernameAvailable)
|
(privacyType === 'public' && username && isUsernameAvailable)
|
||||||
|| (privacyType === 'private' && isPublic)
|
|| (privacyType === 'private' && isPublic),
|
||||||
);
|
);
|
||||||
|
|
||||||
useHistoryBack(isActive, onClose);
|
useHistoryBack(isActive, onClose);
|
||||||
|
|||||||
@ -133,6 +133,7 @@ export const RE_TME_INVITE_LINK = /^(?:https?:\/\/)?(?:t\.me\/joinchat\/)([\d\w_
|
|||||||
|
|
||||||
// MTProto constants
|
// MTProto constants
|
||||||
export const SERVICE_NOTIFICATIONS_USER_ID = 777000;
|
export const SERVICE_NOTIFICATIONS_USER_ID = 777000;
|
||||||
|
export const REPLIES_USER_ID = 1271266957;
|
||||||
export const ALL_FOLDER_ID = 0;
|
export const ALL_FOLDER_ID = 0;
|
||||||
export const ARCHIVED_FOLDER_ID = 1;
|
export const ARCHIVED_FOLDER_ID = 1;
|
||||||
export const DELETED_COMMENTS_CHANNEL_ID = 777;
|
export const DELETED_COMMENTS_CHANNEL_ID = 777;
|
||||||
|
|||||||
@ -500,7 +500,7 @@ export type ActionTypes = (
|
|||||||
'openStickerSetShortName' |
|
'openStickerSetShortName' |
|
||||||
// bots
|
// bots
|
||||||
'clickInlineButton' | 'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
|
'clickInlineButton' | 'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
|
||||||
'resetInlineBot' | 'restartBot' |
|
'resetInlineBot' | 'restartBot' | 'startBot' |
|
||||||
// misc
|
// misc
|
||||||
'openMediaViewer' | 'closeMediaViewer' | 'openAudioPlayer' | 'closeAudioPlayer' | 'openPollModal' | 'closePollModal' |
|
'openMediaViewer' | 'closeMediaViewer' | 'openAudioPlayer' | 'closeAudioPlayer' | 'openPollModal' | 'closePollModal' |
|
||||||
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' | 'setDeviceToken' |
|
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' | 'setDeviceToken' |
|
||||||
|
|||||||
@ -8,7 +8,13 @@ let closeTimeout: number | undefined;
|
|||||||
export default function useMouseInside(
|
export default function useMouseInside(
|
||||||
isOpen: boolean, onClose: NoneToVoidFunction, menuCloseTimeout = MENU_CLOSE_TIMEOUT, isDisabled = false,
|
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(() => {
|
useEffect(() => {
|
||||||
if (closeTimeout) {
|
if (closeTimeout) {
|
||||||
|
|||||||
@ -50,7 +50,6 @@ export async function authFlow(
|
|||||||
client._log.info('Signed in successfully as', utils.getDisplayName(me));
|
client._log.info('Signed in successfully as', utils.getDisplayName(me));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function checkAuthorization(client: TelegramClient) {
|
export async function checkAuthorization(client: TelegramClient) {
|
||||||
try {
|
try {
|
||||||
await client.invoke(new Api.updates.GetState());
|
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.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
|
||||||
messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
|
messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
|
||||||
messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
|
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.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.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;
|
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.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
|
||||||
messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
|
messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
|
||||||
messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
|
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.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.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;
|
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.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
|
||||||
langpack.getLanguages#42c6978f lang_pack:string = Vector<LangPackLanguage>;
|
langpack.getLanguages#42c6978f lang_pack:string = Vector<LangPackLanguage>;
|
||||||
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
|
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;
|
const { username } = payload;
|
||||||
|
|
||||||
let inlineBotData = global.inlineBots.byUsername[username];
|
let inlineBotData = global.inlineBots.byUsername[username];
|
||||||
@ -219,7 +219,23 @@ addReducer('resetInlineBot', ((global, actions, payload) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setGlobal(replaceInlineBotSettings(global, username, inlineBotData));
|
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({
|
async function searchInlineBot({
|
||||||
username,
|
username,
|
||||||
|
|||||||
@ -57,7 +57,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
onUpdateCurrentUser(update);
|
onUpdateCurrentUser(update);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'error':
|
case 'error': {
|
||||||
if (update.error.message === 'SESSION_REVOKED') {
|
if (update.error.message === 'SESSION_REVOKED') {
|
||||||
actions.signOut();
|
actions.signOut();
|
||||||
}
|
}
|
||||||
@ -70,6 +70,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icomoon';
|
font-family: 'icomoon';
|
||||||
src: url('../assets/fonts/icomoon.woff2?7tsg0v') format('woff2'),
|
src: url('../assets/fonts/icomoon.woff2?n9djnk') format('woff2'),
|
||||||
url('../assets/fonts/icomoon.woff?7tsg0v') format('woff');
|
url('../assets/fonts/icomoon.woff?n9djnk') format('woff');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
@ -32,6 +32,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-bot-commands-filled:before {
|
||||||
|
content: "\e97f";
|
||||||
|
}
|
||||||
|
.icon-reply-filled:before {
|
||||||
|
content: "\e980";
|
||||||
|
}
|
||||||
.icon-bug:before {
|
.icon-bug:before {
|
||||||
content: "\e97e";
|
content: "\e97e";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,5 +120,5 @@ export default function getReadableErrorText(error: ApiError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getShippingError(error: ApiError): ApiFieldError | undefined {
|
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