an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm

profile description rich text

rimar1337 37d24d0e 24dd0e22

Changed files
+65 -6
src
components
routes
profile.$did
+16 -1
src/components/Composer.tsx
··· 1 - import { RichText } from "@atproto/api"; 1 + import { AppBskyRichtextFacet, RichText } from "@atproto/api"; 2 2 import { useAtom } from "jotai"; 3 3 import { Dialog } from "radix-ui"; 4 4 import { useEffect, useRef, useState } from "react"; ··· 47 47 try { 48 48 const rt = new RichText({ text: postText }); 49 49 await rt.detectFacets(agent); 50 + 51 + if (rt.facets?.length) { 52 + rt.facets = rt.facets.filter((item) => { 53 + if (item.$type !== "app.bsky.richtext.facet") return true; 54 + if (!item.features?.length) return true; 55 + 56 + item.features = item.features.filter((feature) => { 57 + if (feature.$type !== "app.bsky.richtext.facet#mention") return true; 58 + const did = feature.$type === "app.bsky.richtext.facet#mention" ? (feature as AppBskyRichtextFacet.Mention)?.did : undefined; 59 + return typeof did === "string" && did.startsWith("did:"); 60 + }); 61 + 62 + return item.features.length > 0; 63 + }); 64 + } 50 65 51 66 const record: Record<string, unknown> = { 52 67 $type: "app.bsky.feed.post",
+1 -1
src/components/UniversalPostRenderer.tsx
··· 2649 2649 return { start, end, feature: f.features[0] }; 2650 2650 }); 2651 2651 } 2652 - function renderTextWithFacets({ 2652 + export function renderTextWithFacets({ 2653 2653 text, 2654 2654 facets, 2655 2655 navigate,
+48 -4
src/routes/profile.$did/index.tsx
··· 1 + import { RichText } from "@atproto/api"; 1 2 import { useQueryClient } from "@tanstack/react-query"; 2 - import { createFileRoute } from "@tanstack/react-router"; 3 + import { createFileRoute, useNavigate } from "@tanstack/react-router"; 3 4 import { useAtom } from "jotai"; 4 - import React from "react"; 5 + import React, { type ReactNode,useEffect, useState } from "react"; 5 6 6 7 import { Header } from "~/components/Header"; 7 - import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; 8 + import { renderTextWithFacets, UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; 8 9 import { useAuth } from "~/providers/UnifiedAuthProvider"; 9 10 import { imgCDNAtom } from "~/utils/atoms"; 10 11 import { toggleFollow, useGetFollowState, useGetOneToOneState } from "~/utils/followState"; ··· 21 22 function ProfileComponent() { 22 23 // booo bad this is not always the did it might be a handle, use identity.did instead 23 24 const { did } = Route.useParams(); 25 + const navigate = useNavigate(); 24 26 const queryClient = useQueryClient(); 25 27 const { 26 28 data: identity, ··· 175 177 </div> 176 178 {description && ( 177 179 <div className="text-base leading-relaxed text-gray-800 dark:text-gray-300 mb-5 whitespace-pre-wrap break-words text-[15px]"> 178 - {description} 180 + {/* {description} */} 181 + <RichTextRenderer key={did} description={description}/> 179 182 </div> 180 183 )} 181 184 </div> ··· 308 311 )} 309 312 </> 310 313 ); 314 + } 315 + 316 + export function RichTextRenderer({ description }: { description: string }) { 317 + const [richDescription, setRichDescription] = useState<string | ReactNode[]>(description); 318 + const { agent } = useAuth(); 319 + const navigate = useNavigate(); 320 + 321 + useEffect(() => { 322 + let mounted = true; 323 + 324 + // setRichDescription(description); 325 + 326 + async function processRichText() { 327 + try { 328 + if (!agent?.did) return; 329 + const rt = new RichText({ text: description }); 330 + await rt.detectFacets(agent); 331 + 332 + if (!mounted) return; 333 + 334 + if (rt.facets) { 335 + setRichDescription(renderTextWithFacets({ text: rt.text, facets: rt.facets, navigate })); 336 + } else { 337 + setRichDescription(rt.text); 338 + } 339 + } catch (error) { 340 + console.error("Failed to detect facets:", error); 341 + if (mounted) { 342 + setRichDescription(description); 343 + } 344 + } 345 + } 346 + 347 + processRichText(); 348 + 349 + return () => { 350 + mounted = false; 351 + }; 352 + }, [description, agent, navigate]); 353 + 354 + return <>{richDescription}</>; 311 355 }