diff --git a/internal/web/static/app.css b/internal/web/static/app.css index 67d0cf8..b856888 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -202,7 +202,8 @@ body { .footnote { display: flex; gap: 20px; padding: 10px 18px; font-size: 20.7px; color: var(--muted-fg); border-top: 1px solid var(--border); flex-wrap: wrap; } /* history */ -.page { max-width: 1200px; margin: 0 auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; } +.page { max-width: 1200px; margin: 0 auto; padding: 20px 20px 28px; display: flex; flex-direction: column; gap: 16px; min-height: calc(100vh - 76px); } +.pager { position: sticky; bottom: 0; margin-top: auto; z-index: 5; } .head-row { display: flex; align-items: baseline; gap: 14px; } .head-row h1 { font-size: 34.5px; font-weight: 600; } .search { diff --git a/internal/web/static/table.js b/internal/web/static/table.js index 7824080..2eb77ba 100644 --- a/internal/web/static/table.js +++ b/internal/web/static/table.js @@ -90,10 +90,31 @@ const PaletteTable = (() => { if (opts.hasPager && pager && showing) { const pages = Math.max(1, Math.ceil(state.total / opts.perPage)); if (filtered || state.sortKey) { - showing.textContent = state.sortKey - ? 'Sorted by ' + state.sortKey + ' (' + (state.sortDir === 1 ? 'ascending' : 'descending') + ') · ' + items.length.toLocaleString() + ' of ' + state.total.toLocaleString() - : 'Showing ' + items.length.toLocaleString() + ' matches for "' + state.filter + '"'; - pager.innerHTML = ''; + // Pagination operates over the FILTERED set (#133): never hide the bar. + const filtPages = Math.max(1, Math.ceil(items.length / opts.perPage)); + if (state.page > filtPages) state.page = filtPages; + const fOff = (state.page - 1) * opts.perPage; + const view = items.slice(fOff, fOff + opts.perPage); + rows.innerHTML = view.length ? view.map(opts.rowHtml).join('') : ''; + if (!view.length) { + empty.style.display = 'block'; + empty.textContent = filtered ? opts.emptyFiltered : opts.emptyAll; + } + showing.textContent = `Showing ${view.length === 0 ? 0 : fOff+1}–${fOff+view.length} of ${items.length.toLocaleString()} ${filtered ? 'matches' : 'sorted'} · page ${state.page} of ${filtPages}`; + const btns = []; + const add = (label, target, o={}) => btns.push(``); + add('‹', state.page-1, {dis: state.page===1}); + const win = new Set([1, 2, state.page-1, state.page, state.page+1, filtPages]); + let last = 0; + for (let i = 1; i <= filtPages; i++) { + if (win.has(i)) { + if (last && i - last > 1) btns.push(''); + add(String(i), i, {on: i===state.page}); + last = i; + } + } + add('›', state.page+1, {dis: state.page===filtPages}); + pager.innerHTML = btns.join(''); } else { showing.textContent = state.total === 0 ? 'Nothing here yet' : `Showing ${off+1}–${Math.min(off+opts.perPage, state.total)} of ${state.total.toLocaleString()} · page ${state.page} of ${pages}`; @@ -126,6 +147,7 @@ const PaletteTable = (() => { if (!th) return; const k = th.dataset.sort; if (state.sortKey === k) { state.sortDir = -state.sortDir; } else { state.sortKey = k; state.sortDir = 1; } + state.page = 1; renderSortIndicators(); load(); });