165 lines
5.0 KiB
Vue
165 lines
5.0 KiB
Vue
<script setup lang="ts">
|
|
import { kebabCase } from 'scule'
|
|
import type { ContentNavigationItem, Collections, DocsCollectionItem } from '@nuxt/content'
|
|
import { findPageHeadline } from '@nuxt/content/utils'
|
|
|
|
definePageMeta({
|
|
layout: 'docs',
|
|
})
|
|
|
|
const route = useRoute()
|
|
const { locale, isEnabled, t } = useDocusI18n()
|
|
const { isOpen } = useAssistant()
|
|
const appConfig = useAppConfig()
|
|
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
|
const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
|
|
|
|
const [{ data: page }, { data: surround }] = await Promise.all([
|
|
useAsyncData(kebabCase(route.path), () => queryCollection(collectionName.value as keyof Collections).path(route.path).first() as Promise<DocsCollectionItem>),
|
|
useAsyncData(`${kebabCase(route.path)}-surround`, () => {
|
|
return queryCollectionItemSurroundings(collectionName.value as keyof Collections, route.path, {
|
|
fields: ['description'],
|
|
})
|
|
}),
|
|
])
|
|
|
|
if (!page.value) {
|
|
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
}
|
|
|
|
const title = page.value.seo?.title || page.value.title
|
|
const description = page.value.seo?.description || page.value.description
|
|
|
|
const headline = ref(findPageHeadline(navigation?.value, page.value?.path))
|
|
const breadcrumbs = computed(() => findPageBreadcrumbs(navigation?.value, page.value?.path || ''))
|
|
|
|
// Set `hideHeader: true` in a page's frontmatter to skip the title/description
|
|
// block entirely (e.g. for a page that builds its own custom layout).
|
|
const hideHeader = computed(() => !!(page.value as unknown as Record<string, unknown>)?.hideHeader)
|
|
|
|
// Set `hideCopyPage: true` in a page's frontmatter to hide the "Copy page"
|
|
// dropdown (copy link / view as markdown / open in ChatGPT / Claude).
|
|
const hideCopyPage = computed(() => !!(page.value as unknown as Record<string, unknown>)?.hideCopyPage)
|
|
|
|
// Set `hideToc: true` in a page's frontmatter to hide the right-hand
|
|
// "On this page" table-of-contents sidebar.
|
|
const hideToc = computed(() => !!(page.value as unknown as Record<string, unknown>)?.hideToc)
|
|
|
|
useSeo({
|
|
title,
|
|
description,
|
|
type: 'article',
|
|
modifiedAt: (page.value as unknown as Record<string, unknown>).modifiedAt as string | undefined,
|
|
breadcrumbs,
|
|
})
|
|
watch(() => navigation?.value, () => {
|
|
headline.value = findPageHeadline(navigation?.value, page.value?.path) || headline.value
|
|
})
|
|
|
|
defineOgImage('Docs', {
|
|
headline: headline.value,
|
|
title: title?.slice(0, 60),
|
|
description: formatOgDescription(title, description),
|
|
})
|
|
|
|
const github = computed(() => appConfig.github ? appConfig.github : null)
|
|
const giteaUrl = computed(() => appConfig.socials?.gitea as string | undefined)
|
|
|
|
// "Edit this page" points at Gitea (git.djeex.fr), not the GitHub mirror.
|
|
// Gitea's edit route is `/{owner}/{repo}/_edit/{branch}/{path}` (note the
|
|
// leading underscore — different from GitHub's `/edit/{branch}/{path}`).
|
|
const editLink = computed(() => {
|
|
if (!giteaUrl.value) {
|
|
return
|
|
}
|
|
|
|
return [
|
|
giteaUrl.value,
|
|
'_edit',
|
|
'main',
|
|
'content',
|
|
`${page.value?.stem}.${page.value?.extension}`,
|
|
].filter(Boolean).join('/')
|
|
})
|
|
|
|
// Add the page path to the prerender list
|
|
addPrerenderPath(`/raw${route.path}.md`)
|
|
</script>
|
|
|
|
<template>
|
|
<UPage
|
|
v-if="page"
|
|
class="relative"
|
|
:ui="isOpen ? { center: 'lg:col-span-10' } : undefined"
|
|
>
|
|
<UPageHeader
|
|
v-if="!hideHeader"
|
|
:title="page.title"
|
|
:description="page.description"
|
|
:headline="headline"
|
|
:ui="{
|
|
wrapper: 'flex-row items-center flex-wrap justify-between',
|
|
}"
|
|
>
|
|
<template #links>
|
|
<UButton
|
|
v-for="(link, index) in (page as DocsCollectionItem).links"
|
|
:key="index"
|
|
size="sm"
|
|
v-bind="link"
|
|
/>
|
|
|
|
<DocsPageHeaderLinks v-if="!hideCopyPage" />
|
|
</template>
|
|
</UPageHeader>
|
|
|
|
<UPageBody>
|
|
<ContentRenderer
|
|
v-if="page"
|
|
:value="page"
|
|
/>
|
|
|
|
<USeparator v-if="giteaUrl || github">
|
|
<div
|
|
class="flex items-center gap-2 text-sm text-muted"
|
|
>
|
|
<UButton
|
|
v-if="editLink"
|
|
variant="link"
|
|
color="neutral"
|
|
:to="editLink"
|
|
target="_blank"
|
|
icon="i-lucide-pen"
|
|
:ui="{ leadingIcon: 'size-4' }"
|
|
>
|
|
{{ t('docs.edit') }}
|
|
</UButton>
|
|
<template v-if="giteaUrl">
|
|
<span>{{ t('common.or') }}</span>
|
|
<UButton
|
|
variant="link"
|
|
color="neutral"
|
|
:to="`${giteaUrl}/issues/new`"
|
|
target="_blank"
|
|
icon="i-lucide-alert-circle"
|
|
:ui="{ leadingIcon: 'size-4' }"
|
|
>
|
|
{{ t('docs.report') }}
|
|
</UButton>
|
|
</template>
|
|
</div>
|
|
</USeparator>
|
|
<UContentSurround :surround="surround" />
|
|
</UPageBody>
|
|
|
|
<template
|
|
v-if="!isOpen && !hideToc"
|
|
#right
|
|
>
|
|
<DocsAsideRight
|
|
:page="page"
|
|
/>
|
|
</template>
|
|
</UPage>
|
|
</template>
|