an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm

better experimental features, wafrn full text support

rimar1337 5252ff44 65f60fa0

Changed files
+155 -33
src
+115 -21
src/components/UniversalPostRenderer.tsx
··· 1 - import * as ATPAPI from "@atproto/api" 1 + import * as ATPAPI from "@atproto/api"; 2 2 import { useNavigate } from "@tanstack/react-router"; 3 3 import DOMPurify from "dompurify"; 4 4 import { useAtom } from "jotai"; ··· 10 10 import { 11 11 composerAtom, 12 12 constellationURLAtom, 13 + enableBridgyTextAtom, 14 + enableWafrnTextAtom, 13 15 imgCDNAtom, 14 16 } from "~/utils/atoms"; 15 17 import { useHydratedEmbed } from "~/utils/useHydrated"; ··· 162 164 isQuote, 163 165 filterNoReplies, 164 166 filterMustHaveMedia, 165 - filterMustBeReply 167 + filterMustBeReply, 166 168 }: UniversalPostRendererATURILoaderProps) { 167 169 // todo remove this once tree rendering is implemented, use a prop like isTree 168 170 const TEMPLINEAR = true; ··· 526 528 ? true 527 529 : maxReplies && !oldestOpsReplyElseNewestNonOpsReply 528 530 ? false 529 - : (maxReplies === 0 && (!replies || (!!replies && replies === 0))) ? false : bottomReplyLine 531 + : maxReplies === 0 && (!replies || (!!replies && replies === 0)) 532 + ? false 533 + : bottomReplyLine 530 534 } 531 535 topReplyLine={topReplyLine} 532 536 //bottomBorder={maxReplies&&oldestOpsReplyElseNewestNonOpsReply ? false : bottomBorder} ··· 553 557 filterMustBeReply={filterMustBeReply} 554 558 /> 555 559 <> 556 - {(maxReplies && maxReplies === 0 && replies && replies > 0) ? ( 560 + {maxReplies && maxReplies === 0 && replies && replies > 0 ? ( 557 561 <> 558 - {/* <div>hello</div> */} 559 - <MoreReplies atUri={atUri} /> 562 + {/* <div>hello</div> */} 563 + <MoreReplies atUri={atUri} /> 560 564 </> 561 - ) : (<></>)} 565 + ) : ( 566 + <></> 567 + )} 562 568 </> 563 569 {!isQuote && oldestOpsReplyElseNewestNonOpsReply && ( 564 570 <> ··· 755 761 const hasImages = hasEmbed?.$type === "app.bsky.embed.images"; 756 762 const hasVideo = hasEmbed?.$type === "app.bsky.embed.video"; 757 763 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"; 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"; 760 772 761 - const hasMedia = hasEmbed && (hasImages || hasVideo || isQuotewithImages || isQuotewithVideo); 773 + const hasMedia = 774 + hasEmbed && 775 + (hasImages || hasVideo || isQuotewithImages || isQuotewithVideo); 762 776 763 777 const { 764 778 data: hydratedEmbed, ··· 854 868 // }, [fakepost, get, set]); 855 869 const thereply = (fakepost?.record as AppBskyFeedPost.Record)?.reply?.parent 856 870 ?.uri; 857 - const feedviewpostreplydid = thereply&&!filterNoReplies ? new AtUri(thereply).host : undefined; 871 + const feedviewpostreplydid = 872 + thereply && !filterNoReplies ? new AtUri(thereply).host : undefined; 858 873 const replyhookvalue = useQueryIdentity( 859 874 feedviewpost ? feedviewpostreplydid : undefined 860 875 ); ··· 1237 1252 1238 1253 import defaultpfp from "~/../public/favicon.png"; 1239 1254 import { useAuth } from "~/providers/UnifiedAuthProvider"; 1240 - import { FeedItemRenderAturiLoader, FollowButton, Mutual } from "~/routes/profile.$did"; 1255 + import { 1256 + FeedItemRenderAturiLoader, 1257 + FollowButton, 1258 + Mutual, 1259 + } from "~/routes/profile.$did"; 1241 1260 import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i"; 1242 1261 import { useFastLike } from "~/utils/likeMutationQueue"; 1243 1262 // import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed"; ··· 1446 1465 : undefined; 1447 1466 1448 1467 const emergencySalt = randomString(); 1449 - const fedi = (post.record as { bridgyOriginalText?: string }) 1468 + 1469 + const [showBridgyText] = useAtom(enableBridgyTextAtom); 1470 + const [showWafrnText] = useAtom(enableWafrnTextAtom); 1471 + 1472 + const unfedibridgy = (post.record as { bridgyOriginalText?: string }) 1450 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); 1451 1506 1452 1507 /* fuck you */ 1453 1508 const isMainItem = false; ··· 1586 1641 {post.author.displayName || post.author.handle}{" "} 1587 1642 </div> 1588 1643 <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}{" "} 1644 + <Mutual targetdidorhandle={post.author.did} />@ 1645 + {post.author.handle}{" "} 1590 1646 </div> 1591 1647 </div> 1592 1648 {uprrrsauthor?.description && ( ··· 1834 1890 </div> 1835 1891 </> 1836 1892 )} 1837 - <div style={{ paddingTop: post.embed && !concise && depth < 1 ? 4 : 0 }}> 1893 + <div 1894 + style={{ 1895 + paddingTop: post.embed && !concise && depth < 1 ? 4 : 0, 1896 + }} 1897 + > 1838 1898 <> 1839 1899 {expanded && ( 1840 1900 <div ··· 2203 2263 // <MaybeFeedCard view={embed.record} /> 2204 2264 // </div> 2205 2265 // ) 2206 - } else if (!!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.feed.generator") { 2207 - return <div className="rounded-xl border"><FeedItemRenderAturiLoader aturi={reallybaduri} disableBottomBorder/></div> 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 + ); 2208 2276 } 2209 2277 2210 2278 // list embed ··· 2216 2284 // <MaybeListCard view={embed.record} /> 2217 2285 // </div> 2218 2286 // ) 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> 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 + ); 2221 2302 } 2222 2303 2223 2304 // starter pack embed ··· 2229 2310 // <StarterPackCard starterPack={embed.record} /> 2230 2311 // </div> 2231 2312 // ) 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> 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 + ); 2234 2328 } 2235 2329 2236 2330 // quote post
+30 -12
src/routes/settings.tsx
··· 13 13 defaultslingshotURL, 14 14 defaultVideoCDN, 15 15 enableBitesAtom, 16 + enableBridgyTextAtom, 17 + enableWafrnTextAtom, 16 18 hueAtom, 17 19 imgCDNAtom, 18 20 slingshotURLAtom, ··· 84 86 <SwitchSetting 85 87 atom={enableBitesAtom} 86 88 title={"Bites"} 87 - description={"Enable Wafrn Bites to bite other people"} 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 + } 88 108 //init={false} 89 109 /> 90 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"> ··· 137 157 138 158 return ( 139 159 <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" /> 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> 150 168 151 169 <Switch.Root 152 - id="switch-demo" 170 + id={`switch-${title}`} 153 171 checked={value} 154 172 onCheckedChange={(v) => setValue(v)} 155 173 className="m3switch root"
+10
src/utils/atoms.ts
··· 137 137 "enableBitesAtom", 138 138 false 139 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 + );