Retro Bulletin Board Systems on atproto. Web app and TUI. lazy mirror of alyraffauf/atbbs atbbs.xyz
forums python tui atproto bbs
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 92 lines 2.8 kB view raw
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}