Skip to content

Commit d3aa695

Browse files
fix: anonymous missing template or render function warning (#1353)
1 parent b47a1b2 commit d3aa695

10 files changed

Lines changed: 121 additions & 66 deletions

File tree

app/components/AuthorList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const props = defineProps<{
66
variant?: 'compact' | 'expanded'
77
}>()
88
9-
const { resolvedAuthors } = useAuthorProfiles(props.authors)
9+
const { resolvedAuthors } = useBlueskyAuthorProfiles(props.authors)
1010
</script>
1111

1212
<template>

app/components/EmbeddableBlueskyPost.client.vue renamed to app/components/EmbeddableBlueskyPost.vue

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -50,33 +50,35 @@ function onPostMessage(event: MessageEvent) {
5050
</script>
5151

5252
<template>
53-
<article class="bluesky-embed-container">
54-
<!-- Loading state -->
55-
<LoadingSpinner
56-
v-if="isLoading"
57-
:text="$t('blog.atproto.loading_bluesky_post')"
58-
aria-label="Loading Bluesky post..."
59-
class="loading-spinner"
60-
/>
61-
62-
<!-- Success state -->
63-
<div v-else-if="embedUrl" class="bluesky-embed-container">
64-
<iframe
65-
title="Bluesky Post"
66-
:data-bluesky-id="embeddedId"
67-
:src="embedUrl"
68-
width="100%"
69-
:height="iframeHeight"
70-
frameborder="0"
71-
scrolling="no"
53+
<ClientOnly>
54+
<article class="bluesky-embed-container">
55+
<!-- Loading state -->
56+
<LoadingSpinner
57+
v-if="isLoading"
58+
:text="$t('blog.atproto.loading_bluesky_post')"
59+
aria-label="Loading Bluesky post..."
60+
class="loading-spinner"
7261
/>
73-
</div>
7462

75-
<!-- Fallback state -->
76-
<a v-else :href="url" target="_blank" rel="noopener noreferrer">
77-
{{ $t('blog.atproto.view_on_bluesky') }}
78-
</a>
79-
</article>
63+
<!-- Success state -->
64+
<div v-else-if="embedUrl" class="bluesky-embed-container">
65+
<iframe
66+
title="Bluesky Post"
67+
:data-bluesky-id="embeddedId"
68+
:src="embedUrl"
69+
width="100%"
70+
:height="iframeHeight"
71+
frameborder="0"
72+
scrolling="no"
73+
/>
74+
</div>
75+
76+
<!-- Fallback state -->
77+
<a v-else :href="url" target="_blank" rel="noopener noreferrer">
78+
{{ $t('blog.atproto.view_on_bluesky') }}
79+
</a>
80+
</article>
81+
</ClientOnly>
8082
</template>
8183

8284
<style scoped>

app/components/OgImage/BlogPost.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const props = withDefaults(
1515
},
1616
)
1717
18-
const { resolvedAuthors } = useAuthorProfiles(props.authors)
18+
const { resolvedAuthors } = useBlueskyAuthorProfiles(props.authors)
1919
2020
const formattedDate = computed(() => {
2121
if (!props.date) return ''

app/composables/useAuthorProfiles.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Author, ResolvedAuthor } from '#shared/schemas/blog'
2+
3+
/**
4+
* Fetches author avatar URLs and profile links from the Bluesky API (AT Protocol).
5+
*
6+
* Makes a server-side request to `/api/atproto/bluesky-author-profiles`, which looks up
7+
* each author's Bluesky profile to retrieve their avatar. Results are cached for 1 day.
8+
*
9+
* While the fetch is pending (or if it fails), returns authors with `avatar: null`
10+
* and a constructed profile URL as fallback.
11+
*/
12+
export function useBlueskyAuthorProfiles(authors: Author[]) {
13+
const authorsJson = JSON.stringify(authors)
14+
15+
const { data } = useFetch('/api/atproto/bluesky-author-profiles', {
16+
query: {
17+
authors: authorsJson,
18+
},
19+
})
20+
21+
const resolvedAuthors = computed<ResolvedAuthor[]>(
22+
() => data.value?.authors ?? withoutBlueskyData(authors),
23+
)
24+
25+
return {
26+
resolvedAuthors,
27+
}
28+
}
29+
30+
function withoutBlueskyData(authors: Author[]): ResolvedAuthor[] {
31+
return authors.map(author => ({
32+
...author,
33+
avatar: null,
34+
profileUrl: author.blueskyHandle ? `https://bsky.app/profile/${author.blueskyHandle}` : null,
35+
}))
36+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import EmbeddableBlueskyPost from '~/components/EmbeddableBlueskyPost.client.vue'
1+
import EmbeddableBlueskyPost from '~/components/EmbeddableBlueskyPost.vue'
22

33
/**
44
* INFO: .md files are transformed into Vue SFCs by unplugin-vue-markdown during the Vite transform pipeline

lexicons.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"app.bsky.feed.getPostThread",
1111
"app.bsky.feed.getPosts",
1212
"app.bsky.feed.post",
13+
"com.atproto.identity.resolveHandle",
1314
"com.bad-example.identity.resolveMiniDoc",
1415
"site.standard.document"
1516
],
@@ -98,6 +99,10 @@
9899
"uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.richtext.facet",
99100
"cid": "bafyreidg56eo7zynf6ihz4xb627vwoqf5idnevkmwp7sxc4tijg6xngbu4"
100101
},
102+
"com.atproto.identity.resolveHandle": {
103+
"uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.resolveHandle",
104+
"cid": "bafyreigckmqtt3jrtzd7tvigjatnz6ajqyafs26h5pwcudwag2anedxnmu"
105+
},
101106
"com.atproto.label.defs": {
102107
"uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.label.defs",
103108
"cid": "bafyreig4hmnb2xkecyg4aaqfhr2rrcxxb3gsr4xks4rqb7rscrycalbrji"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"id": "com.atproto.identity.resolveHandle",
3+
"defs": {
4+
"main": {
5+
"type": "query",
6+
"errors": [
7+
{
8+
"name": "HandleNotFound",
9+
"description": "The resolution process confirmed that the handle does not resolve to any DID."
10+
}
11+
],
12+
"output": {
13+
"schema": {
14+
"type": "object",
15+
"required": ["did"],
16+
"properties": {
17+
"did": {
18+
"type": "string",
19+
"format": "did"
20+
}
21+
}
22+
},
23+
"encoding": "application/json"
24+
},
25+
"parameters": {
26+
"type": "params",
27+
"required": ["handle"],
28+
"properties": {
29+
"handle": {
30+
"type": "string",
31+
"format": "handle",
32+
"description": "The handle to resolve."
33+
}
34+
}
35+
},
36+
"description": "Resolves an atproto handle (hostname) to a DID. Does not necessarily bi-directionally verify against the the DID document."
37+
}
38+
},
39+
"$type": "com.atproto.lexicon.schema",
40+
"lexicon": 1
41+
}
File renamed without changes.

server/api/atproto/bluesky-oembed.get.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
BLUESKY_URL_EXTRACT_REGEX,
99
} from '#shared/utils/constants'
1010
import { type BlueskyOEmbedResponse, BlueskyOEmbedRequestSchema } from '#shared/schemas/atproto'
11+
import { Client } from '@atproto/lex'
12+
import * as com from '#shared/types/lexicons/com'
1113

1214
export default defineCachedEventHandler(
1315
async (event): Promise<BlueskyOEmbedResponse> => {
@@ -21,15 +23,14 @@ export default defineCachedEventHandler(
2123
* If the schema passes, this regex is mathematically guaranteed to match and contain both capture groups.
2224
* Match returns ["profile/danielroe.dev/post/123", "danielroe.dev", "123"] — only want the two capture groups, the full match string is discarded.
2325
*/
24-
const [, handle, postId] = url.match(BLUESKY_URL_EXTRACT_REGEX)! as [string, string, string]
26+
const [, handle, postId] = url.match(BLUESKY_URL_EXTRACT_REGEX)! as [
27+
string,
28+
`${string}.${string}`,
29+
string,
30+
]
2531

26-
// INFO: Resolve handle to DID using Bluesky's public API
27-
const { did } = await $fetch<{ did: string }>(
28-
`${BLUESKY_API}com.atproto.identity.resolveHandle`,
29-
{
30-
query: { handle },
31-
},
32-
)
32+
const client = new Client({ service: BLUESKY_API })
33+
const { did } = await client.call(com.atproto.identity.resolveHandle, { handle })
3334

3435
// INFO: Construct the embed URL with the DID
3536
const embedUrl = `${BLUESKY_EMBED_BASE_ROUTE}/embed/${did}/app.bsky.feed.post/${postId}?colorMode=${colorMode}`

0 commit comments

Comments
 (0)