+95
-38
lib/components/BlueskyPostList.tsx
+95
-38
lib/components/BlueskyPostList.tsx
···
117
117
record={record.value}
118
118
rkey={record.rkey}
119
119
did={actorPath}
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
+
uri?: string;
206
208
reason?: AuthorFeedReason;
207
209
replyParent?: ReplyParentInfo;
208
210
hasDivider: boolean;
···
212
214
record,
213
215
rkey,
214
216
did,
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
-
const href = `${blueskyAppBaseUrl}/profile/${did}/post/${rkey}`;
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
+
228
243
const repostLabel =
229
244
reason?.$type === "app.bsky.feed.defs#reasonRepost"
230
-
? `${formatActor(reason.by) ?? "Someone"} reposted`
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
-
const replyLabel = formatReplyTarget(
254
+
const replyTarget = formatReplyTarget(
240
255
parentUri,
241
256
replyParent,
242
257
resolvedReplyHandle,
243
258
);
244
259
260
+
const isReply = !!replyTarget;
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
-
<a
252
-
href={href}
253
-
target="_blank"
254
-
rel="noopener noreferrer"
255
-
aria-label={ariaLabel}
268
+
<div
256
269
style={{
257
-
...listStyles.row,
258
-
color: `var(--atproto-color-text)`,
270
+
...listStyles.rowContainer,
259
271
borderBottom: hasDivider
260
272
? `1px solid var(--atproto-color-border)`
261
273
: "none",
274
+
borderLeft: isReply
275
+
? `3px solid #1185FE`
276
+
: "3px solid transparent",
262
277
}}
263
278
>
264
279
{repostLabel && (
265
-
<span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}>
280
+
<div style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}>
266
281
{repostLabel}
267
-
</span>
282
+
</div>
268
283
)}
269
-
{replyLabel && (
270
-
<span style={{ ...listStyles.rowMeta, color: `var(--atproto-color-text-secondary)` }}>
271
-
{replyLabel}
272
-
</span>
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>
273
301
)}
274
-
{relative && (
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
-
{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>
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>
299
338
);
300
339
};
301
340
···
388
427
fontSize: 13,
389
428
textAlign: "center",
390
429
} satisfies React.CSSProperties,
391
-
row: {
430
+
rowContainer: {
392
431
padding: "18px",
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
+
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,
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
-
return `Replying to @${handle}`;
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
-
return `Replying to @${formatDid(did)}`;
564
+
return `@${formatDid(did)}`;
508
565
}