fix: move cmd+e to toggle organized
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(<ArchivedNoteBanner onUnarchive={vi.fn()} />)
|
||||
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', () => {
|
||||
|
||||
@@ -38,7 +38,7 @@ export function ArchivedNoteBanner({ onUnarchive }: ArchivedNoteBannerProps) {
|
||||
color: 'var(--muted-foreground)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
title="Unarchive (Cmd+E)"
|
||||
title="Unarchive"
|
||||
>
|
||||
<ArrowUUpLeft size={12} />
|
||||
Unarchive
|
||||
|
||||
@@ -74,31 +74,38 @@ describe('BreadcrumbBar — delete', () => {
|
||||
describe('BreadcrumbBar — archive/unarchive', () => {
|
||||
it('shows archive button for non-archived note', () => {
|
||||
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} onArchive={vi.fn()} onUnarchive={vi.fn()} />)
|
||||
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(<BreadcrumbBar entry={archivedEntry} {...defaultProps} onArchive={vi.fn()} onUnarchive={vi.fn()} />)
|
||||
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(<BreadcrumbBar entry={baseEntry} {...defaultProps} onArchive={onArchive} />)
|
||||
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(<BreadcrumbBar entry={archivedEntry} {...defaultProps} onUnarchive={onUnarchive} />)
|
||||
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(<BreadcrumbBar entry={baseEntry} {...defaultProps} onToggleOrganized={vi.fn()} />)
|
||||
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(<BreadcrumbBar entry={baseEntry} {...defaultProps} />)
|
||||
|
||||
@@ -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)'}
|
||||
>
|
||||
<CheckCircle size={16} weight={entry.organized ? 'fill' : 'regular'} />
|
||||
</button>
|
||||
@@ -137,7 +137,7 @@ function BreadcrumbActions({ entry, showDiffToggle, diffMode, diffLoading, onTog
|
||||
<button
|
||||
className="flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors text-muted-foreground hover:text-foreground"
|
||||
onClick={onUnarchive}
|
||||
title="Unarchive (Cmd+E)"
|
||||
title="Unarchive"
|
||||
>
|
||||
<ArrowUUpLeft size={16} />
|
||||
</button>
|
||||
@@ -145,7 +145,7 @@ function BreadcrumbActions({ entry, showDiffToggle, diffMode, diffLoading, onTog
|
||||
<button
|
||||
className="flex items-center justify-center border-none bg-transparent p-0 cursor-pointer transition-colors text-muted-foreground hover:text-foreground"
|
||||
onClick={onArchive}
|
||||
title="Archive (Cmd+E)"
|
||||
title="Archive"
|
||||
>
|
||||
<Archive size={16} />
|
||||
</button>
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface KeyboardActions {
|
||||
onToggleRawEditor?: () => void
|
||||
onToggleInspector?: () => void
|
||||
onToggleFavorite?: (path: string) => void
|
||||
onToggleOrganized?: (path: string) => void
|
||||
onOpenInNewWindow?: () => void
|
||||
activeTabPathRef: MutableRefObject<string | null>
|
||||
}
|
||||
@@ -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?.(),
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -16,6 +16,7 @@ function makeConfig(overrides: Record<string, unknown> = {}) {
|
||||
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', () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<string, OptionalHandler> = {
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user