a tool for shared writing and social publishing
0
fork

Configure Feed

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

set up leaflet and post to use the same layout component and adjusted the interaction panel to work within that

celine 8966f480 abc32646

+242 -292
+1 -1
app/[leaflet_id]/Footer.tsx
··· 20 20 let { data: pub } = useLeafletPublicationData(); 21 21 22 22 return ( 23 - <Media mobile className="mobileFooter w-full z-10 touch-none -mt-4 "> 23 + <Media mobile className="mobileFooter w-full z-10 touch-none -mt-[54px] "> 24 24 {focusedBlock && 25 25 focusedBlock.entityType == "block" && 26 26 entity_set.permissions.write ? (
+4 -23
app/[leaflet_id]/Leaflet.tsx
··· 13 13 import { UpdateLeafletTitle } from "components/utils/UpdateLeafletTitle"; 14 14 import { useUIState } from "src/useUIState"; 15 15 import { LeafletSidebar } from "./Sidebar"; 16 + import { LeafletLayout } from "components/LeafletLayout"; 16 17 17 18 export function Leaflet(props: { 18 19 token: PermissionToken; ··· 36 37 <SelectionManager /> 37 38 {/* we need the padding bottom here because if we don't have it the mobile footer will cut off... 38 39 the dropshadow on the page... the padding is compensated by a negative top margin in mobile footer */} 39 - <div 40 - className="leafletContentWrapper w-full relative overflow-x-scroll snap-x snap-mandatory no-scrollbar grow items-stretch flex h-full pb-4 pwa-padding" 41 - id="page-carousel" 42 - > 43 - {/* if you adjust this padding, remember to adjust the negative margins on page in Pages/index when card borders are hidden (also applies for the pb in the parent div)*/} 44 - <div 45 - id="pages" 46 - className="pages flex pt-2 pb-1 sm:pb-8 sm:pt-6" 47 - onClick={(e) => { 48 - e.currentTarget === e.target && blurPage(); 49 - }} 50 - > 51 - <LeafletSidebar leaflet_id={props.leaflet_id} /> 52 - <Pages rootPage={props.leaflet_id} /> 53 - </div> 54 - </div> 40 + <LeafletLayout className="!pb-[70px] sm:!pb-6"> 41 + <Pages rootPage={props.leaflet_id} /> 42 + </LeafletLayout> 55 43 <LeafletFooter entityID={props.leaflet_id} /> 56 44 </ThemeBackgroundProvider> 57 45 </ThemeProvider> ··· 59 47 </ReplicacheProvider> 60 48 ); 61 49 } 62 - 63 - const blurPage = () => { 64 - useUIState.setState(() => ({ 65 - focusedEntity: null, 66 - selectedBlocks: [], 67 - })); 68 - };
+31 -39
app/[leaflet_id]/Sidebar.tsx
··· 19 19 let { identity } = useIdentityData(); 20 20 21 21 return ( 22 - <div 23 - className="spacer flex justify-end items-start" 24 - style={{ width: `calc(50vw - ((var(--page-width-units)/2))` }} 25 - onClick={(e) => { 26 - e.currentTarget === e.target && blurPage(); 27 - }} 22 + <Media 23 + mobile={false} 24 + className="sidebarContainer relative flex flex-col justify-end h-full w-16" 28 25 > 29 - <Media 30 - mobile={false} 31 - className="sidebarContainer relative flex flex-col justify-end h-full w-16" 32 - > 33 - {entity_set.permissions.write && ( 34 - <Sidebar> 35 - {pub?.publications && 36 - identity?.atp_did && 37 - pub.publications.identity_did === identity.atp_did ? ( 38 - <> 39 - <PublishButton /> 40 - <ShareOptions /> 41 - <ThemePopover entityID={props.leaflet_id} /> 42 - <HelpPopover /> 43 - <hr className="text-border" /> 44 - <BackToPubButton publication={pub.publications} /> 45 - </> 46 - ) : ( 47 - <> 48 - <ShareOptions /> 49 - <ThemePopover entityID={props.leaflet_id} /> 50 - <HelpPopover /> 51 - <hr className="text-border" /> 52 - <HomeButton /> 53 - </> 54 - )} 55 - </Sidebar> 56 - )} 57 - <div className="h-full flex items-end"> 58 - <Watermark /> 59 - </div> 60 - </Media> 61 - </div> 26 + {entity_set.permissions.write && ( 27 + <Sidebar> 28 + {pub?.publications && 29 + identity?.atp_did && 30 + pub.publications.identity_did === identity.atp_did ? ( 31 + <> 32 + <PublishButton /> 33 + <ShareOptions /> 34 + <ThemePopover entityID={props.leaflet_id} /> 35 + <HelpPopover /> 36 + <hr className="text-border" /> 37 + <BackToPubButton publication={pub.publications} /> 38 + </> 39 + ) : ( 40 + <> 41 + <ShareOptions /> 42 + <ThemePopover entityID={props.leaflet_id} /> 43 + <HelpPopover /> 44 + <hr className="text-border" /> 45 + <HomeButton /> 46 + </> 47 + )} 48 + </Sidebar> 49 + )} 50 + <div className="h-full flex items-end"> 51 + <Watermark /> 52 + </div> 53 + </Media> 62 54 ); 63 55 } 64 56
+15 -15
app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx
··· 5 5 import { Json } from "supabase/database.types"; 6 6 import { Comment, Comments } from "./Comments"; 7 7 import { useSearchParams } from "next/navigation"; 8 + import { SandwichSpacer } from "components/LeafletLayout"; 8 9 9 10 export const InteractionDrawer = (props: { 10 11 document_uri: string; ··· 20 21 let currentDrawer = drawer || interactionDrawerSearchParam; 21 22 return ( 22 23 <> 23 - <div className="sm:pr-4 pr-[6px] snap-center"> 24 - <div className="shrink-0 w-[calc(var(--page-width-units)-12px)] sm:w-[var(--page-width-units)] h-full flex z-10"> 25 - <div 26 - id="interaction-drawer" 27 - className="opaque-container !rounded-lg h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll " 28 - > 29 - {currentDrawer === "quotes" ? ( 30 - <Quotes {...props} /> 31 - ) : ( 32 - <Comments 33 - document_uri={props.document_uri} 34 - comments={props.comments} 35 - /> 36 - )} 37 - </div> 24 + <SandwichSpacer className="!w-1 sm:!w-6" /> 25 + <div className="snap-center h-full flex z-10 shrink-0 w-[calc(var(--page-width-units)-12px)] sm:w-[var(--page-width-units)]"> 26 + <div 27 + id="interaction-drawer" 28 + className="opaque-container !rounded-lg h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll " 29 + > 30 + {currentDrawer === "quotes" ? ( 31 + <Quotes {...props} /> 32 + ) : ( 33 + <Comments 34 + document_uri={props.document_uri} 35 + comments={props.comments} 36 + /> 37 + )} 38 38 </div> 39 39 </div> 40 40 </>
-23
app/lish/[did]/[publication]/[rkey]/PageLayout.tsx
··· 1 - "use client"; 2 - 3 - import { useInteractionState } from "./Interactions/Interactions"; 4 - 5 - export function PageLayout(props: { children: React.ReactNode }) { 6 - let { drawerOpen } = useInteractionState(); 7 - return ( 8 - <div 9 - onScroll={(e) => {}} 10 - className="post w-full relative overflow-x-scroll snap-x snap-mandatory no-scrollbar grow items-stretch flex h-full pwa-padding mx-auto " 11 - id="page-carousel" 12 - > 13 - {/* if you adjust this padding, remember to adjust the negative margins on page 14 - in [rkey]/page/PostPage when card borders are hidden */} 15 - <div 16 - id="pages" 17 - className="postWrapper flex h-full gap-0 sm:gap-3 py-2 sm:py-6 w-full" 18 - > 19 - {props.children} 20 - </div> 21 - </div> 22 - ); 23 - }
+105 -84
app/lish/[did]/[publication]/[rkey]/PostPage.tsx
··· 15 15 import { useIdentityData } from "components/IdentityProvider"; 16 16 import { AppBskyFeedDefs } from "@atproto/api"; 17 17 import { create } from "zustand/react"; 18 + import { InteractionDrawer } from "./Interactions/InteractionDrawer"; 19 + import { BookendSpacers, SandwichSpacer } from "components/LeafletLayout"; 18 20 export const usePostPageUIState = create(() => ({ 19 21 pages: [] as string[], 20 22 })); ··· 38 40 }; 39 41 }); 40 42 41 - export function PostPage({ 43 + export function PostPages({ 42 44 document, 43 45 blocks, 44 46 name, ··· 68 70 let hasPageBackground = !!pubRecord.theme?.showPageBackground; 69 71 return ( 70 72 <> 71 - {(drawerOpen || hasPageBackground) && ( 72 - <div 73 - className="spacer sm:block hidden" 74 - style={{ 75 - width: `calc(50vw - 12px - ((var(--page-width-units)/2))`, 76 - }} 73 + {(drawerOpen || hasPageBackground) && <BookendSpacers />} 74 + <PageWrapper 75 + hasPageBackground={hasPageBackground} 76 + drawerOpen={drawerOpen} 77 + > 78 + <PostHeader 79 + data={document} 80 + profile={profile} 81 + name={name} 82 + preferences={preferences} 83 + /> 84 + <PostContent 85 + bskyPostData={bskyPostData} 86 + blocks={blocks} 87 + did={did} 88 + prerenderedCodeBlocks={prerenderedCodeBlocks} 89 + /> 90 + <Interactions 91 + showComments={preferences.showComments} 92 + quotesCount={document.document_mentions_in_bsky.length} 93 + commentsCount={document.comments_on_documents.length} 94 + /> 95 + <hr className="border-border-light mb-4 mt-4" /> 96 + {identity && 97 + identity.atp_did === 98 + document.documents_in_publications[0]?.publications?.identity_did ? ( 99 + <a 100 + href={`https://leaflet.pub/${document.leaflets_in_publications[0]?.leaflet}`} 101 + className="flex gap-2 items-center hover:!no-underline selected-outline px-2 py-0.5 bg-accent-1 text-accent-2 font-bold w-fit rounded-lg !border-accent-1 !outline-accent-1 mx-auto" 102 + > 103 + <EditTiny /> Edit Post 104 + </a> 105 + ) : ( 106 + <SubscribeWithBluesky 107 + isPost 108 + base_url={getPublicationURL( 109 + document.documents_in_publications[0].publications, 110 + )} 111 + pub_uri={document.documents_in_publications[0].publications.uri} 112 + subscribers={ 113 + document.documents_in_publications[0].publications 114 + .publication_subscriptions 115 + } 116 + pubName={name} 117 + /> 118 + )} 119 + </PageWrapper> 120 + 121 + {drawerOpen && ( 122 + <InteractionDrawer 123 + document_uri={document.uri} 124 + comments={ 125 + pubRecord.preferences?.showComments === false 126 + ? [] 127 + : document.comments_on_documents 128 + } 129 + quotes={document.document_mentions_in_bsky} 130 + did={did} 77 131 /> 78 132 )} 79 - <div 80 - id="post-page" 81 - className={`postPageWrapper relative overflow-y-auto sm:mx-0 mx-[6px] w-full 82 - ${drawerOpen || hasPageBackground ? "max-w-[var(--page-width-units)] shrink-0 snap-center " : "w-full"} 83 - ${ 84 - hasPageBackground 85 - ? "h-full bg-[rgba(var(--bg-page),var(--bg-page-alpha))] rounded-lg border border-border " 86 - : "sm:h-[calc(100%+48px)] h-[calc(100%+24px)] sm:-my-6 -my-3 " 87 - }`} 88 - > 89 - <div 90 - className={`postPageContent sm:max-w-prose mx-auto h-fit w-full px-3 sm:px-4 ${hasPageBackground ? " pt-2 pb-3 sm:pb-6" : "py-6 sm:py-9"}`} 91 - > 92 - <PostHeader 93 - data={document} 94 - profile={profile} 95 - name={name} 96 - preferences={preferences} 97 - /> 98 - <PostContent 99 - bskyPostData={bskyPostData} 100 - blocks={blocks} 101 - did={did} 102 - prerenderedCodeBlocks={prerenderedCodeBlocks} 103 - /> 104 - <Interactions 105 - showComments={preferences.showComments} 106 - quotesCount={document.document_mentions_in_bsky.length} 107 - commentsCount={document.comments_on_documents.length} 108 - /> 109 - <hr className="border-border-light mb-4 mt-4" /> 110 - {identity && 111 - identity.atp_did === 112 - document.documents_in_publications[0]?.publications 113 - ?.identity_did ? ( 114 - <a 115 - href={`https://leaflet.pub/${document.leaflets_in_publications[0]?.leaflet}`} 116 - className="flex gap-2 items-center hover:!no-underline selected-outline px-2 py-0.5 bg-accent-1 text-accent-2 font-bold w-fit rounded-lg !border-accent-1 !outline-accent-1 mx-auto" 117 - > 118 - <EditTiny /> Edit Post 119 - </a> 120 - ) : ( 121 - <SubscribeWithBluesky 122 - isPost 123 - base_url={getPublicationURL( 124 - document.documents_in_publications[0].publications, 125 - )} 126 - pub_uri={document.documents_in_publications[0].publications.uri} 127 - subscribers={ 128 - document.documents_in_publications[0].publications 129 - .publication_subscriptions 130 - } 131 - pubName={name} 132 - /> 133 - )} 134 - </div> 135 - </div> 133 + 136 134 {pages.map((p) => { 137 135 let record = document.data as PubLeafletDocument.Record; 138 136 let page = record.pages.find( ··· 140 138 ) as PubLeafletPagesLinearDocument.Main | undefined; 141 139 if (!page) return null; 142 140 return ( 143 - <div 144 - key={page.id} 145 - id="post-page" 146 - className={`postPageWrapper relative overflow-y-auto sm:mx-0 mx-[6px] w-full 147 - ${drawerOpen || hasPageBackground ? "max-w-[var(--page-width-units)] shrink-0 snap-center " : "w-full"} 148 - ${ 149 - hasPageBackground 150 - ? "h-full bg-[rgba(var(--bg-page),var(--bg-page-alpha))] rounded-lg border border-border " 151 - : "sm:h-[calc(100%+48px)] h-[calc(100%+24px)] sm:-my-6 -my-3 " 152 - }`} 153 - > 154 - <button onClick={() => closePage(page?.id!)}>close</button> 155 - <PostContent 156 - pageId={page.id} 157 - bskyPostData={bskyPostData} 158 - blocks={page.blocks} 159 - did={did} 160 - prerenderedCodeBlocks={prerenderedCodeBlocks} 161 - /> 162 - </div> 141 + <> 142 + <SandwichSpacer /> 143 + <PageWrapper 144 + hasPageBackground={hasPageBackground} 145 + drawerOpen={drawerOpen} 146 + > 147 + <button onClick={() => closePage(page?.id!)}>close</button> 148 + 149 + <PostContent 150 + pageId={page.id} 151 + bskyPostData={bskyPostData} 152 + blocks={page.blocks} 153 + did={did} 154 + prerenderedCodeBlocks={prerenderedCodeBlocks} 155 + /> 156 + </PageWrapper> 157 + </> 163 158 ); 164 159 })} 160 + <BookendSpacers /> 165 161 </> 166 162 ); 167 163 } 164 + 165 + const PageWrapper = (props: { 166 + children: React.ReactNode; 167 + hasPageBackground: boolean; 168 + drawerOpen: boolean | undefined; 169 + }) => { 170 + return ( 171 + <div 172 + id="post-page" 173 + className={`postPageWrapper relative overflow-y-auto sm:mx-0 w-full 174 + ${props.drawerOpen || props.hasPageBackground ? "max-w-[var(--page-width-units)] shrink-0 snap-center " : "w-full"} 175 + ${ 176 + props.hasPageBackground 177 + ? "h-full bg-[rgba(var(--bg-page),var(--bg-page-alpha))] rounded-lg border border-border " 178 + : "sm:h-[calc(100%+48px)] h-[calc(100%+24px)] sm:-my-6 -my-3 " 179 + }`} 180 + > 181 + <div 182 + className={`postPageContent sm:max-w-prose mx-auto h-fit w-full px-3 sm:px-4 ${props.hasPageBackground ? " pt-2 pb-3 sm:pb-6" : "py-6 sm:py-9"}`} 183 + > 184 + {props.children} 185 + </div> 186 + </div> 187 + ); 188 + };
+5 -16
app/lish/[did]/[publication]/[rkey]/page.tsx
··· 17 17 } from "components/ThemeManager/PublicationThemeProvider"; 18 18 import { getPostPageData } from "./getPostPageData"; 19 19 import { PostPageContextProvider } from "./PostPageContext"; 20 - import { PostPage } from "./PostPage"; 21 - import { PageLayout } from "./PageLayout"; 20 + import { PostPages } from "./PostPage"; 22 21 import { extractCodeBlocks } from "./extractCodeBlocks"; 22 + import { LeafletLayout } from "components/LeafletLayout"; 23 23 24 24 export async function generateMetadata(props: { 25 25 params: Promise<{ publication: string; did: string; rkey: string }>; ··· 122 122 let pubRecord = document.documents_in_publications[0]?.publications 123 123 .record as PubLeafletPublication.Record; 124 124 125 - let hasPageBackground = !!pubRecord.theme?.showPageBackground; 126 125 let prerenderedCodeBlocks = await extractCodeBlocks(blocks); 127 126 128 127 return ( ··· 153 152 on chrome, if you scroll backward, things stop working 154 153 seems like if you use an older browser, sel direction is not a thing yet 155 154 */} 156 - <PageLayout> 157 - <PostPage 155 + <LeafletLayout> 156 + <PostPages 158 157 preferences={pubRecord.preferences || {}} 159 158 pubRecord={pubRecord} 160 159 profile={JSON.parse(JSON.stringify(profile.data))} ··· 165 164 name={decodeURIComponent((await props.params).publication)} 166 165 prerenderedCodeBlocks={prerenderedCodeBlocks} 167 166 /> 168 - <InteractionDrawer 169 - document_uri={document.uri} 170 - comments={ 171 - pubRecord.preferences?.showComments === false 172 - ? [] 173 - : document.comments_on_documents 174 - } 175 - quotes={document.document_mentions_in_bsky} 176 - did={did} 177 - /> 178 - </PageLayout> 167 + </LeafletLayout> 179 168 180 169 <QuoteHandler /> 181 170 </PublicationBackgroundProvider>
-61
components/Blocks/MailboxBlock.tsx
··· 372 372 ); 373 373 }; 374 374 375 - export const DraftPostOptions = (props: { mailboxEntity: string }) => { 376 - let toaster = useToaster(); 377 - let draft = useEntity(props.mailboxEntity, "mailbox/draft"); 378 - let { rep, permission_token } = useReplicache(); 379 - let entity_set = useEntitySetContext(); 380 - let pagetitle = usePageTitle(permission_token.root_entity); 381 - let subscriber_count = useEntity( 382 - props.mailboxEntity, 383 - "mailbox/subscriber-count", 384 - ); 385 - if (!draft) return null; 386 - 387 - // once the send button is clicked, close the page and show a toast. 388 - return ( 389 - <div className="flex justify-between items-center text-sm"> 390 - <div className="flex gap-2"> 391 - <em>Draft</em> 392 - </div> 393 - <button 394 - className="font-bold text-accent-2 bg-accent-1 border hover:bg-accent-2 hover:text-accent-1 rounded-md px-2" 395 - onClick={async () => { 396 - if (!rep) return; 397 - let blocks = 398 - (await rep?.query((tx) => 399 - getBlocksWithType(tx, draft.data.value), 400 - )) || []; 401 - let html = (await getBlocksAsHTML(rep, blocks))?.join("\n"); 402 - await sendPostToSubscribers({ 403 - title: pagetitle, 404 - permission_token, 405 - mailboxEntity: props.mailboxEntity, 406 - messageEntity: draft.data.value, 407 - contents: { 408 - html, 409 - markdown: htmlToMarkdown(html), 410 - }, 411 - }); 412 - 413 - rep?.mutate.archiveDraft({ 414 - entity_set: entity_set.set, 415 - mailboxEntity: props.mailboxEntity, 416 - newBlockEntity: v7(), 417 - archiveEntity: v7(), 418 - }); 419 - 420 - toaster({ 421 - content: <div className="font-bold">Sent Post to Readers!</div>, 422 - type: "success", 423 - }); 424 - }} 425 - > 426 - Send 427 - {!subscriber_count || 428 - (subscriber_count.data.value !== 0 && 429 - ` to ${subscriber_count.data.value} Reader${subscriber_count.data.value === 1 ? "" : "s"}`)} 430 - ! 431 - </button> 432 - </div> 433 - ); 434 - }; 435 - 436 375 const GoToArchive = (props: { 437 376 entityID: string; 438 377 parent: string;
+58
components/LeafletLayout.tsx
··· 1 + export const LeafletLayout = (props: { 2 + children: React.ReactNode; 3 + className?: string; 4 + }) => { 5 + return ( 6 + <div 7 + className={` 8 + leafetLayout 9 + w-full h-full relative 10 + mx-auto pwa-padding 11 + flex items-stretch grow`} 12 + id="page-carousel" 13 + > 14 + {/* if you adjust this padding, remember to adjust the negative margins on page 15 + in [rkey]/page/PostPage when card borders are hidden */} 16 + <div 17 + id="pages" 18 + className={`pagesWrapper 19 + w-full h-full 20 + flex gap-0 21 + py-2 sm:py-6 22 + overflow-x-scroll snap-x snap-mandatory no-scrollbar 23 + ${props.className}`} 24 + > 25 + {props.children} 26 + </div> 27 + </div> 28 + ); 29 + }; 30 + 31 + export const BookendSpacers = (props: { 32 + onClick?: (e: React.MouseEvent) => void; 33 + children?: React.ReactNode; 34 + }) => { 35 + // these spacers go at the end of the first and last pages so that those pages can be scrolled to the center of the screen 36 + return ( 37 + <div 38 + className="spacer shrink-0 flex justify-end items-start" 39 + style={{ width: `calc(50vw - ((var(--page-width-units)/2))` }} 40 + onClick={props.onClick ? props.onClick : () => {}} 41 + > 42 + {props.children} 43 + </div> 44 + ); 45 + }; 46 + 47 + export const SandwichSpacer = (props: { 48 + onClick?: (e: React.MouseEvent) => void; 49 + className?: string; 50 + }) => { 51 + // these spacers are used between pages so that the page carousel can fit two pages side by side by snapping in between pages 52 + return ( 53 + <div 54 + onClick={props.onClick} 55 + className={`spacer shrink-0 w-6 lg:snap-center ${props.className}`} 56 + /> 57 + ); 58 + };
+23 -30
components/Pages/index.tsx
··· 21 21 import { DesktopPageFooter } from "../DesktopFooter"; 22 22 import { ThemePopover } from "../ThemeManager/ThemeSetter"; 23 23 import { Canvas } from "../Canvas"; 24 - import { DraftPostOptions } from "../Blocks/MailboxBlock"; 25 24 import { Blocks } from "components/Blocks"; 26 25 import { MenuItem, Menu } from "../Layout"; 27 26 import { scanIndex } from "src/replicache/utils"; ··· 37 36 import { PublicationMetadata } from "./PublicationMetadata"; 38 37 import { useCardBorderHidden } from "./useCardBorderHidden"; 39 38 import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 39 + import { BookendSpacers, SandwichSpacer } from "components/LeafletLayout"; 40 + import { LeafletSidebar } from "app/[leaflet_id]/Sidebar"; 40 41 41 42 export function Pages(props: { rootPage: string }) { 42 43 let rootPage = useEntity(props.rootPage, "root/page")[0]; ··· 47 48 48 49 return ( 49 50 <> 51 + <BookendSpacers 52 + onClick={(e) => { 53 + e.currentTarget === e.target && blurPage(); 54 + }} 55 + > 56 + <LeafletSidebar leaflet_id={props.rootPage} /> 57 + </BookendSpacers> 50 58 <div className="flex items-stretch"> 51 59 <CardThemeProvider entityID={firstPage}> 52 60 <Page entityID={firstPage} first /> ··· 59 67 </CardThemeProvider> 60 68 </div> 61 69 ))} 62 - <div 63 - className="spacer" 64 - style={{ width: `calc(50vw - ((var(--page-width-units)/2))` }} 70 + <BookendSpacers 65 71 onClick={(e) => { 66 72 e.currentTarget === e.target && blurPage(); 67 73 }} ··· 79 85 }; 80 86 81 87 function Page(props: { entityID: string; first?: boolean }) { 82 - let { rep, rootEntity } = useReplicache(); 88 + let { rep } = useReplicache(); 83 89 let isDraft = useReferenceToEntity("mailbox/draft", props.entityID); 84 90 85 91 let isFocused = useUIState((s) => { ··· 95 101 return ( 96 102 <> 97 103 {!props.first && ( 98 - <div 99 - className="w-6 lg:snap-center" 104 + <SandwichSpacer 100 105 onClick={(e) => { 101 106 e.currentTarget === e.target && blurPage(); 102 107 }} ··· 128 133 ${isFocused ? "shadow-md border-border" : "border-border-light"} 129 134 `} 130 135 > 131 - <Media mobile={true}> 132 - <PageOptions entityID={props.entityID} first={props.first} /> 133 - </Media> 134 - <DesktopPageFooter pageID={props.entityID} /> 135 - {isDraft.length > 0 && ( 136 - <div 137 - className={`pageStatus pt-[6px] pb-1 ${!props.first ? "pr-10 pl-3 sm:px-4" : "px-3 sm:px-4"} border-b border-border text-tertiary`} 138 - style={{ 139 - backgroundColor: 140 - "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)", 141 - }} 142 - > 143 - <DraftPostOptions mailboxEntity={isDraft[0].entity} /> 144 - </div> 145 - )} 146 - 136 + <PageOptions 137 + entityID={props.entityID} 138 + first={props.first} 139 + isFocused={isFocused} 140 + /> 147 141 {props.first && ( 148 142 <PublicationMetadata cardBorderHidden={!!cardBorderHidden} /> 149 143 )} 150 144 <PageContent entityID={props.entityID} /> 151 145 </div> 152 - <Media mobile={false}> 153 - {isFocused && ( 154 - <PageOptions entityID={props.entityID} first={props.first} /> 155 - )} 156 - </Media> 146 + <DesktopPageFooter pageID={props.entityID} /> 157 147 </div> 158 148 </> 159 149 ); ··· 282 272 const PageOptions = (props: { 283 273 entityID: string; 284 274 first: boolean | undefined; 275 + isFocused: boolean; 285 276 }) => { 286 - let { rootEntity } = useReplicache(); 287 277 let cardBorderHidden = useCardBorderHidden(props.entityID); 288 278 289 279 return ( 290 280 <div 291 - className={`z-10 w-fit absolute ${cardBorderHidden ? "top-1" : "sm:top-3"} sm:-right-[19px] top-0 right-3 flex sm:flex-col flex-row-reverse gap-1 items-start`} 281 + className={`pageOptions w-fit z-10 282 + ${props.isFocused ? "block" : "sm:hidden block"} 283 + absolute sm:-right-[19px] right-3 ${cardBorderHidden ? "top-1" : "sm:top-3 top-0"} 284 + flex sm:flex-col flex-row-reverse gap-1 items-start`} 292 285 > 293 286 {!props.first && ( 294 287 <PageOptionButton