bba1df32cb
git-subtree-dir: cms/core git-subtree-split: 59b9e2075ac48cf017db38009d54dbe56a98fffc
84 lines
3.5 KiB
JavaScript
84 lines
3.5 KiB
JavaScript
import { Hono } from 'hono';
|
|
import { execFile } from 'node:child_process';
|
|
import { promisify } from 'node:util';
|
|
import matter from 'gray-matter';
|
|
import { marked } from 'marked';
|
|
import { safeRel } from '../files.js';
|
|
|
|
// ÖFFENTLICH: Versionsverlauf eines Library-Beitrags aus der Git-History.
|
|
// Der Container hat das Repo unter /site gemountet + git installiert. Wir
|
|
// holen alte Fassungen on-demand (kein Vorbauen) und zeigen sie auf der Site.
|
|
const execFileP = promisify(execFile);
|
|
const SITE_DIR = process.env.SITE_DIR || '/site';
|
|
const git = (...args) => execFileP('git', ['-C', SITE_DIR, ...args], { maxBuffer: 10 * 1024 * 1024 });
|
|
const US = '\x1f'; // Feldtrenner (Unit Separator) — kommt in Commit-Daten nicht vor.
|
|
|
|
const history = new Hono();
|
|
|
|
// Liste der Versionen: neueste zuerst.
|
|
history.get('/', async (c) => {
|
|
let rel;
|
|
try { rel = safeRel(c.req.query('path')); } catch { return c.json({ error: 'Ungültiger Pfad' }, 400); }
|
|
try {
|
|
const { stdout } = await git(
|
|
'log', '--follow', `--format=%H${US}%h${US}%aI${US}%an${US}%s`, '--', `content/${rel}`);
|
|
const versions = stdout.trim().split('\n').filter(Boolean).map((line) => {
|
|
const [rev, short, date, author, subject] = line.split(US);
|
|
return { rev, short, date, author, subject };
|
|
});
|
|
return c.json(versions);
|
|
} catch { return c.json({ error: 'Verlauf nicht verfügbar' }, 500); }
|
|
});
|
|
|
|
// Eine bestimmte Fassung gerendert (HTML), zum Anzeigen auf der Seite.
|
|
history.get('/version', 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', `${rev}:content/${rel}`);
|
|
const { data, content } = matter(stdout);
|
|
return c.json({
|
|
rev,
|
|
title: data.title || '',
|
|
date: data.date ? new Date(data.date).toISOString().slice(0, 10) : null,
|
|
html: renderMarkdown(content),
|
|
});
|
|
} 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).
|
|
function renderMarkdown(md) {
|
|
const defs = {}; const order = [];
|
|
md = md.replace(/^\[\^([^\]]+)\]:[ \t]*(.*)$/gm, (_, id, txt) => { defs[id] = txt; return ''; });
|
|
md = md.replace(/\[\^([^\]]+)\]/g, (_, id) => {
|
|
if (!order.includes(id)) order.push(id);
|
|
return `<sup class="footnote-ref">${order.indexOf(id) + 1}</sup>`;
|
|
});
|
|
let html = marked.parse(md);
|
|
if (order.length) {
|
|
html += '<div class="footnotes"><ol>'
|
|
+ order.map((id) => `<li>${marked.parseInline(defs[id] || '')}</li>`).join('')
|
|
+ '</ol></div>';
|
|
}
|
|
return html;
|
|
}
|
|
|
|
export default history;
|