a tool for shared writing and social publishing

Added a page title to the home layout

+139 -32
+64
app/(home-pages)/home/HomeLayout.tsx
··· 18 DashboardLayout, 19 DashboardState, 20 useDashboardState, 21 } from "components/PageLayouts/DashboardLayout"; 22 import { Actions } from "./Actions/Actions"; 23 import { GetLeafletDataReturnType } from "app/api/rpc/[command]/get_leaflet_data"; ··· 28 HomeEmptyState, 29 PublicationBanner, 30 } from "./HomeEmpty/HomeEmpty"; 31 32 export type Leaflet = { 33 added_at: string; ··· 76 (leaflet) => leaflet.archived === true, 77 ).length > 0; 78 79 return ( 80 <DashboardLayout 81 id="home" ··· 103 ), 104 }, 105 }} 106 /> 107 ); 108 };
··· 18 DashboardLayout, 19 DashboardState, 20 useDashboardState, 21 + PageTitle, 22 } from "components/PageLayouts/DashboardLayout"; 23 import { Actions } from "./Actions/Actions"; 24 import { GetLeafletDataReturnType } from "app/api/rpc/[command]/get_leaflet_data"; ··· 29 HomeEmptyState, 30 PublicationBanner, 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"; 38 39 export type Leaflet = { 40 added_at: string; ··· 83 (leaflet) => leaflet.archived === true, 84 ).length > 0; 85 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 return ( 124 <DashboardLayout 125 id="home" ··· 147 ), 148 }, 149 }} 150 + pageTitle={ 151 + <PageTitle 152 + 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 + } 168 + /> 169 + } 170 /> 171 ); 172 };
+18
app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx
··· 8 import { PublicationSubscribers } from "./PublicationSubscribers"; 9 import { 10 DashboardLayout, 11 PublicationDashboardControls, 12 } from "components/PageLayouts/DashboardLayout"; 13 import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 14 import { type NormalizedPublication } from "src/utils/normalizeRecords"; 15 16 export default function PublicationDashboard({ 17 publication, ··· 76 actions={<Actions publication={publication.uri} />} 77 currentPage="pub" 78 publication={publication.uri} 79 /> 80 ); 81 }
··· 8 import { PublicationSubscribers } from "./PublicationSubscribers"; 9 import { 10 DashboardLayout, 11 + PageTitle, 12 PublicationDashboardControls, 13 } from "components/PageLayouts/DashboardLayout"; 14 import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 15 import { type NormalizedPublication } from "src/utils/normalizeRecords"; 16 + import { PublicationButtons } from "components/ActionBar/Publications"; 17 + import { Popover } from "components/Popover"; 18 19 export default function PublicationDashboard({ 20 publication, ··· 79 actions={<Actions publication={publication.uri} />} 80 currentPage="pub" 81 publication={publication.uri} 82 + pageTitle={ 83 + <PageTitle 84 + pageTitle={record.name} 85 + 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> 94 + } 95 + /> 96 + } 97 /> 98 ); 99 }
+1 -1
components/ActionBar/ActionButton.tsx
··· 71 > 72 <div className="shrink-0 flex flex-row gap-0.5">{icon}</div> 73 <div 74 - className={`flex flex-col pr-1 ${subtext && "leading-snug"} max-w-full min-w-0 ${sidebar.open ? "block" : showLabelOnMobile ? "sm:hidden block" : "hidden"}`} 75 > 76 <div className="truncate text-left">{label}</div> 77 {subtext && (
··· 71 > 72 <div className="shrink-0 flex flex-row gap-0.5">{icon}</div> 73 <div 74 + className={`flex flex-col ${subtext && "leading-snug"} max-w-full min-w-0 ${sidebar.open ? "block" : showLabelOnMobile ? "sm:hidden block" : "hidden"}`} 75 > 76 <div className="truncate text-left">{label}</div> 77 {subtext && (
+8 -4
components/ActionBar/DesktopNavigation.tsx
··· 8 } from "./NavigationButtons"; 9 import { PublicationButtons } from "./Publications"; 10 import { Sidebar } from "./Sidebar"; 11 12 export const DesktopNavigation = (props: { 13 currentPage: navPages; ··· 24 props.currentPage === "pub"; 25 return ( 26 <div className="flex flex-col gap-3"> 27 - {identity?.atp_did && ( 28 - <Sidebar alwaysOpen> 29 <NotificationButton current={props.currentPage === "notifications"} /> 30 - </Sidebar> 31 - )} 32 <Sidebar alwaysOpen> 33 <ReaderButton 34 current={props.currentPage === "reader"}
··· 8 } from "./NavigationButtons"; 9 import { PublicationButtons } from "./Publications"; 10 import { Sidebar } from "./Sidebar"; 11 + import { LoginActionButton, LoginButton } from "components/LoginButton"; 12 13 export const DesktopNavigation = (props: { 14 currentPage: navPages; ··· 25 props.currentPage === "pub"; 26 return ( 27 <div className="flex flex-col gap-3"> 28 + <Sidebar alwaysOpen> 29 + {identity?.atp_did ? ( 30 <NotificationButton current={props.currentPage === "notifications"} /> 31 + ) : ( 32 + <LoginActionButton /> 33 + )} 34 + </Sidebar> 35 + 36 <Sidebar alwaysOpen> 37 <ReaderButton 38 current={props.currentPage === "reader"}
+11 -19
components/ActionBar/NavigationButtons.tsx
··· 23 | "notifications" 24 | "looseleafs" 25 | "tag" 26 - | "profile"; 27 28 - export const HomeButton = (props: { current?: boolean }) => { 29 return ( 30 <SpeedyLink href={"/home"} className="hover:!no-underline"> 31 <ActionButton 32 nav 33 icon={<HomeSmall />} 34 - label="Writer Home" 35 - className={props.current ? "bg-bg-page! border-border-light!" : ""} 36 /> 37 </SpeedyLink> 38 ); ··· 82 nav 83 labelOnMobile={true} 84 icon={<WriterSmall />} 85 - label=<div className="flex flex-row gap-1"> 86 - Writer 87 - {current && ( 88 - <> 89 - <Divider /> {currentIcon} 90 - </> 91 - )} 92 - </div> 93 className={current ? "bg-bg-page! border-border-light!" : ""} 94 /> 95 ) : ( ··· 99 icon={ 100 <> 101 <WriterSmall /> 102 - {current && ( 103 - <> 104 - <Divider /> {currentIcon} 105 - </> 106 - )} 107 </> 108 } 109 label=<div className="flex flex-row gap-1">Writer</div> ··· 116 <ActionButton 117 nav 118 icon={<HomeSmall />} 119 - label="Writer Home" 120 className={ 121 props.currentPage === "home" 122 ? "bg-bg-page! border-border-light!" ··· 145 nav 146 labelOnMobile={props.compactOnMobile} 147 icon={<ReaderUnreadSmall />} 148 - label="Reader" 149 className={props.current ? "bg-bg-page! border-border-light!" : ""} 150 /> 151 </SpeedyLink>
··· 23 | "notifications" 24 | "looseleafs" 25 | "tag" 26 + | "profile" 27 + | "discover"; 28 29 + export const HomeButton = (props: { 30 + current?: boolean; 31 + className?: string; 32 + }) => { 33 return ( 34 <SpeedyLink href={"/home"} className="hover:!no-underline"> 35 <ActionButton 36 nav 37 icon={<HomeSmall />} 38 + label="Write" 39 + className={`${props.current ? "bg-bg-page! border-border-light!" : ""} ${props.className}`} 40 /> 41 </SpeedyLink> 42 ); ··· 86 nav 87 labelOnMobile={true} 88 icon={<WriterSmall />} 89 + label=<div className="flex flex-row gap-1">Write</div> 90 className={current ? "bg-bg-page! border-border-light!" : ""} 91 /> 92 ) : ( ··· 96 icon={ 97 <> 98 <WriterSmall /> 99 </> 100 } 101 label=<div className="flex flex-row gap-1">Writer</div> ··· 108 <ActionButton 109 nav 110 icon={<HomeSmall />} 111 + label="Write" 112 className={ 113 props.currentPage === "home" 114 ? "bg-bg-page! border-border-light!" ··· 137 nav 138 labelOnMobile={props.compactOnMobile} 139 icon={<ReaderUnreadSmall />} 140 + label="Read" 141 className={props.current ? "bg-bg-page! border-border-light!" : ""} 142 /> 143 </SpeedyLink>
+16 -7
components/ActionBar/Publications.tsx
··· 20 import { useState } from "react"; 21 import { LooseLeafSmall } from "components/Icons/LooseleafSmall"; 22 import type { navPages } from "./NavigationButtons"; 23 24 export const PublicationButtons = (props: { 25 currentPage: navPages; 26 currentPubUri: string | undefined; 27 }) => { 28 let { identity } = useIdentityData(); 29 let hasLooseleafs = !!identity?.permission_token_on_homepage.find( ··· 38 return <PubListEmpty />; 39 40 return ( 41 - <div className="pubListWrapper w-full flex flex-col sm:bg-transparent sm:border-0"> 42 {hasLooseleafs && ( 43 <> 44 <SpeedyLink 45 href={`/looseleafs`} 46 - className="flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full" 47 > 48 {/*TODO How should i get if this is the current page or not? 49 theres not "pub" to check the uri for. Do i need to add it as an option to NavPages? thats kinda annoying*/} ··· 51 label="Looseleafs" 52 icon={<LooseLeafSmall />} 53 nav 54 - className={ 55 props.currentPage === "looseleafs" 56 ? "bg-bg-page! border-border!" 57 : "" 58 } 59 /> 60 </SpeedyLink> 61 </> ··· 68 key={d.uri} 69 record={d.record} 70 current={d.uri === props.currentPubUri} 71 /> 72 ); 73 })} 74 <Link 75 href={"/lish/createPub"} 76 - className="pubListCreateNew text-accent-contrast text-sm place-self-end hover:text-accent-contrast" 77 > 78 - New 79 </Link> 80 </div> 81 ); ··· 86 name: string; 87 record: Json; 88 current?: boolean; 89 }) => { 90 let record = normalizePublicationRecord(props.record); 91 if (!record) return; ··· 93 return ( 94 <SpeedyLink 95 href={`${getBasePublicationURL(props)}/dashboard`} 96 - className="flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full" 97 > 98 <ActionButton 99 label={record.name} 100 icon={<PubIcon record={record} uri={props.uri} />} 101 nav 102 - className={props.current ? "bg-bg-page! border-border!" : ""} 103 /> 104 </SpeedyLink> 105 );
··· 20 import { useState } from "react"; 21 import { LooseLeafSmall } from "components/Icons/LooseleafSmall"; 22 import type { navPages } from "./NavigationButtons"; 23 + import { AddTiny } from "components/Icons/AddTiny"; 24 25 export const PublicationButtons = (props: { 26 currentPage: navPages; 27 currentPubUri: string | undefined; 28 + className?: string; 29 + optionClassName?: string; 30 }) => { 31 let { identity } = useIdentityData(); 32 let hasLooseleafs = !!identity?.permission_token_on_homepage.find( ··· 41 return <PubListEmpty />; 42 43 return ( 44 + <div 45 + className={`pubListWrapper w-full flex flex-col sm:bg-transparent sm:border-0 ${props.className}`} 46 + > 47 {hasLooseleafs && ( 48 <> 49 <SpeedyLink 50 href={`/looseleafs`} 51 + className={`flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full `} 52 > 53 {/*TODO How should i get if this is the current page or not? 54 theres not "pub" to check the uri for. Do i need to add it as an option to NavPages? thats kinda annoying*/} ··· 56 label="Looseleafs" 57 icon={<LooseLeafSmall />} 58 nav 59 + className={`${ 60 props.currentPage === "looseleafs" 61 ? "bg-bg-page! border-border!" 62 : "" 63 } 64 + ${props.optionClassName}`} 65 /> 66 </SpeedyLink> 67 </> ··· 74 key={d.uri} 75 record={d.record} 76 current={d.uri === props.currentPubUri} 77 + className={props.optionClassName} 78 /> 79 ); 80 })} 81 <Link 82 href={"/lish/createPub"} 83 + className={`pubListCreateNew group/new-pub text-tertiary hover:text-accent-contrast flex gap-2 items-center p-1 no-underline! ${props.optionClassName}`} 84 > 85 + <div className="group-hover/new-pub:border-accent-contrast w-6 h-6 border-border-light border-2 border-dashed rounded-full" /> 86 + New Publication 87 </Link> 88 </div> 89 ); ··· 94 name: string; 95 record: Json; 96 current?: boolean; 97 + className?: string; 98 }) => { 99 let record = normalizePublicationRecord(props.record); 100 if (!record) return; ··· 102 return ( 103 <SpeedyLink 104 href={`${getBasePublicationURL(props)}/dashboard`} 105 + className={`flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full `} 106 > 107 <ActionButton 108 label={record.name} 109 icon={<PubIcon record={record} uri={props.uri} />} 110 nav 111 + className={`${props.current ? "bg-bg-page! border-border!" : ""} ${props.className}`} 112 /> 113 </SpeedyLink> 114 );
+21 -1
components/PageLayouts/DashboardLayout.tsx
··· 27 import { ExternalLinkTiny } from "components/Icons/ExternalLinkTiny"; 28 import { usePreserveScroll } from "src/hooks/usePreserveScroll"; 29 import { Tab } from "components/Tab"; 30 31 export type DashboardState = { 32 display?: "grid" | "list"; ··· 141 publication?: string; 142 profileDid?: string; 143 actions: React.ReactNode; 144 }) { 145 const searchParams = useSearchParams(); 146 const tabParam = searchParams.get("tab"); ··· 167 let [headerState, setHeaderState] = useState<"default" | "controls">( 168 "default", 169 ); 170 return ( 171 <DashboardIdContext.Provider value={props.id}> 172 <div ··· 186 ref={ref} 187 id="home-content" 188 > 189 {Object.keys(props.tabs).length <= 1 && !controls ? null : ( 190 <> 191 <Header> ··· 256 ); 257 } 258 259 export const HomeDashboardControls = (props: { 260 searchValue: string; 261 setSearchValueAction: (searchValue: string) => void; ··· 449 className={`dashboardSearchInput 450 appearance-none! outline-hidden! 451 w-full min-w-0 text-primary relative pl-7 pr-1 -my-px 452 - border rounded-md border-transparent focus-within:border-border 453 bg-transparent ${props.hasBackgroundImage ? "focus-within:bg-bg-page" : "focus-within:bg-bg-leaflet"} `} 454 type="text" 455 id="pubName"
··· 27 import { ExternalLinkTiny } from "components/Icons/ExternalLinkTiny"; 28 import { usePreserveScroll } from "src/hooks/usePreserveScroll"; 29 import { Tab } from "components/Tab"; 30 + import { PubIcon, PublicationButtons } from "components/ActionBar/Publications"; 31 32 export type DashboardState = { 33 display?: "grid" | "list"; ··· 142 publication?: string; 143 profileDid?: string; 144 actions: React.ReactNode; 145 + pageTitle?: React.ReactNode; 146 }) { 147 const searchParams = useSearchParams(); 148 const tabParam = searchParams.get("tab"); ··· 169 let [headerState, setHeaderState] = useState<"default" | "controls">( 170 "default", 171 ); 172 + 173 return ( 174 <DashboardIdContext.Provider value={props.id}> 175 <div ··· 189 ref={ref} 190 id="home-content" 191 > 192 + {props.pageTitle} 193 + 194 {Object.keys(props.tabs).length <= 1 && !controls ? null : ( 195 <> 196 <Header> ··· 261 ); 262 } 263 264 + export const PageTitle = (props: { 265 + pageTitle: string; 266 + controls: React.ReactNode; 267 + }) => { 268 + return ( 269 + <MediaContents 270 + mobile={true} 271 + className="flex justify-between items-center px-1 mt-1 -mb-1 w-full " 272 + > 273 + <h4 className="grow truncate">{props.pageTitle}</h4> 274 + <div className="shrink-0 h-6">{props.controls}</div> 275 + </MediaContents> 276 + ); 277 + }; 278 + 279 export const HomeDashboardControls = (props: { 280 searchValue: string; 281 setSearchValueAction: (searchValue: string) => void; ··· 469 className={`dashboardSearchInput 470 appearance-none! outline-hidden! 471 w-full min-w-0 text-primary relative pl-7 pr-1 -my-px 472 + border rounded-md border-border-light focus-within:border-border 473 bg-transparent ${props.hasBackgroundImage ? "focus-within:bg-bg-page" : "focus-within:bg-bg-leaflet"} `} 474 type="text" 475 id="pubName"