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

reorder bluesky icon to show on oldest parent rather than most recent child in threads

Changed files
+86 -12
lib
src
+77 -5
lib/components/BlueskyPost.tsx
··· 83 83 */ 84 84 authorHandle: string; 85 85 /** 86 + * The author's display name from their profile. 87 + */ 88 + authorDisplayName?: string; 89 + /** 86 90 * The DID that owns the post record. 87 91 */ 88 92 authorDid: string; ··· 191 195 const avatar = profile?.avatar; 192 196 const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined; 193 197 const avatarCid = avatarCdnUrl ? undefined : getAvatarCid(profile); 198 + const authorDisplayName = profile?.displayName; 194 199 195 200 const { 196 201 record: fetchedRecord, ··· 250 255 <Comp 251 256 {...props} 252 257 authorHandle={authorHandle} 258 + authorDisplayName={authorDisplayName} 253 259 authorDid={repoIdentifier} 254 260 avatarUrl={avatarUrl} 255 261 iconPlacement={iconPlacement} ··· 269 275 avatarCid, 270 276 avatarCdnUrl, 271 277 authorHandle, 278 + authorDisplayName, 272 279 iconPlacement, 273 280 showIcon, 274 281 atUri, 275 282 showParent, 276 283 ]); 277 284 285 + const WrappedWithoutIcon = useMemo(() => { 286 + const WrappedComponent: React.FC<{ 287 + record: FeedPostRecord; 288 + loading: boolean; 289 + error?: Error; 290 + }> = (props) => { 291 + const { url: avatarUrlFromBlob } = useBlob( 292 + repoIdentifier, 293 + avatarCid, 294 + ); 295 + const avatarUrl = avatarCdnUrl || avatarUrlFromBlob; 296 + return ( 297 + <Comp 298 + {...props} 299 + authorHandle={authorHandle} 300 + authorDisplayName={authorDisplayName} 301 + authorDid={repoIdentifier} 302 + avatarUrl={avatarUrl} 303 + iconPlacement={iconPlacement} 304 + showIcon={false} 305 + atUri={atUri} 306 + isInThread 307 + threadDepth={showParent ? 1 : 0} 308 + showThreadBorder={!showParent && !!props.record?.reply?.parent} 309 + /> 310 + ); 311 + }; 312 + WrappedComponent.displayName = "BlueskyPostWrappedRendererWithoutIcon"; 313 + return WrappedComponent; 314 + }, [ 315 + Comp, 316 + repoIdentifier, 317 + avatarCid, 318 + avatarCdnUrl, 319 + authorHandle, 320 + authorDisplayName, 321 + iconPlacement, 322 + atUri, 323 + showParent, 324 + ]); 325 + 278 326 if (!displayHandle && resolvingIdentity) { 279 327 return <div style={{ padding: 8 }}>Resolving handle…</div>; 280 328 } ··· 310 358 ); 311 359 }; 312 360 361 + const renderMainPostWithoutIcon = (mainRecord?: FeedPostRecord) => { 362 + if (mainRecord !== undefined) { 363 + return ( 364 + <AtProtoRecord<FeedPostRecord> 365 + record={mainRecord} 366 + renderer={WrappedWithoutIcon} 367 + fallback={fallback} 368 + loadingIndicator={loadingIndicator} 369 + /> 370 + ); 371 + } 372 + 373 + return ( 374 + <AtProtoRecord<FeedPostRecord> 375 + did={repoIdentifier} 376 + collection={BLUESKY_POST_COLLECTION} 377 + rkey={rkey} 378 + renderer={WrappedWithoutIcon} 379 + fallback={fallback} 380 + loadingIndicator={loadingIndicator} 381 + /> 382 + ); 383 + }; 384 + 313 385 if (showParent) { 314 386 if (currentLoading || (parentLoading && !parentRecord)) { 315 387 return ( ··· 349 421 record={parentRecord} 350 422 showParent={true} 351 423 recursiveParent={true} 352 - showIcon={false} 353 - iconPlacement="cardBottomRight" 424 + showIcon={showIcon} 425 + iconPlacement={iconPlacement} 354 426 /> 355 427 ) : ( 356 428 <BlueskyPost 357 429 did={parentDid} 358 430 rkey={parentRkey} 359 431 record={parentRecord} 360 - showIcon={false} 361 - iconPlacement="cardBottomRight" 432 + showIcon={showIcon} 433 + iconPlacement={iconPlacement} 362 434 /> 363 435 )} 364 436 </div> 365 437 366 438 <div style={replyPostStyle}> 367 - {renderMainPost(record || currentRecord)} 439 + {renderMainPostWithoutIcon(record || currentRecord)} 368 440 </div> 369 441 </div> 370 442 );
+7 -5
lib/renderers/BlueskyPostRenderer.tsx
··· 166 166 gap: inline ? 8 : 0, 167 167 }} 168 168 > 169 - <strong style={{ fontSize: 14 }}>{primaryName}</strong> 170 - {authorDisplayName && authorHandle && ( 169 + <strong style={{ fontSize: 14 }}>{authorDisplayName || primaryName}</strong> 170 + {authorHandle && ( 171 171 <span 172 172 style={{ 173 173 ...baseStyles.handle, ··· 254 254 ))} 255 255 </div> 256 256 )} 257 + {resolvedEmbed && ( 258 + <div style={baseStyles.embedContainer}>{resolvedEmbed}</div> 259 + )} 257 260 <div style={baseStyles.timestampRow}> 258 261 <time 259 262 style={{ ··· 285 288 </span> 286 289 )} 287 290 </div> 288 - {resolvedEmbed && ( 289 - <div style={baseStyles.embedContainer}>{resolvedEmbed}</div> 290 - )} 291 291 </div> 292 292 ); 293 293 ··· 419 419 marginTop: 12, 420 420 padding: 8, 421 421 borderRadius: 12, 422 + border: `1px solid var(--atproto-color-border)`, 423 + background: `var(--atproto-color-bg-elevated)`, 422 424 display: "flex", 423 425 flexDirection: "column", 424 426 gap: 8,
+2 -2
src/App.tsx
··· 329 329 Reply Post Demo 330 330 </h3> 331 331 <BlueskyPost 332 - did="nekomimi.pet" 333 - rkey="3m36jkng6nk22" 332 + did="did:plc:xwhsmuozq3mlsp56dyd7copv" 333 + rkey="3m3je5ydg4s2o" 334 334 showParent={true} 335 335 recursiveParent={true} 336 336 />