Skip to content

Commit 97aa15e

Browse files
committed
- <SkippableImage> to merge into <OgImagePost> as updating shouldShowImage after mounted is too later and reactivity lost in island component, so we must await fetching at the top-level of <script setup> to defer the component being mounted
@ fe
1 parent 65377c3 commit 97aa15e

2 files changed

Lines changed: 33 additions & 45 deletions

File tree

fe/src/components/OgImage/Post.vue

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,29 @@
33
<div class="flex-1 flex-col basis-1/2 m-4">
44
<p>{{ useSiteConfig().name }} {{ routePath }}</p>
55
<h2 v-if="firstPostPageForumName !== undefined">{{ firstPostPageForumName }}吧</h2>
6-
<p v-if="shouldShowImage">右侧为查询结果中第一张图片(不一定来自第一条帖子)</p>
6+
<p v-if="firstImageBase64 !== null">右侧为查询结果中第一张图片(不一定来自第一条帖子)</p>
77
<p v-if="currentQueryType !== 'postID'" class="m-0">下方为查询结果中第一条主题帖/回复帖/楼中楼</p>
88
<h1 v-if="firstThreadTitle !== undefined">{{ firstThreadTitle }}</h1>
99
<NewlineToBr is="h3" :text="firstPostContentTexts" wrapInSpan class="h-auto" />
1010
<template v-for="author in [firstPostAuthor]">
1111
<div :key="author.uid" v-if="author !== undefined" class="m-auto">
12-
<SkippableImage :src="toUserPortraitImageUrl(author.portrait)" />
12+
<img v-if="authorPortraitImageBase64 !== null" :src="authorPortraitImageBase64" />
1313
<span v-if="author.name !== null">{{ author.name }}</span>
1414
<span v-if="author.displayName !== null">{{ author.displayName }}</span>
1515
<span v-if="author.name === null && author.displayName === null">{{ author.uid }}</span>
1616
</div>
1717
</template>
1818
</div>
19-
<div v-if="firstImageUrl !== undefined && shouldShowImage" class="flex-auto basis-1/4">
20-
<SkippableImage @failToLoad="shouldShowImage = false" :src="firstImageUrl" class="h-screen object-contain" />
19+
<div v-if="firstImageBase64 !== null" class="flex-auto basis-1/4">
20+
<img :src="firstImageBase64" class="h-screen object-contain" />
2121
</div>
2222
</div>
2323
</template>
2424

2525
<script setup lang="ts">
2626
import type { UnwrapRef } from 'vue';
2727
28-
const { firstImageUrl } = defineProps<{
28+
const { firstPostAuthor, firstImageUrl } = defineProps<{
2929
routePath: string,
3030
currentQueryType: UnwrapRef<QueryFormDeps['currentQueryType']>,
3131
firstPostPageForumName?: string,
@@ -34,5 +34,32 @@ const { firstImageUrl } = defineProps<{
3434
firstPostAuthor?: User,
3535
firstImageUrl?: string
3636
}>();
37-
const shouldShowImage = ref(firstImageUrl !== undefined);
37+
const fetchImageAsBase64 = async (url: string): Promise<string | null> => {
38+
// eslint-disable-next-line @typescript-eslint/init-declarations
39+
let timeoutId: NodeJS.Timeout;
40+
const abortController = new AbortController();
41+
42+
return Promise.race([
43+
new Promise<null>(resolve => { timeoutId = setTimeout(() => { resolve(null) }, 5000) }),
44+
(async () => {
45+
try {
46+
// https://github.com/vercel/satori/issues/626#issuecomment-2401402201
47+
const response = await fetch(url, { signal: abortController.signal });
48+
const arrayBuffer = await response.arrayBuffer();
49+
const base64 = Buffer.from(arrayBuffer).toString('base64');
50+
const type = response.headers.get('content-type');
51+
52+
return `data:${type ?? 'image/png'};base64,${base64}`;
53+
} catch {
54+
return null;
55+
}
56+
})()
57+
]).finally(() => { clearTimeout(timeoutId); abortController.abort() });
58+
};
59+
const authorPortraitImageBase64 = ref<string | null>(
60+
firstPostAuthor === undefined ? null : await fetchImageAsBase64(toUserPortraitImageUrl(firstPostAuthor.portrait))
61+
);
62+
const firstImageBase64 = ref<string | null>(
63+
firstImageUrl === undefined ? null : await fetchImageAsBase64(firstImageUrl)
64+
);
3865
</script>

fe/src/components/SkippableImage.vue

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)