tangled
alpha
login
or
join now
whey.party
/
red-dwarf
82
fork
atom
an independent Bluesky client using Constellation, PDS Queries, and other services
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
client
app
82
fork
atom
overview
issues
25
pulls
pipelines
post interactions
rimar1337
4 months ago
61ce2144
de4321b1
+625
-157
9 changed files
expand all
collapse all
unified
split
src
auto-imports.d.ts
components
UniversalPostRenderer.tsx
routeTree.gen.ts
routes
notifications.tsx
profile.$did
post.$rkey.liked-by.tsx
post.$rkey.quotes.tsx
post.$rkey.reposted-by.tsx
post.$rkey.tsx
utils
useQuery.ts
+1
src/auto-imports.d.ts
···
19
19
const IconMaterialSymbolsTag: typeof import('~icons/material-symbols/tag.jsx').default
20
20
const IconMdiAccountCircle: typeof import('~icons/mdi/account-circle.jsx').default
21
21
const IconMdiAccountPlus: typeof import('~icons/mdi/account-plus.jsx').default
22
22
+
const IconMdiMessageReplyTextOutline: typeof import('~icons/mdi/message-reply-text-outline.jsx').default
22
23
const IconMdiPencilOutline: typeof import('~icons/mdi/pencil-outline.jsx').default
23
24
}
+18
-4
src/components/UniversalPostRenderer.tsx
···
41
41
ref?: React.Ref<HTMLDivElement>;
42
42
dataIndexPropPass?: number;
43
43
nopics?: boolean;
44
44
+
concise?: boolean;
44
45
lightboxCallback?: (d: LightboxProps) => void;
45
46
maxReplies?: number;
46
47
isQuote?: boolean;
···
152
153
ref,
153
154
dataIndexPropPass,
154
155
nopics,
156
156
+
concise,
155
157
lightboxCallback,
156
158
maxReplies,
157
159
isQuote,
···
536
538
ref={ref}
537
539
dataIndexPropPass={dataIndexPropPass}
538
540
nopics={nopics}
541
541
+
concise={concise}
539
542
lightboxCallback={lightboxCallback}
540
543
maxReplies={maxReplies}
541
544
isQuote={isQuote}
···
567
570
ref={ref}
568
571
dataIndexPropPass={dataIndexPropPass}
569
572
nopics={nopics}
573
573
+
concise={concise}
570
574
lightboxCallback={lightboxCallback}
571
575
maxReplies={
572
576
maxReplies && maxReplies > 0 ? maxReplies - 1 : undefined
···
636
640
ref,
637
641
dataIndexPropPass,
638
642
nopics,
643
643
+
concise,
639
644
lightboxCallback,
640
645
maxReplies,
641
646
isQuote,
···
657
662
ref?: React.Ref<HTMLDivElement>;
658
663
dataIndexPropPass?: number;
659
664
nopics?: boolean;
665
665
+
concise?: boolean;
660
666
lightboxCallback?: (d: LightboxProps) => void;
661
667
maxReplies?: number;
662
668
isQuote?: boolean;
···
874
880
ref={ref}
875
881
dataIndexPropPass={dataIndexPropPass}
876
882
nopics={nopics}
883
883
+
concise={concise}
877
884
lightboxCallback={lightboxCallback}
878
885
maxReplies={maxReplies}
879
886
isQuote={isQuote}
···
1327
1334
ref,
1328
1335
dataIndexPropPass,
1329
1336
nopics,
1337
1337
+
concise,
1330
1338
lightboxCallback,
1331
1339
maxReplies,
1332
1340
}: {
···
1353
1361
ref?: React.Ref<HTMLDivElement>;
1354
1362
dataIndexPropPass?: number;
1355
1363
nopics?: boolean;
1364
1364
+
concise?: boolean;
1356
1365
lightboxCallback?: (d: LightboxProps) => void;
1357
1366
maxReplies?: number;
1358
1367
}) {
···
1759
1768
<div
1760
1769
style={{
1761
1770
fontSize: 16,
1762
1762
-
marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8,
1771
1771
+
marginBottom: !post.embed || concise ? 0 : 8,
1763
1772
whiteSpace: "pre-wrap",
1764
1773
textAlign: "left",
1765
1774
overflowWrap: "anywhere",
1766
1775
wordBreak: "break-word",
1767
1767
-
//color: theme.text,
1776
1776
+
...(concise && {
1777
1777
+
display: "-webkit-box",
1778
1778
+
WebkitBoxOrient: "vertical",
1779
1779
+
WebkitLineClamp: 2,
1780
1780
+
overflow: "hidden",
1781
1781
+
}),
1768
1782
}}
1769
1783
className="text-gray-900 dark:text-gray-100"
1770
1784
>
···
1787
1801
</>
1788
1802
)}
1789
1803
</div>
1790
1790
-
{post.embed && depth < 1 ? (
1804
1804
+
{post.embed && depth < 1 && !concise ? (
1791
1805
<PostEmbeds
1792
1806
embed={post.embed}
1793
1807
//moderation={moderation}
···
1809
1823
</div>
1810
1824
</>
1811
1825
)}
1812
1812
-
<div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}>
1826
1826
+
<div style={{ paddingTop: post.embed && !concise && depth < 1 ? 4 : 0 }}>
1813
1827
<>
1814
1828
{expanded && (
1815
1829
<div
+66
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
24
+
import { Route as ProfileDidPostRkeyRepostedByRouteImport } from './routes/profile.$did/post.$rkey.reposted-by'
25
25
+
import { Route as ProfileDidPostRkeyQuotesRouteImport } from './routes/profile.$did/post.$rkey.quotes'
26
26
+
import { Route as ProfileDidPostRkeyLikedByRouteImport } from './routes/profile.$did/post.$rkey.liked-by'
24
27
import { Route as ProfileDidPostRkeyImageIRouteImport } from './routes/profile.$did/post.$rkey.image.$i'
25
28
26
29
const SettingsRoute = SettingsRouteImport.update({
···
84
87
path: '/profile/$did/post/$rkey',
85
88
getParentRoute: () => rootRouteImport,
86
89
} as any)
90
90
+
const ProfileDidPostRkeyRepostedByRoute =
91
91
+
ProfileDidPostRkeyRepostedByRouteImport.update({
92
92
+
id: '/reposted-by',
93
93
+
path: '/reposted-by',
94
94
+
getParentRoute: () => ProfileDidPostRkeyRoute,
95
95
+
} as any)
96
96
+
const ProfileDidPostRkeyQuotesRoute =
97
97
+
ProfileDidPostRkeyQuotesRouteImport.update({
98
98
+
id: '/quotes',
99
99
+
path: '/quotes',
100
100
+
getParentRoute: () => ProfileDidPostRkeyRoute,
101
101
+
} as any)
102
102
+
const ProfileDidPostRkeyLikedByRoute =
103
103
+
ProfileDidPostRkeyLikedByRouteImport.update({
104
104
+
id: '/liked-by',
105
105
+
path: '/liked-by',
106
106
+
getParentRoute: () => ProfileDidPostRkeyRoute,
107
107
+
} as any)
87
108
const ProfileDidPostRkeyImageIRoute =
88
109
ProfileDidPostRkeyImageIRouteImport.update({
89
110
id: '/image/$i',
···
102
123
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
103
124
'/profile/$did': typeof ProfileDidIndexRoute
104
125
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
126
126
+
'/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute
127
127
+
'/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute
128
128
+
'/profile/$did/post/$rkey/reposted-by': typeof ProfileDidPostRkeyRepostedByRoute
105
129
'/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute
106
130
}
107
131
export interface FileRoutesByTo {
···
115
139
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
116
140
'/profile/$did': typeof ProfileDidIndexRoute
117
141
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
142
142
+
'/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute
143
143
+
'/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute
144
144
+
'/profile/$did/post/$rkey/reposted-by': typeof ProfileDidPostRkeyRepostedByRoute
118
145
'/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute
119
146
}
120
147
export interface FileRoutesById {
···
131
158
'/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
132
159
'/profile/$did/': typeof ProfileDidIndexRoute
133
160
'/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren
161
161
+
'/profile/$did/post/$rkey/liked-by': typeof ProfileDidPostRkeyLikedByRoute
162
162
+
'/profile/$did/post/$rkey/quotes': typeof ProfileDidPostRkeyQuotesRoute
163
163
+
'/profile/$did/post/$rkey/reposted-by': typeof ProfileDidPostRkeyRepostedByRoute
134
164
'/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute
135
165
}
136
166
export interface FileRouteTypes {
···
146
176
| '/route-b'
147
177
| '/profile/$did'
148
178
| '/profile/$did/post/$rkey'
179
179
+
| '/profile/$did/post/$rkey/liked-by'
180
180
+
| '/profile/$did/post/$rkey/quotes'
181
181
+
| '/profile/$did/post/$rkey/reposted-by'
149
182
| '/profile/$did/post/$rkey/image/$i'
150
183
fileRoutesByTo: FileRoutesByTo
151
184
to:
···
159
192
| '/route-b'
160
193
| '/profile/$did'
161
194
| '/profile/$did/post/$rkey'
195
195
+
| '/profile/$did/post/$rkey/liked-by'
196
196
+
| '/profile/$did/post/$rkey/quotes'
197
197
+
| '/profile/$did/post/$rkey/reposted-by'
162
198
| '/profile/$did/post/$rkey/image/$i'
163
199
id:
164
200
| '__root__'
···
174
210
| '/_pathlessLayout/_nested-layout/route-b'
175
211
| '/profile/$did/'
176
212
| '/profile/$did/post/$rkey'
213
213
+
| '/profile/$did/post/$rkey/liked-by'
214
214
+
| '/profile/$did/post/$rkey/quotes'
215
215
+
| '/profile/$did/post/$rkey/reposted-by'
177
216
| '/profile/$did/post/$rkey/image/$i'
178
217
fileRoutesById: FileRoutesById
179
218
}
···
275
314
preLoaderRoute: typeof ProfileDidPostRkeyRouteImport
276
315
parentRoute: typeof rootRouteImport
277
316
}
317
317
+
'/profile/$did/post/$rkey/reposted-by': {
318
318
+
id: '/profile/$did/post/$rkey/reposted-by'
319
319
+
path: '/reposted-by'
320
320
+
fullPath: '/profile/$did/post/$rkey/reposted-by'
321
321
+
preLoaderRoute: typeof ProfileDidPostRkeyRepostedByRouteImport
322
322
+
parentRoute: typeof ProfileDidPostRkeyRoute
323
323
+
}
324
324
+
'/profile/$did/post/$rkey/quotes': {
325
325
+
id: '/profile/$did/post/$rkey/quotes'
326
326
+
path: '/quotes'
327
327
+
fullPath: '/profile/$did/post/$rkey/quotes'
328
328
+
preLoaderRoute: typeof ProfileDidPostRkeyQuotesRouteImport
329
329
+
parentRoute: typeof ProfileDidPostRkeyRoute
330
330
+
}
331
331
+
'/profile/$did/post/$rkey/liked-by': {
332
332
+
id: '/profile/$did/post/$rkey/liked-by'
333
333
+
path: '/liked-by'
334
334
+
fullPath: '/profile/$did/post/$rkey/liked-by'
335
335
+
preLoaderRoute: typeof ProfileDidPostRkeyLikedByRouteImport
336
336
+
parentRoute: typeof ProfileDidPostRkeyRoute
337
337
+
}
278
338
'/profile/$did/post/$rkey/image/$i': {
279
339
id: '/profile/$did/post/$rkey/image/$i'
280
340
path: '/image/$i'
···
316
376
)
317
377
318
378
interface ProfileDidPostRkeyRouteChildren {
379
379
+
ProfileDidPostRkeyLikedByRoute: typeof ProfileDidPostRkeyLikedByRoute
380
380
+
ProfileDidPostRkeyQuotesRoute: typeof ProfileDidPostRkeyQuotesRoute
381
381
+
ProfileDidPostRkeyRepostedByRoute: typeof ProfileDidPostRkeyRepostedByRoute
319
382
ProfileDidPostRkeyImageIRoute: typeof ProfileDidPostRkeyImageIRoute
320
383
}
321
384
322
385
const ProfileDidPostRkeyRouteChildren: ProfileDidPostRkeyRouteChildren = {
386
386
+
ProfileDidPostRkeyLikedByRoute: ProfileDidPostRkeyLikedByRoute,
387
387
+
ProfileDidPostRkeyQuotesRoute: ProfileDidPostRkeyQuotesRoute,
388
388
+
ProfileDidPostRkeyRepostedByRoute: ProfileDidPostRkeyRepostedByRoute,
323
389
ProfileDidPostRkeyImageIRoute: ProfileDidPostRkeyImageIRoute,
324
390
}
325
391
+96
-56
src/routes/notifications.tsx
···
1
1
import { AtUri } from "@atproto/api";
2
2
import * as TabsPrimitive from "@radix-ui/react-tabs";
3
3
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
4
4
-
import { createFileRoute, useNavigate } from "@tanstack/react-router";
4
4
+
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
5
5
import { useAtom } from "jotai";
6
6
import * as React from "react";
7
7
···
47
47
export const Route = createFileRoute("/notifications")({
48
48
component: NotificationsComponent,
49
49
});
50
50
-
51
50
52
51
export default function NotificationsTabs() {
53
52
const [activeTab, setActiveTab] = React.useState("mentions");
···
225
224
);
226
225
}
227
226
228
228
-
229
227
function PostInteractionsTab() {
230
228
const { agent } = useAuth();
231
229
const { data: identity } = useQueryIdentity(agent?.did);
···
274
272
);
275
273
}
276
274
275
275
+
const ORDER: ("like" | "repost" | "reply" | "quote")[] = [
276
276
+
"like",
277
277
+
"repost",
278
278
+
"reply",
279
279
+
"quote",
280
280
+
];
281
281
+
277
282
function PostInteractionsItem({ uri }: { uri: string }) {
278
283
const { data: links } = useQueryConstellation({
279
284
method: "/links/all",
280
285
target: uri,
281
286
});
282
287
283
283
-
const interactions = React.useMemo(() => {
284
284
-
const likes =
285
285
-
links?.links?.["app.bsky.feed.like"]?.[".subject.uri"]?.records || 0;
286
286
-
const replies =
287
287
-
links?.links?.["app.bsky.feed.post"]?.[".reply.parent.uri"]?.records || 0;
288
288
-
const reposts =
289
289
-
links?.links?.["app.bsky.feed.repost"]?.[".subject.uri"]?.records || 0;
290
290
-
const quotes1 =
291
291
-
links?.links?.["app.bsky.feed.post"]?.[".embed.record.uri"]?.records || 0;
292
292
-
const quotes2 =
293
293
-
links?.links?.["app.bsky.feed.post"]?.[".embed.record.record.uri"]
294
294
-
?.records || 0;
288
288
+
const likes =
289
289
+
links?.links?.["app.bsky.feed.like"]?.[".subject.uri"]?.records || 0;
290
290
+
const replies =
291
291
+
links?.links?.["app.bsky.feed.post"]?.[".reply.parent.uri"]?.records || 0;
292
292
+
const reposts =
293
293
+
links?.links?.["app.bsky.feed.repost"]?.[".subject.uri"]?.records || 0;
294
294
+
const quotes1 =
295
295
+
links?.links?.["app.bsky.feed.post"]?.[".embed.record.uri"]?.records || 0;
296
296
+
const quotes2 =
297
297
+
links?.links?.["app.bsky.feed.post"]?.[".embed.record.record.uri"]
298
298
+
?.records || 0;
299
299
+
const quotes = quotes1 + quotes2;
295
300
296
296
-
const totals = {
297
297
-
likes,
298
298
-
replies,
299
299
-
reposts,
300
300
-
quotes: quotes1 + quotes2,
301
301
-
};
302
302
-
303
303
-
const list = (
304
304
-
[
305
305
-
["reply", totals.replies],
306
306
-
["repost", totals.reposts],
307
307
-
["like", totals.likes],
308
308
-
["quote", totals.quotes],
309
309
-
] as const
310
310
-
).filter(([, count]) => count > 0);
311
311
-
312
312
-
return { totals, list };
313
313
-
}, [links]);
301
301
+
const all = likes + replies + reposts + quotes;
314
302
315
303
return (
316
316
-
<div className="flex flex-col border-b pb-8">
317
317
-
<div className="border rounded-xl mx-4 mt-4 ">
304
304
+
<div className="flex flex-col">
305
305
+
<div className="border rounded-xl mx-4 mt-4 overflow-hidden">
318
306
<UniversalPostRendererATURILoader
319
307
isQuote
320
308
key={uri}
321
309
atUri={uri}
322
322
-
nopics
310
310
+
nopics={true}
311
311
+
concise={true}
323
312
/>
324
324
-
</div>
325
325
-
<div className="flex flex-col">
326
326
-
{interactions.list.map(([type, count]) => (
327
327
-
<InteractionsButton key={type} type={type} uri={uri} count={count} />
328
328
-
))}
313
313
+
<div className="flex flex-col divide-x">
314
314
+
<InteractionsButton
315
315
+
key={likes}
316
316
+
type={"like"}
317
317
+
uri={uri}
318
318
+
count={likes}
319
319
+
/>
320
320
+
<InteractionsButton
321
321
+
key={reposts}
322
322
+
type={"repost"}
323
323
+
uri={uri}
324
324
+
count={reposts}
325
325
+
/>
326
326
+
<InteractionsButton
327
327
+
key={replies}
328
328
+
type={"reply"}
329
329
+
uri={uri}
330
330
+
count={replies}
331
331
+
/>
332
332
+
<InteractionsButton
333
333
+
key={quotes}
334
334
+
type={"quote"}
335
335
+
uri={uri}
336
336
+
count={quotes}
337
337
+
/>
338
338
+
{!all && (
339
339
+
<div className="text-center text-gray-500 dark:text-gray-400 pb-3 pt-2 border-t">
340
340
+
No interactions yet.
341
341
+
</div>
342
342
+
)}
343
343
+
</div>
329
344
</div>
330
345
</div>
331
346
);
···
340
355
uri: string;
341
356
count: number;
342
357
}) {
358
358
+
if (!count) return <></>;
359
359
+
const aturi = new AtUri(uri);
343
360
return (
344
344
-
<div className="flex-1 border-t py-2 px-4 flex flex-row items-center gap-2">
361
361
+
<Link
362
362
+
to={
363
363
+
`/profile/$did/post/$rkey` +
364
364
+
(type === "like"
365
365
+
? "/liked-by"
366
366
+
: type === "repost"
367
367
+
? "/reposted-by"
368
368
+
: type === "quote"
369
369
+
? "/quotes"
370
370
+
: "")
371
371
+
}
372
372
+
params={{
373
373
+
did: aturi.host,
374
374
+
rkey: aturi.rkey,
375
375
+
}}
376
376
+
className="flex-1 border-t py-2 px-4 flex flex-row items-center gap-2 transition-colors hover:bg-gray-100 hover:dark:bg-gray-800"
377
377
+
>
345
378
{type === "like" ? (
346
379
<MdiCardsHeartOutline height={22} width={22} />
347
380
) : type === "repost" ? (
348
381
<MdiRepeat height={22} width={22} />
349
382
) : type === "reply" ? (
350
383
<MdiCommentOutline height={22} width={22} />
384
384
+
) : type === "quote" ? (
385
385
+
<IconMdiMessageReplyTextOutline
386
386
+
height={22}
387
387
+
width={22}
388
388
+
className=" text-gray-400"
389
389
+
/>
351
390
) : (
352
391
<></>
353
392
)}
354
393
{type}
355
394
{/* bad grammar replys */}
356
395
{count > 1 ? "s" : ""} <div className="flex-1" /> {count}
357
357
-
</div>
396
396
+
</Link>
358
397
);
359
398
}
360
399
361
361
-
function NotificationItem({ notification }: { notification: string }) {
400
400
+
export function NotificationItem({ notification }: { notification: string }) {
362
401
const aturi = new AtUri(notification);
363
402
const navigate = useNavigate();
364
403
const { data: identity } = useQueryIdentity(aturi.host);
···
381
420
382
421
return (
383
422
<div
384
384
-
className="flex items-center gap-3 p-4 cursor-pointer border-b flex-row"
423
423
+
className="flex items-center p-4 cursor-pointer gap-3 justify-around border-b flex-row"
385
424
onClick={() =>
386
425
aturi &&
387
426
navigate({
···
390
429
})
391
430
}
392
431
>
393
393
-
<div>
432
432
+
{/* <div>
394
433
{aturi.collection === "app.bsky.graph.follow" ? (
395
434
<IconMdiAccountPlus />
435
435
+
) : aturi.collection === "app.bsky.feed.like" ? (
436
436
+
<MdiCardsHeart />
396
437
) : (
397
438
<></>
398
439
)}
399
399
-
</div>
440
440
+
</div> */}
400
441
{profile ? (
401
442
<img
402
443
src={avatar || defaultpfp}
···
406
447
) : (
407
448
<div className="w-10 h-10 rounded-full bg-gray-300 dark:bg-gray-700" />
408
449
)}
409
409
-
<div className="flex flex-col">
410
410
-
<div className="flex flex-row gap-2">
411
411
-
<span className="font-medium text-gray-900 dark:text-gray-100">
450
450
+
<div className="flex flex-col min-w-0">
451
451
+
<div className="flex flex-row gap-2 overflow-hidden text-ellipsis whitespace-nowrap min-w-0">
452
452
+
<span className="font-medium text-gray-900 dark:text-gray-100 truncate">
412
453
{profile?.displayName || identity?.handle || "Someone"}
413
454
</span>
414
414
-
<span className="text-gray-700 dark:text-gray-400">
455
455
+
<span className="text-gray-700 dark:text-gray-400 truncate">
415
456
@{identity?.handle}
416
457
</span>
417
458
</div>
···
428
469
);
429
470
}
430
471
431
431
-
432
432
-
const EmptyState = ({ text }: { text: string }) => (
472
472
+
export const EmptyState = ({ text }: { text: string }) => (
433
473
<div className="py-10 text-center text-gray-500 dark:text-gray-400">
434
474
{text}
435
475
</div>
436
476
);
437
477
438
438
-
const LoadingState = ({ text }: { text: string }) => (
478
478
+
export const LoadingState = ({ text }: { text: string }) => (
439
479
<div className="py-10 text-center text-gray-500 dark:text-gray-400 italic">
440
480
{text}
441
481
</div>
442
482
);
443
483
444
444
-
const ErrorState = ({ error }: { error: unknown }) => (
484
484
+
export const ErrorState = ({ error }: { error: unknown }) => (
445
485
<div className="py-10 text-center text-red-600 dark:text-red-400">
446
486
Error: {(error as Error)?.message || "Something went wrong."}
447
487
</div>
448
448
-
);
488
488
+
);
+100
src/routes/profile.$did/post.$rkey.liked-by.tsx
···
1
1
+
import { useInfiniteQuery } from "@tanstack/react-query";
2
2
+
import { createFileRoute } from "@tanstack/react-router";
3
3
+
import { useAtom } from "jotai";
4
4
+
import React from "react";
5
5
+
6
6
+
import { Header } from "~/components/Header";
7
7
+
import { constellationURLAtom } from "~/utils/atoms";
8
8
+
import { useQueryIdentity, yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks } from "~/utils/useQuery";
9
9
+
10
10
+
import {
11
11
+
EmptyState,
12
12
+
ErrorState,
13
13
+
LoadingState,
14
14
+
NotificationItem,
15
15
+
} from "../notifications";
16
16
+
17
17
+
export const Route = createFileRoute("/profile/$did/post/$rkey/liked-by")({
18
18
+
component: RouteComponent,
19
19
+
});
20
20
+
21
21
+
function RouteComponent() {
22
22
+
const { did, rkey } = Route.useParams();
23
23
+
const { data: identity } = useQueryIdentity(did);
24
24
+
const atUri = identity?.did && rkey ? `at://${decodeURIComponent(identity.did)}/app.bsky.feed.post/${rkey}` : '';
25
25
+
26
26
+
const [constellationurl] = useAtom(constellationURLAtom);
27
27
+
const infinitequeryresults = useInfiniteQuery({
28
28
+
...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(
29
29
+
{
30
30
+
constellation: constellationurl,
31
31
+
method: "/links",
32
32
+
target: atUri,
33
33
+
collection: "app.bsky.feed.like",
34
34
+
path: ".subject.uri",
35
35
+
}
36
36
+
),
37
37
+
enabled: !!atUri,
38
38
+
});
39
39
+
40
40
+
const {
41
41
+
data: infiniteLikesData,
42
42
+
fetchNextPage,
43
43
+
hasNextPage,
44
44
+
isFetchingNextPage,
45
45
+
isLoading,
46
46
+
isError,
47
47
+
error,
48
48
+
} = infinitequeryresults;
49
49
+
50
50
+
const likesAturis = React.useMemo(() => {
51
51
+
// Get all replies from the standard infinite query
52
52
+
return (
53
53
+
infiniteLikesData?.pages.flatMap(
54
54
+
(page) =>
55
55
+
page?.linking_records.map(
56
56
+
(r) => `at://${r.did}/${r.collection}/${r.rkey}`
57
57
+
) ?? []
58
58
+
) ?? []
59
59
+
);
60
60
+
}, [infiniteLikesData]);
61
61
+
62
62
+
return (
63
63
+
<>
64
64
+
<Header
65
65
+
title={`Liked By`}
66
66
+
backButtonCallback={() => {
67
67
+
if (window.history.length > 1) {
68
68
+
window.history.back();
69
69
+
} else {
70
70
+
window.location.assign("/");
71
71
+
}
72
72
+
}}
73
73
+
/>
74
74
+
75
75
+
<>
76
76
+
{(() => {
77
77
+
if (isLoading) return <LoadingState text="Loading likes..." />;
78
78
+
if (isError) return <ErrorState error={error} />;
79
79
+
80
80
+
if (!likesAturis?.length)
81
81
+
return <EmptyState text="No likes yet." />;
82
82
+
})()}
83
83
+
</>
84
84
+
85
85
+
{likesAturis.map((m) => (
86
86
+
<NotificationItem key={m} notification={m} />
87
87
+
))}
88
88
+
89
89
+
{hasNextPage && (
90
90
+
<button
91
91
+
onClick={() => fetchNextPage()}
92
92
+
disabled={isFetchingNextPage}
93
93
+
className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50"
94
94
+
>
95
95
+
{isFetchingNextPage ? "Loading..." : "Load More"}
96
96
+
</button>
97
97
+
)}
98
98
+
</>
99
99
+
);
100
100
+
}
+141
src/routes/profile.$did/post.$rkey.quotes.tsx
···
1
1
+
import { useInfiniteQuery } from "@tanstack/react-query";
2
2
+
import { createFileRoute } from "@tanstack/react-router";
3
3
+
import { useAtom } from "jotai";
4
4
+
import React from "react";
5
5
+
6
6
+
import { Header } from "~/components/Header";
7
7
+
import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
8
8
+
import { constellationURLAtom } from "~/utils/atoms";
9
9
+
import { type linksRecord,useQueryIdentity, yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks } from "~/utils/useQuery";
10
10
+
11
11
+
import {
12
12
+
EmptyState,
13
13
+
ErrorState,
14
14
+
LoadingState,
15
15
+
} from "../notifications";
16
16
+
17
17
+
export const Route = createFileRoute("/profile/$did/post/$rkey/quotes")({
18
18
+
component: RouteComponent,
19
19
+
});
20
20
+
21
21
+
function RouteComponent() {
22
22
+
const { did, rkey } = Route.useParams();
23
23
+
const { data: identity } = useQueryIdentity(did);
24
24
+
const atUri = identity?.did && rkey ? `at://${decodeURIComponent(identity.did)}/app.bsky.feed.post/${rkey}` : '';
25
25
+
26
26
+
const [constellationurl] = useAtom(constellationURLAtom);
27
27
+
const infinitequeryresultsWithoutMedia = useInfiniteQuery({
28
28
+
...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(
29
29
+
{
30
30
+
constellation: constellationurl,
31
31
+
method: "/links",
32
32
+
target: atUri,
33
33
+
collection: "app.bsky.feed.post",
34
34
+
path: ".embed.record.uri", // embed.record.record.uri and embed.record.uri
35
35
+
}
36
36
+
),
37
37
+
enabled: !!atUri,
38
38
+
});
39
39
+
const infinitequeryresultsWithMedia = useInfiniteQuery({
40
40
+
...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(
41
41
+
{
42
42
+
constellation: constellationurl,
43
43
+
method: "/links",
44
44
+
target: atUri,
45
45
+
collection: "app.bsky.feed.post",
46
46
+
path: ".embed.record.record.uri", // embed.record.record.uri and embed.record.uri
47
47
+
}
48
48
+
),
49
49
+
enabled: !!atUri,
50
50
+
});
51
51
+
52
52
+
const {
53
53
+
data: infiniteQuotesDataWithoutMedia,
54
54
+
fetchNextPage: fetchNextPageWithoutMedia,
55
55
+
hasNextPage: hasNextPageWithoutMedia,
56
56
+
isFetchingNextPage: isFetchingNextPageWithoutMedia,
57
57
+
isLoading: isLoadingWithoutMedia,
58
58
+
isError: isErrorWithoutMedia,
59
59
+
error: errorWithoutMedia,
60
60
+
} = infinitequeryresultsWithoutMedia;
61
61
+
const {
62
62
+
data: infiniteQuotesDataWithMedia,
63
63
+
fetchNextPage: fetchNextPageWithMedia,
64
64
+
hasNextPage: hasNextPageWithMedia,
65
65
+
isFetchingNextPage: isFetchingNextPageWithMedia,
66
66
+
isLoading: isLoadingWithMedia,
67
67
+
isError: isErrorWithMedia,
68
68
+
error: errorWithMedia,
69
69
+
} = infinitequeryresultsWithMedia;
70
70
+
71
71
+
const fetchNextPage = async () => {
72
72
+
await Promise.all([
73
73
+
hasNextPageWithMedia && fetchNextPageWithMedia(),
74
74
+
hasNextPageWithoutMedia && fetchNextPageWithoutMedia(),
75
75
+
]);
76
76
+
};
77
77
+
78
78
+
const hasNextPage = hasNextPageWithMedia || hasNextPageWithoutMedia;
79
79
+
const isFetchingNextPage = isFetchingNextPageWithMedia || isFetchingNextPageWithoutMedia;
80
80
+
const isLoading = isLoadingWithMedia || isLoadingWithoutMedia;
81
81
+
82
82
+
const allQuotes = React.useMemo(() => {
83
83
+
const withPages = infiniteQuotesDataWithMedia?.pages ?? [];
84
84
+
const withoutPages = infiniteQuotesDataWithoutMedia?.pages ?? [];
85
85
+
const maxLen = Math.max(withPages.length, withoutPages.length);
86
86
+
const merged: linksRecord[] = [];
87
87
+
88
88
+
for (let i = 0; i < maxLen; i++) {
89
89
+
const a = withPages[i]?.linking_records ?? [];
90
90
+
const b = withoutPages[i]?.linking_records ?? [];
91
91
+
const mergedPage = [...a, ...b].sort((b, a) => a.rkey.localeCompare(b.rkey));
92
92
+
merged.push(...mergedPage);
93
93
+
}
94
94
+
95
95
+
return merged;
96
96
+
}, [infiniteQuotesDataWithMedia?.pages, infiniteQuotesDataWithoutMedia?.pages]);
97
97
+
98
98
+
const quotesAturis = React.useMemo(() => {
99
99
+
return allQuotes.flatMap((r) => `at://${r.did}/${r.collection}/${r.rkey}`);
100
100
+
}, [allQuotes]);
101
101
+
102
102
+
return (
103
103
+
<>
104
104
+
<Header
105
105
+
title={`Quotes`}
106
106
+
backButtonCallback={() => {
107
107
+
if (window.history.length > 1) {
108
108
+
window.history.back();
109
109
+
} else {
110
110
+
window.location.assign("/");
111
111
+
}
112
112
+
}}
113
113
+
/>
114
114
+
115
115
+
<>
116
116
+
{(() => {
117
117
+
if (isLoading) return <LoadingState text="Loading quotes..." />;
118
118
+
if (isErrorWithMedia) return <ErrorState error={errorWithMedia} />;
119
119
+
if (isErrorWithoutMedia) return <ErrorState error={errorWithoutMedia} />;
120
120
+
121
121
+
if (!quotesAturis?.length)
122
122
+
return <EmptyState text="No quotes yet." />;
123
123
+
})()}
124
124
+
</>
125
125
+
126
126
+
{quotesAturis.map((m) => (
127
127
+
<UniversalPostRendererATURILoader key={m} atUri={m} />
128
128
+
))}
129
129
+
130
130
+
{hasNextPage && (
131
131
+
<button
132
132
+
onClick={() => fetchNextPage()}
133
133
+
disabled={isFetchingNextPage}
134
134
+
className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50"
135
135
+
>
136
136
+
{isFetchingNextPage ? "Loading..." : "Load More"}
137
137
+
</button>
138
138
+
)}
139
139
+
</>
140
140
+
);
141
141
+
}
+100
src/routes/profile.$did/post.$rkey.reposted-by.tsx
···
1
1
+
import { useInfiniteQuery } from "@tanstack/react-query";
2
2
+
import { createFileRoute } from "@tanstack/react-router";
3
3
+
import { useAtom } from "jotai";
4
4
+
import React from "react";
5
5
+
6
6
+
import { Header } from "~/components/Header";
7
7
+
import { constellationURLAtom } from "~/utils/atoms";
8
8
+
import { useQueryIdentity, yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks } from "~/utils/useQuery";
9
9
+
10
10
+
import {
11
11
+
EmptyState,
12
12
+
ErrorState,
13
13
+
LoadingState,
14
14
+
NotificationItem,
15
15
+
} from "../notifications";
16
16
+
17
17
+
export const Route = createFileRoute("/profile/$did/post/$rkey/reposted-by")({
18
18
+
component: RouteComponent,
19
19
+
});
20
20
+
21
21
+
function RouteComponent() {
22
22
+
const { did, rkey } = Route.useParams();
23
23
+
const { data: identity } = useQueryIdentity(did);
24
24
+
const atUri = identity?.did && rkey ? `at://${decodeURIComponent(identity.did)}/app.bsky.feed.post/${rkey}` : '';
25
25
+
26
26
+
const [constellationurl] = useAtom(constellationURLAtom);
27
27
+
const infinitequeryresults = useInfiniteQuery({
28
28
+
...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(
29
29
+
{
30
30
+
constellation: constellationurl,
31
31
+
method: "/links",
32
32
+
target: atUri,
33
33
+
collection: "app.bsky.feed.repost",
34
34
+
path: ".subject.uri",
35
35
+
}
36
36
+
),
37
37
+
enabled: !!atUri,
38
38
+
});
39
39
+
40
40
+
const {
41
41
+
data: infiniteRepostsData,
42
42
+
fetchNextPage,
43
43
+
hasNextPage,
44
44
+
isFetchingNextPage,
45
45
+
isLoading,
46
46
+
isError,
47
47
+
error,
48
48
+
} = infinitequeryresults;
49
49
+
50
50
+
const repostsAturis = React.useMemo(() => {
51
51
+
// Get all replies from the standard infinite query
52
52
+
return (
53
53
+
infiniteRepostsData?.pages.flatMap(
54
54
+
(page) =>
55
55
+
page?.linking_records.map(
56
56
+
(r) => `at://${r.did}/${r.collection}/${r.rkey}`
57
57
+
) ?? []
58
58
+
) ?? []
59
59
+
);
60
60
+
}, [infiniteRepostsData]);
61
61
+
62
62
+
return (
63
63
+
<>
64
64
+
<Header
65
65
+
title={`Reposted By`}
66
66
+
backButtonCallback={() => {
67
67
+
if (window.history.length > 1) {
68
68
+
window.history.back();
69
69
+
} else {
70
70
+
window.location.assign("/");
71
71
+
}
72
72
+
}}
73
73
+
/>
74
74
+
75
75
+
<>
76
76
+
{(() => {
77
77
+
if (isLoading) return <LoadingState text="Loading reposts..." />;
78
78
+
if (isError) return <ErrorState error={error} />;
79
79
+
80
80
+
if (!repostsAturis?.length)
81
81
+
return <EmptyState text="No reposts yet." />;
82
82
+
})()}
83
83
+
</>
84
84
+
85
85
+
{repostsAturis.map((m) => (
86
86
+
<NotificationItem key={m} notification={m} />
87
87
+
))}
88
88
+
89
89
+
{hasNextPage && (
90
90
+
<button
91
91
+
onClick={() => fetchNextPage()}
92
92
+
disabled={isFetchingNextPage}
93
93
+
className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50"
94
94
+
>
95
95
+
{isFetchingNextPage ? "Loading..." : "Load More"}
96
96
+
</button>
97
97
+
)}
98
98
+
</>
99
99
+
);
100
100
+
}
+98
-92
src/routes/profile.$did/post.$rkey.tsx
···
1
1
import { AtUri } from "@atproto/api";
2
2
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
3
3
-
import { createFileRoute, Outlet } from "@tanstack/react-router";
3
3
+
import { createFileRoute, Outlet, useMatchRoute } from "@tanstack/react-router";
4
4
import { useAtom } from "jotai";
5
5
import React, { useLayoutEffect } from "react";
6
6
···
52
52
nopics?: boolean;
53
53
lightboxCallback?: (d: LightboxProps) => void;
54
54
}) {
55
55
+
const matchRoute = useMatchRoute()
56
56
+
const showMainPostRoute = !!matchRoute({ to: '/profile/$did/post/$rkey' }) || !!matchRoute({ to: '/profile/$did/post/$rkey/image/$i' })
57
57
+
55
58
//const { get, set } = usePersistentStore();
56
59
const queryClient = useQueryClient();
57
60
// const [resolvedDid, setResolvedDid] = React.useState<string | null>(null);
···
190
193
data: identity,
191
194
isLoading: isIdentityLoading,
192
195
error: identityError,
193
193
-
} = useQueryIdentity(did);
196
196
+
} = useQueryIdentity(showMainPostRoute ? did : undefined);
194
197
195
198
const resolvedDid = did.startsWith("did:") ? did : identity?.did;
196
199
197
200
const atUri = React.useMemo(
198
201
() =>
199
199
-
resolvedDid
202
202
+
resolvedDid && showMainPostRoute
200
203
? `at://${decodeURIComponent(resolvedDid)}/app.bsky.feed.post/${rkey}`
201
204
: undefined,
202
202
-
[resolvedDid, rkey]
205
205
+
[resolvedDid, rkey, showMainPostRoute]
203
206
);
204
207
205
205
-
const { data: mainPost } = useQueryPost(atUri);
208
208
+
const { data: mainPost } = useQueryPost(showMainPostRoute ? atUri : undefined);
206
209
207
210
console.log("atUri",atUri)
208
211
···
215
218
);
216
219
217
220
// @ts-expect-error i hate overloads
218
218
-
const { data: links } = useQueryConstellation(atUri?{
221
221
+
const { data: links } = useQueryConstellation(atUri&&showMainPostRoute?{
219
222
method: "/links/all",
220
223
target: atUri,
221
224
} : {
···
248
251
}, [links]);
249
252
250
253
const { data: opreplies } = useQueryConstellation(
251
251
-
!!opdid && replyCount && replyCount >= 25
254
254
+
showMainPostRoute && !!opdid && replyCount && replyCount >= 25
252
255
? {
253
256
method: "/links",
254
257
target: atUri,
···
289
292
path: ".reply.parent.uri",
290
293
}
291
294
),
292
292
-
enabled: !!atUri,
295
295
+
enabled: !!atUri && showMainPostRoute,
293
296
});
294
297
295
298
const {
···
371
374
const [layoutReady, setLayoutReady] = React.useState(false);
372
375
373
376
useLayoutEffect(() => {
377
377
+
if (!showMainPostRoute) return
374
378
if (parents.length > 0 && !layoutReady && mainPostRef.current) {
375
379
const mainPostElement = mainPostRef.current;
376
380
···
389
393
// eslint-disable-next-line react-hooks/set-state-in-effect
390
394
setLayoutReady(true);
391
395
}
392
392
-
}, [parents, layoutReady]);
396
396
+
}, [parents, layoutReady, showMainPostRoute]);
393
397
394
398
395
399
const [slingshoturl] = useAtom(slingshotURLAtom)
396
400
397
401
React.useEffect(() => {
398
398
-
if (parentsLoading) {
402
402
+
if (parentsLoading || !showMainPostRoute) {
399
403
setLayoutReady(false);
400
404
}
401
405
···
403
407
setLayoutReady(true);
404
408
hasPerformedInitialLayout.current = true;
405
409
}
406
406
-
}, [parentsLoading, mainPost]);
410
410
+
}, [parentsLoading, mainPost, showMainPostRoute]);
407
411
408
412
React.useEffect(() => {
409
413
if (!mainPost?.value?.reply?.parent?.uri) {
···
444
448
return () => {
445
449
ignore = true;
446
450
};
447
447
-
}, [mainPost, queryClient]);
451
451
+
}, [mainPost, queryClient, slingshoturl]);
448
452
449
449
-
if (!did || !rkey) return <div>Invalid post URI</div>;
450
450
-
if (isIdentityLoading) return <div>Resolving handle...</div>;
451
451
-
if (identityError)
453
453
+
if ((!did || !rkey) && showMainPostRoute) return <div>Invalid post URI</div>;
454
454
+
if (isIdentityLoading && showMainPostRoute) return <div>Resolving handle...</div>;
455
455
+
if (identityError && showMainPostRoute)
452
456
return <div style={{ color: "red" }}>{identityError.message}</div>;
453
453
-
if (!atUri) return <div>Could not construct post URI.</div>;
457
457
+
if (!atUri && showMainPostRoute) return <div>Could not construct post URI.</div>;
454
458
455
459
return (
456
460
<>
457
461
<Outlet />
458
458
-
<Header
459
459
-
title={`Post`}
460
460
-
backButtonCallback={() => {
461
461
-
if (window.history.length > 1) {
462
462
-
window.history.back();
463
463
-
} else {
464
464
-
window.location.assign("/");
465
465
-
}
466
466
-
}}
467
467
-
/>
462
462
+
{showMainPostRoute && (<>
463
463
+
<Header
464
464
+
title={`Post`}
465
465
+
backButtonCallback={() => {
466
466
+
if (window.history.length > 1) {
467
467
+
window.history.back();
468
468
+
} else {
469
469
+
window.location.assign("/");
470
470
+
}
471
471
+
}}
472
472
+
/>
468
473
469
469
-
{parentsLoading && (
470
470
-
<div className="text-center text-gray-500 dark:text-gray-400 flex flex-row">
471
471
-
<div className="ml-4 w-[42px] flex justify-center">
472
472
-
<div
473
473
-
style={{ width: 2, height: "100%", opacity: 0.5 }}
474
474
-
className="bg-gray-500 dark:bg-gray-400"
475
475
-
></div>
474
474
+
{parentsLoading && (
475
475
+
<div className="text-center text-gray-500 dark:text-gray-400 flex flex-row">
476
476
+
<div className="ml-4 w-[42px] flex justify-center">
477
477
+
<div
478
478
+
style={{ width: 2, height: "100%", opacity: 0.5 }}
479
479
+
className="bg-gray-500 dark:bg-gray-400"
480
480
+
></div>
481
481
+
</div>
482
482
+
Loading conversation...
476
483
</div>
477
477
-
Loading conversation...
484
484
+
)}
485
485
+
486
486
+
{/* we should use the reply lines here thats provided by UPR*/}
487
487
+
<div style={{ maxWidth: 600, padding: 0 }}>
488
488
+
{parents.map((parent, index) => (
489
489
+
<UniversalPostRendererATURILoader
490
490
+
key={parent.uri}
491
491
+
atUri={parent.uri}
492
492
+
topReplyLine={index > 0}
493
493
+
bottomReplyLine={true}
494
494
+
bottomBorder={false}
495
495
+
/>
496
496
+
))}
478
497
</div>
479
479
-
)}
480
480
-
481
481
-
{/* we should use the reply lines here thats provided by UPR*/}
482
482
-
<div style={{ maxWidth: 600, padding: 0 }}>
483
483
-
{parents.map((parent, index) => (
498
498
+
<div ref={mainPostRef}>
484
499
<UniversalPostRendererATURILoader
485
485
-
key={parent.uri}
486
486
-
atUri={parent.uri}
487
487
-
topReplyLine={index > 0}
488
488
-
bottomReplyLine={true}
489
489
-
bottomBorder={false}
500
500
+
atUri={atUri!}
501
501
+
detailed={true}
502
502
+
topReplyLine={parentsLoading || parents.length > 0}
503
503
+
nopics={!!nopics}
504
504
+
lightboxCallback={lightboxCallback}
490
505
/>
491
491
-
))}
492
492
-
</div>
493
493
-
<div ref={mainPostRef}>
494
494
-
<UniversalPostRendererATURILoader
495
495
-
atUri={atUri}
496
496
-
detailed={true}
497
497
-
topReplyLine={parentsLoading || parents.length > 0}
498
498
-
nopics={!!nopics}
499
499
-
lightboxCallback={lightboxCallback}
500
500
-
/>
501
501
-
</div>
502
502
-
<div
503
503
-
style={{
504
504
-
maxWidth: 600,
505
505
-
//margin: "0px auto 0",
506
506
-
padding: 0,
507
507
-
minHeight: "80dvh",
508
508
-
paddingBottom: "20dvh",
509
509
-
}}
510
510
-
>
506
506
+
</div>
511
507
<div
512
512
-
className="text-gray-500 dark:text-gray-400 text-sm font-bold"
513
508
style={{
514
514
-
fontSize: 18,
515
515
-
margin: "12px 16px 12px 16px",
516
516
-
fontWeight: 600,
509
509
+
maxWidth: 600,
510
510
+
//margin: "0px auto 0",
511
511
+
padding: 0,
512
512
+
minHeight: "80dvh",
513
513
+
paddingBottom: "20dvh",
517
514
}}
518
515
>
519
519
-
Replies
516
516
+
<div
517
517
+
className="text-gray-500 dark:text-gray-400 text-sm font-bold"
518
518
+
style={{
519
519
+
fontSize: 18,
520
520
+
margin: "12px 16px 12px 16px",
521
521
+
fontWeight: 600,
522
522
+
}}
523
523
+
>
524
524
+
Replies
525
525
+
</div>
526
526
+
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
527
527
+
{replyAturis.length > 0 &&
528
528
+
replyAturis.map((reply) => {
529
529
+
//const replyAtUri = `at://${reply.did}/app.bsky.feed.post/${reply.rkey}`;
530
530
+
return (
531
531
+
<UniversalPostRendererATURILoader
532
532
+
key={reply}
533
533
+
atUri={reply}
534
534
+
maxReplies={4}
535
535
+
/>
536
536
+
);
537
537
+
})}
538
538
+
{hasNextPage && (
539
539
+
<button
540
540
+
onClick={() => fetchNextPage()}
541
541
+
disabled={isFetchingNextPage}
542
542
+
className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50"
543
543
+
>
544
544
+
{isFetchingNextPage ? "Loading..." : "Load More"}
545
545
+
</button>
546
546
+
)}
547
547
+
</div>
520
548
</div>
521
521
-
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
522
522
-
{replyAturis.length > 0 &&
523
523
-
replyAturis.map((reply) => {
524
524
-
//const replyAtUri = `at://${reply.did}/app.bsky.feed.post/${reply.rkey}`;
525
525
-
return (
526
526
-
<UniversalPostRendererATURILoader
527
527
-
key={reply}
528
528
-
atUri={reply}
529
529
-
maxReplies={4}
530
530
-
/>
531
531
-
);
532
532
-
})}
533
533
-
{hasNextPage && (
534
534
-
<button
535
535
-
onClick={() => fetchNextPage()}
536
536
-
disabled={isFetchingNextPage}
537
537
-
className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50"
538
538
-
>
539
539
-
{isFetchingNextPage ? "Loading..." : "Load More"}
540
540
-
</button>
541
541
-
)}
542
542
-
</div>
543
543
-
</div>
549
549
+
</>)}
544
550
</>
545
551
);
546
552
}
+5
-5
src/utils/useQuery.ts
···
352
352
);
353
353
}
354
354
355
355
-
type linksRecord = {
355
355
+
export type linksRecord = {
356
356
did: string;
357
357
collection: string;
358
358
rkey: string;
···
634
634
collection: string
635
635
path: string
636
636
}) {
637
637
-
console.log(
638
638
-
'yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks',
639
639
-
query,
640
640
-
)
637
637
+
// console.log(
638
638
+
// 'yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks',
639
639
+
// query,
640
640
+
// )
641
641
642
642
return infiniteQueryOptions({
643
643
enabled: !!query?.target,