docus 5.7.0 → 5.8.1
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/app/app.vue +13 -1
- package/app/components/LanguageSelect.vue +1 -4
- package/app/components/app/AppFooterRight.vue +32 -14
- package/app/components/app/AppHeader.vue +10 -0
- package/app/components/app/AppHeaderBottom.vue +20 -0
- package/app/components/app/AppHeaderBottomRight.vue +3 -0
- package/app/components/docs/DocsAsideLeftBody.vue +2 -4
- package/app/components/docs/DocsAsideLeftTop.vue +18 -1
- package/app/components/docs/DocsAsideMobileBar.vue +88 -0
- package/app/components/docs/DocsAsideRight.vue +33 -0
- package/app/components/docs/DocsAsideRightBottom.vue +1 -1
- package/app/composables/useDocusI18n.ts +21 -5
- package/app/composables/useLogoAssets.ts +1 -1
- package/app/composables/useSubNavigation.ts +55 -0
- package/app/pages/[[lang]]/[...slug].vue +4 -13
- package/i18n/locales/ar.json +1 -0
- package/i18n/locales/be.json +1 -0
- package/i18n/locales/bg.json +1 -0
- package/i18n/locales/bn.json +1 -0
- package/i18n/locales/ca.json +1 -0
- package/i18n/locales/ckb.json +1 -0
- package/i18n/locales/cs.json +1 -0
- package/i18n/locales/da.json +1 -0
- package/i18n/locales/de.json +1 -0
- package/i18n/locales/el.json +1 -0
- package/i18n/locales/en.json +1 -0
- package/i18n/locales/es.json +1 -0
- package/i18n/locales/et.json +1 -0
- package/i18n/locales/fi.json +1 -0
- package/i18n/locales/fr.json +1 -0
- package/i18n/locales/he.json +1 -0
- package/i18n/locales/hi.json +1 -0
- package/i18n/locales/hy.json +1 -0
- package/i18n/locales/id.json +1 -0
- package/i18n/locales/it.json +1 -0
- package/i18n/locales/ja.json +1 -0
- package/i18n/locales/kk.json +1 -0
- package/i18n/locales/km.json +1 -0
- package/i18n/locales/ko.json +1 -0
- package/i18n/locales/ky.json +1 -0
- package/i18n/locales/lb.json +1 -0
- package/i18n/locales/ms.json +1 -0
- package/i18n/locales/nb.json +1 -0
- package/i18n/locales/nl.json +1 -0
- package/i18n/locales/pl.json +1 -0
- package/i18n/locales/pt-BR.json +1 -0
- package/i18n/locales/ro.json +1 -0
- package/i18n/locales/ru.json +1 -0
- package/i18n/locales/si.json +1 -0
- package/i18n/locales/sl.json +1 -0
- package/i18n/locales/sv.json +1 -0
- package/i18n/locales/tr.json +1 -0
- package/i18n/locales/uk.json +1 -0
- package/i18n/locales/ur.json +1 -0
- package/i18n/locales/vi.json +1 -0
- package/i18n/locales/zh-CN.json +1 -0
- package/modules/assistant/runtime/components/AssistantPanel.vue +1 -1
- package/modules/assistant/runtime/components/AssistantPreStream.vue +1 -1
- package/modules/assistant/runtime/composables/useAssistant.ts +7 -3
- package/modules/assistant/runtime/server/api/search.ts +3 -2
- package/modules/config.ts +18 -6
- package/modules/markdown-rewrite.ts +7 -3
- package/modules/routing.ts +4 -1
- package/nuxt.config.ts +4 -2
- package/nuxt.schema.ts +14 -0
- package/package.json +12 -12
- package/server/mcp/tools/get-page.ts +2 -4
- package/server/mcp/tools/list-pages.ts +2 -1
- package/server/routes/sitemap.xml.ts +3 -3
package/app/app.vue
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
|
|
3
3
|
import * as nuxtUiLocales from '@nuxt/ui/locale'
|
|
4
4
|
import { transformNavigation } from './utils/navigation'
|
|
5
|
+
import { useSubNavigation } from './composables/useSubNavigation'
|
|
5
6
|
|
|
6
7
|
const { seo } = useAppConfig()
|
|
7
8
|
const site = useSiteConfig()
|
|
@@ -55,6 +56,8 @@ const { data: files } = useLazyAsyncData(`search_${collectionName.value}`, () =>
|
|
|
55
56
|
})
|
|
56
57
|
|
|
57
58
|
provide('navigation', navigation)
|
|
59
|
+
|
|
60
|
+
const { subNavigationMode } = useSubNavigation(navigation)
|
|
58
61
|
</script>
|
|
59
62
|
|
|
60
63
|
<template>
|
|
@@ -62,7 +65,7 @@ provide('navigation', navigation)
|
|
|
62
65
|
<NuxtLoadingIndicator color="var(--ui-primary)" />
|
|
63
66
|
|
|
64
67
|
<div
|
|
65
|
-
class="transition-[margin-right] duration-200 ease-linear will-change-[margin-right]"
|
|
68
|
+
:class="['transition-[margin-right] duration-200 ease-linear will-change-[margin-right]', { 'docus-sub-header': subNavigationMode === 'header' }]"
|
|
66
69
|
:style="{ marginRight: shouldPushContent ? `${assistantPanelWidth}px` : '0' }"
|
|
67
70
|
>
|
|
68
71
|
<AppHeader v-if="$route.meta.header !== false" />
|
|
@@ -84,3 +87,12 @@ provide('navigation', navigation)
|
|
|
84
87
|
</ClientOnly>
|
|
85
88
|
</UApp>
|
|
86
89
|
</template>
|
|
90
|
+
|
|
91
|
+
<style>
|
|
92
|
+
@media (min-width: 1024px) {
|
|
93
|
+
.docus-sub-header {
|
|
94
|
+
/* 64px base header + 48px sub-navigation bar */
|
|
95
|
+
--ui-header-height: 112px;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const appConfig = useAppConfig()
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
interface FooterLink {
|
|
5
|
+
'icon': string
|
|
6
|
+
'to': string
|
|
7
|
+
'target': '_blank'
|
|
8
|
+
'aria-label': string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const links = computed<FooterLink[]>(() => {
|
|
12
|
+
const socialLinks = Object.entries(appConfig.socials || {}).flatMap(([key, url]) => {
|
|
13
|
+
if (typeof url !== 'string' || !url) {
|
|
14
|
+
return []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return [{
|
|
18
|
+
'icon': `i-simple-icons-${key}`,
|
|
19
|
+
'to': url,
|
|
20
|
+
'target': '_blank' as const,
|
|
21
|
+
'aria-label': `${key} social link`,
|
|
22
|
+
}]
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const githubLink = appConfig.github && appConfig.github.url
|
|
26
|
+
? [{
|
|
27
|
+
'icon': 'i-simple-icons-github',
|
|
28
|
+
'to': appConfig.github.url,
|
|
29
|
+
'target': '_blank' as const,
|
|
30
|
+
'aria-label': 'GitHub repository',
|
|
31
|
+
}]
|
|
32
|
+
: []
|
|
33
|
+
|
|
34
|
+
return [...socialLinks, ...githubLink]
|
|
35
|
+
})
|
|
18
36
|
</script>
|
|
19
37
|
|
|
20
38
|
<template>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useDocusI18n } from '../../composables/useDocusI18n'
|
|
3
|
+
import { useSubNavigation } from '../../composables/useSubNavigation'
|
|
3
4
|
|
|
4
5
|
const appConfig = useAppConfig()
|
|
5
6
|
const site = useSiteConfig()
|
|
6
7
|
|
|
7
8
|
const { isEnabled: isAssistantEnabled } = useAssistant()
|
|
8
9
|
const { localePath, isEnabled, locales } = useDocusI18n()
|
|
10
|
+
const { subNavigationMode } = useSubNavigation()
|
|
9
11
|
|
|
10
12
|
const links = computed(() => appConfig.github && appConfig.github.url
|
|
11
13
|
? [
|
|
@@ -22,6 +24,7 @@ const links = computed(() => appConfig.github && appConfig.github.url
|
|
|
22
24
|
<template>
|
|
23
25
|
<UHeader
|
|
24
26
|
:ui="{ center: 'flex-1' }"
|
|
27
|
+
:class="{ 'flex flex-col': subNavigationMode === 'header' }"
|
|
25
28
|
:to="localePath('/')"
|
|
26
29
|
:title="appConfig.header?.title || site.name"
|
|
27
30
|
>
|
|
@@ -83,5 +86,12 @@ const links = computed(() => appConfig.github && appConfig.github.url
|
|
|
83
86
|
<template #body>
|
|
84
87
|
<AppHeaderBody />
|
|
85
88
|
</template>
|
|
89
|
+
|
|
90
|
+
<template
|
|
91
|
+
v-if="subNavigationMode === 'header'"
|
|
92
|
+
#bottom
|
|
93
|
+
>
|
|
94
|
+
<AppHeaderBottom />
|
|
95
|
+
</template>
|
|
86
96
|
</UHeader>
|
|
87
97
|
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useSubNavigation } from '../../composables/useSubNavigation'
|
|
3
|
+
|
|
4
|
+
const { sections } = useSubNavigation()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<USeparator class="hidden lg:flex" />
|
|
9
|
+
|
|
10
|
+
<UContainer class="hidden lg:flex items-center justify-between">
|
|
11
|
+
<UNavigationMenu
|
|
12
|
+
:items="sections"
|
|
13
|
+
variant="pill"
|
|
14
|
+
highlight
|
|
15
|
+
class="-mx-2.5 -mb-px"
|
|
16
|
+
/>
|
|
17
|
+
|
|
18
|
+
<AppHeaderBottomRight />
|
|
19
|
+
</UContainer>
|
|
20
|
+
</template>
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
|
2
|
+
const { sidebarNavigation } = useSubNavigation()
|
|
5
3
|
</script>
|
|
6
4
|
|
|
7
5
|
<template>
|
|
8
6
|
<UContentNavigation
|
|
9
7
|
highlight
|
|
10
|
-
:navigation="
|
|
8
|
+
:navigation="sidebarNavigation"
|
|
11
9
|
/>
|
|
12
10
|
</template>
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useSubNavigation } from '../../composables/useSubNavigation'
|
|
3
|
+
|
|
4
|
+
const { subNavigationMode, sections } = useSubNavigation()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
1
7
|
<template>
|
|
2
|
-
<div
|
|
8
|
+
<div
|
|
9
|
+
v-if="subNavigationMode === 'aside'"
|
|
10
|
+
class="mb-2"
|
|
11
|
+
>
|
|
12
|
+
<UPageAnchors :links="sections" />
|
|
13
|
+
<USeparator
|
|
14
|
+
type="dashed"
|
|
15
|
+
class="my-4"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div v-else />
|
|
3
20
|
</template>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useSubNavigation } from '../../composables/useSubNavigation'
|
|
3
|
+
import type { ContentTocLink } from '@nuxt/ui'
|
|
4
|
+
|
|
5
|
+
defineProps<{
|
|
6
|
+
links?: ContentTocLink[]
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const { subNavigationMode, sidebarNavigation, currentSection } = useSubNavigation()
|
|
10
|
+
const { t } = useDocusI18n()
|
|
11
|
+
|
|
12
|
+
const menuDrawerOpen = ref(false)
|
|
13
|
+
const tocDrawerOpen = ref(false)
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div
|
|
18
|
+
v-if="subNavigationMode"
|
|
19
|
+
class="lg:hidden sticky top-(--ui-header-height) z-10 bg-default/75 backdrop-blur -mx-4 p-2 border-b border-dashed border-default flex justify-between"
|
|
20
|
+
>
|
|
21
|
+
<UDrawer
|
|
22
|
+
v-model:open="menuDrawerOpen"
|
|
23
|
+
direction="left"
|
|
24
|
+
:title="currentSection?.title"
|
|
25
|
+
:handle="false"
|
|
26
|
+
inset
|
|
27
|
+
side="left"
|
|
28
|
+
:ui="{ content: 'w-full max-w-2/3' }"
|
|
29
|
+
>
|
|
30
|
+
<UButton
|
|
31
|
+
:label="t('docs.menu')"
|
|
32
|
+
icon="i-lucide-text-align-start"
|
|
33
|
+
color="neutral"
|
|
34
|
+
variant="link"
|
|
35
|
+
size="xs"
|
|
36
|
+
:aria-label="t('docs.menu')"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<template #body>
|
|
40
|
+
<UContentNavigation
|
|
41
|
+
:navigation="sidebarNavigation"
|
|
42
|
+
default-open
|
|
43
|
+
trailing-icon="i-lucide-chevron-right"
|
|
44
|
+
:ui="{ linkTrailingIcon: 'group-data-[state=open]:rotate-90' }"
|
|
45
|
+
highlight
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
</UDrawer>
|
|
49
|
+
|
|
50
|
+
<UDrawer
|
|
51
|
+
v-model:open="tocDrawerOpen"
|
|
52
|
+
direction="right"
|
|
53
|
+
:handle="false"
|
|
54
|
+
inset
|
|
55
|
+
side="right"
|
|
56
|
+
no-body-styles
|
|
57
|
+
:ui="{ content: 'w-full max-w-2/3' }"
|
|
58
|
+
>
|
|
59
|
+
<UButton
|
|
60
|
+
:label="t('docs.toc')"
|
|
61
|
+
trailing-icon="i-lucide-chevron-right"
|
|
62
|
+
color="neutral"
|
|
63
|
+
variant="link"
|
|
64
|
+
size="xs"
|
|
65
|
+
:aria-label="t('docs.toc')"
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
<template #body>
|
|
69
|
+
<UContentToc
|
|
70
|
+
v-if="links?.length"
|
|
71
|
+
:links="links"
|
|
72
|
+
:open="true"
|
|
73
|
+
default-open
|
|
74
|
+
:ui="{
|
|
75
|
+
root: '!mx-0 !px-1 top-0 overflow-visible',
|
|
76
|
+
container: '!pt-0 border-b-0',
|
|
77
|
+
trailingIcon: 'hidden',
|
|
78
|
+
bottom: 'flex flex-col',
|
|
79
|
+
}"
|
|
80
|
+
>
|
|
81
|
+
<template #bottom>
|
|
82
|
+
<DocsAsideRightBottom />
|
|
83
|
+
</template>
|
|
84
|
+
</UContentToc>
|
|
85
|
+
</template>
|
|
86
|
+
</UDrawer>
|
|
87
|
+
</div>
|
|
88
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useSubNavigation } from '../../composables/useSubNavigation'
|
|
3
|
+
import type { DocsCollectionItem } from '@nuxt/content'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
page?: DocsCollectionItem | null
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const links = computed(() => props.page?.body?.toc?.links || [])
|
|
10
|
+
|
|
11
|
+
const { shouldPushContent: shouldHideToc } = useAssistant()
|
|
12
|
+
const { subNavigationMode } = useSubNavigation()
|
|
13
|
+
const appConfig = useAppConfig()
|
|
14
|
+
const { t } = useDocusI18n()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<div>
|
|
19
|
+
<UContentToc
|
|
20
|
+
v-if="links.length && !shouldHideToc"
|
|
21
|
+
highlight
|
|
22
|
+
:title="appConfig.toc?.title || t('docs.toc')"
|
|
23
|
+
:links="links"
|
|
24
|
+
:class="{ 'hidden lg:block': subNavigationMode }"
|
|
25
|
+
>
|
|
26
|
+
<template #bottom>
|
|
27
|
+
<DocsAsideRightBottom />
|
|
28
|
+
</template>
|
|
29
|
+
</UContentToc>
|
|
30
|
+
|
|
31
|
+
<DocsAsideMobileBar :links="links" />
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
@@ -16,7 +16,7 @@ const explainIcon = computed(() => appConfig.assistant?.icons?.explain || 'i-luc
|
|
|
16
16
|
<template>
|
|
17
17
|
<div
|
|
18
18
|
v-if="appConfig.toc?.bottom?.links?.length || showExplainWithAi"
|
|
19
|
-
class="
|
|
19
|
+
class="space-y-6"
|
|
20
20
|
>
|
|
21
21
|
<USeparator type="dashed" />
|
|
22
22
|
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
import { useNuxtApp, useRuntimeConfig } from '#imports'
|
|
1
2
|
import type { LocaleObject } from '@nuxtjs/i18n'
|
|
3
|
+
import type { Ref } from 'vue'
|
|
4
|
+
import { ref } from 'vue'
|
|
5
|
+
|
|
6
|
+
type DocusNuxtApp = ReturnType<typeof useNuxtApp> & {
|
|
7
|
+
$i18n?: {
|
|
8
|
+
locale: Ref<string>
|
|
9
|
+
t: (key: string) => string
|
|
10
|
+
}
|
|
11
|
+
$locale?: string
|
|
12
|
+
$localeMessages?: Record<string, unknown>
|
|
13
|
+
$localePath?: (path: string) => string
|
|
14
|
+
$switchLocalePath?: (locale?: string) => string
|
|
15
|
+
}
|
|
2
16
|
|
|
3
17
|
export const useDocusI18n = () => {
|
|
4
18
|
const config = useRuntimeConfig().public
|
|
19
|
+
const nuxtApp = useNuxtApp() as DocusNuxtApp
|
|
5
20
|
const isEnabled = ref(!!config.i18n)
|
|
6
21
|
|
|
7
22
|
if (!isEnabled.value) {
|
|
8
|
-
const locale =
|
|
9
|
-
const localeMessages =
|
|
23
|
+
const locale = nuxtApp.$locale || 'en'
|
|
24
|
+
const localeMessages = nuxtApp.$localeMessages || {}
|
|
10
25
|
|
|
11
26
|
return {
|
|
12
27
|
isEnabled,
|
|
@@ -21,7 +36,8 @@ export const useDocusI18n = () => {
|
|
|
21
36
|
}
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
const
|
|
39
|
+
const locale = nuxtApp.$i18n?.locale || ref('en')
|
|
40
|
+
const t = nuxtApp.$i18n?.t || ((key: string) => key)
|
|
25
41
|
const filteredLocales = (config.docus as { filteredLocales: LocaleObject<string>[] })?.filteredLocales || []
|
|
26
42
|
|
|
27
43
|
return {
|
|
@@ -29,7 +45,7 @@ export const useDocusI18n = () => {
|
|
|
29
45
|
locale,
|
|
30
46
|
locales: filteredLocales,
|
|
31
47
|
t,
|
|
32
|
-
localePath:
|
|
33
|
-
switchLocalePath:
|
|
48
|
+
localePath: nuxtApp.$localePath || ((path: string) => path),
|
|
49
|
+
switchLocalePath: nuxtApp.$switchLocalePath || (() => ''),
|
|
34
50
|
}
|
|
35
51
|
}
|
|
@@ -65,7 +65,7 @@ function triggerLinkDownload(url: string, filename: string) {
|
|
|
65
65
|
|
|
66
66
|
export const useLogoAssets = () => {
|
|
67
67
|
const appConfig = useAppConfig()
|
|
68
|
-
const colorMode = useColorMode()
|
|
68
|
+
const colorMode = useColorMode() as { value: string, forced?: boolean }
|
|
69
69
|
const toast = useToast()
|
|
70
70
|
const { t } = useDocusI18n()
|
|
71
71
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ContentNavigationItem } from '@nuxt/content'
|
|
2
|
+
|
|
3
|
+
function getFirstPagePath(item: ContentNavigationItem): string {
|
|
4
|
+
let current = item
|
|
5
|
+
while (current.children?.length) {
|
|
6
|
+
current = current.children[0]!
|
|
7
|
+
}
|
|
8
|
+
return current.path
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useSubNavigation(providedNavigation?: Ref<ContentNavigationItem[] | null | undefined>) {
|
|
12
|
+
const route = useRoute()
|
|
13
|
+
const appConfig = useAppConfig()
|
|
14
|
+
const navigation = providedNavigation ?? inject<Ref<ContentNavigationItem[]>>('navigation')
|
|
15
|
+
|
|
16
|
+
const isDocsPage = computed(() => route.meta.layout === 'docs')
|
|
17
|
+
|
|
18
|
+
const subNavigationMode = computed(() => {
|
|
19
|
+
if (!isDocsPage.value) return undefined
|
|
20
|
+
return (appConfig.navigation as { sub?: 'header' | 'aside' } | undefined)?.sub
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const currentSection = computed(() => {
|
|
24
|
+
if (!subNavigationMode.value || !navigation?.value) return undefined
|
|
25
|
+
return navigation.value.find(item =>
|
|
26
|
+
route.path === item.path || route.path.startsWith(item.path + '/'),
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const sections = computed(() => {
|
|
31
|
+
if (!subNavigationMode.value || !navigation?.value) return []
|
|
32
|
+
return navigation.value
|
|
33
|
+
.filter(item => item.children?.length)
|
|
34
|
+
.map(item => ({
|
|
35
|
+
label: item.title,
|
|
36
|
+
icon: item.icon as string | undefined,
|
|
37
|
+
to: getFirstPagePath(item),
|
|
38
|
+
active: route.path === item.path || route.path.startsWith(item.path + '/'),
|
|
39
|
+
}))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const sidebarNavigation = computed(() => {
|
|
43
|
+
if (subNavigationMode.value && currentSection.value) {
|
|
44
|
+
return currentSection.value.children || []
|
|
45
|
+
}
|
|
46
|
+
return navigation?.value || []
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
subNavigationMode,
|
|
51
|
+
sections,
|
|
52
|
+
currentSection,
|
|
53
|
+
sidebarNavigation,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -131,19 +131,10 @@ addPrerenderPath(`/raw${route.path}.md`)
|
|
|
131
131
|
<UContentSurround :surround="surround" />
|
|
132
132
|
</UPageBody>
|
|
133
133
|
|
|
134
|
-
<template
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<UContentToc
|
|
139
|
-
highlight
|
|
140
|
-
:title="appConfig.toc?.title || t('docs.toc')"
|
|
141
|
-
:links="page.body?.toc?.links"
|
|
142
|
-
>
|
|
143
|
-
<template #bottom>
|
|
144
|
-
<DocsAsideRightBottom />
|
|
145
|
-
</template>
|
|
146
|
-
</UContentToc>
|
|
134
|
+
<template #right>
|
|
135
|
+
<DocsAsideRight
|
|
136
|
+
:page="page"
|
|
137
|
+
/>
|
|
147
138
|
</template>
|
|
148
139
|
</UPage>
|
|
149
140
|
</template>
|
package/i18n/locales/ar.json
CHANGED
package/i18n/locales/be.json
CHANGED
package/i18n/locales/bg.json
CHANGED
package/i18n/locales/bn.json
CHANGED
package/i18n/locales/ca.json
CHANGED
package/i18n/locales/ckb.json
CHANGED
package/i18n/locales/cs.json
CHANGED
package/i18n/locales/da.json
CHANGED
package/i18n/locales/de.json
CHANGED
package/i18n/locales/el.json
CHANGED
package/i18n/locales/en.json
CHANGED
package/i18n/locales/es.json
CHANGED
package/i18n/locales/et.json
CHANGED
package/i18n/locales/fi.json
CHANGED
package/i18n/locales/fr.json
CHANGED
package/i18n/locales/he.json
CHANGED
package/i18n/locales/hi.json
CHANGED
package/i18n/locales/hy.json
CHANGED
package/i18n/locales/id.json
CHANGED
package/i18n/locales/it.json
CHANGED
package/i18n/locales/ja.json
CHANGED
package/i18n/locales/kk.json
CHANGED
package/i18n/locales/km.json
CHANGED
package/i18n/locales/ko.json
CHANGED
package/i18n/locales/ky.json
CHANGED
package/i18n/locales/lb.json
CHANGED
package/i18n/locales/ms.json
CHANGED
package/i18n/locales/nb.json
CHANGED
package/i18n/locales/nl.json
CHANGED
package/i18n/locales/pl.json
CHANGED
package/i18n/locales/pt-BR.json
CHANGED
package/i18n/locales/ro.json
CHANGED
package/i18n/locales/ru.json
CHANGED
package/i18n/locales/si.json
CHANGED
package/i18n/locales/sl.json
CHANGED
package/i18n/locales/sv.json
CHANGED
package/i18n/locales/tr.json
CHANGED
package/i18n/locales/uk.json
CHANGED
package/i18n/locales/ur.json
CHANGED
package/i18n/locales/vi.json
CHANGED
package/i18n/locales/zh-CN.json
CHANGED
|
@@ -25,7 +25,7 @@ const displayPlaceholder = computed(() => t('assistant.placeholder'))
|
|
|
25
25
|
const chat = new Chat({
|
|
26
26
|
messages: messages.value,
|
|
27
27
|
transport: new DefaultChatTransport({
|
|
28
|
-
api: config.public.assistant.apiPath,
|
|
28
|
+
api: (config.app?.baseURL.replace(/\/$/, '') || '') + config.public.assistant.apiPath,
|
|
29
29
|
}),
|
|
30
30
|
onError: (error: Error) => {
|
|
31
31
|
const message = (() => {
|
|
@@ -3,7 +3,7 @@ import { ShikiCachedRenderer } from 'shiki-stream/vue'
|
|
|
3
3
|
import { useColorMode } from '#imports'
|
|
4
4
|
import { useHighlighter } from '../composables/useHighlighter'
|
|
5
5
|
|
|
6
|
-
const colorMode = useColorMode()
|
|
6
|
+
const colorMode = useColorMode() as { value: string, forced?: boolean }
|
|
7
7
|
const highlighter = await useHighlighter()
|
|
8
8
|
const props = defineProps<{
|
|
9
9
|
code: string
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { UIMessage } from 'ai'
|
|
2
|
+
import { useAppConfig, useRuntimeConfig, useState } from '#imports'
|
|
2
3
|
import { useMediaQuery } from '@vueuse/core'
|
|
4
|
+
import { computed } from 'vue'
|
|
3
5
|
import type { FaqCategory, FaqQuestions, LocalizedFaqQuestions } from '../types'
|
|
4
6
|
|
|
5
7
|
function normalizeFaqQuestions(questions: FaqQuestions): FaqCategory[] {
|
|
@@ -23,7 +25,10 @@ const PANEL_WIDTH_EXPANDED = 520
|
|
|
23
25
|
export function useAssistant() {
|
|
24
26
|
const config = useRuntimeConfig()
|
|
25
27
|
const appConfig = useAppConfig()
|
|
26
|
-
const
|
|
28
|
+
const assistantRuntimeConfig = config.public.assistant as { enabled?: boolean } | undefined
|
|
29
|
+
const assistantConfig = appConfig.assistant as { faqQuestions?: FaqQuestions | LocalizedFaqQuestions } | undefined
|
|
30
|
+
const docusRuntimeConfig = appConfig.docus as { locale?: string } | undefined
|
|
31
|
+
const isEnabled = computed(() => assistantRuntimeConfig?.enabled ?? false)
|
|
27
32
|
|
|
28
33
|
const isOpen = useState('assistant-open', () => false)
|
|
29
34
|
const isExpanded = useState('assistant-expanded', () => false)
|
|
@@ -35,14 +40,13 @@ export function useAssistant() {
|
|
|
35
40
|
const shouldPushContent = computed(() => !isMobile.value && isOpen.value)
|
|
36
41
|
|
|
37
42
|
const faqQuestions = computed<FaqCategory[]>(() => {
|
|
38
|
-
const assistantConfig = appConfig.assistant
|
|
39
43
|
const faqConfig = assistantConfig?.faqQuestions
|
|
40
44
|
if (!faqConfig) return []
|
|
41
45
|
|
|
42
46
|
// Check if it's a localized object (has locale keys like 'en', 'fr')
|
|
43
47
|
if (!Array.isArray(faqConfig)) {
|
|
44
48
|
const localizedConfig = faqConfig as LocalizedFaqQuestions
|
|
45
|
-
const currentLocale =
|
|
49
|
+
const currentLocale = docusRuntimeConfig?.locale || 'en'
|
|
46
50
|
const defaultLocale = config.public.i18n?.defaultLocale || 'en'
|
|
47
51
|
|
|
48
52
|
// Try current locale, then default locale, then first available
|
|
@@ -60,11 +60,12 @@ export default defineEventHandler(async (event) => {
|
|
|
60
60
|
|
|
61
61
|
const mcpServer = config.assistant.mcpServer
|
|
62
62
|
const isExternalUrl = mcpServer.startsWith('http://') || mcpServer.startsWith('https://')
|
|
63
|
+
const baseURL = config.app?.baseURL?.replace(/\/$/, '') || ''
|
|
63
64
|
const mcpUrl = isExternalUrl
|
|
64
65
|
? mcpServer
|
|
65
66
|
: import.meta.dev
|
|
66
|
-
? `http://localhost:3000${mcpServer}`
|
|
67
|
-
: `${getRequestURL(event).origin}${mcpServer}`
|
|
67
|
+
? `http://localhost:3000${baseURL}${mcpServer}`
|
|
68
|
+
: `${getRequestURL(event).origin}${baseURL}${mcpServer}`
|
|
68
69
|
|
|
69
70
|
const httpClient = await createMCPClient({
|
|
70
71
|
transport: { type: 'http', url: mcpUrl },
|
package/modules/config.ts
CHANGED
|
@@ -7,6 +7,13 @@ import { getGitBranch, getGitEnv, getLocalGitInfo } from '../utils/git'
|
|
|
7
7
|
|
|
8
8
|
const log = logger.withTag('Docus')
|
|
9
9
|
|
|
10
|
+
type I18nLocale = string | { code: string, name?: string }
|
|
11
|
+
type DocusI18nOptions = { locales?: I18nLocale[], strategy?: string }
|
|
12
|
+
type RegisterModuleOptions = {
|
|
13
|
+
langDir: string
|
|
14
|
+
locales: Array<{ code: string, name: string, file: string }>
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
export default defineNuxtModule({
|
|
11
18
|
meta: {
|
|
12
19
|
name: 'config',
|
|
@@ -54,11 +61,14 @@ export default defineNuxtModule({
|
|
|
54
61
|
/*
|
|
55
62
|
** I18N
|
|
56
63
|
*/
|
|
57
|
-
|
|
64
|
+
const typedNuxtOptions = nuxt.options as typeof nuxt.options & { i18n?: DocusI18nOptions }
|
|
65
|
+
const i18nOptions = typedNuxtOptions.i18n
|
|
66
|
+
|
|
67
|
+
if (i18nOptions?.locales) {
|
|
58
68
|
const { resolve } = createResolver(import.meta.url)
|
|
59
69
|
|
|
60
70
|
// Filter locales to only include existing ones
|
|
61
|
-
const filteredLocales =
|
|
71
|
+
const filteredLocales = i18nOptions.locales.filter((locale: I18nLocale) => {
|
|
62
72
|
const localeCode = typeof locale === 'string' ? locale : locale.code
|
|
63
73
|
|
|
64
74
|
// Check for JSON locale file
|
|
@@ -81,8 +91,8 @@ export default defineNuxtModule({
|
|
|
81
91
|
})
|
|
82
92
|
|
|
83
93
|
// Override strategy to prefix
|
|
84
|
-
|
|
85
|
-
...
|
|
94
|
+
typedNuxtOptions.i18n = {
|
|
95
|
+
...i18nOptions,
|
|
86
96
|
strategy: 'prefix',
|
|
87
97
|
}
|
|
88
98
|
|
|
@@ -91,10 +101,12 @@ export default defineNuxtModule({
|
|
|
91
101
|
filteredLocales,
|
|
92
102
|
}
|
|
93
103
|
|
|
94
|
-
nuxt.hook(
|
|
104
|
+
const registerI18nModule = nuxt.hook as unknown as (name: string, callback: (register: (options: RegisterModuleOptions) => void) => void) => void
|
|
105
|
+
|
|
106
|
+
registerI18nModule('i18n:registerModule', (register) => {
|
|
95
107
|
const langDir = resolve('../i18n/locales')
|
|
96
108
|
|
|
97
|
-
const locales = filteredLocales
|
|
109
|
+
const locales = filteredLocales.map((locale: I18nLocale) => {
|
|
98
110
|
return typeof locale === 'string'
|
|
99
111
|
? {
|
|
100
112
|
code: locale,
|
|
@@ -4,6 +4,9 @@ import { readFile, writeFile } from 'node:fs/promises'
|
|
|
4
4
|
|
|
5
5
|
const log = logger.withTag('Docus')
|
|
6
6
|
|
|
7
|
+
type I18nLocale = string | { code: string }
|
|
8
|
+
type DocusI18nOptions = { locales?: I18nLocale[] }
|
|
9
|
+
|
|
7
10
|
export default defineNuxtModule({
|
|
8
11
|
meta: {
|
|
9
12
|
name: 'markdown-rewrite',
|
|
@@ -44,13 +47,14 @@ export default defineNuxtModule({
|
|
|
44
47
|
]
|
|
45
48
|
|
|
46
49
|
// Check if i18n is enabled
|
|
47
|
-
const
|
|
50
|
+
const i18nOptions = (nuxt.options as typeof nuxt.options & { i18n?: DocusI18nOptions }).i18n
|
|
51
|
+
const isI18nEnabled = !!i18nOptions?.locales
|
|
48
52
|
let localeCodes: string[] = []
|
|
49
53
|
|
|
50
54
|
if (isI18nEnabled) {
|
|
51
55
|
// Get locale codes
|
|
52
|
-
const locales =
|
|
53
|
-
localeCodes = locales.map((locale) => {
|
|
56
|
+
const locales = i18nOptions?.locales || []
|
|
57
|
+
localeCodes = locales.map((locale: I18nLocale) => {
|
|
54
58
|
return typeof locale === 'string' ? locale : locale.code
|
|
55
59
|
})
|
|
56
60
|
|
package/modules/routing.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { defineNuxtModule, extendPages, createResolver } from '@nuxt/kit'
|
|
2
2
|
import { landingPageExists } from '../utils/pages'
|
|
3
3
|
|
|
4
|
+
type DocusI18nOptions = { locales?: Array<string | { code: string }> }
|
|
5
|
+
|
|
4
6
|
export default defineNuxtModule({
|
|
5
7
|
meta: {
|
|
6
8
|
name: 'routing',
|
|
@@ -8,7 +10,8 @@ export default defineNuxtModule({
|
|
|
8
10
|
async setup(_options, nuxt) {
|
|
9
11
|
const { resolve } = createResolver(import.meta.url)
|
|
10
12
|
|
|
11
|
-
const
|
|
13
|
+
const i18nOptions = (nuxt.options as typeof nuxt.options & { i18n?: DocusI18nOptions }).i18n
|
|
14
|
+
const isI18nEnabled = !!i18nOptions?.locales
|
|
12
15
|
|
|
13
16
|
// Ensure useDocusI18n is available in the app
|
|
14
17
|
nuxt.hook('imports:extend', (imports) => {
|
package/nuxt.config.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { extendViteConfig, createResolver, useNuxt } from '@nuxt/kit'
|
|
|
3
3
|
|
|
4
4
|
const { resolve } = createResolver(import.meta.url)
|
|
5
5
|
|
|
6
|
+
type DocusI18nOptions = { locales?: Array<string | { code: string }> }
|
|
7
|
+
|
|
6
8
|
export default defineNuxtConfig({
|
|
7
9
|
modules: [
|
|
8
10
|
resolve('./modules/config'),
|
|
@@ -87,14 +89,14 @@ export default defineNuxtConfig({
|
|
|
87
89
|
'nitro:config'(nitroConfig) {
|
|
88
90
|
const nuxt = useNuxt()
|
|
89
91
|
|
|
90
|
-
const i18nOptions = nuxt.options.i18n
|
|
92
|
+
const i18nOptions = (nuxt.options as typeof nuxt.options & { i18n?: DocusI18nOptions }).i18n
|
|
91
93
|
|
|
92
94
|
const routes: string[] = []
|
|
93
95
|
if (!i18nOptions) {
|
|
94
96
|
routes.push('/')
|
|
95
97
|
}
|
|
96
98
|
else {
|
|
97
|
-
routes.push(...(i18nOptions.locales?.map(locale => typeof locale === 'string' ? `/${locale}` : `/${locale.code}`) || []))
|
|
99
|
+
routes.push(...(i18nOptions.locales?.map((locale: string | { code: string }) => typeof locale === 'string' ? `/${locale}` : `/${locale.code}`) || []))
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
nitroConfig.prerender = nitroConfig.prerender || {}
|
package/nuxt.schema.ts
CHANGED
|
@@ -193,6 +193,20 @@ export default defineNuxtSchema({
|
|
|
193
193
|
}),
|
|
194
194
|
},
|
|
195
195
|
}),
|
|
196
|
+
navigation: group({
|
|
197
|
+
title: 'Navigation',
|
|
198
|
+
description: 'Navigation configuration.',
|
|
199
|
+
icon: 'i-lucide-navigation',
|
|
200
|
+
fields: {
|
|
201
|
+
sub: field({
|
|
202
|
+
type: 'string',
|
|
203
|
+
title: 'Sub Navigation',
|
|
204
|
+
description: 'Enable sub-navigation for multi-section docs. Use "header" for a secondary tab bar below the header, or "aside" for section anchors at the top of the sidebar.',
|
|
205
|
+
icon: 'i-lucide-layout-panel-left',
|
|
206
|
+
default: '',
|
|
207
|
+
}),
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
196
210
|
socials: field({
|
|
197
211
|
type: 'object',
|
|
198
212
|
title: 'Social Networks',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docus",
|
|
3
3
|
"description": "Nuxt layer for Docus documentation theme",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.8.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
7
7
|
"repository": {
|
|
@@ -22,22 +22,22 @@
|
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@ai-sdk/gateway": "^3.0.
|
|
26
|
-
"@ai-sdk/mcp": "^1.0.
|
|
27
|
-
"@ai-sdk/vue": "3.0.
|
|
28
|
-
"@iconify-json/lucide": "^1.2.
|
|
29
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
30
|
-
"@iconify-json/vscode-icons": "^1.2.
|
|
25
|
+
"@ai-sdk/gateway": "^3.0.66",
|
|
26
|
+
"@ai-sdk/mcp": "^1.0.25",
|
|
27
|
+
"@ai-sdk/vue": "3.0.116",
|
|
28
|
+
"@iconify-json/lucide": "^1.2.96",
|
|
29
|
+
"@iconify-json/simple-icons": "^1.2.73",
|
|
30
|
+
"@iconify-json/vscode-icons": "^1.2.45",
|
|
31
31
|
"@nuxt/content": "^3.12.0",
|
|
32
32
|
"@nuxt/image": "^2.0.0",
|
|
33
33
|
"@nuxt/kit": "^4.3.1",
|
|
34
|
-
"@nuxt/ui": "^4.5.
|
|
34
|
+
"@nuxt/ui": "^4.5.1",
|
|
35
35
|
"@nuxtjs/i18n": "^10.2.3",
|
|
36
36
|
"@nuxtjs/mcp-toolkit": "^0.7.0",
|
|
37
|
-
"@nuxtjs/mdc": "^0.20.
|
|
38
|
-
"@nuxtjs/robots": "^5.7.
|
|
37
|
+
"@nuxtjs/mdc": "^0.20.2",
|
|
38
|
+
"@nuxtjs/robots": "^5.7.1",
|
|
39
39
|
"@vueuse/core": "^14.2.1",
|
|
40
|
-
"ai": "6.0.
|
|
40
|
+
"ai": "6.0.116",
|
|
41
41
|
"defu": "^6.1.4",
|
|
42
42
|
"exsolve": "^1.0.8",
|
|
43
43
|
"git-url-parse": "^16.1.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@shikijs/langs": "^3.22.0",
|
|
52
52
|
"@shikijs/themes": "^3.22.0",
|
|
53
53
|
"shiki-stream": "^0.1.4",
|
|
54
|
-
"tailwindcss": "^4.1
|
|
54
|
+
"tailwindcss": "^4.2.1",
|
|
55
55
|
"ufo": "^1.6.3",
|
|
56
56
|
"zod": "^4.3.6",
|
|
57
57
|
"zod-to-json-schema": "^3.25.1"
|
|
@@ -24,7 +24,7 @@ WORKFLOW: This tool returns the complete page content including title, descripti
|
|
|
24
24
|
handler: async ({ path }) => {
|
|
25
25
|
const event = useEvent()
|
|
26
26
|
const config = useRuntimeConfig(event).public
|
|
27
|
-
const siteUrl =
|
|
27
|
+
const siteUrl = getRequestURL(event).origin || inferSiteURL()
|
|
28
28
|
|
|
29
29
|
const availableLocales = getAvailableLocales(config)
|
|
30
30
|
const collectionName = config.i18n?.locales
|
|
@@ -41,9 +41,7 @@ WORKFLOW: This tool returns the complete page content including title, descripti
|
|
|
41
41
|
return errorResult('Page not found')
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const content = await
|
|
45
|
-
baseURL: siteUrl,
|
|
46
|
-
})
|
|
44
|
+
const content = await event.$fetch<string>(`/raw${path}.md`)
|
|
47
45
|
|
|
48
46
|
return jsonResult({
|
|
49
47
|
title: page.title,
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod'
|
|
|
2
2
|
import { queryCollection } from '@nuxt/content/server'
|
|
3
3
|
import type { Collections } from '@nuxt/content'
|
|
4
4
|
import { getCollectionsToQuery, getAvailableLocales } from '../../utils/content'
|
|
5
|
+
import { inferSiteURL } from '../../../utils/meta'
|
|
5
6
|
|
|
6
7
|
export default defineMcpTool({
|
|
7
8
|
description: `Lists all available documentation pages with their categories and basic information.
|
|
@@ -30,7 +31,7 @@ OUTPUT: Returns a structured list with:
|
|
|
30
31
|
const event = useEvent()
|
|
31
32
|
const config = useRuntimeConfig(event).public
|
|
32
33
|
|
|
33
|
-
const siteUrl =
|
|
34
|
+
const siteUrl = getRequestURL(event).origin || inferSiteURL()
|
|
34
35
|
const availableLocales = getAvailableLocales(config)
|
|
35
36
|
const collections = getCollectionsToQuery(locale, availableLocales)
|
|
36
37
|
|
|
@@ -27,11 +27,11 @@ export default defineEventHandler(async (event) => {
|
|
|
27
27
|
|
|
28
28
|
for (const collection of collections) {
|
|
29
29
|
try {
|
|
30
|
-
const pages = await queryCollection(event, collection
|
|
30
|
+
const pages = await (queryCollection as unknown as (event: unknown, collection: string) => { all: () => Promise<Array<Record<string, unknown> & { path?: string }>> })(event, collection).all()
|
|
31
31
|
|
|
32
32
|
for (const page of pages) {
|
|
33
|
-
const meta = page as
|
|
34
|
-
const pagePath =
|
|
33
|
+
const meta = page as Record<string, unknown>
|
|
34
|
+
const pagePath = page.path || '/'
|
|
35
35
|
|
|
36
36
|
// Skip pages with sitemap: false in frontmatter
|
|
37
37
|
if (meta.sitemap === false) continue
|