a tool for shared writing and social publishing

pulling in the right data, styling adjustments

+154 -105
+3 -13
app/discover/PubListing.tsx
··· 1 1 "use client"; 2 2 import { AtUri } from "@atproto/syntax"; 3 + import { PubIcon } from "components/ActionBar/Publications"; 3 4 import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; 4 5 import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; 5 6 import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api"; ··· 42 43 px-3 py-3 selected-outline 43 44 hover:outline-accent-contrast hover:border-accent-contrast`} 44 45 > 45 - <div 46 - style={{ 47 - backgroundRepeat: "no-repeat", 48 - backgroundPosition: "center", 49 - backgroundSize: "cover", 50 - backgroundImage: record?.icon 51 - ? `url(${blobRefToSrc(record.icon?.ref, new AtUri(props.uri).host)})` 52 - : undefined, 53 - }} 54 - className={`w-6 h-6 rounded-full bg-accent-1 text-accent-2 flex place-content-center leading-snug font-bold text-center shrink-0 ${record.theme?.showPageBackground ? "mt-[6px]" : "mt-0.5"}`} 55 - > 56 - {!record?.icon ? record.name.slice(0, 1).toLocaleUpperCase() : null} 57 - </div> 46 + <PubIcon record={record} uri={props.uri} /> 47 + 58 48 <div 59 49 className={`flex w-full flex-col ${record.theme?.showPageBackground ? "bg-[rgba(var(--bg-page),var(--bg-page-alpha))] px-2 py-1 rounded-lg" : ""}`} 60 50 >
+5 -5
app/home/HomeEmpty/HomeEmpty.tsx
··· 66 66 export const PublicationBanner = (props: { small?: boolean }) => { 67 67 return ( 68 68 <div 69 - className={`accent-container flex sm:py-2 gap-4 items-center ${props.small ? "items-start p-2 text-sm font-normal" : "items-center p-4"}`} 69 + className={`accent-container flex sm:py-2 items-center ${props.small ? "items-start gap-2 p-2 text-sm font-normal" : "items-center p-4 gap-4"}`} 70 70 > 71 71 {props.small ? ( 72 - <PublishSmall className="shrink-0" /> 72 + <PublishSmall className="shrink-0 text-accent-contrast" /> 73 73 ) : ( 74 74 <div className="w-[64px] mx-auto"> 75 75 <PubListEmptyIllo /> 76 76 </div> 77 77 )} 78 - <div className="grow"> 78 + <div className={`${props.small ? "pt-[2px]" : ""} grow`}> 79 79 <Link href={"/lish/createPub"} className="font-bold"> 80 80 Start a Publication 81 81 </Link>{" "} ··· 88 88 export const DiscoverBanner = (props: { small?: boolean }) => { 89 89 return ( 90 90 <div 91 - className={`accent-container flex sm:py-2 gap-2 items-center ${props.small ? "items-start p-2 text-sm font-normal" : "items-center p-4"}`} 91 + className={`accent-container flex sm:py-2 items-center ${props.small ? "items-start gap-2 p-2 text-sm font-normal" : "items-center p-4 gap-4"}`} 92 92 > 93 93 {props.small ? ( 94 94 <DiscoverSmall className="shrink-0 text-accent-contrast" /> ··· 97 97 <DiscoverIllo /> 98 98 </div> 99 99 )} 100 - <div className="grow"> 100 + <div className={`${props.small ? "pt-[2px]" : ""} grow`}> 101 101 <Link href={"/discover"} className="font-bold"> 102 102 Explore Publications 103 103 </Link>{" "}
+91 -71
app/reader/ReaderContent.tsx
··· 1 1 "use client"; 2 + import { AtUri } from "@atproto/api"; 3 + import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 4 + import { PubIcon } from "components/ActionBar/Publications"; 2 5 import { ShareSmall } from "components/Icons/ShareSmall"; 3 6 import { Separator } from "components/Layout"; 4 7 import { useCardBorderHidden } from "components/Pages/useCardBorderHidden"; 8 + import { SpeedyLink } from "components/SpeedyLink"; 9 + import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; 10 + import { BaseThemeProvider } from "components/ThemeManager/ThemeProvider"; 11 + import { PubLeafletDocument, PubLeafletPublication } from "lexicons/api"; 5 12 import Link from "next/link"; 13 + import { blobRefToSrc } from "src/utils/blobRefToSrc"; 14 + import { Json } from "supabase/database.types"; 6 15 7 - export const ReaderContent = (props: { root_entity: string }) => { 8 - let cardBorderHidden = useCardBorderHidden(props.root_entity); 16 + export const ReaderContent = (props: { 17 + root_entity: string; 18 + posts: { 19 + publication: { 20 + href: string; 21 + pubRecord: Json; 22 + uri: string; 23 + }; 24 + documents: { data: Json; uri: string; indexed_at: string }; 25 + }[]; 26 + }) => { 9 27 return ( 10 28 <div className="flex flex-col gap-3"> 11 - {dummyPosts.map((p) => ( 12 - <Post {...p} cardBorderHidden={true} /> 13 - ))} 29 + {props.posts?.map((p) => <Post {...p} />)} 14 30 </div> 15 31 ); 16 32 }; 17 33 18 34 const Post = (props: { 19 - title: string; 20 - description: string; 21 - date: string; 22 - read: boolean; 23 - author: string; 24 - pub: string; 25 - cardBorderHidden: boolean; 35 + publication: { 36 + pubRecord: Json; 37 + uri: string; 38 + href: string; 39 + }; 40 + documents: { data: Json | undefined; uri: string; indexed_at: string }; 26 41 }) => { 42 + let pubRecord = props.publication.pubRecord as PubLeafletPublication.Record; 43 + 44 + let postRecord = props.documents.data as PubLeafletDocument.Record; 45 + let postUri = new AtUri(props.documents.uri); 46 + 47 + let theme = usePubTheme(pubRecord); 48 + let backgroundImage = pubRecord?.theme?.backgroundImage?.image?.ref 49 + ? blobRefToSrc( 50 + pubRecord?.theme?.backgroundImage?.image?.ref, 51 + new AtUri(props.publication.uri).host, 52 + ) 53 + : null; 54 + 55 + let backgroundImageRepeat = pubRecord?.theme?.backgroundImage?.repeat; 56 + let backgroundImageSize = pubRecord?.theme?.backgroundImage?.width || 500; 57 + 58 + let showPageBackground = pubRecord.theme?.showPageBackground; 59 + 27 60 return ( 28 - <div 29 - className={`flex flex-col gap-0 ${props.cardBorderHidden ? "bg-bg-page" : "bg-bg-leaflet"} p-3 rounded-lg border border-border-light`} 30 - > 31 - <div 32 - className={`${props.cardBorderHidden ? "bg-transparent" : "bg-bg-page px-3 py-2"} rounded-md`} 61 + <BaseThemeProvider {...theme} local> 62 + <SpeedyLink 63 + href={`${props.publication.href}/${postUri.rkey}`} 64 + style={{ 65 + backgroundImage: `url(${backgroundImage})`, 66 + backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", 67 + backgroundSize: `${backgroundImageRepeat ? `${backgroundImageSize}px` : "cover"}`, 68 + }} 69 + className={`no-underline! flex flex-row gap-2 w-full 70 + bg-bg-leaflet 71 + border border-border-light rounded-lg 72 + sm:p-2 p-2 selected-outline 73 + hover:outline-accent-contrast hover:border-accent-contrast 74 + `} 33 75 > 34 - <div className="flex justify-between gap-2"> 35 - <Link 36 - href={"/"} 37 - className="text-accent-contrast font-bold no-underline text-sm " 38 - > 39 - {props.pub} 40 - </Link> 41 - <button className="text-tertiary">{/*<ShareSmall />*/}</button> 42 - </div> 43 - <h3 className="truncate">{props.title}</h3> 76 + <div 77 + className={`${showPageBackground ? "bg-bg-page " : "bg-transparent"} rounded-md w-full px-[10px] pt-2 pb-2`} 78 + style={{ 79 + backgroundColor: showPageBackground 80 + ? "rgba(var(--bg-page), var(--bg-page-alpha))" 81 + : "transparent", 82 + }} 83 + > 84 + <div className="flex justify-between gap-2"> 85 + <button className="text-tertiary">{/*<ShareSmall />*/}</button> 86 + </div> 87 + <h3 className="text-primary truncate">{postRecord.title}</h3> 44 88 45 - <p className="text-secondary">{props.description}</p> 46 - <div className="flex gap-2 text-sm text-tertiary items-center pt-3"> 47 - <div className="flex gap-[6px] items-center"> 48 - <div className="bg-test rounded-full h-4 w-4" /> 49 - {props.author} 89 + <p className="text-secondary">{postRecord.description}</p> 90 + <div className="flex gap-2 text-sm text-tertiary items-center pt-3"> 91 + <Link 92 + href={props.publication.href} 93 + className="text-accent-contrast font-bold no-underline text-sm flex gap-[6px] items-center" 94 + > 95 + <PubIcon small record={pubRecord} uri={props.publication.uri} /> 96 + {pubRecord.name} 97 + </Link> 98 + <Separator classname="h-4 !min-h-0" /> 99 + NAME HERE 100 + <Separator classname="h-4 !min-h-0" /> 101 + {postRecord.publishedAt && 102 + new Date(postRecord.publishedAt).toLocaleDateString("en-US", { 103 + year: "numeric", 104 + month: "short", 105 + day: "numeric", 106 + })} 50 107 </div> 51 - <Separator classname="h-4 !min-h-0" /> 52 - {props.date} 53 108 </div> 54 - </div> 55 - </div> 109 + </SpeedyLink> 110 + </BaseThemeProvider> 56 111 ); 57 112 }; 58 - 59 - let dummyPosts: { 60 - title: string; 61 - description: string; 62 - date: string; 63 - read: boolean; 64 - author: string; 65 - pub: string; 66 - }[] = [ 67 - { 68 - title: "First Post", 69 - description: "this is a description", 70 - date: "Oct 2", 71 - read: false, 72 - author: "jared", 73 - pub: "a warm space", 74 - }, 75 - { 76 - title: "This is a second Tost", 77 - description: "It has another description, as you can see", 78 - date: "Oct 2", 79 - read: false, 80 - author: "celine", 81 - pub: "Celine's Super Soliloquy", 82 - }, 83 - { 84 - title: "A Third Post, A Burnt Toast", 85 - description: 86 - "If the first post is bread, the second is toast, and inevitably the third is a plate of charcoal.", 87 - date: "Oct 2", 88 - read: false, 89 - author: "brendan", 90 - pub: "Scraps", 91 - }, 92 - ];
+46 -12
app/reader/page.tsx
··· 18 18 import { DashboardLayout } from "components/PageLayouts/DashboardLayout"; 19 19 import { ReaderContent } from "./ReaderContent"; 20 20 import { SubscriptionsContent } from "./SubscriptionsContent"; 21 + import { Json } from "supabase/database.types"; 22 + import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 23 + import { PubLeafletDocument } from "lexicons/api"; 21 24 22 25 export default async function Reader(props: {}) { 23 26 let cookieStore = await cookies(); ··· 92 95 .select(`publications(*, documents_in_publications(documents(*)))`) 93 96 .eq("identity", auth_res?.atp_did); 94 97 95 - let subbedPubs = publications?.map((pub) => { 96 - let subbedPubsArr: string[] = []; 97 - pub.publications?.name && subbedPubsArr.push(pub.publications?.name); 98 - return subbedPubsArr; 99 - }); 98 + // Flatten all posts from all publications into a single array 99 + let posts = 100 + publications?.flatMap((publication) => { 101 + const postsInPub = 102 + publication.publications?.documents_in_publications.filter( 103 + (d) => !!d?.documents, 104 + ); 100 105 101 - console.log(subbedPubs); 106 + if (!postsInPub || postsInPub.length === 0) return []; 102 107 108 + return postsInPub 109 + .filter( 110 + (postInPub) => 111 + postInPub.documents?.data && 112 + postInPub.documents?.uri && 113 + postInPub.documents?.indexed_at, 114 + ) 115 + .map((postInPub) => ({ 116 + publication: { 117 + href: getPublicationURL(publication.publications!), 118 + pubRecord: publication.publications?.record || null, 119 + uri: publication.publications?.uri || "", 120 + }, 121 + documents: { 122 + data: postInPub.documents!.data, 123 + uri: postInPub.documents!.uri, 124 + indexed_at: postInPub.documents!.indexed_at, 125 + }, 126 + })); 127 + }) || []; 128 + 129 + let sortedPosts = posts.sort((a, b) => { 130 + let recordA = a.documents.data as PubLeafletDocument.Record; 131 + let recordB = b.documents.data as PubLeafletDocument.Record; 132 + const dateA = new Date(recordA.publishedAt || 0); 133 + const dateB = new Date(recordB.publishedAt || 0); 134 + return dateB.getTime() - dateA.getTime(); 135 + }); 103 136 return ( 104 137 <ReplicacheProvider 105 138 rootEntity={root_entity} ··· 121 154 actions={null} 122 155 tabs={{ 123 156 reader: { 124 - controls: <FocusToggle />, 125 - content: <ReaderContent root_entity={root_entity} />, 157 + controls: null, 158 + content: ( 159 + <ReaderContent 160 + root_entity={root_entity} 161 + posts={sortedPosts} 162 + /> 163 + ), 126 164 }, 127 165 subs: { 128 166 controls: null, ··· 141 179 </ReplicacheProvider> 142 180 ); 143 181 } 144 - 145 - const FocusToggle = () => { 146 - return <div className="grow flex justify-end">focus</div>; 147 - };
+9 -3
components/ActionBar/Publications.tsx
··· 93 93 export const PubIcon = (props: { 94 94 record: PubLeafletPublication.Record; 95 95 uri: string; 96 + small?: boolean; 97 + className?: string; 96 98 }) => { 97 99 if (!props.record) return; 98 100 ··· 104 106 backgroundSize: "cover", 105 107 backgroundImage: `url(/api/atproto_images?did=${new AtUri(props.uri).host}&cid=${(props.record.icon?.ref as unknown as { $link: string })["$link"]})`, 106 108 }} 107 - className="w-6 h-6 rounded-full" 109 + className={`${props.small ? "w-5 h-5" : "w-6 h-6"} rounded-full ${props.className}`} 108 110 /> 109 111 ) : ( 110 - <div className="w-6 h-6 rounded-full bg-accent-1 relative"> 111 - <div className="font-bold text-sm absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-accent-2"> 112 + <div 113 + className={`${props.small ? "w-5 h-5" : "w-6 h-6"} rounded-full bg-accent-1 relative`} 114 + > 115 + <div 116 + className={`${props.small ? "text-xs" : "text-sm"} font-bold absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-accent-2`} 117 + > 112 118 {props.record?.name.slice(0, 1)} 113 119 </div> 114 120 </div>
-1
components/PageHeader.tsx
··· 6 6 cardBorderHidden: boolean; 7 7 }) => { 8 8 let [scrollPos, setScrollPos] = useState(0); 9 - console.log(props.cardBorderHidden); 10 9 11 10 useEffect(() => { 12 11 const homeContent = document.getElementById("home-content");