a tool for shared writing and social publishing

Merge branch 'main' into refactor/standard.site

+196 -125
+55 -35
app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx
··· 1 1 "use client"; 2 2 import { Avatar } from "components/Avatar"; 3 - import { blobRefToSrc } from "src/utils/blobRefToSrc"; 4 - import type { ProfileData } from "./layout"; 5 3 import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; 6 4 import { colorToString } from "components/ThemeManager/useColorAttribute"; 7 5 import { PubIcon } from "components/ActionBar/Publications"; ··· 24 22 <Avatar 25 23 src={profileRecord.avatar} 26 24 displayName={profileRecord.displayName} 27 - className="mx-auto mt-3 sm:mt-4" 25 + className="profileAvatar mx-auto mt-3 sm:mt-4" 28 26 giant 29 27 /> 30 28 ); 31 29 32 30 const displayNameElement = ( 33 - <h3 className=" px-3 sm:px-4 pt-2 leading-tight"> 31 + <h3 className="profileName px-3 sm:px-4 pt-2 leading-tight"> 34 32 {profileRecord.displayName 35 33 ? profileRecord.displayName 36 34 : `@${props.profile.handle}`} ··· 39 37 40 38 const handleElement = profileRecord.displayName && ( 41 39 <div 42 - className={`text-tertiary ${props.popover ? "text-xs" : "text-sm"} pb-1 italic px-3 sm:px-4 truncate`} 40 + className={`profileHandle text-secondary ${props.popover ? "text-sm" : "text-sm"} px-3 sm:px-4 truncate`} 43 41 > 44 42 @{props.profile.handle} 45 43 </div> 46 44 ); 45 + console.log(props.profile); 47 46 48 47 return ( 49 48 <div 50 - className={`flex flex-col relative ${props.popover && "text-sm"}`} 49 + className={`profileHeader flex flex-col relative `} 51 50 id="profile-header" 52 51 > 53 - <ProfileLinks handle={props.profile.handle || ""} /> 54 - <div className="flex flex-col"> 55 - <div className="flex flex-col group"> 52 + {!props.popover && <ProfileLinks handle={props.profile.handle || ""} />} 53 + <div className="profileInfo flex flex-col gap-3"> 54 + <div className="profileNameAndHandle flex flex-col "> 56 55 {props.popover ? ( 57 56 <SpeedyLink className={"hover:no-underline!"} href={profileUrl}> 58 57 {avatarElement} ··· 60 59 ) : ( 61 60 avatarElement 62 61 )} 63 - {props.popover ? ( 64 - <SpeedyLink 65 - className={" text-primary group-hover:underline"} 66 - href={profileUrl} 67 - > 68 - {displayNameElement} 69 - </SpeedyLink> 70 - ) : ( 71 - displayNameElement 72 - )} 73 - {props.popover && handleElement ? ( 74 - <SpeedyLink className={"group-hover:underline"} href={profileUrl}> 75 - {handleElement} 76 - </SpeedyLink> 77 - ) : ( 78 - handleElement 79 - )} 62 + {displayNameElement} 63 + 64 + {handleElement} 65 + <KnownFollowers 66 + viewer={props.profile.viewer} 67 + did={props.profile.did} 68 + /> 69 + 70 + <pre className="profileDescription pt-1 px-3 sm:px-4 whitespace-pre-wrap"> 71 + {profileRecord.description 72 + ? parseDescription(profileRecord.description) 73 + : null} 74 + </pre> 80 75 </div> 81 - <pre className="text-secondary px-3 sm:px-4 whitespace-pre-wrap"> 82 - {profileRecord.description 83 - ? parseDescription(profileRecord.description) 84 - : null} 85 - </pre> 86 - <div className=" w-full overflow-x-scroll py-3 mb-3 "> 76 + 77 + <div className="profilePublicationCards w-full overflow-x-scroll"> 87 78 <div 88 79 className={`grid grid-flow-col gap-2 mx-auto w-fit px-3 sm:px-4 ${props.popover ? "auto-cols-[164px]" : "auto-cols-[164px] sm:auto-cols-[240px]"}`} 89 80 > ··· 99 90 100 91 const ProfileLinks = (props: { handle: string }) => { 101 92 return ( 102 - <div className="absolute sm:top-4 top-3 sm:right-4 right-3 flex flex-row gap-2"> 93 + <div className="profileLinks absolute sm:top-4 top-3 sm:right-4 right-3 flex flex-row gap-2"> 103 94 <a 104 95 className="text-tertiary hover:text-accent-contrast hover:no-underline!" 105 96 href={`https://bsky.app/profile/${props.handle}`} ··· 116 107 return ( 117 108 <a 118 109 href={record.url} 119 - className="border border-border p-2 rounded-lg hover:no-underline! text-primary basis-1/2" 110 + className="profilePublicationCard border border-border p-2 rounded-lg hover:no-underline! text-primary basis-1/2" 120 111 style={{ backgroundColor: `rgb(${colorToString(bgLeaflet, "rgb")})` }} 121 112 > 122 113 <div ··· 217 208 ? urlWithoutProtocol.slice(0, 50) + "…" 218 209 : urlWithoutProtocol; 219 210 parts.push( 220 - <a key={key++} href={match.href} target="_blank" rel="noopener noreferrer"> 211 + <a 212 + key={key++} 213 + href={match.href} 214 + target="_blank" 215 + rel="noopener noreferrer" 216 + > 221 217 {displayText} 222 218 </a>, 223 219 ); ··· 233 229 234 230 return parts; 235 231 } 232 + 233 + const KnownFollowers = (props: { 234 + viewer: ProfileViewDetailed["viewer"]; 235 + did: string; 236 + }) => { 237 + if (!props.viewer?.knownFollowers) return null; 238 + let count = props.viewer.knownFollowers.count; 239 + 240 + return ( 241 + <> 242 + <div className="profileKnownFollowers sm:px-4 px-3 text-xs text-tertiary italic"> 243 + Followed by{" "} 244 + <a 245 + className="hover:underline" 246 + href={`https://bsky.app/profile/${props.did}/known-followers`} 247 + target="_blank" 248 + > 249 + {props.viewer?.knownFollowers?.followers[0]?.displayName}{" "} 250 + {count > 1 ? `and ${count - 1} other${count > 2 ? "s" : ""}` : ""} 251 + </a> 252 + </div> 253 + </> 254 + ); 255 + };
+1 -1
app/(home-pages)/p/[didOrHandle]/ProfileTabs.tsx
··· 41 41 const bgColor = cardBorderHidden ? "var(--bg-leaflet)" : "var(--bg-page)"; 42 42 43 43 return ( 44 - <div className="flex flex-col w-full sticky top-3 sm:top-4 z-20 sm:px-4 px-3"> 44 + <div className="flex flex-col w-full sticky top-3 sm:top-4 z-20 sm:px-4 px-3 pt-6"> 45 45 <div 46 46 style={ 47 47 scrollPosWithinTabContent < 20
+2 -4
app/globals.css
··· 309 309 .ProseMirror .didMention.ProseMirror-selectednode { 310 310 @apply text-accent-contrast; 311 311 @apply px-0.5; 312 - @apply -mx-[3px]; /* extra px to account for the border*/ 313 - @apply -my-px; /*to account for the border*/ 314 312 @apply rounded-[4px]; 315 313 @apply box-decoration-clone; 316 314 background-color: rgba(var(--accent-contrast), 0.2); ··· 321 319 @apply cursor-pointer; 322 320 @apply text-accent-contrast; 323 321 @apply px-0.5; 324 - @apply -mx-[3px]; 325 - @apply -my-px; /*to account for the border*/ 326 322 @apply rounded-[4px]; 327 323 @apply box-decoration-clone; 328 324 background-color: rgba(var(--accent-contrast), 0.2); 329 325 border: 1px solid transparent; 326 + display: inline; 327 + white-space: normal; 330 328 } 331 329 332 330 .multiselected:focus-within .selection-highlight {
+2 -2
app/lish/[did]/[publication]/[rkey]/CanvasPage.tsx
··· 216 216 <Interactions 217 217 quotesCount={props.quotesCount || 0} 218 218 commentsCount={props.commentsCount || 0} 219 - showComments={props.preferences.showComments} 220 - showMentions={props.preferences.showMentions} 219 + showComments={props.preferences.showComments !== false} 220 + showMentions={props.preferences.showMentions !== false} 221 221 pageId={props.pageId} 222 222 /> 223 223 {!props.isSubpage && (
+2 -2
app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx
··· 36 36 return ( 37 37 <> 38 38 <SandwichSpacer noWidth /> 39 - <div className="snap-center h-full flex z-10 shrink-0 w-[calc(var(--page-width-units)-6px)] sm:w-[calc(var(--page-width-units))]"> 39 + <div className="snap-center h-full flex z-10 shrink-0 sm:max-w-prose sm:w-full w-[calc(100vw-12px)]"> 40 40 <div 41 41 id="interaction-drawer" 42 - className={`opaque-container h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll -ml-[1px] ${props.showPageBackground ? "rounded-l-none! rounded-r-lg!" : "rounded-lg! sm:mx-2"}`} 42 + className={`opaque-container h-full w-full px-3 sm:px-4 pt-2 sm:pt-3 pb-6 overflow-scroll ${props.showPageBackground ? "rounded-l-none! rounded-r-lg! -ml-[1px]" : "rounded-lg! sm:ml-4"}`} 43 43 > 44 44 {drawer.drawer === "quotes" ? ( 45 45 <Quotes {...props} quotesAndMentions={filteredQuotesAndMentions} />
+6 -23
app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx
··· 106 106 quotesCount: number; 107 107 commentsCount: number; 108 108 className?: string; 109 - showComments?: boolean; 110 - showMentions?: boolean; 109 + showComments: boolean; 110 + showMentions: boolean; 111 111 pageId?: string; 112 112 }) => { 113 113 const { uri: document_uri, quotesAndMentions, normalizedDocument } = useDocument(); ··· 164 164 quotesCount: number; 165 165 commentsCount: number; 166 166 className?: string; 167 - showComments?: boolean; 168 - showMentions?: boolean; 167 + showComments: boolean; 168 + showMentions: boolean; 169 169 pageId?: string; 170 170 }) => { 171 171 const { uri: document_uri, quotesAndMentions, normalizedDocument, publication, leafletId } = useDocument(); ··· 200 200 <div 201 201 className={`text-tertiary px-3 sm:px-4 flex flex-col ${props.className}`} 202 202 > 203 - {!subscribed && !isAuthor && publication && publication.record && ( 204 - <div className="text-center flex flex-col accent-container rounded-md mb-3"> 205 - <div className="flex flex-col py-4"> 206 - <div className="leading-snug flex flex-col pb-2 text-sm"> 207 - <div className="font-bold">Subscribe to {publication.name}</div>{" "} 208 - to get updates in Reader, RSS, or via Bluesky Feed 209 - </div> 210 - <SubscribeWithBluesky 211 - pubName={publication.name} 212 - pub_uri={publication.uri} 213 - base_url={getPublicationURL(publication)} 214 - subscribers={publication?.publication_subscriptions} 215 - /> 216 - </div> 217 - </div> 218 - )} 219 203 {tagCount > 0 && ( 220 204 <> 221 205 <hr className="border-border-light mb-3" /> ··· 232 216 ) : ( 233 217 <> 234 218 <div className="flex gap-2"> 235 - {props.quotesCount === 0 || 236 - props.showMentions === false ? null : ( 219 + {props.quotesCount === 0 || !props.showMentions ? null : ( 237 220 <button 238 221 className="flex w-fit gap-2 items-center px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline" 239 222 onClick={() => { ··· 256 239 >{`Mention${props.quotesCount === 1 ? "" : "s"}`}</span> 257 240 </button> 258 241 )} 259 - {props.showComments === false ? null : ( 242 + {!props.showComments ? null : ( 260 243 <button 261 244 className="flex gap-2 items-center w-fit px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline" 262 245 onClick={() => {
+5 -3
app/lish/[did]/[publication]/[rkey]/LinearDocumentPage.tsx
··· 21 21 import { PollData } from "./fetchPollData"; 22 22 import { SharedPageProps } from "./PostPages"; 23 23 import { PostPrevNextButtons } from "./PostPrevNextButtons"; 24 + import { PostSubscribe } from "./PostSubscribe"; 24 25 25 26 export function LinearDocumentPage({ 26 27 blocks, ··· 78 79 did={did} 79 80 prerenderedCodeBlocks={prerenderedCodeBlocks} 80 81 /> 82 + <PostSubscribe /> 81 83 <PostPrevNextButtons 82 - showPrevNext={preferences.showPrevNext && !isSubpage} 84 + showPrevNext={preferences.showPrevNext !== false && !isSubpage} 83 85 /> 84 86 <ExpandedInteractions 85 87 pageId={pageId} 86 - showComments={preferences.showComments} 87 - showMentions={preferences.showMentions} 88 + showComments={preferences.showComments !== false} 89 + showMentions={preferences.showMentions !== false} 88 90 commentsCount={getCommentCount(document.comments_on_documents, pageId) || 0} 89 91 quotesCount={getQuoteCount(document.quotesAndMentions, pageId) || 0} 90 92 />
+2 -2
app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
··· 85 85 ) : null} 86 86 </div> 87 87 <Interactions 88 - showComments={props.preferences.showComments} 89 - showMentions={props.preferences.showMentions} 88 + showComments={props.preferences.showComments !== false} 89 + showMentions={props.preferences.showMentions !== false} 90 90 quotesCount={getQuoteCount(document?.quotesAndMentions || []) || 0} 91 91 commentsCount={getCommentCount(document?.comments_on_documents || []) || 0} 92 92 />
+1 -3
app/lish/[did]/[publication]/[rkey]/PostPrevNextButtons.tsx
··· 5 5 import { SpeedyLink } from "components/SpeedyLink"; 6 6 import { ArrowRightTiny } from "components/Icons/ArrowRightTiny"; 7 7 8 - export const PostPrevNextButtons = (props: { 9 - showPrevNext: boolean | undefined; 10 - }) => { 8 + export const PostPrevNextButtons = (props: { showPrevNext: boolean }) => { 11 9 const { prevNext, publication } = useDocument(); 12 10 13 11 if (!props.showPrevNext || !publication) return null;
+45
app/lish/[did]/[publication]/[rkey]/PostSubscribe.tsx
··· 1 + "use client"; 2 + import { useContext } from "react"; 3 + import { PostPageContext } from "./PostPageContext"; 4 + import { useIdentityData } from "components/IdentityProvider"; 5 + import { SubscribeWithBluesky } from "app/lish/Subscribe"; 6 + import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 7 + 8 + export const PostSubscribe = () => { 9 + const data = useContext(PostPageContext); 10 + let { identity } = useIdentityData(); 11 + 12 + let publication = data?.documents_in_publications[0]?.publications; 13 + 14 + let subscribed = 15 + identity?.atp_did && 16 + publication?.publication_subscriptions && 17 + publication?.publication_subscriptions.find( 18 + (s) => s.identity === identity.atp_did, 19 + ); 20 + 21 + let isAuthor = 22 + identity && 23 + identity.atp_did === 24 + data?.documents_in_publications[0]?.publications?.identity_did && 25 + data?.leaflets_in_publications[0]; 26 + 27 + if (!subscribed && !isAuthor && publication && publication.record) 28 + return ( 29 + <div className="text-center flex flex-col accent-container rounded-md mb-3 mx-3 sm:mx-4"> 30 + <div className="flex flex-col py-4"> 31 + <div className="leading-snug flex flex-col pb-2 "> 32 + <div className="font-bold">Subscribe to {publication.name}</div> to 33 + get updates in Reader, RSS, or via Bluesky Feed 34 + </div> 35 + <SubscribeWithBluesky 36 + pubName={publication.name} 37 + pub_uri={publication.uri} 38 + base_url={getPublicationURL(publication)} 39 + subscribers={publication?.publication_subscriptions} 40 + /> 41 + </div> 42 + </div> 43 + ); 44 + else return; 45 + };
+2 -2
app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx
··· 140 140 quotesCount={doc.mentionsCount} 141 141 commentsCount={doc.commentsCount} 142 142 tags={doc.record.tags || []} 143 - showComments={pubRecord?.preferences?.showComments} 144 - showMentions={pubRecord?.preferences?.showMentions} 143 + showComments={pubRecord?.preferences?.showComments !== false} 144 + showMentions={pubRecord?.preferences?.showMentions !== false} 145 145 postUrl={`${getPublicationURL(publication)}/${uri.rkey}`} 146 146 /> 147 147 </div>
-1
app/lish/[did]/[publication]/dashboard/settings/PostOptions.tsx
··· 56 56 }, 57 57 }); 58 58 toast({ type: "success", content: <strong>Posts Updated!</strong> }); 59 - console.log(record.preferences?.showPrevNext); 60 59 props.setLoading(false); 61 60 mutate("publication-data"); 62 61 }}
+6 -2
app/lish/[did]/[publication]/page.tsx
··· 166 166 commentsCount={comments} 167 167 tags={tags} 168 168 postUrl={`${getPublicationURL(publication)}/${uri.rkey}`} 169 - showComments={record?.preferences?.showComments} 170 - showMentions={record?.preferences?.showMentions} 169 + showComments={ 170 + record?.preferences?.showComments !== false 171 + } 172 + showMentions={ 173 + record?.preferences?.showMentions !== false 174 + } 171 175 /> 172 176 </div> 173 177 </div>
+1 -1
app/lish/createPub/CreatePubForm.tsx
··· 57 57 showInDiscover, 58 58 showComments: true, 59 59 showMentions: true, 60 - showPrevNext: false, 60 + showPrevNext: true, 61 61 }, 62 62 }); 63 63
+2 -1
components/Blocks/Block.tsx
··· 32 32 import { HorizontalRule } from "./HorizontalRule"; 33 33 import { deepEquals } from "src/utils/deepEquals"; 34 34 import { isTextBlock } from "src/utils/isTextBlock"; 35 + import { focusPage } from "src/utils/focusPage"; 35 36 36 37 export type Block = { 37 38 factID: string; ··· 62 63 // Block handles all block level events like 63 64 // mouse events, keyboard events and longPress, and setting AreYouSure state 64 65 // and shared styling like padding and flex for list layouting 65 - 66 + let { rep } = useReplicache(); 66 67 let mouseHandlers = useBlockMouseHandlers(props); 67 68 let handleDrop = useHandleDrop({ 68 69 parent: props.parent,
+12
components/Blocks/useBlockMouseHandlers.ts
··· 8 8 import { getBlocksWithType } from "src/hooks/queries/useBlocks"; 9 9 import { focusBlock } from "src/utils/focusBlock"; 10 10 import { useIsMobile } from "src/hooks/isMobile"; 11 + import { scrollIntoViewIfNeeded } from "src/utils/scrollIntoViewIfNeeded"; 12 + import { elementId } from "src/utils/elementId"; 11 13 12 14 let debounce: number | null = null; 13 15 export function useBlockMouseHandlers(props: Block) { ··· 39 41 parent: props.parent, 40 42 }); 41 43 useUIState.getState().setSelectedBlock(props); 44 + 45 + // scroll to the page containing the block, if offscreen 46 + let parentPage = elementId.page(props.parent).container; 47 + setTimeout(() => { 48 + scrollIntoViewIfNeeded( 49 + document.getElementById(parentPage), 50 + false, 51 + "smooth", 52 + ); 53 + }, 50); 42 54 } 43 55 }, 44 56 [props, entity_set.permissions.write, isMobile],
+2 -2
components/Canvas.tsx
··· 165 165 if (!pub || !pub.publications) return null; 166 166 167 167 if (!normalizedPublication) return null; 168 - let showComments = normalizedPublication.preferences?.showComments; 169 - let showMentions = normalizedPublication.preferences?.showMentions; 168 + let showComments = normalizedPublication.preferences?.showComments !== false; 169 + let showMentions = normalizedPublication.preferences?.showMentions !== false; 170 170 171 171 return ( 172 172 <div className="flex flex-row gap-3 items-center absolute top-6 right-3 sm:top-4 sm:right-4 bg-bg-page border-border-light rounded-md px-2 py-1 h-fit z-20">
+4 -4
components/InteractionsPreview.tsx
··· 13 13 commentsCount: number; 14 14 tags?: string[]; 15 15 postUrl: string; 16 - showComments: boolean | undefined; 17 - showMentions: boolean | undefined; 16 + showComments: boolean; 17 + showMentions: boolean; 18 18 19 19 share?: boolean; 20 20 }) => { 21 21 let smoker = useSmoker(); 22 22 let interactionsAvailable = 23 - (props.quotesCount > 0 && props.showMentions !== false) || 23 + (props.quotesCount > 0 && props.showMentions) || 24 24 (props.showComments !== false && props.commentsCount > 0); 25 25 26 26 const tagsCount = props.tags?.length || 0; ··· 38 38 </> 39 39 )} 40 40 41 - {props.showMentions === false || props.quotesCount === 0 ? null : ( 41 + {props.showMentions || props.quotesCount === 0 ? null : ( 42 42 <SpeedyLink 43 43 aria-label="Post quotes" 44 44 href={`${props.postUrl}?interactionDrawer=quotes`}
+4 -1
components/Pages/PublicationMetadata.tsx
··· 113 113 {tags && ( 114 114 <> 115 115 <AddTags /> 116 - <Separator classname="h-4!" /> 116 + {pubRecord?.preferences?.showMentions || 117 + pubRecord?.preferences?.showComments ? ( 118 + <Separator classname="h-4!" /> 119 + ) : null} 117 120 </> 118 121 )} 119 122 {normalizedPublication?.preferences?.showMentions && (
+2 -2
components/PostListing.tsx
··· 104 104 quotesCount={quotes} 105 105 commentsCount={comments} 106 106 tags={tags} 107 - showComments={pubRecord?.preferences?.showComments} 108 - showMentions={pubRecord?.preferences?.showMentions} 107 + showComments={pubRecord?.preferences?.showComments !== false} 108 + showMentions={pubRecord?.preferences?.showMentions !== false} 109 109 share 110 110 /> 111 111 </div>
+33 -27
components/ProfilePopover.tsx
··· 7 7 import { SpeedyLink } from "./SpeedyLink"; 8 8 import { Tooltip } from "./Tooltip"; 9 9 import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 10 + import { BlueskyTiny } from "./Icons/BlueskyTiny"; 11 + import { ArrowRightTiny } from "./Icons/ArrowRightTiny"; 10 12 11 13 export const ProfilePopover = (props: { 12 14 trigger: React.ReactNode; ··· 27 29 ); 28 30 29 31 return ( 30 - <Tooltip 32 + <Popover 31 33 className="max-w-sm p-0! text-center" 32 - asChild 33 34 trigger={ 34 - <a 35 + <div 35 36 className="no-underline" 36 - href={`https://leaflet.pub/p/${props.didOrHandle}`} 37 - target="_blank" 38 37 onPointerEnter={(e) => { 39 38 if (hoverTimeout.current) { 40 39 window.clearTimeout(hoverTimeout.current); ··· 53 52 }} 54 53 > 55 54 {props.trigger} 56 - </a> 55 + </div> 57 56 } 58 57 onOpenChange={setIsOpen} 59 58 > ··· 66 65 publications={data.publications} 67 66 popover 68 67 /> 69 - <KnownFollowers viewer={data.profile.viewer} did={data.profile.did} /> 68 + 69 + <ProfileLinks handle={data.profile.handle} /> 70 70 </div> 71 71 ) : ( 72 - <div className="text-secondary py-2 px-4">Profile not found</div> 72 + <div className="text-secondary py-2 px-4">No profile found...</div> 73 73 )} 74 - </Tooltip> 74 + </Popover> 75 75 ); 76 76 }; 77 77 78 - let KnownFollowers = (props: { 79 - viewer: ProfileViewDetailed["viewer"]; 80 - did: string; 81 - }) => { 82 - if (!props.viewer?.knownFollowers) return null; 83 - let count = props.viewer.knownFollowers.count; 78 + const ProfileLinks = (props: { handle: string }) => { 79 + let linkClassName = 80 + "flex gap-1.5 text-tertiary items-center border border-transparent px-1 rounded-md hover:bg-[var(--accent-light)] hover:border-accent-contrast hover:text-accent-contrast no-underline hover:no-underline"; 84 81 return ( 85 - <> 86 - <hr className="border-border" /> 87 - Followed by{" "} 88 - <a 89 - className="hover:underline" 90 - href={`https://bsky.social/profile/${props.did}/known-followers`} 91 - target="_blank" 92 - > 93 - {props.viewer?.knownFollowers?.followers[0]?.displayName}{" "} 94 - {count > 1 ? `and ${count - 1} other${count > 2 ? "s" : ""}` : ""} 95 - </a> 96 - </> 82 + <div className="w-full flex-col"> 83 + <hr className="border-border-light mt-3" /> 84 + <div className="flex gap-2 justify-between sm:px-4 px-3 py-2"> 85 + <div className="flex gap-2"> 86 + <a 87 + href={`https://bsky.app/profile/${props.handle}`} 88 + target="_blank" 89 + className={linkClassName} 90 + > 91 + <BlueskyTiny /> 92 + Bluesky 93 + </a> 94 + </div> 95 + <SpeedyLink 96 + href={`https://leaflet.pub/p/${props.handle}`} 97 + className={linkClassName} 98 + > 99 + Full profile <ArrowRightTiny /> 100 + </SpeedyLink> 101 + </div> 102 + </div> 97 103 ); 98 104 };
+2 -2
components/ThemeManager/Pickers/PageWidthSetter.tsx
··· 89 89 <div 90 90 className={`w-full cursor-pointer ${selectedPreset === "default" ? "text-[#595959]" : "text-[#969696]"}`} 91 91 > 92 - default (624px) 92 + default ({defaultPreset}px) 93 93 </div> 94 94 </Radio> 95 95 </label> ··· 111 111 <div 112 112 className={`w-full cursor-pointer ${selectedPreset === "wide" ? "text-[#595959]" : "text-[#969696]"}`} 113 113 > 114 - wide (756px) 114 + wide ({widePreset}px) 115 115 </div> 116 116 </Radio> 117 117 </label>
+2 -2
components/ThemeManager/PubPickers/PubPageWidthSetter.tsx
··· 76 76 <div 77 77 className={`w-full cursor-pointer ${selectedPreset === "default" ? "text-[#595959]" : "text-[#969696]"}`} 78 78 > 79 - default (624px) 79 + default ({defaultPreset}px) 80 80 </div> 81 81 </Radio> 82 82 </label> ··· 98 98 <div 99 99 className={`w-full cursor-pointer ${selectedPreset === "wide" ? "text-[#595959]" : "text-[#969696]"}`} 100 100 > 101 - wide (756px) 101 + wide ({widePreset}px) 102 102 </div> 103 103 </Radio> 104 104 </label>
+1 -1
lexicons/api/lexicons.ts
··· 1841 1841 }, 1842 1842 showPrevNext: { 1843 1843 type: 'boolean', 1844 - default: false, 1844 + default: true, 1845 1845 }, 1846 1846 }, 1847 1847 },
+1 -1
lexicons/pub/leaflet/publication.json
··· 58 58 }, 59 59 "showPrevNext": { 60 60 "type": "boolean", 61 - "default": false 61 + "default": true 62 62 } 63 63 } 64 64 },
+1 -1
lexicons/src/publication.ts
··· 28 28 showInDiscover: { type: "boolean", default: true }, 29 29 showComments: { type: "boolean", default: true }, 30 30 showMentions: { type: "boolean", default: true }, 31 - showPrevNext: { type: "boolean", default: false }, 31 + showPrevNext: { type: "boolean", default: true }, 32 32 }, 33 33 }, 34 34 theme: {