a tool for shared writing and social publishing

added pagetitle to looseleaf and pub, added shortcut if you only have one pub or looseleaf

+136 -73
+3 -59
app/(home-pages)/home/HomeLayout.tsx
··· 29 29 HomeEmptyState, 30 30 PublicationBanner, 31 31 } from "./HomeEmpty/HomeEmpty"; 32 - import { Popover } from "components/Popover"; 33 - import { PubIcon, PublicationButtons } from "components/ActionBar/Publications"; 34 - import { normalizePublicationRecord } from "src/utils/normalizeRecords"; 35 - import { ButtonPrimary } from "components/Buttons"; 36 - import { LooseLeafSmall } from "components/Icons/LooseleafSmall"; 37 - import { HomeButton } from "components/ActionBar/NavigationButtons"; 32 + import { PublicationNavigation } from "components/ActionBar/Publications"; 38 33 39 34 export type Leaflet = { 40 35 added_at: string; ··· 83 78 (leaflet) => leaflet.archived === true, 84 79 ).length > 0; 85 80 86 - function getPubIcons() { 87 - let hasLooseleafs = !!identity?.permission_token_on_homepage.find( 88 - (f) => 89 - f.permission_tokens.leaflets_to_documents && 90 - f.permission_tokens.leaflets_to_documents[0]?.document, 91 - ); 92 - 93 - if (identity && identity.publications.length >= 1) { 94 - return ( 95 - <div className="flex gap-1"> 96 - {identity.publications.map((pub, index) => { 97 - if (index <= 3) 98 - return ( 99 - <PubIcon 100 - key={pub.uri} 101 - record={normalizePublicationRecord(pub.record)} 102 - uri={pub.uri} 103 - /> 104 - ); 105 - })} 106 - </div> 107 - ); 108 - } 109 - if (identity && hasLooseleafs) { 110 - return ( 111 - <div className="bg-bg-leaflet rounded-full "> 112 - <LooseLeafSmall className="scale-[75%]" /> 113 - </div> 114 - ); 115 - } else 116 - return ( 117 - <ButtonPrimary compact className="text-sm!"> 118 - Create a Publication! 119 - </ButtonPrimary> 120 - ); 121 - } 122 - 123 81 return ( 124 82 <DashboardLayout 125 83 id="home" ··· 150 108 pageTitle={ 151 109 <PageTitle 152 110 pageTitle={"Home"} 153 - controls={ 154 - <Popover 155 - trigger={<div>{getPubIcons()}</div>} 156 - className="pt-1 px-2!" 157 - > 158 - <HomeButton current className="flex-row-reverse! justify-end!" /> 159 - <hr className="my-1 border-border-light" /> 160 - <PublicationButtons 161 - currentPage={"home"} 162 - currentPubUri={undefined} 163 - className="justify-end!" 164 - optionClassName=" flex-row-reverse!" 165 - /> 166 - </Popover> 167 - } 111 + controls={<PublicationNavigation currentPage="home" />} 168 112 /> 169 113 } 170 114 /> ··· 268 212 className={` 269 213 leafletList 270 214 w-full 271 - ${display === "grid" ? "grid auto-rows-max md:grid-cols-4 sm:grid-cols-3 grid-cols-2 gap-y-4 gap-x-4 sm:gap-x-6 sm:gap-y-5 grow" : "flex flex-col gap-2 pt-2"} `} 215 + ${display === "grid" ? "grid auto-rows-max md:grid-cols-4 sm:grid-cols-3 grid-cols-2 gap-y-4 gap-x-4 sm:gap-x-6 sm:gap-y-5 grow" : "flex flex-col gap-2"} `} 272 216 > 273 217 {props.leaflets.map(({ token: leaflet, added_at, archived }, index) => ( 274 218 <ReplicacheProvider
+11 -1
app/(home-pages)/looseleafs/LooseleafsLayout.tsx
··· 1 1 "use client"; 2 - import { DashboardLayout } from "components/PageLayouts/DashboardLayout"; 2 + import { 3 + DashboardLayout, 4 + PageTitle, 5 + } from "components/PageLayouts/DashboardLayout"; 3 6 import { useState } from "react"; 4 7 import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 5 8 import { Fact, PermissionToken } from "src/replicache"; ··· 10 13 import useSWR from "swr"; 11 14 import { getHomeDocs } from "../home/storage"; 12 15 import { Leaflet, LeafletList } from "../home/HomeLayout"; 16 + import { PublicationNavigation } from "components/ActionBar/Publications"; 13 17 14 18 export const LooseleafsLayout = (props: { 15 19 entityID: string | null; ··· 47 51 ), 48 52 }, 49 53 }} 54 + pageTitle={ 55 + <PageTitle 56 + pageTitle={"Looseleafs"} 57 + controls={<PublicationNavigation currentPage="looseleafs" />} 58 + /> 59 + } 50 60 /> 51 61 ); 52 62 };
+5 -10
app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx
··· 13 13 } from "components/PageLayouts/DashboardLayout"; 14 14 import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 15 15 import { type NormalizedPublication } from "src/utils/normalizeRecords"; 16 - import { PublicationButtons } from "components/ActionBar/Publications"; 17 - import { Popover } from "components/Popover"; 16 + import { PublicationNavigation } from "components/ActionBar/Publications"; 18 17 19 18 export default function PublicationDashboard({ 20 19 publication, ··· 83 82 <PageTitle 84 83 pageTitle={record.name} 85 84 controls={ 86 - <Popover trigger={<div>pubs</div>} className="pt-1 px-2!"> 87 - <PublicationButtons 88 - currentPage={"pub"} 89 - currentPubUri={publication.uri} 90 - className="justify-end!" 91 - optionClassName=" flex-row-reverse!" 92 - /> 93 - </Popover> 85 + <PublicationNavigation 86 + currentPage="pub" 87 + currentPubUri={publication.uri} 88 + /> 94 89 } 95 90 /> 96 91 }
+116 -2
components/ActionBar/Publications.tsx
··· 15 15 import { PublishSmall } from "components/Icons/PublishSmall"; 16 16 import { Popover } from "components/Popover"; 17 17 import { BlueskyLogin } from "app/login/LoginForm"; 18 - import { ButtonSecondary } from "components/Buttons"; 18 + import { ButtonPrimary, ButtonSecondary } from "components/Buttons"; 19 19 import { useIsMobile } from "src/hooks/isMobile"; 20 20 import { useState } from "react"; 21 21 import { LooseLeafSmall } from "components/Icons/LooseleafSmall"; 22 - import type { navPages } from "./NavigationButtons"; 22 + import { HomeButton, type navPages } from "./NavigationButtons"; 23 23 import { AddTiny } from "components/Icons/AddTiny"; 24 + import { HomeSmall } from "components/Icons/HomeSmall"; 25 + import { HomeTiny } from "components/Icons/HomeTiny"; 26 + import { LooseleafTiny } from "components/Icons/LooseleafTiny"; 24 27 25 28 export const PublicationButtons = (props: { 26 29 currentPage: navPages; ··· 222 225 {props.record?.name.slice(0, 1).toUpperCase()} 223 226 </div> 224 227 </div> 228 + ); 229 + }; 230 + 231 + export const PublicationNavigation = (props: { 232 + currentPage: navPages; 233 + currentPubUri?: string; 234 + }) => { 235 + let { identity } = useIdentityData(); 236 + 237 + let hasLooseleafs = !!identity?.permission_token_on_homepage.find( 238 + (f) => 239 + f.permission_tokens.leaflets_to_documents && 240 + f.permission_tokens.leaflets_to_documents[0]?.document, 241 + ); 242 + 243 + let pubCount = identity?.publications.length ?? 0; 244 + let onlyOnePub = pubCount === 1 && !hasLooseleafs; 245 + let onlyLooseleafs = pubCount === 0 && hasLooseleafs; 246 + 247 + function getTriggerIcons() { 248 + if (identity && pubCount >= 1) { 249 + return ( 250 + <div className="flex gap-1"> 251 + {identity.publications.map((pub, index) => { 252 + if (index <= 2) 253 + return ( 254 + <PubIcon 255 + key={pub.uri} 256 + record={normalizePublicationRecord(pub.record)} 257 + uri={pub.uri} 258 + /> 259 + ); 260 + })} 261 + </div> 262 + ); 263 + } 264 + if (identity && hasLooseleafs) { 265 + return ( 266 + <div className="bg-bg-leaflet rounded-full"> 267 + <LooseLeafSmall className="scale-[75%]" /> 268 + </div> 269 + ); 270 + } else 271 + return ( 272 + <ButtonPrimary compact className="text-sm!"> 273 + Create a Publication! 274 + </ButtonPrimary> 275 + ); 276 + } 277 + 278 + // Single pub, no looseleafs - just link to that pub 279 + if (onlyOnePub && identity) { 280 + let pub = identity.publications[0]; 281 + if (props.currentPage === "pub") 282 + return ( 283 + <SpeedyLink 284 + href={`/home`} 285 + className="hover:no-underline! text-tertiary text-bold flex gap-2 text-sm border border-border-light rounded-md px-1 py-[px] items-center" 286 + > 287 + Home <HomeTiny /> 288 + </SpeedyLink> 289 + ); 290 + return ( 291 + <SpeedyLink 292 + href={`${getBasePublicationURL(pub)}/dashboard`} 293 + className="hover:no-underline! text-tertiary text-bold flex gap-2 text-sm border border-border-light rounded-md px-1 py-[px] items-center max-w-32 " 294 + > 295 + <div className="truncate">{pub.name}</div> 296 + <PubIcon 297 + small 298 + record={normalizePublicationRecord(pub.record)} 299 + uri={pub.uri} 300 + /> 301 + </SpeedyLink> 302 + ); 303 + } 304 + 305 + // Only looseleafs, no pubs - just link to looseleafs 306 + if (!onlyLooseleafs) { 307 + if (props.currentPage === "looseleafs") 308 + return ( 309 + <SpeedyLink 310 + href={`/home`} 311 + className="hover:no-underline! text-tertiary text-bold flex gap-2 text-sm border border-border-light rounded-md px-1 py-[px] items-center" 312 + > 313 + Home <HomeTiny /> 314 + </SpeedyLink> 315 + ); 316 + return ( 317 + <SpeedyLink href="/looseleafs" className="hover:no-underline!"> 318 + <div className="hover:no-underline! text-tertiary text-bold flex gap-2 text-sm border border-border-light rounded-md px-1 py-[px] items-center max-w-32 "> 319 + Looseleafs <LooseleafTiny /> 320 + </div> 321 + </SpeedyLink> 322 + ); 323 + } 324 + 325 + return ( 326 + <Popover trigger={<div>{getTriggerIcons()}</div>} className="pt-1 px-2!"> 327 + <HomeButton 328 + current={props.currentPage === "home"} 329 + className="flex-row-reverse! justify-end!" 330 + /> 331 + <hr className="my-1 border-border-light" /> 332 + <PublicationButtons 333 + currentPage={props.currentPage} 334 + currentPubUri={props.currentPubUri} 335 + className="justify-end!" 336 + optionClassName=" flex-row-reverse!" 337 + /> 338 + </Popover> 225 339 ); 226 340 }; 227 341
+1 -1
components/PageHeader.tsx
··· 29 29 <div 30 30 className={` 31 31 headerWrapper 32 - sticky top-0 z-10 32 + sticky top-0 z-20 33 33 w-full bg-transparent 34 34 `} 35 35 >