a tool for shared writing and social publishing
at main 5.4 kB view raw
1"use client"; 2import { AtUri } from "@atproto/api"; 3import { PubIcon } from "components/ActionBar/Publications"; 4import { CommentTiny } from "components/Icons/CommentTiny"; 5import { QuoteTiny } from "components/Icons/QuoteTiny"; 6import { Separator } from "components/Layout"; 7import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; 8import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; 9import { useSmoker } from "components/Toast"; 10import { PubLeafletDocument, PubLeafletPublication } from "lexicons/api"; 11import { blobRefToSrc } from "src/utils/blobRefToSrc"; 12import type { Post } from "app/(home-pages)/reader/getReaderFeed"; 13 14import Link from "next/link"; 15import { InteractionPreview } from "./InteractionsPreview"; 16import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 17 18export const PostListing = (props: Post) => { 19 let pubRecord = props.publication?.pubRecord as 20 | PubLeafletPublication.Record 21 | undefined; 22 23 let postRecord = props.documents.data as PubLeafletDocument.Record; 24 let postUri = new AtUri(props.documents.uri); 25 let uri = props.publication ? props.publication?.uri : props.documents.uri; 26 27 // For standalone documents (no publication), pass isStandalone to get correct defaults 28 let isStandalone = !pubRecord; 29 let theme = usePubTheme(pubRecord?.theme || postRecord?.theme, isStandalone); 30 let themeRecord = pubRecord?.theme || postRecord?.theme; 31 let backgroundImage = 32 themeRecord?.backgroundImage?.image?.ref && uri 33 ? blobRefToSrc(themeRecord.backgroundImage.image.ref, new AtUri(uri).host) 34 : null; 35 36 let backgroundImageRepeat = themeRecord?.backgroundImage?.repeat; 37 let backgroundImageSize = themeRecord?.backgroundImage?.width || 500; 38 39 let showPageBackground = pubRecord 40 ? pubRecord?.theme?.showPageBackground 41 : postRecord.theme?.showPageBackground ?? true; 42 43 let quotes = props.documents.document_mentions_in_bsky?.[0]?.count || 0; 44 let comments = 45 pubRecord?.preferences?.showComments === false 46 ? 0 47 : props.documents.comments_on_documents?.[0]?.count || 0; 48 let tags = (postRecord?.tags as string[] | undefined) || []; 49 50 // For standalone posts, link directly to the document 51 let postHref = props.publication 52 ? `${props.publication.href}/${postUri.rkey}` 53 : `/p/${postUri.host}/${postUri.rkey}`; 54 55 return ( 56 <BaseThemeProvider {...theme} local> 57 <div 58 style={{ 59 backgroundImage: backgroundImage 60 ? `url(${backgroundImage})` 61 : undefined, 62 backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", 63 backgroundSize: `${backgroundImageRepeat ? `${backgroundImageSize}px` : "cover"}`, 64 }} 65 className={`no-underline! flex flex-row gap-2 w-full relative 66 bg-bg-leaflet 67 border border-border-light rounded-lg 68 sm:p-2 p-2 selected-outline 69 hover:outline-accent-contrast hover:border-accent-contrast 70 `} 71 > 72 <Link className="h-full w-full absolute top-0 left-0" href={postHref} /> 73 <div 74 className={`${showPageBackground ? "bg-bg-page " : "bg-transparent"} rounded-md w-full px-[10px] pt-2 pb-2`} 75 style={{ 76 backgroundColor: showPageBackground 77 ? "rgba(var(--bg-page), var(--bg-page-alpha))" 78 : "transparent", 79 }} 80 > 81 <h3 className="text-primary truncate">{postRecord.title}</h3> 82 83 <p className="text-secondary italic">{postRecord.description}</p> 84 <div className="flex flex-col-reverse md:flex-row md gap-2 text-sm text-tertiary items-center justify-start pt-1.5 md:pt-3 w-full"> 85 {props.publication && pubRecord && ( 86 <PubInfo 87 href={props.publication.href} 88 pubRecord={pubRecord} 89 uri={props.publication.uri} 90 /> 91 )} 92 <div className="flex flex-row justify-between gap-2 items-center w-full"> 93 <PostInfo publishedAt={postRecord.publishedAt} /> 94 <InteractionPreview 95 postUrl={postHref} 96 quotesCount={quotes} 97 commentsCount={comments} 98 tags={tags} 99 showComments={pubRecord?.preferences?.showComments} 100 share 101 /> 102 </div> 103 </div> 104 </div> 105 </div> 106 </BaseThemeProvider> 107 ); 108}; 109 110const PubInfo = (props: { 111 href: string; 112 pubRecord: PubLeafletPublication.Record; 113 uri: string; 114}) => { 115 return ( 116 <div className="flex flex-col md:w-auto shrink-0 w-full"> 117 <hr className="md:hidden block border-border-light mb-2" /> 118 <Link 119 href={props.href} 120 className="text-accent-contrast font-bold no-underline text-sm flex gap-1 items-center md:w-fit relative shrink-0" 121 > 122 <PubIcon small record={props.pubRecord} uri={props.uri} /> 123 {props.pubRecord.name} 124 </Link> 125 </div> 126 ); 127}; 128 129const PostInfo = (props: { publishedAt: string | undefined }) => { 130 let localizedDate = useLocalizedDate(props.publishedAt || "", { 131 year: "numeric", 132 month: "short", 133 day: "numeric", 134 }); 135 return ( 136 <div className="flex gap-2 items-center shrink-0 self-start"> 137 {props.publishedAt && ( 138 <> 139 <div className="shrink-0">{localizedDate}</div> 140 </> 141 )} 142 </div> 143 ); 144};