/* ObsiGate — Command Palette (Ctrl+Shift+Espace / Ctrl+Alt+Espace) */ import { api } from './auth.js'; import { showToast, toggleTheme, TabManager } from './ui.js'; import { openEditor } from './utils.js'; import { state } from './state.js'; import { t } from './i18n.js'; function triggerGoHome() { const e = document.getElementById('header-logo'); if (e) e.click(); } function getCurrentFile() { const id = TabManager._activeTabId; if (id) { const t = TabManager._tabs.find(x => x.id === id); if (t) return { vault: t.vault, path: t.path }; } if (state.currentVault && state.currentPath) return { vault: state.currentVault, path: state.currentPath }; return null; } // ── Query parser: +word = required, -word = exclude, word = required ── function parseQuery(raw) { const positive = []; const negative = []; const parts = (raw || '').split(/\s+/).filter(Boolean); for (const p of parts) { if (p.startsWith('-') && p.length > 1) negative.push(p.slice(1).toLowerCase()); else if (p.startsWith('+')) positive.push(p.slice(1).toLowerCase()); else positive.push(p.toLowerCase()); } return { positive, negative }; } // ── DOM / state ── let _overlay = null, _input = null, _results = null, _mode = 'files', _sel = 0, _to = null, _items = []; let _browse = { action: null, vault: null, path: '' }; let _browseFilterEl = null, _browseFilter = '', _allItems = []; let _browseFilterTimer = null; // debounce for global search // ── Commands ── const CMDS = [ { id:'home', label:'🏠 Accueil', desc:"Retour à l'accueil", cat:'Navigation', act:()=>{close();triggerGoHome();} }, { id:'edit-current', label:'✏️ Éditer fichier courant', desc:'Ouvrir le fichier actif dans l\'éditeur', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f)openEditor(f.vault,f.path);else showToast(t('toast.no_open_file'),'error');} }, { id:'popout', label:'🪟 Pop-out', desc:'Ouvrir le document dans une fenêtre', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f)window.open(`/popout/${encodeURIComponent(f.vault)}/${encodeURIComponent(f.path)}`,'popout','width=1000,height=700');else showToast(t('toast.no_open_file'),'error');} }, { id:'create-file', label:'📄 Créer fichier markdown', desc:'Choisir la vault, naviguer, puis nommer le fichier', cat:'Fichiers', act:()=>startBrowse('create-file') }, { id:'delete-file', label:'🗑️ Supprimer fichier', desc:'Rechercher un fichier à supprimer', cat:'Fichiers', act:()=>startBrowse('delete-file') }, { id:'delete-cur', label:'🗑️ Supprimer fichier courant', desc:'Supprimer le fichier de l\'onglet actif', cat:'Fichiers', act:async()=>{close();await delCurFile();} }, { id:'close-cur', label:'✕ Fermer fichier courant', desc:'Fermer l\'onglet actif sans supprimer', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f){const tab=TabManager._tabs.find(t=>t.vault===f.vault&&t.path===f.path);if(tab)TabManager.close(tab.id);else triggerGoHome();}else{showToast(t('toast.no_open_file'),'error');}} }, { id:'create-dir', label:'📁 Créer répertoire', desc:'Choisir la vault, naviguer, puis nommer le dossier', cat:'Répertoires', act:()=>startBrowse('create-dir') }, { id:'delete-dir', label:'🗑️ Supprimer répertoire', desc:'Choisir la vault, naviguer et supprimer', cat:'Répertoires', act:()=>startBrowse('delete-dir') }, { id:'theme', label:'🌓 Changer le thème', desc:'Basculer clair/sombre', cat:'Actions', act:()=>{close();toggleTheme();} }, { id:'reindex', label:'🔄 Réindexer', desc:'Forcer la réindexation complète', cat:'Actions', act:async()=>{close();try{await api('/api/index/reload');showToast('Index rechargé','success');}catch{showToast(t('common.error'),'error');}} }, { id:'help', label:'❓ Aide', desc:'Ouvrir le guide', cat:'Système', act:()=>{close();const b=document.getElementById('help-open-btn');if(b)b.click();} }, { id:'config', label:'⚙️ Configuration', desc:'Ouvrir les paramètres', cat:'Système', act:()=>{close();const b=document.getElementById('config-open-btn');if(b)b.click();} }, ]; // ── Browse: vault picker ── function startBrowse(action) { _browse = { action, vault: null, path: '' }; _browseFilter = ''; _allItems = []; switchMode('browse-vault'); renderVaultPicker(); } async function renderVaultPicker() { try { const data = await api('/api/vaults'); const vaults = Array.isArray(data) ? data : []; _items = vaults.map(v => ({ type:'_vault', vault: v.name, label: (v.type==='DIR'?'📂':'🗂️')+' '+v.name, sublabel: v.file_count+' fichiers' })); if (_items.length === 0) { _items = [{ type:'_empty', label: 'Aucune vault trouvée', sublabel: '' }]; } _allItems = _items; render(); } catch { _items = [{ type:'_error', label: 'Erreur de chargement', sublabel: '' }]; _allItems = _items; render(); } } // ── Browse: directory navigation ── async function renderBrowse() { const { vault, path } = _browse; _items = []; const bc = document.getElementById('cp-breadcrumb'); if (bc) { bc.style.display = 'flex'; const segs = path ? path.split('/') : []; let html = `${esc(vault)}`; let acc = ''; for (const s of segs) { acc = acc ? acc+'/'+s : s; html += `/${esc(s)}`; } bc.innerHTML = html; bc.querySelectorAll('.cp-bc-item[data-path]').forEach(el => el.addEventListener('click', ()=>{ _browse.path=el.dataset.path; _browseFilter=''; if(_browseFilterEl)_browseFilterEl.value=''; renderBrowse(); })); bc.querySelector('.cp-bc-root').addEventListener('click', ()=>{ _browse.path=''; _browseFilter=''; if(_browseFilterEl)_browseFilterEl.value=''; renderBrowse(); }); } try { const data = await api(`/api/browse/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`); const items = data.items || []; const dirs = items.filter(i => i.type === 'directory' || i.is_dir); const files = items.filter(i => i.type === 'file' && !i.is_dir); // Action row const actLabel = { 'create-file':'📄 Créer fichier ici', 'create-dir':'📁 Créer dossier ici', 'delete-dir':'🗑️ Supprimer ce dossier', 'delete-file':'🗑️ Supprimer un fichier dans ce dossier', 'open-file':'📂 Ouvrir un fichier dans ce dossier' }; const isDeleteDir = _browse.action === 'delete-dir' && path; const isDeleteFile = _browse.action === 'delete-file'; const isOpenFile = _browse.action === 'open-file'; const needsName = _browse.action === 'create-file' || _browse.action === 'create-dir'; if (isDeleteDir) { _items.push({ type:'_action', label: '🗑️ Supprimer '+ (path.split('/').pop()||path), sublabel: 'Tout le contenu sera supprimé', act:()=>execDelDir(path) }); } else if (needsName) { _items.push({ type:'_action', label: actLabel[_browse.action]||'', sublabel: 'Dans '+(path||'racine'), act:()=>askName() }); } // Parent if (path) { _items.push({ type:'_nav', label: '📂 .. (parent)', go: path.split('/').slice(0,-1).join('/') }); } // Subdirs for (const d of dirs) { const fp = path ? path+'/'+d.name : d.name; _items.push({ type:'_nav', label: '📁 '+d.name, go: fp }); } // Files for (const f of files) { const fp = path ? path+'/'+f.name : f.name; if (isDeleteFile) { _items.push({ type:'_file', label: '📄 '+f.name, sublabel: f.vault+'/'+fp, act:()=>execDelFile(fp) }); } else if (isOpenFile) { _items.push({ type:'_file', label: '📄 '+f.name, sublabel: f.vault+'/'+fp, act:()=>{ close(); TabManager.openPersistent(vault, fp); } }); } else { _items.push({ type:'_info', label: '📄 '+f.name, sublabel: fp }); } } if (_items.length === 0) _items.push({ type:'_empty', label: '📂 Dossier vide', sublabel: '' }); } catch { _items = [{ type:'_error', label: 'Erreur', sublabel: '' }]; } _allItems = _items; applyBrowseFilter(); } // ── Browse filter: local when empty, global suggest when text ── function applyBrowseFilter() { const q = _browseFilter.trim(); if (!q) { // Empty filter: show local directory items _items = _allItems; render(); return; } // Has filter text: debounce and search globally if (_browseFilterTimer) clearTimeout(_browseFilterTimer); _browseFilterTimer = setTimeout(() => searchBrowseGlobal(q), 200); } async function searchBrowseGlobal(rawQ) { const { positive, negative } = parseQuery(rawQ); const action = _browse.action; const isDeleteFile = action === 'delete-file'; const isOpenFile = action === 'open-file'; const isDeleteDir = action === 'delete-dir'; const needsName = action === 'create-file' || action === 'create-dir'; try { const q = positive.join(' '); const vault = _browse.vault || 'all'; const d = await api(`/api/suggest?q=${encodeURIComponent(q)}&vault=${encodeURIComponent(vault)}`); const sug = (d.suggestions || []).filter(s => { const fullPath = (s.vault+'/'+s.path).toLowerCase(); // Client-side negative filter for (const n of negative) { if (fullPath.includes(n)) return false; } return true; }); if (sug.length === 0) { _items = [{ type:'_empty', label: 'Aucun résultat pour "'+rawQ+'"', sublabel: '' }]; render(); return; } _items = []; // Extract unique directories from result paths const dirSet = new Set(); for (const s of sug) { const parts = s.path.split('/'); // Build parent directories for (let i = 0; i < parts.length - 1; i++) { dirSet.add(s.vault + '|' + parts.slice(0, i + 1).join('/')); } } // Show directories as navigable const dirs = [...dirSet].map(d => { const [v, p] = d.split('|'); const name = p.split('/').pop(); return { type:'_nav', label:'📁 '+name, sublabel:v+'/'+p, go:p, vault:v }; }).sort((a,b) => a.sublabel.localeCompare(b.sublabel)); for (const d of dirs) _items.push(d); // Show files for (const s of sug) { const fp = s.path; const name = fp.split('/').pop(); if (isDeleteFile) { _items.push({ type:'_file', label:'📄 '+name, sublabel:s.vault+'/'+fp, act:()=>execDelFileBrowsed(s.vault, fp) }); } else if (isOpenFile) { _items.push({ type:'_file', label:'📄 '+name, sublabel:s.vault+'/'+fp, act:()=>{ close(); TabManager.openPersistent(s.vault, fp); } }); } else if (isDeleteDir) { _items.push({ type:'_file', label:'📁 '+name, sublabel:s.vault+'/'+fp, act:()=>execDelDirBrowsed(s.vault, fp) }); } else { _items.push({ type:'_file', label:'📄 '+name, sublabel:s.vault+'/'+fp, act:()=>{ close(); TabManager.openPersistent(s.vault, fp); } }); } } render(); } catch { _items = [{ type:'_error', label: 'Erreur de recherche', sublabel: '' }]; render(); } } // Navigate to a directory from global search function execNavGlobal(vault, goPath) { _browse.vault = vault; _browse.path = goPath; _browseFilter = ''; if (_browseFilterEl) _browseFilterEl.value = ''; renderBrowse(); } async function execDelFileBrowsed(vault, path) { if (!confirm(t("confirm.delete_file_named", {path: vault + "/" + path}))) return; try { const d = await api(`/api/file/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' }); if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); } else showToast(t('common.error'),'error'); } catch(e) { showToast('Erreur : '+(e.message||e),'error'); } } async function execDelDirBrowsed(vault, path) { if (!confirm(t("confirm.delete_dir_named", {path: path}))) return; try { const d = await api(`/api/directory/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' }); if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); } else showToast(t('common.error'),'error'); } catch(e) { showToast('Erreur : '+(e.message||e),'error'); } } function onBrowseFilterInput() { _browseFilter = _browseFilterEl ? _browseFilterEl.value : ''; applyBrowseFilter(); } function onBrowseFilterKey(e) { if (e.key === 'Escape') { e.preventDefault(); if (_browseFilter) { _browseFilter = ''; if (_browseFilterEl) _browseFilterEl.value = ''; applyBrowseFilter(); } else { close(); } } else if (e.key === 'ArrowDown') { e.preventDefault(); _sel = 0; updSel(_results.querySelectorAll('.cp-item')); _overlay.focus(); } else if (e.key === 'ArrowUp') { e.preventDefault(); const items = _results.querySelectorAll('.cp-item'); _sel = Math.max(items.length - 1, 0); updSel(items); _overlay.focus(); } else if (e.key === 'Enter') { e.preventDefault(); if (_items.length > 0) exec(_sel); } else if (e.key === 'Backspace' && !_browseFilter && (!_browseFilterEl || !_browseFilterEl.value)) { e.preventDefault(); const segs = _browse.path ? _browse.path.split('/') : []; if (segs.length > 0) { _browse.path = segs.slice(0, -1).join('/'); renderBrowse(); } else if (_browse.vault) { switchMode('browse-vault'); renderVaultPicker(); } } } // ── Name input modal ── function askName() { const label = _browse.action === 'create-file' ? 'Nom du fichier' : 'Nom du dossier'; const ext = _browse.action === 'create-file' ? '(.md ajouté si besoin)' : ''; const existingInput = document.getElementById('cp-name-input'); if (existingInput) { existingInput.focus(); return; } _input.style.display = 'none'; const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'none'; const nameDiv = document.createElement('div'); nameDiv.id = 'cp-name-input'; nameDiv.className = 'cp-name-input'; nameDiv.innerHTML = ` Enter confirmer · Esc annuler`; _results.before(nameDiv); const field = document.getElementById('cp-name-field'); field.focus(); field.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); const name = field.value.trim(); if (!name) return; cleanupNameInput(); if (_browse.action === 'create-file') execCreateFile(name); else execCreateDir(name); } else if (e.key === 'Escape') { e.preventDefault(); cleanupNameInput(); _overlay.focus(); renderBrowse(); } }); } function cleanupNameInput() { const el = document.getElementById('cp-name-input'); if (el) el.remove(); const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'flex'; } // ── Execute actions ── async function execCreateFile(name) { let fp = _browse.path ? _browse.path+'/'+name : name; if (!fp.includes('.')) fp += '.md'; try { const d = await api(`/api/file/${encodeURIComponent(_browse.vault)}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({path:fp, content:`# ${name.replace('.md','')}\n\n`}) }); if (d?.status==='ok' || d?.success) { showToast(`✅ ${fp} créé`,'success'); close(); openEditor(_browse.vault, fp); } else showToast(t('toast.create_error'),'error'); } catch(e) { showToast('Erreur : '+(e.message||e),'error'); } } async function execCreateDir(name) { const dp = _browse.path ? _browse.path+'/'+name : name; try { const d = await api(`/api/directory/${encodeURIComponent(_browse.vault)}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({path:dp}) }); if (d?.status==='ok' || d?.success) { showToast(`📁 ${dp} créé`,'success'); close(); refreshSidebar(); } else showToast(t('common.error'),'error'); } catch(e) { showToast('Erreur : '+(e.message||e),'error'); } } async function execDelDir(path) { if (!confirm(t("confirm.delete_dir_named", {path: path}))) return; try { const d = await api(`/api/directory/${encodeURIComponent(_browse.vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' }); if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); } else showToast(t('common.error'),'error'); } catch(e) { showToast('Erreur : '+(e.message||e),'error'); } } async function execDelFile(path) { if (!confirm(t("confirm.delete_file_named", {path: path}))) return; try { const d = await api(`/api/file/${encodeURIComponent(_browse.vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' }); if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); } else showToast(t('common.error'),'error'); } catch(e) { showToast('Erreur : '+(e.message||e),'error'); } } async function delCurFile() { const f = getCurrentFile(); if (!f) { showToast(t('toast.no_open_file'),'error'); return; } if (!confirm(t("confirm.delete_file_named", {path: f.path}))) return; try { const d = await api(`/api/file/${encodeURIComponent(f.vault)}?path=${encodeURIComponent(f.path)}`, { method:'DELETE' }); if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${f.path} supprimé`,'success'); const tab = TabManager._tabs.find(t=>t.vault===f.vault&&t.path===f.path); if(tab) TabManager.close(tab.id); triggerGoHome(); refreshSidebar(); } else showToast(t('common.error'),'error'); } catch(e) { showToast('Erreur : '+(e.message||e),'error'); } } function refreshSidebar() { import('./sidebar.js').then(m => { if (m.loadVaults) m.loadVaults(); }).catch(() => {}); } // ── DOM ── function createDOM() { if (_overlay) return; _overlay = document.createElement('div'); _overlay.className = 'command-palette-overlay'; _overlay.tabIndex = -1; _overlay.innerHTML = `
Ctrl+Shift+Espace
`; _input = _overlay.querySelector('.cp-input'); _browseFilterEl = _overlay.querySelector('#cp-browse-filter'); _results = _overlay.querySelector('.cp-results'); _overlay.addEventListener('click', (e) => { if (e.target === _overlay) close(); }); _input.addEventListener('keydown', onKeyDown); _input.addEventListener('input', onInput); if (_browseFilterEl) { _browseFilterEl.addEventListener('input', onBrowseFilterInput); _browseFilterEl.addEventListener('keydown', onBrowseFilterKey); } _overlay.addEventListener('keydown', onOverlayKeyDown); document.body.appendChild(_overlay); } export function open(q = '') { createDOM(); _overlay.classList.add('active'); _input.value = q; _mode = q.startsWith('>') ? 'commands' : 'files'; _input.disabled = false; _input.style.display = ''; _sel = 0; _items = []; _results.innerHTML = ''; _input.focus(); onInput(); } export function close() { if (!_overlay) return; _overlay.classList.remove('active'); if (_to) clearTimeout(_to); _to = null; if (_browseFilterTimer) clearTimeout(_browseFilterTimer); _mode = 'files'; _browseFilter = ''; _allItems = []; cleanupNameInput(); if (_browseFilterEl) { _browseFilterEl.style.display = 'none'; _browseFilterEl.value = ''; } const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'none'; const si = document.querySelector('.search-input'); if (si) si.focus(); } function isOpen() { return _overlay && _overlay.classList.contains('active'); } function switchMode(m) { _mode = m; if (m === 'browse' || m === 'browse-vault') { _input.style.display = 'none'; _input.disabled = true; _overlay.focus(); if (m === 'browse' && _browseFilterEl) { _browseFilterEl.style.display = ''; _browseFilterEl.value = _browseFilter; _browseFilterEl.focus(); } else if (_browseFilterEl) { _browseFilterEl.style.display = 'none'; } } else { _input.style.display = ''; _input.disabled = false; _input.focus(); if (_browseFilterEl) _browseFilterEl.style.display = 'none'; const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'none'; } } function onInput() { if (_mode === 'browse' || _mode === 'browse-vault') return; const v = _input.value; const nm = v.startsWith('>') ? 'commands' : 'files'; if (nm !== _mode) { _mode = nm; _input.placeholder = _mode==='commands'?'Tapez une commande...':'Rechercher un fichier... (+inclure -exclure)'; } if (_to) clearTimeout(_to); _to = setTimeout(() => { _to = null; if (_mode==='commands') searchCmd(v.slice(1).trim()); else searchFiles(v.trim()); }, 150); } async function searchFiles(rawQ) { if (!rawQ) { _items = [{ type:'_browse', label:'📂 Parcourir les répertoires...', sublabel:'Naviguer dans les vaults pour trouver un fichier', act:()=>startBrowse('open-file') }]; render(); return; } const { positive, negative } = parseQuery(rawQ); try { const q = positive.join(' '); const d = await api(`/api/suggest?q=${encodeURIComponent(q)}&vault=all`); let sug = d.suggestions || []; // Client-side negative filter if (negative.length > 0) { sug = sug.filter(s => { const fullPath = (s.vault+'/'+s.path).toLowerCase(); for (const n of negative) { if (fullPath.includes(n)) return false; } return true; }); } if (sug.length === 0) { _items = [{ type:'_browse', label:'📂 Parcourir les répertoires...', sublabel:'Aucun résultat — naviguer manuellement', act:()=>startBrowse('open-file') }]; } else { _items = sug.map(s => ({ type:'file', vault:s.vault, path:s.path, label:s.title||s.path.split('/').pop(), sublabel:`${s.vault}/${s.path}` })); } render(); } catch { _results.innerHTML = '
Erreur
'; } } function searchCmd(q) { if (!q) { _items = CMDS.map(c => ({ type:'cmd', ...c })); render(); return; } const lq = q.toLowerCase(); _items = CMDS.filter(c => c.label.toLowerCase().includes(lq)||c.desc.toLowerCase().includes(lq)||(c.cat&&c.cat.toLowerCase().includes(lq))).map(c => ({ type:'cmd', ...c })); render(); } function render() { _sel = 0; if (!_items.length) { _results.innerHTML = '
Aucun résultat
'; return; } _results.innerHTML = _items.map((it, i) => `
${esc(it.label)}
${esc(it.sublabel||'')}
`).join(''); _results.querySelectorAll('.cp-item').forEach(el => { el.addEventListener('click', () => exec(parseInt(el.dataset.idx))); el.addEventListener('mouseenter', () => { document.querySelectorAll('.cp-item').forEach(e=>e.classList.remove('cp-selected')); el.classList.add('cp-selected'); _sel = parseInt(el.dataset.idx); }); }); scroll(); } function onKeyDown(e) { const items = _results.querySelectorAll('.cp-item'); if (_mode === 'browse' || _mode === 'browse-vault') { if (e.key === 'Escape') { e.preventDefault(); close(); } else if (e.key === 'Backspace') { e.preventDefault(); if (_mode === 'browse-vault') { _mode='commands'; _input.style.display=''; _input.disabled=false; _input.focus(); searchCmd(''); return; } const segs = _browse.path ? _browse.path.split('/') : []; if (segs.length > 0) { _browse.path = segs.slice(0,-1).join('/'); renderBrowse(); } else if (_browse.vault) { switchMode('browse-vault'); renderVaultPicker(); } } else if (e.key === 'Enter') { e.preventDefault(); exec(_sel); } else if (e.key === 'ArrowDown') { e.preventDefault(); _sel = Math.min(_sel+1, _items.length-1); updSel(items); } else if (e.key === 'ArrowUp') { e.preventDefault(); _sel = Math.max(_sel-1, 0); updSel(items); } return; } if (e.key === 'Escape') { e.preventDefault(); close(); } else if (e.key === 'Enter') { e.preventDefault(); exec(_sel); } else if (e.key === 'ArrowDown') { e.preventDefault(); _sel = Math.min(_sel+1, _items.length-1); updSel(items); } else if (e.key === 'ArrowUp') { e.preventDefault(); _sel = Math.max(_sel-1, 0); updSel(items); } else if (e.key === 'Tab') { e.preventDefault(); if (_mode==='files') { _input.value='> '; setTimeout(()=>_input.setSelectionRange(2,2),0); } else _input.value=''; onInput(); } } function updSel(items) { items.forEach((el,i)=>el.classList.toggle('cp-selected',i===_sel)); scroll(); } function scroll() { const s = _results.querySelector('.cp-selected'); if (s) s.scrollIntoView({block:'nearest'}); } // ── Overlay keydown handler (for browse modes where input is hidden) ── function onOverlayKeyDown(e) { if (_mode !== 'browse' && _mode !== 'browse-vault') return; if (document.getElementById('cp-name-input')) return; if (document.activeElement === _browseFilterEl) return; onKeyDown(e); } function exec(idx) { const it = _items[idx]; if (!it) return; if (it.type === 'file') { close(); if (window.innerWidth <= 768) { TabManager.openPersistent(it.vault, it.path); } else { TabManager.openPreview(it.vault, it.path); } } else if (it.type === 'cmd' || it.type === '_browse') { it.act(); } else if (it.type === '_vault') { _browse.vault = it.vault; switchMode('browse'); renderBrowse(); } else if (it.type === '_nav') { // Check if this is a global search result with vault info if (it.vault && it.vault !== _browse.vault) { _browse.vault = it.vault; } _browse.path = it.go; _browseFilter=''; if(_browseFilterEl) _browseFilterEl.value=''; renderBrowse(); } else if (it.type === '_action' || it.type === '_file') { it.act(); } } function esc(s) { if (!s) return ''; return String(s).replace(/&/g,'&').replace(//g,'>').replace(/\"/g,'"'); } export function initCommandPalette() { document.addEventListener('keydown', (e) => { if ((e.ctrlKey||e.metaKey) && e.shiftKey && !e.altKey && (e.key===' '||e.code==='Space')) { e.preventDefault(); if (isOpen()) close(); else open(''); } if ((e.ctrlKey||e.metaKey) && e.altKey && !e.shiftKey && (e.key===' '||e.code==='Space')) { e.preventDefault(); if (isOpen()) close(); else open('> '); } }); }