+6
-1
src/components/InfiniteCustomFeed.tsx
+6
-1
src/components/InfiniteCustomFeed.tsx
···
14
14
feedUri: string;
15
15
pdsUrl?: string;
16
16
feedServiceDid?: string;
17
+
authedOverride?: boolean;
18
+
unauthedfeedurl?: string;
17
19
}
18
20
19
21
export function InfiniteCustomFeed({
20
22
feedUri,
21
23
pdsUrl,
22
24
feedServiceDid,
25
+
authedOverride,
26
+
unauthedfeedurl,
23
27
}: InfiniteCustomFeedProps) {
24
28
const { agent } = useAuth();
25
-
const authed = !!agent?.did;
29
+
const authed = authedOverride || !!agent?.did;
26
30
27
31
// const identityresultmaybe = useQueryIdentity(agent?.did);
28
32
// const identity = identityresultmaybe?.data;
···
45
49
isAuthed: authed ?? false,
46
50
pdsUrl: pdsUrl,
47
51
feedServiceDid: feedServiceDid,
52
+
unauthedfeedurl: unauthedfeedurl,
48
53
});
49
54
const queryClient = useQueryClient();
50
55
+12
-1
src/components/UniversalPostRenderer.tsx
+12
-1
src/components/UniversalPostRenderer.tsx
···
1204
1204
1205
1205
import defaultpfp from "~/../public/favicon.png";
1206
1206
import { useAuth } from "~/providers/UnifiedAuthProvider";
1207
-
import { FollowButton, Mutual } from "~/routes/profile.$did";
1207
+
import { FeedItemRenderAturiLoader, FollowButton, Mutual } from "~/routes/profile.$did";
1208
1208
import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i";
1209
1209
// import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed";
1210
1210
// import type {
···
2179
2179
}
2180
2180
2181
2181
if (AppBskyEmbedRecord.isView(embed)) {
2182
+
// hey im really lazy and im gonna do it the bad way
2183
+
const reallybaduri = (embed?.record as any)?.uri as string | undefined;
2184
+
const reallybadaturi = reallybaduri ? new AtUri(reallybaduri) : undefined;
2185
+
2182
2186
// custom feed embed (i.e. generator view)
2183
2187
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
2184
2188
// stopgap sorry
···
2188
2192
// <MaybeFeedCard view={embed.record} />
2189
2193
// </div>
2190
2194
// )
2195
+
} else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.feed.generator") {
2196
+
return <div className="rounded-xl border"><FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder/></div>
2191
2197
}
2192
2198
2193
2199
// list embed
···
2199
2205
// <MaybeListCard view={embed.record} />
2200
2206
// </div>
2201
2207
// )
2208
+
} else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.list") {
2209
+
return <div className="rounded-xl border"><FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder listmode /></div>
2202
2210
}
2203
2211
2204
2212
// starter pack embed
···
2210
2218
// <StarterPackCard starterPack={embed.record} />
2211
2219
// </div>
2212
2220
// )
2221
+
} else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.starterpack") {
2222
+
return <div className="rounded-xl border"><FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder listmode /></div>
2213
2223
}
2214
2224
2215
2225
// quote post
···
2269
2279
</div>
2270
2280
);
2271
2281
} else {
2282
+
console.log("what the hell is a ", embed);
2272
2283
return <>sorry</>;
2273
2284
}
2274
2285
//return <QuotePostRenderer record={embed.record} moderation={moderation} />;
+21
src/routeTree.gen.ts
+21
src/routeTree.gen.ts
···
21
21
import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b'
22
22
import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a'
23
23
import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey'
24
+
import { Route as ProfileDidFeedRkeyRouteImport } from './routes/profile.$did/feed.$rkey'
24
25
import { Route as ProfileDidPostRkeyRepostedByRouteImport } from './routes/profile.$did/post.$rkey.reposted-by'
25
26
import { Route as ProfileDidPostRkeyQuotesRouteImport } from './routes/profile.$did/post.$rkey.quotes'
26
27
import { Route as ProfileDidPostRkeyLikedByRouteImport } from './routes/profile.$did/post.$rkey.liked-by'
···
85
86
const ProfileDidPostRkeyRoute = ProfileDidPostRkeyRouteImport.update({
86
87
id: '/profile/$did/post/$rkey',
87
88
path: '/profile/$did/post/$rkey',
89
+
getParentRoute: () => rootRouteImport,
90
+
} as any)
91
+
const ProfileDidFeedRkeyRoute = ProfileDidFeedRkeyRouteImport.update({
92
+
id: '/profile/$did/feed/$rkey',
93
+
path: '/profile/$did/feed/$rkey',
88
94
getParentRoute: () => rootRouteImport,
89
95
} as any)
90
96
const ProfileDidPostRkeyRepostedByRoute =
···
122
128
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
123
129
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
124
130
'/profile/$did': typeof ProfileDidIndexRoute
131
+
'/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute
125
132
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
126
133
'/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute
127
134
'/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute
···
138
145
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
139
146
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
140
147
'/profile/$did': typeof ProfileDidIndexRoute
148
+
'/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute
141
149
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
142
150
'/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute
143
151
'/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute
···
157
165
'/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
158
166
'/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
159
167
'/profile/$did/': typeof ProfileDidIndexRoute
168
+
'/profile/$did/feed/$rkey': typeof ProfileDidFeedRkeyRoute
160
169
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
161
170
'/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute
162
171
'/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute
···
175
184
| '/route-a'
176
185
| '/route-b'
177
186
| '/profile/$did'
187
+
| '/profile/$did/feed/$rkey'
178
188
| '/profile/$did/post/$rkey'
179
189
| '/profile/$did/post/$rkey/liked-by'
180
190
| '/profile/$did/post/$rkey/quotes'
···
191
201
| '/route-a'
192
202
| '/route-b'
193
203
| '/profile/$did'
204
+
| '/profile/$did/feed/$rkey'
194
205
| '/profile/$did/post/$rkey'
195
206
| '/profile/$did/post/$rkey/liked-by'
196
207
| '/profile/$did/post/$rkey/quotes'
···
209
220
| '/_pathlessLayout/_nested-layout/route-a'
210
221
| '/_pathlessLayout/_nested-layout/route-b'
211
222
| '/profile/$did/'
223
+
| '/profile/$did/feed/$rkey'
212
224
| '/profile/$did/post/$rkey'
213
225
| '/profile/$did/post/$rkey/liked-by'
214
226
| '/profile/$did/post/$rkey/quotes'
···
225
237
SettingsRoute: typeof SettingsRoute
226
238
CallbackIndexRoute: typeof CallbackIndexRoute
227
239
ProfileDidIndexRoute: typeof ProfileDidIndexRoute
240
+
ProfileDidFeedRkeyRoute: typeof ProfileDidFeedRkeyRoute
228
241
ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRouteWithChildren
229
242
}
230
243
···
314
327
preLoaderRoute: typeof ProfileDidPostRkeyRouteImport
315
328
parentRoute: typeof rootRouteImport
316
329
}
330
+
'/profile/$did/feed/$rkey': {
331
+
id: '/profile/$did/feed/$rkey'
332
+
path: '/profile/$did/feed/$rkey'
333
+
fullPath: '/profile/$did/feed/$rkey'
334
+
preLoaderRoute: typeof ProfileDidFeedRkeyRouteImport
335
+
parentRoute: typeof rootRouteImport
336
+
}
317
337
'/profile/$did/post/$rkey/reposted-by': {
318
338
id: '/profile/$did/post/$rkey/reposted-by'
319
339
path: '/reposted-by'
···
401
421
SettingsRoute: SettingsRoute,
402
422
CallbackIndexRoute: CallbackIndexRoute,
403
423
ProfileDidIndexRoute: ProfileDidIndexRoute,
424
+
ProfileDidFeedRkeyRoute: ProfileDidFeedRkeyRoute,
404
425
ProfileDidPostRkeyRoute: ProfileDidPostRkeyRouteWithChildren,
405
426
}
406
427
export const routeTree = rootRouteImport
+90
src/routes/profile.$did/feed.$rkey.tsx
+90
src/routes/profile.$did/feed.$rkey.tsx
···
1
+
import * as ATPAPI from "@atproto/api";
2
+
import { AtUri } from "@atproto/api";
3
+
import { createFileRoute } from "@tanstack/react-router";
4
+
import { useAtom } from "jotai";
5
+
6
+
import { Header } from "~/components/Header";
7
+
import { InfiniteCustomFeed } from "~/components/InfiniteCustomFeed";
8
+
import { useAuth } from "~/providers/UnifiedAuthProvider";
9
+
import { quickAuthAtom } from "~/utils/atoms";
10
+
import { useQueryArbitrary, useQueryIdentity } from "~/utils/useQuery";
11
+
12
+
export const Route = createFileRoute("/profile/$did/feed/$rkey")({
13
+
component: FeedRoute,
14
+
});
15
+
16
+
function FeedRoute() {
17
+
const { did, rkey } = Route.useParams();
18
+
const { agent, status } = useAuth();
19
+
const { data: identitydata } = useQueryIdentity(did);
20
+
const { data: identity } = useQueryIdentity(agent?.did);
21
+
const uri = `at://${identitydata?.did || did}/app.bsky.feed.generator/${rkey}`;
22
+
const aturi = new AtUri(uri);
23
+
const { data: feeddata } = useQueryArbitrary(uri);
24
+
25
+
const [quickAuth, setQuickAuth] = useAtom(quickAuthAtom);
26
+
const isAuthRestoring = quickAuth ? status === "loading" : false;
27
+
28
+
const authed = status === "signedIn";
29
+
30
+
const feedServiceDid = !isAuthRestoring
31
+
? ((feeddata?.value as any)?.did as string | undefined)
32
+
: undefined;
33
+
34
+
// const {
35
+
// data: feedData,
36
+
// isLoading: isFeedLoading,
37
+
// error: feedError,
38
+
// } = useQueryFeedSkeleton({
39
+
// feedUri: selectedFeed!,
40
+
// agent: agent ?? undefined,
41
+
// isAuthed: authed ?? false,
42
+
// pdsUrl: identity?.pds,
43
+
// feedServiceDid: feedServiceDid,
44
+
// });
45
+
46
+
// const feed = feedData?.feed || [];
47
+
48
+
const isReadyForAuthedFeed =
49
+
!isAuthRestoring && authed && agent && identity?.pds && feedServiceDid;
50
+
const isReadyForUnauthedFeed = !isAuthRestoring && !authed;
51
+
52
+
const feed: ATPAPI.AppBskyFeedGenerator.Record | undefined = feeddata?.value;
53
+
54
+
const web = feedServiceDid?.replace(/^did:web:/, "") || "";
55
+
56
+
return (
57
+
<>
58
+
<Header
59
+
title={feed?.displayName || aturi.rkey}
60
+
backButtonCallback={() => {
61
+
if (window.history.length > 1) {
62
+
window.history.back();
63
+
} else {
64
+
window.location.assign("/");
65
+
}
66
+
}}
67
+
/>
68
+
69
+
{isAuthRestoring ||
70
+
(authed && (!identity?.pds || !feedServiceDid) && (
71
+
<div className="p-4 text-center text-gray-500">
72
+
Preparing your feed...
73
+
</div>
74
+
))}
75
+
76
+
{!isAuthRestoring && (isReadyForAuthedFeed || isReadyForUnauthedFeed) ? (
77
+
<InfiniteCustomFeed
78
+
key={uri}
79
+
feedUri={uri}
80
+
pdsUrl={identity?.pds}
81
+
feedServiceDid={feedServiceDid}
82
+
authedOverride={true}
83
+
unauthedfeedurl={web}
84
+
/>
85
+
) : (
86
+
<div className="p-4 text-center text-gray-500">Loading.......</div>
87
+
)}
88
+
</>
89
+
);
90
+
}
+63
-19
src/routes/profile.$did/index.tsx
+63
-19
src/routes/profile.$did/index.tsx
···
1
1
import { RichText } from "@atproto/api";
2
2
import * as ATPAPI from "@atproto/api";
3
3
import { useQueryClient } from "@tanstack/react-query";
4
-
import { createFileRoute, useNavigate } from "@tanstack/react-router";
4
+
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
5
5
import { useAtom } from "jotai";
6
6
import React, { type ReactNode, useEffect, useState } from "react";
7
7
···
24
24
} from "~/utils/followState";
25
25
import {
26
26
useInfiniteQueryAuthorFeed,
27
+
useQueryArbitrary,
27
28
useQueryConstellation,
28
29
useQueryIdentity,
29
30
useQueryProfile,
···
403
404
);
404
405
}
405
406
406
-
function FeedItemRender({
407
+
export function FeedItemRenderAturiLoader({
408
+
aturi,
409
+
listmode,
410
+
disableBottomBorder,
411
+
}: {
412
+
aturi: string;
413
+
listmode?: boolean;
414
+
disableBottomBorder?: boolean;
415
+
}) {
416
+
const { data: record } = useQueryArbitrary(aturi);
417
+
418
+
if (!record) return;
419
+
return (
420
+
<FeedItemRender
421
+
listmode={listmode}
422
+
feed={record}
423
+
disableBottomBorder={disableBottomBorder}
424
+
/>
425
+
);
426
+
}
427
+
428
+
export function FeedItemRender({
407
429
feed,
408
-
listmode
430
+
listmode,
431
+
disableBottomBorder,
409
432
}: {
410
-
feed: { uri: string; cid: string; value: ATPAPI.AppBskyFeedGenerator.Record };
433
+
feed: { uri: string; cid: string; value: any };
411
434
listmode?: boolean;
435
+
disableBottomBorder?: boolean;
412
436
}) {
413
-
const name = listmode ? feed.value?.name as string : feed.value?.displayName as string;
437
+
const name = listmode
438
+
? (feed.value?.name as string)
439
+
: (feed.value?.displayName as string);
414
440
const aturi = new ATPAPI.AtUri(feed.uri);
415
-
const {data: identity} = useQueryIdentity(aturi.host);
441
+
const { data: identity } = useQueryIdentity(aturi.host);
416
442
const resolvedDid = identity?.did;
417
443
const [imgcdn] = useAtom(imgCDNAtom);
418
444
···
422
448
return `https://${imgcdn}/img/avatar/plain/${resolvedDid}/${link}@jpeg`;
423
449
}
424
450
451
+
const { data: likes } = useQueryConstellation(
425
452
// @ts-expect-error overloads sucks
426
-
const {data: likes} = useQueryConstellation(!listmode ? {
427
-
target: feed.uri,
428
-
method: "/links/count",
429
-
collection: "app.bsky.feed.like",
430
-
path: ".subject.uri"
431
-
} : undefined)
453
+
!listmode
454
+
? {
455
+
target: feed.uri,
456
+
method: "/links/count",
457
+
collection: "app.bsky.feed.like",
458
+
path: ".subject.uri",
459
+
}
460
+
: undefined
461
+
);
432
462
433
463
return (
434
-
<div className="px-4 py-4 border-b flex flex-col gap-1">
464
+
<Link
465
+
className={`px-4 py-4 ${!disableBottomBorder && "border-b"} flex flex-col gap-1`}
466
+
to="/profile/$did/feed/$rkey"
467
+
params={{ did: aturi.host, rkey: aturi.rkey }}
468
+
>
435
469
<div className="flex flex-row gap-3">
436
470
<div className="min-w-10 min-h-10">
437
-
<img src={getAvatarThumbnailUrl(feed) || defaultpfp} className="h-10 w-10 rounded border" />
471
+
<img
472
+
src={getAvatarThumbnailUrl(feed) || defaultpfp}
473
+
className="h-10 w-10 rounded border"
474
+
/>
438
475
</div>
439
476
<div className="flex flex-col">
440
477
<span className="">{name}</span>
441
-
<span className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center">{feed.value.did || aturi.rkey}</span>
478
+
<span className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center">
479
+
{feed.value.did || aturi.rkey}
480
+
</span>
442
481
</div>
443
482
<div className="flex-1" />
444
483
{/* <div className="button bg-red-500 rounded-full min-w-[60px]" /> */}
445
484
</div>
446
485
<span className=" text-sm">{feed.value?.description}</span>
447
-
{!listmode && (<span className=" text-sm dark:text-gray-400 text-gray-500">Liked by {(likes as unknown as any)?.total as number || 0} users</span>)}
448
-
</div>
486
+
{!listmode && (
487
+
<span className=" text-sm dark:text-gray-400 text-gray-500">
488
+
Liked by {((likes as unknown as any)?.total as number) || 0} users
489
+
</span>
490
+
)}
491
+
</Link>
449
492
);
450
493
}
451
-
452
494
453
495
function ListsTab({ did }: { did: string }) {
454
496
useReusableTabScrollRestore(`Profile` + did);
···
487
529
if (!feed || !feed?.value) return;
488
530
const feedGenRecord =
489
531
feed.value as unknown as ATPAPI.AppBskyFeedGenerator.Record;
490
-
return <FeedItemRender listmode={true} feed={feed as any} key={feed.uri} />;
532
+
return (
533
+
<FeedItemRender listmode={true} feed={feed as any} key={feed.uri} />
534
+
);
491
535
})}
492
536
</div>
493
537
+7
-4
src/utils/useQuery.ts
+7
-4
src/utils/useQuery.ts
···
573
573
isAuthed: boolean;
574
574
pdsUrl?: string;
575
575
feedServiceDid?: string;
576
+
// todo the hell is a unauthedfeedurl
577
+
unauthedfeedurl?: string;
576
578
}) {
577
-
const { feedUri, agent, isAuthed, pdsUrl, feedServiceDid } = options;
579
+
const { feedUri, agent, isAuthed, pdsUrl, feedServiceDid, unauthedfeedurl } = options;
578
580
579
581
return queryOptions({
580
582
queryKey: ["feedSkeleton", feedUri, { isAuthed, did: agent?.did }],
···
582
584
queryFn: async ({ pageParam }: QueryFunctionContext): Promise<FeedSkeletonPage> => {
583
585
const cursorParam = pageParam ? `&cursor=${pageParam}` : "";
584
586
585
-
if (isAuthed) {
587
+
if (isAuthed && !unauthedfeedurl) {
586
588
if (!agent || !pdsUrl || !feedServiceDid) {
587
589
throw new Error("Missing required info for authenticated feed fetch.");
588
590
}
···
597
599
if (!res.ok) throw new Error(`Authenticated feed fetch failed: ${res.statusText}`);
598
600
return (await res.json()) as FeedSkeletonPage;
599
601
} else {
600
-
const url = `https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`;
602
+
const url = `https://${unauthedfeedurl ? unauthedfeedurl : "discover.bsky.app"}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`;
601
603
const res = await fetch(url);
602
604
if (!res.ok) throw new Error(`Public feed fetch failed: ${res.statusText}`);
603
605
return (await res.json()) as FeedSkeletonPage;
···
612
614
isAuthed: boolean;
613
615
pdsUrl?: string;
614
616
feedServiceDid?: string;
617
+
unauthedfeedurl?: string;
615
618
}) {
616
619
const { queryKey, queryFn } = constructInfiniteFeedSkeletonQuery(options);
617
620
···
622
625
getNextPageParam: (lastPage) => lastPage.cursor as null | undefined,
623
626
staleTime: Infinity,
624
627
refetchOnWindowFocus: false,
625
-
enabled: !!options.feedUri && (options.isAuthed ? !!options.agent && !!options.pdsUrl && !!options.feedServiceDid : true),
628
+
enabled: !!options.feedUri && (options.isAuthed ? (!!options.agent && !!options.pdsUrl || !!options.unauthedfeedurl) && !!options.feedServiceDid : true),
626
629
}), queryKey: queryKey};
627
630
}
628
631