diff --git a/palette b/palette index 663a234..a0fd6a8 100755 Binary files a/palette and b/palette differ diff --git a/web/static/app.css b/web/static/app.css index 29d7f97..07207a3 100644 --- a/web/static/app.css +++ b/web/static/app.css @@ -299,3 +299,10 @@ td a.slug:hover { color: var(--accent); } td .url-link { font-size: 19.8px; } td .id-link { color: var(--muted-fg); text-decoration: none; font-family: var(--font-mono); font-size: 19.8px; } td .id-link:hover { color: var(--accent); } + +/* sortable column headers (#42) */ +th.sortable { cursor: pointer; user-select: none; } +th.sortable:hover { color: var(--fg); } +.sort-ind { display: inline-block; width: 0; height: 0; margin-left: 6px; vertical-align: middle; border-left: 5px solid transparent; border-right: 5px solid transparent; } +th.sorted.asc .sort-ind { border-bottom: 6px solid var(--accent); } +th.sorted.desc .sort-ind { border-top: 6px solid var(--accent); } diff --git a/web/templates/history.html b/web/templates/history.html index 47bdd38..b06af41 100644 --- a/web/templates/history.html +++ b/web/templates/history.html @@ -9,7 +9,15 @@
- + + + + + + + + +
PasteLanguageSizeViewsCreatedURLID
PasteLanguageSizeViewsCreatedURLID
@@ -35,6 +43,44 @@ function ago(ts) { } let filter = ''; +let sortKey = null, sortDir = 1; + +const sortVal = (it, k) => { + let v = it[k]; + if (k === 'title' || k === 'custom_slug') v = (v == null || v === '') ? null : String(v).toLowerCase(); + if (k === 'language') v = (v == null || v === '') ? 'text' : String(v).toLowerCase(); + if (k === 'size' || k === 'view_count' || k === 'created_at') return v == null ? -1 : v; + return v == null ? null : v; +}; +function sortItems(items) { + if (!sortKey) return items; + const k = sortKey; + return items.slice().sort((a, b) => { + let va = sortVal(a, k), vb = sortVal(b, k); + const na = va == null, nb = vb == null; + if (na && nb) return 0; + if (na) return 1; // nulls always last + if (nb) return -1; + if (va < vb) return -1 * sortDir; + if (va > vb) return 1 * sortDir; + return (a.created_at || 0) < (b.created_at || 0) ? 1 : -1; // stable tiebreak: newest first + }); +} +function renderSortIndicators() { + document.querySelectorAll('th.sortable').forEach(th => { + th.classList.toggle('sorted', th.dataset.sort === sortKey); + th.classList.toggle('asc', th.dataset.sort === sortKey && sortDir === 1); + th.classList.toggle('desc', th.dataset.sort === sortKey && sortDir === -1); + }); +} +document.querySelector('thead').addEventListener('click', e => { + const th = e.target.closest('th.sortable'); + if (!th) return; + const k = th.dataset.sort; + if (sortKey === k) { sortDir = -sortDir; } else { sortKey = k; sortDir = 1; } + renderSortIndicators(); + load(); +}); function matchesFilter(it) { if (!filter) return true; @@ -48,14 +94,15 @@ async function load() { try { const filtered = filter.length > 0; const off = (page - 1) * PER; - const url = filtered - ? '/api/public?limit=200&offset=0' + const url = (filtered || sortKey) + ? '/api/public?limit=500&offset=0' : '/api/public?limit=' + PER + '&offset=' + off; const res = await fetch(url); const data = await res.json(); total = data.total; - const items = filtered ? data.items.filter(matchesFilter) : data.items; + let items = filtered ? data.items.filter(matchesFilter) : data.items; + items = sortItems(items); $('count').textContent = filtered ? items.length.toLocaleString() + ' matches (of ' + total.toLocaleString() + ' total)' @@ -78,8 +125,10 @@ async function load() { } const pages = Math.max(1, Math.ceil(total / PER)); - if (filtered) { - $('showing').textContent = 'Showing ' + items.length.toLocaleString() + ' matches for "' + filter + '"'; + if (filtered || sortKey) { + $('showing').textContent = sortKey + ? 'Sorted by ' + sortKey + ' (' + (sortDir === 1 ? 'ascending' : 'descending') + ') · ' + items.length.toLocaleString() + ' of ' + total.toLocaleString() + : 'Showing ' + items.length.toLocaleString() + ' matches for "' + filter + '"'; $('pg').innerHTML = ''; } else { $('showing').textContent = total === 0 ? 'Nothing here yet' : @@ -100,6 +149,7 @@ async function load() { add('›', page+1, {dis: page===pages}); $('pg').innerHTML = btns.join(''); } + renderSortIndicators(); } finally { spinner.style.visibility = 'hidden'; }