mirror of
https://github.com/block/buzz.git
synced 2026-11-14 13:21:02 +00:00
fix(composer): prevent phantom newline on Enter by moving submit into Tiptap extension (#324)
This commit is contained in:
@@ -19,6 +19,11 @@ export type RichTextEditorOptions = {
|
||||
editable?: boolean;
|
||||
mentionNames?: string[];
|
||||
channelNames?: string[];
|
||||
/** Called on plain Enter (submit). Handled inside Tiptap's extension system
|
||||
* so it fires *before* ProseMirror's default splitBlock behaviour. */
|
||||
onSubmit?: () => void;
|
||||
/** When true, plain Enter is passed through (e.g. to select an autocomplete item). */
|
||||
isAutocompleteOpen?: React.RefObject<boolean>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -36,10 +41,15 @@ export function useRichTextEditor({
|
||||
editable = true,
|
||||
mentionNames,
|
||||
channelNames,
|
||||
onSubmit,
|
||||
isAutocompleteOpen,
|
||||
}: RichTextEditorOptions) {
|
||||
const onUpdateRef = React.useRef(onUpdate);
|
||||
onUpdateRef.current = onUpdate;
|
||||
|
||||
const onSubmitRef = React.useRef(onSubmit);
|
||||
onSubmitRef.current = onSubmit;
|
||||
|
||||
const placeholderRef = React.useRef(placeholder);
|
||||
placeholderRef.current = placeholder;
|
||||
|
||||
@@ -148,6 +158,24 @@ export function useRichTextEditor({
|
||||
};
|
||||
},
|
||||
}),
|
||||
// Plain Enter → submit the message. This runs inside ProseMirror's
|
||||
// keymap pipeline so it fires *before* the default splitBlock command,
|
||||
// preventing the phantom paragraph-split that caused \n\n in messages.
|
||||
Extension.create({
|
||||
name: "submitOnEnter",
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Enter: () => {
|
||||
// Let autocomplete dropdowns consume Enter first.
|
||||
if (isAutocompleteOpen?.current) return false;
|
||||
// No submit callback → fall through to default behaviour.
|
||||
if (!onSubmitRef.current) return false;
|
||||
onSubmitRef.current();
|
||||
return true; // prevents splitBlock
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
MentionHighlightExtension,
|
||||
Placeholder.configure({
|
||||
placeholder: () => placeholderRef.current ?? "Write a message…",
|
||||
|
||||
@@ -110,6 +110,13 @@ export function MessageComposer({
|
||||
editTargetRef.current = editTarget;
|
||||
channelIdRef.current = channelId;
|
||||
|
||||
// ── Refs consumed by Tiptap's submitOnEnter extension ──────────────
|
||||
const isAutocompleteOpenRef = React.useRef(false);
|
||||
isAutocompleteOpenRef.current =
|
||||
mentions.isMentionOpen || channelLinks.isChannelOpen;
|
||||
|
||||
const submitMessageRef = React.useRef<() => void>(() => {});
|
||||
|
||||
// ── Computed placeholder ─────────────────────────────────────────────
|
||||
const computedPlaceholder = editTarget
|
||||
? "Edit your message"
|
||||
@@ -124,6 +131,8 @@ export function MessageComposer({
|
||||
editable: !disabled,
|
||||
mentionNames: mentions.knownNames,
|
||||
channelNames: channelLinks.knownChannelNames,
|
||||
onSubmit: () => submitMessageRef.current(),
|
||||
isAutocompleteOpen: isAutocompleteOpenRef,
|
||||
onUpdate: ({ markdown, text }) => {
|
||||
setContent(markdown);
|
||||
contentRef.current = markdown;
|
||||
@@ -361,6 +370,7 @@ export function MessageComposer({
|
||||
richText.clearContent,
|
||||
richText.setContent,
|
||||
]);
|
||||
submitMessageRef.current = submitMessage;
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
(event: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -372,8 +382,9 @@ export function MessageComposer({
|
||||
|
||||
// ── Keyboard handling ───────────────────────────────────────────────
|
||||
// Tiptap handles formatting shortcuts (⌘B, ⌘I, etc.) natively.
|
||||
// We intercept Enter (submit) and arrow keys (autocomplete) via a
|
||||
// keydown listener on the editor wrapper.
|
||||
// Plain Enter → submit is now handled inside the Tiptap `submitOnEnter`
|
||||
// extension (fires before ProseMirror's splitBlock). This wrapper only
|
||||
// handles autocomplete arrow/enter keys and Escape for edit mode.
|
||||
const handleEditorKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
// Let autocomplete handle keys first
|
||||
@@ -399,32 +410,12 @@ export function MessageComposer({
|
||||
onCancelEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key !== "Enter" || event.nativeEvent.isComposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shift+Enter or Ctrl+Enter → newline (Tiptap handles this natively
|
||||
// via hard break, so let it through)
|
||||
if (event.shiftKey || event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Meta/Alt+Enter → let through (system shortcuts)
|
||||
if (event.metaKey || event.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Plain Enter → submit
|
||||
event.preventDefault();
|
||||
void submitMessage();
|
||||
},
|
||||
[
|
||||
channelLinks.handleChannelKeyDown,
|
||||
applyChannelInsert,
|
||||
mentions.handleMentionKeyDown,
|
||||
applyMentionInsert,
|
||||
submitMessage,
|
||||
onCancelEdit,
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user