Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b6a0915ee |
@@ -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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -210,6 +210,10 @@ class Profile(TimeAuditModel):
|
||||
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 Profile(TimeAuditModel):
|
||||
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)
|
||||
|
||||
@@ -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<EditorRefApi>(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) => <EditorMentionsRoot {...props} />,
|
||||
}}
|
||||
submitShortcut={comment_submit_shortcut}
|
||||
extendedEditorProps={{}}
|
||||
{...rest}
|
||||
// overriding the containerClassName to add relative class passed
|
||||
|
||||
@@ -59,6 +59,7 @@ export class ProfileStore implements IProfileStore {
|
||||
updated_at: "",
|
||||
language: "",
|
||||
start_of_the_week: EStartOfTheWeek.SUNDAY,
|
||||
comment_submit_shortcut: "enter",
|
||||
};
|
||||
|
||||
// services
|
||||
|
||||
@@ -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<EditorRefApi>(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:
|
||||
<form className="flex flex-col gap-2">
|
||||
<div
|
||||
onKeyDown={(e) => {
|
||||
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);
|
||||
}}
|
||||
>
|
||||
<LiteTextEditor
|
||||
|
||||
@@ -11,10 +11,11 @@ import { useForm, Controller } from "react-hook-form";
|
||||
import { EIssueCommentAccessSpecifier } from "@plane/constants";
|
||||
import type { EditorRefApi } from "@plane/editor";
|
||||
import type { TIssueComment, TCommentsOperations } 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";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
// services
|
||||
import { FileService } from "@/services/file.service";
|
||||
@@ -46,6 +47,9 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
|
||||
const editorRef = useRef<EditorRefApi>(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);
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
+60
@@ -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 (
|
||||
<SettingsControlItem
|
||||
title={t("comment_submit_shortcut.label")}
|
||||
description={t("comment_submit_shortcut.description")}
|
||||
control={
|
||||
<CustomSelect
|
||||
value={comment_submit_shortcut}
|
||||
label={selectedLabel}
|
||||
onChange={(value: CommentSubmitShortcut) => {
|
||||
updateUserProfile({ comment_submit_shortcut: value });
|
||||
}}
|
||||
buttonClassName="border border-subtle-1"
|
||||
input
|
||||
placement="bottom-end"
|
||||
>
|
||||
{COMMENT_SUBMIT_SHORTCUT_OPTIONS.map((option) => (
|
||||
<CustomSelect.Option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -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",
|
||||
}}
|
||||
/>
|
||||
<CommentShortcutSelector />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -66,6 +66,7 @@ export class ProfileStore implements IUserProfileStore {
|
||||
updated_at: "",
|
||||
language: "",
|
||||
start_of_the_week: EStartOfTheWeek.SUNDAY,
|
||||
comment_submit_shortcut: "enter",
|
||||
};
|
||||
|
||||
// services
|
||||
|
||||
@@ -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 <EditorWrapper {...props} extensions={extensions} />;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
type EnterKeyExtensionArgs = {
|
||||
onEnterKeyPress?: () => void;
|
||||
submitShortcut?: CommentSubmitShortcut;
|
||||
};
|
||||
|
||||
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(),
|
||||
]);
|
||||
|
||||
const submit = (editor: Editor) => {
|
||||
const activeDropbarExtensions = editor.storage.utility?.activeDropbarExtensions ?? [];
|
||||
if (activeDropbarExtensions.length === 0) {
|
||||
onEnterKeyPress?.();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return Extension.create({
|
||||
name: CORE_EXTENSIONS.ENTER_KEY,
|
||||
|
||||
addKeyboardShortcuts(this) {
|
||||
return {
|
||||
Enter: () => {
|
||||
const { activeDropbarExtensions } = this.editor.storage.utility;
|
||||
|
||||
if (activeDropbarExtensions.length === 0) {
|
||||
onEnterKeyPress?.();
|
||||
return true;
|
||||
}
|
||||
|
||||
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(),
|
||||
]),
|
||||
};
|
||||
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),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,6 +20,7 @@ export const NAMESPACES = [
|
||||
"notification",
|
||||
"page",
|
||||
"power-k",
|
||||
"profile-preferences",
|
||||
"project",
|
||||
"project-settings",
|
||||
"settings",
|
||||
|
||||
@@ -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ářů."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Tastenkürzel zum Senden von Kommentaren",
|
||||
"description": "Wählen Sie die Tastenkombination zum Absenden von Kommentaren."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Comment submit shortcut",
|
||||
"description": "Choose the keyboard shortcut to submit comments."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Atajo para enviar comentarios",
|
||||
"description": "Elige el atajo de teclado para enviar comentarios."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Raccourci pour soumettre des commentaires",
|
||||
"description": "Choisissez le raccourci clavier pour soumettre des commentaires."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Pintasan keyboard untuk mengirim komentar",
|
||||
"description": "Pilih pintasan keyboard untuk mengirim komentar."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Scorciatoia per inviare commenti",
|
||||
"description": "Scegli la scorciatoia da tastiera per inviare commenti."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "コメント送信のショートカット",
|
||||
"description": "コメントを送信するキーボードショートカットを選択してください。"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "댓글 제출 단축키",
|
||||
"description": "댓글을 제출할 키보드 단축키를 선택하세요."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Skrót klawiszowy do wysyłania komentarzy",
|
||||
"description": "Wybierz skrót klawiszowy do wysyłania komentarzy."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Atalho para enviar comentários",
|
||||
"description": "Escolha o atalho de teclado para enviar comentários."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Горячая клавиша для отправки комментариев",
|
||||
"description": "Выберите сочетание клавиш для отправки комментариев."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Klávesová skratka na odoslanie komentára",
|
||||
"description": "Vyberte klávesovú skratku na odosielanie komentárov."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Yorum gönderme kısayolu",
|
||||
"description": "Yorum göndermek için klavye kısayolunu seçin."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "Гарячі клавіші для надсилання коментарів",
|
||||
"description": "Виберіть комбінацію клавіш для надсилання коментарів."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "评论提交快捷键",
|
||||
"description": "选择提交评论的键盘快捷键。"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment_submit_shortcut": {
|
||||
"label": "留言提交快捷鍵",
|
||||
"description": "選擇提交留言的鍵盤快捷鍵。"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -121,3 +121,5 @@ export const isComplete = <T>(obj: CompleteOrEmpty<T>): obj is T => {
|
||||
};
|
||||
|
||||
export const convertRemToPixel = (rem: number): number => rem * 0.9 * 16;
|
||||
|
||||
export const getIsMac = () => typeof window !== "undefined" && navigator.userAgent.indexOf("Mac") !== -1;
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user