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 = '
';
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();