+16
-1
src/components/Composer.tsx
+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
+1
-1
src/components/UniversalPostRenderer.tsx
+48
-4
src/routes/profile.$did/index.tsx
+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
}