a tool for shared writing and social publishing
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
26 let theme = usePubTheme(pubRecord?.theme || postRecord?.theme);
27 let backgroundImage =
28 pubRecord?.theme?.backgroundImage?.image?.ref && props.publication
29 ? blobRefToSrc(
30 pubRecord.theme.backgroundImage.image.ref,
31 new AtUri(props.publication.uri).host,
32 )
33 : null;
34
35 let backgroundImageRepeat = pubRecord?.theme?.backgroundImage?.repeat;
36 let backgroundImageSize = pubRecord?.theme?.backgroundImage?.width || 500;
37
38 let showPageBackground = pubRecord?.theme?.showPageBackground;
39
40 let quotes = props.documents.document_mentions_in_bsky?.[0]?.count || 0;
41 let comments =
42 pubRecord?.preferences?.showComments === false
43 ? 0
44 : props.documents.comments_on_documents?.[0]?.count || 0;
45 let tags = (postRecord?.tags as string[] | undefined) || [];
46
47 // For standalone posts, link directly to the document
48 let postHref = props.publication
49 ? `${props.publication.href}/${postUri.rkey}`
50 : `/p/${postUri.host}/${postUri.rkey}`;
51
52 return (
53 <BaseThemeProvider {...theme} local>
54 <div
55 style={{
56 backgroundImage: backgroundImage
57 ? `url(${backgroundImage})`
58 : undefined,
59 backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat",
60 backgroundSize: `${backgroundImageRepeat ? `${backgroundImageSize}px` : "cover"}`,
61 }}
62 className={`no-underline! flex flex-row gap-2 w-full relative
63 bg-bg-leaflet
64 border border-border-light rounded-lg
65 sm:p-2 p-2 selected-outline
66 hover:outline-accent-contrast hover:border-accent-contrast
67 `}
68 >
69 <Link className="h-full w-full absolute top-0 left-0" href={postHref} />
70 <div
71 className={`${showPageBackground ? "bg-bg-page " : "bg-transparent"} rounded-md w-full px-[10px] pt-2 pb-2`}
72 style={{
73 backgroundColor: showPageBackground
74 ? "rgba(var(--bg-page), var(--bg-page-alpha))"
75 : "transparent",
76 }}
77 >
78 <h3 className="text-primary truncate">{postRecord.title}</h3>
79
80 <p className="text-secondary italic">{postRecord.description}</p>
81 <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">
82 {props.publication && pubRecord && (
83 <PubInfo
84 href={props.publication.href}
85 pubRecord={pubRecord}
86 uri={props.publication.uri}
87 />
88 )}
89 <div className="flex flex-row justify-between gap-2 items-center w-full">
90 <PostInfo publishedAt={postRecord.publishedAt} />
91 <InteractionPreview
92 postUrl={postHref}
93 quotesCount={quotes}
94 commentsCount={comments}
95 tags={tags}
96 showComments={pubRecord?.preferences?.showComments}
97 share
98 />
99 </div>
100 </div>
101 </div>
102 </div>
103 </BaseThemeProvider>
104 );
105};
106
107const PubInfo = (props: {
108 href: string;
109 pubRecord: PubLeafletPublication.Record;
110 uri: string;
111}) => {
112 return (
113 <div className="flex flex-col md:w-auto shrink-0 w-full">
114 <hr className="md:hidden block border-border-light mb-2" />
115 <Link
116 href={props.href}
117 className="text-accent-contrast font-bold no-underline text-sm flex gap-1 items-center md:w-fit relative shrink-0"
118 >
119 <PubIcon small record={props.pubRecord} uri={props.uri} />
120 {props.pubRecord.name}
121 </Link>
122 </div>
123 );
124};
125
126const PostInfo = (props: { publishedAt: string | undefined }) => {
127 let localizedDate = useLocalizedDate(props.publishedAt || "", {
128 year: "numeric",
129 month: "short",
130 day: "numeric",
131 });
132 return (
133 <div className="flex gap-2 items-center shrink-0 self-start">
134 {props.publishedAt && (
135 <>
136 <div className="shrink-0">{localizedDate}</div>
137 </>
138 )}
139 </div>
140 );
141};