Files
reflector-dashboard/lib/changelog.js
T
bjorneandClaude Fable 5.1 25b481e2ae Public web-only release: SvxPortal front-end + auth backend, install templates and INSTALL.md
Stripped from the internal repo: per-box deploy trees, Go services (nodelist/astmeta/proxy/svxdr),
deploy/sync scripts and all secrets. Config templates for nginx, PHP-FPM, Redis and tmpfiles
live in install/ with CHANGEME placeholders.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 23:07:05 +02:00

81 lines
4.2 KiB
JavaScript
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.
// changelog.js — shared "recent updates" viewer (dmr-web, svx-web).
// Canonical copy: callmgr2/www-mumien/lib/changelog.js; other repos carry a COPY.
// Reads changelog.json (newest first, {date, repo, hash, type, summary}) and
// renders a client-side paginated list. Needs lib/fmt.js (escHtml).
//
// const ch = changelogViewer({ body: 'chBody', pager: 'chPager', fetchAt: 'chFetchAt',
// url: 'changelog.json', perPage: 6 });
// ch.load(); // fetch once, render; later calls just re-render
(function () {
'use strict';
function changelogViewer(opts) {
opts = opts || {};
const ids = { body: opts.body || 'chBody', pager: opts.pager || 'chPager', fetchAt: opts.fetchAt || 'chFetchAt' };
const url = opts.url || 'changelog.json';
const perPage = opts.perPage || 6;
const $ = id => document.getElementById(id);
let entries = null, page = 0, wired = false;
function typeBadge(t) {
const m = { feature: ['Added', 'bg-success'], fix: ['Fix', 'bg-danger'],
change: ['Change', 'bg-info text-dark'] };
const x = m[String(t || '').toLowerCase()] || [t || '—', 'bg-secondary'];
return `<span class="badge ${x[1]}" style="font-size:9px;">${escHtml(x[0])}</span>`;
}
function render() {
const body = $(ids.body), pager = $(ids.pager);
if (!body || !entries) return;
const n = entries.length, pages = Math.max(1, Math.ceil(n / perPage));
page = Math.min(Math.max(0, page), pages - 1);
const start = page * perPage;
const rows = entries.slice(start, start + perPage).map(e => `
<div class="d-flex align-items-start gap-2 py-1" style="border-bottom:1px solid rgba(128,128,128,.15);">
<span class="text-secondary mono" style="min-width:78px;">${escHtml(e.date || '')}</span>
${typeBadge(e.type)}
<span class="flex-grow-1">${escHtml(e.summary || '')}</span>
<span class="text-secondary mono" style="font-size:10px;" title="${escHtml(e.repo || '')} ${escHtml(e.hash || '')}">${escHtml(e.repo || '')} ${escHtml(e.hash || '')}</span>
</div>`).join('');
body.innerHTML = rows || '<div class="text-secondary">No entries.</div>';
if (!pager) return;
if (pages <= 1) { pager.innerHTML = ''; return; }
const btn = (label, p, dis, act) =>
`<button class="btn btn-sm ${act ? 'btn-primary' : 'btn-outline-secondary'}" style="padding:1px 8px;" ${dis ? 'disabled' : ''} data-chpage="${p}">${label}</button>`;
let h = btn('«', 0, page === 0) + btn('‹', page - 1, page === 0);
const lo = Math.max(0, page - 2), hi = Math.min(pages - 1, page + 2);
for (let p = lo; p <= hi; p++) h += btn(String(p + 1), p, false, p === page);
h += btn('›', page + 1, page === pages - 1) + btn('»', pages - 1, page === pages - 1);
h += `<select class="form-select form-select-sm" style="width:auto;padding:1px 6px;" id="chPageSel" title="Jump to page">` +
Array.from({ length: pages }, (_, p) => `<option value="${p}" ${p === page ? 'selected' : ''}>${p + 1}/${pages}</option>`).join('') + `</select>`;
pager.innerHTML = h;
}
async function load() {
if (!wired) {
wired = true;
const pager = $(ids.pager);
if (pager) {
pager.addEventListener('click', ev => {
const b = ev.target.closest('[data-chpage]'); if (!b) return;
page = parseInt(b.dataset.chpage, 10) || 0; render();
});
pager.addEventListener('change', ev => {
if (ev.target && ev.target.id === 'chPageSel') { page = parseInt(ev.target.value, 10) || 0; render(); }
});
}
}
if (entries) { render(); return; }
try {
const r = await fetch(url + '?_=' + Date.now(), { credentials: 'omit' });
const j = await r.json();
entries = Array.isArray(j) ? j : (j.entries || []);
const fa = $(ids.fetchAt);
if (fa && entries[0]) fa.textContent = 'latest ' + (entries[0].date || '');
render();
} catch (_) {
const b = $(ids.body); if (b) b.innerHTML = '<div class="text-secondary">Changelog unavailable.</div>';
}
}
return { load, render };
}
window.changelogViewer = changelogViewer;
})();