an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
92
fork

Configure Feed

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

profile description rich text

rimar1337 37d24d0e 24dd0e22

+65 -6
+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 }