From 8b6a0915ee0dc4c1c09a7945790277f330d28d84 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Fri, 15 May 2026 02:25:27 +0530 Subject: [PATCH] feat: comment submit shortcut preference Add a user profile preference to choose between Enter and Mod+Enter for submitting comments across the web and space apps. Wires the new preference through the Profile model, types, utils, editor extension, and i18n namespace, and exposes a selector in profile preferences. --- .../0122_profile_comment_submit_shortcut.py | 21 ++++++ apps/api/plane/db/models/user.py | 7 ++ .../components/editor/lite-text-editor.tsx | 5 ++ apps/space/store/profile.store.ts | 1 + .../components/comments/card/edit-form.tsx | 19 ++++- .../components/comments/comment-create.tsx | 20 +++--- .../components/editor/lite-text/editor.tsx | 5 ++ .../preferences/comment-shortcut-selector.tsx | 60 ++++++++++++++++ .../pages/preferences/default-list.tsx | 3 + apps/web/core/store/user/profile.store.ts | 1 + .../components/editors/lite-text/editor.tsx | 6 +- .../editor/src/core/extensions/enter-key.ts | 72 +++++++++++++------ packages/editor/src/core/types/editor.ts | 5 +- packages/i18n/src/constants/namespaces.ts | 1 + .../src/locales/cs/profile-preferences.json | 6 ++ .../src/locales/de/profile-preferences.json | 6 ++ .../src/locales/en/profile-preferences.json | 6 ++ .../src/locales/es/profile-preferences.json | 6 ++ .../src/locales/fr/profile-preferences.json | 6 ++ .../src/locales/id/profile-preferences.json | 6 ++ .../src/locales/it/profile-preferences.json | 6 ++ .../src/locales/ja/profile-preferences.json | 6 ++ .../src/locales/ko/profile-preferences.json | 6 ++ .../src/locales/pl/profile-preferences.json | 6 ++ .../locales/pt-BR/profile-preferences.json | 6 ++ .../src/locales/ro/profile-preferences.json | 6 ++ .../src/locales/ru/profile-preferences.json | 6 ++ .../src/locales/sk/profile-preferences.json | 6 ++ .../locales/tr-TR/profile-preferences.json | 6 ++ .../src/locales/ua/profile-preferences.json | 6 ++ .../locales/vi-VN/profile-preferences.json | 6 ++ .../locales/zh-CN/profile-preferences.json | 6 ++ .../locales/zh-TW/profile-preferences.json | 6 ++ packages/types/src/users.ts | 3 + packages/utils/src/comment.ts | 33 +++++++++ packages/utils/src/common.ts | 2 + packages/utils/src/index.ts | 1 + 37 files changed, 342 insertions(+), 37 deletions(-) create mode 100644 apps/api/plane/db/migrations/0122_profile_comment_submit_shortcut.py create mode 100644 apps/web/core/components/settings/profile/content/pages/preferences/comment-shortcut-selector.tsx create mode 100644 packages/i18n/src/locales/cs/profile-preferences.json create mode 100644 packages/i18n/src/locales/de/profile-preferences.json create mode 100644 packages/i18n/src/locales/en/profile-preferences.json create mode 100644 packages/i18n/src/locales/es/profile-preferences.json create mode 100644 packages/i18n/src/locales/fr/profile-preferences.json create mode 100644 packages/i18n/src/locales/id/profile-preferences.json create mode 100644 packages/i18n/src/locales/it/profile-preferences.json create mode 100644 packages/i18n/src/locales/ja/profile-preferences.json create mode 100644 packages/i18n/src/locales/ko/profile-preferences.json create mode 100644 packages/i18n/src/locales/pl/profile-preferences.json create mode 100644 packages/i18n/src/locales/pt-BR/profile-preferences.json create mode 100644 packages/i18n/src/locales/ro/profile-preferences.json create mode 100644 packages/i18n/src/locales/ru/profile-preferences.json create mode 100644 packages/i18n/src/locales/sk/profile-preferences.json create mode 100644 packages/i18n/src/locales/tr-TR/profile-preferences.json create mode 100644 packages/i18n/src/locales/ua/profile-preferences.json create mode 100644 packages/i18n/src/locales/vi-VN/profile-preferences.json create mode 100644 packages/i18n/src/locales/zh-CN/profile-preferences.json create mode 100644 packages/i18n/src/locales/zh-TW/profile-preferences.json create mode 100644 packages/utils/src/comment.ts diff --git a/apps/api/plane/db/migrations/0122_profile_comment_submit_shortcut.py b/apps/api/plane/db/migrations/0122_profile_comment_submit_shortcut.py new file mode 100644 index 00000000000..64f7da64606 --- /dev/null +++ b/apps/api/plane/db/migrations/0122_profile_comment_submit_shortcut.py @@ -0,0 +1,21 @@ +# Generated for comment_submit_shortcut user preference + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("db", "0121_alter_estimate_type"), + ] + + operations = [ + migrations.AddField( + model_name="profile", + name="comment_submit_shortcut", + field=models.CharField( + choices=[("enter", "Enter"), ("mod_enter", "Mod+Enter")], + default="enter", + max_length=20, + ), + ), + ] diff --git a/apps/api/plane/db/models/user.py b/apps/api/plane/db/models/user.py index 7f1ab162dab..eb10c19a5d4 100644 --- a/apps/api/plane/db/models/user.py +++ b/apps/api/plane/db/models/user.py @@ -210,6 +210,10 @@ class NotificationViewMode(models.TextChoices): FULL = "full", "Full" COMPACT = "compact", "Compact" + class CommentSubmitShortcut(models.TextChoices): + ENTER = "enter", "Enter" + MOD_ENTER = "mod_enter", "Mod+Enter" + START_OF_THE_WEEK_CHOICES = ( (SUNDAY, "Sunday"), (MONDAY, "Monday"), @@ -243,6 +247,9 @@ class NotificationViewMode(models.TextChoices): max_length=255, choices=NotificationViewMode.choices, default=NotificationViewMode.FULL ) is_smooth_cursor_enabled = models.BooleanField(default=False) + comment_submit_shortcut = models.CharField( + max_length=20, choices=CommentSubmitShortcut.choices, default=CommentSubmitShortcut.ENTER + ) # mobile is_mobile_onboarded = models.BooleanField(default=False) mobile_onboarding_step = models.JSONField(default=get_mobile_default_onboarding) diff --git a/apps/space/components/editor/lite-text-editor.tsx b/apps/space/components/editor/lite-text-editor.tsx index ecd08da7002..ed8d39086c1 100644 --- a/apps/space/components/editor/lite-text-editor.tsx +++ b/apps/space/components/editor/lite-text-editor.tsx @@ -13,6 +13,7 @@ import { cn, isCommentEmpty } from "@plane/utils"; // helpers import { getEditorFileHandlers } from "@/helpers/editor.helper"; // hooks +import { useUserProfile } from "@/hooks/store/use-user-profile"; import { useParseEditorContent } from "@/hooks/use-parse-editor-content"; // plane web imports import { useEditorFlagging } from "@/hooks/use-editor-flagging"; @@ -57,6 +58,9 @@ export const LiteTextEditor = React.forwardRef(function LiteTextEditor( } // derived values const isEmpty = isCommentEmpty(props.initialValue); + const { + data: { comment_submit_shortcut }, + } = useUserProfile(); const editorRef = isMutableRefObject(ref) ? ref.current : null; const { liteText: liteTextEditorExtensions } = useEditorFlagging(anchor); // parse content @@ -80,6 +84,7 @@ export const LiteTextEditor = React.forwardRef(function LiteTextEditor( mentionHandler={{ renderComponent: (props) => , }} + submitShortcut={comment_submit_shortcut} extendedEditorProps={{}} {...rest} // overriding the containerClassName to add relative class passed diff --git a/apps/space/store/profile.store.ts b/apps/space/store/profile.store.ts index d62d1d37a38..d59a4f97186 100644 --- a/apps/space/store/profile.store.ts +++ b/apps/space/store/profile.store.ts @@ -59,6 +59,7 @@ export class ProfileStore implements IProfileStore { updated_at: "", language: "", start_of_the_week: EStartOfTheWeek.SUNDAY, + comment_submit_shortcut: "enter", }; // services diff --git a/apps/web/core/components/comments/card/edit-form.tsx b/apps/web/core/components/comments/card/edit-form.tsx index 9be8b135521..af0f732bd88 100644 --- a/apps/web/core/components/comments/card/edit-form.tsx +++ b/apps/web/core/components/comments/card/edit-form.tsx @@ -11,9 +11,11 @@ import type { EditorRefApi } from "@plane/editor"; import { CheckIcon, CloseIcon } from "@plane/propel/icons"; // plane imports import type { TCommentsOperations, TIssueComment } from "@plane/types"; -import { cn, isCommentEmpty } from "@plane/utils"; +import { cn, isCommentEmpty, isCommentSubmissionValid } from "@plane/utils"; // components import { LiteTextEditor } from "@/components/editor/lite-text"; +// hooks +import { useUserProfile } from "@/hooks/store/user"; type Props = { activityOperations: TCommentsOperations; @@ -39,6 +41,10 @@ export const CommentCardEditForm = observer(function CommentCardEditForm(props: } = props; // refs const editorRef = useRef(null); + // store hooks + const { + data: { comment_submit_shortcut }, + } = useUserProfile(); // form info const { formState: { isSubmitting }, @@ -77,7 +83,16 @@ export const CommentCardEditForm = observer(function CommentCardEditForm(props:
{ - if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey && !isEmpty) handleSubmit(onEnter)(e); + if ( + isCommentSubmissionValid({ + e, + shortcut: comment_submit_shortcut, + isEmpty, + isSubmitting, + isEditorReadyToDiscard: !!editorRef.current?.isEditorReadyToDiscard(), + }) + ) + handleSubmit(onEnter)(e); }} > (null); // store hooks const workspaceStore = useWorkspace(); + const { + data: { comment_submit_shortcut }, + } = useUserProfile(); // derived values const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string; // form info @@ -95,13 +99,13 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea className={cn("sticky bottom-0 z-[4] bg-surface-1 sm:static")} onKeyDown={(e) => { if ( - e.key === "Enter" && - !e.shiftKey && - !e.ctrlKey && - !e.metaKey && - !isEmpty && - !isSubmitting && - editorRef.current?.isEditorReadyToDiscard() + isCommentSubmissionValid({ + e, + shortcut: comment_submit_shortcut, + isEmpty, + isSubmitting, + isEditorReadyToDiscard: !!editorRef.current?.isEditorReadyToDiscard(), + }) ) handleSubmit(onSubmit)(e); }} diff --git a/apps/web/core/components/editor/lite-text/editor.tsx b/apps/web/core/components/editor/lite-text/editor.tsx index 257f106f8c0..4a8f75f7278 100644 --- a/apps/web/core/components/editor/lite-text/editor.tsx +++ b/apps/web/core/components/editor/lite-text/editor.tsx @@ -19,6 +19,7 @@ import { IssueCommentToolbar } from "@/components/editor/lite-text/toolbar"; // hooks import { useEditorConfig, useEditorMention } from "@/hooks/editor"; import { useMember } from "@/hooks/store/use-member"; +import { useUserProfile } from "@/hooks/store/user"; import { useParseEditorContent } from "@/hooks/use-parse-editor-content"; // plane web hooks import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging"; @@ -95,6 +96,9 @@ export const LiteTextEditor = React.forwardRef(function LiteTextEditor( }); // store hooks const { getUserDetails } = useMember(); + const { + data: { comment_submit_shortcut }, + } = useUserProfile(); // parse content const { getEditorMetaData } = useParseEditorContent({ projectId, @@ -168,6 +172,7 @@ export const LiteTextEditor = React.forwardRef(function LiteTextEditor( "p-2": !editable, })} extendedEditorProps={{}} + submitShortcut={comment_submit_shortcut} editorClassName={editorClassName} {...rest} /> diff --git a/apps/web/core/components/settings/profile/content/pages/preferences/comment-shortcut-selector.tsx b/apps/web/core/components/settings/profile/content/pages/preferences/comment-shortcut-selector.tsx new file mode 100644 index 00000000000..0138a8e2beb --- /dev/null +++ b/apps/web/core/components/settings/profile/content/pages/preferences/comment-shortcut-selector.tsx @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import { observer } from "mobx-react"; +// plane imports +import { useTranslation } from "@plane/i18n"; +import type { CommentSubmitShortcut } from "@plane/types"; +import { CustomSelect } from "@plane/ui"; +import { getIsMac } from "@plane/utils"; +// components +import { SettingsControlItem } from "@/components/settings/control-item"; +// hooks +import { useUserProfile } from "@/hooks/store/user"; + +const isMac = getIsMac(); + +const COMMENT_SUBMIT_SHORTCUT_OPTIONS: { value: CommentSubmitShortcut; label: string }[] = [ + { value: "enter", label: "Enter" }, + { value: "mod_enter", label: isMac ? "⌘ + Enter" : "Ctrl + Enter" }, +]; + +export const CommentShortcutSelector = observer(function CommentShortcutSelector() { + // store hooks + const { + data: { comment_submit_shortcut }, + updateUserProfile, + } = useUserProfile(); + // derived values + const selectedLabel = COMMENT_SUBMIT_SHORTCUT_OPTIONS.find((o) => o.value === comment_submit_shortcut)?.label; + // translation + const { t } = useTranslation(); + + return ( + { + updateUserProfile({ comment_submit_shortcut: value }); + }} + buttonClassName="border border-subtle-1" + input + placement="bottom-end" + > + {COMMENT_SUBMIT_SHORTCUT_OPTIONS.map((option) => ( + + {option.label} + + ))} + + } + /> + ); +}); diff --git a/apps/web/core/components/settings/profile/content/pages/preferences/default-list.tsx b/apps/web/core/components/settings/profile/content/pages/preferences/default-list.tsx index 8b3b010fcaf..4130090ab91 100644 --- a/apps/web/core/components/settings/profile/content/pages/preferences/default-list.tsx +++ b/apps/web/core/components/settings/profile/content/pages/preferences/default-list.tsx @@ -7,6 +7,8 @@ import { observer } from "mobx-react"; // components import { ThemeSwitcher } from "@/plane-web/components/preferences/theme-switcher"; +// local imports +import { CommentShortcutSelector } from "./comment-shortcut-selector"; export const ProfileSettingsDefaultPreferencesList = observer(function ProfileSettingsDefaultPreferencesList() { return ( @@ -18,6 +20,7 @@ export const ProfileSettingsDefaultPreferencesList = observer(function ProfileSe description: "select_or_customize_your_interface_color_scheme", }} /> +
); }); diff --git a/apps/web/core/store/user/profile.store.ts b/apps/web/core/store/user/profile.store.ts index d9024c4fe67..d3c667b1537 100644 --- a/apps/web/core/store/user/profile.store.ts +++ b/apps/web/core/store/user/profile.store.ts @@ -66,6 +66,7 @@ export class ProfileStore implements IUserProfileStore { updated_at: "", language: "", start_of_the_week: EStartOfTheWeek.SUNDAY, + comment_submit_shortcut: "enter", }; // services diff --git a/packages/editor/src/core/components/editors/lite-text/editor.tsx b/packages/editor/src/core/components/editors/lite-text/editor.tsx index 41f986ea097..6da57f74de7 100644 --- a/packages/editor/src/core/components/editors/lite-text/editor.tsx +++ b/packages/editor/src/core/components/editors/lite-text/editor.tsx @@ -13,17 +13,17 @@ import { EnterKeyExtension } from "@/extensions"; import type { EditorRefApi, ILiteTextEditorProps } from "@/types"; function LiteTextEditor(props: ILiteTextEditorProps) { - const { onEnterKeyPress, disabledExtensions, extensions: externalExtensions = [] } = props; + const { onEnterKeyPress, submitShortcut, disabledExtensions, extensions: externalExtensions = [] } = props; const extensions = useMemo(() => { const resolvedExtensions = [...externalExtensions]; if (!disabledExtensions?.includes("enter-key")) { - resolvedExtensions.push(EnterKeyExtension(onEnterKeyPress)); + resolvedExtensions.push(EnterKeyExtension({ onEnterKeyPress, submitShortcut })); } return resolvedExtensions; - }, [externalExtensions, disabledExtensions, onEnterKeyPress]); + }, [externalExtensions, disabledExtensions, onEnterKeyPress, submitShortcut]); return ; } diff --git a/packages/editor/src/core/extensions/enter-key.ts b/packages/editor/src/core/extensions/enter-key.ts index bfe86b1de98..2e5c328cb94 100644 --- a/packages/editor/src/core/extensions/enter-key.ts +++ b/packages/editor/src/core/extensions/enter-key.ts @@ -4,35 +4,61 @@ * See the LICENSE file for details. */ +import type { Editor } from "@tiptap/core"; import { Extension } from "@tiptap/core"; +// plane imports +import type { CommentSubmitShortcut } from "@plane/types"; // constants import { CORE_EXTENSIONS } from "@/constants/extension"; -export const EnterKeyExtension = (onEnterKeyPress?: () => void) => - Extension.create({ - name: CORE_EXTENSIONS.ENTER_KEY, +type EnterKeyExtensionArgs = { + onEnterKeyPress?: () => void; + submitShortcut?: CommentSubmitShortcut; +}; - addKeyboardShortcuts(this) { - return { - Enter: () => { - const { activeDropbarExtensions } = this.editor.storage.utility; +export const EnterKeyExtension = ({ onEnterKeyPress, submitShortcut = "enter" }: EnterKeyExtensionArgs) => { + const newLine = ({ editor }: { editor: Editor }) => + editor.commands.first(({ commands }) => [ + () => commands.newlineInCode(), + () => commands.splitListItem(CORE_EXTENSIONS.LIST_ITEM), + () => commands.splitListItem(CORE_EXTENSIONS.TASK_ITEM), + () => commands.createParagraphNear(), + () => commands.liftEmptyBlock(), + () => commands.splitBlock(), + ]); - if (activeDropbarExtensions.length === 0) { - onEnterKeyPress?.(); - return true; - } + const submit = (editor: Editor) => { + const activeDropbarExtensions = editor.storage.utility?.activeDropbarExtensions ?? []; + if (activeDropbarExtensions.length === 0) { + onEnterKeyPress?.(); + return true; + } + return false; + }; - return false; - }, - "Shift-Enter": ({ editor }) => - editor.commands.first(({ commands }) => [ - () => commands.newlineInCode(), - () => commands.splitListItem(CORE_EXTENSIONS.LIST_ITEM), - () => commands.splitListItem(CORE_EXTENSIONS.TASK_ITEM), - () => commands.createParagraphNear(), - () => commands.liftEmptyBlock(), - () => commands.splitBlock(), - ]), - }; + return Extension.create({ + name: CORE_EXTENSIONS.ENTER_KEY, + + addKeyboardShortcuts(this) { + if (submitShortcut === "enter") { + return { + // Shift+Enter always inserts a new line (its default behavior) + "Shift-Enter": newLine, + // Enter submits + Enter: () => submit(this.editor), + // Mod+Enter always inserts a line-break (its default behavior) + "Mod-Enter": () => false, + }; + } else { + return { + // Shift+Enter always inserts a line-break (its default behavior) + "Shift-Enter": () => false, + // Enter always inserts a new line (its default behavior) + Enter: () => false, + // Mod+Enter submits + "Mod-Enter": () => submit(this.editor), + }; + } }, }); +}; diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 5da8e419a41..cc4d57c0d22 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -10,6 +10,7 @@ import type { Selection } from "@tiptap/pm/state"; import type { EditorProps, EditorView } from "@tiptap/pm/view"; import type { NodeViewProps as TNodeViewProps } from "@tiptap/react"; // plane imports +import type { CommentSubmitShortcut } from "@plane/types"; import type { TCustomComponentsMetaData } from "@plane/utils"; // extension types import type { TTextAlign } from "@/extensions"; @@ -184,7 +185,9 @@ export type IEditorProps = { workItemIdentifier?: string | null; }; -export type ILiteTextEditorProps = IEditorProps; +export type ILiteTextEditorProps = IEditorProps & { + submitShortcut?: CommentSubmitShortcut; +}; export type IRichTextEditorProps = IEditorProps & { dragDropEnabled?: boolean; diff --git a/packages/i18n/src/constants/namespaces.ts b/packages/i18n/src/constants/namespaces.ts index 3a75ed2f7ef..b1b3c160302 100644 --- a/packages/i18n/src/constants/namespaces.ts +++ b/packages/i18n/src/constants/namespaces.ts @@ -20,6 +20,7 @@ export const NAMESPACES = [ "notification", "page", "power-k", + "profile-preferences", "project", "project-settings", "settings", diff --git a/packages/i18n/src/locales/cs/profile-preferences.json b/packages/i18n/src/locales/cs/profile-preferences.json new file mode 100644 index 00000000000..c3260373170 --- /dev/null +++ b/packages/i18n/src/locales/cs/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Klávesová zkratka pro odeslání komentáře", + "description": "Vyberte klávesovou zkratku pro odesílání komentářů." + } +} diff --git a/packages/i18n/src/locales/de/profile-preferences.json b/packages/i18n/src/locales/de/profile-preferences.json new file mode 100644 index 00000000000..3468ea3d150 --- /dev/null +++ b/packages/i18n/src/locales/de/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Tastenkürzel zum Senden von Kommentaren", + "description": "Wählen Sie die Tastenkombination zum Absenden von Kommentaren." + } +} diff --git a/packages/i18n/src/locales/en/profile-preferences.json b/packages/i18n/src/locales/en/profile-preferences.json new file mode 100644 index 00000000000..185df6c64a7 --- /dev/null +++ b/packages/i18n/src/locales/en/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Comment submit shortcut", + "description": "Choose the keyboard shortcut to submit comments." + } +} diff --git a/packages/i18n/src/locales/es/profile-preferences.json b/packages/i18n/src/locales/es/profile-preferences.json new file mode 100644 index 00000000000..e169ed33611 --- /dev/null +++ b/packages/i18n/src/locales/es/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Atajo para enviar comentarios", + "description": "Elige el atajo de teclado para enviar comentarios." + } +} diff --git a/packages/i18n/src/locales/fr/profile-preferences.json b/packages/i18n/src/locales/fr/profile-preferences.json new file mode 100644 index 00000000000..c7f34ebd33a --- /dev/null +++ b/packages/i18n/src/locales/fr/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Raccourci pour soumettre des commentaires", + "description": "Choisissez le raccourci clavier pour soumettre des commentaires." + } +} diff --git a/packages/i18n/src/locales/id/profile-preferences.json b/packages/i18n/src/locales/id/profile-preferences.json new file mode 100644 index 00000000000..e519b0008c5 --- /dev/null +++ b/packages/i18n/src/locales/id/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Pintasan keyboard untuk mengirim komentar", + "description": "Pilih pintasan keyboard untuk mengirim komentar." + } +} diff --git a/packages/i18n/src/locales/it/profile-preferences.json b/packages/i18n/src/locales/it/profile-preferences.json new file mode 100644 index 00000000000..eff045da390 --- /dev/null +++ b/packages/i18n/src/locales/it/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Scorciatoia per inviare commenti", + "description": "Scegli la scorciatoia da tastiera per inviare commenti." + } +} diff --git a/packages/i18n/src/locales/ja/profile-preferences.json b/packages/i18n/src/locales/ja/profile-preferences.json new file mode 100644 index 00000000000..6965fed8624 --- /dev/null +++ b/packages/i18n/src/locales/ja/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "コメント送信のショートカット", + "description": "コメントを送信するキーボードショートカットを選択してください。" + } +} diff --git a/packages/i18n/src/locales/ko/profile-preferences.json b/packages/i18n/src/locales/ko/profile-preferences.json new file mode 100644 index 00000000000..d1e4e3f362b --- /dev/null +++ b/packages/i18n/src/locales/ko/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "댓글 제출 단축키", + "description": "댓글을 제출할 키보드 단축키를 선택하세요." + } +} diff --git a/packages/i18n/src/locales/pl/profile-preferences.json b/packages/i18n/src/locales/pl/profile-preferences.json new file mode 100644 index 00000000000..995618e2a97 --- /dev/null +++ b/packages/i18n/src/locales/pl/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Skrót klawiszowy do wysyłania komentarzy", + "description": "Wybierz skrót klawiszowy do wysyłania komentarzy." + } +} diff --git a/packages/i18n/src/locales/pt-BR/profile-preferences.json b/packages/i18n/src/locales/pt-BR/profile-preferences.json new file mode 100644 index 00000000000..4a9429ff609 --- /dev/null +++ b/packages/i18n/src/locales/pt-BR/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Atalho para enviar comentários", + "description": "Escolha o atalho de teclado para enviar comentários." + } +} diff --git a/packages/i18n/src/locales/ro/profile-preferences.json b/packages/i18n/src/locales/ro/profile-preferences.json new file mode 100644 index 00000000000..5e1b1fe6ba4 --- /dev/null +++ b/packages/i18n/src/locales/ro/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Comandă rapidă pentru trimiterea comentariilor", + "description": "Alegeți comanda rapidă de la tastatură pentru a trimite comentarii." + } +} diff --git a/packages/i18n/src/locales/ru/profile-preferences.json b/packages/i18n/src/locales/ru/profile-preferences.json new file mode 100644 index 00000000000..dabf4a7ecea --- /dev/null +++ b/packages/i18n/src/locales/ru/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Горячая клавиша для отправки комментариев", + "description": "Выберите сочетание клавиш для отправки комментариев." + } +} diff --git a/packages/i18n/src/locales/sk/profile-preferences.json b/packages/i18n/src/locales/sk/profile-preferences.json new file mode 100644 index 00000000000..17304d82c18 --- /dev/null +++ b/packages/i18n/src/locales/sk/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Klávesová skratka na odoslanie komentára", + "description": "Vyberte klávesovú skratku na odosielanie komentárov." + } +} diff --git a/packages/i18n/src/locales/tr-TR/profile-preferences.json b/packages/i18n/src/locales/tr-TR/profile-preferences.json new file mode 100644 index 00000000000..77b8fe66c80 --- /dev/null +++ b/packages/i18n/src/locales/tr-TR/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Yorum gönderme kısayolu", + "description": "Yorum göndermek için klavye kısayolunu seçin." + } +} diff --git a/packages/i18n/src/locales/ua/profile-preferences.json b/packages/i18n/src/locales/ua/profile-preferences.json new file mode 100644 index 00000000000..42282b30160 --- /dev/null +++ b/packages/i18n/src/locales/ua/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Гарячі клавіші для надсилання коментарів", + "description": "Виберіть комбінацію клавіш для надсилання коментарів." + } +} diff --git a/packages/i18n/src/locales/vi-VN/profile-preferences.json b/packages/i18n/src/locales/vi-VN/profile-preferences.json new file mode 100644 index 00000000000..5d1b98c616c --- /dev/null +++ b/packages/i18n/src/locales/vi-VN/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "Phím tắt gửi bình luận", + "description": "Chọn phím tắt bàn phím để gửi bình luận." + } +} diff --git a/packages/i18n/src/locales/zh-CN/profile-preferences.json b/packages/i18n/src/locales/zh-CN/profile-preferences.json new file mode 100644 index 00000000000..dce960f5ffd --- /dev/null +++ b/packages/i18n/src/locales/zh-CN/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "评论提交快捷键", + "description": "选择提交评论的键盘快捷键。" + } +} diff --git a/packages/i18n/src/locales/zh-TW/profile-preferences.json b/packages/i18n/src/locales/zh-TW/profile-preferences.json new file mode 100644 index 00000000000..c263322177d --- /dev/null +++ b/packages/i18n/src/locales/zh-TW/profile-preferences.json @@ -0,0 +1,6 @@ +{ + "comment_submit_shortcut": { + "label": "留言提交快捷鍵", + "description": "選擇提交留言的鍵盤快捷鍵。" + } +} diff --git a/packages/types/src/users.ts b/packages/types/src/users.ts index 7391343c8a0..64d486f475f 100644 --- a/packages/types/src/users.ts +++ b/packages/types/src/users.ts @@ -59,6 +59,8 @@ export interface IUserAccount { updated_at: Date; } +export type CommentSubmitShortcut = "enter" | "mod_enter"; + export type TUserProfile = { id: string | undefined; user: string | undefined; @@ -79,6 +81,7 @@ export type TUserProfile = { has_billing_address: boolean; has_marketing_email_consent: boolean; language: string; + comment_submit_shortcut: CommentSubmitShortcut; created_at: Date | string; updated_at: Date | string; start_of_the_week: EStartOfTheWeek; diff --git a/packages/utils/src/comment.ts b/packages/utils/src/comment.ts new file mode 100644 index 00000000000..06832d4ad32 --- /dev/null +++ b/packages/utils/src/comment.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import type { CommentSubmitShortcut } from "@plane/types"; + +type IsCommentSubmissionValidArgs = { + e: React.KeyboardEvent; + shortcut: CommentSubmitShortcut; + isEmpty: boolean; + isSubmitting: boolean; + isEditorReadyToDiscard: boolean; +}; + +/** + * Returns true if the keyboard event matches the configured comment submit shortcut and- + * - the comment is not empty, + * - the editor is ready to discard, + * - the form is not submitting. + */ +export const isCommentSubmissionValid = ({ + e, + shortcut = "enter", + isEmpty, + isSubmitting, + isEditorReadyToDiscard, +}: IsCommentSubmissionValidArgs): boolean => { + if (e.key !== "Enter" || e.shiftKey) return false; + const isShortcutValid = shortcut === "enter" ? !e.ctrlKey && !e.metaKey : e.ctrlKey || e.metaKey; + return isShortcutValid && !isEmpty && !isSubmitting && isEditorReadyToDiscard; +}; diff --git a/packages/utils/src/common.ts b/packages/utils/src/common.ts index 68fe30b413a..77d777ed71a 100644 --- a/packages/utils/src/common.ts +++ b/packages/utils/src/common.ts @@ -121,3 +121,5 @@ export const isComplete = (obj: CompleteOrEmpty): obj is T => { }; export const convertRemToPixel = (rem: number): number => rem * 0.9 * 16; + +export const getIsMac = () => typeof window !== "undefined" && navigator.userAgent.indexOf("Mac") !== -1; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 450acb4b275..ce5a701e23a 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -9,6 +9,7 @@ export * from "./attachment"; export * from "./auth"; export * from "./calendar"; export * from "./color"; +export * from "./comment"; export * from "./common"; export * from "./cycle"; export * from "./datetime";