Files
palette/web/templates/history.html
T
poslop e31aa44ecb
CI / test (push) Successful in 21s
CI / docker (push) Skipped
History: paste name under Paste column, drop Description column (#43)
2026-09-08 21:32:10 -05:00

131 lines
4.8 KiB
HTML
Raw 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.
{{template "head" .}}
{{template "topbar" .}}
<div class="page">
<div class="head-row">
<h1>Public pastes</h1>
<span class="count" id="count"></span>
</div>
<div class="search"><input id="filter" placeholder="Search…"><span class="search-spinner" id="search-spinner"></span></div>
<div class="float">
<table>
<colgroup><col style="width:300px"><col style="width:156px"><col style="width:138px"><col style="width:111px"><col style="width:165px"></colgroup>
<thead><tr><th>Paste</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 yet. Create the first one.</div>
</div>
<div class="pager float">
<span id="showing"></span>
<div class="pg" id="pg"></div>
</div>
</div>
<script>
const PER = 25;
let page = 1, total = 0;
const $ = id => document.getElementById(id);
function esc(s) { const d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; }
function fmtSize(n) { if (n == null) return 'none'; if (n < 1024) return n + ' B'; if (n < 1048576) return (n/1024).toFixed(1) + ' KB'; return (n/1048576).toFixed(1) + ' MB'; }
function ago(ts) {
const s = Math.floor(Date.now()/1000) - ts;
if (s < 60) return s + 's ago';
if (s < 3600) return Math.floor(s/60) + 'm ago';
if (s < 86400) return Math.floor(s/3600) + 'h ago';
return Math.floor(s/86400) + 'd ago';
}
let filter = '';
function matchesFilter(it) {
if (!filter) return true;
const f = filter.toLowerCase();
return (it.title || '').toLowerCase().includes(f) || (it.id || '').toLowerCase().includes(f);
}
async function load() {
const spinner = document.getElementById('search-spinner');
spinner.style.visibility = 'visible';
try {
const filtered = filter.length > 0;
const off = (page - 1) * PER;
const url = filtered
? '/api/public?limit=200&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;
$('count').textContent = filtered
? items.length.toLocaleString() + ' matches (of ' + total.toLocaleString() + ' total)'
: total.toLocaleString() + ' total';
const rows = $('rows');
if (items.length === 0) {
rows.innerHTML = '';
$('empty').style.display = 'block';
$('empty').textContent = filtered ? 'No pastes match your search.' : 'No pastes yet. Create the first one.';
} else {
$('empty').style.display = 'none';
rows.innerHTML = items.map(it =>
`<tr class="row" data-href="/${esc(it.id)}"><td><a class="slug" href="/${esc(it.id)}">${esc(it.id)}</a>` +
(it.title ? `<div class="paste-sub">${esc(it.title)}</div>` : `<div class="paste-sub dim">none</div>`) + `</td>` +
`<td><span class="badge">${esc(it.language || 'text')}</span></td>` +
`<td class="dim">${fmtSize(it.size)}</td><td class="dim">${it.view_count}</td><td class="dim">${ago(it.created_at)}</td></tr>`
).join('');
}
const pages = Math.max(1, Math.ceil(total / PER));
if (filtered) {
$('showing').textContent = 'Showing ' + items.length.toLocaleString() + ' matches for "' + filter + '"';
$('pg').innerHTML = '';
} else {
$('showing').textContent = total === 0 ? 'Nothing here yet' :
`Showing ${off+1}${Math.min(off+PER, total)} of ${total.toLocaleString()} · page ${page} of ${pages}`;
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 win = new Set([1, 2, page-1, page, page+1, pages]);
let last = 0;
for (let i = 1; i <= pages; i++) {
if (win.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===pages});
$('pg').innerHTML = btns.join('');
}
} finally {
spinner.style.visibility = 'hidden';
}
}
$('pg').addEventListener('click', e => {
const b = e.target.closest('button[data-p]');
if (!b || b.disabled) return;
page = parseInt(b.dataset.p);
load();
window.scrollTo(0, 0);
});
$('rows').addEventListener('click', e => {
const tr = e.target.closest('tr.row[data-href]');
if (!tr || e.target.closest('a')) return; // let real links (e.g. middle-click) work
window.location.href = tr.dataset.href;
});
let filterTimer = null;
$('filter').addEventListener('input', e => {
clearTimeout(filterTimer);
filterTimer = setTimeout(() => {
filter = e.target.value.trim();
page = 1;
load();
}, 200);
});
load();
setInterval(load, 30000); // auto-refresh history every 30s
</script>
{{template "foot" .}}