import { listFiddles, listTags, listCollections, getCollection } from './api.js'; const $ = (sel) => document.querySelector(sel); let debounceTimer = null; let currentPage = 1; let activeTag = ''; let currentView = 'fiddles'; const JS_TYPE_LABELS = { javascript: 'JS', typescript: 'TS', react: 'React', 'react-ts': 'React+TS', vue: 'Vue', svelte: 'Svelte', markdown: 'MD', wasm: 'WASM', }; 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, gridSelector = '#fiddle-grid') { const grid = $(gridSelector); 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(''); const thumb = f.screenshot ? `` : (preview ? `
${esc(preview)}
` : ''); return ` ${thumb}
${esc(f.title)}
${badge} ${relativeTime(f.updated_at)}
${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(); }); // Browse tabs document.querySelectorAll('.browse-tab').forEach(tab => { tab.addEventListener('click', () => { currentView = tab.dataset.view; document.querySelectorAll('.browse-tab').forEach(t => t.classList.toggle('active', t === tab)); $('#fiddles-view').style.display = currentView === 'fiddles' ? '' : 'none'; $('#collections-view').style.display = currentView === 'collections' ? '' : 'none'; if (currentView === 'collections') renderCollections(); }); }); async function renderCollections() { const grid = $('#collections-grid'); const detail = $('#collection-detail'); grid.style.display = ''; detail.style.display = 'none'; try { const { collections } = await listCollections(); if (!collections.length) { grid.innerHTML = '
No collections yet
'; return; } grid.innerHTML = collections.map(c => `
${esc(c.name)}
${esc(c.description || '')}
${c.fiddle_count} fiddle${c.fiddle_count !== 1 ? 's' : ''}
`).join(''); grid.querySelectorAll('.collection-card').forEach(card => { card.addEventListener('click', () => showCollectionDetail(card.dataset.id)); }); } catch (e) { grid.innerHTML = `
Error: ${esc(e.message)}
`; } } async function showCollectionDetail(id) { const grid = $('#collections-grid'); const detail = $('#collection-detail'); grid.style.display = 'none'; detail.style.display = ''; try { const col = await getCollection(id); $('#collection-header').textContent = col.name; $('#collection-desc').textContent = col.description || ''; renderCards(col.fiddles || [], '#collection-fiddles'); } catch (e) { $('#collection-header').textContent = 'Error'; $('#collection-desc').textContent = e.message; } } $('#collection-back').addEventListener('click', () => renderCollections()); // Init renderTagsBar(); fetchAndRender();