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
better experimental features, wafrn full text support
rimar1337
4 months ago
5252ff44
65f60fa0
+155
-33
3 changed files
expand all
collapse all
unified
split
src
components
UniversalPostRenderer.tsx
routes
settings.tsx
utils
atoms.ts
+115
-21
src/components/UniversalPostRenderer.tsx
···
1
-
import * as ATPAPI from "@atproto/api"
2
import { useNavigate } from "@tanstack/react-router";
3
import DOMPurify from "dompurify";
4
import { useAtom } from "jotai";
···
10
import {
11
composerAtom,
12
constellationURLAtom,
0
0
13
imgCDNAtom,
14
} from "~/utils/atoms";
15
import { useHydratedEmbed } from "~/utils/useHydrated";
···
162
isQuote,
163
filterNoReplies,
164
filterMustHaveMedia,
165
-
filterMustBeReply
166
}: UniversalPostRendererATURILoaderProps) {
167
// todo remove this once tree rendering is implemented, use a prop like isTree
168
const TEMPLINEAR = true;
···
526
? true
527
: maxReplies && !oldestOpsReplyElseNewestNonOpsReply
528
? false
529
-
: (maxReplies === 0 && (!replies || (!!replies && replies === 0))) ? false : bottomReplyLine
0
0
530
}
531
topReplyLine={topReplyLine}
532
//bottomBorder={maxReplies&&oldestOpsReplyElseNewestNonOpsReply ? false : bottomBorder}
···
553
filterMustBeReply={filterMustBeReply}
554
/>
555
<>
556
-
{(maxReplies && maxReplies === 0 && replies && replies > 0) ? (
557
<>
558
-
{/* <div>hello</div> */}
559
-
<MoreReplies atUri={atUri} />
560
</>
561
-
) : (<></>)}
0
0
562
</>
563
{!isQuote && oldestOpsReplyElseNewestNonOpsReply && (
564
<>
···
755
const hasImages = hasEmbed?.$type === "app.bsky.embed.images";
756
const hasVideo = hasEmbed?.$type === "app.bsky.embed.video";
757
const isquotewithmedia = hasEmbed?.$type === "app.bsky.embed.recordWithMedia";
758
-
const isQuotewithImages = isquotewithmedia && (hasEmbed as ATPAPI.AppBskyEmbedRecordWithMedia.Main)?.media?.$type === "app.bsky.embed.images";
759
-
const isQuotewithVideo = isquotewithmedia && (hasEmbed as ATPAPI.AppBskyEmbedRecordWithMedia.Main)?.media?.$type === "app.bsky.embed.video";
0
0
0
0
0
0
760
761
-
const hasMedia = hasEmbed && (hasImages || hasVideo || isQuotewithImages || isQuotewithVideo);
0
0
762
763
const {
764
data: hydratedEmbed,
···
854
// }, [fakepost, get, set]);
855
const thereply = (fakepost?.record as AppBskyFeedPost.Record)?.reply?.parent
856
?.uri;
857
-
const feedviewpostreplydid = thereply&&!filterNoReplies ? new AtUri(thereply).host : undefined;
0
858
const replyhookvalue = useQueryIdentity(
859
feedviewpost ? feedviewpostreplydid : undefined
860
);
···
1237
1238
import defaultpfp from "~/../public/favicon.png";
1239
import { useAuth } from "~/providers/UnifiedAuthProvider";
1240
-
import { FeedItemRenderAturiLoader, FollowButton, Mutual } from "~/routes/profile.$did";
0
0
0
0
1241
import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i";
1242
import { useFastLike } from "~/utils/likeMutationQueue";
1243
// import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed";
···
1446
: undefined;
1447
1448
const emergencySalt = randomString();
1449
-
const fedi = (post.record as { bridgyOriginalText?: string })
0
0
0
0
1450
.bridgyOriginalText;
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1451
1452
/* fuck you */
1453
const isMainItem = false;
···
1586
{post.author.displayName || post.author.handle}{" "}
1587
</div>
1588
<div className="text-gray-500 dark:text-gray-400 text-md flex flex-row gap-1">
1589
-
<Mutual targetdidorhandle={post.author.did} />@{post.author.handle}{" "}
0
1590
</div>
1591
</div>
1592
{uprrrsauthor?.description && (
···
1834
</div>
1835
</>
1836
)}
1837
-
<div style={{ paddingTop: post.embed && !concise && depth < 1 ? 4 : 0 }}>
0
0
0
0
1838
<>
1839
{expanded && (
1840
<div
···
2203
// <MaybeFeedCard view={embed.record} />
2204
// </div>
2205
// )
2206
-
} else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.feed.generator") {
2207
-
return <div className="rounded-xl border"><FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder/></div>
0
0
0
0
0
0
0
0
2208
}
2209
2210
// list embed
···
2216
// <MaybeListCard view={embed.record} />
2217
// </div>
2218
// )
2219
-
} else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.list") {
2220
-
return <div className="rounded-xl border"><FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder listmode disablePropagation /></div>
0
0
0
0
0
0
0
0
0
0
0
0
0
2221
}
2222
2223
// starter pack embed
···
2229
// <StarterPackCard starterPack={embed.record} />
2230
// </div>
2231
// )
2232
-
} else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.starterpack") {
2233
-
return <div className="rounded-xl border"><FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder listmode disablePropagation /></div>
0
0
0
0
0
0
0
0
0
0
0
0
0
2234
}
2235
2236
// quote post
···
1
+
import * as ATPAPI from "@atproto/api";
2
import { useNavigate } from "@tanstack/react-router";
3
import DOMPurify from "dompurify";
4
import { useAtom } from "jotai";
···
10
import {
11
composerAtom,
12
constellationURLAtom,
13
+
enableBridgyTextAtom,
14
+
enableWafrnTextAtom,
15
imgCDNAtom,
16
} from "~/utils/atoms";
17
import { useHydratedEmbed } from "~/utils/useHydrated";
···
164
isQuote,
165
filterNoReplies,
166
filterMustHaveMedia,
167
+
filterMustBeReply,
168
}: UniversalPostRendererATURILoaderProps) {
169
// todo remove this once tree rendering is implemented, use a prop like isTree
170
const TEMPLINEAR = true;
···
528
? true
529
: maxReplies && !oldestOpsReplyElseNewestNonOpsReply
530
? false
531
+
: maxReplies === 0 && (!replies || (!!replies && replies === 0))
532
+
? false
533
+
: bottomReplyLine
534
}
535
topReplyLine={topReplyLine}
536
//bottomBorder={maxReplies&&oldestOpsReplyElseNewestNonOpsReply ? false : bottomBorder}
···
557
filterMustBeReply={filterMustBeReply}
558
/>
559
<>
560
+
{maxReplies && maxReplies === 0 && replies && replies > 0 ? (
561
<>
562
+
{/* <div>hello</div> */}
563
+
<MoreReplies atUri={atUri} />
564
</>
565
+
) : (
566
+
<></>
567
+
)}
568
</>
569
{!isQuote && oldestOpsReplyElseNewestNonOpsReply && (
570
<>
···
761
const hasImages = hasEmbed?.$type === "app.bsky.embed.images";
762
const hasVideo = hasEmbed?.$type === "app.bsky.embed.video";
763
const isquotewithmedia = hasEmbed?.$type === "app.bsky.embed.recordWithMedia";
764
+
const isQuotewithImages =
765
+
isquotewithmedia &&
766
+
(hasEmbed as ATPAPI.AppBskyEmbedRecordWithMedia.Main)?.media?.$type ===
767
+
"app.bsky.embed.images";
768
+
const isQuotewithVideo =
769
+
isquotewithmedia &&
770
+
(hasEmbed as ATPAPI.AppBskyEmbedRecordWithMedia.Main)?.media?.$type ===
771
+
"app.bsky.embed.video";
772
773
+
const hasMedia =
774
+
hasEmbed &&
775
+
(hasImages || hasVideo || isQuotewithImages || isQuotewithVideo);
776
777
const {
778
data: hydratedEmbed,
···
868
// }, [fakepost, get, set]);
869
const thereply = (fakepost?.record as AppBskyFeedPost.Record)?.reply?.parent
870
?.uri;
871
+
const feedviewpostreplydid =
872
+
thereply && !filterNoReplies ? new AtUri(thereply).host : undefined;
873
const replyhookvalue = useQueryIdentity(
874
feedviewpost ? feedviewpostreplydid : undefined
875
);
···
1252
1253
import defaultpfp from "~/../public/favicon.png";
1254
import { useAuth } from "~/providers/UnifiedAuthProvider";
1255
+
import {
1256
+
FeedItemRenderAturiLoader,
1257
+
FollowButton,
1258
+
Mutual,
1259
+
} from "~/routes/profile.$did";
1260
import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i";
1261
import { useFastLike } from "~/utils/likeMutationQueue";
1262
// import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed";
···
1465
: undefined;
1466
1467
const emergencySalt = randomString();
1468
+
1469
+
const [showBridgyText] = useAtom(enableBridgyTextAtom);
1470
+
const [showWafrnText] = useAtom(enableWafrnTextAtom);
1471
+
1472
+
const unfedibridgy = (post.record as { bridgyOriginalText?: string })
1473
.bridgyOriginalText;
1474
+
const unfediwafrnPartial = (post.record as { fullText?: string }).fullText;
1475
+
const unfediwafrnTags = (post.record as { fullTags?: string }).fullTags;
1476
+
const unfediwafrnUnHost = (post.record as { fediverseId?: string })
1477
+
.fediverseId;
1478
+
1479
+
const undfediwafrnHost = unfediwafrnUnHost
1480
+
? new URL(unfediwafrnUnHost).hostname
1481
+
: undefined;
1482
+
1483
+
const tags = unfediwafrnTags
1484
+
? unfediwafrnTags
1485
+
.split("\n")
1486
+
.map((t) => t.trim())
1487
+
.filter(Boolean)
1488
+
: undefined;
1489
+
1490
+
const links = tags
1491
+
? tags
1492
+
.map((tag) => {
1493
+
const encoded = encodeURIComponent(tag);
1494
+
return `<a href="https://${undfediwafrnHost}/search/${encoded}" target="_blank">#${tag.replaceAll(' ','-')}</a>`;
1495
+
})
1496
+
.join("<br>")
1497
+
: "";
1498
+
1499
+
const unfediwafrn = unfediwafrnPartial
1500
+
? unfediwafrnPartial + (links ? `<br>${links}` : "")
1501
+
: undefined;
1502
+
1503
+
const fedi =
1504
+
(showBridgyText ? unfedibridgy : undefined) ??
1505
+
(showWafrnText ? unfediwafrn : undefined);
1506
1507
/* fuck you */
1508
const isMainItem = false;
···
1641
{post.author.displayName || post.author.handle}{" "}
1642
</div>
1643
<div className="text-gray-500 dark:text-gray-400 text-md flex flex-row gap-1">
1644
+
<Mutual targetdidorhandle={post.author.did} />@
1645
+
{post.author.handle}{" "}
1646
</div>
1647
</div>
1648
{uprrrsauthor?.description && (
···
1890
</div>
1891
</>
1892
)}
1893
+
<div
1894
+
style={{
1895
+
paddingTop: post.embed && !concise && depth < 1 ? 4 : 0,
1896
+
}}
1897
+
>
1898
<>
1899
{expanded && (
1900
<div
···
2263
// <MaybeFeedCard view={embed.record} />
2264
// </div>
2265
// )
2266
+
} else if (
2267
+
!!reallybaduri &&
2268
+
!!reallybadaturi &&
2269
+
reallybadaturi.collection === "app.bsky.feed.generator"
2270
+
) {
2271
+
return (
2272
+
<div className="rounded-xl border">
2273
+
<FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder />
2274
+
</div>
2275
+
);
2276
}
2277
2278
// list embed
···
2284
// <MaybeListCard view={embed.record} />
2285
// </div>
2286
// )
2287
+
} else if (
2288
+
!!reallybaduri &&
2289
+
!!reallybadaturi &&
2290
+
reallybadaturi.collection === "app.bsky.graph.list"
2291
+
) {
2292
+
return (
2293
+
<div className="rounded-xl border">
2294
+
<FeedItemRenderAturiLoader
2295
+
aturi={reallybaduri}
2296
+
disableBottomBorder
2297
+
listmode
2298
+
disablePropagation
2299
+
/>
2300
+
</div>
2301
+
);
2302
}
2303
2304
// starter pack embed
···
2310
// <StarterPackCard starterPack={embed.record} />
2311
// </div>
2312
// )
2313
+
} else if (
2314
+
!!reallybaduri &&
2315
+
!!reallybadaturi &&
2316
+
reallybadaturi.collection === "app.bsky.graph.starterpack"
2317
+
) {
2318
+
return (
2319
+
<div className="rounded-xl border">
2320
+
<FeedItemRenderAturiLoader
2321
+
aturi={reallybaduri}
2322
+
disableBottomBorder
2323
+
listmode
2324
+
disablePropagation
2325
+
/>
2326
+
</div>
2327
+
);
2328
}
2329
2330
// quote post
+30
-12
src/routes/settings.tsx
···
13
defaultslingshotURL,
14
defaultVideoCDN,
15
enableBitesAtom,
0
0
16
hueAtom,
17
imgCDNAtom,
18
slingshotURLAtom,
···
84
<SwitchSetting
85
atom={enableBitesAtom}
86
title={"Bites"}
87
-
description={"Enable Wafrn Bites to bite other people"}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
88
//init={false}
89
/>
90
<p className="text-gray-500 dark:text-gray-400 py-4 px-4 text-sm border rounded-xl mx-4 mt-8 mb-4">
···
137
138
return (
139
<div className="flex items-center gap-4 px-4 ">
140
-
<div className="flex flex-col">
141
-
<label htmlFor="switch-demo" className="text-md">
142
-
{title}
143
-
</label>
144
-
<span className="text-sm text-gray-500 dark:text-gray-400">
145
-
{description}
146
-
</span>
147
-
</div>
148
-
149
-
<div className="flex-1" />
150
151
<Switch.Root
152
-
id="switch-demo"
153
checked={value}
154
onCheckedChange={(v) => setValue(v)}
155
className="m3switch root"
···
13
defaultslingshotURL,
14
defaultVideoCDN,
15
enableBitesAtom,
16
+
enableBridgyTextAtom,
17
+
enableWafrnTextAtom,
18
hueAtom,
19
imgCDNAtom,
20
slingshotURLAtom,
···
86
<SwitchSetting
87
atom={enableBitesAtom}
88
title={"Bites"}
89
+
description={"Enable Wafrn Bites to bite and be bitten by other people"}
90
+
//init={false}
91
+
/>
92
+
<div className="h-4" />
93
+
<SwitchSetting
94
+
atom={enableBridgyTextAtom}
95
+
title={"Bridgy Text"}
96
+
description={
97
+
"Show the original text of posts bridged from the Fediverse"
98
+
}
99
+
//init={false}
100
+
/>
101
+
<div className="h-4" />
102
+
<SwitchSetting
103
+
atom={enableWafrnTextAtom}
104
+
title={"Wafrn Text"}
105
+
description={
106
+
"Show the original text of posts from Wafrn instances"
107
+
}
108
//init={false}
109
/>
110
<p className="text-gray-500 dark:text-gray-400 py-4 px-4 text-sm border rounded-xl mx-4 mt-8 mb-4">
···
157
158
return (
159
<div className="flex items-center gap-4 px-4 ">
160
+
<label htmlFor={`switch-${title}`} className="flex flex-row flex-1">
161
+
<div className="flex flex-col">
162
+
<span className="text-md">{title}</span>
163
+
<span className="text-sm text-gray-500 dark:text-gray-400">
164
+
{description}
165
+
</span>
166
+
</div>
167
+
</label>
0
0
168
169
<Switch.Root
170
+
id={`switch-${title}`}
171
checked={value}
172
onCheckedChange={(v) => setValue(v)}
173
className="m3switch root"
+10
src/utils/atoms.ts
···
137
"enableBitesAtom",
138
false
139
);
0
0
0
0
0
0
0
0
0
0
···
137
"enableBitesAtom",
138
false
139
);
140
+
141
+
export const enableBridgyTextAtom = atomWithStorage<boolean>(
142
+
"enableBridgyTextAtom",
143
+
false
144
+
);
145
+
146
+
export const enableWafrnTextAtom = atomWithStorage<boolean>(
147
+
"enableWafrnTextAtom",
148
+
false
149
+
);