import { useState, useRef, useEffect, useCallback } from 'react' import type { VaultEntry } from '../types' import { NoteSearchList } from './NoteSearchList' import { useNoteSearch } from '../hooks/useNoteSearch' import { translate, type AppLocale } from '../lib/i18n' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button' import { Plus } from '@phosphor-icons/react' import type { NoteSearchResult } from '../hooks/useNoteSearch' interface QuickOpenPaletteProps { open: boolean entries: VaultEntry[] isLoading?: boolean onSelect: (entry: VaultEntry) => void onCreateNote?: (title: string) => unknown onClose: () => void locale?: AppLocale } interface QuickOpenCreateActionProps { title: string onCreate: () => void locale: AppLocale } function quickOpenEmptyMessage(isLoading: boolean, locale: AppLocale): string { return isLoading ? translate(locale, 'status.vault.reloading') : translate(locale, 'noteList.empty.noMatching') } function QuickOpenCreateAction({ title, onCreate, locale }: QuickOpenCreateActionProps) { return (
) } function useQuickOpenCreateAction({ query, isLoading, resultCount, onCreateNote, onClose, }: { query: string isLoading: boolean resultCount: number onCreateNote?: (title: string) => unknown onClose: () => void }) { const title = query.trim() const canCreate = Boolean(onCreateNote && title && !isLoading && resultCount === 0) const create = useCallback(() => { if (!canCreate) return onCreateNote?.(title) onClose() }, [canCreate, title, onCreateNote, onClose]) return { canCreate, create, title } } function useQuickOpenKeyboard({ open, results, selectedIndex, onSelect, onClose, handleKeyDown, createFromQuery, }: { open: boolean results: NoteSearchResult[] selectedIndex: number onSelect: (entry: VaultEntry) => void onClose: () => void handleKeyDown: (e: KeyboardEvent) => void createFromQuery: () => void }) { useEffect(() => { if (!open) return const handler = (e: KeyboardEvent) => { handleKeyDown(e) if (e.key === 'Escape') { e.preventDefault() onClose() } else if (e.key === 'Enter') { e.preventDefault() const selected = results.at(selectedIndex) if (selected) { onSelect(selected.entry) onClose() } else { createFromQuery() } } } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, [open, results, selectedIndex, onSelect, onClose, handleKeyDown, createFromQuery]) } export function QuickOpenPalette({ open, entries, isLoading = false, onSelect, onCreateNote, onClose, locale = 'en' }: QuickOpenPaletteProps) { const [query, setQuery] = useState('') const inputRef = useRef(null) const { results, selectedIndex, setSelectedIndex, handleKeyDown } = useNoteSearch(entries, query) const createAction = useQuickOpenCreateAction({ query, isLoading, resultCount: results.length, onCreateNote, onClose }) useEffect(() => { if (open) { // eslint-disable-next-line react-hooks/set-state-in-effect -- reset on dialog open setQuery('') setSelectedIndex(0) setTimeout(() => inputRef.current?.focus(), 50) } }, [open, setSelectedIndex]) useQuickOpenKeyboard({ open, results, selectedIndex, onSelect, onClose, handleKeyDown, createFromQuery: createAction.create }) if (!open) return null return (
e.stopPropagation()} > setQuery(e.target.value)} /> item.entry.path} onItemClick={(item) => { onSelect(item.entry) onClose() }} onItemHover={(i) => setSelectedIndex(i)} emptyMessage={quickOpenEmptyMessage(isLoading, locale)} className="flex-1 overflow-y-auto" /> {createAction.canCreate && }
) }