back interdiff of round #2 and #1

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

ERROR
content.config.ts

Failed to calculate interdiff for this file.

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

Failed to calculate interdiff for this file.

NEW
.zed/settings.json
···
"language_servers": ["deno", "..."],
"languages": {
"Vue.js": {
-
"language_servers": ["deno", "..."]
+
"formatter": { "language_server": { "name": "biome" } },
+
"language_servers": ["!deno", "biome", "..."]
},
"JavaScript": {
"formatter": { "language_server": { "name": "biome" } },
-
"language_servers": ["deno", "..."]
+
"language_servers": ["!deno", "..."]
},
"TypeScript": {
"formatter": { "language_server": { "name": "biome" } },
-
"language_servers": ["deno", "..."]
+
"language_servers": ["!deno", "..."]
},
"TSX": {
"formatter": { "language_server": { "name": "biome" } },
-
"language_servers": ["deno", "..."]
+
"language_servers": ["!deno", "..."]
},
"JSON": { "formatter": { "language_server": { "name": "biome" } } },
"JSONC": { "formatter": { "language_server": { "name": "biome" } } },
NEW
app/components/BskyComments.vue
···
+
<script setup lang="ts">
+
import { getBskyReplies, type ReplyThread } from "~/util/atproto";
+
+
const props = defineProps({
+
cid: {
+
type: String,
+
required: true
+
}
+
});
+
const { cid } = toRefs(props);
+
+
const data = ref(await getBskyReplies(cid.value));
+
const err = ref("");
+
const post = ref();
+
+
if (data.value.$type === "app.bsky.feed.defs#blockedPost") {
+
err.value = "Post is blocked";
+
}
+
+
if (data.value.$type === "app.bsky.feed.defs#notFoundPost") {
+
err.value = "Post not found";
+
}
+
+
if (data.value.$type === "app.bsky.feed.defs#threadViewPost") {
+
post.value = data.value;
+
}
+
</script>
+
+
<template>
+
<h3>Join the conversation!</h3>
+
+
<div v-if="err">
+
<div>{{ err }}</div>
+
</div>
+
+
<div v-if="post">
+
<div v-if="post.post.replyCount === 0">
+
<div>No replies yet!</div>
+
</div>
+
+
<div v-else>
+
<p>{{post.post.replyCount}} replies</p>
+
+
<div v-for="reply in post.replies">
+
<a :href="`https://bsky.app/profile/${reply.post.author.handle}`" class="text-blue-500 hover:underline">
+
{{ reply.post.author.displayName }}
+
</a>
+
<div>{{ reply.post.record.text }}</div>
+
</div>
+
</div>
+
</div>
+
</template>
NEW
app/pages/posts/[...slug].vue
···
<ShareActions :title="post.title" :description="post.description" :author="post.authors[0]?.name" />
+
<Suspense>
+
<BskyComments v-if="post.bskyCid" :cid="post.bskyCid" />
+
</Suspense>
+
</article>
<div v-else class="flex items-center justify-center">
NEW
app/util/atproto.ts
···
+
import { Client, simpleFetchHandler } from "@atcute/client";
+
import type { AppBskyFeedDefs } from "@atcute/bluesky";
+
import type { ResourceUri } from "@atcute/lexicons";
+
+
import config from "@/../blog.config";
+
+
const handler = simpleFetchHandler({
+
service: "https://public.api.bsky.app"
+
});
+
const rpc = new Client({ handler });
+
+
export type ReplyThread =
+
| AppBskyFeedDefs.ThreadViewPost
+
| AppBskyFeedDefs.BlockedPost
+
| AppBskyFeedDefs.NotFoundPost;
+
+
export async function getBskyReplies(cid: string) {
+
// uri should be in format: at://did:plc:xxx/app.bsky.feed.post/xxxxx
+
const uri: ResourceUri = `at://${config.authorDid}/app.bsky.feed.post/${cid}`;
+
+
const { ok, data } = await rpc.get("app.bsky.feed.getPostThread", {
+
params: {
+
uri,
+
depth: 10
+
}
+
});
+
+
if (!ok) {
+
console.error(data);
+
console.error("Error fetching thread:", data.error);
+
return { $type: "app.bsky.feed.defs#notFoundPost" };
+
}
+
+
if (ok) {
+
return data.thread;
+
}
+
+
return { $type: "app.bsky.feed.defs#notFoundPost" };
+
}
NEW
blog.config.ts
···
site: "https://finxol.io",
title: "finxol's blog",
author: "finxol",
+
authorDid: "did:plc:hpmpe3pzpdtxbmvhlwrevhju",
meta: [
{
name: "description",
NEW
globals.ts
···
sharingProviders: SharingProvider[];
title: string;
author: string;
+
authorDid: `did:plc:${string}`;
meta: {
name: string;
content: string;
···
: { bluesky: true, clipboard: true, native: true },
title: config.title ?? "My Blog",
author: config.author ?? "finxol",
+
authorDid: config.authorDid,
meta: config.meta ?? [
{ name: "description", content: "My blog description" }
],
NEW
package.json
···
"private": true,
"name": "nuxt-app",
"type": "module",
-
"packageManager": "pnpm@10.20.0",
+
"packageManager": "pnpm@10.23.0",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
···
"format": "biome format --write"
},
"dependencies": {
+
"@atcute/bluesky": "^3.2.10",
+
"@atcute/client": "^4.0.5",
+
"@atcute/lexicons": "^1.2.4",
"@nuxt/content": "^3.8.0",
"@nuxt/icon": "1.11.0",
"@nuxtjs/tailwindcss": "^6.14.0",
NEW
pnpm-lock.yaml
···
.:
dependencies:
+
'@atcute/bluesky':
+
specifier: ^3.2.10
+
version: 3.2.10
+
'@atcute/client':
+
specifier: ^4.0.5
+
version: 4.0.5
+
'@atcute/lexicons':
+
specifier: ^1.2.4
+
version: 1.2.4
'@nuxt/content':
specifier: ^3.8.0
version: 3.8.0(@libsql/client@0.15.15)(better-sqlite3@12.4.1)(magicast@0.5.1)
···
'@apidevtools/json-schema-ref-parser@11.9.3':
resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==}
engines: {node: '>= 16'}
+
+
'@atcute/atproto@3.1.9':
+
resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==}
+
+
'@atcute/bluesky@3.2.10':
+
resolution: {integrity: sha512-qwQWTzRf3umnh2u41gdU+xWYkbzGlKDupc3zeOB+YjmuP1N9wEaUhwS8H7vgrqr0xC9SGNDjeUVcjC4m5BPLBg==}
+
+
'@atcute/client@4.0.5':
+
resolution: {integrity: sha512-R8Qen8goGmEkynYGg2m6XFlVmz0GTDvQ+9w+4QqOob+XMk8/WDpF4aImev7WKEde/rV2gjcqW7zM8E6W9NShDA==}
+
+
'@atcute/identity@1.1.3':
+
resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==}
+
+
'@atcute/lexicons@1.2.4':
+
resolution: {integrity: sha512-s6fl/SVjQMv7jiitLCcZ434X+VrTsJt7Fl9iJg8WXHJIELRz/U0sNUoP++oWd7bvPy1Vcd2Wnm+YtTm/Zn7AIQ==}
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
···
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
+
+
'@badrap/valita@0.4.6':
+
resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==}
+
engines: {node: '>= 18'}
'@biomejs/biome@1.9.4':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
···
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
+
esm-env@1.2.2:
+
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
+
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
···
'@types/json-schema': 7.0.15
js-yaml: 4.1.0
+
'@atcute/atproto@3.1.9':
+
dependencies:
+
'@atcute/lexicons': 1.2.4
+
+
'@atcute/bluesky@3.2.10':
+
dependencies:
+
'@atcute/atproto': 3.1.9
+
'@atcute/lexicons': 1.2.4
+
+
'@atcute/client@4.0.5':
+
dependencies:
+
'@atcute/identity': 1.1.3
+
'@atcute/lexicons': 1.2.4
+
+
'@atcute/identity@1.1.3':
+
dependencies:
+
'@atcute/lexicons': 1.2.4
+
'@badrap/valita': 0.4.6
+
+
'@atcute/lexicons@1.2.4':
+
dependencies:
+
'@standard-schema/spec': 1.0.0
+
esm-env: 1.2.2
+
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
···
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
+
+
'@badrap/valita@0.4.6': {}
'@biomejs/biome@1.9.4':
optionalDependencies:
···
escape-string-regexp@4.0.0: {}
escape-string-regexp@5.0.0: {}
+
+
esm-env@1.2.2: {}
estree-walker@2.0.2: {}
NEW
tsconfig.json
···
{
// https://nuxt.com/docs/guide/concepts/typescript
-
"extends": "./.nuxt/tsconfig.json"
+
"extends": "./.nuxt/tsconfig.json",
+
"compilerOptions": {
+
"types": ["@atcute/bluesky"]
+
}
}