From d22d08e99f89f47705e7dd709874e34cb4bcf666 Mon Sep 17 00:00:00 2001 From: Zoe <62722391+juls0730@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:47:33 -0600 Subject: [PATCH] feat: make shortcuts easier to add across the app --- app/components/ChatInput.vue | 17 +++++++--- app/components/Dialog/index.vue | 8 ++++- app/composables/useKeyboardShortcuts.ts | 45 ++++++++++++++++++------- app/layouts/default.vue | 8 +++-- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/app/components/ChatInput.vue b/app/components/ChatInput.vue index 5374771..c277a10 100644 --- a/app/components/ChatInput.vue +++ b/app/components/ChatInput.vue @@ -107,13 +107,20 @@ const handleKeyDown = async (event: KeyboardEvent) => { }; const handleWindowKeyDown = async (event: KeyboardEvent) => { - if (event.ctrlKey || event.metaKey) { - return; - } + if (event.defaultPrevented) return; + if (event.ctrlKey || event.metaKey || event.altKey) return; if (document.activeElement === document.body) { - // redirect all standard keyboard input to the input field - inputRef.value?.focus(); + const isPrintable = event.key.length === 1; + + if (isPrintable) { + inputRef.value?.focus(); + } else if (event.key === 'Enter') { + event.preventDefault(); + handleSubmit(); + inputRef.value?.focus(); + } + } }; diff --git a/app/components/Dialog/index.vue b/app/components/Dialog/index.vue index ac65a3c..038c9ff 100644 --- a/app/components/Dialog/index.vue +++ b/app/components/Dialog/index.vue @@ -4,7 +4,12 @@ import Settings from './Settings.vue'; import { DialogType } from '~/composables/useDialog'; -const { open, page, data, close, confirm } = useDialog(); +const { open, page, data, close, confirm, openDialog } = useDialog(); +const { addShortcut } = useKeyboardShortcuts(); + +const unbindShortcut = addShortcut(['ctrl', ','], () => { + openDialog(DialogType.Settings); +}); const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { @@ -24,6 +29,7 @@ onUnmounted(() => { if (open.value) { document.body.removeEventListener('keydown', handleKeyDown); } + unbindShortcut(); }); diff --git a/app/composables/useKeyboardShortcuts.ts b/app/composables/useKeyboardShortcuts.ts index 9bff6f2..782914b 100644 --- a/app/composables/useKeyboardShortcuts.ts +++ b/app/composables/useKeyboardShortcuts.ts @@ -1,23 +1,41 @@ export const useKeyboardShortcuts = () => { - const { toggle: toggleSidebar } = useSidebar(); - const { toggle: openSettings, open: isSettingsOpen } = useSettings(); + const shortcutsMap = new Map void>(); + + const normalize = (combo: string[]) => + combo.map(k => k.toLowerCase()).sort().join('+'); + + const addShortcut = (combo: string[], callback: () => void) => { + const normalizedKey = normalize(combo); + shortcutsMap.set(normalizedKey, callback); + + return () => { + shortcutsMap.delete(normalizedKey); + }; + }; const handleKeyDown = (event: KeyboardEvent) => { - // Ctrl+[ to collapse sidebar - if (event.ctrlKey && event.key === '[') { - event.preventDefault(); - toggleSidebar(); + const isInput = ['INPUT', 'TEXTAREA'].includes((event.target as HTMLElement).tagName); + if (isInput && !event.ctrlKey && !event.metaKey && event.key !== 'Escape') { return; } - if (event.ctrlKey && event.key === ',') { - event.preventDefault(); - if (isSettingsOpen.value) { - return; - } + const keys: string[] = []; + if (event.ctrlKey) keys.push('ctrl'); + if (event.metaKey) keys.push('meta'); + if (event.shiftKey) keys.push('shift'); + if (event.altKey) keys.push('alt'); - openSettings(); - return; + const mainKey = event.key.toLowerCase(); + if (!['control', 'shift', 'meta', 'alt'].includes(mainKey)) { + keys.push(mainKey); + } + + const pressedCombo = normalize(keys); + + if (shortcutsMap.has(pressedCombo)) { + event.preventDefault(); + event.stopPropagation(); + shortcutsMap.get(pressedCombo)!(); } }; @@ -30,6 +48,7 @@ export const useKeyboardShortcuts = () => { }); return { + addShortcut, handleKeyDown, }; }; diff --git a/app/layouts/default.vue b/app/layouts/default.vue index 42070e5..2380925 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -2,14 +2,18 @@ import Dialog from '~/components/Dialog/index.vue'; const { colorScheme } = await useUserSettings(); +const { toggle: toggleSidebar } = useSidebar(); +const { addShortcut } = useKeyboardShortcuts(); + +addShortcut(['ctrl', '['], () => { + toggleSidebar(); +}); useHead({ htmlAttrs: { class: colorScheme.class } }); - -useKeyboardShortcuts();