{ "version": 3, "sources": ["../../javascript/sidebar.js", "../../javascript/notifications.js", "../../javascript/koz_dropdown.js", "../../javascript/koz_choices_editor.js", "../../javascript/task_modal.js", "../../javascript/block_editor.js", "../../javascript/page_actions.js"], "sourcesContent": ["// Kozado Sidebar JavaScript\n// Handles sidebar interactivity, workspace switching, dropdowns, etc.\n\ndocument.addEventListener('DOMContentLoaded', function() {\n // ============================================\n // SIDEBAR TOGGLE (Collapse/Expand)\n // ============================================\n\n const sidebarToggle = document.getElementById('sidebar-toggle');\n const primarySidebar = document.getElementById('primary-sidebar');\n const navbarLogoSection = document.getElementById('navbar-logo-section');\n\n if (sidebarToggle && primarySidebar && navbarLogoSection) {\n sidebarToggle.addEventListener('click', function() {\n // Toggle both sidebar and navbar logo section\n primarySidebar.classList.toggle('collapsed');\n navbarLogoSection.classList.toggle('collapsed');\n\n // Save preference\n const isCollapsed = primarySidebar.classList.contains('collapsed');\n localStorage.setItem('kozado_sidebar_collapsed', isCollapsed);\n });\n\n // Restore preference\n const savedState = localStorage.getItem('kozado_sidebar_collapsed');\n if (savedState === 'true') {\n primarySidebar.classList.add('collapsed');\n navbarLogoSection.classList.add('collapsed');\n }\n }\n \n // ============================================\n // WORKSPACE DROPDOWN\n // ============================================\n \n const workspaceDropdownBtn = document.getElementById('workspace-dropdown-btn');\n const workspaceDropdown = document.getElementById('workspace-dropdown');\n \n function closeWorkspaceDropdown() {\n if (workspaceDropdown) {\n workspaceDropdown.classList.remove('show');\n workspaceDropdown.classList.add('hidden');\n }\n }\n\n if (workspaceDropdownBtn && workspaceDropdown) {\n workspaceDropdownBtn.addEventListener('click', function(e) {\n e.stopPropagation();\n workspaceDropdown.classList.toggle('show');\n workspaceDropdown.classList.toggle('hidden');\n });\n \n // Close dropdown when clicking outside\n document.addEventListener('click', function(e) {\n if (!workspaceDropdown.contains(e.target) && !workspaceDropdownBtn.contains(e.target)) {\n closeWorkspaceDropdown();\n }\n });\n \n // Workspace switching\n const workspaceSwitchItems = document.querySelectorAll('.workspace-switch-item');\n workspaceSwitchItems.forEach(item => {\n item.addEventListener('click', function() {\n const slug = this.dataset.workspaceSlug;\n if (slug) {\n window.location.href = '/workspaces/' + slug;\n }\n });\n });\n }\n \n // ============================================\n // PROJECT TREE TOGGLE\n // ============================================\n // Handled inline by _sidebar.html.erb (single source of truth for collapse,\n // icon rotation, and localStorage persistence).\n\n\n // ============================================\n // QUICK SEARCH OVERLAY\n // ============================================\n \n const quickSearchBtn = document.getElementById('quick-search-btn');\n const headerSearchBtn = document.getElementById('header-search-btn');\n const searchOverlay = document.getElementById('search-overlay');\n const globalSearchInput = document.getElementById('global-search-input');\n \n function openSearch() {\n if (searchOverlay) {\n searchOverlay.classList.remove('hidden');\n if (globalSearchInput) {\n globalSearchInput.focus();\n }\n }\n }\n \n function closeSearch() {\n if (searchOverlay) {\n searchOverlay.classList.add('hidden');\n if (globalSearchInput) {\n globalSearchInput.value = '';\n }\n }\n }\n \n if (quickSearchBtn) {\n quickSearchBtn.addEventListener('click', openSearch);\n }\n \n if (headerSearchBtn) {\n headerSearchBtn.addEventListener('click', openSearch);\n }\n \n if (searchOverlay) {\n searchOverlay.addEventListener('click', function(e) {\n if (e.target === searchOverlay) {\n closeSearch();\n }\n });\n }\n \n // Keyboard shortcuts\n document.addEventListener('keydown', function(e) {\n // Cmd/Ctrl + K to open search\n if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n e.preventDefault();\n if (searchOverlay && searchOverlay.classList.contains('hidden')) {\n openSearch();\n } else {\n closeSearch();\n }\n }\n \n // Escape to close overlays\n if (e.key === 'Escape') {\n closeSearch();\n closeNotifications();\n closeUserDropdown();\n closeWorkspaceDropdown();\n }\n });\n \n // ============================================\n // NOTIFICATIONS PANEL\n // ============================================\n \n const notificationsBtn = document.getElementById('notifications-btn');\n const notificationsPanel = document.getElementById('notifications-panel');\n \n function closeNotifications() {\n if (notificationsPanel) {\n notificationsPanel.classList.add('hidden');\n }\n }\n \n if (notificationsBtn && notificationsPanel) {\n notificationsBtn.addEventListener('click', function(e) {\n e.stopPropagation();\n notificationsPanel.classList.toggle('hidden');\n });\n \n document.addEventListener('click', function(e) {\n if (!notificationsPanel.contains(e.target) && !notificationsBtn.contains(e.target)) {\n closeNotifications();\n }\n });\n }\n \n // ============================================\n // USER DROPDOWN\n // ============================================\n \n const userDropdownBtn = document.getElementById('user-dropdown-btn');\n const userDropdown = document.getElementById('user-dropdown');\n \n function closeUserDropdown() {\n if (userDropdown) {\n userDropdown.classList.add('hidden');\n }\n }\n \n if (userDropdownBtn && userDropdown) {\n userDropdownBtn.addEventListener('click', function(e) {\n e.stopPropagation();\n userDropdown.classList.toggle('hidden');\n });\n \n document.addEventListener('click', function(e) {\n if (!userDropdown.contains(e.target) && !userDropdownBtn.contains(e.target)) {\n closeUserDropdown();\n }\n });\n }\n \n // ============================================\n // ALERT CLOSE BUTTONS\n // ============================================\n \n const closeAlertButtons = document.querySelectorAll('.close-alert');\n closeAlertButtons.forEach(btn => {\n btn.addEventListener('click', function() {\n const alert = this.closest('.alert');\n if (alert) {\n alert.style.opacity = '0';\n setTimeout(() => alert.remove(), 300);\n }\n });\n });\n \n // ============================================\n // NEW PROJECT BUTTON\n // ============================================\n // Handled inline by the new-project modal in _sidebar.html.erb.\n\n\n // ============================================\n // TOOLTIP INITIALIZATION (if needed)\n // ============================================\n \n // Add tooltips to buttons with title attribute\n const tooltipElements = document.querySelectorAll('[title]');\n tooltipElements.forEach(el => {\n // Could initialize a tooltip library here\n });\n \n console.log('Kozado sidebar initialized');\n});\n", "// Kozado \u2014 global Notifications panel.\n//\n// Powers the bell button + panel in the navbar. Reads bootstrap data from\n// #notifications-panel (rendered by application.html.erb), fetches the\n// current user's notifications, paints them into the panel, toggles the\n// red unread dot on the bell, subscribes to the per-user Pusher channel\n// for live additions, and wires the \"Mark all read\" button.\n//\n// The toggle (open/close on bell click, ESC, outside click) is handled by\n// app/javascript/sidebar.js \u2014 this file is only concerned with data and\n// rendering.\n\n(function () {\n function init() {\n var panel = document.getElementById('notifications-panel');\n if (!panel) return;\n\n var currentUserId = parseInt(panel.dataset.currentUserId, 10);\n var pusherKey = panel.dataset.pusherKey;\n var pusherCluster = panel.dataset.pusherCluster || 'us2';\n\n var btn = document.getElementById('notifications-btn');\n var dot = document.getElementById('notifications-unread-dot');\n var listEl = document.getElementById('notifications-list');\n var emptyEl = document.getElementById('notifications-empty');\n var markAllBtn = document.getElementById('notifications-mark-all-read');\n\n if (!currentUserId || !listEl) return;\n\n var notifications = [];\n var unreadCount = 0;\n\n function updateDot() {\n if (!dot) return;\n if (unreadCount > 0) dot.classList.remove('hidden');\n else dot.classList.add('hidden');\n // Mirror the unread count to any listening desktop wrapper (Electron app\n // listens for this via its preload bridge). No-op in regular browsers.\n try {\n window.dispatchEvent(new CustomEvent('kozado:unread-count', { detail: unreadCount }));\n } catch (e) { /* ignore */ }\n }\n\n function updateMarkAllState() {\n if (!markAllBtn) return;\n markAllBtn.disabled = unreadCount === 0;\n }\n\n function escapeHtml(s) {\n return String(s == null ? '' : s)\n .replace(/&/g, '&').replace(//g, '>').replace(/\"/g, '"').replace(/'/g, ''');\n }\n\n // Color cue for the actor avatar fallback. Hashing on actor id keeps a\n // user's chip the same color across notifications without needing a\n // server-side color table.\n var GRADIENTS = [\n 'from-brand to-brand-2',\n 'from-purple to-blue',\n 'from-amber to-pink',\n 'from-green to-teal-400',\n 'from-pink to-purple',\n 'from-blue to-cyan-400'\n ];\n function gradientFor(id) {\n var n = (parseInt(id, 10) || 0) % GRADIENTS.length;\n return GRADIENTS[n < 0 ? n + GRADIENTS.length : n];\n }\n\n function renderItem(n) {\n var actor = n.actor || {};\n var initials = escapeHtml(actor.initials || (actor.name ? actor.name.slice(0, 2).toUpperCase() : '\u00B7'));\n var avatar;\n if (actor.image) {\n avatar =\n '