203 lines
6.2 KiB
JavaScript
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();
|
|
}
|
|
});
|
|
})();
|