Retro Bulletin Board Systems on atproto. Web app and TUI.
lazy mirror of alyraffauf/atbbs
atbbs.xyz
forums
python
tui
atproto
bbs
1/** Thread-page data fetcher: refs from Constellation, hydrated page replies,
2 * plus pagination + scroll-to-reply helpers. Optimistic mutations are in
3 * Thread.tsx and update the same query caches via setQueryData. */
4
5import { useEffect } from "react";
6import { useSearchParams } from "react-router-dom";
7import { useSuspenseQuery } from "@tanstack/react-query";
8import { threadPageQuery, threadRefsQuery } from "../lib/queries";
9import { parseAtUri } from "../lib/util";
10import {
11 REPLIES_PER_PAGE,
12 clampPage,
13 pageForRkey,
14 pageForReply,
15 rkeyFromHash,
16} from "../lib/replies";
17
18export function useThreadReplies(threadUri: string) {
19 const [params, setParams] = useSearchParams();
20
21 const { data: refs } = useSuspenseQuery(threadRefsQuery(threadUri));
22 const totalPages = Math.max(1, Math.ceil(refs.length / REPLIES_PER_PAGE));
23
24 // --- Page derived from URL, clamped to the available range ---
25
26 const requestedPage = parseInt(params.get("page") ?? "1", 10);
27 const replyParam = params.get("reply");
28 const hashRkey = rkeyFromHash();
29 const initialPage =
30 pageForRkey(refs, hashRkey) ??
31 pageForReply(refs, replyParam) ??
32 requestedPage;
33 const page = clampPage(initialPage, refs.length);
34
35 const pageStart = (page - 1) * REPLIES_PER_PAGE;
36 const pageRefs = refs.slice(pageStart, pageStart + REPLIES_PER_PAGE);
37
38 const { data: pageData } = useSuspenseQuery(
39 threadPageQuery(threadUri, page, pageRefs),
40 );
41 const { replies, parentReplies } = pageData;
42
43 // --- Keep URL in sync when the derived page differs from what's in it ---
44
45 useEffect(() => {
46 const fromUrl = parseInt(params.get("page") ?? "1", 10);
47 if (fromUrl === page) return;
48 setParams((prev) => writePageParam(prev, page), { replace: true });
49 // eslint-disable-next-line react-hooks/exhaustive-deps -- params identity churns
50 }, [page]);
51
52 // --- Navigation helpers ---
53
54 function setPage(next: number) {
55 const clamped = clampPage(next, refs.length);
56 setParams((prev) => writePageParam(prev, clamped));
57 }
58
59 function scrollToReply(uri: string) {
60 const { rkey } = parseAtUri(uri);
61 const onScreen = document.getElementById(`reply-${rkey}`);
62 if (onScreen) {
63 onScreen.scrollIntoView({ behavior: "smooth" });
64 return;
65 }
66 const targetPage = pageForRkey(refs, rkey);
67 if (targetPage === null) return;
68 setParams((prev) => {
69 const next = writePageParam(prev, targetPage);
70 next.set("reply", uri);
71 return next;
72 });
73 }
74
75 return {
76 page,
77 setPage,
78 totalPages,
79 refs,
80 replies,
81 parentReplies,
82 scrollToReply,
83 };
84}
85
86function writePageParam(prev: URLSearchParams, page: number) {
87 const next = new URLSearchParams(prev);
88 if (page === 1) next.delete("page");
89 else next.set("page", String(page));
90 next.delete("reply");
91 return next;
92}