/** * Base Module JavaScript * Core functionality for the General Bots Suite * Handles navigation, theme, settings, accessibility, and HTMX error handling */ // DOM Elements const appsBtn = document.getElementById("apps-btn"); const appsDropdown = document.getElementById("apps-dropdown"); const settingsBtn = document.getElementById("settings-btn"); const settingsPanel = document.getElementById("settings-panel"); // Apps Menu Toggle if (appsBtn) { appsBtn.addEventListener("click", (e) => { e.stopPropagation(); const isOpen = appsDropdown.classList.toggle("show"); appsBtn.setAttribute("aria-expanded", isOpen); settingsPanel.classList.remove("show"); }); } // Settings Panel Toggle if (settingsBtn) { settingsBtn.addEventListener("click", (e) => { e.stopPropagation(); const isOpen = settingsPanel.classList.toggle("show"); settingsBtn.setAttribute("aria-expanded", isOpen); appsDropdown.classList.remove("show"); }); } // Close dropdowns when clicking outside document.addEventListener("click", (e) => { if (appsDropdown && !appsDropdown.contains(e.target) && !appsBtn.contains(e.target)) { appsDropdown.classList.remove("show"); appsBtn.setAttribute("aria-expanded", "false"); } if (settingsPanel && !settingsPanel.contains(e.target) && !settingsBtn.contains(e.target)) { settingsPanel.classList.remove("show"); settingsBtn.setAttribute("aria-expanded", "false"); } }); // Escape key closes dropdowns document.addEventListener("keydown", (e) => { if (e.key === "Escape") { if (appsDropdown) appsDropdown.classList.remove("show"); if (settingsPanel) settingsPanel.classList.remove("show"); if (appsBtn) appsBtn.setAttribute("aria-expanded", "false"); if (settingsBtn) settingsBtn.setAttribute("aria-expanded", "false"); } }); // Alt+key shortcuts for navigation document.addEventListener("keydown", (e) => { if (e.altKey && !e.ctrlKey && !e.shiftKey) { const shortcuts = { 1: "chat", 2: "drive", 3: "tasks", 4: "mail", 5: "calendar", 6: "meet", 7: "paper", 8: "research", 9: "sources", 0: "analytics", a: "admin", m: "monitoring", }; if (shortcuts[e.key]) { e.preventDefault(); const link = document.querySelector(`a[href="#${shortcuts[e.key]}"]`); if (link) link.click(); if (appsDropdown) appsDropdown.classList.remove("show"); } if (e.key === ",") { e.preventDefault(); if (settingsPanel) settingsPanel.classList.toggle("show"); } if (e.key === "s") { e.preventDefault(); const settingsLink = document.querySelector(`a[href="#settings"]`); if (settingsLink) settingsLink.click(); } } }); // Update active app on HTMX swap document.body.addEventListener("htmx:afterSwap", (e) => { if (e.detail.target.id === "main-content") { const hash = window.location.hash || "#chat"; document.querySelectorAll(".app-item").forEach((item) => { item.classList.toggle("active", item.getAttribute("href") === hash); }); if (settingsPanel) settingsPanel.classList.remove("show"); } }); // Theme handling const themeOptions = document.querySelectorAll(".theme-option"); const savedTheme = localStorage.getItem("gb-theme") || "dark"; document.body.setAttribute("data-theme", savedTheme); document.querySelector(`.theme-option[data-theme="${savedTheme}"]`)?.classList.add("active"); themeOptions.forEach((option) => { option.addEventListener("click", () => { const theme = option.getAttribute("data-theme"); document.body.setAttribute("data-theme", theme); localStorage.setItem("gb-theme", theme); themeOptions.forEach((o) => o.classList.remove("active")); option.classList.add("active"); }); }); // Quick Settings Toggle function toggleQuickSetting(el) { el.classList.toggle("active"); const setting = el.id.replace("toggle-", ""); localStorage.setItem(`gb-${setting}`, el.classList.contains("active")); } // Load quick toggle states ["notifications", "sound", "compact"].forEach((setting) => { const saved = localStorage.getItem(`gb-${setting}`); const toggle = document.getElementById(`toggle-${setting}`); if (toggle && saved !== null) { toggle.classList.toggle("active", saved === "true"); } }); // Show keyboard shortcuts notification function showKeyboardShortcuts() { window.showNotification( "Alt+1-9,0 for apps, Alt+A Admin, Alt+M Monitoring, Alt+S Settings, Alt+, quick settings", "info", 8000 ); } // Accessibility: Announce page changes to screen readers function announceToScreenReader(message) { const liveRegion = document.getElementById("aria-live"); if (liveRegion) { liveRegion.textContent = message; // Clear after announcement setTimeout(() => { liveRegion.textContent = ""; }, 1000); } } // HTMX accessibility hooks document.body.addEventListener("htmx:beforeRequest", function (e) { const target = e.detail.target; if (target && target.id === "main-content") { target.setAttribute("aria-busy", "true"); announceToScreenReader("Loading content..."); } }); document.body.addEventListener("htmx:afterSwap", function (e) { const target = e.detail.target; if (target && target.id === "main-content") { target.setAttribute("aria-busy", "false"); // Focus management: move focus to main content after navigation target.focus(); announceToScreenReader("Content loaded"); } }); document.body.addEventListener("htmx:responseError", function (e) { const target = e.detail.target; if (target) { target.setAttribute("aria-busy", "false"); } announceToScreenReader("Error loading content. Please try again."); }); // Keyboard navigation for apps grid document.addEventListener("keydown", function (e) { const appsGrid = document.querySelector(".apps-grid"); if (!appsGrid || !appsGrid.closest(".show")) return; const items = Array.from(appsGrid.querySelectorAll(".app-item")); const currentIndex = items.findIndex((item) => item === document.activeElement); if (currentIndex === -1) return; let newIndex = currentIndex; const columns = 3; // Grid has 3 columns on desktop switch (e.key) { case "ArrowRight": newIndex = Math.min(currentIndex + 1, items.length - 1); break; case "ArrowLeft": newIndex = Math.max(currentIndex - 1, 0); break; case "ArrowDown": newIndex = Math.min(currentIndex + columns, items.length - 1); break; case "ArrowUp": newIndex = Math.max(currentIndex - columns, 0); break; case "Home": newIndex = 0; break; case "End": newIndex = items.length - 1; break; default: return; } if (newIndex !== currentIndex) { e.preventDefault(); items[newIndex].focus(); } }); // Notification System window.showNotification = function (message, type = "info", duration = 5000) { const container = document.getElementById("notifications"); if (!container) return; const notification = document.createElement("div"); notification.className = `notification ${type}`; notification.innerHTML = `