import { listFiddles, listTags } from './api.js'; const $ = (sel) => document.querySelector(sel); let debounceTimer = null; let currentPage = 1; let activeTag = ''; const JS_TYPE_LABELS = { javascript: 'JS', typescript: 'TS', react: 'React', 'react-ts': 'React+TS', vue: 'Vue', svelte: 'Svelte', }; function relativeTime(dateStr) { const now = Date.now(); const then = new Date(dateStr + 'Z').getTime(); const diff = Math.floor((now - then) / 1000); if (diff < 60) return 'just now'; if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; if (diff < 2592000) return `${Math.floor(diff / 86400)}d ago`; return new Date(dateStr).toLocaleDateString(); } function renderCards(fiddles) { const grid = $('#fiddle-grid'); if (!fiddles.length) { grid.innerHTML = '
No fiddles found. Create one!
'; return; } grid.innerHTML = fiddles.map(f => { const preview = f.js_preview || f.html_preview || ''; const badge = JS_TYPE_LABELS[f.js_type] || 'JS'; const tags = (f.tags || []).map(t => `${esc(t.name)}`).join(''); return `
${esc(f.title)}
${badge} ${relativeTime(f.updated_at)}
${preview ? `
${esc(preview)}
` : ''} ${tags ? `
${tags}
` : ''}
`; }).join(''); } function renderPagination(total, page, limit) { const pages = Math.ceil(total / limit); if (pages <= 1) { $('#pagination').innerHTML = ''; return; } let html = ''; if (page > 1) html += ``; for (let i = 1; i <= pages; i++) { if (pages > 7 && i > 2 && i < pages - 1 && Math.abs(i - page) > 1) { if (i === 3 || i === pages - 2) html += '...'; continue; } html += ``; } if (page < pages) html += ``; const el = $('#pagination'); el.innerHTML = html; el.querySelectorAll('.page-btn').forEach(btn => { btn.addEventListener('click', () => { currentPage = parseInt(btn.dataset.page, 10); fetchAndRender(); }); }); } async function renderTagsBar() { try { const { tags } = await listTags(); const bar = $('#tags-bar'); bar.innerHTML = tags.map(t => `${esc(t.name)} (${t.count})` ).join(''); bar.querySelectorAll('.tag-filter').forEach(el => { el.addEventListener('click', () => { activeTag = activeTag === el.dataset.tag ? '' : el.dataset.tag; currentPage = 1; fetchAndRender(); renderTagsBar(); }); }); } catch (_) { /* ignore */ } } async function fetchAndRender() { const q = $('#search-input').value.trim(); const js_type = $('#filter-framework').value; const sort = $('#filter-sort').value; try { const { fiddles, total, page, limit } = await listFiddles({ q, js_type, tag: activeTag, page: currentPage, sort }); renderCards(fiddles); renderPagination(total, page, limit); } catch (e) { $('#fiddle-grid').innerHTML = `
Error loading fiddles: ${esc(e.message)}
`; } } function esc(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // Event listeners $('#search-input').addEventListener('input', () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { currentPage = 1; fetchAndRender(); }, 300); }); $('#filter-framework').addEventListener('change', () => { currentPage = 1; fetchAndRender(); }); $('#filter-sort').addEventListener('change', () => { currentPage = 1; fetchAndRender(); }); // Init renderTagsBar(); fetchAndRender();