// ── Image gallery ── import { $, esc, api, getCsrf } from './util.js'; const AUDIO_EXT = /\.(mp3|ogg|wav|m4a)$/i; function isAudio(url) { return AUDIO_EXT.test(url); } function mediaTile(url, extraClass = '') { if (isAudio(url)) { return `
`; } return `
`; } import { state } from './state.js'; import { showDialog } from './dialog.js'; function getReferencedUrls() { const urls = new Set(); for (const card of state.cards) { const body = card.body || ''; let m; const mdRe = /!\[[^\]]*\]\(([^)]+)\)/g; while ((m = mdRe.exec(body)) !== null) urls.add(m[1].split(/\s/)[0]); const htmlRe = /]+src=["']([^"']+)["'][^>]*>/gi; while ((m = htmlRe.exec(body)) !== null) urls.add(m[1]); } return urls; } function findCardForUrl(url) { for (const card of state.cards) { const body = card.body || ''; if (body.includes(url)) return card; } return null; } export async function openGallery() { const el = $('gallery'); if (!el) return; const files = await api('api/uploads'); const referenced = getReferencedUrls(); const grid = $('gallery-grid'); const count = $('gallery-count'); const linked = []; const orphaned = []; for (const f of files) { const card = findCardForUrl(f.url); if (card) { linked.push({ ...f, cardId: card.id, cardTitle: card.title, cardColor: card.color }); } else { orphaned.push(f); } } const total = linked.length + orphaned.length; if (count) count.textContent = `${total} image${total !== 1 ? 's' : ''}${orphaned.length ? ` · ${orphaned.length} orphaned` : ''}`; if (!total) { grid.innerHTML = `

no images

`; } else { let html = ''; if (orphaned.length) { html += `
orphaned (not used by any card)
`; html += orphaned.map(f => ` `).join(''); } if (linked.length) { if (orphaned.length) html += `
linked to cards
`; html += linked.map(img => ` `).join(''); } grid.innerHTML = html; // Linked image clicks → open card grid.querySelectorAll('[data-card-id]').forEach(item => { item.addEventListener('click', () => { window._openDetail(item.dataset.cardId, { fromGallery: true, galleryOrigin: item.dataset.galleryOrigin || null, }); }); }); // Orphan: create card from image grid.querySelectorAll('[data-create]').forEach(btn => { btn.addEventListener('click', async e => { e.stopPropagation(); const url = btn.dataset.create; const name = url.split('/').pop().replace(/\.[^.]+$/, ''); await api('api/cards', { method: 'POST', body: { title: name, body: `![${name}](${url})`, color: '#38bdf8', position: -1 } }); await window._reloadAll(); openGallery(); // refresh }); }); // Orphan: delete grid.querySelectorAll('[data-del]').forEach(btn => { btn.addEventListener('click', async e => { e.stopPropagation(); const name = btn.dataset.del; const ok = await showDialog({ title: 'delete_image', message: `"${name}" loeschen?`, danger: true, okLabel: 'delete' }); if (!ok) return; await fetch(`api/uploads/${name}`, { method: 'DELETE', headers: { 'X-CSRF-Token': getCsrf() } }); openGallery(); // refresh }); }); } el.classList.add('open'); } export function closeGallery() { $('gallery')?.classList.remove('open'); } $('gallery-close')?.addEventListener('click', closeGallery); $('gallery')?.addEventListener('click', e => { if (e.target === $('gallery')) closeGallery(); }); $('gallery-btn')?.addEventListener('click', openGallery);