Add command palette (Ctrl+K / Cmd+K) globally
Adds lt-cmd-overlay HTML to layout_header.php and initializes lt.cmdPalette with commands for: navigation (Dashboard, New Ticket), filters (My Tickets, Unassigned, P1 Critical), admin pages (if admin), and recent tickets (last 5 viewed, stored in localStorage). TicketView.php records each viewed ticket ID to localStorage under lt_recent_tickets so the command palette can surface them as Recent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,16 @@ window.ticketData = {
|
|||||||
};
|
};
|
||||||
window.ticketData.id = window.ticketData.ticket_id;
|
window.ticketData.id = window.ticketData.ticket_id;
|
||||||
if (window.lt) lt.keys.initDefaults();
|
if (window.lt) lt.keys.initDefaults();
|
||||||
|
// Track recently viewed tickets for command palette
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
var tid = String(window.ticketData.ticket_id);
|
||||||
|
var key = 'lt_recent_tickets';
|
||||||
|
var r = JSON.parse(localStorage.getItem(key) || '[]');
|
||||||
|
r = [tid].concat(r.filter(function(x){ return x !== tid; })).slice(0, 5);
|
||||||
|
localStorage.setItem(key, JSON.stringify(r));
|
||||||
|
} catch(_) {}
|
||||||
|
})();
|
||||||
JS;
|
JS;
|
||||||
|
|
||||||
include __DIR__ . '/layout_header.php';
|
include __DIR__ . '/layout_header.php';
|
||||||
|
|||||||
@@ -206,4 +206,60 @@ $_lt_assetVer = $GLOBALS['config']['ASSET_VERSION'] ?? '20260329';
|
|||||||
|
|
||||||
</header><!-- /.lt-header -->
|
</header><!-- /.lt-header -->
|
||||||
|
|
||||||
|
<!-- ── COMMAND PALETTE OVERLAY (Ctrl+K / ⌘K) ──────────────────── -->
|
||||||
|
<div id="lt-cmd-overlay" class="lt-cmd-overlay" role="dialog" aria-modal="true" aria-label="Command palette" aria-hidden="true">
|
||||||
|
<div id="lt-cmd-palette" class="lt-cmd-palette" role="combobox" aria-expanded="true" aria-haspopup="listbox">
|
||||||
|
<div class="lt-cmd-input-wrap">
|
||||||
|
<span aria-hidden="true" style="opacity:0.45;margin-right:0.4rem;font-size:0.9em">⌕</span>
|
||||||
|
<input class="lt-cmd-input" type="text" placeholder="Type a command or search…"
|
||||||
|
autocomplete="off" spellcheck="false" aria-label="Command search" aria-autocomplete="list"
|
||||||
|
aria-controls="lt-cmd-results-list">
|
||||||
|
<kbd style="font-size:0.6rem;opacity:0.4;white-space:nowrap">ESC</kbd>
|
||||||
|
</div>
|
||||||
|
<div class="lt-cmd-results" id="lt-cmd-results-list" role="listbox"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
(function() {
|
||||||
|
var isAdmin = <?= json_encode($_lt_isAdmin) ?>;
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var commands = [
|
||||||
|
{ id: 'nav-dashboard', label: 'Dashboard', icon: '⌂', group: 'Navigate', action: function(){ location.href = '/'; } },
|
||||||
|
{ id: 'nav-new-ticket', label: 'New Ticket', icon: '+', group: 'Navigate', kbd: 'N', action: function(){ location.href = '/create'; } },
|
||||||
|
{ id: 'filter-mine', label: 'My Open Tickets', icon: '◈', group: 'Filter', action: function(){ location.href = '/?assigned_to=me&status=Open,In+Progress,Pending'; } },
|
||||||
|
{ id: 'filter-unassigned', label: 'Unassigned Tickets', icon: '◌', group: 'Filter', action: function(){ location.href = '/?assigned_to=none'; } },
|
||||||
|
{ id: 'filter-critical', label: 'P1 Critical Tickets', icon: '!', group: 'Filter', action: function(){ location.href = '/?priority=1'; } },
|
||||||
|
];
|
||||||
|
if (isAdmin) {
|
||||||
|
[
|
||||||
|
{ id: 'admin-templates', label: 'Admin: Templates', icon: '▤', href: '/admin/templates' },
|
||||||
|
{ id: 'admin-workflow', label: 'Admin: Workflow', icon: '⇌', href: '/admin/workflow' },
|
||||||
|
{ id: 'admin-audit', label: 'Admin: Audit Log', icon: '📋', href: '/admin/audit-log' },
|
||||||
|
{ id: 'admin-api-keys', label: 'Admin: API Keys', icon: '🔑', href: '/admin/api-keys' },
|
||||||
|
{ id: 'admin-users', label: 'Admin: User Activity', icon: '👤', href: '/admin/user-activity' },
|
||||||
|
{ id: 'admin-recurring', label: 'Admin: Recurring', icon: '↻', href: '/admin/recurring-tickets' },
|
||||||
|
{ id: 'admin-fields', label: 'Admin: Custom Fields', icon: '⊞', href: '/admin/custom-fields' },
|
||||||
|
].forEach(function(c) {
|
||||||
|
commands.push({ id: c.id, label: c.label, icon: c.icon, group: 'Admin', action: function(href){ return function(){ location.href = href; }; }(c.href) });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Inject recent ticket IDs from localStorage
|
||||||
|
try {
|
||||||
|
var recent = JSON.parse(localStorage.getItem('lt_recent_tickets') || '[]');
|
||||||
|
recent.slice(0, 5).forEach(function(id) {
|
||||||
|
commands.push({ id: 'recent-' + id, label: 'Ticket #' + id, icon: '◷', group: 'Recent', tags: ['ticket'], action: function(tid){ return function(){ location.href = '/ticket/' + tid; }; }(id) });
|
||||||
|
});
|
||||||
|
} catch(_) {}
|
||||||
|
if (window.lt && lt.cmdPalette) lt.cmdPalette.init(commands);
|
||||||
|
});
|
||||||
|
// Keyboard shortcut: Ctrl+K / Cmd+K
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (window.lt && lt.cmdPalette) lt.cmdPalette.open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<main class="lt-main lt-container" id="main-content" style="padding-top: calc(var(--header-height, 56px) + var(--space-lg, 1.5rem))">
|
<main class="lt-main lt-container" id="main-content" style="padding-top: calc(var(--header-height, 56px) + var(--space-lg, 1.5rem))">
|
||||||
|
|||||||
Reference in New Issue
Block a user