a tool for shared writing and social publishing

simplify page handling and support root canvas page

+139 -140
+18 -27
app/lish/[did]/[publication]/[rkey]/CanvasPage.tsx
··· 21 21 import { PostHeader } from "./PostHeader/PostHeader"; 22 22 import { useDrawerOpen } from "./Interactions/InteractionDrawer"; 23 23 import { PollData } from "./fetchPollData"; 24 + import { SharedPageProps } from "./PostPages"; 24 25 25 26 export function CanvasPage({ 26 - document, 27 27 blocks, 28 - did, 29 - profile, 30 - preferences, 31 - pubRecord, 32 - theme, 33 - prerenderedCodeBlocks, 34 - bskyPostData, 35 - pollData, 36 - document_uri, 37 - pageId, 38 - pageOptions, 39 - fullPageScroll, 40 28 pages, 41 - }: { 42 - document_uri: string; 43 - document: PostPageData; 29 + ...props 30 + }: Omit<SharedPageProps, "allPages"> & { 44 31 blocks: PubLeafletPagesCanvas.Block[]; 45 - profile: ProfileViewDetailed; 46 - pubRecord?: PubLeafletPublication.Record; 47 - theme?: PubLeafletPublication.Theme | null; 48 - did: string; 49 - prerenderedCodeBlocks?: Map<string, string>; 50 - bskyPostData: AppBskyFeedDefs.PostView[]; 51 - pollData: PollData[]; 52 - preferences: { showComments?: boolean }; 53 - pageId?: string; 54 - pageOptions?: React.ReactNode; 55 - fullPageScroll: boolean; 56 32 pages: (PubLeafletPagesLinearDocument.Main | PubLeafletPagesCanvas.Main)[]; 57 33 }) { 34 + const { 35 + document, 36 + did, 37 + profile, 38 + preferences, 39 + pubRecord, 40 + theme, 41 + prerenderedCodeBlocks, 42 + bskyPostData, 43 + pollData, 44 + document_uri, 45 + pageId, 46 + pageOptions, 47 + fullPageScroll, 48 + } = props; 58 49 if (!document) return null; 59 50 60 51 let hasPageBackground = !!theme?.showPageBackground;
+8 -6
app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer.tsx
··· 5 5 PubLeafletBlocksBskyPost, 6 6 PubLeafletDocument, 7 7 PubLeafletPagesLinearDocument, 8 + PubLeafletPagesCanvas, 8 9 PubLeafletPublication, 9 10 } from "lexicons/api"; 10 11 import { QuoteHandler } from "./QuoteHandler"; ··· 109 110 document.documents_in_publications[0]?.publications?.identity_did || did; 110 111 111 112 let firstPage = record.pages[0]; 112 - let blocks: PubLeafletPagesLinearDocument.Block[] = []; 113 - if (PubLeafletPagesLinearDocument.isMain(firstPage)) { 114 - blocks = firstPage.blocks || []; 115 - } 116 113 117 - let prerenderedCodeBlocks = await extractCodeBlocks(blocks); 114 + let firstPageBlocks = 115 + ( 116 + firstPage as 117 + | PubLeafletPagesLinearDocument.Main 118 + | PubLeafletPagesCanvas.Main 119 + ).blocks || []; 120 + let prerenderedCodeBlocks = await extractCodeBlocks(firstPageBlocks); 118 121 119 122 return ( 120 123 <PostPageContextProvider value={document}> ··· 129 132 document={document} 130 133 bskyPostData={bskyPostData} 131 134 did={did} 132 - blocks={blocks} 133 135 prerenderedCodeBlocks={prerenderedCodeBlocks} 134 136 pollData={pollData} 135 137 />
+18 -27
app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx
··· 23 23 import { PageWrapper } from "components/Pages/Page"; 24 24 import { decodeQuotePosition } from "./quotePosition"; 25 25 import { PollData } from "./fetchPollData"; 26 + import { SharedPageProps } from "./PostPages"; 26 27 27 28 export function LinearDocumentPage({ 28 - document, 29 29 blocks, 30 - did, 31 - profile, 32 - preferences, 33 - pubRecord, 34 - theme, 35 - prerenderedCodeBlocks, 36 - bskyPostData, 37 - document_uri, 38 - pageId, 39 - pageOptions, 40 - pollData, 41 - fullPageScroll, 42 - }: { 43 - document_uri: string; 44 - document: PostPageData; 30 + ...props 31 + }: Omit<SharedPageProps, "allPages"> & { 45 32 blocks: PubLeafletPagesLinearDocument.Block[]; 46 - profile?: ProfileViewDetailed; 47 - pubRecord?: PubLeafletPublication.Record; 48 - theme?: PubLeafletPublication.Theme | null; 49 - did: string; 50 - prerenderedCodeBlocks?: Map<string, string>; 51 - bskyPostData: AppBskyFeedDefs.PostView[]; 52 - pollData: PollData[]; 53 - preferences: { showComments?: boolean }; 54 - pageId?: string; 55 - pageOptions?: React.ReactNode; 56 - fullPageScroll: boolean; 57 33 }) { 34 + const { 35 + document, 36 + did, 37 + profile, 38 + preferences, 39 + pubRecord, 40 + theme, 41 + prerenderedCodeBlocks, 42 + bskyPostData, 43 + pollData, 44 + document_uri, 45 + pageId, 46 + pageOptions, 47 + fullPageScroll, 48 + } = props; 58 49 let { identity } = useIdentityData(); 59 50 let drawer = useDrawerOpen(document_uri); 60 51
+92 -78
app/lish/[did]/[publication]/[rkey]/PostPages.tsx
··· 98 98 }; 99 99 }); 100 100 101 + // Shared props type for both page components 102 + export type SharedPageProps = { 103 + document: PostPageData; 104 + did: string; 105 + profile: ProfileViewDetailed; 106 + preferences: { showComments?: boolean }; 107 + pubRecord?: PubLeafletPublication.Record; 108 + theme?: PubLeafletPublication.Theme | null; 109 + prerenderedCodeBlocks?: Map<string, string>; 110 + bskyPostData: AppBskyFeedDefs.PostView[]; 111 + pollData: PollData[]; 112 + document_uri: string; 113 + fullPageScroll: boolean; 114 + pageId?: string; 115 + pageOptions?: React.ReactNode; 116 + allPages: (PubLeafletPagesLinearDocument.Main | PubLeafletPagesCanvas.Main)[]; 117 + }; 118 + 119 + // Component that renders either Canvas or Linear page based on page type 120 + function PageRenderer({ 121 + page, 122 + ...sharedProps 123 + }: { 124 + page: PubLeafletPagesLinearDocument.Main | PubLeafletPagesCanvas.Main; 125 + } & SharedPageProps) { 126 + const isCanvas = PubLeafletPagesCanvas.isMain(page); 127 + 128 + if (isCanvas) { 129 + return ( 130 + <CanvasPage 131 + {...sharedProps} 132 + blocks={(page as PubLeafletPagesCanvas.Main).blocks || []} 133 + pages={sharedProps.allPages} 134 + /> 135 + ); 136 + } 137 + 138 + return ( 139 + <LinearDocumentPage 140 + {...sharedProps} 141 + blocks={(page as PubLeafletPagesLinearDocument.Main).blocks || []} 142 + /> 143 + ); 144 + } 145 + 101 146 export function PostPages({ 102 147 document, 103 - blocks, 104 148 did, 105 149 profile, 106 150 preferences, ··· 112 156 }: { 113 157 document_uri: string; 114 158 document: PostPageData; 115 - blocks: PubLeafletPagesLinearDocument.Block[]; 116 159 profile: ProfileViewDetailed; 117 160 pubRecord?: PubLeafletPublication.Record; 118 161 did: string; ··· 123 166 }) { 124 167 let drawer = useDrawerOpen(document_uri); 125 168 useInitializeOpenPages(); 126 - let pages = useOpenPages(); 169 + let openPageIds = useOpenPages(); 127 170 if (!document) return null; 128 171 129 172 let record = document.data as PubLeafletDocument.Record; 130 - 131 - // Get theme from publication or document (for standalone docs) 132 173 let theme = pubRecord?.theme || record.theme || null; 133 174 let hasPageBackground = !!theme?.showPageBackground; 175 + let quotesAndMentions = document.quotesAndMentions; 134 176 135 - let quotesAndMentions = document.quotesAndMentions; 177 + let firstPage = record.pages[0] as 178 + | PubLeafletPagesLinearDocument.Main 179 + | PubLeafletPagesCanvas.Main; 136 180 137 - let fullPageScroll = !hasPageBackground && !drawer && pages.length === 0; 181 + // Shared props used for all pages 182 + const sharedProps: SharedPageProps = { 183 + document, 184 + did, 185 + profile, 186 + preferences, 187 + pubRecord, 188 + theme, 189 + prerenderedCodeBlocks, 190 + bskyPostData, 191 + pollData, 192 + document_uri, 193 + allPages: record.pages as ( 194 + | PubLeafletPagesLinearDocument.Main 195 + | PubLeafletPagesCanvas.Main 196 + )[], 197 + fullPageScroll: !hasPageBackground && !drawer && openPageIds.length === 0, 198 + }; 199 + 138 200 return ( 139 201 <> 140 - {!fullPageScroll && <BookendSpacer />} 141 - <LinearDocumentPage 142 - document={document} 143 - blocks={blocks} 144 - did={did} 145 - profile={profile} 146 - fullPageScroll={fullPageScroll} 147 - pollData={pollData} 148 - preferences={preferences} 149 - pubRecord={pubRecord} 150 - theme={theme} 151 - prerenderedCodeBlocks={prerenderedCodeBlocks} 152 - bskyPostData={bskyPostData} 153 - document_uri={document_uri} 154 - /> 202 + {!sharedProps.fullPageScroll && <BookendSpacer />} 203 + 204 + <PageRenderer page={firstPage} {...sharedProps} /> 155 205 156 206 {drawer && !drawer.pageId && ( 157 207 <InteractionDrawer ··· 166 216 /> 167 217 )} 168 218 169 - {pages.map((p) => { 219 + {openPageIds.map((pageId) => { 170 220 let page = record.pages.find( 171 - (page) => 172 - ( 173 - page as 174 - | PubLeafletPagesLinearDocument.Main 175 - | PubLeafletPagesCanvas.Main 176 - ).id === p, 221 + (p) => 222 + (p as PubLeafletPagesLinearDocument.Main | PubLeafletPagesCanvas.Main) 223 + .id === pageId, 177 224 ) as 178 225 | PubLeafletPagesLinearDocument.Main 179 226 | PubLeafletPagesCanvas.Main 180 227 | undefined; 228 + 181 229 if (!page) return null; 182 230 183 - const isCanvas = PubLeafletPagesCanvas.isMain(page); 184 - 185 231 return ( 186 - <Fragment key={p}> 232 + <Fragment key={pageId}> 187 233 <SandwichSpacer /> 188 - {isCanvas ? ( 189 - <CanvasPage 190 - fullPageScroll={false} 191 - document={document} 192 - blocks={(page as PubLeafletPagesCanvas.Main).blocks} 193 - did={did} 194 - preferences={preferences} 195 - profile={profile} 196 - pubRecord={pubRecord} 197 - theme={theme} 198 - prerenderedCodeBlocks={prerenderedCodeBlocks} 199 - pollData={pollData} 200 - bskyPostData={bskyPostData} 201 - document_uri={document_uri} 202 - pageId={page.id} 203 - pages={record.pages as PubLeafletPagesLinearDocument.Main[]} 204 - pageOptions={ 205 - <PageOptions 206 - onClick={() => closePage(page?.id!)} 207 - hasPageBackground={hasPageBackground} 208 - /> 209 - } 210 - /> 211 - ) : ( 212 - <LinearDocumentPage 213 - fullPageScroll={false} 214 - document={document} 215 - blocks={(page as PubLeafletPagesLinearDocument.Main).blocks} 216 - did={did} 217 - preferences={preferences} 218 - pubRecord={pubRecord} 219 - theme={theme} 220 - pollData={pollData} 221 - prerenderedCodeBlocks={prerenderedCodeBlocks} 222 - bskyPostData={bskyPostData} 223 - document_uri={document_uri} 224 - pageId={page.id} 225 - pageOptions={ 226 - <PageOptions 227 - onClick={() => closePage(page?.id!)} 228 - hasPageBackground={hasPageBackground} 229 - /> 230 - } 231 - /> 232 - )} 234 + <PageRenderer 235 + page={page} 236 + {...sharedProps} 237 + fullPageScroll={false} 238 + pageId={page.id} 239 + pageOptions={ 240 + <PageOptions 241 + onClick={() => closePage(page.id!)} 242 + hasPageBackground={hasPageBackground} 243 + /> 244 + } 245 + /> 233 246 {drawer && drawer.pageId === page.id && ( 234 247 <InteractionDrawer 235 248 pageId={page.id} ··· 246 259 </Fragment> 247 260 ); 248 261 })} 249 - {!fullPageScroll && <BookendSpacer />} 262 + 263 + {!sharedProps.fullPageScroll && <BookendSpacer />} 250 264 </> 251 265 ); 252 266 }
+3 -2
app/lish/[did]/[publication]/[rkey]/extractCodeBlocks.ts
··· 1 1 import { 2 2 PubLeafletDocument, 3 3 PubLeafletPagesLinearDocument, 4 + PubLeafletPagesCanvas, 4 5 PubLeafletBlocksCode, 5 6 } from "lexicons/api"; 6 7 import { codeToHtml, bundledLanguagesInfo, bundledThemesInfo } from "shiki"; 7 8 8 9 export async function extractCodeBlocks( 9 - blocks: PubLeafletPagesLinearDocument.Block[], 10 + blocks: PubLeafletPagesLinearDocument.Block[] | PubLeafletPagesCanvas.Block[], 10 11 ): Promise<Map<string, string>> { 11 12 const codeBlocks = new Map<string, string>(); 12 13 13 - // Process all pages in the document 14 + // Process all blocks (works for both linear and canvas) 14 15 for (let i = 0; i < blocks.length; i++) { 15 16 const block = blocks[i]; 16 17 const currentIndex = [i];