Diskussionsketten: jüngste Antwort im Teilbaum an Feed- und Antwortkarten

Jede Karte in Feed und Antwortliste zeigt die Antwort, die ihre Diskussion
zuletzt fortgesetzt hat: [Beitrag] -- "k weitere Beiträge" -- [Antwort].
Gelöschte Beiträge werden als jüngste übersprungen, zählen auf dem Pfad mit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
irrlicht
2026-09-27 00:52:17 +02:00
co-authored by Claude Opus 5.5
parent 8e79e94bbb
commit ade853f560
7 changed files with 196 additions and 5 deletions
+10
View File
@@ -271,6 +271,16 @@ img.avatar-lg { width: 72px; height: 72px; }
/* Nur die Vorfahren bilden einen Thread -> Linie. Antworten nur eingerückt. */
#ancestors .threadline { border-right: var(--line-w) solid gray; }
/* Diskussionskette in Feed und Antworten (Discussion): Beitrag, ggf.
"k weitere Beiträge", jüngste Antwort. Gleiches Bild wie die Ahnenkette --
Strich an allen Gliedern außer dem letzten. */
.chain > :not(:last-child) .threadline {
display: block;
flex: 0 0 calc(var(--avatar-sm) / 2 - var(--line-w));
margin-right: calc(var(--avatar-sm) / 2);
border-right: var(--line-w) solid gray;
}
/* Interaktionsleiste: Antworten-Button links, Vote-Buttons mittig, Repost
rechts. Antwort- und Repost-Zahl stehen in den Buttons; der Repost-Cooldown
wandert in die Byline (siehe setupBump). */
+25
View File
@@ -65,3 +65,28 @@ function LiveCard({ entry: e, when, open }: { entry: Entry; when: string; open:
</article>
);
}
// Discussion zeigt einen Beitrag mit der Antwort, die seine Diskussion zuletzt
// fortgesetzt hat: [Beitrag] ── "k weitere Beiträge" ── [jüngste Antwort].
// Der Strich verbindet die Glieder wie in der Ahnenkette; "k weitere" führt
// wie die Antwort selbst zu deren Seite, wo die ganze Kette darüber steht.
export function Discussion({ entry: e }: { entry: Entry }) {
if (!e.latest) return <EntryCard entry={e} />;
const { entry: latest, skipped } = e.latest;
return (
<div className="chain">
<EntryCard entry={e} />
{skipped > 0 && (
<div className="threadline-wrapper">
<div className="threadline"></div>
<div className="body muted">
<Link to={`/e/${latest.pid}`}>
{skipped === 1 ? "1 weiterer Beitrag" : `${skipped} weitere Beiträge`}
</Link>
</div>
</div>
)}
<EntryCard entry={latest} />
</div>
);
}
+2 -2
View File
@@ -2,7 +2,7 @@ import { useEffect, useRef } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
import { api, errMsg } from "../api";
import type { Entry } from "../types";
import { EntryCard } from "./EntryCard";
import { Discussion } from "./EntryCard";
// Das Backend deckelt die Seitenzahl bei 100 und liefert darüber wieder
// Seite 100 -- ohne diese Grenze würde der Feed sie endlos wiederholen.
@@ -54,7 +54,7 @@ export function Feed({ queryKey, urlFor, as: Container = "section" }: {
return (
<>
<Container id="feed">
{entries.map((e) => <EntryCard key={e.pid} entry={e} />)}
{entries.map((e) => <Discussion key={e.pid} entry={e} />)}
{feed.isError && <p className="msg">{errMsg(feed.error, "Feed konnte nicht geladen werden.")}</p>}
{feed.isSuccess && !hasNextPage && <p className="muted">YOU REACHED THE END!</p>}
</Container>
+2 -2
View File
@@ -4,7 +4,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api, errMsg } from "../api";
import { useMe } from "../auth";
import { pasteImage } from "../paste";
import { EntryCard } from "../components/EntryCard";
import { Discussion, EntryCard } from "../components/EntryCard";
import type { CreatedEntry, Entry, Thread } from "../types";
// Beim Wechsel zu einem anderen Beitrag frisch anfangen (offene Bearbeitung,
@@ -56,7 +56,7 @@ function ThreadView({ pid }: { pid: string }) {
<section id="replies">
{data && !data.replies.length && <p className="muted">Noch keine Antworten.</p>}
{data?.replies.map((e) => <EntryCard key={e.pid} entry={e} />)}
{data?.replies.map((e) => <Discussion key={e.pid} entry={e} />)}
</section>
</>
);
+3
View File
@@ -15,6 +15,9 @@ export interface Entry {
last_bump: number;
username: string;
avatar: string;
// Nur in Feed und Antwortliste: jüngster Beitrag im Teilbaum, skipped =
// Beiträge auf dem Pfad dazwischen (0 = direkte Antwort).
latest?: { entry: Entry; skipped: number };
}
export interface Thread {