feature: alte Versionen direkt auf openbureau anzeigen

- API (öffentlich): /api/history listet Git-Versionen eines Beitrags,
  /api/history/version rendert eine alte Fassung (marked + Fußnoten-Support),
  on-demand via git im CMS-Container — kein Vorbauen. Pfad/rev validiert.
- Versions-Marke neben dem Kopf jedes Library-Beitrags (zeigt bewusst die
  Fassung); öffnet den Verlauf, Auswahl ersetzt den Text + Rücksprung-Banner.
- CSS für Badge/Panel/Banner; marked als Dependency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 01:38:48 +02:00
parent c6f5beaa7b
commit 0ce2c73004
7 changed files with 248 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
/* OPENBUREAU — Versionsverlauf eines Beitrags direkt auf der Seite.
Die Marke „Version xxx" neben dem Kopf öffnet die Liste der Fassungen
(aus /api/history); Auswahl ersetzt den Beitragstext mit der alten Fassung
(aus /api/history/version) und zeigt einen Banner mit Rücksprung. */
(function () {
var badge = document.getElementById('version-badge');
if (!badge) return;
var path = badge.dataset.path;
var content = document.querySelector('.single-content');
var article = document.querySelector('article.single');
if (!path || !content || !article) return;
var originalHTML = content.innerHTML;
var originalLabel = badge.textContent;
var panel = null, banner = null;
function api(p) {
return fetch(p).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; });
}
function fmt(d) { try { return new Date(d).toLocaleDateString('de-CH'); } catch (e) { return d || ''; } }
function closePanel() {
if (panel) { panel.remove(); panel = null; }
badge.setAttribute('aria-expanded', 'false');
}
function restore() {
content.innerHTML = originalHTML;
badge.textContent = originalLabel;
if (banner) { banner.remove(); banner = null; }
}
function showBanner(v) {
if (banner) banner.remove();
banner = document.createElement('div');
banner.className = 'version-banner';
banner.append('Ältere Fassung vom ' + fmt(v.date) + ' · Version ' + v.short + ' ');
var back = document.createElement('button');
back.type = 'button'; back.className = 'version-back'; back.textContent = '→ Zur aktuellen Fassung';
back.addEventListener('click', restore);
banner.appendChild(back);
article.insertBefore(banner, article.firstChild);
}
function loadVersion(v) {
closePanel();
content.innerHTML = '<p class="version-loading">Lade Fassung …</p>';
api('/api/history/version?path=' + encodeURIComponent(path) + '&rev=' + encodeURIComponent(v.rev)).then(function (data) {
if (!data || !data.html) { restore(); return; }
content.innerHTML = data.html;
badge.textContent = 'Version ' + v.short;
showBanner(v);
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
function openPanel() {
api('/api/history?path=' + encodeURIComponent(path)).then(function (list) {
panel = document.createElement('div');
panel.className = 'version-panel';
if (!list || !list.length) {
panel.innerHTML = '<p class="version-empty">Kein Verlauf verfügbar.</p>';
} else {
var ol = document.createElement('ol');
ol.className = 'version-list';
list.forEach(function (v, i) {
var li = document.createElement('li');
var b = document.createElement('button');
b.type = 'button';
var date = document.createElement('span'); date.className = 'v-date'; date.textContent = fmt(v.date);
var subj = document.createElement('span'); subj.className = 'v-subject'; subj.textContent = v.subject || '';
var hash = document.createElement('span'); hash.className = 'v-hash'; hash.textContent = v.short + (i === 0 ? ' · aktuell' : '');
b.append(date, subj, hash);
if (i === 0) b.addEventListener('click', function () { restore(); closePanel(); });
else b.addEventListener('click', function () { loadVersion(v); });
li.appendChild(b);
ol.appendChild(li);
});
panel.appendChild(ol);
}
badge.parentNode.insertBefore(panel, badge.nextSibling);
badge.setAttribute('aria-expanded', 'true');
});
}
badge.addEventListener('click', function () { panel ? closePanel() : openPanel(); });
})();