A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.

stylize blueskypostlist better

Changed files
+95 -38
lib
components
+95 -38
lib/components/BlueskyPostList.tsx
··· 117 record={record.value} 118 rkey={record.rkey} 119 did={actorPath} 120 reason={record.reason} 121 replyParent={record.replyParent} 122 hasDivider={idx < records.length - 1} ··· 203 record: FeedPostRecord; 204 rkey: string; 205 did: string; 206 reason?: AuthorFeedReason; 207 replyParent?: ReplyParentInfo; 208 hasDivider: boolean; ··· 212 record, 213 rkey, 214 did, 215 reason, 216 replyParent, 217 hasDivider, ··· 224 const absolute = record.createdAt 225 ? new Date(record.createdAt).toLocaleString() 226 : undefined; 227 - const href = `${blueskyAppBaseUrl}/profile/${did}/post/${rkey}`; 228 const repostLabel = 229 reason?.$type === "app.bsky.feed.defs#reasonRepost" 230 - ? `${formatActor(reason.by) ?? "Someone"} reposted` 231 : undefined; 232 const parentUri = replyParent?.uri ?? record.reply?.parent?.uri; 233 const parentDid = ··· 236 const { handle: resolvedReplyHandle } = useDidResolution( 237 replyParent?.author?.handle ? undefined : parentDid, 238 ); 239 - const replyLabel = formatReplyTarget( 240 parentUri, 241 replyParent, 242 resolvedReplyHandle, 243 ); 244 245 const postPreview = text.slice(0, 100); 246 const ariaLabel = text 247 ? `Post by ${did}: ${postPreview}${text.length > 100 ? '...' : ''}` 248 : `Post by ${did}`; 249 250 return ( 251 - <a 252 - href={href} 253 - target="_blank" 254 - rel="noopener noreferrer" 255 - aria-label={ariaLabel} 256 style={{ 257 - ...listStyles.row, 258 - color: `var(--atproto-color-text)`, 259 borderBottom: hasDivider 260 ? `1px solid var(--atproto-color-border)` 261 : "none", 262 }} 263 > 264 {repostLabel && ( 265 - <span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}> 266 {repostLabel} 267 - </span> 268 )} 269 - {replyLabel && ( 270 - <span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}> 271 - {replyLabel} 272 - </span> 273 )} 274 - {relative && ( 275 <span 276 style={{ ...listStyles.rowTime, color: `var(--atproto-color-text-secondary)` }} 277 title={absolute} ··· 279 {relative} 280 </span> 281 )} 282 - {text && ( 283 - <p style={{ ...listStyles.rowBody, color: `var(--atproto-color-text)` }}> 284 - {text} 285 - </p> 286 - )} 287 - {!text && ( 288 - <p 289 - style={{ 290 - ...listStyles.rowBody, 291 - color: `var(--atproto-color-text)`, 292 - fontStyle: "italic", 293 - }} 294 - > 295 - No text content. 296 - </p> 297 - )} 298 - </a> 299 ); 300 }; 301 ··· 388 fontSize: 13, 389 textAlign: "center", 390 } satisfies React.CSSProperties, 391 - row: { 392 padding: "18px", 393 - textDecoration: "none", 394 display: "flex", 395 flexDirection: "column", 396 gap: 6, 397 transition: "background-color 120ms ease", 398 } satisfies React.CSSProperties, 399 rowHeader: { 400 display: "flex", 401 gap: 6, ··· 496 const directHandle = feedParent?.author?.handle; 497 const handle = directHandle ?? resolvedHandle; 498 if (handle) { 499 - return `Replying to @${handle}`; 500 } 501 const parentDid = feedParent?.author?.did; 502 const targetUri = feedParent?.uri ?? parentUri; ··· 504 const parsed = parseAtUri(targetUri); 505 const did = parentDid ?? parsed?.did; 506 if (!did) return undefined; 507 - return `Replying to @${formatDid(did)}`; 508 }
··· 117 record={record.value} 118 rkey={record.rkey} 119 did={actorPath} 120 + uri={record.uri} 121 reason={record.reason} 122 replyParent={record.replyParent} 123 hasDivider={idx < records.length - 1} ··· 204 record: FeedPostRecord; 205 rkey: string; 206 did: string; 207 + uri?: string; 208 reason?: AuthorFeedReason; 209 replyParent?: ReplyParentInfo; 210 hasDivider: boolean; ··· 214 record, 215 rkey, 216 did, 217 + uri, 218 reason, 219 replyParent, 220 hasDivider, ··· 227 const absolute = record.createdAt 228 ? new Date(record.createdAt).toLocaleString() 229 : undefined; 230 + 231 + // Parse the URI to get the actual post's DID and rkey 232 + // This handles reposts correctly by linking to the original post 233 + const parsedUri = uri ? parseAtUri(uri) : undefined; 234 + const postDid = parsedUri?.did ?? did; 235 + const postRkey = parsedUri?.rkey ?? rkey; 236 + const href = `${blueskyAppBaseUrl}/profile/${postDid}/post/${postRkey}`; 237 + 238 + // Resolve the original post author's handle for reposts 239 + const { handle: originalAuthorHandle } = useDidResolution( 240 + reason?.$type === "app.bsky.feed.defs#reasonRepost" ? postDid : undefined, 241 + ); 242 + 243 const repostLabel = 244 reason?.$type === "app.bsky.feed.defs#reasonRepost" 245 + ? `${formatActor(reason.by) ?? "Someone"} reposted @${originalAuthorHandle ?? formatDid(postDid)}` 246 : undefined; 247 const parentUri = replyParent?.uri ?? record.reply?.parent?.uri; 248 const parentDid = ··· 251 const { handle: resolvedReplyHandle } = useDidResolution( 252 replyParent?.author?.handle ? undefined : parentDid, 253 ); 254 + const replyTarget = formatReplyTarget( 255 parentUri, 256 replyParent, 257 resolvedReplyHandle, 258 ); 259 260 + const isReply = !!replyTarget; 261 + 262 const postPreview = text.slice(0, 100); 263 const ariaLabel = text 264 ? `Post by ${did}: ${postPreview}${text.length > 100 ? '...' : ''}` 265 : `Post by ${did}`; 266 267 return ( 268 + <div 269 style={{ 270 + ...listStyles.rowContainer, 271 borderBottom: hasDivider 272 ? `1px solid var(--atproto-color-border)` 273 : "none", 274 + borderLeft: isReply 275 + ? `3px solid #1185FE` 276 + : "3px solid transparent", 277 }} 278 > 279 {repostLabel && ( 280 + <div style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}> 281 {repostLabel} 282 + </div> 283 )} 284 + {isReply && ( 285 + <div style={listStyles.replyHeader}> 286 + <span style={{ ...listStyles.replyArrow, color: `#1185FE` }}> 287 + 288 + </span> 289 + <span style={{ ...listStyles.replyText, color: `var(--atproto-color-text-secondary)` }}> 290 + replying to {replyTarget} 291 + </span> 292 + {relative && ( 293 + <span 294 + style={{ ...listStyles.rowTime, color: `var(--atproto-color-text-secondary)`, marginLeft: "auto" }} 295 + title={absolute} 296 + > 297 + {relative} 298 + </span> 299 + )} 300 + </div> 301 )} 302 + {!isReply && relative && ( 303 <span 304 style={{ ...listStyles.rowTime, color: `var(--atproto-color-text-secondary)` }} 305 title={absolute} ··· 307 {relative} 308 </span> 309 )} 310 + <a 311 + href={href} 312 + target="_blank" 313 + rel="noopener noreferrer" 314 + aria-label={ariaLabel} 315 + style={{ 316 + ...listStyles.rowLink, 317 + color: `var(--atproto-color-text)`, 318 + }} 319 + > 320 + {text && ( 321 + <p style={{ ...listStyles.rowBody, color: `var(--atproto-color-text)` }}> 322 + {text} 323 + </p> 324 + )} 325 + {!text && ( 326 + <p 327 + style={{ 328 + ...listStyles.rowBody, 329 + color: `var(--atproto-color-text)`, 330 + fontStyle: "italic", 331 + }} 332 + > 333 + No text content. 334 + </p> 335 + )} 336 + </a> 337 + </div> 338 ); 339 }; 340 ··· 427 fontSize: 13, 428 textAlign: "center", 429 } satisfies React.CSSProperties, 430 + rowContainer: { 431 padding: "18px", 432 display: "flex", 433 flexDirection: "column", 434 gap: 6, 435 transition: "background-color 120ms ease", 436 } satisfies React.CSSProperties, 437 + rowLink: { 438 + textDecoration: "none", 439 + display: "block", 440 + } satisfies React.CSSProperties, 441 + replyHeader: { 442 + display: "flex", 443 + alignItems: "center", 444 + gap: 6, 445 + fontSize: 12, 446 + fontWeight: 500, 447 + } satisfies React.CSSProperties, 448 + replyArrow: { 449 + fontSize: 14, 450 + fontWeight: 600, 451 + } satisfies React.CSSProperties, 452 + replyText: { 453 + fontSize: 12, 454 + fontWeight: 500, 455 + } satisfies React.CSSProperties, 456 rowHeader: { 457 display: "flex", 458 gap: 6, ··· 553 const directHandle = feedParent?.author?.handle; 554 const handle = directHandle ?? resolvedHandle; 555 if (handle) { 556 + return `@${handle}`; 557 } 558 const parentDid = feedParent?.author?.did; 559 const targetUri = feedParent?.uri ?? parentUri; ··· 561 const parsed = parseAtUri(targetUri); 562 const did = parentDid ?? parsed?.did; 563 if (!did) return undefined; 564 + return `@${formatDid(did)}`; 565 }