diff --git a/app/components/@settings/tabs/settings/SettingsTab.tsx b/app/components/@settings/tabs/settings/SettingsTab.tsx index d2cffde9..28a6e1ca 100644 --- a/app/components/@settings/tabs/settings/SettingsTab.tsx +++ b/app/components/@settings/tabs/settings/SettingsTab.tsx @@ -7,6 +7,32 @@ import { themeStore, kTheme } from '~/lib/stores/theme'; import type { UserProfile } from '~/components/@settings/core/types'; import { useStore } from '@nanostores/react'; import { shortcutsStore } from '~/lib/stores/settings'; +import { isMac } from '~/utils/os'; + +// Helper to format shortcut key display +const formatShortcutKey = (key: string) => { + if (key === '`') { + return '`'; + } + + return key.toUpperCase(); +}; + +// Helper to get modifier key symbols/text +const getModifierSymbol = (modifier: string): string => { + switch (modifier) { + case 'meta': + return isMac ? '⌘' : 'Win'; + case 'alt': + return isMac ? '⌥' : 'Alt'; + case 'ctrl': + return isMac ? '⌃' : 'Ctrl'; + case 'shift': + return '⇧'; + default: + return modifier; + } +}; export default function SettingsTab() { const [currentTimezone, setCurrentTimezone] = useState(''); @@ -237,37 +263,42 @@ export default function SettingsTab() { key={name} className="flex items-center justify-between p-2 rounded-lg bg-[#FAFAFA] dark:bg-[#1A1A1A] hover:bg-purple-50 dark:hover:bg-purple-500/10 transition-colors" > - - {name.replace(/([A-Z])/g, ' $1').toLowerCase()} - +
+ + {name.replace(/([A-Z])/g, ' $1').toLowerCase()} + + {shortcut.description && ( + {shortcut.description} + )} +
{shortcut.ctrlOrMetaKey && ( - {navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'} + {getModifierSymbol(isMac ? 'meta' : 'ctrl')} )} {shortcut.ctrlKey && ( - Ctrl + {getModifierSymbol('ctrl')} )} {shortcut.metaKey && ( - ⌘ + {getModifierSymbol('meta')} )} {shortcut.altKey && ( - {navigator.platform.includes('Mac') ? '⌥' : 'Alt'} + {getModifierSymbol('alt')} )} {shortcut.shiftKey && ( - ⇧ + {getModifierSymbol('shift')} )} - {shortcut.key.toUpperCase()} + {formatShortcutKey(shortcut.key)}
diff --git a/app/lib/hooks/useShortcuts.ts b/app/lib/hooks/useShortcuts.ts index 94f7fb05..39308fcd 100644 --- a/app/lib/hooks/useShortcuts.ts +++ b/app/lib/hooks/useShortcuts.ts @@ -1,6 +1,10 @@ import { useStore } from '@nanostores/react'; import { useEffect } from 'react'; import { shortcutsStore, type Shortcuts } from '~/lib/stores/settings'; +import { isMac } from '~/utils/os'; + +// List of keys that should not trigger shortcuts when typing in input/textarea +const INPUT_ELEMENTS = ['input', 'textarea']; class ShortcutEventEmitter { #emitter = new EventTarget(); @@ -25,55 +29,54 @@ export function useShortcuts(): void { useEffect(() => { const handleKeyDown = (event: KeyboardEvent): void => { - // Debug logging - console.log('Key pressed:', { - key: event.key, - code: event.code, // This gives us the physical key regardless of modifiers - ctrlKey: event.ctrlKey, - shiftKey: event.shiftKey, - altKey: event.altKey, - metaKey: event.metaKey, - }); - - /* - * Check for theme toggle shortcut first (Option + Command + Shift + D) - * Use event.code to check for the physical D key regardless of the character produced - */ + // Don't trigger shortcuts when typing in input fields if ( - event.code === 'KeyD' && - event.metaKey && // Command (Mac) or Windows key - event.altKey && // Option (Mac) or Alt (Windows) - event.shiftKey && - !event.ctrlKey + document.activeElement && + INPUT_ELEMENTS.includes(document.activeElement.tagName.toLowerCase()) && + !event.altKey && // Allow Alt combinations even in input fields + !event.metaKey && // Allow Cmd/Win combinations even in input fields + !event.ctrlKey // Allow Ctrl combinations even in input fields ) { - event.preventDefault(); - event.stopPropagation(); - shortcuts.toggleTheme.action(); - return; } - // Handle other shortcuts - for (const name in shortcuts) { - const shortcut = shortcuts[name as keyof Shortcuts]; + // Debug logging in development only + if (process.env.NODE_ENV === 'development') { + console.log('Key pressed:', { + key: event.key, + code: event.code, + ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, + altKey: event.altKey, + metaKey: event.metaKey, + target: event.target, + }); + } - if (name === 'toggleTheme') { - continue; - } // Skip theme toggle as it's handled above - - // For other shortcuts, check both key and code + // Handle shortcuts + for (const [name, shortcut] of Object.entries(shortcuts)) { const keyMatches = shortcut.key.toLowerCase() === event.key.toLowerCase() || `Key${shortcut.key.toUpperCase()}` === event.code; + // Handle ctrlOrMetaKey based on OS + const ctrlOrMetaKeyMatches = shortcut.ctrlOrMetaKey + ? (isMac && event.metaKey) || (!isMac && event.ctrlKey) + : true; + const modifiersMatch = + ctrlOrMetaKeyMatches && (shortcut.ctrlKey === undefined || shortcut.ctrlKey === event.ctrlKey) && (shortcut.metaKey === undefined || shortcut.metaKey === event.metaKey) && (shortcut.shiftKey === undefined || shortcut.shiftKey === event.shiftKey) && (shortcut.altKey === undefined || shortcut.altKey === event.altKey); if (keyMatches && modifiersMatch) { - event.preventDefault(); - event.stopPropagation(); + // Prevent default browser behavior if specified + if (shortcut.isPreventDefault) { + event.preventDefault(); + event.stopPropagation(); + } + shortcutEventEmitter.dispatch(name as keyof Shortcuts); shortcut.action(); break; diff --git a/app/lib/stores/settings.ts b/app/lib/stores/settings.ts index be8cc109..97802a0b 100644 --- a/app/lib/stores/settings.ts +++ b/app/lib/stores/settings.ts @@ -21,6 +21,8 @@ export interface Shortcut { metaKey?: boolean; ctrlOrMetaKey?: boolean; action: () => void; + description?: string; // Description of what the shortcut does + isPreventDefault?: boolean; // Whether to prevent default browser behavior } export interface Shortcuts { @@ -35,32 +37,41 @@ export const LOCAL_PROVIDERS = ['OpenAILike', 'LMStudio', 'Ollama']; export type ProviderSetting = Record; +// Define safer shortcuts that don't conflict with browser defaults export const shortcutsStore = map({ toggleTerminal: { key: '`', ctrlOrMetaKey: true, action: () => workbenchStore.toggleTerminal(), + description: 'Toggle terminal', + isPreventDefault: true, }, toggleTheme: { key: 'd', - metaKey: true, // Command key on Mac, Windows key on Windows - altKey: true, // Option key on Mac, Alt key on Windows + metaKey: true, + altKey: true, shiftKey: true, action: () => toggleTheme(), + description: 'Toggle theme', + isPreventDefault: true, }, toggleChat: { - key: 'k', + key: 'j', // Changed from 'k' to 'j' to avoid conflicts ctrlOrMetaKey: true, + altKey: true, // Added alt key to make it more unique action: () => chatStore.setKey('showChat', !chatStore.get().showChat), + description: 'Toggle chat', + isPreventDefault: true, }, toggleSettings: { key: 's', ctrlOrMetaKey: true, altKey: true, action: () => { - // This will be connected to the settings panel toggle document.dispatchEvent(new CustomEvent('toggle-settings')); }, + description: 'Toggle settings', + isPreventDefault: true, }, }); diff --git a/app/utils/os.ts b/app/utils/os.ts new file mode 100644 index 00000000..0c8d87b9 --- /dev/null +++ b/app/utils/os.ts @@ -0,0 +1,4 @@ +// Helper to detect OS +export const isMac = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('mac') : false; +export const isWindows = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('win') : false; +export const isLinux = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('linux') : false;