diff --git a/app/(home-pages)/notifications/FollowNotification.tsx b/app/(home-pages)/notifications/FollowNotification.tsx index dbdd5efd..a0d82043 100644 --- a/app/(home-pages)/notifications/FollowNotification.tsx +++ b/app/(home-pages)/notifications/FollowNotification.tsx @@ -23,7 +23,7 @@ export const FollowNotification = (props: HydratedSubscribeNotification) => { } + icon={} actionText={ <> {displayName} subscribed to {pubRecord?.name}! diff --git a/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx b/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx index be7d44da..3fb2b0fd 100644 --- a/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx +++ b/app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx @@ -23,7 +23,7 @@ export const ProfileHeader = (props: { src={profileRecord.avatar} displayName={profileRecord.displayName} className="profileAvatar mx-auto mt-3 sm:mt-4" - giant + size="giant" /> ); @@ -100,7 +100,10 @@ const ProfileLinks = (props: { handle: string }) => { ); }; -const PublicationCard = (props: { record: NormalizedPublication; uri: string }) => { +const PublicationCard = (props: { + record: NormalizedPublication; + uri: string; +}) => { const { record, uri } = props; const { bgLeaflet, bgPage, primary } = usePubTheme(record.theme); diff --git a/app/lish/[did]/[publication]/[rkey]/BlueskyQuotesPage.tsx b/app/lish/[did]/[publication]/[rkey]/BlueskyQuotesPage.tsx index 299969a0..014664e5 100644 --- a/app/lish/[did]/[publication]/[rkey]/BlueskyQuotesPage.tsx +++ b/app/lish/[did]/[publication]/[rkey]/BlueskyQuotesPage.tsx @@ -7,7 +7,12 @@ import { DotLoader } from "components/utils/DotLoader"; import { QuoteTiny } from "components/Icons/QuoteTiny"; import { openPage } from "./PostPages"; import { BskyPostContent } from "./BskyPostContent"; -import { QuotesLink, getQuotesKey, fetchQuotes, prefetchQuotes } from "./PostLinks"; +import { + QuotesLink, + getQuotesKey, + fetchQuotes, + prefetchQuotes, +} from "./PostLinks"; // Re-export for backwards compatibility export { QuotesLink, getQuotesKey, fetchQuotes, prefetchQuotes }; @@ -27,7 +32,9 @@ export function BlueskyQuotesPage(props: { data: quotesData, isLoading, error, - } = useSWR(postUri ? getQuotesKey(postUri) : null, () => fetchQuotes(postUri)); + } = useSWR(postUri ? getQuotesKey(postUri) : null, () => + fetchQuotes(postUri), + ); return ( {posts.map((post) => ( - + ))} ); } -function QuotePost(props: { - post: PostView; - quotesUri: string; -}) { +function QuotePost(props: { post: PostView; quotesUri: string }) { const { post, quotesUri } = props; const parent = { type: "quotes" as const, uri: quotesUri }; @@ -94,10 +94,8 @@ function QuotePost(props: { e.stopPropagation()} onEmbedClick={(e) => e.stopPropagation()} /> diff --git a/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx b/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx index 579a9255..cb1e5d0b 100644 --- a/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx +++ b/app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx @@ -1,8 +1,6 @@ "use client"; import { AppBskyFeedDefs, AppBskyFeedPost } from "@atproto/api"; -import { - BlueskyEmbed, -} from "components/Blocks/BlueskyPostBlock/BlueskyEmbed"; +import { BlueskyEmbed } from "components/Blocks/BlueskyPostBlock/BlueskyEmbed"; import { BlueskyRichText } from "components/Blocks/BlueskyPostBlock/BlueskyRichText"; import { BlueskyTiny } from "components/Icons/BlueskyTiny"; import { CommentTiny } from "components/Icons/CommentTiny"; @@ -12,86 +10,104 @@ import { useLocalizedDate } from "src/hooks/useLocalizedDate"; import { useHasPageLoaded } from "components/InitialPageLoadProvider"; import { OpenPage } from "./PostPages"; import { ThreadLink, QuotesLink } from "./PostLinks"; +import { BlueskyLinkTiny } from "components/Icons/BlueskyLinkTiny"; +import { Avatar } from "components/Avatar"; +import { timeAgo } from "src/utils/timeAgo"; +import { ProfilePopover } from "components/ProfilePopover"; type PostView = AppBskyFeedDefs.PostView; export function BskyPostContent(props: { post: PostView; parent?: OpenPage; - linksEnabled?: boolean; - avatarSize?: "sm" | "md"; + avatarSize?: "tiny" | "medium" | "large" | "giant"; + className?: string; showEmbed?: boolean; showBlueskyLink?: boolean; onEmbedClick?: (e: React.MouseEvent) => void; - onLinkClick?: (e: React.MouseEvent) => void; + quoteCountOnClick?: (e: React.MouseEvent) => void; + replyCountOnClick?: (e: React.MouseEvent) => void; }) { const { post, parent, - linksEnabled = true, avatarSize = "md", showEmbed = true, showBlueskyLink = true, onEmbedClick, - onLinkClick, + quoteCountOnClick, + replyCountOnClick, } = props; const record = post.record as AppBskyFeedPost.Record; const postId = post.uri.split("/")[4]; const url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; - const avatarClass = avatarSize === "sm" ? "w-8 h-8" : "w-10 h-10"; - return ( <> -
- {post.author.avatar ? ( - {`${post.author.displayName}'s - ) : ( -
- )} -
+ -
-
-
- {post.author.displayName} +
+
+
+
+ {post.author.displayName} +
+ + @{post.author.handle} +
+ } + didOrHandle={post.author.handle} + /> +
+
+ {timeAgo(record.createdAt, { compact: true })}
- - @{post.author.handle} -
-
+
{showEmbed && post.embed && (
- +
)}
-
- +
+
+ {showBlueskyLink && ( + <> + + + + + )} +
@@ -101,61 +117,51 @@ export function BskyPostContent(props: { function PostCounts(props: { post: PostView; parent?: OpenPage; - linksEnabled: boolean; + quoteCountOnClick?: (e: React.MouseEvent) => void; + replyCountOnClick?: (e: React.MouseEvent) => void; showBlueskyLink: boolean; url: string; - onLinkClick?: (e: React.MouseEvent) => void; }) { - const { post, parent, linksEnabled, showBlueskyLink, url, onLinkClick } = props; - return ( -
- {post.replyCount != null && post.replyCount > 0 && ( +
+ {props.post.replyCount != null && props.post.replyCount > 0 && ( <> - - {linksEnabled ? ( + {props.replyCountOnClick ? ( - {post.replyCount} + {props.post.replyCount} ) : ( -
- {post.replyCount} +
+ {props.post.replyCount}
)} )} - {post.quoteCount != null && post.quoteCount > 0 && ( - <> - - - {post.quoteCount} - - - - )} - {showBlueskyLink && ( + {props.post.quoteCount != null && props.post.quoteCount > 0 && ( <> - - - - + {props.quoteCountOnClick ? ( + + + {props.post.quoteCount} + + ) : ( +
+ + {props.post.quoteCount} +
+ )} )}
diff --git a/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx b/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx index 33e771f0..fde12e67 100644 --- a/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx +++ b/app/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx @@ -25,7 +25,7 @@ export type Comment = { uri: string; bsky_profiles: { record: Json; did: string } | null; }; -export function Comments(props: { +export function CommentsDrawerContent(props: { document_uri: string; comments: Comment[]; pageId?: string; diff --git a/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx b/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx index 9283aa1c..b690925b 100644 --- a/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx +++ b/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx @@ -1,9 +1,9 @@ "use client"; import { Media } from "components/Media"; -import { Quotes } from "./Quotes"; +import { MentionsDrawerContent } from "./Quotes"; import { InteractionState, useInteractionState } from "./Interactions"; import { Json } from "supabase/database.types"; -import { Comment, Comments } from "./Comments"; +import { Comment, CommentsDrawerContent } from "./Comments"; import { useSearchParams } from "next/navigation"; import { SandwichSpacer } from "components/LeafletLayout"; import { decodeQuotePosition } from "../quotePosition"; @@ -42,9 +42,12 @@ export const InteractionDrawer = (props: { className={`opaque-container h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll ${props.showPageBackground ? "rounded-l-none! rounded-r-lg! -ml-[1px]" : "rounded-lg! sm:ml-4"}`} > {drawer.drawer === "quotes" ? ( - + ) : ( - { @@ -85,18 +86,13 @@ export const Quotes = (props: { }); return ( -
-
- Quotes - -
+
+ {props.quotesAndMentions.length === 0 ? (
no quotes yet!
@@ -108,59 +104,58 @@ export const Quotes = (props: {
) : ( -
- {/* Quotes with links (quoted content) */} - {quotesWithLinks.map((q, index) => { - const pv = postViewMap.get(q.uri); - if (!pv || !q.link) return null; - const url = new URL(q.link); - const quoteParam = url.pathname.split("/l-quote/")[1]; - if (!quoteParam) return null; - const quotePosition = decodeQuotePosition(quoteParam); - if (!quotePosition) return null; - return ( -
- - -
- -
- ); - })} - +
+ {quotesWithLinks.length > 0 && ( +
+ Quotes + {/* Quotes with links (quoted content) */} + {quotesWithLinks.map((q, index) => { + return ( +
+ +
+ ); + })} +
+ )} {/* Direct post mentions (without quoted content) */} {directMentions.length > 0 && (
-
Post Mentions
+
+ Mentions on Bluesky +
{directMentions.map((q, index) => { - const pv = postViewMap.get(q.uri); - if (!pv) return null; + const post = postViewMap.get(q.uri); + if (!post) return null; + + const parent = { type: "thread" as const, uri: q.uri }; return ( - + ); })}
@@ -172,6 +167,44 @@ export const Quotes = (props: { ); }; +const Quote = (props: { + q: { + uri: string; + link?: string; + }; + index: number; + did: string; + postViewMap: Map; +}) => { + const post = props.postViewMap.get(props.q.uri); + if (!post || !props.q.link) return null; + const parent = { type: "thread" as const, uri: props.q.uri }; + const url = new URL(props.q.link); + const quoteParam = url.pathname.split("/l-quote/")[1]; + if (!quoteParam) return null; + const quotePosition = decodeQuotePosition(quoteParam); + if (!quotePosition) return null; + + return ( +
+ + +
+ +
+ ); +}; + export const QuoteContent = (props: { position: QuotePosition; index: number; @@ -206,12 +239,15 @@ export const QuoteContent = (props: { className="quoteSectionQuote text-secondary text-sm text-left hover:cursor-pointer" onClick={(e) => { if (props.position.pageId) - flushSync(() => openPage(undefined, { type: "doc", id: props.position.pageId! })); + flushSync(() => + openPage(undefined, { type: "doc", id: props.position.pageId! }), + ); let scrollMargin = isMobile ? 16 : e.currentTarget.getBoundingClientRect().top; let scrollContainerId = `post-page-${props.position.pageId ?? document_uri}`; - let scrollContainer = window.document.getElementById(scrollContainerId); + let scrollContainer = + window.document.getElementById(scrollContainerId); let el = window.document.getElementById( props.position.start.block.join("."), ); @@ -244,72 +280,6 @@ export const QuoteContent = (props: { ); }; -export const BskyPost = (props: { - uri: string; - rkey: string; - content: string; - user: string; - handle: string; - profile: ProfileViewBasic; - replyCount?: number; - quoteCount?: number; -}) => { - const handleOpenThread = () => { - openPage(undefined, { type: "thread", uri: props.uri }); - }; - - return ( -
- {props.profile.avatar && ( - {props.profile.displayName} - )} -
- -
{props.content}
-
- {props.replyCount != null && props.replyCount > 0 && ( - e.stopPropagation()} - className="flex items-center gap-1 text-tertiary text-xs hover:text-accent-contrast" - > - - {props.replyCount} {props.replyCount === 1 ? "reply" : "replies"} - - )} - {props.quoteCount != null && props.quoteCount > 0 && ( - e.stopPropagation()} - className="flex items-center gap-1 text-tertiary text-xs hover:text-accent-contrast" - > - - {props.quoteCount} {props.quoteCount === 1 ? "quote" : "quotes"} - - )} -
-
-
- ); -}; - function extractQuotedBlocks( blocks: PubLeafletPagesLinearDocument.Block[], quotePosition: QuotePosition, diff --git a/app/lish/[did]/[publication]/[rkey]/PostLinks.tsx b/app/lish/[did]/[publication]/[rkey]/PostLinks.tsx index 80da034b..0800cbda 100644 --- a/app/lish/[did]/[publication]/[rkey]/PostLinks.tsx +++ b/app/lish/[did]/[publication]/[rkey]/PostLinks.tsx @@ -104,7 +104,6 @@ export function QuotesLink(props: { const handlePrefetch = () => { prefetchQuotes(postUri); }; - return ( - {isCollapsed ? ( - - ) : ( -
- -
- )} -
- )} - {hasReplies && depth >= 3 && ( - - View more replies - - )} -
+ { + e.stopPropagation(); + e.preventDefault(); + if (parentUri) toggleCollapsed(parentUri); + console.log("collapse?"); + }} + isCollapsed={isCollapsed} + depth={props.depth} + /> ); })} + {parentUri && depth > 0 && replies.length > 3 && ( + +
+ View {replies.length - 3} more{" "} + {replies.length === 4 ? "reply" : "replies"} + + )}
); } -function ReplyPost(props: { +const ReplyPost = (props: { post: ThreadViewPost; - showReplyLine: boolean; isLast: boolean; threadUri: string; -}) { + toggleCollapsed: (e: React.MouseEvent) => void; + isCollapsed: boolean; + depth: number; +}) => { const { post, threadUri } = props; const postView = post.post; const parent = { type: "thread" as const, uri: threadUri }; + const hasReplies = props.post.replies && props.post.replies.length > 0; + + // was in the middle of trying to get the right set of comments to close when this line is clicked + // then i really need to style the parent and grandparent threads, hide some of the content unless its the main post + // the thread line on them is also weird return ( -
openPage(parent, { type: "thread", uri: postView.uri })} - > - e.stopPropagation()} - onEmbedClick={(e) => e.stopPropagation()} - /> +
+ {props.depth > 0 && ( + + )} + + + {hasReplies && props.depth < 3 && ( +
+ {!props.isCollapsed && ( +
+ +
+ )} +
+ )} + + {hasReplies && props.depth >= 3 && ( + + View more replies + + )}
); -} +}; diff --git a/components/Avatar.tsx b/components/Avatar.tsx index 8efc20a3..0e193e34 100644 --- a/components/Avatar.tsx +++ b/components/Avatar.tsx @@ -4,14 +4,25 @@ export const Avatar = (props: { src: string | undefined; displayName: string | undefined; className?: string; - tiny?: boolean; - large?: boolean; - giant?: boolean; + size?: "tiny" | "small" | "medium" | "large" | "giant"; }) => { + let sizeClassName = + props.size === "tiny" + ? "w-4 h-4" + : props.size === "small" + ? "w-5 h-5" + : props.size === "medium" + ? "h-6 w-6" + : props.size === "large" + ? "w-8 h-8" + : props.size === "giant" + ? "h-16 w-16" + : "w-6 h-6"; + if (props.src) return ( { - +
); }; diff --git a/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx b/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx index 96b70eb0..5d8eaefb 100644 --- a/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx +++ b/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx @@ -14,6 +14,7 @@ import { export const BlueskyEmbed = (props: { embed: Exclude; postUrl?: string; + className?: string; }) => { // check this file from bluesky for ref // https://github.com/bluesky-social/social-app/blob/main/bskyembed/src/components/embed.tsx @@ -81,7 +82,7 @@ export const BlueskyEmbed = (props: { {externalEmbed.external.thumb === undefined ? null : ( <> @@ -116,7 +117,7 @@ export const BlueskyEmbed = (props: { : 16 / 9; return (
+
0) { + return `${diffYears}y`; + } else if (diffMonths > 0) { + return `${diffMonths}mo`; + } else if (diffWeeks > 0) { + return `${diffWeeks}w`; + } else if (diffDays > 0) { + return `${diffDays}d`; + } else if (diffHours > 0) { + return `${diffHours}h`; + } else if (diffMinutes > 0) { + return `${diffMinutes}m`; + } else { + return "now"; + } + } + if (diffYears > 0) { return `${diffYears} year${diffYears === 1 ? "" : "s"} ago`; } else if (diffMonths > 0) {