102 lines
2.8 KiB
Vue
102 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { t } from '@/i18n'
|
|
|
|
import { computed, ref, watch } from 'vue'
|
|
import FallbackChip from '@/components/FallbackChip.vue'
|
|
import type { MediaItem } from '@/composables/useMedia'
|
|
import type { Site } from '@/composables/useSites'
|
|
import { hostOf } from '@/lib/directory'
|
|
import { chipLabel } from '@/lib/seoCopy'
|
|
import {
|
|
socialDescription,
|
|
socialTitle,
|
|
type SeoPreviewInput,
|
|
type SocialImageSource,
|
|
} from '@/lib/seoPreview'
|
|
|
|
const props = defineProps<{
|
|
input: SeoPreviewInput
|
|
site: Site | undefined
|
|
imageSource: SocialImageSource
|
|
imageName: string
|
|
imageItem: MediaItem | null
|
|
}>()
|
|
|
|
const imageFailed = ref(false)
|
|
|
|
watch(
|
|
() => props.imageItem?.url,
|
|
() => {
|
|
imageFailed.value = false
|
|
},
|
|
)
|
|
|
|
const host = computed(() => hostOf(props.site?.base_url ?? ''))
|
|
const title = computed(() => socialTitle(props.input))
|
|
const description = computed(() => socialDescription(props.input))
|
|
const chips = computed(() => {
|
|
const imageChips: Record<SocialImageSource, string | null> = {
|
|
social: null,
|
|
cover: chipLabel('cover_image'),
|
|
site: chipLabel('site_image'),
|
|
none: chipLabel('no_image'),
|
|
}
|
|
return [
|
|
props.input.seo.social_title ? null : chipLabel('meta_title'),
|
|
props.input.seo.social_description ? null : chipLabel('meta_description'),
|
|
imageChips[props.imageSource],
|
|
].filter((chip): chip is string => chip !== null)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-col gap-2" data-testid="social-card">
|
|
<span class="font-semibold text-highlighted">{{ t('Social card') }}</span>
|
|
<UCard
|
|
variant="outline"
|
|
:ui="{
|
|
root: 'rounded-none bg-default ring-0 border border-default divide-y-0 shadow-none',
|
|
body: 'p-0 sm:p-0',
|
|
}"
|
|
>
|
|
<div
|
|
class="social-card-image relative flex aspect-191/100 items-center justify-center overflow-hidden font-mono text-2xs text-muted"
|
|
data-testid="social-card-image"
|
|
>
|
|
<span v-if="imageSource !== 'none'">{{ imageName }}</span>
|
|
<img
|
|
v-if="imageItem && !imageFailed"
|
|
:src="imageItem.url"
|
|
alt=""
|
|
class="absolute inset-0 size-full object-cover"
|
|
@error="imageFailed = true"
|
|
/>
|
|
</div>
|
|
<div class="flex flex-col gap-0.5 border-t border-default px-3 py-2.5">
|
|
<span class="text-2xs/4 text-muted uppercase">{{ host }}</span>
|
|
<span
|
|
class="truncate font-semibold text-highlighted"
|
|
data-testid="social-card-title"
|
|
>{{ title }}</span
|
|
>
|
|
<span
|
|
class="line-clamp-2 text-xs/4.5 text-toned"
|
|
data-testid="social-card-description"
|
|
>{{ description }}</span
|
|
>
|
|
</div>
|
|
</UCard>
|
|
<div v-if="chips.length > 0" class="flex flex-wrap gap-1.5">
|
|
<FallbackChip v-for="chip in chips" :key="chip" :label="chip" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.social-card-image {
|
|
background:
|
|
repeating-linear-gradient(135deg, var(--ui-border) 0 1px, transparent 1px 12px),
|
|
var(--rc-sand);
|
|
}
|
|
</style>
|