a tool for shared writing and social publishing

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

+131 -51
+116 -23
app/reader/ReaderContent.tsx
··· 1 "use client"; 2 import { AtUri } from "@atproto/api"; 3 - import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 4 import { PubIcon } from "components/ActionBar/Publications"; 5 import { ButtonPrimary } from "components/Buttons"; 6 import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 7 - import { ShareSmall } from "components/Icons/ShareSmall"; 8 import { Separator } from "components/Layout"; 9 - import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 10 import { SpeedyLink } from "components/SpeedyLink"; 11 import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; 12 import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; 13 import { PubLeafletDocument, PubLeafletPublication } from "lexicons/api"; 14 - import Link from "next/link"; 15 import { blobRefToSrc } from "src/utils/blobRefToSrc"; 16 import { Json } from "supabase/database.types"; 17 ··· 23 pubRecord: Json; 24 uri: string; 25 }; 26 - documents: { data: Json; uri: string; indexed_at: string }; 27 }[]; 28 }) => { 29 if (props.posts.length === 0) return <ReaderEmpty />; ··· 40 uri: string; 41 href: string; 42 }; 43 - documents: { data: Json | undefined; uri: string; indexed_at: string }; 44 }) => { 45 let pubRecord = props.publication.pubRecord as PubLeafletPublication.Record; 46 ··· 60 61 let showPageBackground = pubRecord.theme?.showPageBackground; 62 63 return ( 64 <BaseThemeProvider {...theme} local> 65 <div ··· 93 <h3 className="text-primary truncate">{postRecord.title}</h3> 94 95 <p className="text-secondary">{postRecord.description}</p> 96 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 - })} 114 </div> 115 </div> 116 </div> ··· 118 ); 119 }; 120 121 const ReaderEmpty = () => { 122 return ( 123 <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 "use client"; 2 import { AtUri } from "@atproto/api"; 3 + import { Interactions } from "app/lish/[did]/[publication]/[rkey]/Interactions/Interactions"; 4 import { PubIcon } from "components/ActionBar/Publications"; 5 import { ButtonPrimary } from "components/Buttons"; 6 + import { CommentTiny } from "components/Icons/CommentTiny"; 7 import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 8 + import { QuoteTiny } from "components/Icons/QuoteTiny"; 9 import { Separator } from "components/Layout"; 10 import { SpeedyLink } from "components/SpeedyLink"; 11 import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; 12 import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; 13 import { PubLeafletDocument, PubLeafletPublication } from "lexicons/api"; 14 import { blobRefToSrc } from "src/utils/blobRefToSrc"; 15 import { Json } from "supabase/database.types"; 16 ··· 22 pubRecord: Json; 23 uri: string; 24 }; 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 + }; 40 }[]; 41 }) => { 42 if (props.posts.length === 0) return <ReaderEmpty />; ··· 53 uri: string; 54 href: string; 55 }; 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 + }; 71 }) => { 72 let pubRecord = props.publication.pubRecord as PubLeafletPublication.Record; 73 ··· 87 88 let showPageBackground = pubRecord.theme?.showPageBackground; 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 + 96 return ( 97 <BaseThemeProvider {...theme} local> 98 <div ··· 126 <h3 className="text-primary truncate">{postRecord.title}</h3> 127 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> 142 143 + <PostInterations 144 + quotesCount={quotes} 145 + commentsCount={comments} 146 + showComments={pubRecord.preferences?.showComments} 147 + /> 148 </div> 149 </div> 150 </div> ··· 152 ); 153 }; 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 + }; 214 const ReaderEmpty = () => { 215 return ( 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 import { PubListing } from "app/discover/PubListing"; 2 - import { PublicationsList } from "app/discover/page"; 3 import { ButtonPrimary } from "components/Buttons"; 4 import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 5 import { Json } from "supabase/database.types";
··· 1 import { PubListing } from "app/discover/PubListing"; 2 import { ButtonPrimary } from "components/Buttons"; 3 import { DiscoverSmall } from "components/Icons/DiscoverSmall"; 4 import { Json } from "supabase/database.types";
+10 -6
app/reader/page.tsx
··· 92 if (!auth_res?.atp_did) return; 93 let { data: publications } = await supabaseServerClient 94 .from("publication_subscriptions") 95 - .select(`publications(*, documents_in_publications(documents(*)))`) 96 .eq("identity", auth_res?.atp_did); 97 98 // get publications to fit PublicationList type ··· 127 data: postInPub.documents!.data, 128 uri: postInPub.documents!.uri, 129 indexed_at: postInPub.documents!.indexed_at, 130 }, 131 })); 132 }) || []; ··· 172 content: ( 173 <SubscriptionsContent publications={subbedPublications} /> 174 ), 175 - }, 176 - discover: { 177 - controls: null, 178 - content: <></>, 179 - href: "/discover", 180 }, 181 }} 182 />
··· 92 if (!auth_res?.atp_did) return; 93 let { data: publications } = await supabaseServerClient 94 .from("publication_subscriptions") 95 + .select( 96 + `publications(*, documents_in_publications(documents( 97 + *, 98 + comments_on_documents(count), 99 + document_mentions_in_bsky(count) 100 + )))`, 101 + ) 102 .eq("identity", auth_res?.atp_did); 103 104 // get publications to fit PublicationList type ··· 133 data: postInPub.documents!.data, 134 uri: postInPub.documents!.uri, 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, 139 }, 140 })); 141 }) || []; ··· 181 content: ( 182 <SubscriptionsContent publications={subbedPublications} /> 183 ), 184 }, 185 }} 186 />
+4 -4
components/ActionBar/Publications.tsx
··· 99 }) => { 100 if (!props.record) return; 101 102 return props.record.icon ? ( 103 <div 104 style={{ ··· 107 backgroundSize: "cover", 108 backgroundImage: `url(/api/atproto_images?did=${new AtUri(props.uri).host}&cid=${(props.record.icon?.ref as unknown as { $link: string })["$link"]})`, 109 }} 110 - className={`${props.small ? "w-5 h-5" : props.large ? "w-12 h-12" : "w-6 h-6"} rounded-full ${props.className}`} 111 /> 112 ) : ( 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 - > 116 <div 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 >
··· 99 }) => { 100 if (!props.record) return; 101 102 + let iconSizeClassName = `${props.small ? "w-4 h-4" : props.large ? "w-12 h-12" : "w-6 h-6"} rounded-full`; 103 + 104 return props.record.icon ? ( 105 <div 106 style={{ ··· 109 backgroundSize: "cover", 110 backgroundImage: `url(/api/atproto_images?did=${new AtUri(props.uri).host}&cid=${(props.record.icon?.ref as unknown as { $link: string })["$link"]})`, 111 }} 112 + className={`${iconSizeClassName} ${props.className}`} 113 /> 114 ) : ( 115 + <div className={`${iconSizeClassName} bg-accent-1 relative`}> 116 <div 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 >
+1 -17
components/PageLayouts/DashboardLayout.tsx
··· 122 [name: string]: { 123 content: React.ReactNode; 124 controls: React.ReactNode; 125 - href?: string; 126 }; 127 }, 128 >(props: { ··· 135 actions: React.ReactNode; 136 }) { 137 let [tab, setTab] = useState(props.defaultTab); 138 - let { content, controls, href } = props.tabs[tab]; 139 140 let [headerState, setHeaderState] = useState<"default" | "controls">( 141 "default", ··· 166 {Object.keys(props.tabs).length > 1 && ( 167 <div className="pubDashTabs flex flex-row gap-1"> 168 {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 return ( 185 <Tab 186 key={t}
··· 122 [name: string]: { 123 content: React.ReactNode; 124 controls: React.ReactNode; 125 }; 126 }, 127 >(props: { ··· 134 actions: React.ReactNode; 135 }) { 136 let [tab, setTab] = useState(props.defaultTab); 137 + let { content, controls } = props.tabs[tab]; 138 139 let [headerState, setHeaderState] = useState<"default" | "controls">( 140 "default", ··· 165 {Object.keys(props.tabs).length > 1 && ( 166 <div className="pubDashTabs flex flex-row gap-1"> 167 {Object.keys(props.tabs).map((t) => { 168 return ( 169 <Tab 170 key={t}