From c6589bf30f6a4d33f268e76388e8a14ca08e374c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 10:51:15 +0000 Subject: [PATCH] fix: handle Ctrl/Cmd+Enter directly in suggestion onKeyDown handlers The TipTap suggestion plugin intercepts key events at the plugin level, which runs before extension keyboard shortcuts. Even returning false from the suggestion's onKeyDown didn't properly propagate events. Now the suggestion handlers directly handle Ctrl/Cmd+Enter by: 1. Capturing the editor reference from onStart (where it's available) 2. Checking for Ctrl/Cmd+Enter in onKeyDown 3. Closing the popup and calling handleSubmitRef.current() Also moved handleSubmitRef to the top of the component so it can be accessed from within the suggestion config closures. --- src/components/editor/MentionEditor.tsx | 30 +++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/components/editor/MentionEditor.tsx b/src/components/editor/MentionEditor.tsx index 28b8eb1..a9423eb 100644 --- a/src/components/editor/MentionEditor.tsx +++ b/src/components/editor/MentionEditor.tsx @@ -157,6 +157,9 @@ export const MentionEditor = forwardRef< }, ref, ) => { + // Ref to access handleSubmit from suggestion plugins (defined early so useMemo can access it) + const handleSubmitRef = useRef<(editor: any) => void>(() => {}); + // Create mention suggestion configuration for @ mentions const mentionSuggestion: Omit = useMemo( () => ({ @@ -168,9 +171,11 @@ export const MentionEditor = forwardRef< render: () => { let component: ReactRenderer; let popup: TippyInstance[]; + let editorRef: any; return { onStart: (props) => { + editorRef = props.editor; component = new ReactRenderer(ProfileSuggestionList, { props: { items: props.items, @@ -218,6 +223,16 @@ export const MentionEditor = forwardRef< return true; } + // Ctrl/Cmd+Enter submits the message + if ( + props.event.key === "Enter" && + (props.event.ctrlKey || props.event.metaKey) + ) { + popup[0]?.hide(); + handleSubmitRef.current(editorRef); + return true; + } + return component.ref?.onKeyDown(props.event) ?? false; }, @@ -244,9 +259,11 @@ export const MentionEditor = forwardRef< render: () => { let component: ReactRenderer; let popup: TippyInstance[]; + let editorRef: any; return { onStart: (props) => { + editorRef = props.editor; component = new ReactRenderer(EmojiSuggestionList, { props: { items: props.items, @@ -294,6 +311,16 @@ export const MentionEditor = forwardRef< return true; } + // Ctrl/Cmd+Enter submits the message + if ( + props.event.key === "Enter" && + (props.event.ctrlKey || props.event.metaKey) + ) { + popup[0]?.hide(); + handleSubmitRef.current(editorRef); + return true; + } + return component.ref?.onKeyDown(props.event) ?? false; }, @@ -377,8 +404,7 @@ export const MentionEditor = forwardRef< [onSubmit, serializeContent], ); - // Ref to access handleSubmit from keyboard extension without recreating it - const handleSubmitRef = useRef(handleSubmit); + // Keep ref updated with latest handleSubmit handleSubmitRef.current = handleSubmit; // Build extensions array