Personal blog finxol.io
blog

Add Bluesky comments to blog posts #1

merged opened by finxol.io targeting main from feat/add-bsky-comments

Add a bsky post cid to have display comments directly in the blog post

Labels

None yet.

Participants 1
AT URI
at://did:plc:hpmpe3pzpdtxbmvhlwrevhju/sh.tangled.repo.pull/3m6ehz6pa3k22
+148 -16
Interdiff #2 โ†’ #3
.zed/settings.json

This file has not been changed.

+78 -12
app/components/BskyComments.vue
··· 22 } 23 24 if (data.value.$type === "app.bsky.feed.defs#threadViewPost") { 25 post.value = data.value; 26 } 27 </script> 28 29 <template> 30 - <h3>Join the conversation!</h3> 31 32 - <div v-if="err"> 33 - <div>{{ err }}</div> 34 - </div> 35 36 - <div v-if="post"> 37 - <div v-if="post.post.replyCount === 0"> 38 - <div>No replies yet!</div> 39 </div> 40 41 - <div v-else> 42 - <p>{{post.post.replyCount}} replies</p> 43 44 - <div v-for="reply in post.replies"> 45 - <a :href="`https://bsky.app/profile/${reply.post.author.handle}`" class="text-blue-500 hover:underline"> 46 {{ reply.post.author.displayName }} 47 </a> 48 - <div>{{ reply.post.record.text }}</div> 49 </div> 50 </div> 51 </div>
··· 22 } 23 24 if (data.value.$type === "app.bsky.feed.defs#threadViewPost") { 25 + console.log(data.value); 26 post.value = data.value; 27 } 28 </script> 29 30 <template> 31 + <div class="md:w-[80%] mx-auto mt-16"> 32 + <div class="flex items-baseline gap-4"> 33 + <h3 class="font-bold text-xl">Join the conversation!</h3> 34 + <p class="text-gray-500 text-sm" title="Replies"> 35 + <Icon name="ri:reply-line" class="-mb-[2px] mr-1" /> 36 + {{post.post.replyCount}} 37 + </p> 38 + <p class="text-gray-500 text-sm" title="Likes"> 39 + <Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" /> 40 + <span> 41 + {{post.post.likeCount}} 42 + </span> 43 + </p> 44 + <p class="text-gray-500 text-sm" title="Bookmarks"> 45 + <Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" /> 46 + {{post.post.bookmarkCount}} 47 + </p> 48 + </div> 49 50 + <p class="text-gray-600 text-md mb-6"> 51 + <a class="underline" :href="`https://bsky.app/profile/${post.post.author.handle}/post/${cid}`">Reply on Bluesky</a> to take part in the discussion. 52 + </p> 53 54 + <div v-if="err"> 55 + <div>{{ err }}</div> 56 </div> 57 58 + <div v-if="post"> 59 + <div v-if="post.post.replyCount === 0"> 60 + <div>No replies yet!</div> 61 + </div> 62 63 + <div v-else v-for="reply in post.replies" class="mt-6"> 64 + <BskyPost :post="reply" :depth="0" /> 65 + 66 + <!-- <a :href="`https://bsky.app/profile/${reply.post.author.handle}`" class="flex items-center gap-2 text-blue-500 hover:underline w-fit"> 67 + <img :src="reply.post.author.avatar" :alt="reply.post.author.displayName" class="size-8 rounded-full" /> 68 + <span> 69 {{ reply.post.author.displayName }} 70 + </span> 71 </a> 72 + <div class="ml-10">{{ reply.post.record.text }}</div> 73 + <div class="flex items-baseline gap-4 ml-10 mt-2"> 74 + <p class="text-gray-500 text-sm" title="Replies"> 75 + <Icon name="ri:reply-line" class="-mb-[2px] mr-1" /> 76 + {{reply.post.replyCount}} 77 + </p> 78 + <p class="text-gray-500 text-sm" title="Likes"> 79 + <Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" /> 80 + <span> 81 + {{reply.post.likeCount}} 82 + </span> 83 + </p> 84 + <p class="text-gray-500 text-sm" title="Bookmarks"> 85 + <Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" /> 86 + {{reply.post.bookmarkCount}} 87 + </p> 88 + </div> 89 + 90 + <div v-for="rep in reply.replies" class="mt-6 ml-10"> 91 + <a :href="`https://bsky.app/profile/${rep.post.author.handle}`" class="flex items-center gap-2 text-blue-500 hover:underline w-fit"> 92 + <img :src="rep.post.author.avatar" :alt="rep.post.author.displayName" class="size-8 rounded-full" /> 93 + <span> 94 + {{ rep.post.author.displayName }} 95 + </span> 96 + </a> 97 + <div class="ml-10">{{ rep.post.record.text }}</div> 98 + <div class="flex items-baseline gap-4 ml-10 mt-2"> 99 + <p class="text-gray-500 text-sm" title="Replies"> 100 + <Icon name="ri:reply-line" class="-mb-[2px] mr-1" /> 101 + {{rep.post.replyCount}} 102 + </p> 103 + <p class="text-gray-500 text-sm" title="Likes"> 104 + <Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" /> 105 + <span> 106 + {{rep.post.likeCount}} 107 + </span> 108 + </p> 109 + <p class="text-gray-500 text-sm" title="Bookmarks"> 110 + <Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" /> 111 + {{rep.post.bookmarkCount}} 112 + </p> 113 + </div> 114 + </div> --> 115 </div> 116 </div> 117 </div>
+4
app/pages/posts/[...slug].vue
··· 93 94 <Suspense> 95 <BskyComments v-if="post.bskyCid" :cid="post.bskyCid" /> 96 </Suspense> 97 98 </article>
··· 93 94 <Suspense> 95 <BskyComments v-if="post.bskyCid" :cid="post.bskyCid" /> 96 + 97 + <template #fallback> 98 + <h1 class="text-xl font-bold text-stone-600">Loading comments...</h1> 99 + </template> 100 </Suspense> 101 102 </article>
+8 -1
app/util/atproto.ts
··· 26 }); 27 28 if (!ok) { 29 - console.error(data); 30 console.error("Error fetching thread:", data.error); 31 return { $type: "app.bsky.feed.defs#notFoundPost" }; 32 } ··· 36 } 37 38 return { $type: "app.bsky.feed.defs#notFoundPost" }; 39 }
··· 26 }); 27 28 if (!ok) { 29 console.error("Error fetching thread:", data.error); 30 return { $type: "app.bsky.feed.defs#notFoundPost" }; 31 } ··· 35 } 36 37 return { $type: "app.bsky.feed.defs#notFoundPost" }; 38 + } 39 + 40 + export function extractPostId(uri: ResourceUri) { 41 + if (uri.includes("app.bsky.feed.post")) { 42 + const parts = uri.split("/"); 43 + return parts.at(-1); 44 + } 45 + return ""; 46 }
blog.config.ts

