import { useCallback, useEffect, useState } from 'react'; import { apiGet, apiDelete } from '../api.js'; import { Markdown } from '../markdown.jsx'; import Lightbox from '../components/Lightbox.jsx'; import { SkeletonCards } from '../components/Skeleton.jsx'; const dateFmt = new Intl.DateTimeFormat('de-DE', { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric', }); const monthFmt = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' }); /** Relative Angabe wie „vor 3 Tagen" für die Karten-Ecke */ function relative(iso) { const days = Math.floor((Date.now() - new Date(iso).getTime()) / 86400000); if (days <= 0) return 'heute'; if (days === 1) return 'gestern'; if (days < 30) return `vor ${days} Tagen`; const months = Math.floor(days / 30); return months === 1 ? 'vor 1 Monat' : `vor ${months} Monaten`; } /** * Devlog-Inhalt in Karten-Teile zerlegen: * - Embed-Titel („Devlog — Projekt") → Projekt-Chip statt Fließtext * - Abschließende Kursiv-Zeile („*X Commits heute …*") → abgesetzte Fußzeile */ export function splitContent(raw) { const blocks = raw.split(/\n{2,}/); let project = null; let footer = null; const title = blocks[0]?.match(/^Devlog\s*[—–-]\s*(.+)$/i); if (title) { project = title[1].trim(); blocks.shift(); } const last = blocks[blocks.length - 1]; if (blocks.length > 1 && /^\*[^*]+\*$/s.test(last?.trim() ?? '')) { footer = last.trim().slice(1, -1); blocks.pop(); } return { project, footer, body: blocks.join('\n\n') }; } export default function Devlogs({ me }) { const [data, setData] = useState(null); const [page, setPage] = useState(1); const [error, setError] = useState(false); const [qInput, setQInput] = useState(''); const [q, setQ] = useState(''); const [lightbox, setLightbox] = useState(null); // { images, start } // Sucheingabe entprellen (300 ms), bei neuer Suche zurück auf Seite 1 useEffect(() => { const t = setTimeout(() => { setQ(qInput.trim()); setPage(1); }, 300); return () => clearTimeout(t); }, [qInput]); const load = useCallback(() => { setError(false); const search = q ? `&q=${encodeURIComponent(q)}` : ''; apiGet(`/api/devlogs?page=${page}${search}`).then(setData).catch(() => setError(true)); }, [page, q]); useEffect(() => { load(); window.scrollTo(0, 0); }, [load]); // Admin: Devlog aus dem Archiv entfernen (löscht auch die lokalen Bilder) async function remove(id) { if (!window.confirm('Dieses Devlog aus dem Archiv löschen?')) return; try { await apiDelete(`/api/devlogs/${id}`); load(); } catch { window.alert('Löschen fehlgeschlagen.'); } } const pages = data ? Math.ceil(data.total / data.pageSize) : 0; // Monats-Trenner in der Timeline let lastMonth = null; return ( <>
DEVLOG
setQInput(e.target.value)} /> {q && data && {data.total} Treffer}
{error &&

Devlogs konnten nicht geladen werden.

} {!error && !data && } {data?.total === 0 && (

{q ? `Keine Treffer für „${q}".` : 'Noch keine Devlogs archiviert.'}

)} {data?.total > 0 && (
{data.items.map((d) => { const { project, footer, body } = splitContent(d.content); const month = monthFmt.format(new Date(d.posted_at)); const showMonth = month !== lastMonth; lastMonth = month; return (
{showMonth &&
{month}
}
); })}
)} {lightbox && ( setLightbox(null)} /> )} {pages > 1 && (
{String(page).padStart(2, '0')} / {String(pages).padStart(2, '0')}
)}
); }