Files

208 lines
9.9 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Public history — Palette</title>
<link rel="stylesheet" href="../themes/tokens.css">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--font-body);
background: var(--bg);
color: var(--fg);
line-height: 1.45;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
font-size: 14px;
}
.topbar {
display: flex; align-items: center; gap: 20px;
padding: 0 20px; height: 52px;
background: var(--surface); border-bottom: 1px solid var(--border);
}
.logo { font-weight: 700; font-size: 16px; letter-spacing: -0.02em; text-decoration: none; color: var(--fg); }
.logo em { font-style: normal; color: var(--muted-fg); font-weight: 400; }
.topbar nav { display: flex; gap: 4px; }
.topbar nav a { color: var(--muted-fg); text-decoration: none; padding: 6px 12px; border-radius: 8px; font-size: 13.5px; }
.topbar nav a:hover { background: var(--surface-2); color: var(--fg); }
.topbar nav a.on { background: var(--accent); color: var(--bg); }
.topbar .spacer { flex: 1; }
.deck { max-width: 860px; margin: 0 auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
.head-row { display: flex; align-items: baseline; gap: 14px; }
h1 { font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
.count { font-size: 12.5px; color: var(--muted-fg); }
.head-row .spacer { flex: 1; }
.search {
display: flex; align-items: center; gap: 8px; background: var(--surface);
border: 1px solid var(--border); border-radius: 10px; padding: 8px 14px; width: 260px;
}
.search input { border: none; outline: none; background: transparent; color: var(--fg); font: inherit; font-size: 13px; width: 100%; }
.search input::placeholder { color: var(--muted); }
.float {
background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
box-shadow: 0 2px 6px rgba(20,14,32,.25), 0 12px 32px rgba(20,14,32,.3);
overflow: hidden;
}
table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; }
th:nth-child(1), td:nth-child(1) { width: 130px; }
th:nth-child(2), td:nth-child(2) { width: auto; }
th:nth-child(3), td:nth-child(3) { width: 80px; }
th:nth-child(4), td:nth-child(4) { width: 80px; }
th:nth-child(5), td:nth-child(5) { width: 64px; }
th:nth-child(6), td:nth-child(6) { width: 90px; }
th {
text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
color: var(--muted-fg); padding: 10px 16px; border-bottom: 1px solid var(--border); font-weight: 600;
}
td { padding: 10px 16px; border-bottom: 1px solid var(--border); }
tr:last-child td { border-bottom: none; }
tr.row:hover td { background: var(--surface-2); }
td a.slug { font-family: var(--font-mono); font-size: 12.5px; color: var(--fg); text-decoration: none; }
td a.slug:hover { color: var(--accent); }
.title-cell { color: var(--muted-fg); max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.badge { font-size: 11px; border: 1px solid var(--border); color: var(--muted-fg); border-radius: 999px; padding: 1px 8px; margin-right: 4px; white-space: nowrap; }
.badge.lock { color: var(--accent); border-color: var(--accent); }
.dim { color: var(--muted-fg); white-space: nowrap; }
.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="/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" id="count"></span>
<div class="spacer"></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 id="rows"></tbody>
</table>
<div class="empty" id="empty" style="display:none">No pastes match that filter.</div>
<div class="pager">
<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>