/** * Kanbante — Command palette (Ctrl+K), roadmap #10. * * One overlay for the whole app: static actions, every project, and * global issue search (via /api/palette). Fully keyboard driven. */ (function () { var overlay = document.getElementById('palette-overlay'); if (!overlay) return; var input = document.getElementById('palette-input'); var list = document.getElementById('palette-results'); var ACTIONS = [ { icon: '🏠', label: 'Projets — dashboard', href: '/' }, { icon: '🎯', label: 'Focus — en cours, QA, révision', href: '/focus' }, { icon: '🌅', label: "Focus du jour — les 3 cartes d'aujourd'hui", href: '/today' }, { icon: '🔮', label: 'Prévisions — on finit quand ?', href: '/forecast' }, { icon: '🗣️', label: 'Standup automatique — hier, aujourd\'hui, blocages', href: '/standup' }, { icon: '⚙️', label: 'Règles d\'automatisation', href: '/rules' }, { icon: '🩺', label: 'Santé du flux — goulots et zombies', href: '/health' }, { icon: '👤', label: 'Mes issues', href: '/my' }, { icon: '📰', label: 'Activité — journal des événements', href: '/activity' }, { icon: '📊', label: 'Briefing quotidien', href: '/briefing' }, { icon: '⚡', label: 'Capture éclair — nouvelle idée', href: '/capture' }, { icon: '📥', label: 'Mode Triage — vider un backlog au clavier', href: '/triage' }, { icon: '🧠', label: 'Digest hebdo — vers ObsiGate', href: '/digest' }, { icon: '🖥️', label: 'Wallboard — mode TV', href: '/wall' }, { icon: '🔍', label: 'Recherche avancée', href: '/search' }, ]; var projects = null; // cached after first fetch var issues = []; // latest search results for the query var items = []; // rendered items var activeIndex = 0; var debounce = null; var lastQuery = ''; function open() { overlay.hidden = false; document.body.style.overflow = 'hidden'; input.value = ''; issues = []; lastQuery = ''; render(''); input.focus(); if (projects === null) { fetch('/api/palette') .then(function (r) { return r.json(); }) .then(function (data) { projects = data.projects || []; render(input.value.trim()); }) .catch(function () { projects = []; }); } } function close() { overlay.hidden = true; document.body.style.overflow = ''; } function buildItems(query) { var q = query.toLowerCase(); var out = []; ACTIONS.forEach(function (a) { if (!q || a.label.toLowerCase().includes(q)) { out.push({ icon: a.icon, label: a.label, sub: 'Action', href: a.href }); } }); (projects || []).forEach(function (p) { if (out.length >= 14) return; if (!q || p.full_name.toLowerCase().includes(q)) { out.push({ icon: '📂', label: p.full_name, sub: 'Projet · ouvrir le board', href: '/board/' + p.full_name, }); } }); issues.forEach(function (i) { out.push({ icon: '🐛', label: '#' + i.number + ' · ' + i.title, sub: i.repo + ' · ' + i.column_label + (i.state === 'closed' ? ' · fermée' : ''), href: '/board/' + i.repo, }); }); return out.slice(0, 14); } function render(query) { items = buildItems(query); activeIndex = Math.min(activeIndex, Math.max(0, items.length - 1)); list.innerHTML = ''; if (!items.length) { var empty = document.createElement('li'); empty.className = 'palette-empty'; empty.textContent = 'Aucun résultat — essaie un nom de projet ou un mot du titre d\'une issue.'; list.appendChild(empty); return; } items.forEach(function (item, idx) { var li = document.createElement('li'); li.className = 'palette-item' + (idx === activeIndex ? ' active' : ''); var icon = document.createElement('span'); icon.className = 'palette-item-icon'; icon.textContent = item.icon; var main = document.createElement('span'); main.className = 'palette-item-main'; main.textContent = item.label; var sub = document.createElement('span'); sub.className = 'palette-item-sub'; sub.textContent = item.sub; li.appendChild(icon); li.appendChild(main); li.appendChild(sub); li.addEventListener('click', function () { go(item); }); li.addEventListener('mousemove', function () { if (activeIndex !== idx) { activeIndex = idx; render(input.value.trim()); } }); list.appendChild(li); }); } function go(item) { if (item) window.location.href = item.href; } function searchIssues(query) { if (query.length < 2) { issues = []; render(query); return; } fetch('/api/palette?q=' + encodeURIComponent(query)) .then(function (r) { return r.json(); }) .then(function (data) { if (input.value.trim() !== query) return; // stale response issues = data.issues || []; if (projects === null) projects = data.projects || []; render(query); }) .catch(function () { /* keep previous results */ }); } input.addEventListener('input', function () { var query = input.value.trim(); activeIndex = 0; render(query); if (query !== lastQuery) { lastQuery = query; clearTimeout(debounce); debounce = setTimeout(function () { searchIssues(query); }, 180); } }); input.addEventListener('keydown', function (e) { if (e.key === 'ArrowDown') { e.preventDefault(); activeIndex = Math.min(activeIndex + 1, items.length - 1); render(input.value.trim()); } else if (e.key === 'ArrowUp') { e.preventDefault(); activeIndex = Math.max(activeIndex - 1, 0); render(input.value.trim()); } else if (e.key === 'Enter') { e.preventDefault(); go(items[activeIndex]); } }); overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); }); document.addEventListener('keydown', function (e) { var combo = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k'; if (combo) { e.preventDefault(); if (overlay.hidden) open(); else close(); } else if (e.key === 'Escape' && !overlay.hidden) { close(); } }); document.querySelectorAll('.palette-trigger').forEach(function (btn) { btn.addEventListener('click', function () { open(); }); }); })();