a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

added comment and quote counts to post listing, cleaned up some other stuff

celine 0e194d64 9c9192e2

+131 -51
+116 -23
app/reader/ReaderContent.tsx
··· 1 1 "use client"; 2 2 import { AtUri } from "@atproto/api"; 3 - import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 3 + import { Interactions } from "app/lish/[did]/[publication]/[rkey]/Interactions/Interactions"; 4 4 import { PubIcon } from "components/ActionBar/Publications"; 5 5 import { ButtonPrimary } from "components/Buttons"; 6 + import { CommentTiny } from "components/Icons/CommentTiny"; 6 7 import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 7 - import { ShareSmall } from "components/Icons/ShareSmall"; 8 + import { QuoteTiny } from "components/Icons/QuoteTiny"; 8 9 import { Separator } from "components/Layout"; 9 - import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 10 10 import { SpeedyLink } from "components/SpeedyLink"; 11 11 import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; 12 12 import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; 13 13 import { PubLeafletDocument, PubLeafletPublication } from "lexicons/api"; 14 - import Link from "next/link"; 15 14 import { blobRefToSrc } from "src/utils/blobRefToSrc"; 16 15 import { Json } from "supabase/database.types"; 17 16 ··· 23 22 pubRecord: Json; 24 23 uri: string; 25 24 }; 26 - documents: { data: Json; uri: string; indexed_at: string }; 25 + documents: { 26 + data: Json; 27 + uri: string; 28 + indexed_at: string; 29 + comments_on_documents: 30 + | { 31 + count: number; 32 + }[] 33 + | undefined; 34 + document_mentions_in_bsky: 35 + | { 36 + count: number; 37 + }[] 38 + | undefined; 39 + }; 27 40 }[]; 28 41 }) => { 29 42 if (props.posts.length === 0) return <ReaderEmpty />; ··· 40 53 uri: string; 41 54 href: string; 42 55 }; 43 - documents: { data: Json | undefined; uri: string; indexed_at: string }; 56 + documents: { 57 + data: Json; 58 + uri: string; 59 + indexed_at: string; 60 + comments_on_documents: 61 + | { 62 + count: number; 63 + }[] 64 + | undefined; 65 + document_mentions_in_bsky: 66 + | { 67 + count: number; 68 + }[] 69 + | undefined; 70 + }; 44 71 }) => { 45 72 let pubRecord = props.publication.pubRecord as PubLeafletPublication.Record; 46 73 ··· 60 87 61 88 let showPageBackground = pubRecord.theme?.showPageBackground; 62 89 90 + let quotes = props.documents.document_mentions_in_bsky?.[0]?.count || 0; 91 + let comments = 92 + pubRecord.preferences?.showComments === false 93 + ? 0 94 + : props.documents.comments_on_documents?.[0]?.count || 0; 95 + 63 96 return ( 64 97 <BaseThemeProvider {...theme} local> 65 98 <div ··· 93 126 <h3 className="text-primary truncate">{postRecord.title}</h3> 94 127 95 128 <p className="text-secondary">{postRecord.description}</p> 129 + <div className="flex justify-between items-end"> 130 + <div className="flex flex-col-reverse md:flex-row md gap-3 md:gap-2 text-sm text-tertiary items-center justify-start pt-1 md:pt-3"> 131 + <PubInfo 132 + href={props.publication.href} 133 + pubRecord={pubRecord} 134 + uri={props.publication.uri} 135 + /> 136 + <Separator classname="h-4 !min-h-0 md:block hidden" /> 137 + <PostInfo 138 + author="NAME HERE" 139 + publishedAt={postRecord.publishedAt} 140 + /> 141 + </div> 96 142 97 - <div className="flex gap-2 text-sm text-tertiary items-center pt-3"> 98 - <SpeedyLink 99 - href={props.publication.href} 100 - className="text-accent-contrast font-bold no-underline text-sm flex gap-[6px] items-center relative" 101 - > 102 - <PubIcon small record={pubRecord} uri={props.publication.uri} /> 103 - {pubRecord.name} 104 - </SpeedyLink> 105 - <Separator classname="h-4 !min-h-0" /> 106 - NAME HERE 107 - <Separator classname="h-4 !min-h-0" /> 108 - {postRecord.publishedAt && 109 - new Date(postRecord.publishedAt).toLocaleDateString("en-US", { 110 - year: "numeric", 111 - month: "short", 112 - day: "numeric", 113 - })} 143 + <PostInterations 144 + quotesCount={quotes} 145 + commentsCount={comments} 146 + showComments={pubRecord.preferences?.showComments} 147 + /> 114 148 </div> 115 149 </div> 116 150 </div> ··· 118 152 ); 119 153 }; 120 154 155 + const PubInfo = (props: { 156 + href: string; 157 + pubRecord: PubLeafletPublication.Record; 158 + uri: string; 159 + }) => { 160 + return ( 161 + <SpeedyLink 162 + href={props.href} 163 + className="text-accent-contrast font-bold no-underline text-sm flex gap-1 items-center md:w-fit w-full relative shrink-0" 164 + > 165 + <PubIcon small record={props.pubRecord} uri={props.uri} /> 166 + {props.pubRecord.name} 167 + </SpeedyLink> 168 + ); 169 + }; 170 + 171 + const PostInfo = (props: { 172 + author: string; 173 + publishedAt: string | undefined; 174 + }) => { 175 + return ( 176 + <div className="flex gap-2 items-center shrink-0"> 177 + NAME HERE 178 + {props.publishedAt && ( 179 + <> 180 + <Separator classname="h-4 !min-h-0" /> 181 + {new Date(props.publishedAt).toLocaleDateString("en-US", { 182 + year: "numeric", 183 + month: "short", 184 + day: "numeric", 185 + })}{" "} 186 + </> 187 + )} 188 + </div> 189 + ); 190 + }; 191 + 192 + const PostInterations = (props: { 193 + quotesCount: number; 194 + commentsCount: number; 195 + showComments: boolean | undefined; 196 + }) => { 197 + return ( 198 + <div className={`flex gap-2 text-tertiary text-sm `}> 199 + {props.quotesCount === 0 ? null : ( 200 + <div className={`flex gap-1 items-center `}> 201 + <span className="sr-only">Post quotes</span> 202 + <QuoteTiny aria-hidden /> {props.quotesCount} 203 + </div> 204 + )} 205 + {props.showComments === false ? null : ( 206 + <div className={`flex gap-1 items-center`}> 207 + <span className="sr-only">Post comments</span> 208 + <CommentTiny aria-hidden /> {props.commentsCount} 209 + </div> 210 + )} 211 + </div> 212 + ); 213 + }; 121 214 const ReaderEmpty = () => { 122 215 return ( 123 216 <div className="flex flex-col gap-2 container bg-[rgba(var(--bg-page),.7)] sm:p-4 p-3 justify-between text-center font-bold text-tertiary">
-1
app/reader/SubscriptionsContent.tsx
··· 1 1 import { PubListing } from "app/discover/PubListing"; 2 - import { PublicationsList } from "app/discover/page"; 3 2 import { ButtonPrimary } from "components/Buttons"; 4 3 import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 5 4 import { Json } from "supabase/database.types";
+10 -6
app/reader/page.tsx
··· 92 92 if (!auth_res?.atp_did) return; 93 93 let { data: publications } = await supabaseServerClient 94 94 .from("publication_subscriptions") 95 - .select(`publications(*, documents_in_publications(documents(*)))`) 95 + .select( 96 + `publications(*, documents_in_publications(documents( 97 + *, 98 + comments_on_documents(count), 99 + document_mentions_in_bsky(count) 100 + )))`, 101 + ) 96 102 .eq("identity", auth_res?.atp_did); 97 103 98 104 // get publications to fit PublicationList type ··· 127 133 data: postInPub.documents!.data, 128 134 uri: postInPub.documents!.uri, 129 135 indexed_at: postInPub.documents!.indexed_at, 136 + comments_on_documents: postInPub.documents?.comments_on_documents, 137 + document_mentions_in_bsky: 138 + postInPub.documents?.document_mentions_in_bsky, 130 139 }, 131 140 })); 132 141 }) || []; ··· 172 181 content: ( 173 182 <SubscriptionsContent publications={subbedPublications} /> 174 183 ), 175 - }, 176 - discover: { 177 - controls: null, 178 - content: <></>, 179 - href: "/discover", 180 184 }, 181 185 }} 182 186 />
+4 -4
components/ActionBar/Publications.tsx
··· 99 99 }) => { 100 100 if (!props.record) return; 101 101 102 + let iconSizeClassName = `${props.small ? "w-4 h-4" : props.large ? "w-12 h-12" : "w-6 h-6"} rounded-full`; 103 + 102 104 return props.record.icon ? ( 103 105 <div 104 106 style={{ ··· 107 109 backgroundSize: "cover", 108 110 backgroundImage: `url(/api/atproto_images?did=${new AtUri(props.uri).host}&cid=${(props.record.icon?.ref as unknown as { $link: string })["$link"]})`, 109 111 }} 110 - className={`${props.small ? "w-5 h-5" : props.large ? "w-12 h-12" : "w-6 h-6"} rounded-full ${props.className}`} 112 + className={`${iconSizeClassName} ${props.className}`} 111 113 /> 112 114 ) : ( 113 - <div 114 - className={`${props.small ? "w-5 h-5" : props.large ? "w-12 h-12" : "w-6 h-6"} rounded-full bg-accent-1 relative`} 115 - > 115 + <div className={`${iconSizeClassName} bg-accent-1 relative`}> 116 116 <div 117 117 className={`${props.small ? "text-xs" : props.large ? "text-2xl" : "text-sm"} font-bold absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-accent-2`} 118 118 >
+1 -17
components/PageLayouts/DashboardLayout.tsx
··· 122 122 [name: string]: { 123 123 content: React.ReactNode; 124 124 controls: React.ReactNode; 125 - href?: string; 126 125 }; 127 126 }, 128 127 >(props: { ··· 135 134 actions: React.ReactNode; 136 135 }) { 137 136 let [tab, setTab] = useState(props.defaultTab); 138 - let { content, controls, href } = props.tabs[tab]; 137 + let { content, controls } = props.tabs[tab]; 139 138 140 139 let [headerState, setHeaderState] = useState<"default" | "controls">( 141 140 "default", ··· 166 165 {Object.keys(props.tabs).length > 1 && ( 167 166 <div className="pubDashTabs flex flex-row gap-1"> 168 167 {Object.keys(props.tabs).map((t) => { 169 - if (props.tabs[t].href) 170 - return ( 171 - <Link 172 - key={t} 173 - href={props.tabs[t].href} 174 - className="no-underline" 175 - > 176 - <Tab 177 - name={t} 178 - selected={t === tab} 179 - href={props.tabs[t].href} 180 - onSelect={() => setTab(t)} 181 - /> 182 - </Link> 183 - ); 184 168 return ( 185 169 <Tab 186 170 key={t}