2025-11-10 07:12:06 +01:00

102 lines
3.2 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import TimeLine from './TimeLine';
import TimeLineItem from './TimeLineItem';
type ChangeItem = { html: string; sub?: string[] };
type ChangeEntry = { version: string; dateIso: string; dateLabel: string; items: ChangeItem[] };
function fmtDateDE(iso: string) {
try {
return new Intl.DateTimeFormat('de-DE', { dateStyle: 'short' }).format(new Date(iso));
} catch {
return iso;
}
}
function parseXml(xml: string): ChangeEntry[] {
const doc = new DOMParser().parseFromString(xml, 'text/xml');
const entries = Array.from(doc.getElementsByTagName('entry')).map((e) => {
const version = e.getAttribute('version') ?? '';
const dateIso = e.getAttribute('date') ?? '';
const dateLabel = fmtDateDE(dateIso);
const items = Array.from(e.children)
.filter((c) => c.tagName === 'item')
.map((itemEl) => {
// Haupttext: nur Text- und CDATA-Knoten zusammensetzen (Subitems ausblenden)
const mainText = Array.from(itemEl.childNodes)
.filter((n) => n.nodeType === Node.TEXT_NODE || n.nodeType === Node.CDATA_SECTION_NODE)
.map((n) => n.textContent ?? '')
.join('')
.trim();
const subs = Array.from(itemEl.getElementsByTagName('subitem')).map(
(s) => (s.textContent ?? '').trim()
);
return { html: mainText, sub: subs.length ? subs : undefined };
});
return { version, dateIso, dateLabel, items };
});
// Neueste zuerst
entries.sort((a, b) => new Date(b.dateIso).getTime() - new Date(a.dateIso).getTime());
return entries;
}
export default function Changelog() {
const [data, setData] = useState<ChangeEntry[] | null>(null);
const [err, setErr] = useState<string | null>(null);
useEffect(() => {
let alive = true;
fetch('/changelog.xml', { cache: 'no-cache' })
.then((r) => (r.ok ? r.text() : Promise.reject(r.statusText)))
.then((txt) => {
if (!alive) return;
setData(parseXml(txt));
})
.catch((e) => {
if (!alive) return;
setErr(String(e));
});
return () => {
alive = false;
};
}, []);
if (err) {
return <div className="text-sm text-red-600">Changelog konnte nicht geladen werden: {err}</div>;
}
if (!data) {
return <div className="text-sm text-gray-500">Changelog wird geladen</div>;
}
return (
<TimeLine>
{data.map((entry) => (
<TimeLineItem key={`${entry.version}-${entry.dateIso}`} title={entry.version} date={entry.dateLabel}>
<ul className="list-disc list-inside">
{entry.items.map((it, idx) => (
<li key={idx}>
{/* HTML aus XML (z. B. <u>…</u>) ist erlaubt, da Datei aus eigenem Repo kommt */}
<span dangerouslySetInnerHTML={{ __html: it.html }} />
{it.sub && it.sub.length > 0 && (
<ul className="list-disc list-inside pl-6">
{it.sub.map((s, i) => (
<li key={i}>{s}</li>
))}
</ul>
)}
</li>
))}
</ul>
</TimeLineItem>
))}
</TimeLine>
);
}