feature: Versionsverlauf + Diff (rot/grün) komplett auf der Seite
- API: /api/history/diff liefert den Unified-Diff einer Fassung (git show) - „Version vom <Datum>"-Pill (statt Marke oben + Git-Links) öffnet den Verlauf direkt auf openbureau: Liste der Fassungen → Diff rot/grün wie auf GitHub, Toggle „ganze Fassung anzeigen", Rücksprung. Keine externen Git-Links mehr. - Pills neu: Dialog (Akzent-Outline, wie zuvor) + Version/Zitieren im Tag-Look - CSS: Tag-Pills, Diff-Styling (d-add/d-del/d-hunk), alte Badge-Styles raus Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+46
-31
@@ -554,19 +554,20 @@ a.byline-author:hover, a.journal-author:hover { color: var(--accent); }
|
||||
|
||||
/* ── Dialog ───────────────────────────────────────────────────────────────── */
|
||||
/* Link am Ende des Beitrags (der Beitrag selbst bleibt sauber) */
|
||||
/* Dialog-Pill in der Provenance-Reihe: gleiche Form wie die anderen, sticht
|
||||
aber durch Pfeil-Symbol + Akzentfarbe (Rahmen/Text) hervor. */
|
||||
/* Dialog-Pill: Akzent-Outline + Pfeil — sticht neben den gefüllten Tag-Pills
|
||||
hervor (wie der frühere Dialog-Link). */
|
||||
.prov-dialog {
|
||||
display: inline-block;
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: 600;
|
||||
font-family: var(--font-family-display);
|
||||
font-weight: 500;
|
||||
font-size: 0.82rem;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 999px;
|
||||
padding: 0.12em 0.7em;
|
||||
padding: 0.28em 0.85em;
|
||||
}
|
||||
.prov-dialog:hover { background: var(--accent); color: #fff; }
|
||||
|
||||
@@ -1486,22 +1487,30 @@ img:hover { filter: grayscale(0%); }
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
/* Version / Verlauf / Zitieren als dezente Outline-Pills (wie die Versions-Marke). */
|
||||
.provenance a,
|
||||
/* Version/Zitieren im Tag-Look (gefüllt, weich); Dialog separat (Akzent-Outline). */
|
||||
.prov-version,
|
||||
.prov-cite {
|
||||
display: inline-block;
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-text-muted);
|
||||
background: none;
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 0.35rem 0.85rem;
|
||||
line-height: 1.3;
|
||||
font-family: var(--font-family-display);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: lowercase;
|
||||
color: var(--color-text-primary);
|
||||
background: color-mix(in oklab, var(--section-color, var(--accent)) 14%, transparent);
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
padding: 0.12em 0.7em;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.provenance a:hover,
|
||||
.prov-cite:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.prov-version:hover,
|
||||
.prov-cite:hover,
|
||||
.prov-version[aria-expanded="true"],
|
||||
.prov-cite[aria-expanded="true"] {
|
||||
background: color-mix(in oklab, var(--section-color, var(--accent)) 30%, transparent);
|
||||
}
|
||||
|
||||
/* Aufklappendes Zitat-Panel: zeigt die Quellenangabe lesbar + Kopieren. */
|
||||
.prov-citation {
|
||||
@@ -1539,20 +1548,6 @@ img:hover { filter: grayscale(0%); }
|
||||
/* ------------------------------------------------------------------------
|
||||
Versions-Marke + Verlauf — alte Fassungen direkt auf der Seite anzeigen.
|
||||
------------------------------------------------------------------------ */
|
||||
.version-line { margin: 0.5rem 0 0; }
|
||||
.version-badge {
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-text-muted);
|
||||
background: none;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 999px;
|
||||
padding: 0.12em 0.7em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.version-badge:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.version-badge[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
|
||||
|
||||
.version-panel {
|
||||
margin-top: 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
@@ -1594,7 +1589,8 @@ img:hover { filter: grayscale(0%); }
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.version-back {
|
||||
.version-back,
|
||||
.version-toggle {
|
||||
font: inherit;
|
||||
color: var(--accent);
|
||||
background: none;
|
||||
@@ -1603,3 +1599,22 @@ img:hover { filter: grayscale(0%); }
|
||||
cursor: pointer;
|
||||
}
|
||||
.version-loading { color: var(--color-text-muted); font-style: italic; }
|
||||
|
||||
/* Diff-Ansicht (rot/grün, wie auf GitHub) — direkt auf der Seite. */
|
||||
.diff {
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.diff-line {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
padding: 0.05em 0.7em;
|
||||
}
|
||||
.diff-line.d-add { background: color-mix(in oklab, #2ea043 20%, transparent); }
|
||||
.diff-line.d-del { background: color-mix(in oklab, #f85149 20%, transparent); }
|
||||
.diff-line.d-hunk { color: var(--color-text-muted); background: var(--color-bg-secondary); }
|
||||
.diff-line.d-ctx { color: var(--color-text-primary); }
|
||||
|
||||
@@ -48,6 +48,19 @@ history.get('/version', async (c) => {
|
||||
} catch { return c.json({ error: 'Version nicht gefunden' }, 404); }
|
||||
});
|
||||
|
||||
// Unified-Diff einer Fassung (was dieser Commit an der Datei geändert hat) —
|
||||
// fürs rot/grün-Diff auf der Seite. Roh-Diff; das Frontend färbt +/- ein.
|
||||
history.get('/diff', async (c) => {
|
||||
let rel;
|
||||
try { rel = safeRel(c.req.query('path')); } catch { return c.json({ error: 'Ungültiger Pfad' }, 400); }
|
||||
const rev = c.req.query('rev') || '';
|
||||
if (!/^[0-9a-f]{7,40}$/i.test(rev)) return c.json({ error: 'Ungültige Version' }, 400);
|
||||
try {
|
||||
const { stdout } = await git('show', '--format=', '--no-color', rev, '--', `content/${rel}`);
|
||||
return c.json({ rev, diff: stdout });
|
||||
} catch { return c.json({ error: 'Diff nicht verfügbar' }, 404); }
|
||||
});
|
||||
|
||||
// Markdown → HTML. marked kennt Goldmarks Fußnoten ([^id]) nicht — daher
|
||||
// vorab: Definitionen einsammeln, Verweise zu <sup>-Nummern, „Quellen" anhängen
|
||||
// (greift dieselbe .footnotes-CSS wie die Live-Seite).
|
||||
|
||||
@@ -44,15 +44,6 @@
|
||||
</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Versions-Marke: macht bewusst, dass dies eine bestimmte Fassung ist,
|
||||
und öffnet den Verlauf (alte Fassungen direkt auf der Seite). */}}
|
||||
{{ if and (eq .Section "library") .GitInfo }}
|
||||
<p class="version-line">
|
||||
<button type="button" id="version-badge" class="version-badge" aria-expanded="false"
|
||||
data-path="{{ .File.Path }}">Version {{ .GitInfo.AbbreviatedHash }}</button>
|
||||
</p>
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
{{/* Table of Contents */}}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
{{/* Herkunft eines Beitrags: Version (→ Commit), Verlauf, Zitieren.
|
||||
Version/Verlauf nur, wenn Git-Info da ist (enableGitInfo) und repoURL gesetzt.
|
||||
„Zitieren" kopiert eine Quellenangabe in die Zwischenablage. */}}
|
||||
{{ $repo := site.Params.repoURL }}
|
||||
{{ $author := .Params.author | default site.Params.author.name }}
|
||||
<div class="provenance" aria-label="Herkunft">
|
||||
{{/* Dialog: gleiche Pill-Reihe, sticht durch Pfeil + Akzentfarbe hervor. */}}
|
||||
<a class="prov-dialog" id="dialog-link" data-thread="{{ .RelPermalink }}" href="/dialog/?thread={{ .RelPermalink }}">→ Dialog</a>
|
||||
{{ with .GitInfo }}
|
||||
{{ with $repo }}
|
||||
<a class="prov-version" href="{{ . }}/commit/{{ $.GitInfo.Hash }}" title="Diese Version als Commit ansehen">Version {{ $.GitInfo.AbbreviatedHash }}</a>
|
||||
<a class="prov-history" href="{{ . }}/commits/branch/main/content/{{ $.File.Path }}">Verlauf</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{/* „Version vom <Datum>" öffnet den Verlauf direkt auf der Seite (Diff rot/grün). */}}
|
||||
<button type="button" class="prov-version" id="version-badge" aria-expanded="false"
|
||||
data-path="{{ .File.Path }}">Version vom {{ .Lastmod.Format "02.01.2006" }}</button>
|
||||
<button type="button" class="prov-cite" aria-expanded="false"
|
||||
data-title="{{ .Title }}"
|
||||
data-author="{{ $author }}"
|
||||
|
||||
+60
-24
@@ -1,55 +1,90 @@
|
||||
/* 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. */
|
||||
Die Pill „Version vom …" öffnet die Liste der Fassungen (aus /api/history).
|
||||
Auswahl zeigt standardmäßig den Diff (rot/grün, wie auf GitHub) aus
|
||||
/api/history/diff; ein Toggle zeigt die ganze alte Fassung (/api/history/version).
|
||||
Alles auf openbureau — keine externen Git-Links. */
|
||||
(function () {
|
||||
var badge = document.getElementById('version-badge');
|
||||
if (!badge) return;
|
||||
var path = badge.dataset.path;
|
||||
var trigger = document.getElementById('version-badge');
|
||||
if (!trigger) return;
|
||||
var path = trigger.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 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 q(p, rev) { return p + '?path=' + encodeURIComponent(path) + '&rev=' + encodeURIComponent(rev); }
|
||||
function toTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); }
|
||||
|
||||
function closePanel() {
|
||||
if (panel) { panel.remove(); panel = null; }
|
||||
badge.setAttribute('aria-expanded', 'false');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
function restore() {
|
||||
content.innerHTML = originalHTML;
|
||||
badge.textContent = originalLabel;
|
||||
if (banner) { banner.remove(); banner = null; }
|
||||
}
|
||||
function showBanner(v) {
|
||||
|
||||
// Unified-Diff → farbige Zeilen (rot gelöscht, grün neu, grau Hunk/Kontext).
|
||||
function renderDiff(diff) {
|
||||
var box = document.createElement('div');
|
||||
box.className = 'diff';
|
||||
diff.split('\n').forEach(function (ln) {
|
||||
if (/^(diff --git|index |new file|deleted file|similarity |rename |--- |\+\+\+ )/.test(ln)) return;
|
||||
var cls = 'd-ctx';
|
||||
if (/^@@/.test(ln)) cls = 'd-hunk';
|
||||
else if (ln.charAt(0) === '+') cls = 'd-add';
|
||||
else if (ln.charAt(0) === '-') cls = 'd-del';
|
||||
var row = document.createElement('div');
|
||||
row.className = 'diff-line ' + cls;
|
||||
row.textContent = ln || ' ';
|
||||
box.appendChild(row);
|
||||
});
|
||||
return box;
|
||||
}
|
||||
|
||||
function showBanner(v, mode) {
|
||||
if (banner) banner.remove();
|
||||
banner = document.createElement('div');
|
||||
banner.className = 'version-banner';
|
||||
banner.append('Ältere Fassung vom ' + fmt(v.date) + ' · Version ' + v.short + ' ');
|
||||
banner.append((mode === 'diff' ? 'Änderungen' : 'Fassung') + ' vom ' + fmt(v.date) + ' · Version ' + v.short + ' ');
|
||||
var toggle = document.createElement('button');
|
||||
toggle.type = 'button'; toggle.className = 'version-toggle';
|
||||
toggle.textContent = mode === 'diff' ? 'ganze Fassung anzeigen' : 'Änderungen anzeigen';
|
||||
toggle.addEventListener('click', function () { mode === 'diff' ? loadVersion(v) : loadDiff(v); });
|
||||
var back = document.createElement('button');
|
||||
back.type = 'button'; back.className = 'version-back'; back.textContent = '→ Zur aktuellen Fassung';
|
||||
back.type = 'button'; back.className = 'version-back'; back.textContent = '→ zur aktuellen Fassung';
|
||||
back.addEventListener('click', restore);
|
||||
banner.appendChild(back);
|
||||
banner.append(toggle, ' ', back);
|
||||
article.insertBefore(banner, article.firstChild);
|
||||
}
|
||||
|
||||
function loadDiff(v) {
|
||||
closePanel();
|
||||
content.innerHTML = '<p class="version-loading">Lade Änderungen …</p>';
|
||||
api(q('/api/history/diff', v.rev)).then(function (data) {
|
||||
if (!data || data.diff == null) { restore(); return; }
|
||||
content.innerHTML = '';
|
||||
if (!data.diff.trim()) content.innerHTML = '<p class="version-empty">Keine Änderungen an dieser Datei in dieser Fassung.</p>';
|
||||
else content.appendChild(renderDiff(data.diff));
|
||||
showBanner(v, 'diff');
|
||||
toTop();
|
||||
});
|
||||
}
|
||||
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) {
|
||||
api(q('/api/history/version', 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' });
|
||||
showBanner(v, 'full');
|
||||
toTop();
|
||||
});
|
||||
}
|
||||
|
||||
function openPanel() {
|
||||
api('/api/history?path=' + encodeURIComponent(path)).then(function (list) {
|
||||
panel = document.createElement('div');
|
||||
@@ -68,16 +103,17 @@
|
||||
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); });
|
||||
else b.addEventListener('click', function () { loadDiff(v); });
|
||||
li.appendChild(b);
|
||||
ol.appendChild(li);
|
||||
});
|
||||
panel.appendChild(ol);
|
||||
}
|
||||
badge.parentNode.insertBefore(panel, badge.nextSibling);
|
||||
badge.setAttribute('aria-expanded', 'true');
|
||||
var prov = trigger.closest('.provenance') || trigger;
|
||||
prov.parentNode.insertBefore(panel, prov.nextSibling);
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
badge.addEventListener('click', function () { panel ? closePanel() : openPanel(); });
|
||||
trigger.addEventListener('click', function () { panel ? closePanel() : openPanel(); });
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user