Files
tolaria/src/hooks/useAppCommands.ts

572 lines
18 KiB
TypeScript
Raw Normal View History

import { useCallback, useRef } from 'react'
2026-04-14 11:55:48 +02:00
import type { AiAgentId, AiAgentsStatus } from '../lib/aiAgents'
2026-04-26 08:18:47 +02:00
import type { AppLocale, UiLanguagePreference } from '../lib/i18n'
2026-04-29 02:35:52 +02:00
import type { ThemeMode } from '../lib/themeMode'
import type { VaultAiGuidanceStatus } from '../lib/vaultAiGuidance'
import { useAppKeyboard } from './useAppKeyboard'
import { useCommandRegistry } from './useCommandRegistry'
import type { CommandAction } from './useCommandRegistry'
import { useKeyboardNavigation } from './useKeyboardNavigation'
import { useMenuEvents } from './useMenuEvents'
2026-04-29 19:26:24 +02:00
import type { NoteWidthMode, SidebarSelection, SidebarFilter, VaultEntry } from '../types'
import { requestAddRemote } from '../utils/addRemoteEvents'
import type { NoteListFilter } from '../utils/noteListHelpers'
import type { ViewMode } from './useViewMode'
import type { NoteListMultiSelectionCommands } from '../components/note-list/multiSelectionCommands'
interface AppCommandsConfig {
activeTabPath: string | null
activeTabPathRef: React.MutableRefObject<string | null>
entries: VaultEntry[]
visibleNotesRef: React.RefObject<VaultEntry[]>
multiSelectionCommandRef: React.MutableRefObject<NoteListMultiSelectionCommands | null>
modifiedCount: number
selection: SidebarSelection
onQuickOpen: () => void
onCommandPalette: () => void
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64) * feat: add search backend (qmd CLI) and design file - Add search.rs: qmd integration for keyword (BM25), semantic (vsearch), and hybrid search modes via CLI JSON output - Register search_vault Tauri command in lib.rs - Design file with 3 frames: empty, results, no-results states - Fix pre-existing test failures: install @tauri-apps/plugin-opener, add mock in test setup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add SearchPanel UI with Cmd+Shift+F shortcut - SearchPanel component: full-text search overlay with keyword/semantic mode toggle, debounced search (200ms), arrow key navigation, result count + elapsed time display, note type badges from vault entries - Cmd+Shift+F shortcut registered in useAppKeyboard - Mock search_vault handler in mock-tauri for browser dev mode - SearchResult/SearchResponse types in types.ts - Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut - scrollIntoView mock in test setup for jsdom compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct SearchPanel imports for Tauri/browser dual-mode Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri with a searchCall wrapper, fixing the TypeScript build error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: make test_detect_collection_fallback robust when qmd not installed * fix: reset localStorage between App tests to prevent view mode state leak --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
onSearch: () => void
2026-04-27 10:12:13 +02:00
onFindInNote?: () => void
onReplaceInNote?: () => void
onCreateNote: () => void
onCreateNoteOfType: (type: string) => void
onSave: () => void
onOpenSettings: () => void
2026-04-10 12:45:37 +02:00
onOpenFeedback?: () => void
onDeleteNote: (path: string) => void
onArchiveNote: (path: string) => void
onUnarchiveNote: (path: string) => void
onCommitPush: () => void
onPull?: () => void
onResolveConflicts?: () => void
onSetViewMode: (mode: ViewMode) => void
onToggleInspector: () => void
onToggleDiff?: () => void
onToggleRawEditor?: () => void
2026-04-28 20:41:07 +02:00
selectedViewName?: string
onMoveSelectedViewUp?: () => void
onMoveSelectedViewDown?: () => void
canMoveSelectedViewUp?: boolean
canMoveSelectedViewDown?: boolean
2026-04-29 19:26:24 +02:00
noteWidth?: NoteWidthMode
defaultNoteWidth?: NoteWidthMode
onSetNoteWidth?: (mode: NoteWidthMode) => void
onSetDefaultNoteWidth?: (mode: NoteWidthMode) => void
activeNoteModified: boolean
onZoomIn: () => void
onZoomOut: () => void
onZoomReset: () => void
zoomLevel: number
onSelect: (sel: SidebarSelection) => void
onRenameFolder?: () => void
onDeleteFolder?: () => void
showInbox?: boolean
onReplaceActiveTab: (entry: VaultEntry) => void
onSelectNote: (entry: VaultEntry) => void
feat: back/forward navigation between opened notes (#75) * feat: add useNavigationHistory hook for browser-style back/forward Pure state management hook that tracks a navigation stack of note paths with cursor-based back/forward traversal. Handles edge cases: duplicate pushes (no-op), forward stack cleared on new push, invalid path skipping, and path removal when tabs close. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: integrate back/forward navigation into UI and keyboard shortcuts - Add Back/Forward arrow buttons to TabBar (left of tabs) - Wire useNavigationHistory through App → Editor → TabBar - Add Cmd+[ and Cmd+] keyboard shortcuts via useAppKeyboard - Register Go Back/Go Forward in command palette - History tracks active tab changes, skips closed tabs gracefully - Navigation from history doesn't re-push to stack (ref guard) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add mouse button 3/4 and trackpad swipe for back/forward - Mouse buttons 3 (back) and 4 (forward) trigger navigation - macOS trackpad two-finger horizontal swipe triggers back/forward using accumulated wheel deltaX with a 120px threshold - Debounced reset after 300ms of inactivity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: add back-forward-nav.pen with 3 state frames Shows: default (both disabled), one note visited (back disabled), two notes visited (back enabled). Matches tab bar button placement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:39:12 +01:00
onGoBack?: () => void
onGoForward?: () => void
canGoBack?: boolean
canGoForward?: boolean
onOpenVault?: () => void
2026-04-19 01:06:40 +02:00
onCreateEmptyVault?: () => void
onAddRemote?: () => void
canAddRemote?: boolean
2026-04-26 17:08:14 +02:00
isGitVault?: boolean
onInitializeGit?: () => void
onCreateType?: () => void
onToggleAIChat?: () => void
onCheckForUpdates?: () => void
onRemoveActiveVault?: () => void
onRestoreGettingStarted?: () => void
isGettingStartedHidden?: boolean
vaultCount?: number
2026-04-26 08:18:47 +02:00
locale?: AppLocale
systemLocale?: AppLocale
selectedUiLanguage?: UiLanguagePreference
onSetUiLanguage?: (language: UiLanguagePreference) => void
2026-04-29 02:35:52 +02:00
onSetThemeMode?: (mode: ThemeMode) => void
mcpStatus?: string
onInstallMcp?: () => void
2026-04-14 11:55:48 +02:00
aiAgentsStatus?: AiAgentsStatus
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
2026-04-13 19:37:59 +02:00
onOpenAiAgents?: () => void
onRestoreVaultAiGuidance?: () => void
2026-04-14 11:55:48 +02:00
onSetDefaultAiAgent?: (agent: AiAgentId) => void
selectedAiAgent?: AiAgentId
2026-04-13 19:37:59 +02:00
onCycleDefaultAiAgent?: () => void
selectedAiAgentLabel?: string
claudeCodeStatus?: string
claudeCodeVersion?: string
onReloadVault?: () => void
onRepairVault?: () => void
onSetNoteIcon?: () => void
onRemoveNoteIcon?: () => void
onChangeNoteType?: () => void
onMoveNoteToFolder?: () => void
canMoveNoteToFolder?: boolean
activeNoteHasIcon?: boolean
noteListFilter?: NoteListFilter
onSetNoteListFilter?: (filter: NoteListFilter) => void
onOpenInNewWindow?: () => void
2026-04-27 01:45:16 +02:00
onRevealActiveFile?: (path: string) => void
onCopyActiveFilePath?: (path: string) => void
onOpenActiveFileExternal?: (path: string) => void
onRevealSelectedFolder?: () => void
onCopySelectedFolderPath?: () => void
onToggleFavorite?: (path: string) => void
onToggleOrganized?: (path: string) => void
onCustomizeNoteListColumns?: () => void
canCustomizeNoteListColumns?: boolean
2026-04-18 20:31:25 +02:00
noteListColumnsLabel?: string
onRestoreDeletedNote?: () => void
canRestoreDeletedNote?: boolean
}
type CommandRegistryConfig = Parameters<typeof useCommandRegistry>[0]
type CommandRegistrySelectionState = Pick<
CommandRegistryConfig,
| 'activeNoteModified'
| 'onZoomIn'
| 'onZoomOut'
| 'onZoomReset'
| 'zoomLevel'
| 'onSelect'
| 'onRenameFolder'
| 'onDeleteFolder'
2026-04-27 01:45:16 +02:00
| 'onRevealSelectedFolder'
| 'onCopySelectedFolderPath'
| 'showInbox'
| 'onGoBack'
| 'onGoForward'
| 'canGoBack'
| 'canGoForward'
| 'selection'
>
type CommandRegistryCoreActions = Pick<
CommandRegistryConfig,
| 'activeTabPath'
| 'entries'
| 'modifiedCount'
| 'onQuickOpen'
| 'onCreateNote'
| 'onCreateNoteOfType'
| 'onSave'
2026-04-27 10:12:13 +02:00
| 'onFindInNote'
| 'onReplaceInNote'
| 'onOpenSettings'
| 'onOpenFeedback'
| 'onDeleteNote'
| 'onArchiveNote'
| 'onUnarchiveNote'
| 'onCommitPush'
| 'onPull'
| 'onResolveConflicts'
| 'onSetViewMode'
| 'onToggleInspector'
| 'onToggleDiff'
| 'onToggleRawEditor'
2026-04-28 20:41:07 +02:00
| 'selectedViewName'
| 'onMoveSelectedViewUp'
| 'onMoveSelectedViewDown'
| 'canMoveSelectedViewUp'
| 'canMoveSelectedViewDown'
2026-04-29 19:26:24 +02:00
| 'noteWidth'
| 'defaultNoteWidth'
| 'onSetNoteWidth'
| 'onSetDefaultNoteWidth'
| 'onToggleAIChat'
>
type CommandRegistryVaultActions = Pick<
CommandRegistryConfig,
| 'onOpenVault'
| 'onCreateEmptyVault'
| 'onAddRemote'
| 'canAddRemote'
2026-04-26 17:08:14 +02:00
| 'isGitVault'
| 'onInitializeGit'
| 'onCheckForUpdates'
| 'onCreateType'
2026-04-26 08:18:47 +02:00
| 'locale'
| 'systemLocale'
| 'selectedUiLanguage'
| 'onSetUiLanguage'
2026-04-29 02:35:52 +02:00
| 'onSetThemeMode'
| 'onRemoveActiveVault'
| 'onRestoreGettingStarted'
| 'isGettingStartedHidden'
| 'vaultCount'
| 'onReloadVault'
| 'onRepairVault'
| 'onOpenInNewWindow'
2026-04-27 01:45:16 +02:00
| 'onRevealActiveFile'
| 'onCopyActiveFilePath'
| 'onOpenActiveFileExternal'
| 'onRestoreDeletedNote'
| 'canRestoreDeletedNote'
>
type CommandRegistryAiActions = Pick<
CommandRegistryConfig,
| 'mcpStatus'
| 'onInstallMcp'
| 'aiAgentsStatus'
| 'vaultAiGuidanceStatus'
| 'onOpenAiAgents'
| 'onRestoreVaultAiGuidance'
| 'onSetDefaultAiAgent'
| 'selectedAiAgent'
| 'onCycleDefaultAiAgent'
| 'selectedAiAgentLabel'
>
type CommandRegistryNoteActions = Pick<
CommandRegistryConfig,
| 'onSetNoteIcon'
| 'onRemoveNoteIcon'
| 'onChangeNoteType'
| 'onMoveNoteToFolder'
| 'canMoveNoteToFolder'
| 'activeNoteHasIcon'
| 'noteListFilter'
| 'onSetNoteListFilter'
| 'onToggleFavorite'
| 'onToggleOrganized'
| 'onCustomizeNoteListColumns'
| 'canCustomizeNoteListColumns'
| 'noteListColumnsLabel'
>
function createKeyboardActions(
config: AppCommandsConfig,
): Omit<Parameters<typeof useAppKeyboard>[0], 'onArchiveNote'> {
return {
onQuickOpen: config.onQuickOpen,
onCommandPalette: config.onCommandPalette,
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64) * feat: add search backend (qmd CLI) and design file - Add search.rs: qmd integration for keyword (BM25), semantic (vsearch), and hybrid search modes via CLI JSON output - Register search_vault Tauri command in lib.rs - Design file with 3 frames: empty, results, no-results states - Fix pre-existing test failures: install @tauri-apps/plugin-opener, add mock in test setup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add SearchPanel UI with Cmd+Shift+F shortcut - SearchPanel component: full-text search overlay with keyword/semantic mode toggle, debounced search (200ms), arrow key navigation, result count + elapsed time display, note type badges from vault entries - Cmd+Shift+F shortcut registered in useAppKeyboard - Mock search_vault handler in mock-tauri for browser dev mode - SearchResult/SearchResponse types in types.ts - Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut - scrollIntoView mock in test setup for jsdom compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct SearchPanel imports for Tauri/browser dual-mode Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri with a searchCall wrapper, fixing the TypeScript build error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: make test_detect_collection_fallback robust when qmd not installed * fix: reset localStorage between App tests to prevent view mode state leak --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
onSearch: config.onSearch,
2026-04-27 10:12:13 +02:00
onFindInNote: config.onFindInNote,
onReplaceInNote: config.onReplaceInNote,
onCreateNote: config.onCreateNote,
onSave: config.onSave,
onOpenSettings: config.onOpenSettings,
onDeleteNote: config.onDeleteNote,
onSetViewMode: config.onSetViewMode,
onZoomIn: config.onZoomIn,
onZoomOut: config.onZoomOut,
onZoomReset: config.onZoomReset,
feat: back/forward navigation between opened notes (#75) * feat: add useNavigationHistory hook for browser-style back/forward Pure state management hook that tracks a navigation stack of note paths with cursor-based back/forward traversal. Handles edge cases: duplicate pushes (no-op), forward stack cleared on new push, invalid path skipping, and path removal when tabs close. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: integrate back/forward navigation into UI and keyboard shortcuts - Add Back/Forward arrow buttons to TabBar (left of tabs) - Wire useNavigationHistory through App → Editor → TabBar - Add Cmd+[ and Cmd+] keyboard shortcuts via useAppKeyboard - Register Go Back/Go Forward in command palette - History tracks active tab changes, skips closed tabs gracefully - Navigation from history doesn't re-push to stack (ref guard) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add mouse button 3/4 and trackpad swipe for back/forward - Mouse buttons 3 (back) and 4 (forward) trigger navigation - macOS trackpad two-finger horizontal swipe triggers back/forward using accumulated wheel deltaX with a 120px threshold - Debounced reset after 300ms of inactivity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: add back-forward-nav.pen with 3 state frames Shows: default (both disabled), one note visited (back disabled), two notes visited (back enabled). Matches tab bar button placement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:39:12 +01:00
onGoBack: config.onGoBack,
onGoForward: config.onGoForward,
onToggleAIChat: config.onToggleAIChat,
onToggleRawEditor: config.onToggleRawEditor,
onToggleInspector: config.onToggleInspector,
onToggleFavorite: config.onToggleFavorite,
2026-04-10 12:08:17 +02:00
onToggleOrganized: config.onToggleOrganized,
onOpenInNewWindow: config.onOpenInNewWindow,
activeTabPathRef: config.activeTabPathRef,
multiSelectionCommandRef: config.multiSelectionCommandRef,
}
}
function createMenuEventHandlers(
config: AppCommandsConfig,
selectFilter: (filter: SidebarFilter) => void,
viewChanges: () => void,
): Omit<Parameters<typeof useMenuEvents>[0], 'onArchiveNote'> {
return {
...createMenuEventActionHandlers(config, selectFilter),
...createMenuEventVaultHandlers(config, viewChanges),
...createMenuEventState(config),
}
}
function createMenuEventActionHandlers(
config: AppCommandsConfig,
selectFilter: (filter: SidebarFilter) => void,
): Pick<
Omit<Parameters<typeof useMenuEvents>[0], 'onArchiveNote'>,
| 'onSetViewMode'
| 'onCreateNote'
| 'onCreateType'
| 'onQuickOpen'
| 'onSave'
| 'onOpenSettings'
| 'onToggleInspector'
| 'onCommandPalette'
| 'onZoomIn'
| 'onZoomOut'
| 'onZoomReset'
| 'onDeleteNote'
2026-04-27 10:12:13 +02:00
| 'onFindInNote'
| 'onReplaceInNote'
| 'onSearch'
| 'onToggleRawEditor'
| 'onToggleDiff'
| 'onToggleAIChat'
| 'onToggleOrganized'
| 'onGoBack'
| 'onGoForward'
| 'onCheckForUpdates'
| 'onSelectFilter'
> {
return {
onSetViewMode: config.onSetViewMode,
onCreateNote: config.onCreateNote,
onCreateType: config.onCreateType,
onQuickOpen: config.onQuickOpen,
onSave: config.onSave,
onOpenSettings: config.onOpenSettings,
onToggleInspector: config.onToggleInspector,
onCommandPalette: config.onCommandPalette,
onZoomIn: config.onZoomIn,
onZoomOut: config.onZoomOut,
onZoomReset: config.onZoomReset,
onDeleteNote: config.onDeleteNote,
2026-04-27 10:12:13 +02:00
onFindInNote: config.onFindInNote,
onReplaceInNote: config.onReplaceInNote,
onSearch: config.onSearch,
onToggleRawEditor: config.onToggleRawEditor,
onToggleDiff: config.onToggleDiff,
onToggleAIChat: config.onToggleAIChat,
2026-04-10 12:08:17 +02:00
onToggleOrganized: config.onToggleOrganized,
onGoBack: config.onGoBack,
onGoForward: config.onGoForward,
onCheckForUpdates: config.onCheckForUpdates,
onSelectFilter: selectFilter,
}
}
function createMenuEventVaultHandlers(
config: AppCommandsConfig,
viewChanges: () => void,
): Pick<
Omit<Parameters<typeof useMenuEvents>[0], 'onArchiveNote'>,
| 'onOpenVault'
| 'onRemoveActiveVault'
| 'onRestoreGettingStarted'
| 'onAddRemote'
| 'onCommitPush'
| 'onPull'
| 'onResolveConflicts'
| 'onViewChanges'
| 'onInstallMcp'
| 'onReloadVault'
| 'onRepairVault'
| 'onOpenInNewWindow'
| 'onRestoreDeletedNote'
> {
return {
onOpenVault: config.onOpenVault,
onRemoveActiveVault: config.onRemoveActiveVault,
onRestoreGettingStarted: config.onRestoreGettingStarted,
onAddRemote: config.onAddRemote ?? requestAddRemote,
onCommitPush: config.onCommitPush,
onPull: config.onPull,
onResolveConflicts: config.onResolveConflicts,
onViewChanges: viewChanges,
onInstallMcp: config.onInstallMcp,
onReloadVault: config.onReloadVault,
onRepairVault: config.onRepairVault,
onOpenInNewWindow: config.onOpenInNewWindow,
onRestoreDeletedNote: config.onRestoreDeletedNote,
}
}
function createMenuEventState(
config: AppCommandsConfig,
): Pick<
Omit<Parameters<typeof useMenuEvents>[0], 'onArchiveNote'>,
| 'activeTabPathRef'
| 'multiSelectionCommandRef'
| 'activeTabPath'
| 'modifiedCount'
| 'hasRestorableDeletedNote'
| 'hasNoRemote'
> {
return {
activeTabPathRef: config.activeTabPathRef,
multiSelectionCommandRef: config.multiSelectionCommandRef,
activeTabPath: config.activeTabPath,
modifiedCount: config.modifiedCount,
hasRestorableDeletedNote: config.canRestoreDeletedNote,
hasNoRemote: config.canAddRemote ?? true,
}
}
function createCommandRegistrySelectionConfig(
config: AppCommandsConfig,
): CommandRegistrySelectionState {
return {
activeNoteModified: config.activeNoteModified,
onZoomIn: config.onZoomIn,
onZoomOut: config.onZoomOut,
onZoomReset: config.onZoomReset,
zoomLevel: config.zoomLevel,
onSelect: config.onSelect,
onRenameFolder: config.onRenameFolder,
onDeleteFolder: config.onDeleteFolder,
2026-04-27 01:45:16 +02:00
onRevealSelectedFolder: config.onRevealSelectedFolder,
onCopySelectedFolderPath: config.onCopySelectedFolderPath,
showInbox: config.showInbox,
onGoBack: config.onGoBack,
onGoForward: config.onGoForward,
canGoBack: config.canGoBack,
canGoForward: config.canGoForward,
selection: config.selection,
}
}
function createCommandRegistryCoreConfig(
config: AppCommandsConfig,
): CommandRegistryCoreActions {
return {
activeTabPath: config.activeTabPath,
entries: config.entries,
modifiedCount: config.modifiedCount,
onQuickOpen: config.onQuickOpen,
onCreateNote: config.onCreateNote,
onCreateNoteOfType: config.onCreateNoteOfType,
onSave: config.onSave,
onOpenSettings: config.onOpenSettings,
2026-04-10 12:45:37 +02:00
onOpenFeedback: config.onOpenFeedback,
onDeleteNote: config.onDeleteNote,
onArchiveNote: config.onArchiveNote,
onUnarchiveNote: config.onUnarchiveNote,
onCommitPush: config.onCommitPush,
onPull: config.onPull,
onResolveConflicts: config.onResolveConflicts,
onSetViewMode: config.onSetViewMode,
onToggleInspector: config.onToggleInspector,
onToggleDiff: config.onToggleDiff,
onToggleRawEditor: config.onToggleRawEditor,
2026-04-28 20:41:07 +02:00
selectedViewName: config.selectedViewName,
onMoveSelectedViewUp: config.onMoveSelectedViewUp,
onMoveSelectedViewDown: config.onMoveSelectedViewDown,
canMoveSelectedViewUp: config.canMoveSelectedViewUp,
canMoveSelectedViewDown: config.canMoveSelectedViewDown,
2026-04-27 10:12:13 +02:00
onFindInNote: config.onFindInNote,
onReplaceInNote: config.onReplaceInNote,
2026-04-29 19:26:24 +02:00
noteWidth: config.noteWidth,
defaultNoteWidth: config.defaultNoteWidth,
onSetNoteWidth: config.onSetNoteWidth,
onSetDefaultNoteWidth: config.onSetDefaultNoteWidth,
onToggleAIChat: config.onToggleAIChat,
}
}
function createCommandRegistryVaultConfig(
config: AppCommandsConfig,
): CommandRegistryVaultActions {
return {
onOpenVault: config.onOpenVault,
2026-04-19 01:06:40 +02:00
onCreateEmptyVault: config.onCreateEmptyVault,
onAddRemote: config.onAddRemote ?? requestAddRemote,
canAddRemote: config.canAddRemote ?? true,
2026-04-26 17:08:14 +02:00
isGitVault: config.isGitVault,
onInitializeGit: config.onInitializeGit,
onCheckForUpdates: config.onCheckForUpdates,
onCreateType: config.onCreateType,
2026-04-26 08:18:47 +02:00
locale: config.locale,
systemLocale: config.systemLocale,
selectedUiLanguage: config.selectedUiLanguage,
onSetUiLanguage: config.onSetUiLanguage,
2026-04-29 02:35:52 +02:00
onSetThemeMode: config.onSetThemeMode,
onRemoveActiveVault: config.onRemoveActiveVault,
onRestoreGettingStarted: config.onRestoreGettingStarted,
isGettingStartedHidden: config.isGettingStartedHidden,
vaultCount: config.vaultCount,
onReloadVault: config.onReloadVault,
onRepairVault: config.onRepairVault,
onOpenInNewWindow: config.onOpenInNewWindow,
2026-04-27 01:45:16 +02:00
onRevealActiveFile: config.onRevealActiveFile,
onCopyActiveFilePath: config.onCopyActiveFilePath,
onOpenActiveFileExternal: config.onOpenActiveFileExternal,
onRestoreDeletedNote: config.onRestoreDeletedNote,
canRestoreDeletedNote: config.canRestoreDeletedNote,
}
}
function createCommandRegistryAiConfig(
config: AppCommandsConfig,
): CommandRegistryAiActions {
return {
mcpStatus: config.mcpStatus,
onInstallMcp: config.onInstallMcp,
2026-04-14 11:55:48 +02:00
aiAgentsStatus: config.aiAgentsStatus,
vaultAiGuidanceStatus: config.vaultAiGuidanceStatus,
2026-04-13 19:37:59 +02:00
onOpenAiAgents: config.onOpenAiAgents,
onRestoreVaultAiGuidance: config.onRestoreVaultAiGuidance,
2026-04-14 11:55:48 +02:00
onSetDefaultAiAgent: config.onSetDefaultAiAgent,
selectedAiAgent: config.selectedAiAgent,
2026-04-13 19:37:59 +02:00
onCycleDefaultAiAgent: config.onCycleDefaultAiAgent,
selectedAiAgentLabel: config.selectedAiAgentLabel,
}
}
function createCommandRegistryNoteConfig(
config: AppCommandsConfig,
): CommandRegistryNoteActions {
return {
onSetNoteIcon: config.onSetNoteIcon,
onRemoveNoteIcon: config.onRemoveNoteIcon,
onChangeNoteType: config.onChangeNoteType,
onMoveNoteToFolder: config.onMoveNoteToFolder,
canMoveNoteToFolder: config.canMoveNoteToFolder,
activeNoteHasIcon: config.activeNoteHasIcon,
noteListFilter: config.noteListFilter,
onSetNoteListFilter: config.onSetNoteListFilter,
onToggleFavorite: config.onToggleFavorite,
onToggleOrganized: config.onToggleOrganized,
onCustomizeNoteListColumns: config.onCustomizeNoteListColumns,
canCustomizeNoteListColumns: config.canCustomizeNoteListColumns,
2026-04-18 20:31:25 +02:00
noteListColumnsLabel: config.noteListColumnsLabel,
}
}
function createCommandRegistryConfig(config: AppCommandsConfig): CommandRegistryConfig {
return {
...createCommandRegistryCoreConfig(config),
...createCommandRegistrySelectionConfig(config),
...createCommandRegistryVaultConfig(config),
...createCommandRegistryAiConfig(config),
...createCommandRegistryNoteConfig(config),
}
}
/** Sets up keyboard shortcuts, command registry, menu events, and keyboard navigation. */
export function useAppCommands(config: AppCommandsConfig): CommandAction[] {
const entriesRef = useRef(config.entries)
// eslint-disable-next-line react-hooks/refs
entriesRef.current = config.entries
const toggleArchive = useCallback((path: string) => {
const entry = entriesRef.current.find(e => e.path === path)
;(entry?.archived ? config.onUnarchiveNote : config.onArchiveNote)(path)
}, [config.onArchiveNote, config.onUnarchiveNote])
const { onSelect } = config
const selectFilter = useCallback((filter: SidebarFilter) => {
const safeFilter = !config.showInbox && filter === 'inbox' ? 'all' : filter
onSelect({ kind: 'filter', filter: safeFilter })
}, [config.showInbox, onSelect])
const viewChanges = useCallback(() => {
onSelect({ kind: 'filter', filter: 'changes' })
}, [onSelect])
const keyboardActions = createKeyboardActions(config)
const menuEventHandlers = createMenuEventHandlers(config, selectFilter, viewChanges)
useAppKeyboard({ ...keyboardActions, onArchiveNote: toggleArchive })
useMenuEvents({ ...menuEventHandlers, onArchiveNote: toggleArchive })
const commands = useCommandRegistry(createCommandRegistryConfig(config))
useKeyboardNavigation({
activeTabPath: config.activeTabPath,
visibleNotesRef: config.visibleNotesRef,
onReplaceActiveTab: config.onReplaceActiveTab,
onSelectNote: config.onSelectNote,
})
return commands
}