diff --git a/src-tauri/src/menu.rs b/src-tauri/src/menu.rs index 197a7d4c..d34a48ed 100644 --- a/src-tauri/src/menu.rs +++ b/src-tauri/src/menu.rs @@ -1,5 +1,5 @@ use tauri::{ - menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder}, + menu::{MenuBuilder, MenuItemBuilder, PredefinedMenuItem, SubmenuBuilder}, App, Emitter, }; @@ -10,6 +10,16 @@ const VIEW_ITEMS: [(&str, &str, &str); 3] = [ ]; pub fn setup_menu(app: &App) -> Result<(), Box> { + let edit_submenu = SubmenuBuilder::new(app, "Edit") + .item(&PredefinedMenuItem::undo(app, Some("Undo"))?) + .item(&PredefinedMenuItem::redo(app, Some("Redo"))?) + .separator() + .item(&PredefinedMenuItem::cut(app, Some("Cut"))?) + .item(&PredefinedMenuItem::copy(app, Some("Copy"))?) + .item(&PredefinedMenuItem::paste(app, Some("Paste"))?) + .item(&PredefinedMenuItem::select_all(app, Some("Select All"))?) + .build()?; + let mut view_menu = SubmenuBuilder::new(app, "View"); for (id, label, accel) in &VIEW_ITEMS { let item = MenuItemBuilder::new(*label) @@ -20,7 +30,10 @@ pub fn setup_menu(app: &App) -> Result<(), Box> { } let view_submenu = view_menu.build()?; - let menu = MenuBuilder::new(app).item(&view_submenu).build()?; + let menu = MenuBuilder::new(app) + .item(&edit_submenu) + .item(&view_submenu) + .build()?; app.set_menu(menu)?; diff --git a/src/hooks/useAppKeyboard.test.ts b/src/hooks/useAppKeyboard.test.ts index c8544990..e51df58e 100644 --- a/src/hooks/useAppKeyboard.test.ts +++ b/src/hooks/useAppKeyboard.test.ts @@ -111,4 +111,36 @@ describe('useAppKeyboard', () => { expect(actions.onQuickOpen).not.toHaveBeenCalled() expect(actions.onCreateNote).not.toHaveBeenCalled() }) + + function withFocusedInput(fn: () => void) { + const input = document.createElement('input') + document.body.appendChild(input) + input.focus() + try { fn() } finally { document.body.removeChild(input) } + } + + it('Cmd+Backspace does not trash note when text input is focused', () => { + const actions = makeActions() + renderHook(() => useAppKeyboard(actions)) + withFocusedInput(() => { + fireKey('Backspace', { metaKey: true }) + expect(actions.onTrashNote).not.toHaveBeenCalled() + }) + }) + + it('Cmd+Backspace trashes note when no text input is focused', () => { + const actions = makeActions() + renderHook(() => useAppKeyboard(actions)) + fireKey('Backspace', { metaKey: true }) + expect(actions.onTrashNote).toHaveBeenCalledWith('/vault/test.md') + }) + + it('Cmd+K still works when text input is focused', () => { + const actions = makeActions() + renderHook(() => useAppKeyboard(actions)) + withFocusedInput(() => { + fireKey('k', { metaKey: true }) + expect(actions.onCommandPalette).toHaveBeenCalled() + }) + }) }) diff --git a/src/hooks/useAppKeyboard.ts b/src/hooks/useAppKeyboard.ts index 92894283..62a96541 100644 --- a/src/hooks/useAppKeyboard.ts +++ b/src/hooks/useAppKeyboard.ts @@ -17,6 +17,13 @@ interface KeyboardActions { type ShortcutHandler = () => void +const TEXT_EDITING_KEYS = new Set(['Backspace', 'Delete']) + +function isTextInputFocused(): boolean { + const tag = document.activeElement?.tagName + return tag === 'INPUT' || tag === 'TEXTAREA' +} + const VIEW_MODE_KEYS: Record = { '1': 'editor-only', '2': 'editor-list', @@ -41,6 +48,7 @@ function handleCmdKey(e: KeyboardEvent, keyMap: Record) if (!mod) return false const handler = keyMap[e.key] if (!handler) return false + if (TEXT_EDITING_KEYS.has(e.key) && isTextInputFocused()) return false e.preventDefault() handler() return true