/** * Keyboard shortcuts for power users */ document.addEventListener('DOMContentLoaded', function() { document.addEventListener('keydown', function(e) { // ESC: Close modals, cancel edit mode, blur inputs if (e.key === 'Escape') { // Close any open modals first const openModals = document.querySelectorAll('.modal-overlay'); let closedModal = false; openModals.forEach(modal => { if (modal.style.display !== 'none' && modal.offsetParent !== null) { modal.remove(); document.body.classList.remove('modal-open'); closedModal = true; } }); // Close settings modal if open const settingsModal = document.getElementById('settingsModal'); if (settingsModal && settingsModal.style.display !== 'none') { settingsModal.style.display = 'none'; document.body.classList.remove('modal-open'); closedModal = true; } // Close advanced search modal if open const searchModal = document.getElementById('advancedSearchModal'); if (searchModal && searchModal.style.display !== 'none') { searchModal.style.display = 'none'; document.body.classList.remove('modal-open'); closedModal = true; } // If we closed a modal, stop here if (closedModal) { e.preventDefault(); return; } // Blur any focused input if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) { e.target.blur(); } // Cancel edit mode on ticket pages const editButton = document.getElementById('editButton'); if (editButton && editButton.classList.contains('active')) { window.location.reload(); } return; } // Skip other shortcuts if user is typing in an input/textarea if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) { return; } // Ctrl/Cmd + E: Toggle edit mode (on ticket pages) if ((e.ctrlKey || e.metaKey) && e.key === 'e') { e.preventDefault(); const editButton = document.getElementById('editButton'); if (editButton) { editButton.click(); toast.info('Edit mode ' + (editButton.classList.contains('active') ? 'enabled' : 'disabled')); } } // Ctrl/Cmd + S: Save ticket (on ticket pages) if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); const editButton = document.getElementById('editButton'); if (editButton && editButton.classList.contains('active')) { editButton.click(); toast.success('Saving ticket...'); } } // Ctrl/Cmd + K: Focus search (on dashboard) if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); const searchBox = document.querySelector('.search-box'); if (searchBox) { searchBox.focus(); searchBox.select(); } } // ? : Show keyboard shortcuts help (requires Shift on most keyboards) if (e.key === '?') { e.preventDefault(); showKeyboardHelp(); } // J: Move to next row in table (Gmail-style) if (e.key === 'j') { e.preventDefault(); navigateTableRow('next'); } // K: Move to previous row in table (Gmail-style) if (e.key === 'k') { e.preventDefault(); navigateTableRow('prev'); } // Enter: Open selected ticket if (e.key === 'Enter') { const selectedRow = document.querySelector('tbody tr.keyboard-selected'); if (selectedRow) { e.preventDefault(); const ticketLink = selectedRow.querySelector('a[href*="/ticket/"]'); if (ticketLink) { window.location.href = ticketLink.href; } } } // N: Create new ticket (on dashboard) if (e.key === 'n') { e.preventDefault(); const newTicketBtn = document.querySelector('a[href*="/create"]'); if (newTicketBtn) { window.location.href = newTicketBtn.href; } } // C: Focus comment textarea (on ticket page) if (e.key === 'c') { const commentBox = document.getElementById('newComment'); if (commentBox) { e.preventDefault(); commentBox.focus(); commentBox.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } // G then D: Go to Dashboard (vim-style) if (e.key === 'g') { window._pendingG = true; setTimeout(() => { window._pendingG = false; }, 1000); } if (e.key === 'd' && window._pendingG) { e.preventDefault(); window._pendingG = false; window.location.href = '/'; } // 1-4: Quick status change on ticket page if (['1', '2', '3', '4'].includes(e.key)) { const statusSelect = document.getElementById('statusSelect'); if (statusSelect && !document.querySelector('.modal-overlay')) { const statusMap = { '1': 'Open', '2': 'Pending', '3': 'In Progress', '4': 'Closed' }; const targetStatus = statusMap[e.key]; const option = Array.from(statusSelect.options).find(opt => opt.value === targetStatus); if (option && !option.disabled) { e.preventDefault(); statusSelect.value = targetStatus; statusSelect.dispatchEvent(new Event('change')); } } } }); }); // Track currently selected row for J/K navigation let currentSelectedRowIndex = -1; function navigateTableRow(direction) { const rows = document.querySelectorAll('tbody tr'); if (rows.length === 0) return; // Remove current selection rows.forEach(row => row.classList.remove('keyboard-selected')); if (direction === 'next') { currentSelectedRowIndex = Math.min(currentSelectedRowIndex + 1, rows.length - 1); } else { currentSelectedRowIndex = Math.max(currentSelectedRowIndex - 1, 0); } // Add selection to new row const selectedRow = rows[currentSelectedRowIndex]; if (selectedRow) { selectedRow.classList.add('keyboard-selected'); selectedRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } } function showKeyboardHelp() { // Check if help is already showing if (document.getElementById('keyboardHelpModal')) { return; } const modal = document.createElement('div'); modal.id = 'keyboardHelpModal'; modal.className = 'modal-overlay'; modal.innerHTML = `
`; document.body.appendChild(modal); // Add event listener for the close button modal.querySelector('[data-action="close-shortcuts-modal"]').addEventListener('click', function() { modal.remove(); }); }