The keyboard navigation was computing its own note list with hardcoded sortByModified, which didn't match the actual UI order (Inbox sorts by createdAt, custom types use configurable sorts, etc.). Now the NoteList component writes its sorted list to a shared ref that the navigation hook reads directly, ensuring navigation always matches visual order. Also fixes navigation not wrapping at list boundaries per spec. Syncs CodeScene thresholds with current remote scores. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
import { useEffect, useRef } from 'react'
|
|
import type { VaultEntry } from '../types'
|
|
|
|
interface KeyboardNavigationOptions {
|
|
activeTabPath: string | null
|
|
visibleNotesRef: React.RefObject<VaultEntry[]>
|
|
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)
|
|
: currentIndex + direction
|
|
|
|
// Clamp to list bounds — don't wrap around
|
|
if (nextIndex < 0 || nextIndex >= notes.length) return
|
|
|
|
const nextNote = notes[nextIndex]
|
|
if (currentPath) {
|
|
onReplace.current!(nextNote)
|
|
} else {
|
|
onSelect.current!(nextNote)
|
|
}
|
|
}
|
|
|
|
function useLatestRef<T>(value: T): React.RefObject<T> {
|
|
const ref = useRef(value)
|
|
useEffect(() => { ref.current = value })
|
|
return ref
|
|
}
|
|
|
|
export function useKeyboardNavigation({
|
|
activeTabPath, visibleNotesRef,
|
|
onReplaceActiveTab, onSelectNote,
|
|
}: KeyboardNavigationOptions) {
|
|
const activeTabPathRef = useLatestRef(activeTabPath)
|
|
const onReplaceRef = useLatestRef(onReplaceActiveTab)
|
|
const onSelectNoteRef = useLatestRef(onSelectNote)
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
const mod = e.metaKey || e.ctrlKey
|
|
if (!mod) return
|
|
// Cmd+Alt+ArrowUp/Down: navigate notes in the current list
|
|
if (e.altKey && !e.shiftKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
|
|
e.preventDefault()
|
|
const direction: 1 | -1 = e.key === 'ArrowDown' ? 1 : -1
|
|
navigateNote(visibleNotesRef, activeTabPathRef, onReplaceRef, onSelectNoteRef, direction)
|
|
}
|
|
}
|
|
window.addEventListener('keydown', handleKeyDown)
|
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
}, [activeTabPathRef, visibleNotesRef, onReplaceRef, onSelectNoteRef])
|
|
}
|