@vkuttyp/docus 5.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -0
- package/app/app.config.ts +33 -0
- package/app/app.vue +79 -0
- package/app/components/IconMenuToggle.vue +83 -0
- package/app/components/LanguageSelect.vue +83 -0
- package/app/components/OgImage/OgImageDocs.vue +78 -0
- package/app/components/OgImage/OgImageLanding.vue +75 -0
- package/app/components/app/AppFooter.vue +11 -0
- package/app/components/app/AppFooterLeft.vue +5 -0
- package/app/components/app/AppFooterRight.vue +30 -0
- package/app/components/app/AppHeader.vue +82 -0
- package/app/components/app/AppHeaderBody.vue +13 -0
- package/app/components/app/AppHeaderCTA.vue +3 -0
- package/app/components/app/AppHeaderCenter.vue +10 -0
- package/app/components/app/AppHeaderLogo.vue +16 -0
- package/app/components/docs/DocsAsideLeftBody.vue +12 -0
- package/app/components/docs/DocsAsideLeftTop.vue +3 -0
- package/app/components/docs/DocsAsideRightBottom.vue +18 -0
- package/app/components/docs/DocsPageHeaderLinks.vue +96 -0
- package/app/composables/useDocusI18n.ts +35 -0
- package/app/error.vue +79 -0
- package/app/layouts/default.vue +5 -0
- package/app/layouts/docs.vue +15 -0
- package/app/pages/[[lang]]/[...slug].vue +145 -0
- package/app/plugins/i18n.ts +40 -0
- package/app/templates/landing.vue +44 -0
- package/app/types/index.d.ts +42 -0
- package/app/utils/navigation.ts +7 -0
- package/app/utils/prerender.ts +12 -0
- package/content.config.ts +67 -0
- package/i18n/locales/ar.json +22 -0
- package/i18n/locales/be.json +23 -0
- package/i18n/locales/bg.json +22 -0
- package/i18n/locales/bn.json +22 -0
- package/i18n/locales/ca.json +22 -0
- package/i18n/locales/ckb.json +18 -0
- package/i18n/locales/cs.json +22 -0
- package/i18n/locales/da.json +22 -0
- package/i18n/locales/de.json +22 -0
- package/i18n/locales/el.json +22 -0
- package/i18n/locales/en.json +22 -0
- package/i18n/locales/es.json +22 -0
- package/i18n/locales/et.json +22 -0
- package/i18n/locales/fi.json +22 -0
- package/i18n/locales/fr.json +22 -0
- package/i18n/locales/he.json +22 -0
- package/i18n/locales/hi.json +22 -0
- package/i18n/locales/hy.json +22 -0
- package/i18n/locales/it.json +22 -0
- package/i18n/locales/ja.json +22 -0
- package/i18n/locales/kk.json +22 -0
- package/i18n/locales/km.json +22 -0
- package/i18n/locales/ko.json +22 -0
- package/i18n/locales/ky.json +22 -0
- package/i18n/locales/lb.json +22 -0
- package/i18n/locales/ms.json +22 -0
- package/i18n/locales/nb.json +22 -0
- package/i18n/locales/nl.json +22 -0
- package/i18n/locales/pl.json +23 -0
- package/i18n/locales/ro.json +22 -0
- package/i18n/locales/ru.json +23 -0
- package/i18n/locales/sl.json +22 -0
- package/i18n/locales/sv.json +22 -0
- package/i18n/locales/uk.json +22 -0
- package/i18n/locales/ur.json +22 -0
- package/i18n/locales/vi.json +22 -0
- package/modules/config.ts +116 -0
- package/modules/css.ts +32 -0
- package/modules/routing.ts +41 -0
- package/nuxt.config.ts +84 -0
- package/nuxt.schema.ts +218 -0
- package/package.json +55 -0
- package/server/mcp/tools/get-page.ts +60 -0
- package/server/mcp/tools/list-pages.ts +60 -0
- package/server/routes/raw/[...slug].md.get.ts +45 -0
- package/server/utils/content.ts +37 -0
- package/utils/git.ts +110 -0
- package/utils/meta.ts +29 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const appConfig = useAppConfig()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<UColorModeImage
|
|
7
|
+
v-if="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
|
|
8
|
+
:light="appConfig.header?.logo?.light || appConfig.header?.logo?.dark"
|
|
9
|
+
:dark="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
|
|
10
|
+
:alt="appConfig.header?.logo?.alt || appConfig.header?.title"
|
|
11
|
+
class="h-6 w-auto shrink-0"
|
|
12
|
+
/>
|
|
13
|
+
<span v-else>
|
|
14
|
+
{{ appConfig.header?.title || '{appConfig.header.title}' }}
|
|
15
|
+
</span>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ContentNavigationItem } from '@nuxt/content'
|
|
3
|
+
|
|
4
|
+
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<UContentNavigation
|
|
9
|
+
highlight
|
|
10
|
+
:navigation="navigation"
|
|
11
|
+
/>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const appConfig = useAppConfig()
|
|
3
|
+
const { t } = useDocusI18n()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div
|
|
8
|
+
v-if="appConfig.toc?.bottom?.links?.length"
|
|
9
|
+
class="hidden lg:block space-y-6"
|
|
10
|
+
>
|
|
11
|
+
<USeparator type="dashed" />
|
|
12
|
+
|
|
13
|
+
<UPageLinks
|
|
14
|
+
:title="appConfig.toc?.bottom?.title || t('docs.links')"
|
|
15
|
+
:links="appConfig.toc?.bottom?.links"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useClipboard } from '@vueuse/core'
|
|
3
|
+
import { useRuntimeConfig } from '#imports'
|
|
4
|
+
|
|
5
|
+
const route = useRoute()
|
|
6
|
+
const toast = useToast()
|
|
7
|
+
const appBaseURL = useRuntimeConfig().app?.baseURL || '/'
|
|
8
|
+
|
|
9
|
+
const { copy, copied } = useClipboard()
|
|
10
|
+
const { t } = useDocusI18n()
|
|
11
|
+
|
|
12
|
+
const markdownLink = computed(() => `${window?.location?.origin}${appBaseURL}raw${route.path}.md`)
|
|
13
|
+
const items = [
|
|
14
|
+
[{
|
|
15
|
+
label: t('docs.copy.link'),
|
|
16
|
+
icon: 'i-lucide-link',
|
|
17
|
+
onSelect() {
|
|
18
|
+
copy(markdownLink.value)
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
label: t('docs.copy.view'),
|
|
23
|
+
icon: 'i-simple-icons:markdown',
|
|
24
|
+
target: '_blank',
|
|
25
|
+
to: markdownLink.value,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: t('docs.copy.gpt'),
|
|
29
|
+
icon: 'i-simple-icons:openai',
|
|
30
|
+
target: '_blank',
|
|
31
|
+
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: t('docs.copy.claude'),
|
|
35
|
+
icon: 'i-simple-icons:anthropic',
|
|
36
|
+
target: '_blank',
|
|
37
|
+
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
|
38
|
+
}],
|
|
39
|
+
[
|
|
40
|
+
{
|
|
41
|
+
label: 'Copy MCP Server URL',
|
|
42
|
+
icon: 'i-lucide-link',
|
|
43
|
+
onSelect() {
|
|
44
|
+
copy(`${window?.location?.origin}${appBaseURL}mcp`)
|
|
45
|
+
toast.add({
|
|
46
|
+
title: 'Copied to clipboard',
|
|
47
|
+
icon: 'i-lucide-check-circle',
|
|
48
|
+
})
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Add MCP Server',
|
|
53
|
+
icon: 'i-simple-icons:cursor',
|
|
54
|
+
target: '_blank',
|
|
55
|
+
to: `/mcp/deeplink`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
async function copyPage() {
|
|
61
|
+
const page = await $fetch<string>(`/raw${route.path}.md`)
|
|
62
|
+
copy(page)
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<UFieldGroup size="sm">
|
|
68
|
+
<UButton
|
|
69
|
+
:label="t('docs.copy.page')"
|
|
70
|
+
:icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
|
|
71
|
+
color="neutral"
|
|
72
|
+
variant="soft"
|
|
73
|
+
:ui="{
|
|
74
|
+
leadingIcon: 'text-neutral size-3.5',
|
|
75
|
+
}"
|
|
76
|
+
@click="copyPage"
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<UDropdownMenu
|
|
80
|
+
size="sm"
|
|
81
|
+
:items="items"
|
|
82
|
+
:content="{
|
|
83
|
+
align: 'end',
|
|
84
|
+
side: 'bottom',
|
|
85
|
+
sideOffset: 8,
|
|
86
|
+
}"
|
|
87
|
+
>
|
|
88
|
+
<UButton
|
|
89
|
+
icon="i-lucide-chevron-down"
|
|
90
|
+
color="neutral"
|
|
91
|
+
variant="soft"
|
|
92
|
+
class="border-l border-muted"
|
|
93
|
+
/>
|
|
94
|
+
</UDropdownMenu>
|
|
95
|
+
</UFieldGroup>
|
|
96
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { LocaleObject } from '@nuxtjs/i18n'
|
|
2
|
+
|
|
3
|
+
export const useDocusI18n = () => {
|
|
4
|
+
const config = useRuntimeConfig().public
|
|
5
|
+
const isEnabled = ref(!!config.i18n)
|
|
6
|
+
|
|
7
|
+
if (!isEnabled.value) {
|
|
8
|
+
const locale = useNuxtApp().$locale as string
|
|
9
|
+
const localeMessages = useNuxtApp().$localeMessages as Record<string, unknown>
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
isEnabled,
|
|
13
|
+
locale: ref(locale),
|
|
14
|
+
locales: [],
|
|
15
|
+
localePath: (path: string) => path,
|
|
16
|
+
switchLocalePath: () => {},
|
|
17
|
+
t: (key: string): string => {
|
|
18
|
+
const path = key.split('.')
|
|
19
|
+
return path.reduce((acc: unknown, curr) => (acc as Record<string, unknown>)?.[curr], localeMessages) as string
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { locale, t } = useI18n()
|
|
25
|
+
const filteredLocales = (config.docus as { filteredLocales: LocaleObject<string>[] })?.filteredLocales || []
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
isEnabled,
|
|
29
|
+
locale,
|
|
30
|
+
locales: filteredLocales,
|
|
31
|
+
t,
|
|
32
|
+
localePath: useLocalePath(),
|
|
33
|
+
switchLocalePath: useSwitchLocalePath(),
|
|
34
|
+
}
|
|
35
|
+
}
|
package/app/error.vue
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { NuxtError } from '#app'
|
|
3
|
+
import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
|
|
4
|
+
import * as nuxtUiLocales from '@nuxt/ui/locale'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
error: NuxtError
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const { locale, locales, isEnabled, t, switchLocalePath } = useDocusI18n()
|
|
11
|
+
|
|
12
|
+
const nuxtUiLocale = computed(() => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales] || nuxtUiLocales.en)
|
|
13
|
+
const lang = computed(() => nuxtUiLocale.value.code)
|
|
14
|
+
const dir = computed(() => nuxtUiLocale.value.dir)
|
|
15
|
+
|
|
16
|
+
useHead({
|
|
17
|
+
htmlAttrs: {
|
|
18
|
+
lang,
|
|
19
|
+
dir,
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const localizedError = computed(() => {
|
|
24
|
+
return {
|
|
25
|
+
...props.error,
|
|
26
|
+
statusMessage: t('common.error.title'),
|
|
27
|
+
message: t('common.error.description'),
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
useSeoMeta({
|
|
32
|
+
title: () => t('common.error.title'),
|
|
33
|
+
description: () => t('common.error.description'),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (isEnabled.value) {
|
|
37
|
+
const route = useRoute()
|
|
38
|
+
const defaultLocale = useRuntimeConfig().public.i18n.defaultLocale!
|
|
39
|
+
onMounted(() => {
|
|
40
|
+
const currentLocale = route.path.split('/')[1]
|
|
41
|
+
if (!locales.some(locale => locale.code === currentLocale)) {
|
|
42
|
+
return navigateTo(switchLocalePath(defaultLocale) as string)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
|
|
48
|
+
|
|
49
|
+
const { data: navigation } = await useAsyncData(`navigation_${collectionName.value}`, () => queryCollectionNavigation(collectionName.value as keyof PageCollections), {
|
|
50
|
+
transform: (data: ContentNavigationItem[]) => {
|
|
51
|
+
const rootResult = data.find(item => item.path === '/docs')?.children || data || []
|
|
52
|
+
|
|
53
|
+
return rootResult.find(item => item.path === `/${locale.value}`)?.children || rootResult
|
|
54
|
+
},
|
|
55
|
+
watch: [locale],
|
|
56
|
+
})
|
|
57
|
+
const { data: files } = useLazyAsyncData(`search_${collectionName.value}`, () => queryCollectionSearchSections(collectionName.value as keyof PageCollections), {
|
|
58
|
+
server: false,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
provide('navigation', navigation)
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<UApp :locale="nuxtUiLocale">
|
|
66
|
+
<AppHeader />
|
|
67
|
+
|
|
68
|
+
<UError :error="localizedError" />
|
|
69
|
+
|
|
70
|
+
<AppFooter />
|
|
71
|
+
|
|
72
|
+
<ClientOnly>
|
|
73
|
+
<LazyUContentSearch
|
|
74
|
+
:files="files"
|
|
75
|
+
:navigation="navigation"
|
|
76
|
+
/>
|
|
77
|
+
</ClientOnly>
|
|
78
|
+
</UApp>
|
|
79
|
+
</template>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { kebabCase } from 'scule'
|
|
3
|
+
import type { ContentNavigationItem, Collections, DocsCollectionItem } from '@nuxt/content'
|
|
4
|
+
import { findPageHeadline } from '@nuxt/content/utils'
|
|
5
|
+
import { addPrerenderPath } from '../../utils/prerender'
|
|
6
|
+
|
|
7
|
+
definePageMeta({
|
|
8
|
+
layout: 'docs',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
const { locale, isEnabled, t } = useDocusI18n()
|
|
13
|
+
const appConfig = useAppConfig()
|
|
14
|
+
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
|
15
|
+
|
|
16
|
+
const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
|
|
17
|
+
|
|
18
|
+
const [{ data: page }, { data: surround }] = await Promise.all([
|
|
19
|
+
useAsyncData(kebabCase(route.path), () => queryCollection(collectionName.value as keyof Collections).path(route.path).first() as Promise<DocsCollectionItem>),
|
|
20
|
+
useAsyncData(`${kebabCase(route.path)}-surround`, () => {
|
|
21
|
+
return queryCollectionItemSurroundings(collectionName.value as keyof Collections, route.path, {
|
|
22
|
+
fields: ['description'],
|
|
23
|
+
})
|
|
24
|
+
}),
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
if (!page.value) {
|
|
28
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Add the page path to the prerender list
|
|
32
|
+
addPrerenderPath(`/raw${route.path}.md`)
|
|
33
|
+
|
|
34
|
+
const title = page.value.seo?.title || page.value.title
|
|
35
|
+
const description = page.value.seo?.description || page.value.description
|
|
36
|
+
|
|
37
|
+
useSeoMeta({
|
|
38
|
+
title,
|
|
39
|
+
ogTitle: title,
|
|
40
|
+
description,
|
|
41
|
+
ogDescription: description,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const headline = ref(findPageHeadline(navigation?.value, page.value?.path))
|
|
45
|
+
watch(() => navigation?.value, () => {
|
|
46
|
+
headline.value = findPageHeadline(navigation?.value, page.value?.path) || headline.value
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
defineOgImageComponent('Docs', {
|
|
50
|
+
headline: headline.value,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const github = computed(() => appConfig.github ? appConfig.github : null)
|
|
54
|
+
|
|
55
|
+
const editLink = computed(() => {
|
|
56
|
+
if (!github.value) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return [
|
|
61
|
+
github.value.url,
|
|
62
|
+
'edit',
|
|
63
|
+
github.value.branch,
|
|
64
|
+
github.value.rootDir,
|
|
65
|
+
'content',
|
|
66
|
+
`${page.value?.stem}.${page.value?.extension}`,
|
|
67
|
+
].filter(Boolean).join('/')
|
|
68
|
+
})
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<UPage v-if="page">
|
|
73
|
+
<UPageHeader
|
|
74
|
+
:title="page.title"
|
|
75
|
+
:description="page.description"
|
|
76
|
+
:headline="headline"
|
|
77
|
+
:ui="{
|
|
78
|
+
wrapper: 'flex-row items-center flex-wrap justify-between',
|
|
79
|
+
}"
|
|
80
|
+
>
|
|
81
|
+
<template #links>
|
|
82
|
+
<UButton
|
|
83
|
+
v-for="(link, index) in (page as DocsCollectionItem).links"
|
|
84
|
+
:key="index"
|
|
85
|
+
size="sm"
|
|
86
|
+
v-bind="link"
|
|
87
|
+
/>
|
|
88
|
+
|
|
89
|
+
<DocsPageHeaderLinks />
|
|
90
|
+
</template>
|
|
91
|
+
</UPageHeader>
|
|
92
|
+
|
|
93
|
+
<UPageBody>
|
|
94
|
+
<ContentRenderer
|
|
95
|
+
v-if="page"
|
|
96
|
+
:value="page"
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<USeparator>
|
|
100
|
+
<div
|
|
101
|
+
v-if="github"
|
|
102
|
+
class="flex items-center gap-2 text-sm text-muted"
|
|
103
|
+
>
|
|
104
|
+
<UButton
|
|
105
|
+
variant="link"
|
|
106
|
+
color="neutral"
|
|
107
|
+
:to="editLink"
|
|
108
|
+
target="_blank"
|
|
109
|
+
icon="i-lucide-pen"
|
|
110
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
111
|
+
>
|
|
112
|
+
{{ t('docs.edit') }}
|
|
113
|
+
</UButton>
|
|
114
|
+
<span>{{ t('common.or') }}</span>
|
|
115
|
+
<UButton
|
|
116
|
+
variant="link"
|
|
117
|
+
color="neutral"
|
|
118
|
+
:to="`${github.url}/issues/new/choose`"
|
|
119
|
+
target="_blank"
|
|
120
|
+
icon="i-lucide-alert-circle"
|
|
121
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
122
|
+
>
|
|
123
|
+
{{ t('docs.report') }}
|
|
124
|
+
</UButton>
|
|
125
|
+
</div>
|
|
126
|
+
</USeparator>
|
|
127
|
+
<UContentSurround :surround="surround" />
|
|
128
|
+
</UPageBody>
|
|
129
|
+
|
|
130
|
+
<template
|
|
131
|
+
v-if="page?.body?.toc?.links?.length"
|
|
132
|
+
#right
|
|
133
|
+
>
|
|
134
|
+
<UContentToc
|
|
135
|
+
highlight
|
|
136
|
+
:title="appConfig.toc?.title || t('docs.toc')"
|
|
137
|
+
:links="page.body?.toc?.links"
|
|
138
|
+
>
|
|
139
|
+
<template #bottom>
|
|
140
|
+
<DocsAsideRightBottom />
|
|
141
|
+
</template>
|
|
142
|
+
</UContentToc>
|
|
143
|
+
</template>
|
|
144
|
+
</UPage>
|
|
145
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import en from '../../i18n/locales/en.json'
|
|
2
|
+
import type { RouteLocationNormalized } from 'vue-router'
|
|
3
|
+
|
|
4
|
+
export default defineNuxtPlugin(async () => {
|
|
5
|
+
const nuxtApp = useNuxtApp()
|
|
6
|
+
|
|
7
|
+
const i18nConfig = nuxtApp.$config.public.i18n
|
|
8
|
+
|
|
9
|
+
// If i18n is not enabled, fetch and provide the configured locale in app config
|
|
10
|
+
if (!i18nConfig) {
|
|
11
|
+
let locale = 'en'
|
|
12
|
+
let resolvedMessages: Record<string, unknown> = en
|
|
13
|
+
|
|
14
|
+
const appConfig = useAppConfig()
|
|
15
|
+
const configuredLocale = appConfig.docus.locale
|
|
16
|
+
if (configuredLocale !== 'en') {
|
|
17
|
+
const localeMessages = await import(`../../i18n/locales/${configuredLocale}.json`)
|
|
18
|
+
if (!localeMessages) {
|
|
19
|
+
console.warn(`[Docus] Missing locale file for "${configuredLocale}". Falling back to "en".`)
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
locale = configuredLocale
|
|
23
|
+
resolvedMessages = localeMessages
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
nuxtApp.provide('locale', locale)
|
|
28
|
+
nuxtApp.provide('localeMessages', resolvedMessages)
|
|
29
|
+
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
addRouteMiddleware((to: RouteLocationNormalized) => {
|
|
34
|
+
if (to.path === '/') {
|
|
35
|
+
const cookieLocale = useCookie('i18n_redirected').value || i18nConfig.defaultLocale || 'en'
|
|
36
|
+
|
|
37
|
+
return navigateTo(`/${cookieLocale}`)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Collections } from '@nuxt/content'
|
|
3
|
+
|
|
4
|
+
const route = useRoute()
|
|
5
|
+
const { locale, isEnabled } = useDocusI18n()
|
|
6
|
+
|
|
7
|
+
// Dynamic collection name based on i18n status
|
|
8
|
+
const collectionName = computed(() => isEnabled.value ? `landing_${locale.value}` : 'landing')
|
|
9
|
+
|
|
10
|
+
const { data: page } = await useAsyncData(collectionName.value, () => queryCollection(collectionName.value as keyof Collections).path(route.path).first())
|
|
11
|
+
if (!page.value) {
|
|
12
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const title = page.value.seo?.title || page.value.title
|
|
16
|
+
const description = page.value.seo?.description || page.value.description
|
|
17
|
+
|
|
18
|
+
useSeoMeta({
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
ogTitle: title,
|
|
22
|
+
ogDescription: description,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
if (page.value?.seo?.ogImage) {
|
|
26
|
+
useSeoMeta({
|
|
27
|
+
ogImage: page.value.seo.ogImage,
|
|
28
|
+
twitterImage: page.value.seo.ogImage,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
defineOgImageComponent('Landing', {
|
|
33
|
+
title,
|
|
34
|
+
description,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<ContentRenderer
|
|
41
|
+
v-if="page"
|
|
42
|
+
:value="page"
|
|
43
|
+
/>
|
|
44
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare module 'nuxt/schema' {
|
|
2
|
+
interface AppConfig {
|
|
3
|
+
docus: {
|
|
4
|
+
locale: string
|
|
5
|
+
}
|
|
6
|
+
seo: {
|
|
7
|
+
titleTemplate: string
|
|
8
|
+
title: string
|
|
9
|
+
description: string
|
|
10
|
+
}
|
|
11
|
+
header: {
|
|
12
|
+
title: string
|
|
13
|
+
logo: {
|
|
14
|
+
light: string
|
|
15
|
+
dark: string
|
|
16
|
+
alt: string
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
socials: Record<string, string>
|
|
20
|
+
toc: {
|
|
21
|
+
title: string
|
|
22
|
+
bottom: {
|
|
23
|
+
title: string
|
|
24
|
+
links: {
|
|
25
|
+
icon: string
|
|
26
|
+
label: string
|
|
27
|
+
to: string
|
|
28
|
+
target: string
|
|
29
|
+
}[]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
github: {
|
|
33
|
+
owner: string
|
|
34
|
+
name: string
|
|
35
|
+
url: string
|
|
36
|
+
branch: string
|
|
37
|
+
rootDir?: string
|
|
38
|
+
} | false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const addPrerenderPath = (path: string) => {
|
|
2
|
+
const event = useRequestEvent()
|
|
3
|
+
if (event) {
|
|
4
|
+
event.node.res.setHeader(
|
|
5
|
+
'x-nitro-prerender',
|
|
6
|
+
[
|
|
7
|
+
event.node.res.getHeader('x-nitro-prerender'),
|
|
8
|
+
path,
|
|
9
|
+
].filter(Boolean).join(','),
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { DefinedCollection } from '@nuxt/content'
|
|
2
|
+
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
|
|
3
|
+
import { useNuxt } from '@nuxt/kit'
|
|
4
|
+
import { joinURL } from 'ufo'
|
|
5
|
+
|
|
6
|
+
const { options } = useNuxt()
|
|
7
|
+
const cwd = joinURL(options.rootDir, 'content')
|
|
8
|
+
const locales = options.i18n?.locales
|
|
9
|
+
|
|
10
|
+
const createDocsSchema = () => z.object({
|
|
11
|
+
links: z.array(z.object({
|
|
12
|
+
label: z.string(),
|
|
13
|
+
icon: z.string(),
|
|
14
|
+
to: z.string(),
|
|
15
|
+
target: z.string().optional(),
|
|
16
|
+
})).optional(),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
let collections: Record<string, DefinedCollection>
|
|
20
|
+
|
|
21
|
+
if (locales && Array.isArray(locales)) {
|
|
22
|
+
collections = {}
|
|
23
|
+
for (const locale of locales) {
|
|
24
|
+
const code = typeof locale === 'string' ? locale : locale.code
|
|
25
|
+
|
|
26
|
+
collections[`landing_${code}`] = defineCollection({
|
|
27
|
+
type: 'page',
|
|
28
|
+
source: {
|
|
29
|
+
cwd,
|
|
30
|
+
include: `${code}/index.md`,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
collections[`docs_${code}`] = defineCollection({
|
|
35
|
+
type: 'page',
|
|
36
|
+
source: {
|
|
37
|
+
cwd,
|
|
38
|
+
include: `${code}/**/*`,
|
|
39
|
+
prefix: `/${code}`,
|
|
40
|
+
exclude: [`${code}/index.md`],
|
|
41
|
+
},
|
|
42
|
+
schema: createDocsSchema(),
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
collections = {
|
|
48
|
+
landing: defineCollection({
|
|
49
|
+
type: 'page',
|
|
50
|
+
source: {
|
|
51
|
+
cwd,
|
|
52
|
+
include: 'index.md',
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
docs: defineCollection({
|
|
56
|
+
type: 'page',
|
|
57
|
+
source: {
|
|
58
|
+
cwd,
|
|
59
|
+
include: '**',
|
|
60
|
+
exclude: ['index.md'],
|
|
61
|
+
},
|
|
62
|
+
schema: createDocsSchema(),
|
|
63
|
+
}),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default defineContentConfig({ collections })
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"or": "أو",
|
|
4
|
+
"error": {
|
|
5
|
+
"title": "الصفحة غير موجودة",
|
|
6
|
+
"description": "نأسف، لكن الصفحة التي تبحث عنها غير موجودة."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"docs": {
|
|
10
|
+
"copy": {
|
|
11
|
+
"page": "نسخ الصفحة",
|
|
12
|
+
"link": "نسخ صفحة Markdown",
|
|
13
|
+
"view": "عرض كـ Markdown",
|
|
14
|
+
"gpt": "فتح في ChatGPT",
|
|
15
|
+
"claude": "فتح في Claude"
|
|
16
|
+
},
|
|
17
|
+
"links": "المجتمع",
|
|
18
|
+
"toc": "في هذه الصفحة",
|
|
19
|
+
"report": "الإبلاغ عن مشكلة",
|
|
20
|
+
"edit": "تحرير هذه الصفحة"
|
|
21
|
+
}
|
|
22
|
+
}
|