feat: show reply replies, max depth 2

finxol.io e61c1bea e4c3a778

verified
Changed files
+95 -7
app
+35 -7
app/components/BskyComments.vue
···
<div class="md:w-[80%] mx-auto mt-16">
<div class="flex items-baseline gap-4">
<h3 class="font-bold text-xl">Join the conversation!</h3>
-
<p class="text-gray-500 text-sm">
<Icon name="ri:reply-line" class="-mb-[2px] mr-1" />
{{post.post.replyCount}}
</p>
-
<p class="text-gray-500 text-sm">
<Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" />
<span>
{{post.post.likeCount}}
</span>
</p>
-
<p class="text-gray-500 text-sm">
<Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" />
{{post.post.bookmarkCount}}
</p>
···
</div>
<div v-else v-for="reply in post.replies" class="mt-6">
-
<a :href="`https://bsky.app/profile/${reply.post.author.handle}`" class="flex items-center gap-2 text-blue-500 hover:underline w-fit">
<img :src="reply.post.author.avatar" :alt="reply.post.author.displayName" class="size-8 rounded-full" />
<span>
{{ reply.post.author.displayName }}
···
</a>
<div class="ml-10">{{ reply.post.record.text }}</div>
<div class="flex items-baseline gap-4 ml-10 mt-2">
-
<p class="text-gray-500 text-sm">
<Icon name="ri:reply-line" class="-mb-[2px] mr-1" />
{{reply.post.replyCount}}
</p>
-
<p class="text-gray-500 text-sm">
<Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" />
<span>
{{reply.post.likeCount}}
</span>
</p>
-
<p class="text-gray-500 text-sm">
<Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" />
{{reply.post.bookmarkCount}}
</p>
</div>
</div>
</div>
</div>
···
<div class="md:w-[80%] mx-auto mt-16">
<div class="flex items-baseline gap-4">
<h3 class="font-bold text-xl">Join the conversation!</h3>
+
<p class="text-gray-500 text-sm" title="Replies">
<Icon name="ri:reply-line" class="-mb-[2px] mr-1" />
{{post.post.replyCount}}
</p>
+
<p class="text-gray-500 text-sm" title="Likes">
<Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" />
<span>
{{post.post.likeCount}}
</span>
</p>
+
<p class="text-gray-500 text-sm" title="Bookmarks">
<Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" />
{{post.post.bookmarkCount}}
</p>
···
</div>
<div v-else v-for="reply in post.replies" class="mt-6">
+
<BskyPost :post="reply" :depth="0" />
+
+
<!-- <a :href="`https://bsky.app/profile/${reply.post.author.handle}`" class="flex items-center gap-2 text-blue-500 hover:underline w-fit">
<img :src="reply.post.author.avatar" :alt="reply.post.author.displayName" class="size-8 rounded-full" />
<span>
{{ reply.post.author.displayName }}
···
</a>
<div class="ml-10">{{ reply.post.record.text }}</div>
<div class="flex items-baseline gap-4 ml-10 mt-2">
+
<p class="text-gray-500 text-sm" title="Replies">
<Icon name="ri:reply-line" class="-mb-[2px] mr-1" />
{{reply.post.replyCount}}
</p>
+
<p class="text-gray-500 text-sm" title="Likes">
<Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" />
<span>
{{reply.post.likeCount}}
</span>
</p>
+
<p class="text-gray-500 text-sm" title="Bookmarks">
<Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" />
{{reply.post.bookmarkCount}}
</p>
</div>
+
+
<div v-for="rep in reply.replies" class="mt-6 ml-10">
+
<a :href="`https://bsky.app/profile/${rep.post.author.handle}`" class="flex items-center gap-2 text-blue-500 hover:underline w-fit">
+
<img :src="rep.post.author.avatar" :alt="rep.post.author.displayName" class="size-8 rounded-full" />
+
<span>
+
{{ rep.post.author.displayName }}
+
</span>
+
</a>
+
<div class="ml-10">{{ rep.post.record.text }}</div>
+
<div class="flex items-baseline gap-4 ml-10 mt-2">
+
<p class="text-gray-500 text-sm" title="Replies">
+
<Icon name="ri:reply-line" class="-mb-[2px] mr-1" />
+
{{rep.post.replyCount}}
+
</p>
+
<p class="text-gray-500 text-sm" title="Likes">
+
<Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" />
+
<span>
+
{{rep.post.likeCount}}
+
</span>
+
</p>
+
<p class="text-gray-500 text-sm" title="Bookmarks">
+
<Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" />
+
{{rep.post.bookmarkCount}}
+
</p>
+
</div>
+
</div> -->
</div>
</div>
</div>
+52
app/components/BskyPost.vue
···
···
+
<script setup lang="ts">
+
import type { AppBskyFeedDefs } from "@atcute/bluesky";
+
import { extractPostId } from "~/util/atproto";
+
+
const props = defineProps<{
+
post: AppBskyFeedDefs.ThreadViewPost;
+
depth: number;
+
}>();
+
const { post, depth } = toRefs(props);
+
+
const MAX_DEPTH = 2; // Max number of replies to a reply
+
</script>
+
+
<template>
+
<div v-if="post && depth <= MAX_DEPTH" :class="['mt-6', depth > 0 ? 'ml-10' : '']">
+
<a :href="`https://bsky.app/profile/${post.post.author.handle}`" class="flex items-center gap-2 text-blue-500 hover:underline w-fit">
+
<img :src="post.post.author.avatar" :alt="post.post.author.displayName" class="size-8 rounded-full" />
+
<span>
+
{{ post.post.author.displayName }}
+
</span>
+
</a>
+
<div class="ml-10">{{ post.post.record.text }}</div>
+
<div class="flex items-baseline gap-4 ml-10 mt-2">
+
<p class="text-gray-500 text-sm" title="Replies">
+
<Icon name="ri:reply-line" class="-mb-[2px] mr-1" />
+
{{post.post.replyCount}}
+
</p>
+
<p class="text-gray-500 text-sm" title="Likes">
+
<Icon name="ri:heart-3-line" class="-mb-[2px] mr-1" />
+
<span>
+
{{post.post.likeCount}}
+
</span>
+
</p>
+
<p class="text-gray-500 text-sm" title="Bookmarks">
+
<Icon name="ri:bookmark-line" class="-mb-[2px] mr-1" />
+
{{post.post.bookmarkCount}}
+
</p>
+
</div>
+
+
<div v-if="post.replies">
+
<div v-if="depth === MAX_DEPTH">
+
<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">
+
View more replies on Bluesky
+
<Icon name='ri:arrow-drop-right-line' />
+
</a>
+
</div>
+
<div v-for="reply in post.replies">
+
<BskyPost v-if="reply.$type === 'app.bsky.feed.defs#threadViewPost'" :post="reply" :depth="depth + 1" />
+
</div>
+
</div>
+
</div>
+
</template>
+8
app/util/atproto.ts
···
return { $type: "app.bsky.feed.defs#notFoundPost" };
}
···
return { $type: "app.bsky.feed.defs#notFoundPost" };
}
+
+
export function extractPostId(uri: ResourceUri) {
+
if (uri.includes("app.bsky.feed.post")) {
+
const parts = uri.split("/");
+
return parts.at(-1);
+
}
+
return "";
+
}