(undefined);
+
+ useEffect(() => {
+ if(!feedviewpost) return;
+ let cancelled = false;
+
+ const run = async () => {
+ const thereply = (fakepost?.record as AppBskyFeedPost.Record)?.reply?.parent?.uri;
+ const feedviewpostreplydid = thereply ? new AtUri(thereply).host : undefined;
+
+ if (feedviewpostreplydid) {
+ const opi = await cachedResolveIdentity({
+ didOrHandle: feedviewpostreplydid,
+ get,
+ set,
+ });
+
+ if (!cancelled) {
+ setFeedviewpostreplyhandle(opi?.handle);
+ }
+ }
+ };
+
+ run();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [fakepost, get, set]);
+
return (
<>
{/*
@@ -484,31 +568,13 @@ export function UniversalPostRendererRawRecordShim({
});
}
}}
- post={{
- $type: "app.bsky.feed.defs#postView",
- uri: aturi,
- cid: postRecord?.cid || "",
- author: {
- did: resolved?.did || "",
- handle: resolved?.handle || "",
- displayName: profileRecord?.value?.displayName || "",
- avatar: getAvatarUrl(profileRecord) || "",
- viewer: undefined,
- labels: profileRecord?.labels || undefined,
- verification: undefined,
- },
- record: postRecord?.value || {},
- embed: hydratedEmbed ?? undefined,
- replyCount: repliesCount ?? 0,
- repostCount: repostsCount ?? 0,
- likeCount: likesCount ?? 0,
- quoteCount: 0,
- indexedAt: postRecord?.value?.createdAt || "",
- viewer: undefined,
- labels: postRecord?.labels || undefined,
- threadgate: undefined,
- }}
+ post={fakepost}
salt={aturi}
+ bottomReplyLine={bottomReplyLine}
+ topReplyLine={topReplyLine}
+ bottomBorder={bottomBorder}
+ //extraOptionalItemInfo={{reply: postRecord?.value?.reply as AppBskyFeedDefs.ReplyRef, post: fakepost}}
+ feedviewpostreplyhandle={feedviewpostreplyhandle}
/>
>
);
@@ -1071,6 +1137,7 @@ import {
AppBskyFeedDefs,
AppBskyFeedPost,
AppBskyGraphDefs,
+ AtUri,
//AppBskyLabelerDefs,
//AtUri,
//ComAtprotoRepoStrongRef,
@@ -1171,6 +1238,7 @@ function UniversalPostRenderer({
topReplyLine,
salt,
bottomBorder = true,
+ feedviewpostreplyhandle,
}: {
post: PostView;
// optional for now because i havent ported every use to this yet
@@ -1187,6 +1255,7 @@ function UniversalPostRenderer({
topReplyLine?: boolean;
salt: string;
bottomBorder?: boolean;
+ feedviewpostreplyhandle?: string;
}) {
const navigate = useNavigate();
const [hasRetweeted, setHasRetweeted] = useState(
@@ -1319,6 +1388,7 @@ function UniversalPostRenderer({
//opacity: 0.5,
// no flex here
}}
+ className="bg-gray-500 dark:bg-gray-400"
/>
)}
)}
{/*
{/* reply indicator */}
- {false && isReply && (
+ {!!feedviewpostreplyhandle && (
- Reply to some other post lmao
+ Reply to {feedviewpostreplyhandle}
)}
-
,
- //,
+
+
+
);
}
diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx
index e3bec84..4468825 100644
--- a/src/routes/__root.tsx
+++ b/src/routes/__root.tsx
@@ -176,9 +176,9 @@ function RootDocument({ children }: { children: React.ReactNode }) {
Red Dwarf{" "}
-
+ {/*
lite
-
+ */}
Post
+
+ git repo
+
- Red Dwarf lite is a bluesky client that uses Constellation and
- direct PDS queries. Red Dwarf (without the lite) would be a
- self-hosted bluesky "instance". Stay tuned for the "without the
- lite" version.
+ Red Dwarf is a bluesky client that uses Constellation and
+ direct PDS queries. Skylite would be a
+ self-hosted bluesky "instance". Stay tuned for the release of Skylite.
diff --git a/src/routes/profile.$did/index.tsx b/src/routes/profile.$did/index.tsx
index 3d09f53..932428e 100644
--- a/src/routes/profile.$did/index.tsx
+++ b/src/routes/profile.$did/index.tsx
@@ -404,6 +404,7 @@ function ProfileComponent() {
);
})}
diff --git a/src/routes/profile.$did/post.$rkey.tsx b/src/routes/profile.$did/post.$rkey.tsx
index dc2770c..0b97fd3 100644
--- a/src/routes/profile.$did/post.$rkey.tsx
+++ b/src/routes/profile.$did/post.$rkey.tsx
@@ -1,24 +1,18 @@
-import { createFileRoute, Link } from "@tanstack/react-router";
-import React from "react";
-import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
-import { usePersistentStore } from "~/providers/PersistentStoreProvider";
+import { createFileRoute, Link } from '@tanstack/react-router';
+import React from 'react';
+import { UniversalPostRendererATURILoader, cachedGetRecord } from '~/components/UniversalPostRenderer';
+import { usePersistentStore } from '~/providers/PersistentStoreProvider';
const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour
-export const Route = createFileRoute("/profile/$did/post/$rkey")({
+export const Route = createFileRoute('/profile/$did/post/$rkey')({
component: RouterWrapper,
});
function RouterWrapper() {
const { did, rkey } = Route.useParams();
- return (
-
- );
+ return ;
}
function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
@@ -26,6 +20,10 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
const [resolvedDid, setResolvedDid] = React.useState(null);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(null);
+
+ const [mainPost, setMainPost] = React.useState(null);
+ const [parents, setParents] = React.useState([]);
+ const [parentsLoading, setParentsLoading] = React.useState(false);
const [replies, setReplies] = React.useState([]);
React.useEffect(() => {
@@ -35,7 +33,7 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
setResolvedDid(null);
return;
}
- if (did.startsWith("did:")) {
+ if (did.startsWith('did:')) {
setResolvedDid(did);
return;
}
@@ -44,12 +42,7 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
const cacheKey = `handleDid:${did}`;
const now = Date.now();
const cached = await get(cacheKey); // <-- await here
- if (
- cached &&
- cached.value &&
- cached.time &&
- now - cached.time < HANDLE_DID_CACHE_TIMEOUT
- ) {
+ if (cached && cached.value && cached.time && now - cached.time < HANDLE_DID_CACHE_TIMEOUT) {
try {
const data = JSON.parse(cached.value);
if (!ignore) setResolvedDid(data.did);
@@ -60,12 +53,12 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
try {
const url = `https://free-fly-24.deno.dev/?handle=${encodeURIComponent(did)}`;
const res = await fetch(url);
- if (!res.ok) throw new Error("Failed to resolve handle");
+ if (!res.ok) throw new Error('Failed to resolve handle');
const data = await res.json();
await set(cacheKey, JSON.stringify(data)); // <-- await here
if (!ignore) setResolvedDid(data.did);
} catch (e: any) {
- if (!ignore) setError("Failed to resolve handle: " + (e?.message || e));
+ if (!ignore) setError('Failed to resolve handle: ' + (e?.message || e));
} finally {
setLoading(false);
}
@@ -76,21 +69,72 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
};
}, [did, get, set]);
- const atUri =
- resolvedDid && rkey
- ? `at://${decodeURIComponent(resolvedDid)}/app.bsky.feed.post/${rkey}`
- : "";
+ const atUri = resolvedDid && rkey ? `at://${decodeURIComponent(resolvedDid)}/app.bsky.feed.post/${rkey}` : '';
- const handleConstellation = React.useCallback((data: any) => {}, []);
+ React.useEffect(() => {
+ if (!atUri) return;
+ let ignore = false;
+ async function fetchMainPost() {
+ try {
+ const postData = await cachedGetRecord({ atUri, get, set });
+ if (!ignore) {
+ setMainPost(postData);
+ }
+ } catch (e) {
+ console.error('Failed to fetch main post record:', e);
+ }
+ }
+ fetchMainPost();
+ return () => {
+ ignore = true;
+ };
+ }, [atUri, get, set]);
+
+ React.useEffect(() => {
+ if (!mainPost) return;
+ let ignore = false;
+ async function fetchParents() {
+ setParentsLoading(true);
+ const parentChain: any[] = [];
+ let currentParentUri = mainPost.value?.reply?.parent?.uri;
+ const MAX_PARENTS = 25; // Important to know theres a limit
+ let safetyCounter = 0;
+
+ while (currentParentUri && safetyCounter < MAX_PARENTS) {
+ try {
+ const parentPost = await cachedGetRecord({ atUri: currentParentUri, get, set });
+ if (!parentPost) break;
+ parentChain.push(parentPost);
+ currentParentUri = parentPost.value?.reply?.parent?.uri;
+ safetyCounter++;
+ } catch (error) {
+ console.error('Failed to fetch a parent post:', error);
+ break;
+ }
+ }
+
+ if (!ignore) {
+ setParents(parentChain.reverse());
+ setParentsLoading(false);
+ }
+ }
+
+ fetchParents();
+ return () => {
+ ignore = true;
+ };
+ }, [mainPost, get, set]);
React.useEffect(() => {
if (!atUri) return;
let ignore = false;
async function fetchReplies() {
try {
- const url = `https://constellation.microcosm.blue/links?target=${encodeURIComponent(atUri)}&collection=app.bsky.feed.post&path=.reply.parent.uri`;
+ const url = `https://constellation.microcosm.blue/links?target=${encodeURIComponent(
+ atUri,
+ )}&collection=app.bsky.feed.post&path=.reply.parent.uri`;
const res = await fetch(url);
- if (!res.ok) throw new Error("Failed to fetch replies");
+ if (!res.ok) throw new Error('Failed to fetch replies');
const data = await res.json();
if (!ignore && data.linking_records) {
setReplies(data.linking_records.slice(0, 50));
@@ -107,22 +151,18 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
if (!did || !rkey) return Invalid post URI
;
if (loading) return Resolving handle...
;
- if (error) return {error}
;
+ if (error) return {error}
;
if (!atUri) return Invalid post URI
;
- console.log("atUri", atUri);
-
return (
<>
{
+ onClick={e => {
e.preventDefault();
- window.history.length > 1
- ? window.history.back()
- : window.location.assign("/");
+ window.history.length > 1 ? window.history.back() : window.location.assign('/');
}}
aria-label="Go back"
>
@@ -130,36 +170,38 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) {
Post
-
+
+ {parentsLoading && Loading conversation...
}
+
+ {/* we should use the reply lines here thats provided by UPR*/}
+
+ {parents.map((parent, index) => (
+ 0}
+ bottomReplyLine={true}
+ bottomBorder={false}
+ />
+ ))}
+
+
+ 0} />
+
{replies.length > 0 && (
-
+
Replies
-
- {replies.map((reply, i) => {
+
+ {replies.map(reply => {
const replyAtUri = `at://${reply.did}/app.bsky.feed.post/${reply.rkey}`;
- return (
-
- );
+ return ;
})}
)}
>
);
-}
+}
\ No newline at end of file