2026-02-25 17:24:21 +01:00
|
|
|
import { useState, useRef, useEffect } from 'react'
|
2026-02-14 21:17:38 +01:00
|
|
|
import type { VaultEntry } from '../types'
|
2026-02-25 17:24:21 +01:00
|
|
|
import { NoteSearchList } from './NoteSearchList'
|
|
|
|
|
import { useNoteSearch } from '../hooks/useNoteSearch'
|
2026-02-14 21:17:38 +01:00
|
|
|
|
|
|
|
|
interface QuickOpenPaletteProps {
|
|
|
|
|
open: boolean
|
|
|
|
|
entries: VaultEntry[]
|
|
|
|
|
onSelect: (entry: VaultEntry) => void
|
|
|
|
|
onClose: () => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function QuickOpenPalette({ open, entries, onSelect, onClose }: QuickOpenPaletteProps) {
|
|
|
|
|
const [query, setQuery] = useState('')
|
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null)
|
2026-02-25 17:24:21 +01:00
|
|
|
const { results, selectedIndex, setSelectedIndex, handleKeyDown } = useNoteSearch(entries, query)
|
2026-02-14 21:17:38 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (open) {
|
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
|
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- reset on dialog open
|
2026-02-25 17:24:21 +01:00
|
|
|
setQuery('')
|
|
|
|
|
setSelectedIndex(0)
|
2026-02-14 21:17:38 +01:00
|
|
|
setTimeout(() => inputRef.current?.focus(), 50)
|
|
|
|
|
}
|
2026-02-25 17:24:21 +01:00
|
|
|
}, [open, setSelectedIndex])
|
2026-02-14 21:17:38 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open) return
|
2026-02-25 17:24:21 +01:00
|
|
|
const handler = (e: KeyboardEvent) => {
|
|
|
|
|
handleKeyDown(e)
|
2026-02-14 21:17:38 +01:00
|
|
|
if (e.key === 'Escape') {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
onClose()
|
|
|
|
|
} else if (e.key === 'Enter') {
|
|
|
|
|
e.preventDefault()
|
2026-02-25 17:24:21 +01:00
|
|
|
const selected = results[selectedIndex]
|
|
|
|
|
if (selected) {
|
|
|
|
|
onSelect(selected.entry)
|
2026-02-14 21:17:38 +01:00
|
|
|
onClose()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-25 17:24:21 +01:00
|
|
|
window.addEventListener('keydown', handler)
|
|
|
|
|
return () => window.removeEventListener('keydown', handler)
|
|
|
|
|
}, [open, results, selectedIndex, onSelect, onClose, handleKeyDown])
|
2026-02-14 21:17:38 +01:00
|
|
|
|
|
|
|
|
if (!open) return null
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-16 16:56:44 +01:00
|
|
|
<div
|
2026-04-10 18:14:52 +02:00
|
|
|
data-testid="quick-open-palette"
|
2026-02-16 16:56:44 +01:00
|
|
|
className="fixed inset-0 z-[1000] flex justify-center bg-[var(--shadow-dialog)] pt-[15vh]"
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className="flex w-[500px] max-w-[90vw] max-h-[400px] flex-col self-start overflow-hidden rounded-xl border border-[var(--border-dialog)] bg-popover shadow-[0_8px_32px_var(--shadow-dialog)]"
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
>
|
2026-02-14 21:17:38 +01:00
|
|
|
<input
|
|
|
|
|
ref={inputRef}
|
2026-02-16 16:56:44 +01:00
|
|
|
className="border-b border-border bg-transparent px-4 py-3 text-[15px] text-foreground outline-none placeholder:text-muted-foreground"
|
2026-02-14 21:17:38 +01:00
|
|
|
type="text"
|
|
|
|
|
placeholder="Search notes..."
|
|
|
|
|
value={query}
|
|
|
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
|
|
|
/>
|
2026-02-25 17:24:21 +01:00
|
|
|
<NoteSearchList
|
|
|
|
|
items={results}
|
|
|
|
|
selectedIndex={selectedIndex}
|
|
|
|
|
getItemKey={(item) => item.entry.path}
|
|
|
|
|
onItemClick={(item) => {
|
|
|
|
|
onSelect(item.entry)
|
|
|
|
|
onClose()
|
|
|
|
|
}}
|
|
|
|
|
onItemHover={(i) => setSelectedIndex(i)}
|
|
|
|
|
emptyMessage="No matching notes"
|
|
|
|
|
className="flex-1 overflow-y-auto"
|
|
|
|
|
/>
|
2026-02-14 21:17:38 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|