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