From b8983f16b2cd44a8063fab09f0fa799bbd0cd79e Mon Sep 17 00:00:00 2001 From: Test Date: Fri, 10 Apr 2026 12:08:17 +0200 Subject: [PATCH] fix: move cmd+e to toggle organized --- src-tauri/src/menu.rs | 9 ++++++++- src/components/ArchivedNoteBanner.test.tsx | 4 ++-- src/components/ArchivedNoteBanner.tsx | 2 +- src/components/BreadcrumbBar.test.tsx | 19 +++++++++++++------ src/components/BreadcrumbBar.tsx | 6 +++--- src/hooks/appKeyboardShortcuts.ts | 3 ++- src/hooks/commands/noteCommands.ts | 4 ++-- src/hooks/useAppCommands.ts | 2 ++ src/hooks/useAppKeyboard.test.ts | 9 +++++++++ src/hooks/useCommandRegistry.test.ts | 8 ++++++++ src/hooks/useMenuEvents.test.ts | 14 ++++++++++++++ src/hooks/useMenuEvents.ts | 4 +++- 12 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src-tauri/src/menu.rs b/src-tauri/src/menu.rs index 9fb0317f..adedee61 100644 --- a/src-tauri/src/menu.rs +++ b/src-tauri/src/menu.rs @@ -35,6 +35,7 @@ const GO_ARCHIVED: &str = "go-archived"; const GO_CHANGES: &str = "go-changes"; const GO_INBOX: &str = "go-inbox"; +const NOTE_TOGGLE_ORGANIZED: &str = "note-toggle-organized"; const NOTE_ARCHIVE: &str = "note-archive"; const NOTE_DELETE: &str = "note-delete"; const NOTE_OPEN_IN_NEW_WINDOW: &str = "note-open-in-new-window"; @@ -77,6 +78,7 @@ const CUSTOM_IDS: &[&str] = &[ GO_ALL_NOTES, GO_ARCHIVED, GO_CHANGES, + NOTE_TOGGLE_ORGANIZED, NOTE_ARCHIVE, NOTE_DELETE, NOTE_OPEN_IN_NEW_WINDOW, @@ -96,6 +98,7 @@ const CUSTOM_IDS: &[&str] = &[ /// IDs of menu items that should be disabled when no note tab is active. const NOTE_DEPENDENT_IDS: &[&str] = &[ FILE_SAVE, + NOTE_TOGGLE_ORGANIZED, NOTE_ARCHIVE, NOTE_DELETE, EDIT_TOGGLE_RAW_EDITOR, @@ -273,9 +276,12 @@ fn build_go_menu(app: &App) -> MenuResult { } fn build_note_menu(app: &App) -> MenuResult { + let toggle_organized = MenuItemBuilder::new("Toggle Organized") + .id(NOTE_TOGGLE_ORGANIZED) + .accelerator("CmdOrCtrl+E") + .build(app)?; let archive_note = MenuItemBuilder::new("Archive Note") .id(NOTE_ARCHIVE) - .accelerator("CmdOrCtrl+E") .build(app)?; let delete_note = MenuItemBuilder::new("Delete Note") .id(NOTE_DELETE) @@ -302,6 +308,7 @@ fn build_note_menu(app: &App) -> MenuResult { .build(app)?; Ok(SubmenuBuilder::new(app, "Note") + .item(&toggle_organized) .item(&archive_note) .item(&delete_note) .item(&restore_deleted_note) diff --git a/src/components/ArchivedNoteBanner.test.tsx b/src/components/ArchivedNoteBanner.test.tsx index 9d8c7985..f6d2d059 100644 --- a/src/components/ArchivedNoteBanner.test.tsx +++ b/src/components/ArchivedNoteBanner.test.tsx @@ -9,12 +9,12 @@ describe('ArchivedNoteBanner', () => { expect(screen.getByText('Archived')).toBeTruthy() }) - it('renders unarchive button with keyboard hint', () => { + it('renders unarchive button without the stale archive shortcut hint', () => { render() const btn = screen.getByTestId('unarchive-btn') expect(btn).toBeTruthy() expect(btn.textContent).toContain('Unarchive') - expect(btn.title).toBe('Unarchive (Cmd+E)') + expect(btn.title).toBe('Unarchive') }) it('calls onUnarchive when button is clicked', () => { diff --git a/src/components/ArchivedNoteBanner.tsx b/src/components/ArchivedNoteBanner.tsx index e55e1b78..46e55143 100644 --- a/src/components/ArchivedNoteBanner.tsx +++ b/src/components/ArchivedNoteBanner.tsx @@ -38,7 +38,7 @@ export function ArchivedNoteBanner({ onUnarchive }: ArchivedNoteBannerProps) { color: 'var(--muted-foreground)', cursor: 'pointer', }} - title="Unarchive (Cmd+E)" + title="Unarchive" > Unarchive diff --git a/src/components/BreadcrumbBar.test.tsx b/src/components/BreadcrumbBar.test.tsx index a7614f8e..97e8ca83 100644 --- a/src/components/BreadcrumbBar.test.tsx +++ b/src/components/BreadcrumbBar.test.tsx @@ -74,31 +74,38 @@ describe('BreadcrumbBar — delete', () => { describe('BreadcrumbBar — archive/unarchive', () => { it('shows archive button for non-archived note', () => { render() - expect(screen.getByTitle('Archive (Cmd+E)')).toBeInTheDocument() - expect(screen.queryByTitle('Unarchive (Cmd+E)')).not.toBeInTheDocument() + expect(screen.getByTitle('Archive')).toBeInTheDocument() + expect(screen.queryByTitle('Unarchive')).not.toBeInTheDocument() }) it('shows unarchive button for archived note', () => { render() - expect(screen.getByTitle('Unarchive (Cmd+E)')).toBeInTheDocument() - expect(screen.queryByTitle('Archive (Cmd+E)')).not.toBeInTheDocument() + expect(screen.getByTitle('Unarchive')).toBeInTheDocument() + expect(screen.queryByTitle('Archive')).not.toBeInTheDocument() }) it('calls onArchive when archive button is clicked', () => { const onArchive = vi.fn() render() - fireEvent.click(screen.getByTitle('Archive (Cmd+E)')) + fireEvent.click(screen.getByTitle('Archive')) expect(onArchive).toHaveBeenCalledOnce() }) it('calls onUnarchive when unarchive button is clicked', () => { const onUnarchive = vi.fn() render() - fireEvent.click(screen.getByTitle('Unarchive (Cmd+E)')) + fireEvent.click(screen.getByTitle('Unarchive')) expect(onUnarchive).toHaveBeenCalledOnce() }) }) +describe('BreadcrumbBar — organized shortcut hint', () => { + it('shows Cmd+E on the organized toggle tooltip', () => { + render() + expect(screen.getByTitle('Mark as organized (remove from Inbox) (Cmd+E)')).toBeInTheDocument() + }) +}) + describe('BreadcrumbBar — title in breadcrumb (always rendered, CSS-toggled)', () => { it('always renders title elements in the DOM', () => { render() diff --git a/src/components/BreadcrumbBar.tsx b/src/components/BreadcrumbBar.tsx index e3e1b317..7079ad48 100644 --- a/src/components/BreadcrumbBar.tsx +++ b/src/components/BreadcrumbBar.tsx @@ -81,7 +81,7 @@ function BreadcrumbActions({ entry, showDiffToggle, diffMode, diffLoading, onTog entry.organized ? "text-green-600" : "text-muted-foreground hover:text-foreground" )} onClick={onToggleOrganized} - title={entry.organized ? 'Mark as unorganized (back to Inbox)' : 'Mark as organized (remove from Inbox)'} + title={entry.organized ? 'Mark as unorganized (back to Inbox) (Cmd+E)' : 'Mark as organized (remove from Inbox) (Cmd+E)'} > @@ -137,7 +137,7 @@ function BreadcrumbActions({ entry, showDiffToggle, diffMode, diffLoading, onTog @@ -145,7 +145,7 @@ function BreadcrumbActions({ entry, showDiffToggle, diffMode, diffLoading, onTog diff --git a/src/hooks/appKeyboardShortcuts.ts b/src/hooks/appKeyboardShortcuts.ts index 60a0ac21..07a158df 100644 --- a/src/hooks/appKeyboardShortcuts.ts +++ b/src/hooks/appKeyboardShortcuts.ts @@ -22,6 +22,7 @@ export interface KeyboardActions { onToggleRawEditor?: () => void onToggleInspector?: () => void onToggleFavorite?: (path: string) => void + onToggleOrganized?: (path: string) => void onOpenInNewWindow?: () => void activeTabPathRef: MutableRefObject } @@ -77,7 +78,7 @@ export function createCommandKeyMap(actions: KeyboardActions): ShortcutMap { s: actions.onSave, ',': actions.onOpenSettings, d: withActiveTab(activeTabPathRef, (path) => actions.onToggleFavorite?.(path)), - e: withActiveTab(activeTabPathRef, actions.onArchiveNote), + e: withActiveTab(activeTabPathRef, (path) => actions.onToggleOrganized?.(path)), Backspace: withActiveTab(activeTabPathRef, actions.onDeleteNote), Delete: withActiveTab(activeTabPathRef, actions.onDeleteNote), '[': () => actions.onGoBack?.(), diff --git a/src/hooks/commands/noteCommands.ts b/src/hooks/commands/noteCommands.ts index d00239db..ec588f4b 100644 --- a/src/hooks/commands/noteCommands.ts +++ b/src/hooks/commands/noteCommands.ts @@ -45,7 +45,7 @@ export function buildNoteCommands(config: NoteCommandsConfig): CommandAction[] { execute: () => { if (activeTabPath) onDeleteNote(activeTabPath) }, }, { - id: 'archive-note', label: isArchived ? 'Unarchive Note' : 'Archive Note', group: 'Note', shortcut: '⌘E', + id: 'archive-note', label: isArchived ? 'Unarchive Note' : 'Archive Note', group: 'Note', keywords: ['archive'], enabled: hasActiveNote, execute: () => { if (activeTabPath) (isArchived ? onUnarchiveNote : onArchiveNote)(activeTabPath) }, }, @@ -62,7 +62,7 @@ export function buildNoteCommands(config: NoteCommandsConfig): CommandAction[] { execute: () => { if (activeTabPath) onToggleFavorite?.(activeTabPath) }, }, { - id: 'toggle-organized', label: isOrganized ? 'Mark as Unorganized' : 'Mark as Organized', group: 'Note', + id: 'toggle-organized', label: isOrganized ? 'Mark as Unorganized' : 'Mark as Organized', group: 'Note', shortcut: '⌘E', keywords: ['organized', 'inbox', 'triage', 'done'], enabled: hasActiveNote && !!onToggleOrganized, execute: () => { if (activeTabPath) onToggleOrganized?.(activeTabPath) }, diff --git a/src/hooks/useAppCommands.ts b/src/hooks/useAppCommands.ts index b856579d..5451a44b 100644 --- a/src/hooks/useAppCommands.ts +++ b/src/hooks/useAppCommands.ts @@ -115,6 +115,7 @@ export function useAppCommands(config: AppCommandsConfig): CommandAction[] { onToggleRawEditor: config.onToggleRawEditor, onToggleInspector: config.onToggleInspector, onToggleFavorite: config.onToggleFavorite, + onToggleOrganized: config.onToggleOrganized, onOpenInNewWindow: config.onOpenInNewWindow, activeTabPathRef: config.activeTabPathRef, }) @@ -138,6 +139,7 @@ export function useAppCommands(config: AppCommandsConfig): CommandAction[] { onToggleRawEditor: config.onToggleRawEditor, onToggleDiff: config.onToggleDiff, onToggleAIChat: config.onToggleAIChat, + onToggleOrganized: config.onToggleOrganized, onGoBack: config.onGoBack, onGoForward: config.onGoForward, onCheckForUpdates: config.onCheckForUpdates, diff --git a/src/hooks/useAppKeyboard.test.ts b/src/hooks/useAppKeyboard.test.ts index ecf701eb..4a79a62f 100644 --- a/src/hooks/useAppKeyboard.test.ts +++ b/src/hooks/useAppKeyboard.test.ts @@ -38,6 +38,7 @@ function makeActions() { onOpenSettings: vi.fn(), onDeleteNote: vi.fn(), onArchiveNote: vi.fn(), + onToggleOrganized: vi.fn(), onSetViewMode: vi.fn(), onZoomIn: vi.fn(), onZoomOut: vi.fn(), @@ -99,6 +100,14 @@ describe('useAppKeyboard', () => { expect(actions.onToggleFavorite).toHaveBeenCalledWith('/vault/test.md') }) + it('Cmd+E triggers toggle organized on active note, not archive', () => { + const actions = makeActions() + renderHook(() => useAppKeyboard(actions)) + fireKey('e', { metaKey: true }) + expect(actions.onToggleOrganized).toHaveBeenCalledWith('/vault/test.md') + expect(actions.onArchiveNote).not.toHaveBeenCalled() + }) + it('Cmd+J triggers open daily note', () => { const actions = makeActions() renderHook(() => useAppKeyboard(actions)) diff --git a/src/hooks/useCommandRegistry.test.ts b/src/hooks/useCommandRegistry.test.ts index 05e2f758..a6931bf5 100644 --- a/src/hooks/useCommandRegistry.test.ts +++ b/src/hooks/useCommandRegistry.test.ts @@ -16,6 +16,7 @@ function makeConfig(overrides: Record = {}) { onDeleteNote: vi.fn(), onArchiveNote: vi.fn(), onUnarchiveNote: vi.fn(), + onToggleOrganized: vi.fn(), onCommitPush: vi.fn(), onResolveConflicts: vi.fn(), onSetViewMode: vi.fn(), @@ -192,6 +193,13 @@ describe('useCommandRegistry', () => { const cmd = findCommand(result.current, 'customize-inbox-columns') expect(cmd!.enabled).toBe(false) }) + + it('shows Cmd+E on toggle organized and removes it from archive note', () => { + const config = makeConfig() + const { result } = renderHook(() => useCommandRegistry(config)) + expect(findCommand(result.current, 'toggle-organized')?.shortcut).toBe('⌘E') + expect(findCommand(result.current, 'archive-note')?.shortcut).toBeUndefined() + }) }) describe('pluralizeType', () => { diff --git a/src/hooks/useMenuEvents.test.ts b/src/hooks/useMenuEvents.test.ts index 1578f294..0cd8e2e0 100644 --- a/src/hooks/useMenuEvents.test.ts +++ b/src/hooks/useMenuEvents.test.ts @@ -15,6 +15,7 @@ function makeHandlers(): MenuEventHandlers { onZoomIn: vi.fn(), onZoomOut: vi.fn(), onZoomReset: vi.fn(), + onToggleOrganized: vi.fn(), onArchiveNote: vi.fn(), onDeleteNote: vi.fn(), onSearch: vi.fn(), @@ -143,6 +144,19 @@ describe('dispatchMenuEvent', () => { expect(h.onArchiveNote).not.toHaveBeenCalled() }) + it('note-toggle-organized triggers organized toggle on active tab', () => { + const h = makeHandlers() + dispatchMenuEvent('note-toggle-organized', h) + expect(h.onToggleOrganized).toHaveBeenCalledWith('/vault/test.md') + }) + + it('note-toggle-organized does nothing when no active tab', () => { + const h = makeHandlers() + h.activeTabPathRef = { current: null } + dispatchMenuEvent('note-toggle-organized', h) + expect(h.onToggleOrganized).not.toHaveBeenCalled() + }) + it('note-delete triggers delete on active tab', () => { const h = makeHandlers() dispatchMenuEvent('note-delete', h) diff --git a/src/hooks/useMenuEvents.ts b/src/hooks/useMenuEvents.ts index 8695b492..5183089c 100644 --- a/src/hooks/useMenuEvents.ts +++ b/src/hooks/useMenuEvents.ts @@ -16,6 +16,7 @@ export interface MenuEventHandlers { onZoomIn: () => void onZoomOut: () => void onZoomReset: () => void + onToggleOrganized?: (path: string) => void onArchiveNote: (path: string) => void onDeleteNote: (path: string) => void onSearch: () => void @@ -106,7 +107,8 @@ const OPTIONAL_EVENT_MAP: Record = { function dispatchActiveTabEvent(id: string, h: MenuEventHandlers): boolean { const path = h.activeTabPathRef.current - if (!path) return id === 'note-archive' || id === 'note-delete' + if (!path) return id === 'note-toggle-organized' || id === 'note-archive' || id === 'note-delete' + if (id === 'note-toggle-organized') { h.onToggleOrganized?.(path); return true } if (id === 'note-archive') { h.onArchiveNote(path); return true } if (id === 'note-delete') { h.onDeleteNote(path); return true } return false