82 lines
2.7 KiB
Vue
82 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import type { ArticleMeta } from '~/components/ArticleCard.vue'
|
|
|
|
/* matches the article page's transition so opening/closing the reader
|
|
slides one surface away as the next arrives (see main.scss) */
|
|
definePageMeta({ transition: { name: 'page', mode: 'out-in' } })
|
|
|
|
const api = `${useRuntimeConfig().public.apiBase}/api`
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const { t } = useI18n()
|
|
const { show: openSearch } = useSearch()
|
|
|
|
/* the bar opens the Ctrl/⌘+K popup rather than searching on the page itself,
|
|
but it still reads back the query the address carries — the popup's
|
|
"all results" link lands here with one */
|
|
const askedQuery = computed(() => String(route.query.q ?? ''))
|
|
|
|
/* fetch one article from the API and open it */
|
|
const randomPending = ref(false)
|
|
async function showRandomArticle() {
|
|
if (randomPending.value) return
|
|
randomPending.value = true
|
|
try {
|
|
const article = await $fetch<ArticleMeta>(`${api}/articles/random`)
|
|
/* the canonical slug is the article's address, and the middleware there
|
|
turns it onto the reader's language; an orphaned translation answers to
|
|
no such address, so that one still goes straight to its own version */
|
|
await router.push(
|
|
article.baseSlug
|
|
? versionHref(identityOf(article), articleLanguage(article))
|
|
: `/wiki/${encodeURIComponent(article.slug)}`
|
|
)
|
|
} catch {
|
|
// no articles yet or API unreachable — the banner below already covers API issues
|
|
} finally {
|
|
randomPending.value = false
|
|
}
|
|
}
|
|
|
|
useSeoMeta({
|
|
title: () => t('home.metaTitle'),
|
|
description: () => t('home.metaDescription'),
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="home-main is-home">
|
|
<section class="hero">
|
|
<h1 v-html="t('home.hero')"></h1>
|
|
|
|
<div class="search-wrap">
|
|
<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
<circle cx="11" cy="11" r="7" />
|
|
<path d="m20 20-3.2-3.2" />
|
|
</svg>
|
|
<button
|
|
type="button"
|
|
class="search-input search-trigger"
|
|
:aria-label="t('common.searchArticles')"
|
|
@click="openSearch()"
|
|
>
|
|
{{ askedQuery || t('home.searchPlaceholder') }}
|
|
</button>
|
|
<span class="search-kbd">/</span>
|
|
</div>
|
|
|
|
<div class="hero-actions">
|
|
<NuxtLink to="/new" class="btn btn-primary">{{ t('home.writeNew') }}</NuxtLink>
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost"
|
|
:disabled="randomPending"
|
|
@click="showRandomArticle"
|
|
>
|
|
{{ t('home.random') }}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|