docus 3.0.0-beta.8 → 3.0.0

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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -9
  3. package/app/app/app.config.ts +33 -0
  4. package/app/app/app.vue +54 -0
  5. package/app/app/assets/css/main.css +5 -0
  6. package/app/app/components/IconMenuToggle.vue +81 -0
  7. package/app/app/components/OgImage/OgImageDocs.vue +76 -0
  8. package/app/app/components/OgImage/OgImageLanding.vue +73 -0
  9. package/app/app/components/app/AppFooter.vue +40 -0
  10. package/app/app/components/app/AppHeader.vue +57 -0
  11. package/app/app/components/app/AppHeaderBody.vue +13 -0
  12. package/{theme/components/globals/Logo.vue → app/app/components/app/AppHeaderCTA.vue} +1 -1
  13. package/app/app/components/app/AppHeaderCenter.vue +6 -0
  14. package/app/app/components/app/AppHeaderLogo.vue +16 -0
  15. package/app/app/components/docs/DocsAsideLeftTop.vue +3 -0
  16. package/app/app/components/docs/DocsAsideRightBottom.vue +17 -0
  17. package/app/app/components/docs/DocsPageHeaderLinks.vue +91 -0
  18. package/app/app/error.vue +42 -0
  19. package/app/app/layouts/docs.vue +25 -0
  20. package/app/app/pages/[...slug].vue +111 -0
  21. package/app/app/pages/index.vue +37 -0
  22. package/app/content.config.ts +31 -0
  23. package/app/nuxt.config.ts +41 -0
  24. package/app/nuxt.schema.ts +255 -0
  25. package/app/server/routes/raw/[...slug].md.get.ts +24 -0
  26. package/dist/main.mjs +242 -0
  27. package/package.json +66 -39
  28. package/theme/app/router.options.ts +0 -18
  29. package/theme/assets/css/fonts.css +0 -84
  30. package/theme/assets/css/main.css +0 -104
  31. package/theme/components/app/Container.vue +0 -25
  32. package/theme/components/app/Footer.vue +0 -40
  33. package/theme/components/app/MobileNav.vue +0 -85
  34. package/theme/components/app/Navbar.vue +0 -37
  35. package/theme/components/app/NavbarLogo.vue +0 -33
  36. package/theme/components/app/Page.vue +0 -7
  37. package/theme/components/app/PoweredByDocus.vue +0 -11
  38. package/theme/components/content/Alert.vue +0 -124
  39. package/theme/components/content/BlockHero.vue +0 -54
  40. package/theme/components/content/ButtonLink.vue +0 -45
  41. package/theme/components/content/Card.vue +0 -46
  42. package/theme/components/content/CardGrid.vue +0 -23
  43. package/theme/components/content/CodeBlock.vue +0 -47
  44. package/theme/components/content/CodeGroup.vue +0 -135
  45. package/theme/components/content/CopyButton.vue +0 -49
  46. package/theme/components/content/List.vue +0 -5
  47. package/theme/components/content/NeedContribution.vue +0 -23
  48. package/theme/components/content/ReadMore.vue +0 -25
  49. package/theme/components/content/Sandbox.vue +0 -102
  50. package/theme/components/content/TabsHeader.vue +0 -44
  51. package/theme/components/content/Terminal.vue +0 -64
  52. package/theme/components/content/VideoPlayer.vue +0 -115
  53. package/theme/components/dev/Debug.vue +0 -66
  54. package/theme/components/docs/DocsAside.vue +0 -21
  55. package/theme/components/docs/DocsAsideTree.vue +0 -104
  56. package/theme/components/docs/DocsHero.vue +0 -39
  57. package/theme/components/docs/DocsPage.vue +0 -21
  58. package/theme/components/docs/DocsPageContent.vue +0 -32
  59. package/theme/components/docs/DocsToc.vue +0 -77
  60. package/theme/components/globals/Icon.vue +0 -24
  61. package/theme/components/globals/NuxtImg.vue +0 -45
  62. package/theme/components/globals/SocialIcons.vue +0 -45
  63. package/theme/components/globals/ThemeSelect.vue +0 -35
  64. package/theme/components/icons/IconAlgolia.vue +0 -8
  65. package/theme/components/icons/IconArrowLeft.vue +0 -10
  66. package/theme/components/icons/IconArrowRight.vue +0 -10
  67. package/theme/components/icons/IconBadgeCheck.vue +0 -14
  68. package/theme/components/icons/IconCheck.vue +0 -10
  69. package/theme/components/icons/IconCheckCircle.vue +0 -10
  70. package/theme/components/icons/IconChevronRight.vue +0 -12
  71. package/theme/components/icons/IconClipboardCheck.vue +0 -14
  72. package/theme/components/icons/IconClipboardCopy.vue +0 -14
  73. package/theme/components/icons/IconCodeSandbox.vue +0 -8
  74. package/theme/components/icons/IconCopy.vue +0 -17
  75. package/theme/components/icons/IconDots.vue +0 -10
  76. package/theme/components/icons/IconEdit.vue +0 -18
  77. package/theme/components/icons/IconExclamationCircle.vue +0 -12
  78. package/theme/components/icons/IconExclamationTriangle.vue +0 -10
  79. package/theme/components/icons/IconExternalLink.vue +0 -12
  80. package/theme/components/icons/IconGit.vue +0 -7
  81. package/theme/components/icons/IconGitHub.vue +0 -10
  82. package/theme/components/icons/IconHeart.vue +0 -9
  83. package/theme/components/icons/IconInformationCircle.vue +0 -10
  84. package/theme/components/icons/IconLighthouse.vue +0 -83
  85. package/theme/components/icons/IconLine.vue +0 -10
  86. package/theme/components/icons/IconMarkdown.vue +0 -13
  87. package/theme/components/icons/IconMenu.vue +0 -12
  88. package/theme/components/icons/IconMenuAlt.vue +0 -10
  89. package/theme/components/icons/IconMinus.vue +0 -10
  90. package/theme/components/icons/IconMoon.vue +0 -10
  91. package/theme/components/icons/IconNuxt.vue +0 -14
  92. package/theme/components/icons/IconNuxtContent.vue +0 -20
  93. package/theme/components/icons/IconNuxtLabs.vue +0 -21
  94. package/theme/components/icons/IconPlus.vue +0 -10
  95. package/theme/components/icons/IconPuzzle.vue +0 -8
  96. package/theme/components/icons/IconSSG.vue +0 -7
  97. package/theme/components/icons/IconSearch.vue +0 -12
  98. package/theme/components/icons/IconSun.vue +0 -10
  99. package/theme/components/icons/IconTailwind.vue +0 -3
  100. package/theme/components/icons/IconTocBack.vue +0 -21
  101. package/theme/components/icons/IconTocCurrent.vue +0 -21
  102. package/theme/components/icons/IconTocNext.vue +0 -8
  103. package/theme/components/icons/IconTranslate.vue +0 -14
  104. package/theme/components/icons/IconTwitter.vue +0 -8
  105. package/theme/components/icons/IconVite.vue +0 -30
  106. package/theme/components/icons/IconVue.vue +0 -6
  107. package/theme/components/icons/IconVueTelescope.vue +0 -11
  108. package/theme/components/icons/IconWindi.vue +0 -17
  109. package/theme/components/icons/IconX.vue +0 -12
  110. package/theme/components/icons/IconXCircle.vue +0 -10
  111. package/theme/components/icons/IconZap.vue +0 -8
  112. package/theme/components/prose/ProseA.vue +0 -66
  113. package/theme/components/prose/ProseBlockquote.vue +0 -21
  114. package/theme/components/prose/ProseCode.vue +0 -67
  115. package/theme/components/prose/ProseCodeInline.vue +0 -38
  116. package/theme/components/prose/ProseEm.vue +0 -11
  117. package/theme/components/prose/ProseH1.vue +0 -22
  118. package/theme/components/prose/ProseH2.vue +0 -22
  119. package/theme/components/prose/ProseH3.vue +0 -24
  120. package/theme/components/prose/ProseH4.vue +0 -24
  121. package/theme/components/prose/ProseHr.vue +0 -13
  122. package/theme/components/prose/ProseImg.vue +0 -30
  123. package/theme/components/prose/ProseLi.vue +0 -31
  124. package/theme/components/prose/ProseOl.vue +0 -16
  125. package/theme/components/prose/ProseP.vue +0 -14
  126. package/theme/components/prose/ProseStrong.vue +0 -14
  127. package/theme/components/prose/ProseTable.vue +0 -13
  128. package/theme/components/prose/ProseTbody.vue +0 -5
  129. package/theme/components/prose/ProseTd.vue +0 -11
  130. package/theme/components/prose/ProseTh.vue +0 -11
  131. package/theme/components/prose/ProseThead.vue +0 -11
  132. package/theme/components/prose/ProseTr.vue +0 -11
  133. package/theme/components/prose/ProseUl.vue +0 -15
  134. package/theme/composables/useDocus.ts +0 -43
  135. package/theme/composables/useMenu.ts +0 -7
  136. package/theme/composables/useScrollToHeading.ts +0 -35
  137. package/theme/composables/useScrollspy.ts +0 -46
  138. package/theme/composables/useUserAgent.ts +0 -7
  139. package/theme/composables/utils.ts +0 -4
  140. package/theme/layouts/default.vue +0 -29
  141. package/theme/layouts/page.vue +0 -19
  142. package/theme/middleware/components.ts +0 -26
  143. package/theme/middleware/navigation.global.ts +0 -12
  144. package/theme/middleware/page.ts +0 -8
  145. package/theme/middleware/theme.global.ts +0 -12
  146. package/theme/nuxt.config.ts +0 -171
  147. package/theme/pages/[...slug].vue +0 -64
  148. package/theme/plugins/menu.ts +0 -67
  149. package/theme/plugins/user-agent.ts +0 -27
  150. package/theme/utils/components.ts +0 -25
  151. package/theme/utils/navigation.ts +0 -49
  152. package/theme/utils/plugin.ts +0 -21
  153. package/theme/utils/queries.ts +0 -68
  154. package/theme/utils/state.ts +0 -32
  155. package/theme/utils/theme.ts +0 -66
