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 @@
- | Paste | Language | Size | Views | Created | URL | ID |
+
+ | Paste |
+ Language |
+ Size |
+ Views |
+ Created |
+ URL |
+ ID |
+
No pastes yet. Create the first one.
@@ -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';
}