+ {yamlError && (
+
+ YAML error:
+ {yamlError}
+
+ )}
+
+ {autocomplete && autocomplete.items.length > 0 && (
+
+ `${item.title}-${item.path ?? i}`}
+ onItemClick={(item) => insertWikilink(item.entryTitle ?? item.title)}
+ onItemHover={(i) => setAutocomplete(prev => prev ? { ...prev, selectedIndex: i } : null)}
+ />
+
+ )}
+
+ )
+}
diff --git a/src/hooks/useAppCommands.ts b/src/hooks/useAppCommands.ts
index 0d2bbcb2..51be1e07 100644
--- a/src/hooks/useAppCommands.ts
+++ b/src/hooks/useAppCommands.ts
@@ -33,6 +33,7 @@ interface AppCommandsConfig {
onCommitPush: () => void
onSetViewMode: (mode: ViewMode) => void
onToggleInspector: () => void
+ onToggleRawEditor?: () => void
onZoomIn: () => void
onZoomOut: () => void
onZoomReset: () => void
@@ -129,6 +130,7 @@ export function useAppCommands(config: AppCommandsConfig): CommandAction[] {
onCommitPush: config.onCommitPush,
onSetViewMode: config.onSetViewMode,
onToggleInspector: config.onToggleInspector,
+ onToggleRawEditor: config.onToggleRawEditor,
onZoomIn: config.onZoomIn,
onZoomOut: config.onZoomOut,
onZoomReset: config.onZoomReset,
diff --git a/src/hooks/useCommandRegistry.test.ts b/src/hooks/useCommandRegistry.test.ts
index e0d60ff4..1f3b2d34 100644
--- a/src/hooks/useCommandRegistry.test.ts
+++ b/src/hooks/useCommandRegistry.test.ts
@@ -156,6 +156,36 @@ describe('useCommandRegistry', () => {
expect(onTrashNote).toHaveBeenCalledWith('/vault/note/test.md')
})
+ it('has toggle-raw-editor command in View group', () => {
+ const { result } = renderHook(() => useCommandRegistry(makeConfig()))
+ const cmd = result.current.find(c => c.id === 'toggle-raw-editor')
+ expect(cmd).toBeDefined()
+ expect(cmd!.group).toBe('View')
+ })
+
+ it('disables toggle-raw-editor when no note is open', () => {
+ const { result } = renderHook(() => useCommandRegistry(makeConfig({ activeTabPath: null })))
+ const cmd = result.current.find(c => c.id === 'toggle-raw-editor')
+ expect(cmd!.enabled).toBe(false)
+ })
+
+ it('enables toggle-raw-editor when a note is open', () => {
+ const { result } = renderHook(() =>
+ useCommandRegistry(makeConfig({ activeTabPath: '/vault/note/test.md' })),
+ )
+ const cmd = result.current.find(c => c.id === 'toggle-raw-editor')
+ expect(cmd!.enabled).toBe(true)
+ })
+
+ it('calls onToggleRawEditor when toggle-raw-editor executes', () => {
+ const onToggleRawEditor = vi.fn()
+ const { result } = renderHook(() =>
+ useCommandRegistry(makeConfig({ activeTabPath: '/vault/note/test.md', onToggleRawEditor })),
+ )
+ result.current.find(c => c.id === 'toggle-raw-editor')!.execute()
+ expect(onToggleRawEditor).toHaveBeenCalledOnce()
+ })
+
it('disables commit when no modified files', () => {
const { result } = renderHook(() => useCommandRegistry(makeConfig({ modifiedCount: 0 })))
expect(result.current.find(c => c.id === 'commit-push')!.enabled).toBe(false)
diff --git a/src/hooks/useCommandRegistry.ts b/src/hooks/useCommandRegistry.ts
index 6a394ce8..fc3f8d26 100644
--- a/src/hooks/useCommandRegistry.ts
+++ b/src/hooks/useCommandRegistry.ts
@@ -32,6 +32,7 @@ interface CommandRegistryConfig {
onCommitPush: () => void
onSetViewMode: (mode: ViewMode) => void
onToggleInspector: () => void
+ onToggleRawEditor?: () => void
onToggleAIChat?: () => void
onZoomIn: () => void
onZoomOut: () => void
@@ -102,6 +103,30 @@ export function buildTypeCommands(
})
}
+export function buildViewCommands(
+ hasActiveNote: boolean,
+ onSetViewMode: (mode: ViewMode) => void,
+ onToggleInspector: () => void,
+ onToggleRawEditor: (() => void) | undefined,
+ onToggleAIChat: (() => void) | undefined,
+ zoomLevel: number,
+ onZoomIn: () => void,
+ onZoomOut: () => void,
+ onZoomReset: () => void,
+): CommandAction[] {
+ return [
+ { id: 'view-editor', label: 'Editor Only', group: 'View', shortcut: '⌘1', keywords: ['layout', 'focus'], enabled: true, execute: () => onSetViewMode('editor-only') },
+ { id: 'view-editor-list', label: 'Editor + Note List', group: 'View', shortcut: '⌘2', keywords: ['layout'], enabled: true, execute: () => onSetViewMode('editor-list') },
+ { id: 'view-all', label: 'Full Layout', group: 'View', shortcut: '⌘3', keywords: ['layout', 'sidebar'], enabled: true, execute: () => onSetViewMode('all') },
+ { id: 'toggle-inspector', label: 'Toggle Inspector', group: 'View', keywords: ['properties', 'panel', 'right'], enabled: true, execute: onToggleInspector },
+ { id: 'toggle-raw-editor', label: 'Toggle Raw Editor', group: 'View', keywords: ['raw', 'source', 'markdown', 'frontmatter', 'code', 'textarea'], enabled: hasActiveNote, execute: () => onToggleRawEditor?.() },
+ { id: 'toggle-ai-chat', label: 'Toggle AI Chat', group: 'View', shortcut: '⌘I', keywords: ['ai', 'agent', 'chat', 'assistant', 'contextual'], enabled: true, execute: () => onToggleAIChat?.() },
+ { id: 'zoom-in', label: `Zoom In (${zoomLevel}%)`, group: 'View', shortcut: '⌘=', keywords: ['zoom', 'bigger', 'larger', 'scale'], enabled: zoomLevel < 150, execute: onZoomIn },
+ { id: 'zoom-out', label: `Zoom Out (${zoomLevel}%)`, group: 'View', shortcut: '⌘-', keywords: ['zoom', 'smaller', 'scale'], enabled: zoomLevel > 80, execute: onZoomOut },
+ { id: 'zoom-reset', label: 'Reset Zoom', group: 'View', shortcut: '⌘0', keywords: ['zoom', 'actual', 'default', '100'], enabled: zoomLevel !== 100, execute: onZoomReset },
+ ]
+}
+
export function buildThemeCommands(
themes: ThemeFile[] | undefined,
activeThemeId: string | null | undefined,
@@ -130,7 +155,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
activeTabPath, entries, modifiedCount,
onQuickOpen, onCreateNote, onCreateNoteOfType, onSave, onOpenSettings,
onTrashNote, onRestoreNote, onArchiveNote, onUnarchiveNote,
- onCommitPush, onSetViewMode, onToggleInspector, onToggleAIChat, onOpenVault,
+ onCommitPush, onSetViewMode, onToggleInspector, onToggleRawEditor, onToggleAIChat, onOpenVault,
onZoomIn, onZoomOut, onZoomReset, zoomLevel,
onSelect, onOpenDailyNote, onCloseTab,
onGoBack, onGoForward, canGoBack, canGoForward,
@@ -181,14 +206,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
{ id: 'view-changes', label: 'View Pending Changes', group: 'Git', keywords: ['modified', 'diff'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'changes' }) },
// View
- { id: 'view-editor', label: 'Editor Only', group: 'View', shortcut: '⌘1', keywords: ['layout', 'focus'], enabled: true, execute: () => onSetViewMode('editor-only') },
- { id: 'view-editor-list', label: 'Editor + Note List', group: 'View', shortcut: '⌘2', keywords: ['layout'], enabled: true, execute: () => onSetViewMode('editor-list') },
- { id: 'view-all', label: 'Full Layout', group: 'View', shortcut: '⌘3', keywords: ['layout', 'sidebar'], enabled: true, execute: () => onSetViewMode('all') },
- { id: 'toggle-inspector', label: 'Toggle Inspector', group: 'View', keywords: ['properties', 'panel', 'right'], enabled: true, execute: onToggleInspector },
- { id: 'toggle-ai-chat', label: 'Toggle AI Chat', group: 'View', shortcut: '⌘I', keywords: ['ai', 'agent', 'chat', 'assistant', 'contextual'], enabled: true, execute: () => onToggleAIChat?.() },
- { id: 'zoom-in', label: `Zoom In (${zoomLevel}%)`, group: 'View', shortcut: '⌘=', keywords: ['zoom', 'bigger', 'larger', 'scale'], enabled: zoomLevel < 150, execute: onZoomIn },
- { id: 'zoom-out', label: `Zoom Out (${zoomLevel}%)`, group: 'View', shortcut: '⌘-', keywords: ['zoom', 'smaller', 'scale'], enabled: zoomLevel > 80, execute: onZoomOut },
- { id: 'zoom-reset', label: 'Reset Zoom', group: 'View', shortcut: '⌘0', keywords: ['zoom', 'actual', 'default', '100'], enabled: zoomLevel !== 100, execute: onZoomReset },
+ ...buildViewCommands(hasActiveNote, onSetViewMode, onToggleInspector, onToggleRawEditor, onToggleAIChat, zoomLevel, onZoomIn, onZoomOut, onZoomReset),
// Appearance
...buildThemeCommands(themes, activeThemeId, onSwitchTheme, onCreateTheme),
@@ -206,7 +224,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
hasActiveNote, activeTabPath, isArchived, isTrashed, modifiedCount,
onQuickOpen, onCreateNote, onCreateNoteOfType, onSave, onOpenSettings,
onTrashNote, onRestoreNote, onArchiveNote, onUnarchiveNote,
- onCommitPush, onSetViewMode, onToggleInspector, onToggleAIChat, onOpenVault,
+ onCommitPush, onSetViewMode, onToggleInspector, onToggleRawEditor, onToggleAIChat, onOpenVault,
onZoomIn, onZoomOut, onZoomReset, zoomLevel,
onSelect, onOpenDailyNote, onCloseTab,
onGoBack, onGoForward, canGoBack, canGoForward,
diff --git a/src/hooks/useRawMode.test.ts b/src/hooks/useRawMode.test.ts
new file mode 100644
index 00000000..6b207293
--- /dev/null
+++ b/src/hooks/useRawMode.test.ts
@@ -0,0 +1,87 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { renderHook, act } from '@testing-library/react'
+import { useRawMode } from './useRawMode'
+
+describe('useRawMode', () => {
+ let onFlushPending: ReturnType