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.

web: add clickable news posts

+86 -5
+12 -5
web/src/pages/BBS.tsx
··· 119 119 120 120 {bbs.news.length ? ( 121 121 bbs.news.map((item, i) => ( 122 - <div 122 + <Link 123 123 key={item.tid} 124 - className={`reply-card bg-neutral-900 border border-neutral-800 rounded p-4 ${i < bbs.news.length - 1 ? "mb-2" : ""}`} 124 + to={`/bbs/${handle}/news/${item.tid}`} 125 + className={`reply-card block bg-neutral-900 border border-neutral-800 rounded p-4 hover:border-neutral-700 ${i < bbs.news.length - 1 ? "mb-2" : ""}`} 125 126 > 126 127 <div className="flex items-baseline justify-between mb-2"> 127 128 <div className="flex items-baseline gap-2"> ··· 133 134 <span className="reply-actions"> 134 135 <button 135 136 type="button" 136 - onClick={() => removeNews(item.tid)} 137 + onClick={(e) => { 138 + e.preventDefault(); 139 + removeNews(item.tid); 140 + }} 137 141 className="text-xs text-neutral-500 hover:text-red-400" 138 142 > 139 143 delete ··· 141 145 </span> 142 146 )} 143 147 </div> 144 - <PostBody>{item.body}</PostBody> 145 - </div> 148 + <div className="line-clamp-3 text-neutral-400"> 149 + {item.body.substring(0, 200) + 150 + (item.body.length > 200 ? "..." : "")} 151 + </div> 152 + </Link> 146 153 )) 147 154 ) : ( 148 155 <p className="text-neutral-500">No news yet.</p>
+69
web/src/pages/News.tsx
··· 1 + import { useNavigate, useParams, useRouteLoaderData } from "react-router-dom"; 2 + import { useAuth } from "../lib/auth"; 3 + import { useBreadcrumb } from "../hooks/useBreadcrumb"; 4 + import { useTitle } from "../hooks/useTitle"; 5 + import { formatFullDate, relativeDate } from "../lib/util"; 6 + import { NEWS } from "../lib/lexicon"; 7 + import { deleteRecord } from "../lib/writes"; 8 + import type { BBSLoaderData } from "../router/loaders"; 9 + import PostBody from "../components/PostBody"; 10 + 11 + export default function NewsPage() { 12 + const { handle, tid } = useParams(); 13 + const { bbs } = useRouteLoaderData("bbs") as BBSLoaderData; 14 + const { user, agent } = useAuth(); 15 + const navigate = useNavigate(); 16 + 17 + const item = bbs.news.find((n) => n.tid === tid); 18 + 19 + useBreadcrumb( 20 + [ 21 + { label: bbs.site.name, to: `/bbs/${handle}` }, 22 + { label: item?.title ?? "News" }, 23 + ], 24 + [bbs, handle, tid], 25 + ); 26 + useTitle( 27 + item ? `${item.title} — ${bbs.site.name}` : `News — ${bbs.site.name}`, 28 + ); 29 + 30 + if (!item) { 31 + return <p className="text-neutral-500">News post not found.</p>; 32 + } 33 + 34 + const isSysop = user && user.did === bbs.identity.did; 35 + 36 + async function onDelete() { 37 + if (!agent || !tid) return; 38 + if (!confirm("Delete this news post?")) return; 39 + await deleteRecord(agent, NEWS, tid); 40 + navigate(`/bbs/${handle}`); 41 + } 42 + 43 + return ( 44 + <article className="bg-neutral-900 border border-neutral-800 rounded p-4"> 45 + <div className="flex items-baseline justify-between mb-3"> 46 + <div className="flex items-baseline gap-2"> 47 + <span className="text-neutral-200">{handle}</span> 48 + <span className="text-neutral-600">·</span> 49 + <time 50 + className="text-xs text-neutral-500" 51 + title={formatFullDate(item.createdAt)} 52 + > 53 + {relativeDate(item.createdAt)} 54 + </time> 55 + </div> 56 + {isSysop && ( 57 + <button 58 + onClick={onDelete} 59 + className="text-xs text-neutral-500 hover:text-red-400" 60 + > 61 + delete 62 + </button> 63 + )} 64 + </div> 65 + <h1 className="text-lg text-neutral-200 font-bold mb-3">{item.title}</h1> 66 + <PostBody>{item.body}</PostBody> 67 + </article> 68 + ); 69 + }
+5
web/src/router/routes.tsx
··· 17 17 import SysopCreate from "../pages/SysopCreate"; 18 18 import SysopEdit from "../pages/SysopEdit"; 19 19 import SysopModerate from "../pages/SysopModerate"; 20 + import News from "../pages/News"; 20 21 import NotFound from "../pages/NotFound"; 21 22 22 23 import { ··· 83 84 loader: threadLoader, 84 85 element: <Thread />, 85 86 errorElement: <ErrorPage />, 87 + }, 88 + { 89 + path: "news/:tid", 90 + element: <News />, 86 91 }, 87 92 ], 88 93 },