First commit

This commit is contained in:
2026-09-30 01:49:35 +02:00
commit 6edd03aace
109 changed files with 27020 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import type { ArticleMeta } from '~/components/ArticleCard.vue'
definePageMeta({ transition: { name: 'page', mode: 'out-in' } })
const api = `${useRuntimeConfig().public.apiBase}/api`
const route = useRoute()
const slug = computed(() => String(route.params.slug))
const { t } = useI18n()
const { th } = useAppLocale()
const { data: article, status, error } = await useFetch<ArticleMeta & { content?: string }>(
`${api}/articles/slug/${encodeURIComponent(slug.value)}`,
{ key: `article-${slug.value}-edit` }
)
useSeoMeta({ title: () => t('editor.metaEditing', { title: article.value?.title ?? slug.value }) })
</script>
<template>
<div>
<div v-if="error" class="empty" style="padding-top: 96px">
<div class="glyph">∄</div>
<h3>{{ t('editor.nothingToEdit') }}</h3>
<p v-html="th('editor.noArticleAt', { path: `/wiki/${slug}` })"></p>
<NuxtLink to="/new" class="btn btn-primary">{{ t('editor.createInstead') }}</NuxtLink>
</div>
<div v-else-if="status === 'pending'" style="padding-top: 48px">
<div class="skeleton" style="height: 40px; width: 40%; margin-bottom: 14px; border-radius: 8px" />
<div class="skeleton" style="height: 420px" />
</div>
<AuthGate v-else-if="article" :heading="t('editor.gateEdit', { title: article.title })">
<ArticleForm mode="edit" :article="article" :key="article._id" />
</AuthGate>
</div>
</template>

View File

@@ -0,0 +1,55 @@
<script setup lang="ts">
/* The article route is a thin host for the book that ArticleBookStack draws.
The open-article middleware has already laid the article on the library's
shelf — that is what the page in view is made of, and what the stack of
open pages is filed under — so all that is left to do here is speak for the
URL: the title, and the visitor's "recently viewed" line.
When nothing lives at the address, this is what says so, over the top of
whatever else is open. */
definePageMeta({ middleware: 'open-article' })
const route = useRoute()
const slug = computed(() => String(route.params.slug))
const { t } = useI18n()
const { th } = useAppLocale()
const { get, unreadable } = useArticleLibrary()
const article = computed(() => get(slug.value))
const { record } = useRecentlyViewed()
watch(
article,
(a) => {
if (import.meta.client && a?.slug && a?.title) record({ slug: a.slug, title: a.title })
},
{ immediate: true }
)
useSeoMeta({
title: () => article.value?.title ?? t('card.kickerArticle'),
})
/* what the shelf could not bring in, the API said why */
const missingStatus = computed(() => (article.value ? 0 : unreadable.value[slug.value] ?? 0))
</script>
<template>
<div class="reader-host">
<div v-if="!article" class="reader-note">
<div class="empty" style="padding-top: 64px">
<template v-if="missingStatus === 404">
<div class="glyph">∄</div>
<h3>{{ t('reader.notFoundTitle') }}</h3>
<p v-html="th('reader.notFoundText', { path: `/wiki/${slug}` })"></p>
<NuxtLink to="/new" class="btn btn-primary">{{ t('reader.writeTopic') }}</NuxtLink>
</template>
<template v-else>
<div class="glyph">!</div>
<h3>{{ t('reader.loadFailTitle') }}</h3>
<p v-html="t('common.apiShort')"></p>
<NuxtLink to="/" class="btn btn-ghost">{{ t('common.backHome') }}</NuxtLink>
</template>
</div>
</div>
</div>
</template>