import van, { State } from "vanjs-core"; import { OpenFile } from "./filestate"; import { addEditor } from "./editorgrid"; import { allFiles, shouldIgnore } from "./foldernav"; const v = van.tags; export const QuickOpen = (() => { const isOpen = van.state(false); const query = van.state(""); const files = van.state([]); const filteredFiles = van.derive(() => { let result = files.val; if (query.val) { result = result.filter(f => f.toLowerCase().includes(query.val.toLowerCase())); } return result .map(path => ({ path, ignored: shouldIgnore(path) })) .sort((a, b) => (a.ignored === b.ignored ? 0 : a.ignored ? 1 : -1)) .slice(0, 100); }); const selectedIndex = van.state(0); // Update selectedIndex when filteredFiles changes van.derive(() => { filteredFiles.val; // track dependency selectedIndex.val = 0; }); const close = () => { isOpen.val = false; query.val = ""; selectedIndex.val = 0; }; const openSelectedFile = async () => { const item = filteredFiles.val[selectedIndex.val]; if (item) { const file = await OpenFile.openFile(item.path); if (file) { addEditor(file); } } close(); }; const handleKeyDown = (e: KeyboardEvent) => { if (!isOpen.val) return; if (e.key === "Escape") { close(); e.preventDefault(); } else if (e.key === "Enter") { openSelectedFile(); e.preventDefault(); } else if (e.key === "ArrowDown") { selectedIndex.val = (selectedIndex.val + 1) % filteredFiles.val.length; e.preventDefault(); } else if (e.key === "ArrowUp") { selectedIndex.val = (selectedIndex.val - 1 + filteredFiles.val.length) % filteredFiles.val.length; e.preventDefault(); } }; const open = async () => { query.val = ""; files.val = allFiles.val; isOpen.val = true; // Focus the input after the DOM is updated setTimeout(() => { const input = document.getElementById("quick-open-input") as HTMLInputElement; input?.focus(); }, 0); }; document.addEventListener("keydown", handleKeyDown, { capture: true }); const modal = v.div( { class: () => isOpen.val ? "fixed inset-0 z-50 flex items-start justify-center pt-20 bg-black bg-opacity-50" : "hidden", onclick: (e: MouseEvent) => { if ((e.target as HTMLElement).id === "quick-open-overlay") { close(); } } }, v.div( { id: "quick-open-overlay", class: "bg-white dark:bg-gray-800 w-1/2 max-w-2xl rounded shadow-lg overflow-hidden flex flex-col", }, v.input( { id: "quick-open-input", class: "w-full p-4 text-lg border-b dark:border-gray-700 dark:bg-gray-800 dark:text-white outline-none", placeholder: "Quick Open...", value: query, oninput: (e: any) => (query.val = e.target.value), }, ), v.div( { class: "max-h-96 overflow-y-auto" }, () => v.ul( filteredFiles.val.map(({ path, ignored }, i) => v.div( { class: () => `p-2 cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-900 ${selectedIndex.val === i ? "bg-blue-200 dark:bg-blue-800" : ""} ${ignored ? "opacity-50" : ""}`, onclick: () => { selectedIndex.val = i; openSelectedFile(); }, }, path ) )) ) ) ); return { dom: modal, open, }; })();