diff --git a/src/components/UniversalPostRenderer.tsx b/src/components/UniversalPostRenderer.tsx index fba2ed8..32134de 100644 --- a/src/components/UniversalPostRenderer.tsx +++ b/src/components/UniversalPostRenderer.tsx @@ -2,7 +2,9 @@ import { useNavigate } from "@tanstack/react-router"; import { useAtom } from "jotai"; import * as React from "react"; import { type SVGProps } from "react"; +import { createPortal } from "react-dom"; +import { ProfilePostComponent } from "~/routes/profile.$did/post.$rkey"; import { likedPostsAtom } from "~/utils/atoms"; import { useHydratedEmbed } from "~/utils/useHydrated"; import { @@ -28,6 +30,10 @@ export interface UniversalPostRendererATURILoaderProps { bottomBorder?: boolean; feedviewpost?: boolean; repostedby?: string; + style?: React.CSSProperties; + ref?: React.Ref; + dataIndexPropPass?: number; + nopics?: boolean; } // export async function cachedGetRecord({ @@ -132,6 +138,10 @@ export function UniversalPostRendererATURILoader({ bottomBorder = true, feedviewpost = false, repostedby, + style, + ref, + dataIndexPropPass, + nopics, }: UniversalPostRendererATURILoaderProps) { // /*mass comment*/ console.log("atUri", atUri); //const { get, set } = usePersistentStore(); @@ -406,6 +416,10 @@ export function UniversalPostRendererATURILoader({ bottomBorder={bottomBorder} feedviewpost={feedviewpost} repostedby={repostedby} + style={style} + ref={ref} + dataIndexPropPass={dataIndexPropPass} + nopics={nopics} /> ); } @@ -430,6 +444,10 @@ export function UniversalPostRendererRawRecordShim({ bottomBorder = true, feedviewpost = false, repostedby, + style, + ref, + dataIndexPropPass, + nopics, }: { postRecord: any; profileRecord: any; @@ -444,6 +462,10 @@ export function UniversalPostRendererRawRecordShim({ bottomBorder?: boolean; feedviewpost?: boolean; repostedby?: string; + style?: React.CSSProperties; + ref?: React.Ref; + dataIndexPropPass?: number; + nopics?: boolean; }) { // /*mass comment*/ console.log(`received aturi: ${aturi} of post content: ${postRecord}`); const navigate = useNavigate(); @@ -638,6 +660,10 @@ export function UniversalPostRendererRawRecordShim({ //extraOptionalItemInfo={{reply: postRecord?.value?.reply as AppBskyFeedDefs.ReplyRef, post: fakepost}} feedviewpostreplyhandle={feedviewpostreplyhandle} repostedby={feedviewpostrepostedbyhandle} + style={style} + ref={ref} + dataIndexPropPass={dataIndexPropPass} + nopics={nopics} /> ); @@ -1079,6 +1105,10 @@ function UniversalPostRenderer({ feedviewpostreplyhandle, depth = 0, repostedby, + style, + ref, + dataIndexPropPass, + nopics, }: { post: PostView; // optional for now because i havent ported every use to this yet @@ -1098,7 +1128,12 @@ function UniversalPostRenderer({ feedviewpostreplyhandle?: string; depth?: number; repostedby?: string; + style?: React.CSSProperties; + ref?: React.Ref; + dataIndexPropPass?: number; + nopics?: boolean; }) { + const parsed = new AtUri(post.uri); const navigate = useNavigate(); const [likedPosts, setLikedPosts] = useAtom(likedPostsAtom); const [hasRetweeted, setHasRetweeted] = useState( @@ -1171,405 +1206,413 @@ function UniversalPostRenderer({ /* fuck you */ const isMainItem = false; const setMainItem = (any: any) => {}; + // eslint-disable-next-line react-hooks/refs + console.log("Received ref in UniversalPostRenderer:", ref); return ( -
{ - setMainItem({ post: post }); - onPostClick(e); - } - : () => { - setMainItem({ post: post }); - } - : undefined - } - 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 : 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-600" - > - {isRepost && ( -
- Reposted by @{isRepost}{" "} -
- )} - {!isQuote && ( -
- )} +
{ + setMainItem({ post: post }); + onPostClick(e); + } + : () => { + setMainItem({ post: post }); + } + : undefined + } style={{ - position: "absolute", - //top: isRepost ? "calc(16px + 1rem)" : 16, - //left: 16, - zIndex: 1, - top: isRepost ? "calc(16px + 1rem)" : isQuote ? 12 : 16, - left: isQuote ? 12 : 16, + //...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 : 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, }} - onClick={onProfileClick} + className="border-gray-300 dark:border-gray-600" > - avatar -
-
+ {isRepost && ( +
+ Reposted by @{isRepost}{" "} +
+ )} + {!isQuote && ( +
+ )}
- {/* dummy for later use */} -
- {/* reply line !!!! bottomReplyLine */} - {bottomReplyLine && ( -
- )} - {/*
*/} + className="border border-gray-300 dark:border-gray-600 bg-gray-300 dark:bg-gray-600" + />
-
+
+ {/* dummy for later use */} +
+ {/* reply line !!!! bottomReplyLine */} + {bottomReplyLine && ( +
+ )} + {/*
*/} +
+
+
- @{post.author.handle} - + + · {/* time placeholder */} + {shortTimeAgo(post.indexedAt)} + +
-
- - · {/* time placeholder */} - {shortTimeAgo(post.indexedAt)} - -
-
- {/* reply indicator */} - {!!feedviewpostreplyhandle && ( + Reply to @{feedviewpostreplyhandle} +
+ )}
0*/ ? 0 : 8, + whiteSpace: "pre-wrap", + textAlign: "left", + overflowWrap: "anywhere", + wordBreak: "break-word", + //color: theme.text, }} - className="text-gray-500 dark:text-gray-400" + className="text-gray-900 dark:text-gray-100" > - Reply to @{feedviewpostreplyhandle} + {renderTextWithFacets({ + text: (post.record as { text?: string }).text ?? "", + facets: (post.record.facets as Facet[]) ?? [], + navigate: navigate, + })} + {}
- )} -
0*/ ? 0 : 8, - whiteSpace: "pre-wrap", - textAlign: "left", - overflowWrap: "anywhere", - wordBreak: "break-word", - //color: theme.text, - }} - className="text-gray-900 dark:text-gray-100" - > - {renderTextWithFacets({ - text: (post.record as { text?: string }).text ?? "", - facets: (post.record.facets as Facet[]) ?? [], - navigate: navigate, - })} - {} -
- {post.embed && depth < 1 ? ( - - ) : null} - {post.embed && depth > 0 && ( - /* pretty bad hack imo. its trying to sync up with how the embed shim doesnt + {post.embed && depth < 1 ? ( + + ) : 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 && ( + <> +
+ (there is an embed here thats too deep to render) +
+ + )} +
+ <> + {expanded && ( +
+ {fullDateTimeFormat(post.indexedAt)} +
+ )} + + {!isQuote && (
- {fullDateTimeFormat(post.indexedAt)} -
- )} - - {!isQuote && ( -
- - - {post.replyCount} - - { - repostOrUnrepostPost(); - }} - style={{ - ...btnstyle, - ...(hasRetweeted ? { color: "#5CEFAA" } : {}), - }} - > - {hasRetweeted ? : } - {(post.repostCount || 0) + (hasRetweeted ? 1 : 0)} - - { - likeOrUnlikePost(); - }} - style={{ - ...btnstyle, - ...(hasLiked ? { color: "#EC4899" } : {}), + gap: 32, + paddingTop: 8, + //color: theme.textSecondary, + fontSize: 15, + justifyContent: "space-between", + //background: "#0f0", }} + className="text-gray-500 dark:text-gray-400" > - {hasLiked ? : } - {(post.likeCount || 0) + (hasLiked ? 1 : 0)} - -
+ + + {post.replyCount} + { - e.stopPropagation(); - try { - await navigator.clipboard.writeText( - "https://bsky.app" + - "/profile/" + - post.author.handle + - "/post/" + - post.uri.split("/").pop() - ); - } catch (_e) { - // idk - } + onClick={() => { + repostOrUnrepostPost(); }} style={{ ...btnstyle, + ...(hasRetweeted ? { color: "#5CEFAA" } : {}), }} > - + {hasRetweeted ? : } + {(post.repostCount || 0) + (hasRetweeted ? 1 : 0)} - - - + { + likeOrUnlikePost(); + }} + style={{ + ...btnstyle, + ...(hasLiked ? { color: "#EC4899" } : {}), + }} + > + {hasLiked ? : } + {(post.likeCount || 0) + (hasLiked ? 1 : 0)} + +
+ { + e.stopPropagation(); + try { + await navigator.clipboard.writeText( + "https://bsky.app" + + "/profile/" + + post.author.handle + + "/post/" + + post.uri.split("/").pop() + ); + } catch (_e) { + // idk + } + }} + style={{ + ...btnstyle, + }} + > + + + + + +
-
- )} + )} +
+
-
@@ -1661,6 +1704,8 @@ function PostEmbeds({ viewContext, salt, navigate, + postid, + nopics, }: { embed?: Embed; moderation?: ModerationDecision; @@ -1669,6 +1714,8 @@ function PostEmbeds({ viewContext?: PostEmbedViewContext; salt: string; navigate: (_: any) => void; + postid?: { did: string; rkey: string }; + nopics?: boolean; }) { const [lightboxIndex, setLightboxIndex] = useState(null); if ( @@ -1704,6 +1751,8 @@ function PostEmbeds({ viewContext={viewContext} salt={salt} navigate={navigate} + postid={postid} + nopics={nopics} /> {/* padding empty div of 8px height */}
@@ -1871,7 +1920,7 @@ function PostEmbeds({ // image embed // = - if (AppBskyEmbedImages.isView(embed)) { + if (AppBskyEmbedImages.isView(embed) && !nopics) { const { images } = embed; const lightboxImages = images.map((img) => ({ @@ -1915,6 +1964,7 @@ function PostEmbeds({ index={lightboxIndex} onClose={() => setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} + post={postid} /> )} setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} + post={postid} /> )} {images.map((img, i) => ( @@ -2004,6 +2055,7 @@ function PostEmbeds({ index={lightboxIndex} onClose={() => setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} + post={postid} /> )} {/* Left: 1:1 */} @@ -2088,6 +2140,7 @@ function PostEmbeds({ index={lightboxIndex} onClose={() => setLightboxIndex(null)} onNavigate={(newIndex) => setLightboxIndex(newIndex)} + post={postid} /> )} {images.map((img, i) => ( @@ -2180,18 +2233,19 @@ function PostEmbeds({ return
; } -import { createPortal } from "react-dom"; type LightboxProps = { images: { src: string; alt?: string }[]; index: number; onClose: () => void; onNavigate?: (newIndex: number) => void; + post?: { did: string; rkey: string }; }; export function Lightbox({ images, index, onClose, onNavigate, + post, }: LightboxProps) { const image = images[index]; @@ -2208,69 +2262,86 @@ export function Lightbox({ }, [index, images.length, onClose, onNavigate]); return createPortal( -
{ - e.stopPropagation(); - onClose(); - }} - > - {image.alt} e.stopPropagation()} - /> + <> + {post && ( +
{ + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + }} + className="lightbox-sidebar overscroll-none disablegutter border-l dark:border-gray-700 border-gray-300 fixed z-50 flex top-0 right-0 flex-col max-w-[350px] min-w-[350px] max-h-screen overflow-y-scroll dark:bg-gray-950 bg-white" + > + +
+ )} +
{ + e.stopPropagation(); + onClose(); + }} + > + {image.alt} e.stopPropagation()} + /> - {images.length > 1 && ( - <> - - + - - )} -
, + + + + + + + + + )} +
+ , document.body ); } diff --git a/src/main.tsx b/src/main.tsx index 83d81f5..b07915a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,17 +1,17 @@ -import { StrictMode } from "react"; -import ReactDOM from "react-dom/client"; -import { RouterProvider, createRouter } from "@tanstack/react-router"; - -// Import the generated route tree -import { routeTree } from "./routeTree.gen"; - import "~/styles/app.css"; -import reportWebVitals from "./reportWebVitals.ts"; + +import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; import { QueryClient, QueryClientProvider, } from "@tanstack/react-query"; import { persistQueryClient, } from "@tanstack/react-query-persist-client"; -import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; +import { createRouter,RouterProvider } from "@tanstack/react-router"; +//import { StrictMode } from "react"; +import ReactDOM from "react-dom/client"; + +import reportWebVitals from "./reportWebVitals.ts"; +// Import the generated route tree +import { routeTree } from "./routeTree.gen"; const queryClient = new QueryClient({ diff --git a/src/routes/profile.$did/post.$rkey.tsx b/src/routes/profile.$did/post.$rkey.tsx index 13c9d36..737beb8 100644 --- a/src/routes/profile.$did/post.$rkey.tsx +++ b/src/routes/profile.$did/post.$rkey.tsx @@ -1,5 +1,5 @@ import { useQueryClient } from "@tanstack/react-query"; -import { createFileRoute, Link } from "@tanstack/react-router"; +import { createFileRoute } from "@tanstack/react-router"; import React, { useLayoutEffect } from "react"; import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; @@ -32,7 +32,15 @@ function RouterWrapper() { ); } -function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) { +export function ProfilePostComponent({ + did, + rkey, + nopics, +}: { + did: string; + rkey: string; + nopics?: () => void; +}) { //const { get, set } = usePersistentStore(); const queryClient = useQueryClient(); // const [resolvedDid, setResolvedDid] = React.useState(null); @@ -201,10 +209,8 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) { const scrollAnchor = React.useRef<{ top: number } | null>(null); - React.useEffect(() => { const onScroll = () => { - if (window.scrollY > 50) { userHasScrolled.current = true; @@ -291,21 +297,35 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) { return ( <>
- { - e.preventDefault(); - if (window.history.length > 1) { - window.history.back(); - } else { - window.location.assign("/"); - } - }} - aria-label="Go back" - > - ← - + {!nopics ? ( + + ) : ( + + )} Post
@@ -322,7 +342,7 @@ function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) { )} {/* we should use the reply lines here thats provided by UPR*/} -
+
{parents.map((parent, index) => ( 0} + nopics={!!nopics} />
= 64rem /* 1024px */) { - html, - body { + html:not(:has(.disablegutter)), + body:not(:has(.disablegutter)) { scrollbar-gutter: stable both-edges !important; } + html:has(.disablegutter), + body:has(.disablegutter) { + scrollbar-width: none; + overflow-y: hidden; + } } + +.lightbox:has(+.lightbox-sidebar){ + opacity: 0; +} + .scroll-thin { scrollbar-width: thin; /*scrollbar-gutter: stable both-edges !important;*/