import * as ATPAPI from "@atproto/api"; import { useNavigate } from "@tanstack/react-router"; import DOMPurify from "dompurify"; import { useAtom } from "jotai"; import { DropdownMenu } from "radix-ui"; import { HoverCard } from "radix-ui"; import * as React from "react"; import { type SVGProps } from "react"; import { composerAtom, constellationURLAtom, enableBridgyTextAtom, enableWafrnTextAtom, imgCDNAtom, } from "~/utils/atoms"; import { useHydratedEmbed } from "~/utils/useHydrated"; import { useQueryConstellation, useQueryIdentity, useQueryPost, useQueryProfile, yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks, } from "~/utils/useQuery"; function asTyped(obj: T): $Typed { return obj as $Typed; } export const CACHE_TIMEOUT = 5 * 60 * 1000; const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour export interface UniversalPostRendererATURILoaderProps { atUri: string; onConstellation?: (data: any) => void; detailed?: boolean; bottomReplyLine?: boolean; topReplyLine?: boolean; bottomBorder?: boolean; feedviewpost?: boolean; repostedby?: string; style?: React.CSSProperties; ref?: React.RefObject; dataIndexPropPass?: number; nopics?: boolean; concise?: boolean; lightboxCallback?: (d: LightboxProps) => void; maxReplies?: number; isQuote?: boolean; filterNoReplies?: boolean; filterMustHaveMedia?: boolean; filterMustBeReply?: boolean; } // export async function cachedGetRecord({ // atUri, // cacheTimeout = CACHE_TIMEOUT, // get, // set, // }: { // atUri: string; // //resolved: { pdsUrl: string; did: string } | null | undefined; // cacheTimeout?: number; // get: (key: string) => any; // set: (key: string, value: string) => void; // }): Promise { // const cacheKey = `record:${atUri}`; // const cached = get(cacheKey); // const now = Date.now(); // if ( // cached && // cached.value && // cached.time && // now - cached.time < cacheTimeout // ) { // try { // return JSON.parse(cached.value); // } catch { // // fall through to fetch // } // } // const parsed = parseAtUri(atUri); // if (!parsed) return null; // const resolved = await cachedResolveIdentity({ // didOrHandle: parsed.did, // get, // set, // }); // if (!resolved?.pdsUrl || !resolved?.did) // throw new Error("Missing resolved PDS info"); // if (!parsed) throw new Error("Invalid atUri"); // const { collection, rkey } = parsed; // const url = `${ // resolved.pdsUrl // }/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent( // resolved.did, // )}&collection=${encodeURIComponent(collection)}&rkey=${encodeURIComponent( // rkey, // )}`; // const res = await fetch(url); // if (!res.ok) throw new Error("Failed to fetch base record"); // const data = await res.json(); // set(cacheKey, JSON.stringify(data)); // return data; // } // export async function cachedResolveIdentity({ // didOrHandle, // cacheTimeout = HANDLE_DID_CACHE_TIMEOUT, // get, // set, // }: { // didOrHandle: string; // cacheTimeout?: number; // get: (key: string) => any; // set: (key: string, value: string) => void; // }): Promise { // const isDidInput = didOrHandle.startsWith("did:"); // const cacheKey = `handleDid:${didOrHandle}`; // const now = Date.now(); // const cached = get(cacheKey); // if ( // cached && // cached.value && // cached.time && // now - cached.time < cacheTimeout // ) { // try { // return JSON.parse(cached.value); // } catch {} // } // const url = `https://free-fly-24.deno.dev/?${ // isDidInput // ? `did=${encodeURIComponent(didOrHandle)}` // : `handle=${encodeURIComponent(didOrHandle)}` // }`; // const res = await fetch(url); // if (!res.ok) throw new Error("Failed to resolve handle/did"); // const data = await res.json(); // set(cacheKey, JSON.stringify(data)); // if (!isDidInput && data.did) { // set(`handleDid:${data.did}`, JSON.stringify(data)); // } // return data; // } export function UniversalPostRendererATURILoader({ atUri, onConstellation, detailed = false, bottomReplyLine, topReplyLine, bottomBorder = true, feedviewpost = false, repostedby, style, ref, dataIndexPropPass, nopics, concise, lightboxCallback, maxReplies, isQuote, filterNoReplies, filterMustHaveMedia, filterMustBeReply, }: UniversalPostRendererATURILoaderProps) { // todo remove this once tree rendering is implemented, use a prop like isTree const TEMPLINEAR = true; // /*mass comment*/ console.log("atUri", atUri); //const { get, set } = usePersistentStore(); //const [record, setRecord] = React.useState(null); //const [links, setLinks] = React.useState(null); //const [error, setError] = React.useState(null); //const [cacheTime, setCacheTime] = React.useState(null); //const [resolved, setResolved] = React.useState(null); // { did, pdsUrl, bskyPds, handle } //const [opProfile, setOpProfile] = React.useState(null); // const [opProfileCacheTime, setOpProfileCacheTime] = React.useState< // number | null // >(null); //const router = useRouter(); //const parsed = React.useMemo(() => parseAtUri(atUri), [atUri]); const parsed = new AtUri(atUri); const did = parsed?.host; const rkey = parsed?.rkey; // /*mass comment*/ console.log("did", did); // /*mass comment*/ console.log("rkey", rkey); // React.useEffect(() => { // const checkCache = async () => { // const postUri = atUri; // const cacheKey = `record:${postUri}`; // const cached = await get(cacheKey); // const now = Date.now(); // // /*mass comment*/ console.log( // "UniversalPostRenderer checking cache for", // cacheKey, // "cached:", // !!cached, // ); // if ( // cached && // cached.value && // cached.time && // now - cached.time < CACHE_TIMEOUT // ) { // try { // // /*mass comment*/ console.log("UniversalPostRenderer found cached data for", cacheKey); // setRecord(JSON.parse(cached.value)); // } catch { // setRecord(null); // } // } // }; // checkCache(); // }, [atUri, get]); const { data: postQuery, isLoading: isPostLoading, isError: isPostError, } = useQueryPost(atUri); //const record = postQuery?.value; // React.useEffect(() => { // if (!did || record) return; // (async () => { // try { // const resolvedData = await cachedResolveIdentity({ // didOrHandle: did, // get, // set, // }); // setResolved(resolvedData); // } catch (e: any) { // //setError("Failed to resolve handle/did: " + e?.message); // } // })(); // }, [did, get, set, record]); const { data: resolved } = useQueryIdentity(did || ""); // React.useEffect(() => { // if (!resolved || !resolved.pdsUrl || !resolved.did || !rkey || record) // return; // let ignore = false; // (async () => { // try { // const data = await cachedGetRecord({ // atUri, // get, // set, // }); // if (!ignore) setRecord(data); // } catch (e: any) { // //if (!ignore) setError("Failed to fetch base record: " + e?.message); // } // })(); // return () => { // ignore = true; // }; // }, [resolved, rkey, atUri, record]); // React.useEffect(() => { // if (!resolved || !resolved.did || !rkey) return; // const fetchLinks = async () => { // const postUri = atUri; // const cacheKey = `constellation:${postUri}`; // const cached = await get(cacheKey); // const now = Date.now(); // if ( // cached && // cached.value && // cached.time && // now - cached.time < CACHE_TIMEOUT // ) { // try { // const data = JSON.parse(cached.value); // setLinks(data); // if (onConstellation) onConstellation(data); // } catch { // setLinks(null); // } // //setCacheTime(cached.time); // return; // } // try { // const url = `https://constellation.microcosm.blue/links/all?target=${encodeURIComponent( // atUri, // )}`; // const res = await fetch(url); // if (!res.ok) throw new Error("Failed to fetch constellation links"); // const data = await res.json(); // setLinks(data); // //setCacheTime(now); // set(cacheKey, JSON.stringify(data)); // if (onConstellation) onConstellation(data); // } catch (e: any) { // //setError("Failed to fetch constellation links: " + e?.message); // } // }; // fetchLinks(); // }, [resolved, rkey, get, set, atUri, onConstellation]); const { data: links } = useQueryConstellation({ method: "/links/all", target: atUri, }); // React.useEffect(() => { // if (!record || !resolved || !resolved.did) return; // const fetchOpProfile = async () => { // const opDid = resolved.did; // const postUri = atUri; // const cacheKey = `profile:${postUri}`; // const cached = await get(cacheKey); // const now = Date.now(); // if ( // cached && // cached.value && // cached.time && // now - cached.time < CACHE_TIMEOUT // ) { // try { // setOpProfile(JSON.parse(cached.value)); // } catch { // setOpProfile(null); // } // //setOpProfileCacheTime(cached.time); // return; // } // try { // let opResolvedRaw = await get(`handleDid:${opDid}`); // let opResolved: any = null; // if ( // opResolvedRaw && // opResolvedRaw.value && // opResolvedRaw.time && // now - opResolvedRaw.time < HANDLE_DID_CACHE_TIMEOUT // ) { // try { // opResolved = JSON.parse(opResolvedRaw.value); // } catch { // opResolved = null; // } // } else { // const url = `https://free-fly-24.deno.dev/?did=${encodeURIComponent( // opDid, // )}`; // const res = await fetch(url); // if (!res.ok) throw new Error("Failed to resolve OP did"); // opResolved = await res.json(); // set(`handleDid:${opDid}`, JSON.stringify(opResolved)); // } // if (!opResolved || !opResolved.pdsUrl) // throw new Error("OP did resolution failed or missing pdsUrl"); // const profileUrl = `${ // opResolved.pdsUrl // }/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent( // opDid, // )}&collection=app.bsky.actor.profile&rkey=self`; // const profileRes = await fetch(profileUrl); // if (!profileRes.ok) throw new Error("Failed to fetch OP profile"); // const profileData = await profileRes.json(); // setOpProfile(profileData); // //setOpProfileCacheTime(now); // set(cacheKey, JSON.stringify(profileData)); // } catch (e: any) { // //setError("Failed to fetch OP profile: " + e?.message); // } // }; // fetchOpProfile(); // }, [record, get, set, rkey, resolved, atUri]); const { data: opProfile } = useQueryProfile( resolved ? `at://${resolved?.did}/app.bsky.actor.profile/self` : undefined ); // const displayName = // opProfile?.value?.displayName || resolved?.handle || resolved?.did; // const handle = resolved?.handle ? `@${resolved.handle}` : resolved?.did; // const postText = record?.value?.text || ""; // const createdAt = record?.value?.createdAt // ? new Date(record.value.createdAt) // : null; // const langTags = record?.value?.langs || []; const [likes, setLikes] = React.useState(null); const [reposts, setReposts] = React.useState(null); const [replies, setReplies] = React.useState(null); React.useEffect(() => { // /*mass comment*/ console.log(JSON.stringify(links, null, 2)); setLikes( links ? links?.links?.["app.bsky.feed.like"]?.[".subject.uri"]?.records || 0 : null ); setReposts( links ? links?.links?.["app.bsky.feed.repost"]?.[".subject.uri"]?.records || 0 : null ); setReplies( links ? links?.links?.["app.bsky.feed.post"]?.[".reply.parent.uri"] ?.records || 0 : null ); }, [links]); // const { data: repliesData } = useQueryConstellation({ // method: "/links", // target: atUri, // collection: "app.bsky.feed.post", // path: ".reply.parent.uri", // }); const [constellationurl] = useAtom(constellationURLAtom); const infinitequeryresults = useInfiniteQuery({ ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( { constellation: constellationurl, method: "/links", target: atUri, collection: "app.bsky.feed.post", path: ".reply.parent.uri", } ), enabled: !!atUri && !!maxReplies && !isQuote, }); const { data: repliesData, // fetchNextPage, // hasNextPage, // isFetchingNextPage, } = infinitequeryresults; // auto-fetch all pages useEffect(() => { if (!maxReplies || isQuote || TEMPLINEAR) return; if ( infinitequeryresults.hasNextPage && !infinitequeryresults.isFetchingNextPage ) { console.log("Fetching the next page..."); infinitequeryresults.fetchNextPage(); } }, [TEMPLINEAR, infinitequeryresults, isQuote, maxReplies]); const replyAturis = repliesData ? repliesData.pages.flatMap((page) => page ? page.linking_records.map((record) => { const aturi = `at://${record.did}/${record.collection}/${record.rkey}`; return aturi; }) : [] ) : []; //const [oldestOpsReply, setOldestOpsReply] = useState(undefined); const { oldestOpsReply, oldestOpsReplyElseNewestNonOpsReply } = (() => { if (isQuote || !replyAturis || replyAturis.length === 0 || !maxReplies) return { oldestOpsReply: undefined, oldestOpsReplyElseNewestNonOpsReply: undefined, }; const opdid = new AtUri( //postQuery?.value.reply?.root.uri ?? postQuery?.uri ?? atUri atUri ).host; const opReplies = replyAturis.filter( (aturi) => new AtUri(aturi).host === opdid ); if (opReplies.length > 0) { const opreply = opReplies[opReplies.length - 1]; //setOldestOpsReply(opreply); return { oldestOpsReply: opreply, oldestOpsReplyElseNewestNonOpsReply: opreply, }; } else { return { oldestOpsReply: undefined, oldestOpsReplyElseNewestNonOpsReply: replyAturis[0], }; } })(); // const navigateToProfile = (e: React.MouseEvent) => { // e.stopPropagation(); // if (resolved?.did) { // router.navigate({ // to: "/profile/$did", // params: { did: resolved.did }, // }); // } // }; if (!postQuery?.value) { // deleted post more often than a non-resolvable post return <>; } return ( <> {/* uprrs {maxReplies} {!!maxReplies&&!!oldestOpsReplyElseNewestNonOpsReply ? "true" : "false"} */} <> {maxReplies && maxReplies === 0 && replies && replies > 0 ? ( <> {/*
hello
*/} ) : ( <> )} {!isQuote && oldestOpsReplyElseNewestNonOpsReply && ( <> {/* hello {maxReplies} */} 0} topReplyLine={ (!!(maxReplies && maxReplies - 1 === 0) && !!(replies && replies > 0)) || !!((maxReplies ?? 0) > 1) } bottomBorder={bottomBorder} feedviewpost={feedviewpost} repostedby={repostedby} style={style} ref={ref} dataIndexPropPass={dataIndexPropPass} nopics={nopics} concise={concise} lightboxCallback={lightboxCallback} maxReplies={ maxReplies && maxReplies > 0 ? maxReplies - 1 : undefined } /> )} ); } function MoreReplies({ atUri }: { atUri: string }) { const navigate = useNavigate(); const aturio = new AtUri(atUri); return (
navigate({ to: "/profile/$did/post/$rkey", params: { did: aturio.host, rkey: aturio.rkey }, }) } className="border-b border-gray-300 dark:border-gray-800 flex flex-row px-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 transition-colors" >
More Replies
); } function getAvatarUrl(opProfile: any, did: string, cdn: string) { const link = opProfile?.value?.avatar?.ref?.["$link"]; if (!link) return null; return `https://${cdn}/img/avatar/plain/${did}/${link}@jpeg`; } export function UniversalPostRendererRawRecordShim({ postRecord, profileRecord, aturi, resolved, likesCount, repostsCount, repliesCount, detailed = false, bottomReplyLine = false, topReplyLine = false, bottomBorder = true, feedviewpost = false, repostedby, style, ref, dataIndexPropPass, nopics, concise, lightboxCallback, maxReplies, isQuote, filterNoReplies, filterMustHaveMedia, filterMustBeReply, }: { postRecord: any; profileRecord: any; aturi: string; resolved: any; likesCount?: number | null; repostsCount?: number | null; repliesCount?: number | null; detailed?: boolean; bottomReplyLine?: boolean; topReplyLine?: boolean; bottomBorder?: boolean; feedviewpost?: boolean; repostedby?: string; style?: React.CSSProperties; ref?: React.RefObject; dataIndexPropPass?: number; nopics?: boolean; concise?: boolean; lightboxCallback?: (d: LightboxProps) => void; maxReplies?: number; isQuote?: boolean; filterNoReplies?: boolean; filterMustHaveMedia?: boolean; filterMustBeReply?: boolean; }) { // /*mass comment*/ console.log(`received aturi: ${aturi} of post content: ${postRecord}`); const navigate = useNavigate(); //const { get, set } = usePersistentStore(); // const [hydratedEmbed, setHydratedEmbed] = useState(undefined); // useEffect(() => { // const run = async () => { // if (!postRecord?.value?.embed) return; // const embed = postRecord?.value?.embed; // if (!embed || !embed.$type) { // setHydratedEmbed(undefined); // return; // } // try { // let result: any; // if (embed?.$type === "app.bsky.embed.recordWithMedia") { // const mediaEmbed = embed.media; // let hydratedMedia; // if (mediaEmbed?.$type === "app.bsky.embed.images") { // hydratedMedia = hydrateEmbedImages(mediaEmbed, resolved?.did); // } else if (mediaEmbed?.$type === "app.bsky.embed.external") { // hydratedMedia = hydrateEmbedExternal(mediaEmbed, resolved?.did); // } else if (mediaEmbed?.$type === "app.bsky.embed.video") { // hydratedMedia = hydrateEmbedVideo(mediaEmbed, resolved?.did); // } else { // throw new Error("idiot"); // } // if (!hydratedMedia) throw new Error("idiot"); // // hydrate the outer recordWithMedia now using the hydrated media // result = await hydrateEmbedRecordWithMedia( // embed, // resolved?.did, // hydratedMedia, // get, // set, // ); // } else { // const hydrated = // embed?.$type === "app.bsky.embed.images" // ? hydrateEmbedImages(embed, resolved?.did) // : embed?.$type === "app.bsky.embed.external" // ? hydrateEmbedExternal(embed, resolved?.did) // : embed?.$type === "app.bsky.embed.video" // ? hydrateEmbedVideo(embed, resolved?.did) // : embed?.$type === "app.bsky.embed.record" // ? hydrateEmbedRecord(embed, resolved?.did, get, set) // : undefined; // result = hydrated instanceof Promise ? await hydrated : hydrated; // } // // /*mass comment*/ console.log( // String(result) + " hydrateEmbedRecordWithMedia hey hyeh ye", // ); // setHydratedEmbed(result); // } catch (e) { // console.error("Error hydrating embed", e); // setHydratedEmbed(undefined); // } // }; // run(); // }, [postRecord, resolved?.did]); const hasEmbed = (postRecord?.value as ATPAPI.AppBskyFeedPost.Record)?.embed; const hasImages = hasEmbed?.$type === "app.bsky.embed.images"; const hasVideo = hasEmbed?.$type === "app.bsky.embed.video"; const isquotewithmedia = hasEmbed?.$type === "app.bsky.embed.recordWithMedia"; const isQuotewithImages = isquotewithmedia && (hasEmbed as ATPAPI.AppBskyEmbedRecordWithMedia.Main)?.media?.$type === "app.bsky.embed.images"; const isQuotewithVideo = isquotewithmedia && (hasEmbed as ATPAPI.AppBskyEmbedRecordWithMedia.Main)?.media?.$type === "app.bsky.embed.video"; const hasMedia = hasEmbed && (hasImages || hasVideo || isQuotewithImages || isQuotewithVideo); const { data: hydratedEmbed, isLoading: isEmbedLoading, error: embedError, } = useHydratedEmbed(postRecord?.value?.embed, resolved?.did); const [imgcdn] = useAtom(imgCDNAtom); const parsedaturi = new AtUri(aturi); //parseAtUri(aturi); const fakeprofileviewbasic = React.useMemo( () => ({ did: resolved?.did || "", handle: resolved?.handle || "", displayName: profileRecord?.value?.displayName || "", avatar: getAvatarUrl(profileRecord, resolved?.did, imgcdn) || "", viewer: undefined, labels: profileRecord?.labels || undefined, verification: undefined, }), [imgcdn, profileRecord, resolved?.did, resolved?.handle] ); const fakeprofileviewdetailed = React.useMemo( () => ({ ...fakeprofileviewbasic, $type: "app.bsky.actor.defs#profileViewDetailed", description: profileRecord?.value?.description || undefined, }), [fakeprofileviewbasic, profileRecord?.value?.description] ); const fakepost = React.useMemo( () => ({ $type: "app.bsky.feed.defs#postView", uri: aturi, cid: postRecord?.cid || "", author: fakeprofileviewbasic, 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, }), [ aturi, postRecord?.cid, postRecord?.value, postRecord?.labels, fakeprofileviewbasic, hydratedEmbed, repliesCount, repostsCount, likesCount, ] ); //const [feedviewpostreplyhandle, setFeedviewpostreplyhandle] = useState(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]); const thereply = (fakepost?.record as AppBskyFeedPost.Record)?.reply?.parent ?.uri; const feedviewpostreplydid = thereply && !filterNoReplies ? new AtUri(thereply).host : undefined; const replyhookvalue = useQueryIdentity( feedviewpost ? feedviewpostreplydid : undefined ); const feedviewpostreplyhandle = replyhookvalue?.data?.handle; const aturirepostbydid = repostedby ? new AtUri(repostedby).host : undefined; const repostedbyhookvalue = useQueryIdentity( repostedby ? aturirepostbydid : undefined ); const feedviewpostrepostedbyhandle = repostedbyhookvalue?.data?.handle; if (filterNoReplies && thereply) return null; if (filterMustHaveMedia && !hasMedia) return null; if (filterMustBeReply && !thereply) return null; return ( <> {/*

{postRecord?.value?.embed.$type + " " + JSON.stringify(hydratedEmbed)}

*/} {/* filtermustbereply is {filterMustBeReply ? "true" : "false"} thereply is {thereply ? "true" : "false"} */} parsedaturi && navigate({ to: "/profile/$did/post/$rkey", params: { did: parsedaturi.host, rkey: parsedaturi.rkey }, }) } // onProfileClick={() => parsedaturi && navigate({to: "/profile/$did", // params: {did: parsedaturi.did} // })} onProfileClick={(e) => { e.stopPropagation(); if (parsedaturi) { navigate({ to: "/profile/$did", params: { did: parsedaturi.host }, }); } }} post={fakepost} uprrrsauthor={fakeprofileviewdetailed} salt={aturi} bottomReplyLine={bottomReplyLine} topReplyLine={topReplyLine} bottomBorder={bottomBorder} //extraOptionalItemInfo={{reply: postRecord?.value?.reply as AppBskyFeedDefs.ReplyRef, post: fakepost}} feedviewpostreplyhandle={feedviewpostreplyhandle} repostedby={feedviewpostrepostedbyhandle} style={style} ref={ref} dataIndexPropPass={dataIndexPropPass} nopics={nopics} concise={concise} lightboxCallback={lightboxCallback} maxReplies={maxReplies} isQuote={isQuote} /> ); } // export function parseAtUri( // atUri: string // ): { did: string; collection: string; rkey: string } | null { // const PREFIX = "at://"; // if (!atUri.startsWith(PREFIX)) { // return null; // } // const parts = atUri.slice(PREFIX.length).split("/"); // if (parts.length !== 3) { // return null; // } // const [did, collection, rkey] = parts; // if (!did || !collection || !rkey) { // return null; // } // return { did, collection, rkey }; // } export function MdiCommentOutline(props: SVGProps) { return ( ); } export function MdiRepeat(props: SVGProps) { return ( ); } export function MdiRepeatGreen(props: SVGProps) { return ( ); } export function MdiCardsHeart(props: SVGProps) { return ( ); } export function MdiCardsHeartOutline(props: SVGProps) { return ( ); } export function MdiShareVariant(props: SVGProps) { return ( ); } export function MdiMoreHoriz(props: SVGProps) { return ( ); } export function MdiGlobe(props: SVGProps) { return ( ); } export function MdiVerified(props: SVGProps) { return ( ); } export function MdiReply(props: SVGProps) { return ( ); } export function LineMdLoadingLoop(props: SVGProps) { return ( ); } export function MdiRepost(props: SVGProps) { return ( ); } export function MdiRepeatVariant(props: SVGProps) { return ( ); } export function MdiPlayCircle(props: SVGProps) { return ( ); } /* what imported from testfront */ //import Masonry from "@mui/lab/Masonry"; import { type $Typed, AppBskyActorDefs, AppBskyEmbedDefs, AppBskyEmbedExternal, AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, AppBskyEmbedVideo, AppBskyFeedDefs, AppBskyFeedPost, AppBskyGraphDefs, AtUri, type Facet, //AppBskyLabelerDefs, //AtUri, //ComAtprotoRepoStrongRef, ModerationDecision, } from "@atproto/api"; import type { //BlockedPost, FeedViewPost, //NotFoundPost, PostView, //ThreadViewPost, } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; import { useInfiniteQuery } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; import ReactPlayer from "react-player"; import defaultpfp from "~/../public/favicon.png"; import { useAuth } from "~/providers/UnifiedAuthProvider"; import { renderSnack } from "~/routes/__root"; import { FeedItemRenderAturiLoader, FollowButton, Mutual, } from "~/routes/profile.$did"; import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i"; import { useFastLike } from "~/utils/likeMutationQueue"; // import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed"; // import type { // ViewRecord, // ViewNotFound, // ViewBlocked, // ViewDetached, // } from "@atproto/api/dist/client/types/app/bsky/embed/record"; //import type { MasonryItemData } from "./onemason/masonry.types"; //import { MasonryLayout } from "./onemason/MasonryLayout"; // const agent = new AtpAgent({ // service: 'https://public.api.bsky.app' // }) type HitSlopButtonProps = React.ButtonHTMLAttributes & { hitSlop?: number; }; const HitSlopButtonCustom: React.FC = ({ children, hitSlop = 8, style, ...rest }) => ( ); const HitSlopButton = ({ onClick, children, style = {}, ...rest }: React.HTMLAttributes & { onClick?: (e: React.MouseEvent) => void; children: React.ReactNode; style?: React.CSSProperties; }) => ( { e.stopPropagation(); onClick?.(e); }} /> {children} ); const btnstyle = { display: "flex", gap: 4, cursor: "pointer", alignItems: "center", fontSize: 14, }; function randomString(length = 8) { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return Array.from( { length }, () => chars[Math.floor(Math.random() * chars.length)] ).join(""); } function UniversalPostRenderer({ post, uprrrsauthor, //setMainItem, //isMainItem, onPostClick, onProfileClick, expanded, //expanded, isQuote, //isQuote, extraOptionalItemInfo, bottomReplyLine, topReplyLine, salt, bottomBorder = true, feedviewpostreplyhandle, depth = 0, repostedby, style, ref, dataIndexPropPass, nopics, concise, lightboxCallback, maxReplies, }: { post: PostView; uprrrsauthor?: AppBskyActorDefs.ProfileViewDetailed; // optional for now because i havent ported every use to this yet // setMainItem?: React.Dispatch< // React.SetStateAction // >; //isMainItem?: boolean; onPostClick?: (e: React.MouseEvent) => void; onProfileClick?: (e: React.MouseEvent) => void; expanded?: boolean; isQuote?: boolean; extraOptionalItemInfo?: FeedViewPost; bottomReplyLine?: boolean; topReplyLine?: boolean; salt: string; bottomBorder?: boolean; feedviewpostreplyhandle?: string; depth?: number; repostedby?: string; style?: React.CSSProperties; ref?: React.RefObject; dataIndexPropPass?: number; nopics?: boolean; concise?: boolean; lightboxCallback?: (d: LightboxProps) => void; maxReplies?: number; }) { const parsed = new AtUri(post.uri); const navigate = useNavigate(); const [hasRetweeted, setHasRetweeted] = useState( post.viewer?.repost ? true : false ); const [, setComposerPost] = useAtom(composerAtom); const { agent } = useAuth(); const [retweetUri, setRetweetUri] = useState( post.viewer?.repost ); const { liked, toggle, backfill } = useFastLike(post.uri, post.cid); // const bovref = useBackfillOnView(post.uri, post.cid); // React.useLayoutEffect(()=>{ // if (expanded && !isQuote) { // backfill(); // } // },[backfill, expanded, isQuote]) const repostOrUnrepostPost = async () => { if (!agent) { console.error("Agent is null or undefined"); return; } if (hasRetweeted) { if (retweetUri) { await agent.deleteRepost(retweetUri); setHasRetweeted(false); } } else { const { uri } = await agent.repost(post.uri, post.cid); setRetweetUri(uri); setHasRetweeted(true); } }; const isRepost = repostedby ? repostedby : extraOptionalItemInfo ? AppBskyFeedDefs.isReasonRepost(extraOptionalItemInfo.reason) ? extraOptionalItemInfo.reason?.by.displayName : undefined : undefined; const isReply = extraOptionalItemInfo ? extraOptionalItemInfo.reply : undefined; const emergencySalt = randomString(); const [showBridgyText] = useAtom(enableBridgyTextAtom); const [showWafrnText] = useAtom(enableWafrnTextAtom); const unfedibridgy = (post.record as { bridgyOriginalText?: string }) .bridgyOriginalText; const unfediwafrnPartial = (post.record as { fullText?: string }).fullText; const unfediwafrnTags = (post.record as { fullTags?: string }).fullTags; const unfediwafrnUnHost = (post.record as { fediverseId?: string }) .fediverseId; const undfediwafrnHost = unfediwafrnUnHost ? new URL(unfediwafrnUnHost).hostname : undefined; const tags = unfediwafrnTags ? unfediwafrnTags .split("\n") .map((t) => t.trim()) .filter(Boolean) : undefined; const links = tags ? tags .map((tag) => { const encoded = encodeURIComponent(tag); return `#${tag.replaceAll(" ", "-")}`; }) .join("
") : ""; const unfediwafrn = unfediwafrnPartial ? unfediwafrnPartial + (links ? `
${links}` : "") : undefined; const fedi = (showBridgyText ? unfedibridgy : undefined) ?? (showWafrnText ? unfediwafrn : undefined); /* fuck you */ const isMainItem = false; const setMainItem = (any: any) => {}; // eslint-disable-next-line react-hooks/refs //console.log("Received ref in UniversalPostRenderer:", usedref); return (
{ setMainItem({ post: post }); onPostClick(e); } : () => { setMainItem({ post: post }); } : undefined } style={{ //...style, //border: "1px solid #e1e8ed", //borderRadius: 12, opacity: "1 !important", background: "transparent", paddingLeft: isQuote ? 12 : 16, paddingRight: isQuote ? 12 : 16, //paddingTop: 16, paddingTop: isRepost ? 10 : isQuote ? 12 : topReplyLine ? 8 : 16, //paddingBottom: bottomReplyLine ? 0 : 16, paddingBottom: 0, fontFamily: "system-ui, sans-serif", //boxShadow: "0 2px 8px rgba(0,0,0,0.04)", position: "relative", // dont cursor: "pointer", borderBottomWidth: bottomBorder ? (isQuote ? 0 : 1) : 0, }} className="border-gray-300 dark:border-gray-800" > {isRepost && (
Reposted by @{isRepost}{" "}
)} {!isQuote && (
)}
avatar
avatar
{post.author.displayName || post.author.handle}{" "}
@ {post.author.handle}{" "}
{uprrrsauthor?.description && (
{uprrrsauthor.description}
)} {/*
0
Following
2,900
Followers
*/}
{/* */}
{/* dummy for later use */}
{/* reply line !!!! bottomReplyLine */} {bottomReplyLine && (
)} {/*
*/}
· {/* time placeholder */} {shortTimeAgo(post.indexedAt)}
{/* reply indicator */} {!!feedviewpostreplyhandle && (
Reply to @{feedviewpostreplyhandle}
)}
{fedi ? ( <> ) : ( <> {renderTextWithFacets({ text: (post.record as { text?: string }).text ?? "", facets: (post.record.facets as Facet[]) ?? [], navigate: navigate, })} )}
{post.embed && depth < 1 && !concise ? ( ) : null} {post.embed && depth > 0 && ( /* pretty bad hack imo. its trying to sync up with how the embed shim doesnt hydrate embeds this deep but the connection here is implicit todo: idk make this a real part of the embed shim so its not implicit */ <>
(there is an embed here thats too deep to render)
)}
<> {expanded && (
{fullDateTimeFormat(post.indexedAt)}
)} {!isQuote && (
{ setComposerPost({ kind: "reply", parent: post.uri }); }} style={{ ...btnstyle, }} > {post.replyCount}
{hasRetweeted ? : } {post.repostCount ?? 0}
{hasRetweeted ? "Undo Repost" : "Repost"} { setComposerPost({ kind: "quote", subject: post.uri, }); }} className="px-3 py-2 text-sm flex items-center gap-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-700" > {/* You might want a specific quote icon here */} Quote
{ toggle(); }} style={{ ...btnstyle, ...(liked ? { color: "#EC4899" } : {}), }} > {liked ? : } {(post.likeCount || 0) + (liked ? 1 : 0)}
{ e.stopPropagation(); try { await navigator.clipboard.writeText( "https://bsky.app" + "/profile/" + post.author.handle + "/post/" + post.uri.split("/").pop() ); renderSnack({ title: "Copied to clipboard!", }); } catch (_e) { // idk renderSnack({ title: "Failed to copy link", }); } }} style={{ ...btnstyle, }} > { renderSnack({ title: "Not implemented yet...", }); }} >
)}
); } const fullDateTimeFormat = (iso: string) => { const date = new Date(iso); return date.toLocaleString("en-US", { month: "long", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", hour12: true, }); }; const shortTimeAgo = (iso: string) => { const diff = Date.now() - new Date(iso).getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return "now"; if (mins < 60) return `${mins}m`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h`; const days = Math.floor(hrs / 24); return `${days}d`; }; // const toAtUri = (url: string) => // url // .replace("https://bsky.app/profile/", "at://") // .replace("/feed/", "/app.bsky.feed.generator/"); // function PostSizedElipsis() { // return ( //
//
// // more posts // //
// ); // } type Embed = | AppBskyEmbedRecord.View | AppBskyEmbedImages.View | AppBskyEmbedVideo.View | AppBskyEmbedExternal.View | AppBskyEmbedRecordWithMedia.View | { $type: string; [k: string]: unknown }; enum PostEmbedViewContext { ThreadHighlighted = "ThreadHighlighted", Feed = "Feed", FeedEmbedRecordWithMedia = "FeedEmbedRecordWithMedia", } const stopgap = { display: "flex", justifyContent: "center", padding: "32px 12px", borderRadius: 12, border: "1px solid rgba(161, 170, 174, 0.38)", }; function PostEmbeds({ embed, moderation, onOpen, allowNestedQuotes, viewContext, salt, navigate, postid, nopics, lightboxCallback, }: { embed?: Embed; moderation?: ModerationDecision; onOpen?: () => void; allowNestedQuotes?: boolean; viewContext?: PostEmbedViewContext; salt: string; navigate: (_: any) => void; postid?: { did: string; rkey: string }; nopics?: boolean; lightboxCallback?: (d: LightboxProps) => void; }) { //const [lightboxIndex, setLightboxIndex] = useState(null); function setLightboxIndex(number: number) { navigate({ to: "/profile/$did/post/$rkey/image/$i", params: { did: postid?.did, rkey: postid?.rkey, i: number.toString(), }, }); } if ( AppBskyEmbedRecordWithMedia.isView(embed) && AppBskyEmbedRecord.isViewRecord(embed.record.record) && AppBskyFeedPost.isRecord(embed.record.record.value) //&& //AppBskyFeedPost.validateRecord(embed.record.record.value).success ) { const post: PostView = { $type: "app.bsky.feed.defs#postView", // lmao lies uri: embed.record.record.uri, cid: embed.record.record.cid, author: embed.record.record.author, record: embed.record.record.value as { [key: string]: unknown }, embed: embed.record.record.embeds ? embed.record.record.embeds?.[0] : undefined, // quotes handles embeds differently, its an array for some reason replyCount: embed.record.record.replyCount, repostCount: embed.record.record.repostCount, likeCount: embed.record.record.likeCount, quoteCount: embed.record.record.quoteCount, indexedAt: embed.record.record.indexedAt, // we dont have a viewer, so this is a best effort conversion, still requires full query later on labels: embed.record.record.labels, // neither do we have threadgate. remember to please fetch the full post later }; return (
{/* padding empty div of 8px height */}
{/* stopgap sorry*/}
{ e.stopPropagation(); const parsed = new AtUri(post.uri); //parseAtUri(post.uri); if (parsed) { navigate({ to: "/profile/$did/post/$rkey", params: { did: parsed.host, rkey: parsed.rkey }, }); } }} depth={1} />
{/* */} {/* stopgap sorry */} {/*
quote post placeholder
*/} {/* {quote post placeholder
*/} {/* {} */}
); } if (AppBskyEmbedRecord.isView(embed)) { // hey im really lazy and im gonna do it the bad way const reallybaduri = (embed?.record as any)?.uri as string | undefined; const reallybadaturi = reallybaduri ? new AtUri(reallybaduri) : undefined; // custom feed embed (i.e. generator view) if (AppBskyFeedDefs.isGeneratorView(embed.record)) { // stopgap sorry return
feedgen placeholder
; // return ( //
// //
// ) } else if ( !!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.feed.generator" ) { return (
); } // list embed if (AppBskyGraphDefs.isListView(embed.record)) { // stopgap sorry return
list placeholder
; // return ( //
// //
// ) } else if ( !!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.list" ) { return (
); } // starter pack embed if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) { // stopgap sorry return
starter pack card placeholder
; // return ( //
// //
// ) } else if ( !!reallybaduri && !!reallybadaturi && reallybadaturi.collection === "app.bsky.graph.starterpack" ) { return (
); } // quote post // = // stopgap sorry if ( AppBskyEmbedRecord.isViewRecord(embed.record) && AppBskyFeedPost.isRecord(embed.record.value) // && //AppBskyFeedPost.validateRecord(embed.record.value).success ) { const post: PostView = { $type: "app.bsky.feed.defs#postView", // lmao lies uri: embed.record.uri, cid: embed.record.cid, author: embed.record.author, record: embed.record.value as { [key: string]: unknown }, embed: embed.record.embeds ? embed.record.embeds?.[0] : undefined, // quotes handles embeds differently, its an array for some reason replyCount: embed.record.replyCount, repostCount: embed.record.repostCount, likeCount: embed.record.likeCount, quoteCount: embed.record.quoteCount, indexedAt: embed.record.indexedAt, // we dont have a viewer, so this is a best effort conversion, still requires full query later on labels: embed.record.labels, // neither do we have threadgate. remember to please fetch the full post later }; return (
{ e.stopPropagation(); const parsed = new AtUri(post.uri); //parseAtUri(post.uri); if (parsed) { navigate({ to: "/profile/$did/post/$rkey", params: { did: parsed.host, rkey: parsed.rkey }, }); } }} depth={1} />
); } else { console.log("what the hell is a ", embed); return <>sorry; } //return ; //return
quote post placeholder
; // return ( // // ) } // image embed // = if (AppBskyEmbedImages.isView(embed)) { const { images } = embed; const lightboxImages = images.map((img) => ({ src: img.fullsize, alt: img.alt, })); console.log("rendering images"); if (lightboxCallback) { lightboxCallback({ images: lightboxImages }); console.log("rendering images"); } if (nopics) return; if (images.length > 0) { // const items = embed.images.map(img => ({ // uri: img.fullsize, // thumbUri: img.thumb, // alt: img.alt, // dimensions: img.aspectRatio ?? null, // })) if (images.length === 1) { const image = images[0]; return (
{ const { width, height } = image.aspectRatio; const ratio = width / height; return ratio < 0.5 ? "1 / 2" : `${width} / ${height}`; })() : "1 / 1", // fallback to square //backgroundColor: theme.background, // fallback letterboxing color borderRadius: 12, //border: `1px solid ${theme.border}`, overflow: "hidden", }} className="border border-gray-200 dark:border-gray-800 was7 bg-gray-200 dark:bg-gray-900" > {/* {lightboxIndex !== null && ( setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} post={postid} /> )} */} {image.alt} { e.stopPropagation(); setLightboxIndex(0); }} />
); } // 2 images: side by side, both 1:1, cropped if (images.length === 2) { return (
{/* {lightboxIndex !== null && ( setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} post={postid} /> )} */} {images.map((img, i) => (
{img.alt} { e.stopPropagation(); setLightboxIndex(i); }} />
))}
); } // 3 images: left is 1:1, right is two stacked 2:1 if (images.length === 3) { return (
{/* {lightboxIndex !== null && ( setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} post={postid} /> )} */} {/* Left: 1:1 */}
{images[0].alt} { e.stopPropagation(); setLightboxIndex(0); }} />
{/* Right: two stacked 2:1 */}
{[1, 2].map((i) => (
{images[i].alt} { e.stopPropagation(); setLightboxIndex(i + 1); }} />
))}
); } // 4 images: 2x2 grid, all 3:2 if (images.length === 4) { return (
{/* {lightboxIndex !== null && ( setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} post={postid} /> )} */} {images.map((img, i) => (
{img.alt} { e.stopPropagation(); setLightboxIndex(i); }} />
))}
); } // stopgap sorry return
image count more than one placeholder
; // return ( //
// //
// ) } } // external link embed // = if (AppBskyEmbedExternal.isView(embed)) { const link = embed.external; return ( ); } // video embed // = if (AppBskyEmbedVideo.isView(embed)) { // hls playlist if (nopics) return; const playlist = embed.playlist; return ( ); // stopgap sorry //return (
video
) // return ( // // ) } return
; } function getDomain(url: string) { try { const { hostname } = new URL(url); return hostname; } catch (e) { // In case it's a bare domain like "example.com" if (!url.startsWith("http")) { try { const { hostname } = new URL("http://" + url); return hostname; } catch { return null; } } return null; } } function getByteToCharMap(text: string): number[] { const encoder = new TextEncoder(); //const utf8 = encoder.encode(text); const map: number[] = []; let byteIndex = 0; let charIndex = 0; for (const char of text) { const bytes = encoder.encode(char); for (let i = 0; i < bytes.length; i++) { map[byteIndex++] = charIndex; } charIndex += char.length; } return map; } function facetByteRangeToCharRange( byteStart: number, byteEnd: number, byteToCharMap: number[] ): [number, number] { return [ byteToCharMap[byteStart] ?? 0, byteToCharMap[byteEnd - 1]! + 1, // inclusive end -> exclusive char end ]; } interface FacetRange { start: number; end: number; feature: Facet["features"][number]; } function extractFacetRanges(text: string, facets: Facet[]): FacetRange[] { const map = getByteToCharMap(text); return facets.map((f) => { const [start, end] = facetByteRangeToCharRange( f.index.byteStart, f.index.byteEnd, map ); return { start, end, feature: f.features[0] }; }); } export function renderTextWithFacets({ text, facets, navigate, }: { text: string; facets: Facet[]; navigate: (_: any) => void; }) { const ranges = extractFacetRanges(text, facets).sort( (a: any, b: any) => a.start - b.start ); const result: React.ReactNode[] = []; let current = 0; for (const { start, end, feature } of ranges) { if (current < start) { result.push({text.slice(current, start)}); } const fragment = text.slice(start, end); // @ts-expect-error i didnt bother with the correct types here sorry. bsky api types are cursed if (feature.$type === "app.bsky.richtext.facet#link" && feature.uri) { result.push( { e.stopPropagation(); }} > {fragment} ); } else if ( feature.$type === "app.bsky.richtext.facet#mention" && // @ts-expect-error i didnt bother with the correct types here sorry. bsky api types are cursed feature.did ) { result.push( { e.stopPropagation(); navigate({ to: "/profile/$did", // @ts-expect-error i didnt bother with the correct types here sorry. bsky api types are cursed params: { did: feature.did }, }); }} > {fragment} ); } else if (feature.$type === "app.bsky.richtext.facet#tag") { result.push( { e.stopPropagation(); }} > {fragment} ); } else { result.push({fragment}); } current = end; } if (current < text.length) { result.push({text.slice(current)}); } return result; } function ExternalLinkEmbed({ link, onOpen, style, }: { link: AppBskyEmbedExternal.ViewExternal; onOpen?: () => void; style?: React.CSSProperties; }) { //const { theme } = useTheme(); const { uri, title, description, thumb } = link; const thumbAspectRatio = 1.91; const titleStyle = { fontSize: 16, fontWeight: 700, marginBottom: 4, //color: theme.text, wordBreak: "break-word", textAlign: "left", maxHeight: "4em", // 2 lines * 1.5em line-height // stupid shit display: "-webkit-box", WebkitBoxOrient: "vertical", overflow: "hidden", WebkitLineClamp: 2, }; const descriptionStyle = { fontSize: 14, //color: theme.textSecondary, marginBottom: 8, wordBreak: "break-word", textAlign: "left", maxHeight: "5em", // 3 lines * 1.5em line-height // stupid shit display: "-webkit-box", WebkitBoxOrient: "vertical", overflow: "hidden", WebkitLineClamp: 3, }; const linkStyle = { textDecoration: "none", //color: theme.textSecondary, wordBreak: "break-all", textAlign: "left", }; const containerStyle = { display: "flex", flexDirection: "column", //backgroundColor: theme.background, //background: '#eee', borderRadius: 12, //border: `1px solid ${theme.border}`, //boxShadow: theme.cardShadow, maxWidth: "100%", overflow: "hidden", ...style, }; return ( { e.stopPropagation(); if (onOpen) onOpen(); }} /* @ts-expect-error css arent typed or something idk fuck you */ style={linkStyle} className="text-gray-500 dark:text-gray-400" >
{thumb && (
{description}
)}
{/* @ts-expect-error css */}
{title}
{description}
{/* small 1px divider here */}
{getDomain(uri)}
); } const SmartHLSPlayer = ({ url, thumbnail, aspect, }: { url: string; thumbnail?: string; aspect?: AppBskyEmbedDefs.AspectRatio; }) => { const [playing, setPlaying] = useState(false); const containerRef = useRef(null); // pause the player if it goes out of viewport useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (!entry.isIntersecting && playing) { setPlaying(false); } }, { root: null, threshold: 0.25, } ); if (containerRef.current) { observer.observe(containerRef.current); } return () => { if (containerRef.current) { observer.unobserve(containerRef.current); } }; }, [playing]); return (
{!playing && ( <> Video thumbnail { e.stopPropagation(); setPlaying(true); }} />
{ e.stopPropagation(); setPlaying(true); }} style={{ position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", //fontSize: 48, color: "white", //textShadow: theme.cardShadow, pointerEvents: "none", userSelect: "none", }} className="text-shadow-md" > {/*▶️*/}
)} {playing && (
{/* setPlaying(false)} onEnded={() => setPlaying(false)} /> */}
)}
); };