Skip to content

Commit 07e440a

Browse files
committed
feat: added build and runtime comments refresh to bluesky comments and
caching for rate limiting
1 parent 8b22636 commit 07e440a

File tree

9 files changed

+374
-226
lines changed

9 files changed

+374
-226
lines changed

TODOs.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717

1818
## On Deck
1919

20+
- How does i18n deal with dynamic values? $t('blog.post.title'),
21+
- i18n - currently incomplete
2022
- automatic pulls or pushes from an action runner
2123
- records walkers
2224
- bsky posts stuff - Bluesky comments
23-
- How does i18n deal with dynamic values? $t('blog.post.title'),
2425
- blog publishing for https://bsky.app/profile/npmx.dev - cli/actions pipeline
2526
- site.standard.publication lexicon - declares it's a blog on atproto can be manual setup
2627
- site.standard.document - publishes everytime there's a new blog entry.

app/components/BlogPostWrapper.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ useSeoMeta({
1616
const slug = computed(() => props.frontmatter?.slug)
1717
1818
// Use Constellation to find the Bluesky post linking to this blog post
19-
const { data: blueskyLink } = useBlogPostBlueskyLink(slug)
19+
const { data: blueskyLink } = await useBlogPostBlueskyLink(slug)
2020
const blueskyPostUri = computed(() => blueskyLink.value?.postUri ?? null)
2121
</script>
2222

@@ -26,6 +26,10 @@ const blueskyPostUri = computed(() => blueskyLink.value?.postUri ?? null)
2626
<slot />
2727
</article>
2828

29+
<!--
30+
- Only renders if Constellation found a Bluesky post linking to this slug
31+
- Cached API route avoids rate limits during build
32+
-->
2933
<LazyBlueskyComments v-if="blueskyPostUri" :post-uri="blueskyPostUri" />
3034
</main>
3135
</template>

app/components/BlueskyComments.client.vue

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

app/components/BlueskyComments.vue

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<script setup lang="ts">
2+
const props = defineProps<{
3+
postUri: string
4+
}>()
5+
6+
// Since we need this to work isomorphically across SSR/build & client runtime fetch comments via server API
7+
const { data, pending, error } = useBlueskyComments(() => props.postUri)
8+
9+
const thread = computed(() => data.value?.thread)
10+
const likes = computed(() => data.value?.likes ?? [])
11+
const totalLikes = computed(() => data.value?.totalLikes ?? 0)
12+
const postUrl = computed(() => data.value?.postUrl)
13+
</script>
14+
15+
<template>
16+
<section class="mt-12 pt-8 border-t border-border max-w-prose mx-auto">
17+
<!-- Likes -->
18+
<div v-if="likes.length > 0" class="mb-8">
19+
<h3 class="text-lg font-semibold text-fg mb-4">Likes on Bluesky ({{ totalLikes }})</h3>
20+
<ul class="flex flex-wrap gap-1 list-none p-0 m-0">
21+
<li v-for="like in likes" :key="like.actor.did" class="m-0 p-0">
22+
<a
23+
:href="`https://bsky.app/profile/${like.actor.handle}`"
24+
target="_blank"
25+
rel="noopener noreferrer"
26+
:title="like.actor.displayName || like.actor.handle"
27+
>
28+
<img
29+
v-if="like.actor.avatar"
30+
:src="like.actor.avatar"
31+
:alt="like.actor.displayName || like.actor.handle"
32+
class="w-8 h-8 rounded-full hover:opacity-80 transition-opacity m-0"
33+
/>
34+
<div
35+
v-else
36+
class="w-8 h-8 rounded-full bg-bg-subtle flex items-center justify-center text-fg-muted text-xs"
37+
>
38+
{{ (like.actor.displayName || like.actor.handle).charAt(0).toUpperCase() }}
39+
</div>
40+
</a>
41+
</li>
42+
<li
43+
v-if="totalLikes > likes.length"
44+
class="flex items-center text-fg-muted text-sm m-0 p-0 pl-2"
45+
>
46+
<a
47+
v-if="postUrl"
48+
:href="postUrl"
49+
target="_blank"
50+
rel="noopener noreferrer"
51+
class="link ms-auto"
52+
>
53+
+{{ totalLikes - likes.length }} more
54+
</a>
55+
</li>
56+
</ul>
57+
</div>
58+
59+
<!-- Comments Section -->
60+
<div class="mb-8">
61+
<h3 class="text-lg font-semibold text-fg mb-4">Comments</h3>
62+
63+
<!-- Build-time/Initial loading -->
64+
<div v-if="pending && !thread" class="flex items-center gap-2 text-fg-muted" role="status">
65+
<span class="i-svg-spinners:90-ring-with-bg h-5 w-5" aria-hidden="true" />
66+
<span>Loading comments...</span>
67+
</div>
68+
69+
<!-- Background refresh indicator -->
70+
<div v-else-if="pending && thread" class="text-xs text-fg-subtle mb-4 animate-pulse">
71+
Updating...
72+
</div>
73+
74+
<!-- Error State -->
75+
<div v-else-if="error" class="text-fg-muted">
76+
Could not load comments.
77+
<a v-if="postUrl" :href="postUrl" target="_blank" rel="noopener noreferrer" class="link">
78+
View on Bluesky
79+
</a>
80+
</div>
81+
82+
<!-- No comments -->
83+
<div v-else-if="!thread || thread.replies.length === 0">
84+
<p class="text-fg-muted mb-4">No comments yet.</p>
85+
<a v-if="postUrl" :href="postUrl" target="_blank" rel="noopener noreferrer" class="link">
86+
Reply on Bluesky
87+
</a>
88+
</div>
89+
90+
<!-- Comments Thread -->
91+
<div v-else class="flex flex-col gap-6">
92+
<div class="flex items-center gap-4 text-sm text-fg-muted">
93+
<span>{{ thread.replyCount }} {{ thread.replyCount === 1 ? 'reply' : 'replies' }}</span>
94+
<a
95+
v-if="postUrl"
96+
:href="postUrl"
97+
target="_blank"
98+
rel="noopener noreferrer"
99+
class="link ms-auto"
100+
>
101+
Reply on Bluesky
102+
</a>
103+
</div>
104+
105+
<BlueskyComment
106+
v-for="reply in thread.replies"
107+
:key="reply.uri"
108+
:comment="reply"
109+
:depth="0"
110+
/>
111+
112+
<a
113+
v-if="postUrl"
114+
:href="postUrl"
115+
target="_blank"
116+
rel="noopener noreferrer"
117+
class="link inline-flex items-center gap-2"
118+
>
119+
Like this post or add your comment on Bluesky
120+
<span class="i-carbon:arrow-right" aria-hidden="true" />
121+
</a>
122+
</div>
123+
</div>
124+
</section>
125+
</template>

0 commit comments

Comments
 (0)