@@ -0,0 +1,42 @@
1
+ <script setup lang="ts">
2
+ import type { NuxtError } from '#app'
3
+
4
+ defineProps<{
5
+ error: NuxtError
6
+ }>()
7
+
8
+ useHead({
9
+ htmlAttrs: {
10
+ lang: 'en',
11
+ },
12
+ })
13
+
14
+ useSeoMeta({
15
+ title: 'Page not found',
16
+ description: 'We are sorry but this page could not be found.',
17
+ })
18
+
19
+ const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))
20
+ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
21
+ server: false,
22
+ })
23
+
24
+ provide('navigation', navigation)
25
+ </script>
26
+
27
+ <template>
28
+ <UApp>
29
+ <AppHeader />
30
+
31
+ <UError :error="error" />
32
+
33
+ <AppFooter />
34
+
35
+ <ClientOnly>
36
+ <LazyUContentSearch
37
+ :files="files"
38
+ :navigation="navigation"
39
+ />
40
+ </ClientOnly>
41
+ </UApp>
42
+ </template>
@@ -0,0 +1,25 @@
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
+ <UContainer>
9
+ <UPage>
10
+ <template #left>
11
+ <UPageAside>
12
+ <DocsAsideLeftTop />
13
+
14
+ <UContentNavigation
15
+ highlight
16
+ variant="link"
17
+ :navigation="navigation"
18
+ />
19
+ </UPageAside>
20
+ </template>
21
+
22
+ <slot />
23
+ </UPage>
24
+ </UContainer>
25
+ </template>
@@ -0,0 +1,111 @@
1
+ <script setup lang="ts">
2
+ import type { ContentNavigationItem } from '@nuxt/content'
3
+ import { findPageHeadline } from '#ui-pro/utils/content'
4
+
5
+ definePageMeta({
6
+ layout: 'docs',
7
+ })
8
+
9
+ const route = useRoute()
10
+ const appConfig = useAppConfig()
11
+ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
12
+
13
+ const { data: page } = await useAsyncData(route.path, () => queryCollection('docs').path(route.path).first())
14
+ if (!page.value) {
15
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
16
+ }
17
+
18
+ const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
19
+ return queryCollectionItemSurroundings('docs', route.path, {
20
+ fields: ['description'],
21
+ })
22
+ })
23
+
24
+ const title = page.value.seo?.title || page.value.title
25
+ const description = page.value.seo?.description || page.value.description
26
+
27
+ useSeoMeta({
28
+ title,
29
+ ogTitle: title,
30
+ description,
31
+ ogDescription: description,
32
+ })
33
+
34
+ const headline = computed(() => findPageHeadline(navigation?.value, page.value))
35
+ defineOgImageComponent('Docs', {
36
+ headline: headline.value,
37
+ })
38
+
39
+ const editLink = computed(() => {
40
+ return appConfig.github && `${appConfig.github.url}/edit/${appConfig.github.branch}/content/${page.value?.stem}.${page.value?.extension}`
41
+ })
42
+ </script>
43
+
44
+ <template>
45
+ <UPage v-if="page">
46
+ <UPageHeader
47
+ :title="page.title"
48
+ :description="page.description"
49
+ :links="page.links"
50
+ :headline="headline"
51
+ :ui="{
52
+ wrapper: 'flex-row items-center flex-wrap justify-between',
53
+ }"
54
+ >
55
+ <template #links>
56
+ <DocsPageHeaderLinks />
57
+ </template>
58
+ </UPageHeader>
59
+
60
+ <UPageBody>
61
+ <ContentRenderer
62
+ v-if="page"
63
+ :value="page"
64
+ />
65
+
66
+ <USeparator>
67
+ <div
68
+ v-if="editLink"
69
+ class="flex items-center gap-2 text-sm text-muted"
70
+ >
71
+ <UButton
72
+ variant="link"
73
+ color="neutral"
74
+ :to="editLink"
75
+ target="_blank"
76
+ icon="i-lucide-pen"
77
+ :ui="{ leadingIcon: 'size-4' }"
78
+ >
79
+ Edit this page
80
+ </UButton>
81
+ or
82
+ <UButton
83
+ variant="link"
84
+ color="neutral"
85
+ :to="`${appConfig.github.url}/issues/new/choose`"
86
+ target="_blank"
87
+ icon="i-lucide-alert-circle"
88
+ :ui="{ leadingIcon: 'size-4' }"
89
+ >
90
+ Report an issue
91
+ </UButton>
92
+ </div>
93
+ </USeparator>
94
+ <UContentSurround :surround="surround" />
95
+ </UPageBody>
96
+
97
+ <template
98
+ v-if="page?.body?.toc?.links?.length"
99
+ #right
100
+ >
101
+ <UContentToc
102
+ :title="appConfig.toc?.title || 'Table of Contents'"
103
+ :links="page.body?.toc?.links"
104
+ >
105
+ <template #bottom>
106
+ <DocsAsideRightBottom />
107
+ </template>
108
+ </UContentToc>
109
+ </template>
110
+ </UPage>
111
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ const { data: page } = await useAsyncData('index', () => queryCollection('landing').path('/').first())
3
+ if (!page.value) {
4
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
5
+ }
6
+
7
+ const title = page.value.seo?.title || page.value.title
8
+ const description = page.value.seo?.description || page.value.description
9
+
10
+ useSeoMeta({
11
+ title,
12
+ description,
13
+ ogTitle: title,
14
+ ogDescription: description,
15
+ })
16
+
17
+ if (page.value?.seo?.ogImage) {
18
+ useSeoMeta({
19
+ ogImage: page.value.seo.ogImage,
20
+ twitterImage: page.value.seo.ogImage,
21
+ })
22
+ }
23
+ else {
24
+ defineOgImageComponent('Landing', {
25
+ title,
26
+ description,
27
+ })
28
+ }
29
+ </script>
30
+
31
+ <template>
32
+ <ContentRenderer
33
+ v-if="page"
34
+ :value="page"
35
+ :prose="false"
36
+ />
37
+ </template>
@@ -0,0 +1,31 @@
1
+ import { defineContentConfig, defineCollection, z } from '@nuxt/content'
2
+
3
+ export default defineContentConfig({
4
+ collections: {
5
+ landing: defineCollection({
6
+ type: 'page',
7
+ source: {
8
+ // @ts-expect-error __DOCS_DIR__ is not defined
9
+ cwd: globalThis.__DOCS_DIR__,
10
+ include: 'index.md',
11
+ },
12
+ }),
13
+ docs: defineCollection({
14
+ type: 'page',
15
+ source: {
16
+ // @ts-expect-error __DOCS_DIR__ is not defined
17
+ cwd: globalThis.__DOCS_DIR__,
18
+ include: '**',
19
+ exclude: ['index.md'],
20
+ },
21
+ schema: z.object({
22
+ links: z.array(z.object({
23
+ label: z.string(),
24
+ icon: z.string(),
25
+ to: z.string(),
26
+ target: z.string().optional(),
27
+ })).optional(),
28
+ }),
29
+ }),
30
+ },
31
+ })
@@ -0,0 +1,41 @@
1
+ import { extendViteConfig } from '@nuxt/kit'
2
+
3
+ // Flag enabled when developing docs theme
4
+ const dev = !!process.env.NUXT_DOCS_DEV
5
+
6
+ export default defineNuxtConfig({
7
+ modules: [
8
+ '@nuxt/ui-pro',
9
+ '@nuxt/content',
10
+ '@nuxt/image',
11
+ '@nuxtjs/robots',
12
+ 'nuxt-og-image',
13
+ () => {
14
+ // Update @nuxt/content optimizeDeps options
15
+ extendViteConfig((config) => {
16
+ config.optimizeDeps ||= {}
17
+ config.optimizeDeps.include ||= []
18
+ config.optimizeDeps.include.push('@nuxt/content > slugify')
19
+ config.optimizeDeps.include = config.optimizeDeps.include
20
+ .map(id => id.replace(/^@nuxt\/content > /, 'docus > @nuxt/content > '))
21
+ })
22
+ },
23
+ ],
24
+ devtools: {
25
+ enabled: dev,
26
+ },
27
+ css: ['../app/assets/css/main.css'],
28
+ future: {
29
+ compatibilityVersion: 4,
30
+ },
31
+ nitro: {
32
+ prerender: {
33
+ routes: ['/'],
34
+ crawlLinks: true,
35
+ failOnError: false,
36
+ },
37
+ },
38
+ icon: {
39
+ provider: 'iconify',
40
+ },
41
+ })
@@ -0,0 +1,255 @@
1
+ import { field, group } from '@nuxt/content/preview'
2
+
3
+ export default defineNuxtSchema({
4
+ appConfig: {
5
+ ui: group({
6
+ title: 'UI',
7
+ description: 'UI Customization.',
8
+ icon: 'i-lucide-palette',
9
+ fields: {
10
+ colors: group({
11
+ title: 'Colors',
12
+ description: 'Manage main colors of your application',
13
+ icon: 'i-lucide-palette',
14
+ fields: {
15
+ primary: field({
16
+ type: 'string',
17
+ title: 'Primary',
18
+ description: 'Primary color of your UI.',
19
+ icon: 'i-lucide-palette',
20
+ default: 'green',
21
+ required: ['red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'],
22
+ }),
23
+ neutral: field({
24
+ type: 'string',
25
+ title: 'Neutral',
26
+ description: 'Neutral color of your UI.',
27
+ icon: 'i-lucide-palette',
28
+ default: 'slate',
29
+ required: ['slate', 'gray', 'zinc', 'neutral', 'stone'],
30
+ }),
31
+ },
32
+ }),
33
+ icons: group({
34
+ title: 'Icons',
35
+ description: 'Manage icons used in the application.',
36
+ icon: 'i-lucide-settings',
37
+ fields: {
38
+ search: field({
39
+ type: 'icon',
40
+ title: 'Search Bar',
41
+ description: 'Icon to display in the search bar.',
42
+ icon: 'i-lucide-search',
43
+ default: 'i-lucide-search',
44
+ }),
45
+ dark: field({
46
+ type: 'icon',
47
+ title: 'Dark mode',
48
+ description: 'Icon of color mode button for dark mode.',
49
+ icon: 'i-lucide-moon',
50
+ default: 'i-lucide-moon',
51
+ }),
52
+ light: field({
53
+ type: 'icon',
54
+ title: 'Light mode',
55
+ description: 'Icon of color mode button for light mode.',
56
+ icon: 'i-lucide-sun',
57
+ default: 'i-lucide-sun',
58
+ }),
59
+ external: field({
60
+ type: 'icon',
61
+ title: 'External Link',
62
+ description: 'Icon for external link.',
63
+ icon: 'i-lucide-external-link',
64
+ default: 'i-lucide-external-link',
65
+ }),
66
+ chevron: field({
67
+ type: 'icon',
68
+ title: 'Chevron',
69
+ description: 'Icon for chevron.',
70
+ icon: 'i-lucide-chevron-down',
71
+ default: 'i-lucide-chevron-down',
72
+ }),
73
+ hash: field({
74
+ type: 'icon',
75
+ title: 'Hash',
76
+ description: 'Icon for hash anchors.',
77
+ icon: 'i-lucide-hash',
78
+ default: 'i-lucide-hash',
79
+ }),
80
+ },
81
+ }),
82
+ },
83
+ }),
84
+ seo: group({
85
+ title: 'SEO',
86
+ description: 'SEO configuration.',
87
+ icon: 'i-lucide-search',
88
+ fields: {
89
+ title: field({
90
+ type: 'string',
91
+ title: 'Title',
92
+ description: 'Title to display in the header.',
93
+ icon: 'i-lucide-type',
94
+ default: '',
95
+ }),
96
+ description: field({
97
+ type: 'string',
98
+ title: 'Description',
99
+ description: 'Description to display in the header.',
100
+ icon: 'i-lucide-type',
101
+ default: '',
102
+ }),
103
+ },
104
+ }),
105
+ header: group({
106
+ title: 'Header',
107
+ description: 'Header configuration.',
108
+ icon: 'i-lucide-layout',
109
+ fields: {
110
+ title: field({
111
+ type: 'string',
112
+ title: 'Title',
113
+ description: 'Title to display in the header.',
114
+ icon: 'i-lucide-type',
115
+ default: '',
116
+ }),
117
+ logo: group({
118
+ title: 'Logo',
119
+ description: 'Header logo configuration.',
120
+ icon: 'i-lucide-image',
121
+ fields: {
122
+ light: field({
123
+ type: 'media',
124
+ title: 'Light Mode Logo',
125
+ description: 'Pick an image from your gallery.',
126
+ icon: 'i-lucide-sun',
127
+ default: '',
128
+ }),
129
+ dark: field({
130
+ type: 'media',
131
+ title: 'Dark Mode Logo',
132
+ description: 'Pick an image from your gallery.',
133
+ icon: 'i-lucide-moon',
134
+ default: '',
135
+ }),
136
+ alt: field({
137
+ type: 'string',
138
+ title: 'Alt',
139
+ description: 'Alt to display for accessibility.',
140
+ icon: 'i-lucide-text',
141
+ default: '',
142
+ }),
143
+ },
144
+ }),
145
+ },
146
+ }),
147
+ socials: field({
148
+ type: 'object',
149
+ title: 'Social Networks',
150
+ description: 'Social links configuration.',
151
+ icon: 'i-lucide-network',
152
+ default: {},
153
+ }),
154
+ toc: group({
155
+ title: 'Table of contents',
156
+ description: 'TOC configuration.',
157
+ icon: 'i-lucide-list',
158
+ fields: {
159
+ title: field({
160
+ type: 'string',
161
+ title: 'Title',
162
+ description: 'Title of the table of contents.',
163
+ icon: 'i-lucide-heading',
164
+ default: 'On this page',
165
+ }),
166
+ bottom: group({
167
+ title: 'Bottom',
168
+ description: 'Bottom section of the table of contents.',
169
+ icon: 'i-lucide-list',
170
+ fields: {
171
+ title: field({
172
+ type: 'string',
173
+ title: 'Title',
174
+ description: 'Title of the bottom section.',
175
+ icon: 'i-lucide-heading',
176
+ default: 'Community',
177
+ }),
178
+ links: field({
179
+ type: 'array',
180
+ title: 'Links',
181
+ description: 'Links to display in the bottom section.',
182
+ icon: 'i-lucide-link',
183
+ default: [],
184
+ }),
185
+ },
186
+ }),
187
+ },
188
+ }),
189
+ github: group({
190
+ title: 'GitHub',
191
+ description: 'GitHub configuration.',
192
+ icon: 'i-lucide-github',
193
+ fields: {
194
+ url: field({
195
+ type: 'string',
196
+ title: 'URL',
197
+ description: 'GitHub URL.',
198
+ icon: 'i-lucide-github',
199
+ default: '',
200
+ }),
201
+ },
202
+ }),
203
+ },
204
+ })
205
+
206
+ declare module '@nuxt/schema' {
207
+ interface AppConfig {
208
+ seo: {
209
+ titleTemplate: string
210
+ title: string
211
+ description: string
212
+ }
213
+ ui: {
214
+ colors: {
215
+ primary: string
216
+ neutral: string
217
+ }
218
+ icons: {
219
+ search: string
220
+ dark: string
221
+ light: string
222
+ external: string
223
+ chevron: string
224
+ hash: string
225
+ } & Record<string, string>
226
+ }
227
+ header: {
228
+ title: string
229
+ logo: {
230
+ light: string
231
+ dark: string
232
+ alt: string
233
+ }
234
+ }
235
+ socials: Record<string, string>
236
+ toc: {
237
+ title: string
238
+ bottom: {
239
+ title: string
240
+ links: {
241
+ icon: string
242
+ label: string
243
+ to: string
244
+ target: string
245
+ }[]
246
+ }
247
+ }
248
+ github: {
249
+ owner: string
250
+ name: string
251
+ url: string
252
+ branch: string
253
+ }
254
+ }
255
+ }
@@ -0,0 +1,24 @@
1
+ import { stringify } from 'minimark/stringify'
2
+ import { withLeadingSlash } from 'ufo'
3
+
4
+ export default eventHandler(async (event) => {
5
+ const slug = getRouterParams(event)['slug.md']
6
+ if (!slug?.endsWith('.md')) {
7
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
8
+ }
9
+
10
+ const path = withLeadingSlash(slug.replace('.md', ''))
11
+ const page = await queryCollection(event, 'docs').path(path).first()
12
+ if (!page) {
13
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
14
+ }
15
+
16
+ // Add title and description to the top of the page if missing
17
+ if (page.body.value[0]?.[0] !== 'h1') {
18
+ page.body.value.unshift(['blockquote', {}, page.description])
19
+ page.body.value.unshift(['h1', {}, page.title])
20
+ }
21
+
22
+ setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
23
+ return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
24
+ })