a tool for shared writing and social publishing
0

Configure Feed

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

combine comments and bsky mentions in all places

celine (May 29, 2026, 10:00 PM EDT) 41277c7f 1203ffdb

+77 -114
+1 -1
app/(app)/lish/[did]/[publication]/[rkey]/Blocks/PublishBskyPostBlock.tsx
··· 33 33 avatarSize="large" 34 34 quoteEnabled 35 35 replyEnabled 36 - className="text-sm text-secondary block-border sm:px-3 sm:py-2 px-2 py-1 bg-bg-page mb-2 hover:border-accent-contrast!" 36 + className="publishedBskyPostBlock text-sm text-secondary block-border sm:px-3 sm:py-2 px-2 py-1 bg-bg-page mb-2 hover:border-accent-contrast!" 37 37 clientHost={props.clientHost} 38 38 /> 39 39 );
+11 -28
app/(app)/lish/[did]/[publication]/[rkey]/Blocks/PublishedPageBlock.tsx
··· 24 24 useInteractionState, 25 25 } from "../Interactions/Interactions"; 26 26 import { CommentTiny } from "components/Icons/CommentTiny"; 27 - import { QuoteTiny } from "components/Icons/QuoteTiny"; 28 27 import { CanvasBackgroundPattern } from "components/Canvas"; 29 28 30 29 export function PublishedPageLinkBlock(props: { ··· 213 212 214 213 let { drawerOpen, drawer, pageId } = useInteractionState(document_uri); 215 214 215 + let defaultDrawer: "comments" | "quotes" = comments > 0 ? "comments" : "quotes"; 216 + 216 217 return ( 217 218 <div 218 219 className={`flex gap-2 text-tertiary text-sm absolute bottom-2 bg-bg-page`} 219 220 > 220 - {quotes > 0 && ( 221 - <button 222 - className={`flex gap-1 items-center`} 223 - onClick={(e) => { 224 - e.preventDefault(); 225 - e.stopPropagation(); 226 - openPage( 227 - props.parentPageId 228 - ? { type: "doc", id: props.parentPageId } 229 - : undefined, 230 - { type: "doc", id: props.pageId }, 231 - { scrollIntoView: false }, 232 - ); 233 - if (!drawerOpen || drawer !== "quotes") 234 - openInteractionDrawer("quotes", document_uri, props.pageId); 235 - else setInteractionState(document_uri, { drawerOpen: false }); 236 - }} 237 - > 238 - <span className="sr-only">Page quotes</span> 239 - <QuoteTiny aria-hidden /> {quotes}{" "} 240 - </button> 241 - )} 242 - {comments > 0 && ( 221 + {quotes + comments > 0 && ( 243 222 <button 244 223 className={`flex gap-1 items-center`} 245 224 onClick={(e) => { ··· 252 231 { type: "doc", id: props.pageId }, 253 232 { scrollIntoView: false }, 254 233 ); 255 - if (!drawerOpen || drawer !== "comments" || pageId !== props.pageId) 256 - openInteractionDrawer("comments", document_uri, props.pageId); 234 + if ( 235 + !drawerOpen || 236 + drawer !== defaultDrawer || 237 + pageId !== props.pageId 238 + ) 239 + openInteractionDrawer(defaultDrawer, document_uri, props.pageId); 257 240 else setInteractionState(document_uri, { drawerOpen: false }); 258 241 }} 259 242 > 260 - <span className="sr-only">Page comments</span> 261 - <CommentTiny aria-hidden /> {comments}{" "} 243 + <span className="sr-only">Page discussions</span> 244 + <CommentTiny aria-hidden /> {comments + quotes}{" "} 262 245 </button> 263 246 )} 264 247 </div>
+13 -33
app/(app)/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx
··· 4 4 import { BlueskyRichText } from "components/Blocks/BlueskyPostBlock/BlueskyRichText"; 5 5 import { BlueskyTiny } from "components/Icons/BlueskyTiny"; 6 6 import { CommentTiny } from "components/Icons/CommentTiny"; 7 - import { QuoteTiny } from "components/Icons/QuoteTiny"; 8 7 import { Separator } from "components/Layout"; 9 8 import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 10 9 import { useHasPageLoaded } from "components/InitialPageLoadProvider"; 11 10 import { OpenPage, openPage } from "./postPageState"; 12 - import { ThreadLink, QuotesLink } from "./PostLinks"; 11 + import { ThreadLink } from "./PostLinks"; 13 12 import { BlueskyLinkTiny } from "components/Icons/BlueskyLinkTiny"; 14 13 import { Avatar } from "components/Avatar"; 15 14 import { timeAgo } from "src/utils/timeAgo"; ··· 285 284 showBlueskyLink: boolean; 286 285 url: string; 287 286 }) { 288 - const replyContent = props.post.replyCount != null && 289 - props.post.replyCount > 0 && ( 290 - <div className="postRepliesCount flex items-center gap-1 text-xs"> 291 - <CommentTiny /> 292 - {props.post.replyCount} 293 - </div> 294 - ); 287 + const total = (props.post.replyCount ?? 0) + (props.post.quoteCount ?? 0); 295 288 296 - const quoteContent = props.post.quoteCount != null && 297 - props.post.quoteCount > 0 && ( 298 - <div className="postQuoteCount flex items-center gap-1 text-xs"> 299 - <QuoteTiny /> 300 - {props.post.quoteCount} 301 - </div> 302 - ); 289 + const countContent = total > 0 && ( 290 + <div className="postDiscussionsCount flex items-center gap-1 text-xs"> 291 + <CommentTiny /> 292 + {total} 293 + </div> 294 + ); 303 295 304 296 return ( 305 297 <div className="postCounts flex gap-2 items-center w-full text-tertiary mb-1"> 306 - {replyContent && 307 - (props.replyEnabled ? ( 298 + {countContent && 299 + (props.replyEnabled || props.quoteEnabled ? ( 308 300 <ThreadLink 309 301 postUri={props.post.uri} 310 302 parent={props.parent} 311 - className="relative postRepliesLink hover:text-accent-contrast" 303 + className="relative postDiscussionsLink hover:text-accent-contrast" 312 304 onClick={props.replyOnClick} 313 305 > 314 - {replyContent} 306 + {countContent} 315 307 </ThreadLink> 316 308 ) : ( 317 - replyContent 318 - ))} 319 - {quoteContent && 320 - (props.quoteEnabled ? ( 321 - <QuotesLink 322 - postUri={props.post.uri} 323 - parent={props.parent} 324 - className="relative hover:text-accent-contrast" 325 - > 326 - {quoteContent} 327 - </QuotesLink> 328 - ) : ( 329 - quoteContent 309 + countContent 330 310 ))} 331 311 </div> 332 312 );
+1 -1
app/(app)/lish/[did]/[publication]/[rkey]/Interactions/Comments/index.tsx
··· 138 138 size={"medium"} 139 139 /> 140 140 141 - <div className="grow flex flex-col pt-1"> 141 + <div className="min-w-0 w-full grow flex flex-col pt-1"> 142 142 <PostInfo 143 143 displayName={props.profile?.displayName} 144 144 handle={props.profile?.handle || ""}
+24 -5
app/(app)/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx
··· 10 10 import { decodeQuotePosition } from "../quotePosition"; 11 11 import { CloseTiny } from "components/Icons/CloseTiny"; 12 12 import { ToggleGroup } from "components/ToggleGroup"; 13 + import { useDocument } from "contexts/DocumentContext"; 13 14 14 15 export const InteractionDrawer = (props: { 15 16 showPageBackground: boolean | undefined; ··· 20 21 pageId?: string; 21 22 }) => { 22 23 let drawer = useDrawerOpen(props.document_uri); 24 + let { commentsCount } = useDocument(); 23 25 if (!drawer) return null; 24 26 25 27 const filteredQuotesAndMentions = props.quotesAndMentions.filter((q) => { ··· 57 59 <ToggleGroup 58 60 fullWidth 59 61 value={activeTab} 60 - onChange={(value) => 61 - setInteractionState(props.document_uri, { drawer: value }) 62 - } 62 + onChange={(value, e) => { 63 + e?.preventDefault(); 64 + setInteractionState(props.document_uri, { drawer: value }); 65 + }} 63 66 options={[ 64 - { value: "comments", label: "Comments" }, 65 - { value: "quotes", label: "Bluesky Mentions" }, 67 + { 68 + value: "comments", 69 + label: 70 + commentsCount > 0 71 + ? `Comments (${commentsCount})` 72 + : "Comments", 73 + }, 74 + { 75 + value: "quotes", 76 + label: ( 77 + <div> 78 + Bluesky{" "} 79 + <span className="hidden sm:inline">Mentions</span>{" "} 80 + {filteredQuotesAndMentions.length > 0 && 81 + `(${filteredQuotesAndMentions.length})`} 82 + </div> 83 + ), 84 + }, 66 85 ]} 67 86 /> 68 87 ) : (
+1 -1
app/(app)/lish/[did]/[publication]/[rkey]/ThreadPage.tsx
··· 223 223 224 224 {/* Replies */} 225 225 {post.replies && post.replies.length > 0 && ( 226 - <div className="threadReplies flex flex-col mt-4 pt-4 border-t border-border-light w-full"> 226 + <div className="threadReplies flex flex-col mt-4 pt-2 border-t border-border-light w-full"> 227 227 <Replies 228 228 replies={post.replies as any[]} 229 229 pageUri={post.post.uri}
+1 -7
components/Canvas.tsx
··· 18 18 import { Popover } from "./Popover"; 19 19 import { Separator } from "./Layout"; 20 20 import { CommentTiny } from "./Icons/CommentTiny"; 21 - import { QuoteTiny } from "./Icons/QuoteTiny"; 22 21 import { AddTags, PublicationMetadata } from "./Pages/PublicationMetadata"; 23 22 import { useLeafletPublicationData } from "./PageSWRDataProvider"; 24 23 import { useHandleCanvasDrop } from "./Blocks/useHandleCanvasDrop"; ··· 192 191 <RecommendTinyEmpty className="text-border" /> — 193 192 </div> 194 193 )} 195 - {showComments && ( 194 + {(showComments || showMentions) && ( 196 195 <div className="flex gap-1 text-tertiary items-center"> 197 196 <CommentTiny className="text-border" /> — 198 - </div> 199 - )} 200 - {showMentions && ( 201 - <div className="flex gap-1 text-tertiary items-center"> 202 - <QuoteTiny className="text-border" /> — 203 197 </div> 204 198 )} 205 199
+11 -16
components/InteractionsPreview.tsx
··· 1 1 "use client"; 2 2 import { Separator } from "./Layout"; 3 3 import { CommentTiny } from "./Icons/CommentTiny"; 4 - import { QuoteTiny } from "./Icons/QuoteTiny"; 5 4 import { useSmoker } from "./Toast"; 6 5 import { Tag } from "./Tags"; 7 6 import { Popover } from "./Popover"; ··· 23 22 share?: boolean; 24 23 }) => { 25 24 let smoker = useSmoker(); 25 + let commentsAvailable = props.showComments !== false && props.commentsCount > 0; 26 + let mentionsAvailable = props.showMentions && props.quotesCount > 0; 27 + let discussionsAvailable = commentsAvailable || mentionsAvailable; 28 + let defaultDrawer: "comments" | "quotes" = commentsAvailable 29 + ? "comments" 30 + : "quotes"; 26 31 let interactionsAvailable = 27 - (props.quotesCount > 0 && props.showMentions) || 28 - (props.showComments !== false && props.commentsCount > 0) || 32 + discussionsAvailable || 29 33 (props.showRecommends !== false && props.recommendsCount > 0); 30 34 31 35 const tagsCount = props.tags?.length || 0; ··· 39 43 /> 40 44 )} 41 45 42 - {!props.showMentions || props.quotesCount === 0 ? null : ( 46 + {!discussionsAvailable ? null : ( 43 47 <SpeedyLink 44 - aria-label="Post quotes" 45 - href={`${props.postUrl}?interactionDrawer=quotes`} 48 + aria-label="Post discussions" 49 + href={`${props.postUrl}?interactionDrawer=${defaultDrawer}`} 46 50 className="relative flex flex-row gap-1 text-sm items-center hover:text-accent-contrast hover:no-underline! text-tertiary" 47 51 > 48 - <QuoteTiny /> {props.quotesCount} 49 - </SpeedyLink> 50 - )} 51 - {!props.showComments || props.commentsCount === 0 ? null : ( 52 - <SpeedyLink 53 - aria-label="Post comments" 54 - href={`${props.postUrl}?interactionDrawer=comments`} 55 - className="relative flex flex-row gap-1 text-sm items-center hover:text-accent-contrast hover:no-underline! text-tertiary" 56 - > 57 - <CommentTiny /> {props.commentsCount} 52 + <CommentTiny /> {props.commentsCount + props.quotesCount} 58 53 </SpeedyLink> 59 54 )} 60 55 {tagsCount === 0 ? null : (
+2 -7
components/Pages/PublicationMetadata.tsx
··· 13 13 import { useEntitySetContext } from "components/EntitySetProvider"; 14 14 import { timeAgo } from "src/utils/timeAgo"; 15 15 import { CommentTiny } from "components/Icons/CommentTiny"; 16 - import { QuoteTiny } from "components/Icons/QuoteTiny"; 17 16 import { TagTiny } from "components/Icons/TagTiny"; 18 17 import { Popover } from "components/Popover"; 19 18 import { TagSelector } from "components/Tags"; ··· 139 138 </div> 140 139 )} 141 140 142 - {merged.showMentions !== false && ( 143 - <div className="flex gap-1 items-center"> 144 - <QuoteTiny />— 145 - </div> 146 - )} 147 - {merged.showComments !== false && ( 141 + {(merged.showComments !== false || 142 + merged.showMentions !== false) && ( 148 143 <div className="flex gap-1 items-center"> 149 144 <CommentTiny />— 150 145 </div>
+11 -14
components/PostListing.tsx
··· 17 17 import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 18 18 import { useSmoker } from "./Toast"; 19 19 import { CommentTiny } from "./Icons/CommentTiny"; 20 - import { QuoteTiny } from "./Icons/QuoteTiny"; 21 20 import { ShareTiny } from "./Icons/ShareTiny"; 22 21 import { useSelectedPostListing } from "src/useSelectedPostState"; 23 22 import { mergePreferences } from "src/utils/mergePreferences"; ··· 249 248 }); 250 249 }; 251 250 251 + let commentsAvailable = props.showComments && props.commentsCount > 0; 252 + let mentionsAvailable = props.showMentions && props.quotesCount > 0; 253 + let discussionsAvailable = commentsAvailable || mentionsAvailable; 254 + let defaultDrawer: "comments" | "quotes" = commentsAvailable 255 + ? "comments" 256 + : "quotes"; 257 + 252 258 return ( 253 259 <div 254 260 className={`flex gap-2 text-tertiary text-sm items-center justify-between px-1`} ··· 258 264 documentUri={props.documentUri} 259 265 recommendsCount={props.recommendsCount} 260 266 /> 261 - {!props.showMentions || props.quotesCount === 0 ? null : ( 262 - <button 263 - aria-label="Post quotes" 264 - onClick={() => selectPostListing("quotes")} 265 - className="relative flex flex-row gap-1 text-sm items-center hover:text-accent-contrast text-tertiary" 266 - > 267 - <QuoteTiny /> {props.quotesCount} 268 - </button> 269 - )} 270 - {!props.showComments || props.commentsCount === 0 ? null : ( 267 + {!discussionsAvailable ? null : ( 271 268 <button 272 - aria-label="Post comments" 273 - onClick={() => selectPostListing("comments")} 269 + aria-label="Post discussions" 270 + onClick={() => selectPostListing(defaultDrawer)} 274 271 className="relative flex flex-row gap-1 text-sm items-center hover:text-accent-contrast text-tertiary" 275 272 > 276 - <CommentTiny /> {props.commentsCount} 273 + <CommentTiny /> {props.commentsCount + props.quotesCount} 277 274 </button> 278 275 )} 279 276 </div>
+1 -1
components/ToggleGroup.tsx
··· 2 2 3 3 export function ToggleGroup<T extends string>(props: { 4 4 value: T; 5 - onChange: (value: T) => void; 5 + onChange: (value: T, e?: React.MouseEvent) => void; 6 6 options: { value: T; label: ReactNode }[]; 7 7 className?: string; 8 8 optionClassName?: string;