mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-01 01:16:17 +02:00
Chat input had `submitOnEnter` enabled while the comment editor used `Mod+Enter`. Two consequences: - Inconsistent muscle memory between the two inputs. - In chat, bare Enter sending stole the only key that continues a TipTap bullet/ordered list. Shift+Enter falls through to HardBreak (a <br> inside the same list item), so bullet lists were stuck at one item. Drop `submitOnEnter` from the chat input so it follows the editor default. Mod+Enter (⌘↵ / Ctrl+Enter) sends in both places; bare Enter now continues lists and inserts paragraphs as users expect. Surface the shortcut on the SubmitButton via a new optional `tooltip` prop, and route the comment input through SubmitButton instead of an ad-hoc Button — same affordance, deduped. Add unit coverage for the submit-shortcut extension that pins Mod-Enter, the submitOnEnter=false case, IME, and code-block guards. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { getExtensionField } from "@tiptap/core";
|
|
import type { Editor } from "@tiptap/core";
|
|
import { createSubmitExtension } from "./submit-shortcut";
|
|
|
|
function getShortcuts(
|
|
ext: ReturnType<typeof createSubmitExtension>,
|
|
editor: Partial<Editor>,
|
|
): Record<string, () => boolean> {
|
|
const fn = getExtensionField<
|
|
() => Record<string, () => boolean>
|
|
>(ext, "addKeyboardShortcuts", {
|
|
name: "submitShortcut",
|
|
options: {},
|
|
storage: {},
|
|
editor: editor as Editor,
|
|
type: null,
|
|
});
|
|
return fn?.() ?? {};
|
|
}
|
|
|
|
describe("createSubmitExtension", () => {
|
|
const baseEditor = {
|
|
view: { composing: false } as unknown as Editor["view"],
|
|
isActive: () => false,
|
|
} as Partial<Editor>;
|
|
|
|
it("Mod-Enter always submits", () => {
|
|
const onSubmit = vi.fn(() => true);
|
|
const shortcuts = getShortcuts(
|
|
createSubmitExtension(onSubmit, { submitOnEnter: false }),
|
|
baseEditor,
|
|
);
|
|
|
|
expect(shortcuts["Mod-Enter"]).toBeDefined();
|
|
shortcuts["Mod-Enter"]!();
|
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("bare Enter is not bound when submitOnEnter is false", () => {
|
|
const onSubmit = vi.fn(() => true);
|
|
const shortcuts = getShortcuts(
|
|
createSubmitExtension(onSubmit, { submitOnEnter: false }),
|
|
baseEditor,
|
|
);
|
|
|
|
expect(shortcuts.Enter).toBeUndefined();
|
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("bare Enter submits when submitOnEnter is true", () => {
|
|
const onSubmit = vi.fn(() => true);
|
|
const shortcuts = getShortcuts(
|
|
createSubmitExtension(onSubmit, { submitOnEnter: true }),
|
|
baseEditor,
|
|
);
|
|
|
|
expect(shortcuts.Enter).toBeDefined();
|
|
expect(shortcuts.Enter!()).toBe(true);
|
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("Enter is suppressed during IME composition", () => {
|
|
const onSubmit = vi.fn(() => true);
|
|
const shortcuts = getShortcuts(
|
|
createSubmitExtension(onSubmit, { submitOnEnter: true }),
|
|
{
|
|
view: { composing: true } as unknown as Editor["view"],
|
|
isActive: () => false,
|
|
},
|
|
);
|
|
|
|
expect(shortcuts.Enter!()).toBe(false);
|
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("Enter is suppressed inside a code block", () => {
|
|
const onSubmit = vi.fn(() => true);
|
|
const shortcuts = getShortcuts(
|
|
createSubmitExtension(onSubmit, { submitOnEnter: true }),
|
|
{
|
|
view: { composing: false } as unknown as Editor["view"],
|
|
isActive: (name: string) => name === "codeBlock",
|
|
},
|
|
);
|
|
|
|
expect(shortcuts.Enter!()).toBe(false);
|
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
});
|
|
});
|