History: working pagination over full 2431-row dataset, live filter
This commit is contained in:
+114
-15
@@ -63,40 +63,139 @@
|
||||
.pager { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; font-size: 12.5px; color: var(--muted-fg); }
|
||||
.pager .pg { display: flex; gap: 6px; }
|
||||
.pager button { border: 1px solid var(--border); background: var(--surface-2); color: var(--muted-fg); border-radius: 7px; padding: 4px 11px; font: inherit; font-size: 12.5px; cursor: pointer; }
|
||||
.pager button:hover:not(:disabled) { color: var(--fg); border-color: var(--muted); }
|
||||
.pager button.on { background: var(--accent); color: var(--bg); border-color: var(--accent); }
|
||||
.pager button:disabled { opacity: .4; cursor: default; }
|
||||
.empty { text-align: center; padding: 40px 16px; color: var(--muted-fg); font-size: 13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body data-preset="midnight">
|
||||
<div class="topbar">
|
||||
<a class="logo" href="index.html">Palette <em>/ v0</em></a>
|
||||
<nav><a href="/new/v2.html">new</a><a class="on" href="index.html">history</a><a href="https://git.archfox.org/poslop/palette">git</a></nav>
|
||||
<a class="logo" href="/history/">Palette <em>/ v0</em></a>
|
||||
<nav><a href="/new/">new</a><a class="on" href="/history/">history</a><a href="https://git.archfox.org/poslop/palette">git</a></nav>
|
||||
<div class="spacer"></div>
|
||||
</div>
|
||||
<div class="deck">
|
||||
<div class="head-row">
|
||||
<h1>Public pastes</h1>
|
||||
<span class="count">2,431 total</span>
|
||||
<span class="count" id="count"></span>
|
||||
<div class="spacer"></div>
|
||||
<div class="search"><input placeholder="filter by language or slug…"></div>
|
||||
<div class="search"><input id="filter" placeholder="filter by language or slug…"></div>
|
||||
</div>
|
||||
<div class="float">
|
||||
<table>
|
||||
<thead><tr><th>Paste</th><th>Description</th><th>Language</th><th>Size</th><th>Views</th><th>Created</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="row"><td><a class="slug" href="/paste/index.html">k3sYt2</a></td><td class="title-cell">traefik redirect middleware snippet</td><td><span class="badge">go</span></td><td class="dim">412 B</td><td class="dim">12</td><td class="dim">2m ago</td></tr>
|
||||
<tr class="row"><td><a class="slug" href="/paste/index.html">b7QwRn</a></td><td class="title-cell">nginx notes for staging</td><td><span class="badge">txt</span></td><td class="dim">1.2 KB</td><td class="dim">3</td><td class="dim">14m ago</td></tr>
|
||||
<tr class="row"><td><a class="slug" href="/paste/index.html">xT8vZc</a></td><td class="title-cell">csv parse helper</td><td><span class="badge">py</span></td><td class="dim">890 B</td><td class="dim">41</td><td class="dim">3h ago</td></tr>
|
||||
<tr class="row"><td><a class="slug" href="/unlock/index.html">9mQkLp</a><span class="badge lock">🔒</span></td><td class="title-cell">uptime kuma compose</td><td><span class="badge">yaml</span></td><td class="dim">2.4 KB</td><td class="dim">—</td><td class="dim">5h ago</td></tr>
|
||||
<tr class="row"><td><a class="slug" href="/can-demo/index.html">can/9fL2m</a></td><td class="title-cell">incident screenshots + logs (4 items)</td><td><span class="badge">can</span></td><td class="dim">3.8 MB</td><td class="dim">7</td><td class="dim">1h ago</td></tr>
|
||||
<tr class="row"><td><a class="slug" href="/paste/index.html">vR4nWq</a></td><td class="title-cell">sql index check</td><td><span class="badge">sql</span></td><td class="dim">220 B</td><td class="dim">2</td><td class="dim">6h ago</td></tr>
|
||||
<tr class="row"><td><a class="slug" href="/unlock/index.html">pL2xCa</a><span class="badge lock">🔒</span></td><td class="title-cell">todo: rewrite auth flow</td><td><span class="badge">md</span></td><td class="dim">560 B</td><td class="dim">—</td><td class="dim">1d ago</td></tr>
|
||||
</tbody>
|
||||
<tbody id="rows"></tbody>
|
||||
</table>
|
||||
<div class="empty" id="empty" style="display:none">No pastes match that filter.</div>
|
||||
<div class="pager">
|
||||
<span>Showing 1–7 of 2,431 · page 1 of 348</span>
|
||||
<div class="pg"><button class="on">1</button><button>2</button><button>3</button><button>…</button><button>348</button></div>
|
||||
<span id="showing"></span>
|
||||
<div class="pg" id="pg"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Demo dataset: 7 real entries + generated filler to simulate 2,431 total
|
||||
const real = [
|
||||
{slug:"k3sYt2", href:"/paste/index.html", desc:"traefik redirect middleware snippet", lang:"go", size:"412 B", views:"12", created:"2m ago", lock:false},
|
||||
{slug:"b7QwRn", href:"/paste/index.html", desc:"nginx notes for staging", lang:"txt", size:"1.2 KB", views:"3", created:"14m ago",lock:false},
|
||||
{slug:"xT8vZc", href:"/paste/index.html", desc:"csv parse helper", lang:"py", size:"890 B", views:"41", created:"3h ago", lock:false},
|
||||
{slug:"9mQkLp", href:"/unlock/index.html", desc:"uptime kuma compose", lang:"yaml", size:"2.4 KB", views:"—", created:"5h ago", lock:true},
|
||||
{slug:"can/9fL2m", href:"/can-demo/index.html",desc:"incident screenshots + logs (4 items)",lang:"can", size:"3.8 MB", views:"7", created:"1h ago", lock:false},
|
||||
{slug:"vR4nWq", href:"/paste/index.html", desc:"sql index check", lang:"sql", size:"220 B", views:"2", created:"6h ago", lock:false},
|
||||
{slug:"pL2xCa", href:"/unlock/index.html", desc:"todo: rewrite auth flow", lang:"md", size:"560 B", views:"—", created:"1d ago", lock:true},
|
||||
];
|
||||
const langs = ["go","py","txt","js","sh","yaml","md","sql","rs","c"];
|
||||
const TOTAL = 2431, PER_PAGE = 7;
|
||||
const PAGES = Math.ceil(TOTAL / PER_PAGE); // 348
|
||||
// deterministic pseudo-random filler so pages look stable between renders
|
||||
function filler(i) {
|
||||
const lang = langs[(i * 7 + 3) % langs.length];
|
||||
const locked = i % 11 === 0;
|
||||
const slug = locked ? String(100000 + (i * 3719) % 899999).slice(0,6) : genSlugDemo(i);
|
||||
return {
|
||||
slug,
|
||||
href: locked ? "/unlock/index.html" : "/paste/index.html",
|
||||
desc: ["scratch notes","config diff","error log","snippet dump","test output","benchmark results","query plan","draft changelog"][i % 8] + " #" + (i+1),
|
||||
lang, locked,
|
||||
size: (0.2 + (i % 40) / 10).toFixed(1) + " KB",
|
||||
views: locked ? "—" : String((i * 13) % 97),
|
||||
created: (i % 24) + "h ago",
|
||||
};
|
||||
}
|
||||
function genSlugDemo(i) {
|
||||
const a = "23456789abcdefghjkmnpqrstuvwxyz";
|
||||
let s = "";
|
||||
let x = i * 2654435761 % 4294967296;
|
||||
for (let j = 0; j < 6; j++) { s += a[x % a.length]; x = Math.floor(x / a.length) + 7; }
|
||||
return s;
|
||||
}
|
||||
const data = [...real];
|
||||
for (let i = real.length; i < TOTAL; i++) data.push(filler(i));
|
||||
|
||||
let page = 1;
|
||||
const rowsEl = document.getElementById('rows');
|
||||
const pgEl = document.getElementById('pg');
|
||||
const showingEl = document.getElementById('showing');
|
||||
const countEl = document.getElementById('count');
|
||||
const filterEl = document.getElementById('filter');
|
||||
|
||||
function filtered() {
|
||||
const q = filterEl.value.trim().toLowerCase();
|
||||
if (!q) return data;
|
||||
return data.filter(d => d.slug.toLowerCase().includes(q) || d.lang.includes(q) || d.desc.toLowerCase().includes(q));
|
||||
}
|
||||
|
||||
function render() {
|
||||
const all = filtered();
|
||||
const totalPages = Math.max(1, Math.ceil(all.length / PER_PAGE));
|
||||
if (page > totalPages) page = totalPages;
|
||||
const start = (page - 1) * PER_PAGE;
|
||||
const slice = all.slice(start, start + PER_PAGE);
|
||||
|
||||
if (slice.length === 0) {
|
||||
rowsEl.innerHTML = '';
|
||||
document.getElementById('empty').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('empty').style.display = 'none';
|
||||
rowsEl.innerHTML = slice.map(d =>
|
||||
`<tr class="row"><td><a class="slug" href="${d.href}">${d.slug}</a>${d.lock ? '<span class="badge lock">🔒</span>' : ''}</td>` +
|
||||
`<td class="title-cell">${d.desc}</td>` +
|
||||
`<td><span class="badge">${d.lang}</span></td>` +
|
||||
`<td class="dim">${d.size}</td><td class="dim">${d.views}</td><td class="dim">${d.created}</td></tr>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
countEl.textContent = all.length.toLocaleString() + " total";
|
||||
const lo = all.length === 0 ? 0 : start + 1;
|
||||
const hi = Math.min(start + PER_PAGE, all.length);
|
||||
showingEl.textContent = `Showing ${lo}–${hi} of ${all.length.toLocaleString()} · page ${page} of ${totalPages}`;
|
||||
|
||||
// pager: 1, prev-1, [cur], next+1, last, plus ellipses
|
||||
const btns = [];
|
||||
const add = (label, target, opts={}) => btns.push(`<button ${opts.on?'class="on"':''} ${opts.dis?'disabled':''} data-p="${target}">${label}</button>`);
|
||||
add('‹', page-1, {dis: page===1});
|
||||
const window = new Set([1, 2, page-1, page, page+1, totalPages]);
|
||||
let last = 0;
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
if (window.has(i)) {
|
||||
if (last && i - last > 1) btns.push('<span class="dim">…</span>');
|
||||
add(String(i), i, {on: i===page});
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
add('›', page+1, {dis: page===totalPages});
|
||||
pgEl.innerHTML = btns.join('');
|
||||
}
|
||||
|
||||
pgEl.addEventListener('click', e => {
|
||||
const b = e.target.closest('button[data-p]');
|
||||
if (!b || b.disabled) return;
|
||||
page = parseInt(b.dataset.p);
|
||||
render();
|
||||
});
|
||||
filterEl.addEventListener('input', () => { page = 1; render(); });
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user