Attachment Modal: Send attachments on enter when input not focused (#6397)

This commit is contained in:
Alexander Zinchuk 2025-11-06 11:36:33 +01:00
parent c9f9b27332
commit 28a41a6764
2 changed files with 47 additions and 34 deletions

View File

@ -14,7 +14,7 @@ import type {
} from '../../../types'; } from '../../../types';
import type { Signal } from '../../../util/signals'; import type { Signal } from '../../../util/signals';
import { EDITABLE_INPUT_ID } from '../../../config'; import { EDITABLE_INPUT_ID, EDITABLE_INPUT_MODAL_ID } from '../../../config';
import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fasterdom'; import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fasterdom';
import { selectCanPlayAnimatedEmojis, selectDraft, selectIsInSelectMode } from '../../../global/selectors'; import { selectCanPlayAnimatedEmojis, selectDraft, selectIsInSelectMode } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState'; import { selectSharedSettings } from '../../../global/selectors/sharedState';
@ -23,7 +23,7 @@ import {
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV, IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV,
} from '../../../util/browser/windowEnvironment'; } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import captureKeyboardListeners from '../../../util/captureKeyboardListeners'; import captureKeyboardListeners, { hasActiveHandler } from '../../../util/captureKeyboardListeners';
import { getIsDirectTextInputDisabled } from '../../../util/directInputManager'; import { getIsDirectTextInputDisabled } from '../../../util/directInputManager';
import parseEmojiOnlyString from '../../../util/emoji/parseEmojiOnlyString'; import parseEmojiOnlyString from '../../../util/emoji/parseEmojiOnlyString';
import focusEditableElement from '../../../util/focusEditableElement'; import focusEditableElement from '../../../util/focusEditableElement';
@ -379,6 +379,22 @@ const MessageInput: FC<OwnProps & StateProps> = ({
document.addEventListener('keydown', handleCloseContextMenu); document.addEventListener('keydown', handleCloseContextMenu);
} }
const isSendShortcut = useLastCallback((e: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>) => {
return e.key === 'Enter'
&& !e.shiftKey
&& !isMobileDevice
&& (
(messageSendKeyCombo === 'enter' && !e.shiftKey)
|| (messageSendKeyCombo === 'ctrl-enter' && (e.ctrlKey || e.metaKey))
);
});
const handleSendShortcut = useLastCallback((e: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault();
closeTextFormatter();
onSend();
});
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) { function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
// https://levelup.gitconnected.com/javascript-events-handlers-keyboard-and-load-events-1b3e46a6b0c3#1960 // https://levelup.gitconnected.com/javascript-events-handlers-keyboard-and-load-events-1b3e46a6b0c3#1960
const { isComposing } = e; const { isComposing } = e;
@ -394,19 +410,8 @@ const MessageInput: FC<OwnProps & StateProps> = ({
} }
} }
if (!isComposing && e.key === 'Enter' && !e.shiftKey) { if (!isComposing && isSendShortcut(e)) {
if ( handleSendShortcut(e);
!isMobileDevice
&& (
(messageSendKeyCombo === 'enter' && !e.shiftKey)
|| (messageSendKeyCombo === 'ctrl-enter' && (e.ctrlKey || e.metaKey))
)
) {
e.preventDefault();
closeTextFormatter();
onSend();
}
} else if (!isComposing && e.key === 'ArrowUp' && !html && !e.metaKey && !e.ctrlKey && !e.altKey) { } else if (!isComposing && e.key === 'ArrowUp' && !html && !e.metaKey && !e.ctrlKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
editLastMessage(); editLastMessage();
@ -474,8 +479,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
if ( if (
!chatId !chatId
|| editableInputId !== EDITABLE_INPUT_ID || (editableInputId !== EDITABLE_INPUT_ID && editableInputId !== EDITABLE_INPUT_MODAL_ID)
|| noFocusInterception
|| isMobileDevice || isMobileDevice
|| isSelectModeActive || isSelectModeActive
) { ) {
@ -483,18 +487,29 @@ const MessageInput: FC<OwnProps & StateProps> = ({
} }
const handleDocumentKeyDown = (e: KeyboardEvent) => { const handleDocumentKeyDown = (e: KeyboardEvent) => {
if (getIsDirectTextInputDisabled()) { const target = e.target as HTMLElement | undefined;
const input = inputRef.current!;
const shouldHandleDocumentKeyDown =
isActive && input && target
&& target !== input
&& target.tagName !== 'INPUT'
&& target.tagName !== 'TEXTAREA'
&& !target.isContentEditable
&& !hasActiveHandler('Enter');
if (!shouldHandleDocumentKeyDown) return;
if (isSendShortcut(e)) {
handleSendShortcut(e);
return; return;
} }
const { key } = e; const { key } = e;
const target = e.target as HTMLElement | undefined; if (noFocusInterception || getIsDirectTextInputDisabled() || IGNORE_KEYS.includes(key)) {
if (!target || IGNORE_KEYS.includes(key)) {
return; return;
} }
const input = inputRef.current!;
const isSelectionCollapsed = document.getSelection()?.isCollapsed; const isSelectionCollapsed = document.getSelection()?.isCollapsed;
if ( if (
@ -504,18 +519,10 @@ const MessageInput: FC<OwnProps & StateProps> = ({
return; return;
} }
if ( focusEditableElement(input, true, true);
input
&& target !== input
&& target.tagName !== 'INPUT'
&& target.tagName !== 'TEXTAREA'
&& !target.isContentEditable
) {
focusEditableElement(input, true, true);
const newEvent = new KeyboardEvent(e.type, e as any); const newEvent = new KeyboardEvent(e.type, e as any);
input.dispatchEvent(newEvent); input.dispatchEvent(newEvent);
}
}; };
document.addEventListener('keydown', handleDocumentKeyDown, true); document.addEventListener('keydown', handleDocumentKeyDown, true);
@ -523,7 +530,8 @@ const MessageInput: FC<OwnProps & StateProps> = ({
return () => { return () => {
document.removeEventListener('keydown', handleDocumentKeyDown, true); document.removeEventListener('keydown', handleDocumentKeyDown, true);
}; };
}, [chatId, editableInputId, isMobileDevice, isSelectModeActive, noFocusInterception]); }, [chatId, editableInputId, isMobileDevice,
isActive, isSelectModeActive, noFocusInterception]);
useEffect(() => { useEffect(() => {
const captureFirstTab = debounce((e: KeyboardEvent) => { const captureFirstTab = debounce((e: KeyboardEvent) => {

View File

@ -62,6 +62,11 @@ function hasActiveHandlers() {
return Object.values(handlers).some((keyHandlers) => Boolean(keyHandlers.length)); return Object.values(handlers).some((keyHandlers) => Boolean(keyHandlers.length));
} }
export function hasActiveHandler(key: string) {
const handlerName = keyToHandlerName[key];
return handlerName ? Boolean(handlers[handlerName].length) : false;
}
function handleKeyDown(e: KeyboardEvent) { function handleKeyDown(e: KeyboardEvent) {
const handlerName = keyToHandlerName[e.key]; const handlerName = keyToHandlerName[e.key];
if (!handlerName) { if (!handlerName) {