Files
PackControl/pirp/public/assets/command-palette.js

203 lines
6.2 KiB
JavaScript

// PIRP Command Palette (Ctrl+K / Cmd+K)
(function() {
'use strict';
var overlay = null;
var input = null;
var resultsList = null;
var debounceTimer = null;
var activeIndex = -1;
var currentResults = [];
var typeLabels = {
invoice: 'Rechnungen',
customer: 'Kunden',
expense: 'Ausgaben',
journal: 'Journal'
};
var typeIcons = {
invoice: '\u25B8',
customer: '\u25CF',
expense: '\u25A0',
journal: '\u25C6'
};
function create() {
overlay = document.createElement('div');
overlay.className = 'cmd-palette-overlay hidden';
overlay.addEventListener('click', function(e) {
if (e.target === overlay) close();
});
var modal = document.createElement('div');
modal.className = 'cmd-palette';
input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Suche nach Rechnungen, Kunden, Ausgaben, Journal...';
input.addEventListener('input', function() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(doSearch, 200);
});
input.addEventListener('keydown', handleKeydown);
resultsList = document.createElement('div');
resultsList.className = 'cmd-palette-results';
modal.appendChild(input);
modal.appendChild(resultsList);
overlay.appendChild(modal);
document.body.appendChild(overlay);
}
function open() {
if (!overlay) create();
overlay.classList.remove('hidden');
input.value = '';
resultsList.innerHTML = '';
activeIndex = -1;
currentResults = [];
setTimeout(function() { input.focus(); }, 10);
}
function close() {
if (overlay) overlay.classList.add('hidden');
}
function isOpen() {
return overlay && !overlay.classList.contains('hidden');
}
function doSearch() {
var q = input.value.trim();
if (q.length < 2) {
resultsList.innerHTML = '';
activeIndex = -1;
currentResults = [];
return;
}
fetch('search_api.php?q=' + encodeURIComponent(q))
.then(function(r) { return r.json(); })
.then(function(data) {
if (!data.ok) return;
currentResults = data.results;
activeIndex = -1;
renderResults(data.results);
})
.catch(function() {});
}
function renderResults(results) {
resultsList.innerHTML = '';
if (!results.length) {
var empty = document.createElement('div');
empty.className = 'cmd-palette-empty';
empty.textContent = 'Keine Ergebnisse';
resultsList.appendChild(empty);
return;
}
// Group by type
var groups = {};
var order = [];
results.forEach(function(r) {
if (!groups[r.type]) {
groups[r.type] = [];
order.push(r.type);
}
groups[r.type].push(r);
});
var idx = 0;
order.forEach(function(type) {
var header = document.createElement('div');
header.className = 'cmd-palette-group';
header.textContent = typeLabels[type] || type;
resultsList.appendChild(header);
groups[type].forEach(function(r) {
var item = document.createElement('a');
item.className = 'cmd-palette-item';
item.href = r.url;
item.setAttribute('data-idx', idx);
var icon = document.createElement('span');
icon.className = 'cmd-palette-icon';
icon.textContent = typeIcons[r.type] || '\u25B8';
item.appendChild(icon);
var text = document.createElement('span');
text.className = 'cmd-palette-text';
var title = document.createElement('span');
title.className = 'cmd-palette-title';
title.textContent = r.title;
text.appendChild(title);
if (r.subtitle) {
var sub = document.createElement('span');
sub.className = 'cmd-palette-subtitle';
sub.textContent = r.subtitle;
text.appendChild(sub);
}
item.appendChild(text);
item.addEventListener('mouseenter', function() {
setActive(parseInt(item.getAttribute('data-idx')));
});
resultsList.appendChild(item);
idx++;
});
});
}
function setActive(idx) {
var items = resultsList.querySelectorAll('.cmd-palette-item');
items.forEach(function(el) { el.classList.remove('active'); });
activeIndex = idx;
if (idx >= 0 && idx < items.length) {
items[idx].classList.add('active');
items[idx].scrollIntoView({ block: 'nearest' });
}
}
function handleKeydown(e) {
var items = resultsList.querySelectorAll('.cmd-palette-item');
var count = items.length;
if (e.key === 'ArrowDown') {
e.preventDefault();
setActive(activeIndex < count - 1 ? activeIndex + 1 : 0);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActive(activeIndex > 0 ? activeIndex - 1 : count - 1);
} else if (e.key === 'Enter') {
e.preventDefault();
if (activeIndex >= 0 && activeIndex < count) {
window.location = items[activeIndex].href;
}
} else if (e.key === 'Escape') {
e.preventDefault();
close();
}
}
// Global keyboard listener
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
if (isOpen()) {
close();
} else {
open();
}
}
if (e.key === 'Escape' && isOpen()) {
e.preventDefault();
close();
}
});
})();