Devlog-Löschung: Discord-Delete-Sync + Admin-Button im Webinterface

- MessageDelete-Listener (mit Partials): in Discord gelöschte Devlogs verschwinden
  automatisch aus dem Archiv, inkl. lokaler Bilder
- DELETE /api/devlogs/:id (nur Admin) + ✕-Button auf den Karten (nur für Admin sichtbar)
- Anlass: Test-Post mit Commit-Liste von vor der Prosa-Sperre stand noch im Archiv

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 01:16:44 +02:00
co-authored by Claude Opus 4.8
parent bf0eca3546
commit 71d2bb07f1
8 changed files with 97 additions and 12 deletions
+29 -5
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { apiGet } from '../api.js';
import { useCallback, useEffect, useState } from 'react';
import { apiGet, apiDelete } from '../api.js';
import { Markdown } from '../markdown.jsx';
const dateFmt = new Intl.DateTimeFormat('de-DE', {
@@ -40,17 +40,32 @@ function splitContent(raw) {
return { project, footer, body: blocks.join('\n\n') };
}
export default function Devlogs() {
export default function Devlogs({ me }) {
const [data, setData] = useState(null);
const [page, setPage] = useState(1);
const [error, setError] = useState(false);
useEffect(() => {
const load = useCallback(() => {
setError(false);
apiGet(`/api/devlogs?page=${page}`).then(setData).catch(() => setError(true));
window.scrollTo(0, 0);
}, [page]);
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
@@ -92,6 +107,15 @@ export default function Devlogs() {
</span>
{project && <span className="card-project">{project}</span>}
<span className="card-rel">{relative(d.posted_at)}</span>
{me?.admin && (
<button
className="card-delete"
title="Aus dem Archiv löschen"
onClick={() => remove(d.message_id)}
>
</button>
)}
</div>
<div className="card-body">
<Markdown text={body} />