// ── Context menu ── import { $, esc, api } from './util.js'; import { state, getChildren, CARD_COLORS } from './state.js'; import { showDialog } from './dialog.js'; import { pushCardEdit, pushCardDelete, pushBatch, snapshotCard } from './undo.js'; const menu = $('ctx-menu'); const items = $('ctx-items'); const colors = $('ctx-colors'); const buckets = $('ctx-buckets'); const members = $('ctx-members'); export function hideContextMenu() { menu.classList.add('hidden'); } document.addEventListener('click', hideContextMenu); document.addEventListener('contextmenu', e => { if (!menu.contains(e.target)) hideContextMenu(); }); export function showContextMenu(x, y, card) { const children = getChildren(card.id); const isParent = children.length > 0; const isChild = !!card.parent_id; items.innerHTML = `
${isParent ? ` ` : ''} ${isChild ? ` ` : ''} `; // Color sub-panel const grid = $('ctx-color-grid'); grid.innerHTML = CARD_COLORS.map(c => ``).join(''); colors.classList.add('hidden'); // Members sub-panel if (isParent) { members.innerHTML = [card, ...children].map(gc => ` `).join(''); } members.classList.add('hidden'); // Bucket sub-panel buckets.innerHTML = `` + state.buckets.map(b => ``).join(''); buckets.classList.add('hidden'); menu.style.left = Math.min(x, innerWidth - 188) + 'px'; menu.style.top = Math.min(y, innerHeight - 300) + 'px'; menu.classList.remove('hidden'); const toggle = (panel) => { [colors, buckets, members].forEach(p => { if (p !== panel) p.classList.add('hidden'); }); panel.classList.toggle('hidden'); }; // Actions items.querySelectorAll('[data-action]').forEach(btn => btn.addEventListener('click', async e => { e.stopPropagation(); const a = btn.dataset.action; if (a === 'open') { hideContextMenu(); window._openDetail(card.id); } else if (a === 'edit') { hideContextMenu(); window._openModal(card); } else if (a === 'duplicate') { hideContextMenu(); await api('api/cards', { method: 'POST', body: { title: card.title + ' (copy)', body: card.body, color: card.color, bucket_id: card.bucket_id, position: -1 } }); await window._reloadAll(); } else if (a === 'color') toggle(colors); else if (a === 'bucket') toggle(buckets); else if (a === 'group-members') toggle(members); else if (a === 'expand-group') { hideContextMenu(); state.activeGroup = card.id; window._renderBoard(); } else if (a === 'go-to-group') { hideContextMenu(); const p = state.cards.find(c => c.id == card.parent_id); if (p) { state.activeGroup = p.id; window._renderBoard(); } } else if (a === 'dissolve') { hideContextMenu(); const ok = await showDialog({ title: 'dissolve_group', message: `Gruppe "${card.title}" aufloesen?`, danger: true, okLabel: 'dissolve' }); if (ok) { const ch = getChildren(card.id); pushBatch('dissolve', ch.map(c => ({ cardId: c.id, prevData: { parent_id: card.id } }))); await Promise.all(ch.map(c => api(`api/cards/${c.id}`, { method: 'PATCH', body: { parent_id: null } }))); await window._reloadAll(); } } else if (a === 'ungroup') { hideContextMenu(); pushCardEdit('ungroup', card.id, { parent_id: card.parent_id }); await api(`api/cards/${card.id}`, { method: 'PATCH', body: { parent_id: null } }); await window._reloadAll(); } else if (a === 'delete') { hideContextMenu(); const ok = await showDialog({ title: 'delete_card', message: `"${card.title}" wirklich loeschen?`, danger: true, okLabel: 'delete' }); if (ok) { pushCardDelete('delete', snapshotCard(card)); await api(`api/cards/${card.id}`, { method: 'DELETE' }); await window._reloadAll(); } } })); grid.querySelectorAll('[data-ctx-color]').forEach(btn => btn.addEventListener('click', async e => { e.stopPropagation(); hideContextMenu(); pushCardEdit('color', card.id, { color: card.color }); await api(`api/cards/${card.id}`, { method: 'PATCH', body: { color: btn.dataset.ctxColor } }); await window._reloadAll(); })); buckets.querySelectorAll('[data-ctx-bucket]').forEach(btn => btn.addEventListener('click', async e => { e.stopPropagation(); hideContextMenu(); pushCardEdit('bucket', card.id, { bucket_id: card.bucket_id }); await api(`api/cards/${card.id}`, { method: 'PATCH', body: { bucket_id: btn.dataset.ctxBucket || null } }); await window._reloadAll(); })); members.querySelectorAll('[data-ctx-member]').forEach(btn => btn.addEventListener('click', e => { e.stopPropagation(); if (e.target.closest('[data-ctx-kick]')) return; hideContextMenu(); window._openDetail(btn.dataset.ctxMember); })); members.querySelectorAll('[data-ctx-kick]').forEach(btn => btn.addEventListener('click', async e => { e.stopPropagation(); hideContextMenu(); await api(`api/cards/${btn.dataset.ctxKick}`, { method: 'PATCH', body: { parent_id: null } }); await window._reloadAll(); })); }