56 lines
1.7 KiB
Vue
56 lines
1.7 KiB
Vue
<script setup lang="ts">
|
|
interface OgData {
|
|
image?: string
|
|
title?: string
|
|
description?: string
|
|
url?: string
|
|
}
|
|
|
|
const props = defineProps<{
|
|
link: string
|
|
}>()
|
|
|
|
// Unique key per component instance so multiple BlogLink usages (even of the
|
|
// same URL) don't share/collide their async data state.
|
|
const instanceId = useId()
|
|
const { data: og } = await useAsyncData<Partial<OgData>>(
|
|
`blog-link-${instanceId}`,
|
|
() => $fetch('/api/ogimage', { params: { url: props.link } }),
|
|
{ default: () => ({}) }
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<a
|
|
:href="props.link" target="_blank" rel="noopener noreferrer"
|
|
class="block ml-auto mr-auto mt-2 mb-2 sm:max-w-[55%] max-w-[75%] rounded-xl overflow-hidden border border-slate-200 hover:border-slate-400 transition-colors bg-white group no-underline"
|
|
>
|
|
|
|
<img
|
|
v-if="og?.image" :src="og.image" :alt="og?.title || props.link"
|
|
class="w-full max-h-[320px] object-cover block" loading="lazy"
|
|
>
|
|
|
|
<div v-if="og?.title || og?.description" class="p-4">
|
|
<h3 v-if="og?.title" class="text-lg font-semibold text-slate-800 group-hover:underline">{{ og.title }}</h3>
|
|
<p v-if="og?.description" class="text-sm text-slate-500 mt-1 line-clamp-2">{{ og.description }}</p>
|
|
<p class="text-xs text-slate-400 mt-2 pb-1 leading-relaxed truncate">{{ props.link }}</p>
|
|
</div>
|
|
|
|
<!-- Fallback when no metadata could be retrieved -->
|
|
<div v-else class="p-4">
|
|
<h3 class="text-lg text-center font-semibold text-slate-800 group-hover:underline break-all">{{ props.link }}</h3>
|
|
</div>
|
|
|
|
<div v-if="$slots.default" class="ml-auto mr-auto mt-2 w-fit max-w-[80%] slot pb-4">
|
|
<slot />
|
|
</div>
|
|
</a>
|
|
</template>
|
|
|
|
<style lang="css" scoped>
|
|
.slot > * {
|
|
font-size: 16px;
|
|
}
|
|
</style>
|