Soft-Delete: Beiträge als [deleted] erhalten statt löschen

- entry.deleted; entrySelect LEFT JOIN + COALESCE(username)
- POST /entry/{pid}/delete: deleted=1, Inhalt/Bild/Autor geleert, Zeile bleibt
- Konto-Löschung soft-deletet die eigenen Beiträge (statt hartem DELETE),
  damit fremde Antworten nicht verwaisen
- Frontend: [deleted]-Platzhalter im Feed, Löschen-Button auf Focus-Seite
- notes/migrations.md: ALTER TABLE deleted

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 13:21:42 +02:00
parent ba085eff4e
commit cb3da3bb24
8 changed files with 164 additions and 17 deletions
+14 -3
View File
@@ -35,9 +35,20 @@ async function load() {
focus.innerHTML = "";
focus.appendChild(renderEntry(data.entry));
// Reply-Box nur eingeloggt zeigen.
const h = await api.get("/auth/headerbar");
document.getElementById("reply-box").hidden = !h.loggedin;
// Eingeloggt? Reply-Box zeigen; eigener, nicht gelöschter Beitrag -> Löschen-Button.
const me = await api.get("/user/info");
document.getElementById("reply-box").hidden = !me.uid;
if (me.uid === data.entry.uid && !data.entry.deleted) {
const btn = document.createElement("button");
btn.textContent = "Löschen";
btn.addEventListener("click", async () => {
if (!confirm("Beitrag wirklich löschen?")) return;
const res = await api.post(`/entry/${pid}/delete`, new FormData());
if (res.ok) load();
else alert(res.data.error || "Löschen fehlgeschlagen.");
});
focus.appendChild(btn);
}
// Direkte Antworten.
const replies = document.getElementById("replies");
+13 -3
View File
@@ -34,15 +34,25 @@ function renderEntry(e) {
el.className = "card";
const when = new Date(e.created_at * 1000).toLocaleString("de-DE");
const replyLabel =
e.reply_count === 1 ? "1 Antwort" : `${e.reply_count} Antworten`;
// Soft-gelöschter Beitrag: nur Platzhalter, damit der Thread navigierbar bleibt.
if (e.deleted) {
el.innerHTML = `
<div class="muted">[deleted] · ${when}</div>
<div class="content">[deleted]</div>
<div class="muted"><a href="/e/${e.pid}">${replyLabel}</a></div>
`;
return el;
}
const user = encodeURIComponent(e.username);
let media = "";
if (e.filepath) {
media = `<img src="/${e.filepath}" alt="" loading="lazy">`;
}
const replyLabel =
e.reply_count === 1 ? "1 Antwort" : `${e.reply_count} Antworten`;
el.innerHTML = `
<div class="muted"><a href="/u/${user}">${escapeHtml(e.username)}</a> · ${when}</div>
<div class="content">${linkify(e.content)}</div>