Composer: Bot keyboard should open automatically (#1372)

This commit is contained in:
Alexander Zinchuk 2021-08-11 01:28:04 +03:00
parent 3635d312ce
commit fbb401dac9
7 changed files with 52 additions and 34 deletions

View File

@ -16,7 +16,7 @@ import {
ApiWebPage, ApiWebPage,
ApiMessageEntity, ApiMessageEntity,
ApiFormattedText, ApiFormattedText,
ApiKeyboardButtons, ApiReplyKeyboard,
ApiKeyboardButton, ApiKeyboardButton,
ApiChat, ApiChat,
ApiThreadInfo, ApiThreadInfo,
@ -138,7 +138,9 @@ export function buildApiMessageWithChatId(chatId: number, mtpMessage: UniversalM
const { replyToMsgId, replyToTopId } = mtpMessage.replyTo || {}; const { replyToMsgId, replyToTopId } = mtpMessage.replyTo || {};
const isEdited = mtpMessage.editDate && !mtpMessage.editHide; const isEdited = mtpMessage.editDate && !mtpMessage.editHide;
const { inlineButtons, keyboardButtons } = buildReplyButtons(mtpMessage) || {}; const {
inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse,
} = buildReplyButtons(mtpMessage) || {};
const forwardInfo = mtpMessage.fwdFrom && buildApiMessageForwardInfo(mtpMessage.fwdFrom, isChatWithSelf); const forwardInfo = mtpMessage.fwdFrom && buildApiMessageForwardInfo(mtpMessage.fwdFrom, isChatWithSelf);
const { replies, mediaUnread: isMediaUnread, postAuthor } = mtpMessage; const { replies, mediaUnread: isMediaUnread, postAuthor } = mtpMessage;
const groupedId = mtpMessage.groupedId && mtpMessage.groupedId.toString(); const groupedId = mtpMessage.groupedId && mtpMessage.groupedId.toString();
@ -165,7 +167,7 @@ export function buildApiMessageWithChatId(chatId: number, mtpMessage: UniversalM
isInAlbum, isInAlbum,
}), }),
inlineButtons, inlineButtons,
...(keyboardButtons && { keyboardButtons }), ...(keyboardButtons && { keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse }),
...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons }), ...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons }),
...(mtpMessage.viaBotId && { viaBotId: mtpMessage.viaBotId }), ...(mtpMessage.viaBotId && { viaBotId: mtpMessage.viaBotId }),
...(replies && replies.comments && { threadInfo: buildThreadInfo(replies, mtpMessage.id, chatId) }), ...(replies && replies.comments && { threadInfo: buildThreadInfo(replies, mtpMessage.id, chatId) }),
@ -693,9 +695,7 @@ function buildAction(
}; };
} }
function buildReplyButtons(message: UniversalMessage): { function buildReplyButtons(message: UniversalMessage): ApiReplyKeyboard | undefined {
[K in 'inlineButtons' | 'keyboardButtons']?: ApiKeyboardButtons
} | undefined {
const { id: messageId, replyMarkup, media } = message; const { id: messageId, replyMarkup, media } = message;
if (!replyMarkup) { if (!replyMarkup) {
@ -756,7 +756,13 @@ function buildReplyButtons(message: UniversalMessage): {
}); });
}); });
return { [replyMarkup instanceof GramJs.ReplyKeyboardMarkup ? 'keyboardButtons' : 'inlineButtons']: markup }; return {
[replyMarkup instanceof GramJs.ReplyKeyboardMarkup ? 'keyboardButtons' : 'inlineButtons']: markup,
...(replyMarkup instanceof GramJs.ReplyKeyboardMarkup && {
keyboardPlaceholder: replyMarkup.placeholder,
isKeyboardSingleUse: replyMarkup.singleUse,
}),
};
} }
function getFilenameFromDocument(document: GramJs.Document, defaultBase = 'file') { function getFilenameFromDocument(document: GramJs.Document, defaultBase = 'file') {

View File

@ -243,6 +243,8 @@ export interface ApiMessage {
hasUnreadMention?: boolean; hasUnreadMention?: boolean;
inlineButtons?: ApiKeyboardButtons; inlineButtons?: ApiKeyboardButtons;
keyboardButtons?: ApiKeyboardButtons; keyboardButtons?: ApiKeyboardButtons;
keyboardPlaceholder?: string;
isKeyboardSingleUse?: boolean;
viaBotId?: number; viaBotId?: number;
threadInfo?: ApiThreadInfo; threadInfo?: ApiThreadInfo;
adminTitle?: string; adminTitle?: string;
@ -272,6 +274,12 @@ export interface ApiKeyboardButton {
} }
export type ApiKeyboardButtons = ApiKeyboardButton[][]; export type ApiKeyboardButtons = ApiKeyboardButton[][];
export type ApiReplyKeyboard = {
keyboardPlaceholder?: string;
isKeyboardSingleUse?: boolean;
} & {
[K in 'inlineButtons' | 'keyboardButtons']?: ApiKeyboardButtons;
};
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'profilePhoto'; export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'profilePhoto';
export type ApiGlobalMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice'; export type ApiGlobalMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice';

View File

@ -31,7 +31,6 @@ 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 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 BotKeyboardMenu } from '../components/middle/composer/BotKeyboardMenu';
export { default as CustomSendMenu } from '../components/middle/composer/CustomSendMenu'; export { default as CustomSendMenu } from '../components/middle/composer/CustomSendMenu';
export { default as DropArea } from '../components/middle/composer/DropArea'; export { default as DropArea } from '../components/middle/composer/DropArea';
export { default as TextFormatter } from '../components/middle/composer/TextFormatter'; export { default as TextFormatter } from '../components/middle/composer/TextFormatter';

View File

@ -1,15 +0,0 @@
import React, { FC, memo } from '../../../lib/teact/teact';
import { OwnProps } from './BotKeyboardMenu';
import { Bundles } from '../../../util/moduleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const BotKeyboardMenuAsync: FC<OwnProps> = (props) => {
const { isOpen } = props;
const BotKeyboardMenu = useModuleLoader(Bundles.Extra, 'BotKeyboardMenu', !isOpen);
// eslint-disable-next-line react/jsx-props-no-spreading
return BotKeyboardMenu ? <BotKeyboardMenu {...props} /> : undefined;
};
export default memo(BotKeyboardMenuAsync);

