import { marked } from 'https://esm.sh/marked@14'; const recordsEl = document.getElementById('records'); const records = recordsEl ? JSON.parse(recordsEl.textContent || '[]') : []; const filterInput = document.querySelector('.filter'); const toggleArchived = document.querySelector('.toggle-archived'); const toggleGrouping = document.querySelector('.toggle-grouping'); const drawer = document.querySelector('.drawer'); const drawerContent = drawer?.querySelector('.drawer-content'); const drawerClose = drawer?.querySelector('.drawer-close'); const refreshInfo = document.querySelector('.refresh-info'); const header = document.querySelector('header'); const board = document.querySelector('main.board'); const generatedAt = header?.dataset.generated ? new Date(header.dataset.generated) : null; const NEXT_TICK_MIN = 5; const STATUS_ORDER = ['open', 'in_progress', 'paused', 'blocked', 'done']; const STATUS_LABEL = { open: 'open', in_progress: 'in-progress', paused: 'paused', blocked: 'blocked', done: 'done', }; const STATUS_EMOJI = { open: '⚪', in_progress: '🔴', paused: '🟡', blocked: '🔵', done: '🟢', }; const ARCHIVE_DAYS = 14; function escapeHtml(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatAge(iso, now) { if (!iso) return null; const then = new Date(iso); const diff = Math.max(0, now.getTime() - then.getTime()); const m = Math.floor(diff / 60_000); if (m < 60) return `${m}m`; const h = Math.floor(diff / 3_600_000); if (h < 24) return `${h}h`; const d = Math.floor(diff / 86_400_000); if (d < 7) return `${d}d`; if (d < 30) return `${Math.floor(d / 7)}w`; let mo = (now.getUTCFullYear() - then.getUTCFullYear()) * 12 + (now.getUTCMonth() - then.getUTCMonth()); if (now.getUTCDate() < then.getUTCDate()) mo -= 1; mo = Math.max(0, mo); if (mo < 12) return `${mo}mo`; return `${Math.floor(mo / 12)}y`; } function isArchived(rec, now) { if (rec.status !== 'done' || !rec.last_commit_iso) return false; const ms = now.getTime() - new Date(rec.last_commit_iso).getTime(); return ms > ARCHIVE_DAYS * 86_400_000; } function truncate(s, max) { return s.length <= max ? s : s.slice(0, max) + '…'; } function renderCard(rec, now, mode) { const age = formatAge(rec.last_commit_iso, now); const archived = isArchived(rec, now); const primary = mode === 'by-project' ? `${STATUS_EMOJI[rec.status]} ${STATUS_LABEL[rec.status]}` : `${escapeHtml(rec.project)}`; const ownerPill = rec.project_owner ? `${escapeHtml(rec.project_owner)}` : ''; const dateSpan = rec.last_commit_iso ? `${escapeHtml(rec.last_commit_iso.slice(0, 10))}` : ''; return `
loading…
`; drawer.hidden = false; try { const res = await fetch(rawUrl, { credentials: 'omit' }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const md = await res.text(); drawerContent.innerHTML = `Failed to load: ${escapeHtml(String(err))}
`; } } document.addEventListener('click', (e) => { const card = e.target.closest?.('.card'); if (card) openDrawer(card); }); drawerClose?.addEventListener('click', () => { if (drawer) drawer.hidden = true; }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && drawer) drawer.hidden = true; }); console.log(`board-viewer: ${records.length} records loaded`);