11<script setup lang="ts">
2- import { BLUESKY_API , BSKY_POST_AT_URI_REGEX } from ' #shared/utils/constants'
2+ import {
3+ BLUESKY_API ,
4+ BLUESKY_URL_EXTRACT_REGEX ,
5+ BSKY_POST_AT_URI_REGEX ,
6+ } from ' #shared/utils/constants'
37
48const props = defineProps <{
59 /** AT URI of the post, e.g. at://did:plc:.../app.bsky.feed.post/... */
6- uri: string
10+ uri? : string
11+ /** Bluesky URL of the post, e.g. https://bsky.app/profile/handle/post/rkey */
12+ url? : string
713}>()
814
915interface PostAuthor {
@@ -30,24 +36,60 @@ interface BlueskyPost {
3036 repostCount? : number
3137}
3238
33- const postUrl = computed (() => {
34- const match = props .uri .match (BSKY_POST_AT_URI_REGEX )
39+ /**
40+ * Resolve a bsky.app URL to an AT URI by resolving the handle to a DID.
41+ * If an AT URI is provided directly, returns it as-is.
42+ */
43+ async function resolveAtUri(): Promise <string | null > {
44+ if (props .uri ) return props .uri
45+
46+ if (! props .url ) return null
47+ const match = props .url .match (BLUESKY_URL_EXTRACT_REGEX )
3548 if (! match ) return null
36- const [, did, rkey] = match
37- return ` https://bsky.app/profile/${did }/post/${rkey } `
38- })
49+ const [, handle, rkey] = match
50+
51+ // If the handle is already a DID, build the AT URI directly
52+ if (handle .startsWith (' did:' )) {
53+ return ` at://${handle }/app.bsky.feed.post/${rkey } `
54+ }
55+
56+ // Resolve handle to DID
57+ const res = await $fetch <{ did: string }>(
58+ ` ${BLUESKY_API }/xrpc/com.atproto.identity.resolveHandle ` ,
59+ { query: { handle } },
60+ )
61+ return ` at://${res .did }/app.bsky.feed.post/${rkey } `
62+ }
63+
64+ const cacheKey = computed (() => ` bsky-post-${props .uri || props .url } ` )
3965
4066const { data : post, status } = useAsyncData (
41- ` bsky-post-${ props . uri } ` ,
67+ cacheKey . value ,
4268 async (): Promise <BlueskyPost | null > => {
69+ const atUri = await resolveAtUri ()
70+ if (! atUri ) return null
71+
4372 const response = await $fetch <{ posts: BlueskyPost [] }>(
4473 ` ${BLUESKY_API }/xrpc/app.bsky.feed.getPosts ` ,
45- { query: { uris: props . uri } },
74+ { query: { uris: atUri } },
4675 )
4776 return response .posts [0 ] ?? null
4877 },
4978 { lazy: true , server: false },
5079)
80+
81+ const postUrl = computed (() => {
82+ // Prefer the explicit URL prop if provided
83+ if (props .url ) return props .url
84+
85+ // Otherwise derive from the fetched post's AT URI
86+ const uri = post .value ?.uri ?? props .uri
87+ if (! uri ) return null
88+ const match = uri .match (BSKY_POST_AT_URI_REGEX )
89+ if (! match ) return null
90+ const [, did, rkey] = match
91+ return ` https://bsky.app/profile/${did }/post/${rkey } `
92+ })
5193 </script >
5294
5395<template >
@@ -63,7 +105,7 @@ const { data: post, status } = useAsyncData(
63105 :href =" postUrl ?? '#'"
64106 target =" _blank"
65107 rel =" noopener noreferrer"
66- class =" block rounded-lg border border-border bg-bg-subtle p-4 sm:p-5 no-underline hover:border-border-hover transition-colors duration-200"
108+ class =" not-prose block rounded-lg border border-border bg-bg-subtle p-4 sm:p-5 no-underline hover:border-border-hover transition-colors duration-200"
67109 >
68110 <!-- Author row -->
69111 <div class =" flex items-center gap-3 mb-3" >
0 commit comments