Skip to content

Commit 81df1b5

Browse files
committed
fix(blog): Medium-style nested comments with responsive layout
Nested comments use inline avatar on mobile (Medium-style) for full content width, and classic avatar-column on desktop. Each nesting level costs ~12px on mobile instead of ~44px, eliminating horizontal scroll. Removes BlueskyCommentThread in favor of recursive depth.
1 parent e3afaae commit 81df1b5

18 files changed

+97
-112
lines changed

app/components/BlueskyComment.vue

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ function getCommentUrl(comment: Comment): string {
1313
}
1414
const props = defineProps<{
1515
comment: Comment
16-
isReply?: boolean
17-
replyingTo?: string
16+
depth: number
1817
}>()
1918
19+
const MaxDepth = 4
20+
2021
function getFeatureUrl(feature: RichtextFeature): string | undefined {
2122
if (feature.$type === 'app.bsky.richtext.facet#link') return feature.uri
2223
if (feature.$type === 'app.bsky.richtext.facet#mention')
@@ -40,19 +41,23 @@ function getHostname(uri: string): string {
4041
</script>
4142

4243
<template>
43-
<div class="flex gap-3">
44-
<!-- Avatar -->
44+
<!--
45+
Depth 0: classic avatar-column layout (all screens)
46+
Depth 1+: Medium-style inline avatar on mobile, avatar-column on desktop
47+
-->
48+
<div :class="depth === 0 ? 'flex gap-3' : 'sm:flex sm:gap-3'">
49+
<!-- Column avatar: always shown at depth 0, desktop-only at depth 1+ -->
4550
<a
4651
:href="`https://bsky.app/profile/${comment.author.handle}`"
4752
target="_blank"
4853
rel="noopener noreferrer"
49-
class="shrink-0"
54+
:class="['shrink-0', depth > 0 ? 'hidden sm:block' : '']"
5055
>
5156
<img
5257
v-if="comment.author.avatar"
5358
:src="comment.author.avatar"
5459
:alt="comment.author.displayName || comment.author.handle"
55-
:class="['rounded-full', isReply ? 'w-8 h-8' : 'w-10 h-10']"
60+
:class="['rounded-full', depth === 0 ? 'w-10 h-10' : 'w-8 h-8']"
5661
width="40"
5762
height="40"
5863
loading="lazy"
@@ -61,30 +66,48 @@ function getHostname(uri: string): string {
6166
v-else
6267
:class="[
6368
'rounded-full bg-border flex items-center justify-center text-fg-muted',
64-
isReply ? 'w-8 h-8 text-sm' : 'w-10 h-10',
69+
depth === 0 ? 'w-10 h-10' : 'w-8 h-8 text-sm',
6570
]"
6671
>
6772
{{ (comment.author.displayName || comment.author.handle).charAt(0).toUpperCase() }}
6873
</div>
6974
</a>
7075

7176
<div class="flex-1 min-w-0">
72-
<!-- Replying to label -->
73-
<div v-if="replyingTo" class="text-xs text-fg-subtle mb-0.5">
74-
{{ $t('blog.atproto.replying_to', { name: replyingTo }) }}
75-
</div>
76-
7777
<!-- Author info + timestamp -->
78-
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-0">
78+
<div class="flex flex-wrap items-center gap-x-2 gap-y-0">
79+
<!-- Inline avatar: mobile-only for nested comments -->
80+
<a
81+
v-if="depth > 0"
82+
:href="`https://bsky.app/profile/${comment.author.handle}`"
83+
target="_blank"
84+
rel="noopener noreferrer"
85+
class="shrink-0 sm:hidden"
86+
>
87+
<img
88+
v-if="comment.author.avatar"
89+
:src="comment.author.avatar"
90+
:alt="comment.author.displayName || comment.author.handle"
91+
class="w-6 h-6 rounded-full"
92+
width="24"
93+
height="24"
94+
loading="lazy"
95+
/>
96+
<div
97+
v-else
98+
class="w-6 h-6 rounded-full bg-border flex items-center justify-center text-fg-muted text-xs"
99+
>
100+
{{ (comment.author.displayName || comment.author.handle).charAt(0).toUpperCase() }}
101+
</div>
102+
</a>
79103
<a
80104
:href="`https://bsky.app/profile/${comment.author.handle}`"
81105
target="_blank"
82106
rel="noopener noreferrer"
83-
class="font-medium text-fg hover:underline"
107+
:class="['font-medium text-fg hover:underline', depth > 0 ? 'text-sm' : '']"
84108
>
85109
{{ comment.author.displayName || comment.author.handle }}
86110
</a>
87-
<span class="text-fg-subtle text-sm">@{{ comment.author.handle }}</span>
88111
<span class="text-fg-subtle text-sm">·</span>
89112
<a
90113
:href="getCommentUrl(props.comment)"
@@ -175,6 +198,33 @@ function getHostname(uri: string): string {
175198
{{ $t('blog.atproto.repost_count', { count: comment.repostCount }, comment.repostCount) }}
176199
</span>
177200
</div>
201+
202+
<!-- Nested replies -->
203+
<template v-if="comment.replies.length > 0">
204+
<div v-if="depth < MaxDepth" class="mt-3 ps-3 border-is-2 border-border flex flex-col gap-3">
205+
<BlueskyComment
206+
v-for="reply in comment.replies"
207+
:key="reply.uri"
208+
:comment="reply"
209+
:depth="depth + 1"
210+
/>
211+
</div>
212+
<a
213+
v-else
214+
:href="getCommentUrl(comment.replies[0]!)"
215+
target="_blank"
216+
rel="noopener noreferrer"
217+
class="mt-2 block text-sm link"
218+
>
219+
{{
220+
$t(
221+
'blog.atproto.more_replies',
222+
{ count: comment.replies.length },
223+
comment.replies.length,
224+
)
225+
}}
226+
</a>
227+
</template>
178228
</div>
179229
</div>
180230
</template>

app/components/BlueskyCommentThread.vue

Lines changed: 0 additions & 72 deletions
This file was deleted.

app/components/BlueskyComments.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,12 @@ const postUrl = computed(() => data.value?.postUrl)
100100
</LinkBase>
101101
</div>
102102

103-
<BlueskyCommentThread v-for="reply in thread.replies" :key="reply.uri" :comment="reply" />
103+
<BlueskyComment
104+
v-for="reply in thread.replies"
105+
:key="reply.uri"
106+
:comment="reply"
107+
:depth="0"
108+
/>
104109

105110
<LinkBase v-if="postUrl" variant="button-primary" :to="postUrl">
106111
{{ $t('blog.atproto.like_or_reply_on_bluesky') }}

i18n/locales/az-AZ.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@
106106
"updating": "Yenilənir...",
107107
"reply_count": "{count} cavab",
108108
"like_count": "{count} bəyənmə",
109-
"repost_count": "{count} yenidən paylaşım"
109+
"repost_count": "{count} yenidən paylaşım",
110+
"more_replies": "daha {count} cavab... | daha {count} cavab..."
110111
}
111112
},
112113
"settings": {

i18n/locales/cs-CZ.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@
106106
"updating": "Aktualizace ...",
107107
"reply_count": "{count} odpověď | {count} odpovědi | {count} odpovědí",
108108
"like_count": "{count} lajk | {count} lajky | {count} lajků",
109-
"repost_count": "{count} repost | {count} reposty | {count} repostů"
109+
"repost_count": "{count} repost | {count} reposty | {count} repostů",
110+
"more_replies": "ještě {count} odpověď… | ještě {count} odpovědi… | ještě {count} odpovědí…"
110111
}
111112
},
112113
"settings": {

i18n/locales/de-DE.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@
106106
"updating": "(wird aktualisiert...)",
107107
"reply_count": "{count} Antwort | {count} Antworten",
108108
"like_count": "{count} Like | {count} Likes",
109-
"repost_count": "{count} Repost | {count} Reposts"
109+
"repost_count": "{count} Repost | {count} Reposts",
110+
"more_replies": "noch {count} Antwort… | noch {count} Antworten…"
110111
}
111112
},
112113
"settings": {

i18n/locales/en.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,7 @@
109109
"reply_count": "{count} reply | {count} replies",
110110
"like_count": "{count} like | {count} likes",
111111
"repost_count": "{count} repost | {count} reposts",
112-
"replying_to": "Replying to {name}",
113-
"view_replies": "View {count} reply | View {count} replies",
114-
"hide_replies": "Hide replies"
112+
"more_replies": "{count} more reply... | {count} more replies..."
115113
}
116114
},
117115
"settings": {

i18n/locales/es.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@
108108
"updating": "Actualizando...",
109109
"reply_count": "{count} respuesta | {count} respuestas",
110110
"like_count": "{count} me gusta | {count} me gusta",
111-
"repost_count": "{count} republicación | {count} republicaciones"
111+
"repost_count": "{count} republicación | {count} republicaciones",
112+
"more_replies": "{count} respuesta más... | {count} respuestas más..."
112113
}
113114
},
114115
"settings": {

i18n/locales/fr-FR.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@
106106
"updating": "Mise à jour...",
107107
"reply_count": "{count} réponse | {count} réponses",
108108
"like_count": "{count} j'aime | {count} j'aime",
109-
"repost_count": "{count} repartage | {count} repartages"
109+
"repost_count": "{count} repartage | {count} repartages",
110+
"more_replies": "{count} réponse supplémentaire... | {count} réponses supplémentaires..."
110111
}
111112
},
112113
"settings": {

i18n/locales/id-ID.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@
108108
"updating": "Memperbarui...",
109109
"reply_count": "{count} balasan | {count} balasan",
110110
"like_count": "{count} suka | {count} suka",
111-
"repost_count": "{count} repost | {count} repost"
111+
"repost_count": "{count} repost | {count} repost",
112+
"more_replies": "{count} balasan lagi... | {count} balasan lagi..."
112113
}
113114
},
114115
"settings": {

0 commit comments

Comments
 (0)