Devlog-Seite: Bildergalerie, Projekt-Chips, Monats-Trenner
- Bild-Attachments werden beim Archivieren lokal gespeichert (Discord-CDN-URLs laufen ab) und unter /devlog-assets/ ausgeliefert; DB-Migration: images-Spalte - Frontend: Bildergalerie im Grid (responsive nach Anzahl), Embed-Titel als Projekt-Chip, Commit-Fußzeile abgesetzt, Monats-Trenner in der Timeline - Backfill lädt Bilder alter Devlogs nach (Dedupe verhindert Doppel-Downloads) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { Markdown } from '../markdown.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) {
|
||||
@@ -16,6 +17,29 @@ function relative(iso) {
|
||||
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
|
||||
*/
|
||||
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() {
|
||||
const [data, setData] = useState(null);
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -29,6 +53,9 @@ export default function Devlogs() {
|
||||
|
||||
const pages = data ? Math.ceil(data.total / data.pageSize) : 0;
|
||||
|
||||
// Monats-Trenner in der Timeline
|
||||
let lastMonth = null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="page-header">
|
||||
@@ -50,19 +77,43 @@ export default function Devlogs() {
|
||||
|
||||
{data?.total > 0 && (
|
||||
<div className="timeline">
|
||||
{data.items.map((d) => (
|
||||
<article className="card" key={d.message_id}>
|
||||
<div className="card-meta">
|
||||
<span className="card-date">
|
||||
{dateFmt.format(new Date(d.posted_at))}
|
||||
</span>
|
||||
<span className="card-rel">{relative(d.posted_at)}</span>
|
||||
{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 (
|
||||
<div key={d.message_id}>
|
||||
{showMonth && <div className="month-sep">{month}</div>}
|
||||
<article className="card">
|
||||
<div className="card-meta">
|
||||
<span className="card-date">
|
||||
{dateFmt.format(new Date(d.posted_at))}
|
||||
</span>
|
||||
{project && <span className="card-project">{project}</span>}
|
||||
<span className="card-rel">{relative(d.posted_at)}</span>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<Markdown text={body} />
|
||||
</div>
|
||||
{d.images?.length > 0 && (
|
||||
<div className={`card-images n${Math.min(d.images.length, 4)}`}>
|
||||
{d.images.map((src) => (
|
||||
<a key={src} href={src} target="_blank" rel="noreferrer">
|
||||
<img src={src} alt="Devlog-Screenshot" loading="lazy" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{footer && (
|
||||
<div className="card-footer">
|
||||
<Markdown text={footer} />
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<Markdown text={d.content} />
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user