Stijnus commited on
Commit
8035a76
Β·
1 Parent(s): 84f45dd

Bug fix for the Keyboard Shortcuts

Browse files

MAC OS SHORTCUTS:
- Toggle Terminal: ⌘ + `
- Toggle Theme: ⌘ + βŒ₯ + ⇧ + D
- Toggle Chat: ⌘ + βŒ₯ + J
- Toggle Settings: ⌘ + βŒ₯ + S

WINDOWS/LINUX SHORTCUTS:
- Toggle Terminal: Ctrl + `
- Toggle Theme: Win + Alt + Shift + D
- Toggle Chat: Ctrl + Alt + J
- Toggle Settings: Ctrl + Alt + S

app/components/@settings/tabs/settings/SettingsTab.tsx CHANGED
@@ -7,6 +7,32 @@ import { themeStore, kTheme } from '~/lib/stores/theme';
7
  import type { UserProfile } from '~/components/@settings/core/types';
8
  import { useStore } from '@nanostores/react';
9
  import { shortcutsStore } from '~/lib/stores/settings';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  export default function SettingsTab() {
12
  const [currentTimezone, setCurrentTimezone] = useState('');
@@ -237,37 +263,42 @@ export default function SettingsTab() {
237
  key={name}
238
  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"
239
  >
240
- <span className="text-sm text-bolt-elements-textPrimary capitalize">
241
- {name.replace(/([A-Z])/g, ' $1').toLowerCase()}
242
- </span>
 
 
 
 
 
243
  <div className="flex items-center gap-1">
244
  {shortcut.ctrlOrMetaKey && (
245
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
246
- {navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'}
247
  </kbd>
248
  )}
249
  {shortcut.ctrlKey && (
250
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
251
- Ctrl
252
  </kbd>
253
  )}
254
  {shortcut.metaKey && (
255
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
256
- ⌘
257
  </kbd>
258
  )}
259
  {shortcut.altKey && (
260
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
261
- {navigator.platform.includes('Mac') ? 'βŒ₯' : 'Alt'}
262
  </kbd>
263
  )}
264
  {shortcut.shiftKey && (
265
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
266
- ⇧
267
  </kbd>
268
  )}
269
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
270
- {shortcut.key.toUpperCase()}
271
  </kbd>
272
  </div>
273
  </div>
 
7
  import type { UserProfile } from '~/components/@settings/core/types';
8
  import { useStore } from '@nanostores/react';
9
  import { shortcutsStore } from '~/lib/stores/settings';
10
+ import { isMac } from '~/utils/os';
11
+
12
+ // Helper to format shortcut key display
13
+ const formatShortcutKey = (key: string) => {
14
+ if (key === '`') {
15
+ return '`';
16
+ }
17
+
18
+ return key.toUpperCase();
19
+ };
20
+
21
+ // Helper to get modifier key symbols/text
22
+ const getModifierSymbol = (modifier: string): string => {
23
+ switch (modifier) {
24
+ case 'meta':
25
+ return isMac ? '⌘' : 'Win';
26
+ case 'alt':
27
+ return isMac ? 'βŒ₯' : 'Alt';
28
+ case 'ctrl':
29
+ return isMac ? 'βŒƒ' : 'Ctrl';
30
+ case 'shift':
31
+ return '⇧';
32
+ default:
33
+ return modifier;
34
+ }
35
+ };
36
 
37
  export default function SettingsTab() {
38
  const [currentTimezone, setCurrentTimezone] = useState('');
 
263
  key={name}
264
  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"
265
  >
266
+ <div className="flex flex-col">
267
+ <span className="text-sm text-bolt-elements-textPrimary capitalize">
268
+ {name.replace(/([A-Z])/g, ' $1').toLowerCase()}
269
+ </span>
270
+ {shortcut.description && (
271
+ <span className="text-xs text-bolt-elements-textSecondary">{shortcut.description}</span>
272
+ )}
273
+ </div>
274
  <div className="flex items-center gap-1">
275
  {shortcut.ctrlOrMetaKey && (
276
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
277
+ {getModifierSymbol(isMac ? 'meta' : 'ctrl')}
278
  </kbd>
279
  )}
280
  {shortcut.ctrlKey && (
281
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
282
+ {getModifierSymbol('ctrl')}
283
  </kbd>
284
  )}
285
  {shortcut.metaKey && (
286
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
287
+ {getModifierSymbol('meta')}
288
  </kbd>
289
  )}
290
  {shortcut.altKey && (
291
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
292
+ {getModifierSymbol('alt')}
293
  </kbd>
294
  )}
295
  {shortcut.shiftKey && (
296
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
297
+ {getModifierSymbol('shift')}
298
  </kbd>
299
  )}
300
  <kbd className="px-2 py-1 text-xs font-semibold text-bolt-elements-textSecondary bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] rounded shadow-sm">
301
+ {formatShortcutKey(shortcut.key)}
302
  </kbd>
303
  </div>
304
  </div>
app/lib/hooks/useShortcuts.ts CHANGED
@@ -1,6 +1,10 @@
1
  import { useStore } from '@nanostores/react';
2
  import { useEffect } from 'react';
3
  import { shortcutsStore, type Shortcuts } from '~/lib/stores/settings';
 
 
 
 
4
 
5
  class ShortcutEventEmitter {
6
  #emitter = new EventTarget();
@@ -25,55 +29,54 @@ export function useShortcuts(): void {
25
 
26
  useEffect(() => {
27
  const handleKeyDown = (event: KeyboardEvent): void => {
28
- // Debug logging
29
- console.log('Key pressed:', {
30
- key: event.key,
31
- code: event.code, // This gives us the physical key regardless of modifiers
32
- ctrlKey: event.ctrlKey,
33
- shiftKey: event.shiftKey,
34
- altKey: event.altKey,
35
- metaKey: event.metaKey,
36
- });
37
-
38
- /*
39
- * Check for theme toggle shortcut first (Option + Command + Shift + D)
40
- * Use event.code to check for the physical D key regardless of the character produced
41
- */
42
  if (
43
- event.code === 'KeyD' &&
44
- event.metaKey && // Command (Mac) or Windows key
45
- event.altKey && // Option (Mac) or Alt (Windows)
46
- event.shiftKey &&
47
- !event.ctrlKey
48
  ) {
49
- event.preventDefault();
50
- event.stopPropagation();
51
- shortcuts.toggleTheme.action();
52
-
53
  return;
54
  }
55
 
56
- // Handle other shortcuts
57
- for (const name in shortcuts) {
58
- const shortcut = shortcuts[name as keyof Shortcuts];
59
-
60
- if (name === 'toggleTheme') {
61
- continue;
62
- } // Skip theme toggle as it's handled above
 
 
 
 
 
63
 
64
- // For other shortcuts, check both key and code
 
65
  const keyMatches =
66
  shortcut.key.toLowerCase() === event.key.toLowerCase() || `Key${shortcut.key.toUpperCase()}` === event.code;
67
 
 
 
 
 
 
68
  const modifiersMatch =
 
69
  (shortcut.ctrlKey === undefined || shortcut.ctrlKey === event.ctrlKey) &&
70
  (shortcut.metaKey === undefined || shortcut.metaKey === event.metaKey) &&
71
  (shortcut.shiftKey === undefined || shortcut.shiftKey === event.shiftKey) &&
72
  (shortcut.altKey === undefined || shortcut.altKey === event.altKey);
73
 
74
  if (keyMatches && modifiersMatch) {
75
- event.preventDefault();
76
- event.stopPropagation();
 
 
 
 
77
  shortcutEventEmitter.dispatch(name as keyof Shortcuts);
78
  shortcut.action();
79
  break;
 
1
  import { useStore } from '@nanostores/react';
2
  import { useEffect } from 'react';
3
  import { shortcutsStore, type Shortcuts } from '~/lib/stores/settings';
4
+ import { isMac } from '~/utils/os';
5
+
6
+ // List of keys that should not trigger shortcuts when typing in input/textarea
7
+ const INPUT_ELEMENTS = ['input', 'textarea'];
8
 
9
  class ShortcutEventEmitter {
10
  #emitter = new EventTarget();
 
29
 
30
  useEffect(() => {
31
  const handleKeyDown = (event: KeyboardEvent): void => {
32
+ // Don't trigger shortcuts when typing in input fields
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  if (
34
+ document.activeElement &&
35
+ INPUT_ELEMENTS.includes(document.activeElement.tagName.toLowerCase()) &&
36
+ !event.altKey && // Allow Alt combinations even in input fields
37
+ !event.metaKey && // Allow Cmd/Win combinations even in input fields
38
+ !event.ctrlKey // Allow Ctrl combinations even in input fields
39
  ) {
 
 
 
 
40
  return;
41
  }
42
 
43
+ // Debug logging in development only
44
+ if (process.env.NODE_ENV === 'development') {
45
+ console.log('Key pressed:', {
46
+ key: event.key,
47
+ code: event.code,
48
+ ctrlKey: event.ctrlKey,
49
+ shiftKey: event.shiftKey,
50
+ altKey: event.altKey,
51
+ metaKey: event.metaKey,
52
+ target: event.target,
53
+ });
54
+ }
55
 
56
+ // Handle shortcuts
57
+ for (const [name, shortcut] of Object.entries(shortcuts)) {
58
  const keyMatches =
59
  shortcut.key.toLowerCase() === event.key.toLowerCase() || `Key${shortcut.key.toUpperCase()}` === event.code;
60
 
61
+ // Handle ctrlOrMetaKey based on OS
62
+ const ctrlOrMetaKeyMatches = shortcut.ctrlOrMetaKey
63
+ ? (isMac && event.metaKey) || (!isMac && event.ctrlKey)
64
+ : true;
65
+
66
  const modifiersMatch =
67
+ ctrlOrMetaKeyMatches &&
68
  (shortcut.ctrlKey === undefined || shortcut.ctrlKey === event.ctrlKey) &&
69
  (shortcut.metaKey === undefined || shortcut.metaKey === event.metaKey) &&
70
  (shortcut.shiftKey === undefined || shortcut.shiftKey === event.shiftKey) &&
71
  (shortcut.altKey === undefined || shortcut.altKey === event.altKey);
72
 
73
  if (keyMatches && modifiersMatch) {
74
+ // Prevent default browser behavior if specified
75
+ if (shortcut.isPreventDefault) {
76
+ event.preventDefault();
77
+ event.stopPropagation();
78
+ }
79
+
80
  shortcutEventEmitter.dispatch(name as keyof Shortcuts);
81
  shortcut.action();
82
  break;
app/lib/stores/settings.ts CHANGED
@@ -21,6 +21,8 @@ export interface Shortcut {
21
  metaKey?: boolean;
22
  ctrlOrMetaKey?: boolean;
23
  action: () => void;
 
 
24
  }
25
 
26
  export interface Shortcuts {
@@ -35,32 +37,41 @@ export const LOCAL_PROVIDERS = ['OpenAILike', 'LMStudio', 'Ollama'];
35
 
36
  export type ProviderSetting = Record<string, IProviderConfig>;
37
 
 
38
  export const shortcutsStore = map<Shortcuts>({
39
  toggleTerminal: {
40
  key: '`',
41
  ctrlOrMetaKey: true,
42
  action: () => workbenchStore.toggleTerminal(),
 
 
43
  },
44
  toggleTheme: {
45
  key: 'd',
46
- metaKey: true, // Command key on Mac, Windows key on Windows
47
- altKey: true, // Option key on Mac, Alt key on Windows
48
  shiftKey: true,
49
  action: () => toggleTheme(),
 
 
50
  },
51
  toggleChat: {
52
- key: 'k',
53
  ctrlOrMetaKey: true,
 
54
  action: () => chatStore.setKey('showChat', !chatStore.get().showChat),
 
 
55
  },
56
  toggleSettings: {
57
  key: 's',
58
  ctrlOrMetaKey: true,
59
  altKey: true,
60
  action: () => {
61
- // This will be connected to the settings panel toggle
62
  document.dispatchEvent(new CustomEvent('toggle-settings'));
63
  },
 
 
64
  },
65
  });
66
 
 
21
  metaKey?: boolean;
22
  ctrlOrMetaKey?: boolean;
23
  action: () => void;
24
+ description?: string; // Description of what the shortcut does
25
+ isPreventDefault?: boolean; // Whether to prevent default browser behavior
26
  }
27
 
28
  export interface Shortcuts {
 
37
 
38
  export type ProviderSetting = Record<string, IProviderConfig>;
39
 
40
+ // Define safer shortcuts that don't conflict with browser defaults
41
  export const shortcutsStore = map<Shortcuts>({
42
  toggleTerminal: {
43
  key: '`',
44
  ctrlOrMetaKey: true,
45
  action: () => workbenchStore.toggleTerminal(),
46
+ description: 'Toggle terminal',
47
+ isPreventDefault: true,
48
  },
49
  toggleTheme: {
50
  key: 'd',
51
+ metaKey: true,
52
+ altKey: true,
53
  shiftKey: true,
54
  action: () => toggleTheme(),
55
+ description: 'Toggle theme',
56
+ isPreventDefault: true,
57
  },
58
  toggleChat: {
59
+ key: 'j', // Changed from 'k' to 'j' to avoid conflicts
60
  ctrlOrMetaKey: true,
61
+ altKey: true, // Added alt key to make it more unique
62
  action: () => chatStore.setKey('showChat', !chatStore.get().showChat),
63
+ description: 'Toggle chat',
64
+ isPreventDefault: true,
65
  },
66
  toggleSettings: {
67
  key: 's',
68
  ctrlOrMetaKey: true,
69
  altKey: true,
70
  action: () => {
 
71
  document.dispatchEvent(new CustomEvent('toggle-settings'));
72
  },
73
+ description: 'Toggle settings',
74
+ isPreventDefault: true,
75
  },
76
  });
77
 
app/utils/os.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ // Helper to detect OS
2
+ export const isMac = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('mac') : false;
3
+ export const isWindows = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('win') : false;
4
+ export const isLinux = typeof navigator !== 'undefined' ? navigator.platform.toLowerCase().includes('linux') : false;