a tool for shared writing and social publishing

refactor the bskypostcontent to use booleans for reply and quote rather than functions, include its own reply line, some renaming of variables to be more consistant

+118 -100
+2
app/lish/[did]/[publication]/[rkey]/BlueskyQuotesPage.tsx
··· 92 92 parent={parent} 93 93 showEmbed={true} 94 94 showBlueskyLink={true} 95 + quoteEnabled 96 + replyEnabled 95 97 onEmbedClick={(e) => e.stopPropagation()} 96 98 className="relative py-2 px-2 hover:bg-bg-page rounded cursor-pointer" 97 99 />
+97 -83
app/lish/[did]/[publication]/[rkey]/BskyPostContent.tsx
··· 19 19 20 20 export function BskyPostContent(props: { 21 21 post: PostView; 22 - parent?: OpenPage; 22 + parent: OpenPage; 23 23 avatarSize?: "tiny" | "medium" | "large" | "giant"; 24 24 className?: string; 25 25 showEmbed?: boolean; ··· 50 50 const url = `https://bsky.app/profile/${post.author.handle}/post/${postId}`; 51 51 52 52 return ( 53 - <div className="bskyPost relative flex flex-col"> 54 - {replyLine && ( 55 - <div className="replyLine absolute top-0 bottom-0 shrink-0 w-6 bg-test"> 53 + <div className="bskyPost relative flex flex-col w-full"> 54 + <div className={`flex gap-2 text-left w-full ${props.className}`}> 55 + <div className="flex flex-col items-start shrink-0 w-fit "> 56 + {replyLine && ( 57 + <button 58 + onClick={(e) => { 59 + replyLine.onToggle(e); 60 + }} 61 + className="w-full h-2 shrink-0 flex place" 62 + aria-label="Toggle replies" 63 + > 64 + <div className="w-0.5 h-full bg-border-light mx-auto" /> 65 + </button> 66 + )} 67 + <Avatar 68 + src={post.author.avatar} 69 + displayName={post.author.displayName} 70 + size={props.avatarSize ? props.avatarSize : "medium"} 71 + /> 72 + {replyLine && ( 73 + <button 74 + onClick={(e) => { 75 + replyLine.onToggle(e); 76 + }} 77 + className="relative w-full grow flex " 78 + aria-label="Toggle replies" 79 + > 80 + <div className="w-0.5 h-full bg-border-light mx-auto" /> 81 + </button> 82 + )} 83 + </div> 84 + <div 85 + className={`flex flex-col w-full z-0 ${props.replyLine ? "mt-2" : ""}`} 86 + > 56 87 <button 57 - onClick={(e) => { 58 - e.preventDefault(); 59 - e.stopPropagation(); 60 - replyLine.onToggle(e); 61 - console.log("clicked"); 88 + className={`bskyPostTextContent flex flex-col grow min-w-0 mt-1 text-left`} 89 + onClick={() => { 90 + openPage(parent, { type: "thread", uri: post.uri }); 62 91 }} 63 - className="w-full h-full flex justify-center" 64 - aria-label="Toggle replies" 65 92 > 66 - <div className="w-0.5 h-full bg-border-light" /> 67 - </button> 68 - </div> 69 - )} 70 - <button 71 - className={`flex gap-2 text-left ${props.className}`} 72 - onClick={() => { 73 - openPage(parent, { type: "thread", uri: post.uri }); 74 - console.log("do this"); 75 - }} 76 - > 77 - <Avatar 78 - src={post.author.avatar} 79 - displayName={post.author.displayName} 80 - size={props.avatarSize ? props.avatarSize : "medium"} 81 - /> 82 - 83 - <div className={`bskyPostTextContent flex flex-col grow min-w-0 mt-1 `}> 84 - <div 85 - className={`flex justify-between items-center gap-2 leading-tight `} 86 - > 87 - <div className="flex gap-2 items-center"> 88 - <div className="font-bold text-secondary"> 89 - {post.author.displayName} 93 + <div 94 + className={`postInfo flex justify-between items-center gap-2 leading-tight `} 95 + > 96 + <div className="flex gap-2 items-center"> 97 + <div className="font-bold text-secondary"> 98 + {post.author.displayName} 99 + </div> 100 + <ProfilePopover 101 + trigger={ 102 + <div className="text-sm text-tertiary hover:underline"> 103 + @{post.author.handle} 104 + </div> 105 + } 106 + didOrHandle={post.author.handle} 107 + /> 90 108 </div> 91 - <ProfilePopover 92 - trigger={ 93 - <div className="text-sm text-tertiary hover:underline"> 94 - @{post.author.handle} 95 - </div> 96 - } 97 - didOrHandle={post.author.handle} 98 - /> 99 - </div> 100 - <div className="text-sm text-tertiary"> 101 - {timeAgo(record.createdAt, { compact: true })} 109 + <div className="text-sm text-tertiary"> 110 + {timeAgo(record.createdAt, { compact: true })} 111 + </div> 102 112 </div> 103 - </div> 104 113 105 - <div 106 - className={`flex flex-col gap-2 ${avatarSize === "large" ? "mt-0.5" : "mt-1"}`} 107 - > 108 - <div className="text-sm text-secondary"> 109 - <BlueskyRichText record={record} /> 110 - </div> 111 - {showEmbed && post.embed && ( 112 - <div onClick={onEmbedClick}> 113 - <BlueskyEmbed 114 - embed={post.embed} 115 - postUrl={url} 116 - className="text-sm" 117 - /> 114 + <div className={`postContent flex flex-col gap-2 mt-0.5`}> 115 + <div className="text-sm text-secondary"> 116 + <BlueskyRichText record={record} /> 118 117 </div> 119 - )} 120 - </div> 121 - 122 - <div className={`flex gap-2 items-center justify-between mt-2`}> 123 - <PostCounts 124 - post={post} 125 - parent={parent} 126 - replyEnabled={replyEnabled} 127 - replyOnClick={replyOnClick} 128 - quoteEnabled={quoteEnabled} 129 - showBlueskyLink={showBlueskyLink} 130 - url={url} 131 - /> 132 - <div className="flex gap-3 items-center"> 133 - {showBlueskyLink && ( 134 - <> 135 - <a className="text-tertiary" target="_blank" href={url}> 136 - <BlueskyLinkTiny /> 137 - </a> 138 - </> 118 + {showEmbed && post.embed && ( 119 + <div onClick={onEmbedClick}> 120 + <BlueskyEmbed 121 + embed={post.embed} 122 + postUrl={url} 123 + className="text-sm" 124 + /> 125 + </div> 139 126 )} 140 127 </div> 141 - </div> 128 + </button> 129 + {props.showBlueskyLink || 130 + (props.post.quoteCount && props.post.quoteCount > 0) || 131 + (props.post.replyCount && props.post.replyCount > 0) ? ( 132 + <div 133 + className={`postCountsAndLink flex gap-2 items-center justify-between mt-2`} 134 + > 135 + <PostCounts 136 + post={post} 137 + parent={parent} 138 + replyEnabled={replyEnabled} 139 + replyOnClick={replyOnClick} 140 + quoteEnabled={quoteEnabled} 141 + showBlueskyLink={showBlueskyLink} 142 + url={url} 143 + /> 144 + 145 + <div className="flex gap-3 items-center"> 146 + {showBlueskyLink && ( 147 + <> 148 + <a className="text-tertiary" target="_blank" href={url}> 149 + <BlueskyLinkTiny /> 150 + </a> 151 + </> 152 + )} 153 + </div> 154 + </div> 155 + ) : null} 142 156 </div> 143 - </button> 157 + </div> 144 158 </div> 145 159 ); 146 160 } ··· 171 185 ); 172 186 173 187 return ( 174 - <div className="postCounts flex gap-2 items-center"> 188 + <div className="postCounts flex gap-2 items-center w-full"> 175 189 {replyContent && 176 190 (props.replyEnabled ? ( 177 191 <ThreadLink
+2 -1
app/lish/[did]/[publication]/[rkey]/Interactions/Quotes.tsx
··· 143 143 showBlueskyLink={true} 144 144 showEmbed={true} 145 145 avatarSize="large" 146 - quoteEnabled={true} 146 + quoteEnabled 147 + replyEnabled 147 148 /> 148 149 ); 149 150 })}
+17 -16
app/lish/[did]/[publication]/[rkey]/ThreadPage.tsx
··· 60 60 Failed to load thread 61 61 </div> 62 62 ) : thread ? ( 63 - <ThreadContent thread={thread} parentUri={parentUri} /> 63 + <ThreadContent post={thread} parentUri={parentUri} /> 64 64 ) : null} 65 65 </div> 66 66 </PageWrapper> 67 67 ); 68 68 } 69 69 70 - function ThreadContent(props: { thread: ThreadType; parentUri: string }) { 71 - const { thread, parentUri: parentUri } = props; 70 + function ThreadContent(props: { post: ThreadType; parentUri: string }) { 71 + const { post, parentUri } = props; 72 72 const mainPostRef = useRef<HTMLDivElement>(null); 73 73 74 74 // Scroll the main post into view when the thread loads ··· 81 81 } 82 82 }, []); 83 83 84 - if (AppBskyFeedDefs.isNotFoundPost(thread)) { 84 + if (AppBskyFeedDefs.isNotFoundPost(post)) { 85 85 return <PostNotAvailable />; 86 86 } 87 87 88 - if (AppBskyFeedDefs.isBlockedPost(thread)) { 88 + if (AppBskyFeedDefs.isBlockedPost(post)) { 89 89 return ( 90 90 <div className="text-tertiary italic text-sm text-center py-8"> 91 91 This post is blocked ··· 93 93 ); 94 94 } 95 95 96 - if (!AppBskyFeedDefs.isThreadViewPost(thread)) { 96 + if (!AppBskyFeedDefs.isThreadViewPost(post)) { 97 97 return <PostNotAvailable />; 98 98 } 99 99 100 100 // Collect all parent posts in order (oldest first) 101 101 const parents: ThreadViewPost[] = []; 102 - let currentParent = thread.parent; 102 + let currentParent = post.parent; 103 103 while (currentParent && AppBskyFeedDefs.isThreadViewPost(currentParent)) { 104 104 parents.unshift(currentParent); 105 105 currentParent = currentParent.parent; ··· 122 122 {/* Main post */} 123 123 <div ref={mainPostRef}> 124 124 <ThreadPost 125 - post={thread} 125 + post={post} 126 126 isMainPost={true} 127 127 showReplyLine={false} 128 128 parentUri={parentUri} ··· 130 130 </div> 131 131 132 132 {/* Replies */} 133 - {thread.replies && thread.replies.length > 0 && ( 133 + {post.replies && post.replies.length > 0 && ( 134 134 <div className="threadReplies flex flex-col mt-2 pt-2 border-t border-border-light"> 135 135 <Replies 136 - replies={thread.replies as any[]} 137 - parentUri={parentUri} 136 + replies={post.replies as any[]} 137 + parentUri={post.post.uri} 138 138 depth={0} 139 - parentAuthorDid={thread.post.author.did} 139 + parentAuthorDid={post.post.author.did} 140 140 /> 141 141 </div> 142 142 )} ··· 166 166 showBlueskyLink={true} 167 167 showEmbed={true} 168 168 quoteEnabled 169 - replyEnabled 169 + replyEnabled={!isMainPost} 170 170 /> 171 171 </div> 172 172 ); ··· 269 269 const hasReplies = props.post.replies && props.post.replies.length > 0; 270 270 271 271 return ( 272 - <div className="threadReply relative flex flex-col"> 272 + <div 273 + className={`threadReply relative flex flex-col ${props.depth === 0 && "mb-2"}`} 274 + > 273 275 <BskyPostContent 274 276 post={postView} 275 277 parent={{ type: "thread", uri: parentUri }} ··· 280 282 ? { 281 283 onToggle: () => { 282 284 props.toggleCollapsed(props.parentUri); 283 - console.log("click click"); 284 285 }, 285 286 } 286 287 : undefined ··· 299 300 {!props.isCollapsed && ( 300 301 <div className="grow"> 301 302 <Replies 302 - parentUri={postView.uri} 303 + parentUri={parentUri} 303 304 replies={props.post.replies as any[]} 304 305 depth={props.depth + 1} 305 306 parentAuthorDid={props.post.post.author.did}