/** * DashCaddy Keyboard Shortcuts System * Provides global keyboard shortcuts for improved navigation */ (function() { 'use strict'; // All modal selectors that can be closed with Escape const MODAL_SELECTORS = [ '#app-selector-modal', '#app-deploy-modal', '#weather-modal', '#token-management-modal', '#service-edit-modal', '#notifications-modal', '#backup-modal', '#stats-modal', '#arr-setup-modal', '#add-service-modal', '#error-log-modal', '#logs-modal', '#dns-template-modal' ]; // Quick search state let quickSearchModal = null; let quickSearchInput = null; let quickSearchResults = null; /** * Initialize the keyboard shortcuts system */ function init() { try { // Create quick search modal createQuickSearchModal(); // Add global keyboard listener document.addEventListener('keydown', handleKeyDown); console.log('[Keyboard Shortcuts] Initialized'); console.log('[Keyboard Shortcuts] Press Ctrl+K to open quick search'); console.log('[Keyboard Shortcuts] Press Escape to close modals'); } catch (e) { console.warn('[Keyboard Shortcuts] Failed to initialize:', e.message); } } /** * Create the quick search modal */ function createQuickSearchModal() { quickSearchModal = document.createElement('div'); quickSearchModal.id = 'quick-search-modal'; quickSearchModal.className = 'quick-search-modal'; quickSearchModal.innerHTML = `
🔍 ESC
`; // Add styles const style = document.createElement('style'); style.textContent = ` .quick-search-modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); z-index: 100000; justify-content: center; align-items: flex-start; padding-top: 15vh; backdrop-filter: blur(4px); } .quick-search-modal.show { display: flex; } .quick-search-content { background: var(--card-base, #1a1a2e); border: 1px solid var(--border, #333); border-radius: 12px; width: 90%; max-width: 600px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); overflow: hidden; } .quick-search-input-wrapper { display: flex; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border, #333); gap: 12px; } .quick-search-icon { font-size: 20px; opacity: 0.6; } #quick-search-input { flex: 1; background: transparent; border: none; outline: none; color: var(--fg, #fff); font-size: 18px; font-family: inherit; } #quick-search-input::placeholder { color: var(--muted, #666); } .quick-search-shortcut { background: var(--card-hover, #2a2a4e); padding: 4px 8px; border-radius: 4px; font-size: 12px; color: var(--muted, #666); font-family: monospace; } .quick-search-results { max-height: 400px; overflow-y: auto; } .quick-search-category { padding: 8px 20px; font-size: 11px; font-weight: 600; text-transform: uppercase; color: var(--muted, #666); background: var(--card-hover, #2a2a4e); letter-spacing: 0.5px; } .quick-search-item { display: flex; align-items: center; padding: 12px 20px; cursor: pointer; transition: background 0.15s; gap: 12px; } .quick-search-item:hover, .quick-search-item.selected { background: var(--accent, #3498db); } .quick-search-item-icon { font-size: 24px; width: 32px; text-align: center; } .quick-search-item-content { flex: 1; } .quick-search-item-title { font-weight: 500; color: var(--fg, #fff); } .quick-search-item-description { font-size: 12px; color: var(--muted, #aaa); margin-top: 2px; } .quick-search-item-badge { padding: 2px 8px; border-radius: 4px; font-size: 11px; background: var(--card-hover, #2a2a4e); color: var(--muted, #888); } .quick-search-empty { padding: 40px 20px; text-align: center; color: var(--muted, #666); } .quick-search-footer { display: flex; justify-content: center; gap: 24px; padding: 12px 20px; border-top: 1px solid var(--border, #333); background: var(--card-hover, #2a2a4e); font-size: 12px; color: var(--muted, #666); } .quick-search-footer kbd { background: var(--card-base, #1a1a2e); padding: 2px 6px; border-radius: 4px; font-family: monospace; margin-right: 4px; } `; document.head.appendChild(style); document.body.appendChild(quickSearchModal); quickSearchInput = document.getElementById('quick-search-input'); quickSearchResults = document.getElementById('quick-search-results'); // Input handling quickSearchInput.addEventListener('input', handleSearchInput); quickSearchInput.addEventListener('keydown', handleSearchKeyDown); // Close on backdrop click quickSearchModal.addEventListener('click', (e) => { if (e.target === quickSearchModal) { closeQuickSearch(); } }); } /** * Handle global keydown events */ function handleKeyDown(e) { try { // Ctrl+K or Cmd+K to open quick search if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); openQuickSearch(); return; } // Escape to close modals if (e.key === 'Escape') { // First check if quick search is open if (quickSearchModal && quickSearchModal.classList.contains('show')) { closeQuickSearch(); return; } // Then close any other open modal closeTopModal(); } } catch (e2) { console.warn('[Keyboard Shortcuts] Error handling keydown:', e2.message); } } /** * Open quick search modal */ function openQuickSearch() { try { quickSearchModal.classList.add('show'); quickSearchInput.value = ''; quickSearchInput.focus(); showDefaultResults(); } catch (e) { console.warn('[Keyboard Shortcuts] Error opening quick search:', e.message); } } /** * Close quick search modal */ function closeQuickSearch() { try { quickSearchModal.classList.remove('show'); quickSearchInput.value = ''; quickSearchResults.innerHTML = ''; } catch (e) { console.warn('[Keyboard Shortcuts] Error closing quick search:', e.message); } } /** * Close the topmost open modal */ function closeTopModal() { for (const selector of MODAL_SELECTORS) { const modal = document.querySelector(selector); if (modal && (modal.classList.contains('show') || modal.style.display === 'flex')) { modal.classList.remove('show'); modal.style.display = 'none'; return true; } } return false; } /** * Show default/suggested results */ function showDefaultResults() { const html = `
Quick Actions
🔄
Refresh Dashboard
Refresh all service statuses
Reload Caddy
Reload Caddy configuration
Add Service
Open service configuration modal
📱
App Selector
Deploy new applications
Services
${getServiceItems()} `; quickSearchResults.innerHTML = html; attachResultListeners(); } /** * Get service items from the dashboard */ function getServiceItems() { const cards = document.querySelectorAll('.card[data-app], #cards .card'); let html = ''; cards.forEach(card => { const name = card.querySelector('.name')?.textContent || 'Unknown'; const status = card.dataset.status || 'unknown'; const app = card.dataset.app || ''; if (name && name !== '--') { html += `
${status === 'on' ? '🟢' : '🔴'}
${name}
Click to open service
${status.toUpperCase()}
`; } }); return html || '
No services found
'; } /** * Handle search input */ function handleSearchInput(e) { try { const query = e.target.value.toLowerCase().trim(); if (!query) { showDefaultResults(); return; } // Search through services and actions const results = searchAll(query); displaySearchResults(results); } catch (e2) { console.warn('[Keyboard Shortcuts] Error handling search input:', e2.message); } } /** * Search all items */ function searchAll(query) { const results = { actions: [], services: [] }; // Search actions const actions = [ { id: 'refresh', title: 'Refresh Dashboard', icon: '🔄', keywords: 'refresh reload update status' }, { id: 'reload-caddy', title: 'Reload Caddy', icon: '⚡', keywords: 'reload caddy proxy config' }, { id: 'add-service', title: 'Add Service', icon: '➕', keywords: 'add new service create' }, { id: 'app-selector', title: 'App Selector', icon: '📱', keywords: 'app deploy install docker container' }, { id: 'backup', title: 'Backup & Restore', icon: '💾', keywords: 'backup restore export import' }, { id: 'stats', title: 'Container Stats', icon: '📊', keywords: 'stats resources cpu memory' }, { id: 'logs', title: 'View Logs', icon: '📋', keywords: 'logs error debug' }, { id: 'tokens', title: 'Manage Tokens', icon: '🔑', keywords: 'tokens api keys credentials' }, { id: 'notifications', title: 'Notifications', icon: '🔔', keywords: 'alerts notifications discord telegram' }, { id: 'theme', title: 'Change Theme', icon: '🎨', keywords: 'theme dark light appearance' }, { id: 'tour', title: 'Help Tour', icon: '🎓', keywords: 'help tour guide onboarding' } ]; actions.forEach(action => { if (action.title.toLowerCase().includes(query) || action.keywords.includes(query)) { results.actions.push(action); } }); // Search services const cards = document.querySelectorAll('.card[data-app], #cards .card'); cards.forEach(card => { const name = card.querySelector('.name')?.textContent || ''; const app = card.dataset.app || ''; const status = card.dataset.status || 'unknown'; if (name.toLowerCase().includes(query) || app.toLowerCase().includes(query)) { results.services.push({ id: app, title: name, status: status, icon: status === 'on' ? '🟢' : '🔴' }); } }); return results; } /** * Display search results */ function displaySearchResults(results) { let html = ''; if (results.actions.length > 0) { html += '
Actions
'; results.actions.forEach(action => { html += `
${action.icon}
${action.title}
`; }); } if (results.services.length > 0) { html += '
Services
'; results.services.forEach(service => { html += `
${service.icon}
${service.title}
${service.status.toUpperCase()}
`; }); } if (!html) { html = '
No results found
'; } quickSearchResults.innerHTML = html; attachResultListeners(); } /** * Attach click listeners to result items */ function attachResultListeners() { const items = quickSearchResults.querySelectorAll('.quick-search-item'); items.forEach((item, index) => { item.addEventListener('click', () => executeAction(item)); // Select first item by default if (index === 0) { item.classList.add('selected'); } }); } /** * Handle keyboard navigation in search */ function handleSearchKeyDown(e) { try { const items = quickSearchResults.querySelectorAll('.quick-search-item'); const selected = quickSearchResults.querySelector('.quick-search-item.selected'); const selectedIndex = Array.from(items).indexOf(selected); if (e.key === 'ArrowDown') { e.preventDefault(); if (selected) selected.classList.remove('selected'); const nextIndex = (selectedIndex + 1) % items.length; items[nextIndex]?.classList.add('selected'); items[nextIndex]?.scrollIntoView({ block: 'nearest' }); } else if (e.key === 'ArrowUp') { e.preventDefault(); if (selected) selected.classList.remove('selected'); const prevIndex = selectedIndex <= 0 ? items.length - 1 : selectedIndex - 1; items[prevIndex]?.classList.add('selected'); items[prevIndex]?.scrollIntoView({ block: 'nearest' }); } else if (e.key === 'Enter') { e.preventDefault(); if (selected) { executeAction(selected); } } } catch (e2) { console.warn('[Keyboard Shortcuts] Error handling search navigation:', e2.message); } } /** * Execute an action from quick search */ function executeAction(item) { try { const action = item.dataset.action; const service = item.dataset.service; closeQuickSearch(); switch (action) { case 'refresh': document.getElementById('refresh')?.click(); break; case 'reload-caddy': document.getElementById('reload-caddy-top')?.click(); break; case 'add-service': document.getElementById('add-service')?.click(); break; case 'app-selector': document.getElementById('add-service-btn')?.click(); break; case 'backup': document.getElementById('backup-restore-btn')?.click(); break; case 'stats': document.getElementById('container-stats-btn')?.click(); break; case 'logs': document.getElementById('view-error-logs')?.click(); break; case 'tokens': document.getElementById('manage-tokens')?.click(); break; case 'notifications': document.getElementById('manage-notifications')?.click(); break; case 'theme': document.getElementById('theme')?.click(); break; case 'tour': document.getElementById('restart-tour-btn')?.click(); break; case 'open-service': if (service) { const openBtn = document.querySelector(`[data-app="${service}"] [id$="-open"], [data-app="${service}"] button:not(.restart-btn):not(.logs-btn):not(.settings-btn)`); if (openBtn) { openBtn.click(); } else { // Try to find the card and click it const card = document.querySelector(`[data-app="${service}"]`); if (card) card.click(); } } break; default: console.log('[Keyboard Shortcuts] Unknown action:', action); } } catch (e) { console.warn('[Keyboard Shortcuts] Error executing action:', e.message); } } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // Expose to global scope window.DashCaddyKeyboardShortcuts = { openQuickSearch, closeQuickSearch }; })();