"use client"; import { AtUri } from "@atproto/api"; import { PubIcon } from "components/ActionBar/Publications"; import { PublicationThemeWrapper } from "components/ThemeManager/PublicationThemeProvider"; import { blobRefToSrc, COVER_THUMBNAIL_WIDTH } from "src/utils/blobRefToSrc"; import type { NormalizedDocument, NormalizedPublication, } from "src/utils/normalizeRecords"; import { hasLeafletContent } from "lexicons/src/normalize"; import type { Post } from "actions/reader/getReaderFeed"; import Link from "next/link"; import { useEffect, useRef, useState } from "react"; import { PostByline } from "./PostByline"; import { namedBylineProfiles } from "src/utils/byline"; import { useLocalizedDate } from "src/hooks/useLocalizedDate"; import { useSelectedPostListing } from "src/useSelectedPostState"; import { mergePreferences } from "src/utils/mergePreferences"; import { ExternalLinkTiny } from "./Icons/ExternalLinkTiny"; import { getDocumentURL } from "src/utils/getPublicationURL"; import { RecommendButton } from "./Interactions/RecommendButton"; import { getFirstParagraph } from "src/utils/getFirstParagraph"; import { DiscussionButton } from "./Interactions/DiscussionButton"; import { InteractionShareButton } from "./Interactions/InteractionShareButton"; import { PublicationPostItemLarge } from "app/(app)/(published)/lish/[did]/[publication]/PublicationPostItem"; export const PostListing = (props: Post & { selected?: boolean }) => { let pubRecord = props.publication?.pubRecord as | NormalizedPublication | undefined; let postRecord = props.documents.data as NormalizedDocument | null; // Don't render anything for records that can't be normalized (e.g., site.standard records without expected fields) if (!postRecord) { return null; } let postUri = new AtUri(props.documents.uri); let uri = props.publication ? props.publication?.uri : props.documents.uri; let themeRecord = pubRecord?.theme || postRecord?.theme; let elRef = useRef(null); let [hasBackgroundImage, setHasBackgroundImage] = useState(false); useEffect(() => { if (!themeRecord?.backgroundImage?.image || !elRef.current) { setHasBackgroundImage(false); return; } let alpha = Number( window .getComputedStyle(elRef.current) .getPropertyValue("--bg-page-alpha"), ); setHasBackgroundImage(alpha < 0.7); }, [themeRecord?.backgroundImage?.image]); let backgroundImage = themeRecord?.backgroundImage?.image?.ref && uri ? blobRefToSrc(themeRecord.backgroundImage.image.ref, new AtUri(uri).host) : null; let backgroundImageRepeat = themeRecord?.backgroundImage?.repeat; let backgroundImageSize = themeRecord?.backgroundImage?.width || 500; let showPageBackground = pubRecord ? pubRecord?.theme?.showPageBackground : postRecord.theme?.showPageBackground ?? true; let mergedPrefs = mergePreferences( postRecord?.preferences, pubRecord?.preferences, ); let quotes = props.documents.mentionsCount ?? props.documents.document_mentions_in_bsky?.[0]?.count ?? 0; let comments = mergedPrefs.showComments === false ? 0 : props.documents.comments_on_documents?.[0]?.count || 0; let recommends = props.documents.recommends_on_documents?.[0]?.count || 0; let tags = (postRecord?.tags as string[] | undefined) || []; let namedContributors = namedBylineProfiles(props.contributors); // For standalone posts, link directly to the document let postUrl = getDocumentURL(postRecord, props.documents.uri, pubRecord); let coverImageSrc = postRecord.coverImage ? blobRefToSrc(postRecord.coverImage.ref, postUri.host, undefined, { width: COVER_THUMBNAIL_WIDTH.large, }) : undefined; // Compute nodes conditionally so MetaRow doesn't render an orphan Separator // when there's no author or no date. let author = namedContributors.length > 0 ? ( ) : undefined; let date = postRecord.publishedAt ? ( ) : undefined; let pubInfo = props.publication && pubRecord ? ( ) : undefined; let interactions = (
); return (
); }; const PubInfo = (props: { href: string; pubRecord: NormalizedPublication; uri: string; postRecord: NormalizedDocument; }) => { let isLeaflet = hasLeafletContent(props.postRecord); let cleanUrl = props.pubRecord.url ?.replace(/^https?:\/\//, "") .replace(/^www\./, ""); return (
{props.pubRecord.name}
{!isLeaflet && (
{cleanUrl}
)}
); }; const PostDate = (props: { publishedAt: string | undefined }) => { let localizedDate = useLocalizedDate(props.publishedAt || "", { year: "numeric", month: "short", day: "numeric", }); if (props.publishedAt) { return
{localizedDate}
; } else return null; }; const Interactions = (props: { quotesCount: number; commentsCount: number; recommendsCount: number; tags?: string[]; postUrl: string; showComments: boolean; showMentions: boolean; documentUri: string; document: NormalizedDocument; publication?: NormalizedPublication; }) => { let setSelectedPostListing = useSelectedPostListing( (s) => s.setSelectedPostListing, ); let defaultDrawer: "comments" | "quotes" = props.showComments && props.commentsCount > 0 ? "comments" : "quotes"; return (
{ // Keep the listing highlighted (read by the reader feed) while the // modal is up. if (open) setSelectedPostListing({ document_uri: props.documentUri, document: props.document, publication: props.publication, drawer: defaultDrawer, }); else setSelectedPostListing(null); }} />
); };