2026-04-06 10:29:27 +02:00
|
|
|
import { useEffect, useRef } from 'react'
|
|
|
|
|
import type { VaultEntry } from '../types'
|
2026-02-21 10:50:50 +01:00
|
|
|
|
|
|
|
|
interface KeyboardNavigationOptions {
|
|
|
|
|
activeTabPath: string | null
|
2026-04-06 10:29:27 +02:00
|
|
|
visibleNotesRef: React.RefObject<VaultEntry[]>
|
2026-02-21 10:50:50 +01:00
|
|
|
onReplaceActiveTab: (entry: VaultEntry) => void
|
|
|
|
|
onSelectNote: (entry: VaultEntry) => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function navigateNote(
|
|
|
|
|
visibleNotesRef: React.RefObject<VaultEntry[]>,
|
|
|
|
|
activeTabPathRef: React.RefObject<string | null>,
|
|
|
|
|
onReplace: React.RefObject<(entry: VaultEntry) => void>,
|
|
|
|
|
onSelect: React.RefObject<(entry: VaultEntry) => void>,
|
|
|
|
|
direction: 1 | -1,
|
|
|
|
|
) {
|
|
|
|
|
const notes = visibleNotesRef.current!
|
|
|
|
|
if (notes.length === 0) return
|
|
|
|
|
|
|
|
|
|
const currentPath = activeTabPathRef.current
|
|
|
|
|
const currentIndex = notes.findIndex((n) => n.path === currentPath)
|
|
|
|
|
|
|
|
|
|
const nextIndex = currentIndex === -1
|
|
|
|
|
? (direction === 1 ? 0 : notes.length - 1)
|
2026-04-06 10:29:27 +02:00
|
|
|
: currentIndex + direction
|
|
|
|
|
|
|
|
|
|
// Clamp to list bounds — don't wrap around
|
|
|
|
|
if (nextIndex < 0 || nextIndex >= notes.length) return
|
2026-02-21 10:50:50 +01:00
|
|
|
|
2026-05-07 20:44:45 +02:00
|
|
|
const nextNote = notes.at(nextIndex)
|
|
|
|
|
if (!nextNote) return
|
2026-02-21 10:50:50 +01:00
|
|
|
if (currentPath) {
|
|
|
|
|
onReplace.current!(nextNote)
|
|
|
|
|
} else {
|
|
|
|
|
onSelect.current!(nextNote)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 20:44:45 +02:00
|
|
|
function noteNavigationDirection(event: KeyboardEvent): 1 | -1 | null {
|
|
|
|
|
if (!event.altKey || event.shiftKey) return null
|
|
|
|
|
if (event.key === 'ArrowDown') return 1
|
|
|
|
|
if (event.key === 'ArrowUp') return -1
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 10:55:45 +01:00
|
|
|
function useLatestRef<T>(value: T): React.RefObject<T> {
|
|
|
|
|
const ref = useRef(value)
|
fix: resolve React hooks/compiler ESLint errors
- Wrap ref assignments in useEffect to fix refs-during-render in
useTabManagement, useKeyboardNavigation, and Editor
- Rewrite useAppKeyboard to define keyMap inside useEffect, eliminating
ref access during render
- Add eslint-disable for react-hooks/set-state-in-effect on legitimate
dialog reset patterns (CommitDialog, CreateNoteDialog, CreateTypeDialog,
QuickOpenPalette, AIChatPanel, useVaultLoader)
- Add eslint-disable for react-hooks/static-components on icon lookups
(NoteItem, NoteList PinnedCard) — stateless icon components from a
static map
- Add eslint-disable for react-hooks/purity on Date.now() in
NoteItem TrashDateLine — intentionally memoized on trashedAt
- Remove unused `model` param from buildSystemPrompt (ai-chat.ts) and
update callers
- Fix exhaustive-deps: suppress vault object dep in App.tsx git history
effect (vault object is unstable, loadGitHistory is the real dep)
- Remove unused `modifiedFiles` from useNoteListData params and caller
- Add missing `ref` to Sidebar useOutsideClick deps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:06:51 +01:00
|
|
|
useEffect(() => { ref.current = value })
|
2026-02-22 10:55:45 +01:00
|
|
|
return ref
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 10:50:50 +01:00
|
|
|
export function useKeyboardNavigation({
|
2026-04-06 10:29:27 +02:00
|
|
|
activeTabPath, visibleNotesRef,
|
refactor: remove tab bar — single note open at a time
Replace the multi-tab model with single-note navigation. One note is
open at a time; clicking any note (sidebar, wikilink, relationship)
replaces the current note in the editor. Back/Forward history still
works via Cmd+[/].
Removed: TabBar component, useClosedTabHistory, tab reorder/close/
reopen logic, Cmd+W/Cmd+Shift+T shortcuts, Close Tab and Reopen
Closed Tab menu items, tabLayout utility, and all related tests.
Simplified: useTabManagement (single-note), useAppNavigation (no tab
lookup), useKeyboardNavigation (note nav only), useNoteCreation (no
close-tab cleanup), useDeleteActions (deselect instead of close tab),
Editor (no TabBar render), Rust menu (removed 2 menu items).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:24:49 +01:00
|
|
|
onReplaceActiveTab, onSelectNote,
|
2026-02-21 10:50:50 +01:00
|
|
|
}: KeyboardNavigationOptions) {
|
2026-02-22 10:55:45 +01:00
|
|
|
const activeTabPathRef = useLatestRef(activeTabPath)
|
|
|
|
|
const onReplaceRef = useLatestRef(onReplaceActiveTab)
|
|
|
|
|
const onSelectNoteRef = useLatestRef(onSelectNote)
|
2026-02-21 10:50:50 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
refactor: remove tab bar — single note open at a time
Replace the multi-tab model with single-note navigation. One note is
open at a time; clicking any note (sidebar, wikilink, relationship)
replaces the current note in the editor. Back/Forward history still
works via Cmd+[/].
Removed: TabBar component, useClosedTabHistory, tab reorder/close/
reopen logic, Cmd+W/Cmd+Shift+T shortcuts, Close Tab and Reopen
Closed Tab menu items, tabLayout utility, and all related tests.
Simplified: useTabManagement (single-note), useAppNavigation (no tab
lookup), useKeyboardNavigation (note nav only), useNoteCreation (no
close-tab cleanup), useDeleteActions (deselect instead of close tab),
Editor (no TabBar render), Rust menu (removed 2 menu items).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:24:49 +01:00
|
|
|
const mod = e.metaKey || e.ctrlKey
|
|
|
|
|
if (!mod) return
|
|
|
|
|
// Cmd+Alt+ArrowUp/Down: navigate notes in the current list
|
2026-05-07 20:44:45 +02:00
|
|
|
const direction = noteNavigationDirection(e)
|
|
|
|
|
if (direction === null) return
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
navigateNote(visibleNotesRef, activeTabPathRef, onReplaceRef, onSelectNoteRef, direction)
|
2026-02-21 10:50:50 +01:00
|
|
|
}
|
|
|
|
|
window.addEventListener('keydown', handleKeyDown)
|
|
|
|
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
refactor: remove tab bar — single note open at a time
Replace the multi-tab model with single-note navigation. One note is
open at a time; clicking any note (sidebar, wikilink, relationship)
replaces the current note in the editor. Back/Forward history still
works via Cmd+[/].
Removed: TabBar component, useClosedTabHistory, tab reorder/close/
reopen logic, Cmd+W/Cmd+Shift+T shortcuts, Close Tab and Reopen
Closed Tab menu items, tabLayout utility, and all related tests.
Simplified: useTabManagement (single-note), useAppNavigation (no tab
lookup), useKeyboardNavigation (note nav only), useNoteCreation (no
close-tab cleanup), useDeleteActions (deselect instead of close tab),
Editor (no TabBar render), Rust menu (removed 2 menu items).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:24:49 +01:00
|
|
|
}, [activeTabPathRef, visibleNotesRef, onReplaceRef, onSelectNoteRef])
|
2026-02-21 10:50:50 +01:00
|
|
|
}
|