From d75e0cef4806fdd12c81b1b52572e3a70503b410 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Thu, 12 Mar 2026 08:53:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=8F=91=E9=80=81=E5=92=8C=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8D=A2=E8=A1=8C=E5=BF=AB=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/chat/message-input.tsx | 17 ++++++++++++- src/components/chat/question-dialog.tsx | 7 ++++-- src/components/settings/shortcut-settings.tsx | 4 +++- src/i18n/messages/ar.json | 8 +++++++ src/i18n/messages/de.json | 8 +++++++ src/i18n/messages/en.json | 8 +++++++ src/i18n/messages/es.json | 8 +++++++ src/i18n/messages/fr.json | 8 +++++++ src/i18n/messages/ja.json | 8 +++++++ src/i18n/messages/ko.json | 8 +++++++ src/i18n/messages/pt.json | 8 +++++++ src/i18n/messages/zh-CN.json | 8 +++++++ src/i18n/messages/zh-TW.json | 8 +++++++ src/lib/keyboard-shortcuts.ts | 24 +++++++++++++++++-- 14 files changed, 126 insertions(+), 6 deletions(-) diff --git a/src/components/chat/message-input.tsx b/src/components/chat/message-input.tsx index 365a6b0..32d2047 100644 --- a/src/components/chat/message-input.tsx +++ b/src/components/chat/message-input.tsx @@ -15,6 +15,8 @@ import { import { Textarea } from "@/components/ui/textarea" import { Ellipsis, FileSearch, Plus, Send, Square, X } from "lucide-react" import { cn } from "@/lib/utils" +import { matchShortcutEvent } from "@/lib/keyboard-shortcuts" +import { useShortcutSettings } from "@/hooks/use-shortcut-settings" import { readFileBase64 } from "@/lib/tauri" import { disposeTauriListener } from "@/lib/tauri-listener" import type { @@ -261,6 +263,7 @@ export function MessageInput({ isActive = false, }: MessageInputProps) { const t = useTranslations("Folder.chat.messageInput") + const { shortcuts } = useShortcutSettings() const effectiveDraftStorageKey = draftStorageKey ?? attachmentTabId ?? null const resolvedPlaceholder = placeholder ?? t("askAnything") const [text, setText] = useState(() => { @@ -932,14 +935,26 @@ export function MessageInput({ } } - if (e.key === "Enter" && !e.shiftKey) { + if (matchShortcutEvent(e, shortcuts.send_message)) { e.preventDefault() if (!disabled) handleSend() + } else if (matchShortcutEvent(e, shortcuts.newline_in_message)) { + e.preventDefault() + const textarea = e.currentTarget as HTMLTextAreaElement + const start = textarea.selectionStart + const end = textarea.selectionEnd + const value = textarea.value + const newValue = value.substring(0, start) + "\n" + value.substring(end) + setText(newValue) + requestAnimationFrame(() => { + textarea.selectionStart = textarea.selectionEnd = start + 1 + }) } }, [ disabled, handleSend, + shortcuts, slashMenuOpen, filteredSlashCommands, slashSelectedIndex, diff --git a/src/components/chat/question-dialog.tsx b/src/components/chat/question-dialog.tsx index 0bb455e..b56123c 100644 --- a/src/components/chat/question-dialog.tsx +++ b/src/components/chat/question-dialog.tsx @@ -4,6 +4,8 @@ import { useState, useRef, useEffect, useCallback } from "react" import { useTranslations } from "next-intl" import { MessageCircleQuestion, SendHorizonal } from "lucide-react" import { Button } from "@/components/ui/button" +import { matchShortcutEvent } from "@/lib/keyboard-shortcuts" +import { useShortcutSettings } from "@/hooks/use-shortcut-settings" import type { PendingQuestion } from "@/contexts/acp-connections-context" interface QuestionDialogProps { @@ -13,6 +15,7 @@ interface QuestionDialogProps { export function QuestionDialog({ question, onAnswer }: QuestionDialogProps) { const t = useTranslations("Folder.chat.questionDialog") + const { shortcuts } = useShortcutSettings() const [answer, setAnswer] = useState("") const textareaRef = useRef(null) const prevQuestionIdRef = useRef(null) @@ -40,12 +43,12 @@ export function QuestionDialog({ question, onAnswer }: QuestionDialogProps) { const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { + if (matchShortcutEvent(e, shortcuts.send_message)) { e.preventDefault() handleSubmit() } }, - [handleSubmit] + [handleSubmit, shortcuts] ) if (!question) return null diff --git a/src/components/settings/shortcut-settings.tsx b/src/components/settings/shortcut-settings.tsx index ccdb52f..537e4eb 100644 --- a/src/components/settings/shortcut-settings.tsx +++ b/src/components/settings/shortcut-settings.tsx @@ -8,6 +8,7 @@ import { useIsMac } from "@/hooks/use-is-mac" import { useShortcutSettings } from "@/hooks/use-shortcut-settings" import { DEFAULT_SHORTCUTS, + INPUT_SHORTCUT_IDS, SHORTCUT_DEFINITIONS, type ShortcutActionId, formatShortcutLabel, @@ -64,7 +65,8 @@ export function ShortcutSettings() { return } - const shortcut = shortcutFromKeyboardEvent(event) + const allowNoModifier = INPUT_SHORTCUT_IDS.has(recordingAction) + const shortcut = shortcutFromKeyboardEvent(event, allowNoModifier) if (!shortcut) return const conflict = SHORTCUT_DEFINITIONS.find( diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 0874db8..9729a07 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "إغلاق جميع تبويبات الملفات", "description": "إغلاق جميع تبويبات الملفات في وضع الملفات فقط" + }, + "send_message": { + "title": "إرسال الرسالة", + "description": "إرسال الرسالة الحالية في مربع الإدخال" + }, + "newline_in_message": { + "title": "سطر جديد في الرسالة", + "description": "إدراج سطر جديد في مربع الإدخال" } } }, diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 6f17238..52bd16c 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "Alle Dateitabs schließen", "description": "Schließt alle Dateitabs nur im Dateimodus" + }, + "send_message": { + "title": "Nachricht senden", + "description": "Die aktuelle Nachricht im Eingabefeld senden" + }, + "newline_in_message": { + "title": "Zeilenumbruch einfügen", + "description": "Einen Zeilenumbruch im Eingabefeld einfügen" } } }, diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 85a08a2..43d959f 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "Close All File Tabs", "description": "Close all file tabs in file mode only" + }, + "send_message": { + "title": "Send Message", + "description": "Send the current message in the input box" + }, + "newline_in_message": { + "title": "Newline in Message", + "description": "Insert a newline in the message input box" } } }, diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 3430e4d..f86127d 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "Cerrar todas las pestañas de archivos", "description": "Cierra todas las pestañas de archivos solo en modo de archivos" + }, + "send_message": { + "title": "Enviar mensaje", + "description": "Enviar el mensaje actual en el cuadro de entrada" + }, + "newline_in_message": { + "title": "Nueva línea en mensaje", + "description": "Insertar una nueva línea en el cuadro de entrada" } } }, diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 552a452..30e856a 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "Fermer tous les onglets de fichiers", "description": "Fermer tous les onglets de fichiers uniquement en mode fichiers" + }, + "send_message": { + "title": "Envoyer le message", + "description": "Envoyer le message actuel dans la zone de saisie" + }, + "newline_in_message": { + "title": "Retour à la ligne", + "description": "Insérer un retour à la ligne dans la zone de saisie" } } }, diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 1feac08..d41b1dc 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "すべてのファイルタブを閉じる", "description": "ファイルモードでのみすべてのファイルタブを閉じます" + }, + "send_message": { + "title": "メッセージを送信", + "description": "入力欄のメッセージを送信する" + }, + "newline_in_message": { + "title": "メッセージ内で改行", + "description": "入力欄で改行を挿入する" } } }, diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index c57c83f..bb8e6b8 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "모든 파일 탭 닫기", "description": "파일 모드에서만 모든 파일 탭을 닫습니다" + }, + "send_message": { + "title": "메시지 보내기", + "description": "입력창에서 현재 메시지를 전송" + }, + "newline_in_message": { + "title": "메시지 줄바꿈", + "description": "입력창에 줄바꿈을 삽입" } } }, diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 0c3d2bb..0db30af 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "Fechar todas as abas de arquivo", "description": "Fecha todas as abas de arquivo apenas no modo de arquivos" + }, + "send_message": { + "title": "Enviar mensagem", + "description": "Enviar a mensagem atual na caixa de entrada" + }, + "newline_in_message": { + "title": "Nova linha na mensagem", + "description": "Inserir uma nova linha na caixa de entrada" } } }, diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index f55f6ef..aa3cb30 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "关闭全部文件标签", "description": "仅在文件模式下关闭所有文件标签" + }, + "send_message": { + "title": "发送消息", + "description": "在输入框中发送当前消息" + }, + "newline_in_message": { + "title": "消息换行", + "description": "在输入框中插入换行符" } } }, diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 959ec53..de959ac 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -183,6 +183,14 @@ "close_all_file_tabs": { "title": "關閉全部檔案分頁", "description": "僅在檔案模式下關閉所有檔案分頁" + }, + "send_message": { + "title": "傳送訊息", + "description": "在輸入框中傳送目前的訊息" + }, + "newline_in_message": { + "title": "訊息換行", + "description": "在輸入框中插入換行符" } } }, diff --git a/src/lib/keyboard-shortcuts.ts b/src/lib/keyboard-shortcuts.ts index ed2cee4..5fc7b43 100644 --- a/src/lib/keyboard-shortcuts.ts +++ b/src/lib/keyboard-shortcuts.ts @@ -10,6 +10,8 @@ export type ShortcutActionId = | "open_settings" | "close_current_tab" | "close_all_file_tabs" + | "send_message" + | "newline_in_message" export interface ShortcutDefinition { id: ShortcutActionId @@ -49,8 +51,20 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [ { id: "close_all_file_tabs", }, + { + id: "send_message", + }, + { + id: "newline_in_message", + }, ] +/** Actions that allow shortcuts without modifier keys (e.g. plain Enter). */ +export const INPUT_SHORTCUT_IDS = new Set([ + "send_message", + "newline_in_message", +]) + export type ShortcutSettings = Record export const DEFAULT_SHORTCUTS: ShortcutSettings = { @@ -65,6 +79,8 @@ export const DEFAULT_SHORTCUTS: ShortcutSettings = { open_settings: "mod+,", close_current_tab: "mod+w", close_all_file_tabs: "mod+shift+w", + send_message: "enter", + newline_in_message: "shift+enter", } export const SHORTCUTS_STORAGE_KEY = "settings:shortcuts:v1" @@ -230,12 +246,16 @@ export function shortcutFromKeyboardEvent( event: Pick< KeyboardEvent, "key" | "metaKey" | "ctrlKey" | "altKey" | "shiftKey" - > + >, + /** When true, allow shortcuts without modifier keys (e.g. plain Enter). */ + allowNoModifier = false ): string | null { const keyToken = normalizeKeyToken(event.key) if (!keyToken || MODIFIER_KEY_SET.has(keyToken)) return null - if (!event.metaKey && !event.ctrlKey && !event.altKey) return null + if (!allowNoModifier && !event.metaKey && !event.ctrlKey && !event.altKey) { + return null + } const parts: string[] = [] if (event.metaKey || event.ctrlKey) parts.push("mod")