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
+308 -10
Diff #3
+5 -4
.zed/settings.json
··· 2 "language_servers": ["deno", "..."], 3 "languages": { 4 "Vue.js": { 5 - "language_servers": ["deno", "..."] 6 }, 7 8 "JavaScript": { 9 "formatter": { "language_server": { "name": "biome" } }, 10 - "language_servers": ["deno", "..."] 11 }, 12 13 "TypeScript": { 14 "formatter": { "language_server": { "name": "biome" } }, 15 - "language_servers": ["deno", "..."] 16 }, 17 18 "TSX": { 19 "formatter": { "language_server": { "name": "biome" } }, 20 - "language_servers": ["deno", "..."] 21 }, 22 "JSON": { "formatter": { "language_server": { "name": "biome" } } }, 23 "JSONC": { "formatter": { "language_server": { "name": "biome" } } },
··· 2 "language_servers": ["deno", "..."], 3 "languages": { 4 "Vue.js": { 5 + "formatter": { "language_server": { "name": "biome" } }, 6 + "language_servers": ["!deno", "biome", "..."] 7 }, 8 9 "JavaScript": { 10 "formatter": { "language_server": { "name": "biome" } }, 11 + "language_servers": ["!deno", "..."] 12 }, 13 14 "TypeScript": { 15 "formatter": { "language_server": { "name": "biome" } }, 16 + "language_servers": ["!deno", "..."] 17 }, 18 19 "TSX": { 20 "formatter": { "language_server": { "name": "biome" } }, 21 + "language_servers": ["!deno", "..."] 22 }, 23 "JSON": { "formatter": { "language_server": { "name": "biome" } } }, 24 "JSONC": { "formatter": { "language_server": { "name": "biome" } } },
+118
app/components/BskyComments.vue
···
··· 1 + <script setup lang="ts"> 2 + import { getBskyReplies, type ReplyThread } from "~/util/atproto"; 3 + 4 + const props = defineProps({ 5 + cid: { 6 + type: String, 7 + required: true 8 + } 9 + }); 10 + const { cid } = toRefs(props); 11 + 12 + const data = ref(await getBskyReplies(cid.value)); 13 + const err = ref(""); 14 + const post = ref(); 15 + 16 + if (data.value.$type === "app.bsky.feed.defs#blockedPost") { 17 + err.value = "Post is blocked"; 18 + } 19 + 20 + if (data.value.$type === "app.bsky.feed.defs#notFoundPost") { 21 + err.value = "Post not found"; 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> 118 + </template>
+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">
+8
app/pages/posts/[...slug].vue
··· 91 92 <ShareActions :title="post.title" :description="post.description" :author="post.authors[0]?.name" /> 93 94 </article> 95 96 <div v-else class="flex items-center justify-center">
··· 91 92 <ShareActions :title="post.title" :description="post.description" :author="post.authors[0]?.name" /> 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> 103 104 <div v-else class="flex items-center justify-center">
+46
app/util/atproto.ts
···
··· 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 + import type { AppBskyFeedDefs } from "@atcute/bluesky"; 3 + import type { ResourceUri } from "@atcute/lexicons"; 4 + 5 + import config from "@/../blog.config"; 6 + 7 + const handler = simpleFetchHandler({ 8 + service: "https://public.api.bsky.app" 9 + }); 10 + const rpc = new Client({ handler }); 11 + 12 + export type ReplyThread = 13 + | AppBskyFeedDefs.ThreadViewPost 14 + | AppBskyFeedDefs.BlockedPost 15 + | AppBskyFeedDefs.NotFoundPost; 16 + 17 + export async function getBskyReplies(cid: string) { 18 + // uri should be in format: at://did:plc:xxx/app.bsky.feed.post/xxxxx 19 + const uri: ResourceUri = `at://${config.authorDid}/app.bsky.feed.post/${cid}`; 20 + 21 + const { ok, data } = await rpc.get("app.bsky.feed.getPostThread", { 22 + params: { 23 + uri, 24 + depth: 10 25 + } 26 + }); 27 + 28 + if (!ok) { 29 + console.error("Error fetching thread:", data.error); 30 + return { $type: "app.bsky.feed.defs#notFoundPost" }; 31 + } 32 + 33 + if (ok) { 34 + return data.thread; 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 + }
+1
blog.config.ts
··· 4 site: "https://finxol.io", 5 title: "finxol's blog", 6 author: "finxol", 7 meta: [ 8 { 9 name: "description",
··· 4 site: "https://finxol.io", 5 title: "finxol's blog", 6 author: "finxol", 7 + authorDid: "did:plc:hpmpe3pzpdtxbmvhlwrevhju", 8 meta: [ 9 { 10 name: "description",
+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/embracing-atproto-pt-2-tangled-knot.md
··· 10 - atproto 11 - self-hosting 12 published: true 13 --- 14 15 I recently set up my own atproto PDS, for use with Bluesky and all other atproto apps.
··· 10 - atproto 11 - self-hosting 12 published: true 13 + bskyCid: 3lyzhrumfu22n 14 --- 15 16 I recently set up my own atproto PDS, for use with Bluesky and all other atproto apps.
+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 -1
content.config.ts
··· 26 }) 27 ), 28 tags: z.array(z.string()), 29 - published: z.boolean().optional() 30 }) 31 }) 32 )
··· 26 }) 27 ), 28 tags: z.array(z.string()), 29 + published: z.boolean().optional(), 30 + bskyCid: z.string().optional() 31 }) 32 }) 33 )
+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 }
+2
globals.ts
··· 10 sharingProviders: SharingProvider[]; 11 title: string; 12 author: string; 13 meta: { 14 name: string; 15 content: string; ··· 36 : { bluesky: true, clipboard: true, native: true }, 37 title: config.title ?? "My Blog", 38 author: config.author ?? "finxol", 39 meta: config.meta ?? [ 40 { name: "description", content: "My blog description" } 41 ],
··· 10 sharingProviders: SharingProvider[]; 11 title: string; 12 author: string; 13 + authorDid: `did:plc:${string}`; 14 meta: { 15 name: string; 16 content: string; ··· 37 : { bluesky: true, clipboard: true, native: true }, 38 title: config.title ?? "My Blog", 39 author: config.author ?? "finxol", 40 + authorDid: config.authorDid, 41 meta: config.meta ?? [ 42 { name: "description", content: "My blog description" } 43 ],
+4 -1
package.json
··· 2 "private": true, 3 "name": "nuxt-app", 4 "type": "module", 5 - "packageManager": "pnpm@10.20.0", 6 "scripts": { 7 "build": "nuxt build", 8 "dev": "nuxt dev", ··· 12 "format": "biome format --write" 13 }, 14 "dependencies": { 15 "@nuxt/content": "^3.8.0", 16 "@nuxt/icon": "1.11.0", 17 "@nuxtjs/tailwindcss": "^6.14.0",
··· 2 "private": true, 3 "name": "nuxt-app", 4 "type": "module", 5 + "packageManager": "pnpm@10.23.0", 6 "scripts": { 7 "build": "nuxt build", 8 "dev": "nuxt dev", ··· 12 "format": "biome format --write" 13 }, 14 "dependencies": { 15 + "@atcute/bluesky": "^3.2.10", 16 + "@atcute/client": "^4.0.5", 17 + "@atcute/lexicons": "^1.2.4", 18 "@nuxt/content": "^3.8.0", 19 "@nuxt/icon": "1.11.0", 20 "@nuxtjs/tailwindcss": "^6.14.0",
+59
pnpm-lock.yaml
··· 8 9 .: 10 dependencies: 11 '@nuxt/content': 12 specifier: ^3.8.0 13 version: 3.8.0(@libsql/client@0.15.15)(better-sqlite3@12.4.1)(magicast@0.5.1) ··· 70 '@apidevtools/json-schema-ref-parser@11.9.3': 71 resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} 72 engines: {node: '>= 16'} 73 74 '@babel/code-frame@7.27.1': 75 resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} ··· 187 '@babel/types@7.28.5': 188 resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} 189 engines: {node: '>=6.9.0'} 190 191 '@biomejs/biome@1.9.4': 192 resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} ··· 2304 resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} 2305 engines: {node: '>=12'} 2306 2307 estree-walker@2.0.2: 2308 resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 2309 ··· 4802 '@types/json-schema': 7.0.15 4803 js-yaml: 4.1.0 4804 4805 '@babel/code-frame@7.27.1': 4806 dependencies: 4807 '@babel/helper-validator-identifier': 7.28.5 ··· 4968 dependencies: 4969 '@babel/helper-string-parser': 7.27.1 4970 '@babel/helper-validator-identifier': 7.28.5 4971 4972 '@biomejs/biome@1.9.4': 4973 optionalDependencies: ··· 7258 escape-string-regexp@4.0.0: {} 7259 7260 escape-string-regexp@5.0.0: {} 7261 7262 estree-walker@2.0.2: {} 7263
··· 8 9 .: 10 dependencies: 11 + '@atcute/bluesky': 12 + specifier: ^3.2.10 13 + version: 3.2.10 14 + '@atcute/client': 15 + specifier: ^4.0.5 16 + version: 4.0.5 17 + '@atcute/lexicons': 18 + specifier: ^1.2.4 19 + version: 1.2.4 20 '@nuxt/content': 21 specifier: ^3.8.0 22 version: 3.8.0(@libsql/client@0.15.15)(better-sqlite3@12.4.1)(magicast@0.5.1) ··· 79 '@apidevtools/json-schema-ref-parser@11.9.3': 80 resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} 81 engines: {node: '>= 16'} 82 + 83 + '@atcute/atproto@3.1.9': 84 + resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==} 85 + 86 + '@atcute/bluesky@3.2.10': 87 + resolution: {integrity: sha512-qwQWTzRf3umnh2u41gdU+xWYkbzGlKDupc3zeOB+YjmuP1N9wEaUhwS8H7vgrqr0xC9SGNDjeUVcjC4m5BPLBg==} 88 + 89 + '@atcute/client@4.0.5': 90 + resolution: {integrity: sha512-R8Qen8goGmEkynYGg2m6XFlVmz0GTDvQ+9w+4QqOob+XMk8/WDpF4aImev7WKEde/rV2gjcqW7zM8E6W9NShDA==} 91 + 92 + '@atcute/identity@1.1.3': 93 + resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==} 94 + 95 + '@atcute/lexicons@1.2.4': 96 + resolution: {integrity: sha512-s6fl/SVjQMv7jiitLCcZ434X+VrTsJt7Fl9iJg8WXHJIELRz/U0sNUoP++oWd7bvPy1Vcd2Wnm+YtTm/Zn7AIQ==} 97 98 '@babel/code-frame@7.27.1': 99 resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} ··· 211 '@babel/types@7.28.5': 212 resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} 213 engines: {node: '>=6.9.0'} 214 + 215 + '@badrap/valita@0.4.6': 216 + resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==} 217 + engines: {node: '>= 18'} 218 219 '@biomejs/biome@1.9.4': 220 resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} ··· 2332 resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} 2333 engines: {node: '>=12'} 2334 2335 + esm-env@1.2.2: 2336 + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 2337 + 2338 estree-walker@2.0.2: 2339 resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 2340 ··· 4833 '@types/json-schema': 7.0.15 4834 js-yaml: 4.1.0 4835 4836 + '@atcute/atproto@3.1.9': 4837 + dependencies: 4838 + '@atcute/lexicons': 1.2.4 4839 + 4840 + '@atcute/bluesky@3.2.10': 4841 + dependencies: 4842 + '@atcute/atproto': 3.1.9 4843 + '@atcute/lexicons': 1.2.4 4844 + 4845 + '@atcute/client@4.0.5': 4846 + dependencies: 4847 + '@atcute/identity': 1.1.3 4848 + '@atcute/lexicons': 1.2.4 4849 + 4850 + '@atcute/identity@1.1.3': 4851 + dependencies: 4852 + '@atcute/lexicons': 1.2.4 4853 + '@badrap/valita': 0.4.6 4854 + 4855 + '@atcute/lexicons@1.2.4': 4856 + dependencies: 4857 + '@standard-schema/spec': 1.0.0 4858 + esm-env: 1.2.2 4859 + 4860 '@babel/code-frame@7.27.1': 4861 dependencies: 4862 '@babel/helper-validator-identifier': 7.28.5 ··· 5023 dependencies: 5024 '@babel/helper-string-parser': 7.27.1 5025 '@babel/helper-validator-identifier': 7.28.5 5026 + 5027 + '@badrap/valita@0.4.6': {} 5028 5029 '@biomejs/biome@1.9.4': 5030 optionalDependencies: ··· 7315 escape-string-regexp@4.0.0: {} 7316 7317 escape-string-regexp@5.0.0: {} 7318 + 7319 + esm-env@1.2.2: {} 7320 7321 estree-walker@2.0.2: {} 7322
+4 -1
tsconfig.json
··· 1 { 2 // https://nuxt.com/docs/guide/concepts/typescript 3 - "extends": "./.nuxt/tsconfig.json" 4 }
··· 1 { 2 // https://nuxt.com/docs/guide/concepts/typescript 3 + "extends": "./.nuxt/tsconfig.json", 4 + "compilerOptions": { 5 + "types": ["@atcute/bluesky"] 6 + } 7 }

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