From aff1d76010045ff216721b8ce6c0f600961c0016 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 7 Jul 2026 22:24:32 +0100 Subject: [PATCH] feat: allow submitting comments via keyboard shortcut --- apps/web/src/components/Editor.tsx | 51 ++++++++++++------- .../views/card/components/NewCommentForm.tsx | 43 +++++++++++----- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/apps/web/src/components/Editor.tsx b/apps/web/src/components/Editor.tsx index 3afa1a05..ce3dff20 100644 --- a/apps/web/src/components/Editor.tsx +++ b/apps/web/src/components/Editor.tsx @@ -10,6 +10,7 @@ import { t } from "@lingui/core/macro"; import Link from "@tiptap/extension-link"; import Mention from "@tiptap/extension-mention"; import Placeholder from "@tiptap/extension-placeholder"; +import Typography from "@tiptap/extension-typography"; import { BubbleMenu, EditorContent, @@ -17,7 +18,6 @@ import { ReactRenderer, useEditor, } from "@tiptap/react"; -import Typography from "@tiptap/extension-typography"; import StarterKit from "@tiptap/starter-kit"; import Suggestion from "@tiptap/suggestion"; import { @@ -440,6 +440,7 @@ export default function Editor({ content, onChange, onBlur, + onSubmit, readOnly = false, workspaceMembers, enableYouTubeEmbed = true, @@ -449,6 +450,7 @@ export default function Editor({ content: string | null; onChange?: (value: string) => void; onBlur?: () => void; + onSubmit?: () => void; readOnly?: boolean; workspaceMembers: WorkspaceMember[]; enableYouTubeEmbed?: boolean; @@ -461,12 +463,16 @@ export default function Editor({ // in refs to avoid the editor capturing stale closures on re-render. const onChangeRef = useRef(onChange); const onBlurRef = useRef(onBlur); + const onSubmitRef = useRef(onSubmit); useEffect(() => { onChangeRef.current = onChange; }, [onChange]); useEffect(() => { onBlurRef.current = onBlur; }, [onBlur]); + useEffect(() => { + onSubmitRef.current = onSubmit; + }, [onSubmit]); const editor = useEditor( { @@ -489,8 +495,8 @@ export default function Editor({ Placeholder.configure({ placeholder: readOnly ? "" - : placeholder ?? - t`Add description... (type '/' to open commands or '@' to mention)`, + : (placeholder ?? + t`Add description... (type '/' to open commands or '@' to mention)`), }), SlashCommands.configure({ commandItems: getCommandItems(disableHeadings), @@ -508,24 +514,26 @@ export default function Editor({ suggestion: { char: "@", items: ({ query }: { query: string }) => { - const withEmail = workspaceMembers.filter((member) => member.email); - + const withEmail = workspaceMembers.filter( + (member) => member.email, + ); + const mapped = withEmail.map((member: WorkspaceMember) => ({ id: member.publicId, label: member?.user?.name?.trim() || member.email || "", image: member?.user?.image ?? null, })); - + const all: MentionItem[] = mapped.filter( (item) => item.label && item.label.length > 0, ); - + const q = query.toLowerCase().trim(); - + if (q === "") { return all; } - + const filtered = all.filter((u) => u.label.toLowerCase().includes(q), ); @@ -551,15 +559,15 @@ export default function Editor({ }, }), Typography.configure({ - openDoubleQuote: false, - closeDoubleQuote: false, - openSingleQuote: false, - closeSingleQuote: false, - oneHalf: false, - oneQuarter: false, - threeQuarters: false, - superscriptTwo: false, - superscriptThree: false, + openDoubleQuote: false, + closeDoubleQuote: false, + openSingleQuote: false, + closeSingleQuote: false, + oneHalf: false, + oneQuarter: false, + threeQuarters: false, + superscriptTwo: false, + superscriptThree: false, }), ...(enableYouTubeEmbed ? [YouTubeNode] : []), ], @@ -581,6 +589,13 @@ export default function Editor({ attributes: { class: "outline-none focus:outline-none focus-visible:ring-0", }, + handleKeyDown: (_view, event) => { + if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { + onSubmitRef.current?.(); + return true; + } + return false; + }, }, editable: !readOnly, injectCSS: false, diff --git a/apps/web/src/views/card/components/NewCommentForm.tsx b/apps/web/src/views/card/components/NewCommentForm.tsx index c1a9aade..865b58e9 100644 --- a/apps/web/src/views/card/components/NewCommentForm.tsx +++ b/apps/web/src/views/card/components/NewCommentForm.tsx @@ -2,9 +2,10 @@ import { t } from "@lingui/core/macro"; import { useForm } from "react-hook-form"; import { HiOutlineArrowUp } from "react-icons/hi2"; -import Editor from "~/components/Editor"; import type { WorkspaceMember } from "~/components/Editor"; +import Editor from "~/components/Editor"; import LoadingSpinner from "~/components/LoadingSpinner"; +import { Tooltip } from "~/components/Tooltip"; import { usePermissions } from "~/hooks/usePermissions"; import { usePopup } from "~/providers/popup"; import { api } from "~/utils/api"; @@ -51,6 +52,21 @@ const NewCommentForm = ({ }); }; + const isMac = + typeof navigator !== "undefined" && navigator.userAgent.includes("Mac"); + + const submitTooltip = ( +
+ {t`Submit`} + + {isMac ? "⌘" : "Ctrl"} + + + Enter + +
+ ); + if (!canCreateComment) { return null; } @@ -63,23 +79,26 @@ const NewCommentForm = ({ setValue("comment", value)} + onSubmit={handleSubmit(onSubmit)} workspaceMembers={workspaceMembers} enableYouTubeEmbed={false} placeholder={t`Add comment... (type '/' to open commands or '@' to mention)`} disableHeadings={true} />
- + + +
);