This file has not been changed.

content/posts/embracing-atproto-pt-2-tangled-knot.md

This file has not been changed.

content.config.ts

This file has not been changed.

globals.ts

This file has not been changed.

package.json

This file has not been changed.

pnpm-lock.yaml

This file has not been changed.

tsconfig.json

This file has not been changed.

+52
app/components/BskyPost.vue
···
··· 1 + <script setup lang="ts"> 2 + import type { AppBskyFeedDefs } from "@atcute/bluesky"; 3 + import { extractPostId } from "~/util/atproto"; 4 + 5 + const props = defineProps<{ 6 + post: AppBskyFeedDefs.ThreadViewPost; 7 + depth: number; 8 + }>(); 9 + const { post, depth } = toRefs(props); 10 + 11 + const MAX_DEPTH = 2; // Max number of replies to a reply 12 + </script> 13 + 14 + <template> 15 + <div v-if="post && depth <= MAX_DEPTH" :class="['mt-6', depth > 0 ? 'ml-10' : '']"> 16 + <a :href="`https://bsky.app/profile/${post.post.author.handle}`" class="flex items-center gap-2 text-blue-500 hover:underline w-fit"> 17 + <img :src="post.post.author.avatar" :alt="post.post.author.displayName" class="size-8 rounded-full" /> 18 + <span> 19 + {{ post.post.author.displayName }} 20 + </span> 21 + </a> 22 + <div class="ml-10">{{ post.post.record.text }}</div> 23 + <div class="flex items-baseline gap-4 ml-10 mt-2"> 24 + <p class="text-gray-500 text-sm" title="Replies"> 25 + <Icon name="ri:reply-line" class="-mb-[2px] mr-1" /> 26 + {{post.post.replyCount}} 27 + </p> 28 + <p class="text-gray-500 text-sm" title="Likes"> 29 + <Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" /> 30 + <span> 31 + {{post.post.likeCount}} 32 + </span> 33 + </p> 34 + <p class="text-gray-500 text-sm" title="Bookmarks"> 35 + <Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" /> 36 + {{post.post.bookmarkCount}} 37 + </p> 38 + </div> 39 + 40 + <div v-if="post.replies"> 41 + <div v-if="depth === MAX_DEPTH"> 42 + <a :href="`https://bsky.app/profile/${post.post.author.handle}/post/${extractPostId(post.post.uri)}`" class="text-gray-500 text-sm flex items-center gap-2 mt-4 ml-10"> 43 + View more replies on Bluesky 44 + <Icon name='ri:arrow-drop-right-line' /> 45 + </a> 46 + </div> 47 + <div v-for="reply in post.replies"> 48 + <BskyPost v-if="reply.$type === 'app.bsky.feed.defs#threadViewPost'" :post="reply" :depth="depth + 1" /> 49 + </div> 50 + </div> 51 + </div> 52 + </template>
+1 -1
app/components/ShareActions.vue
··· 70 71 <template> 72 <aside 73 - class="print:hidden flex flex-row justify-center items-center gap-4 mt-24 md:w-[80%] mx-auto" 74 > 75 <div class="h-1 bg-stone-200 dark:bg-stone-700 grow" /> 76 <div class="whitespace-nowrap px-4 flex flex-row items-center gap-4">
··· 70 71 <template> 72 <aside 73 + class="print:hidden flex flex-row justify-center items-center gap-4 mt-16 md:w-[80%] mx-auto" 74 > 75 <div class="h-1 bg-stone-200 dark:bg-stone-700 grow" /> 76 <div class="whitespace-nowrap px-4 flex flex-row items-center gap-4">
+1
content/posts/blog-template.md
··· 7 tags: 8 - open source 9 published: true 10 --- 11 12 Are you enjoying this blog?
··· 7 tags: 8 - open source 9 published: true 10 + bskyCid: 3lqatrbwkjk2a 11 --- 12 13 Are you enjoying this blog?
+1
content/posts/embracing-atproto-pt-1-hosting-pds.md
··· 9 - atproto 10 - self-hosting 11 published: true 12 --- 13 14 The [Atmosphere Protocol](https://atproto.com/), or atproto for short, is the protocol behind the Bluesky social network.
··· 9 - atproto 10 - self-hosting 11 published: true 12 + bskyCid: 3lxwbhjz4l22i 13 --- 14 15 The [Atmosphere Protocol](https://atproto.com/), or atproto for short, is the protocol behind the Bluesky social network.
+1
content/posts/extending-openauth.md
··· 8 - auth 9 - open source 10 published: true 11 --- 12 13 I'm currently building [Karr](https://karr.mobi/?utm_source=finxol-blog&utm_content=openauth-post), an open-source federated carpool platformโ€”it's still very early days, not much there yet.
··· 8 - auth 9 - open source 10 published: true 11 + bskyCid: 3llnnj4z2xs2u 12 --- 13 14 I'm currently building [Karr](https://karr.mobi/?utm_source=finxol-blog&utm_content=openauth-post), an open-source federated carpool platformโ€”it's still very early days, not much there yet.
+2 -2
deno.jsonc
··· 1 { 2 "deploy": { 3 - "org": "finxol", 4 - "app": "blogging" 5 } 6 }
··· 1 { 2 "deploy": { 3 + "org": "finxol", 4 + "app": "blogging" 5 } 6 }

History

5 rounds 1 comment
sign up or login to add to the discussion
10 commits
expand
chore: add bsky post cid to content schema
chore: add deps
feat: first draft at bsky replies
fix: add loading state
feat: improve appearance
feat: add icons and bsky stats
feat: add bsky post CIDs to more blog posts
feat: show reply replies, max depth 2
fix: properly align elements
fix: max depth truncate + post elements alignment
expand 0 comments
pull request successfully merged
8 commits
expand
chore: add bsky post cid to content schema
chore: add deps
feat: first draft at bsky replies
fix: add loading state
feat: improve appearance
feat: add icons and bsky stats
feat: add bsky post CIDs to more blog posts
feat: show reply replies, max depth 2
expand 1 comment

Still needs some minor adjustments

3 commits
expand
chore: add bsky post cid to content schema
chore: add deps
feat: first draft at bsky replies
expand 0 comments
1 commit
expand
chore: add bsky post cid to content schema
expand 0 comments
1 commit
expand
chore: add bsky post cid to content schema
expand 0 comments