// FlowDeck Notion UI — Client-side logic // Alpine.js + SortableJS + HTMX (function() { 'use strict'; // Theme persistence const savedTheme = localStorage.getItem('theme'); if (savedTheme) { document.documentElement.setAttribute('data-theme', savedTheme); } // Close context menu on click outside document.addEventListener('click', function(e) { const menu = document.getElementById('context-menu'); if (menu && menu.classList.contains('visible')) { if (!menu.contains(e.target)) { menu.classList.remove('visible'); } } }); // Close context menu on escape document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { const menu = document.getElementById('context-menu'); if (menu) menu.classList.remove('visible'); } }); // Tooltip system document.addEventListener('mouseover', function(e) { const target = e.target.closest('[data-tooltip]'); if (!target) return; const tip = document.getElementById('tooltip'); if (!tip) return; tip.textContent = target.getAttribute('data-tooltip'); tip.style.display = 'block'; const rect = target.getBoundingClientRect(); tip.style.left = rect.left + 'px'; tip.style.top = (rect.bottom + 4) + 'px'; }); document.addEventListener('mouseout', function(e) { const target = e.target.closest('[data-tooltip]'); if (!target) return; const tip = document.getElementById('tooltip'); if (tip) tip.style.display = 'none'; }); // Keyboard shortcuts document.addEventListener('keydown', function(e) { // Ctrl+O: New chat (placeholder) if (e.ctrlKey && e.key === 'o') { e.preventDefault(); console.log('New chat shortcut'); } }); // Modal functions window.openModal = function(title) { const modal = document.getElementById('card-modal'); const titleEl = document.getElementById('card-modal-title'); if (modal && titleEl) { titleEl.textContent = title || 'Détail'; modal.style.display = 'flex'; } }; window.closeModal = function() { const modal = document.getElementById('card-modal'); if (modal) modal.style.display = 'none'; }; // Close modal on overlay click document.addEventListener('click', function(e) { if (e.target.id === 'card-modal') { closeModal(); } }); // HTMX event handlers document.addEventListener('htmx:afterSwap', function(evt) { // Reinitialize Sortable after HTMX swaps if (evt.target.id === 'kanban-root' || evt.target.closest('.kanban-container')) { initSortable(); } }); // Initialize SortableJS on kanban columns function initSortable() { document.querySelectorAll('.kanban-cards').forEach(function(el) { if (el._sortable) return; // Already initialized el._sortable = new Sortable(el, { group: 'kanban', animation: 200, ghostClass: 'sortable-ghost', dragClass: 'sortable-drag', onEnd: function(evt) { const cardId = evt.item.dataset.cardId; const fromCol = evt.from.dataset.status; const toCol = evt.to.dataset.status; const group = evt.to.dataset.group; console.log('Moved card', cardId, 'from', fromCol, 'to', toCol, 'group', group); // TODO: Send move to API // htmx.ajax('POST', '/api/move', { ... }); } }); }); } // Init on page load document.addEventListener('DOMContentLoaded', function() { initSortable(); }); // Re-init when Alpine initializes document.addEventListener('alpine:initialized', function() { setTimeout(initSortable, 100); }); })();