'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(null); const [err, setErr] = useState(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
Changelog konnte nicht geladen werden: {err}
; } if (!data) { return
Changelog wird geladen…
; } return ( {data.map((entry) => (
    {entry.items.map((it, idx) => (
  • {/* HTML aus XML (z. B. …) ist erlaubt, da Datei aus eigenem Repo kommt */} {it.sub && it.sub.length > 0 && (
      {it.sub.map((s, i) => (
    • {s}
    • ))}
    )}
  • ))}
))}
); }