Files
RenewCMS/web/src/components/SocialCardPreview.vue
T

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>