Files
mathew/frontend/app/components/ArticleCard.vue
2026-09-30 01:49:35 +02:00

78 lines
3.0 KiB
Vue

<script setup lang="ts">
/* One language version of an article, as the version switcher lists them. */
export interface ArticleVersion {
language: string
slug: string
title: string
updatedAt?: string
}
export interface ArticleMeta {
_id: string
slug: string
title: string
tags?: string[]
language?: string
/* set on translations: the canonical (original) version's slug */
baseSlug?: string | null
/* every language version the article exists in, from the single-article GET */
versions?: ArticleVersion[]
createdAt?: string
updatedAt?: string
}
const props = defineProps<{ article: ArticleMeta }>()
const { t, locale } = useI18n()
/* shown only when an article steps off the English default — a quiet badge
so the shelf's other languages are visible at a glance; when the article
exists in several languages, its codes ride together instead */
const language = computed(() => articleLanguage(props.article))
const languageLabel = computed(() => languageName(language.value))
const langs = computed(() => props.article.versions ?? [])
const multiLang = computed(() => langs.value.length > 1)
const langCodes = computed(() => langs.value.map((v) => v.language.toUpperCase()).join(' · '))
const langNames = computed(() => langs.value.map((v) => languageName(v.language)).join(', '))
/* where the card leads: the language address of the version it shows */
const href = computed(() =>
versionHref(identityOf(props.article), { language: language.value, slug: props.article.slug })
)
const updated = computed(() => {
const d = props.article.updatedAt ? new Date(props.article.updatedAt) : null
if (!d || Number.isNaN(d.getTime())) return ''
return d.toLocaleDateString(locale.value, { year: 'numeric', month: 'short', day: 'numeric' })
})
</script>
<template>
<NuxtLink :to="href" class="article-card">
<div class="card-top">
<span class="card-kicker">{{ article.baseSlug ? t('card.kickerTranslation') : t('card.kickerArticle') }}</span>
<span v-if="multiLang" class="card-langs" :title="t('card.existsIn', { names: langNames })">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
{{ langCodes }}
</span>
<span
v-else-if="language !== 'en'"
class="lang-badge"
:title="t('common.writtenIn', { lang: languageLabel })"
>{{ languageLabel }}</span
>
</div>
<h3 class="card-title" :lang="language">{{ article.title }}</h3>
<div v-if="article.tags?.length" class="tags">
<span v-for="tag in article.tags.slice(0, 3)" :key="tag" class="tag">{{ tag }}</span>
</div>
<div class="card-meta">
<span>{{ href }}</span>
<span v-if="updated">{{ t('card.updated', { date: updated }) }}</span>
</div>
</NuxtLink>
</template>