View File

@ -1,4 +1,4 @@
.KeyboardMenu { .BotKeyboardMenu {
.bubble { .bubble {
width: 100% !important; width: 100% !important;
max-width: 27rem; max-width: 27rem;
@ -40,6 +40,7 @@
&:hover { &:hover {
color: #fff; color: #fff;
border-color: var(--color-primary-shade); border-color: var(--color-primary-shade);
background: var(--color-primary-shade);
} }
} }

View File

@ -1,4 +1,4 @@
import React, { FC, memo } from '../../../lib/teact/teact'; import React, { FC, memo, useEffect } from '../../../lib/teact/teact';
import { withGlobal } from '../../../lib/teact/teactn'; import { withGlobal } from '../../../lib/teact/teactn';
import { GlobalActions } from '../../../global/types'; import { GlobalActions } from '../../../global/types';
@ -8,6 +8,7 @@ import { IS_TOUCH_ENV } from '../../../util/environment';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { selectChatMessage, selectCurrentMessageList } from '../../../modules/selectors'; import { selectChatMessage, selectCurrentMessageList } from '../../../modules/selectors';
import useMouseInside from '../../../hooks/useMouseInside'; import useMouseInside from '../../../hooks/useMouseInside';
import useFlag from '../../../hooks/useFlag';
import Menu from '../../ui/Menu'; import Menu from '../../ui/Menu';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
@ -30,6 +31,17 @@ const BotKeyboardMenu: FC<OwnProps & StateProps & DispatchProps> = ({
isOpen, message, onClose, clickInlineButton, isOpen, message, onClose, clickInlineButton,
}) => { }) => {
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose); const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
const { isKeyboardSingleUse } = message || {};
const [forceOpen, markForceOpen, unmarkForceOpen] = useFlag(true);
const handleClose = () => {
unmarkForceOpen();
onClose();
};
useEffect(() => {
markForceOpen();
}, [markForceOpen, message]);
if (!message || !message.keyboardButtons) { if (!message || !message.keyboardButtons) {
return undefined; return undefined;
@ -37,16 +49,15 @@ const BotKeyboardMenu: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<Menu <Menu
isOpen={isOpen} isOpen={isOpen || forceOpen}
autoClose autoClose={isKeyboardSingleUse}
positionX="right" positionX="right"
positionY="bottom" positionY="bottom"
onClose={onClose} onClose={handleClose}
className="KeyboardMenu" className="BotKeyboardMenu"
onCloseAnimationEnd={onClose} onCloseAnimationEnd={handleClose}
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined} onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined} onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
noCloseOnBackdrop={!IS_TOUCH_ENV}
> >
<div className="content"> <div className="content">
{message.keyboardButtons.map((row) => ( {message.keyboardButtons.map((row) => (

View File

@ -33,6 +33,7 @@ import {
selectEditingMessage, selectEditingMessage,
selectIsChatWithSelf, selectIsChatWithSelf,
selectChatUser, selectChatUser,
selectChatMessage,
} from '../../../modules/selectors'; } from '../../../modules/selectors';
import { import {
getAllowedAttachmentOptions, getAllowedAttachmentOptions,
@ -77,7 +78,7 @@ 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 BotKeyboardMenu from './BotKeyboardMenu.async'; 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';
@ -113,6 +114,7 @@ type StateProps = {
isPaymentModalOpen?: boolean; isPaymentModalOpen?: boolean;
isReceiptModalOpen?: boolean; isReceiptModalOpen?: boolean;
botKeyboardMessageId?: number; botKeyboardMessageId?: number;
botKeyboardPlaceholder?: string;
withScheduledButton?: boolean; withScheduledButton?: boolean;
shouldSchedule?: boolean; shouldSchedule?: boolean;
canScheduleUntilOnline?: boolean; canScheduleUntilOnline?: boolean;
@ -178,6 +180,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
isPaymentModalOpen, isPaymentModalOpen,
isReceiptModalOpen, isReceiptModalOpen,
botKeyboardMessageId, botKeyboardMessageId,
botKeyboardPlaceholder,
withScheduledButton, withScheduledButton,
stickersForEmoji, stickersForEmoji,
groupChatMembers, groupChatMembers,
@ -851,7 +854,9 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
id="message-input-text" id="message-input-text"
html={!attachments.length ? html : ''} html={!attachments.length ? html : ''}
placeholder={ placeholder={
activeVoiceRecording && windowWidth <= SCREEN_WIDTH_TO_HIDE_PLACEHOLDER ? '' : lang('Message') activeVoiceRecording && windowWidth <= SCREEN_WIDTH_TO_HIDE_PLACEHOLDER
? ''
: botKeyboardPlaceholder || lang('Message')
} }
forcedPlaceholder={inlineBotHelp} forcedPlaceholder={inlineBotHelp}
shouldSetFocus={isSymbolMenuOpen} shouldSetFocus={isSymbolMenuOpen}
@ -1006,6 +1011,8 @@ export default memo(withGlobal<OwnProps>(
const { language } = global.settings.byKey; const { language } = global.settings.byKey;
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG]; const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined; const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;
const botKeyboardMessageId = messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined;
const keyboardMessage = botKeyboardMessageId ? selectChatMessage(global, chatId, botKeyboardMessageId) : undefined;
return { return {
editingMessage: selectEditingMessage(global, chatId, threadId, messageListType), editingMessage: selectEditingMessage(global, chatId, threadId, messageListType),
@ -1026,7 +1033,8 @@ export default memo(withGlobal<OwnProps>(
&& Boolean(scheduledIds && scheduledIds.length) && Boolean(scheduledIds && scheduledIds.length)
), ),
shouldSchedule: messageListType === 'scheduled', shouldSchedule: messageListType === 'scheduled',
botKeyboardMessageId: messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined, botKeyboardMessageId,
botKeyboardPlaceholder: keyboardMessage ? keyboardMessage.keyboardPlaceholder : undefined,
isForwarding: chatId === global.forwardMessages.toChatId, isForwarding: chatId === global.forwardMessages.toChatId,
isPollModalOpen: global.isPollModalOpen, isPollModalOpen: global.isPollModalOpen,
stickersForEmoji: global.stickers.forEmoji.stickers, stickersForEmoji: global.stickers.forEmoji.stickers,