@movk/nuxt-docs 1.5.2 → 1.6.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.
- package/README.md +54 -24
- package/app/components/DocsAsideLeftBody.vue +13 -0
- package/app/components/DocsAsideLeftTop.vue +6 -0
- package/app/components/DocsAsideRightBottom.vue +29 -0
- package/app/components/OgImage/Nuxt.vue +40 -16
- package/app/components/PageHeaderLinks.vue +35 -9
- package/app/components/content/CommitChangelog.vue +10 -10
- package/app/components/content/ComponentEmits.vue +2 -2
- package/app/components/content/ComponentExample.vue +10 -10
- package/app/components/content/ComponentProps.vue +5 -3
- package/app/components/content/ComponentPropsSchema.vue +5 -1
- package/app/components/content/ComponentSlots.vue +2 -2
- package/app/components/content/HighlightInlineType.vue +1 -1
- package/app/components/content/PageLastCommit.vue +12 -8
- package/app/components/footer/Footer.vue +22 -0
- package/app/components/footer/FooterLeft.vue +7 -0
- package/app/components/footer/FooterRight.vue +14 -0
- package/app/components/header/Header.vue +4 -7
- package/app/components/header/HeaderCTA.vue +18 -0
- package/app/components/header/HeaderCenter.vue +7 -0
- package/app/components/theme-picker/ThemePicker.vue +24 -201
- package/app/composables/useFaq.ts +21 -0
- package/app/composables/useHighlighter.ts +22 -0
- package/app/composables/useTheme.ts +223 -0
- package/app/layouts/docs.vue +2 -15
- package/app/pages/docs/[...slug].vue +1 -1
- package/app/utils/shiki-transformer-icon-highlight.ts +1 -1
- package/app/utils/unicode.ts +12 -0
- package/modules/ai-chat/index.ts +90 -0
- package/modules/ai-chat/runtime/components/AiChat.vue +22 -0
- package/modules/ai-chat/runtime/components/AiChatFloatingInput.vue +85 -0
- package/modules/ai-chat/runtime/components/AiChatModelSelect.vue +24 -0
- package/modules/ai-chat/runtime/components/AiChatPreStream.vue +58 -0
- package/modules/ai-chat/runtime/components/AiChatReasoning.vue +49 -0
- package/modules/ai-chat/runtime/components/AiChatSlideover.vue +245 -0
- package/modules/ai-chat/runtime/components/AiChatSlideoverFaq.vue +41 -0
- package/modules/ai-chat/runtime/components/AiChatToolCall.vue +31 -0
- package/modules/ai-chat/runtime/composables/useAIChat.ts +45 -0
- package/modules/ai-chat/runtime/composables/useModels.ts +58 -0
- package/modules/ai-chat/runtime/composables/useTools.ts +31 -0
- package/modules/ai-chat/runtime/server/api/search.ts +84 -0
- package/modules/ai-chat/runtime/server/utils/docs_agent.ts +49 -0
- package/modules/ai-chat/runtime/server/utils/getModel.ts +25 -0
- package/modules/css.ts +2 -0
- package/nuxt.config.ts +27 -39
- package/package.json +16 -7
- package/server/mcp/tools/get-page.ts +60 -0
- package/server/mcp/tools/list-pages.ts +49 -0
- package/app/components/AdsCarbon.vue +0 -3
- package/app/components/Footer.vue +0 -32
- package/modules/llms.ts +0 -27
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<USeparator :icon="route.path === '/' ? undefined : 'i-simple-icons-nuxtdotjs'" class="h-px" />
|
|
7
|
+
|
|
8
|
+
<UFooter
|
|
9
|
+
:ui="{
|
|
10
|
+
left: 'text-sm text-muted',
|
|
11
|
+
root: 'border-t border-default'
|
|
12
|
+
}"
|
|
13
|
+
>
|
|
14
|
+
<template #left>
|
|
15
|
+
<FooterLeft />
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<template #right>
|
|
19
|
+
<FooterRight />
|
|
20
|
+
</template>
|
|
21
|
+
</UFooter>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
const { footer } = useAppConfig()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<UTooltip
|
|
7
|
+
v-for="(link, count) in footer.socials"
|
|
8
|
+
:key="count"
|
|
9
|
+
:text="link.label || (link as any)['aria-label']"
|
|
10
|
+
class="hidden lg:flex"
|
|
11
|
+
>
|
|
12
|
+
<UButton v-bind="{ color: 'neutral', variant: 'ghost', ...link }" />
|
|
13
|
+
</UTooltip>
|
|
14
|
+
</template>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import type { ButtonProps } from '@nuxt/ui'
|
|
3
3
|
|
|
4
4
|
const route = useRoute()
|
|
5
|
-
const { desktopLinks } = useHeader()
|
|
6
5
|
const { header, github } = useAppConfig()
|
|
7
6
|
|
|
8
7
|
const links = computed<ButtonProps[]>(() => github && github.url
|
|
@@ -24,9 +23,11 @@ const links = computed<ButtonProps[]>(() => github && github.url
|
|
|
24
23
|
<HeaderLogo />
|
|
25
24
|
</template>
|
|
26
25
|
|
|
27
|
-
<
|
|
26
|
+
<HeaderCenter />
|
|
28
27
|
|
|
29
28
|
<template #right>
|
|
29
|
+
<HeaderCTA />
|
|
30
|
+
|
|
30
31
|
<ThemePicker />
|
|
31
32
|
|
|
32
33
|
<UTooltip text="Search" :kbds="['meta', 'K']">
|
|
@@ -50,11 +51,7 @@ const links = computed<ButtonProps[]>(() => github && github.url
|
|
|
50
51
|
</template>
|
|
51
52
|
|
|
52
53
|
<template #toggle="{ open, toggle, ui }">
|
|
53
|
-
<HeaderToggleButton
|
|
54
|
-
:open="open"
|
|
55
|
-
:class="ui.toggle({ toggleSide: 'right' })"
|
|
56
|
-
@click="toggle"
|
|
57
|
-
/>
|
|
54
|
+
<HeaderToggleButton :open="open" :class="ui.toggle({ toggleSide: 'right' })" @click="toggle" />
|
|
58
55
|
</template>
|
|
59
56
|
|
|
60
57
|
<template #body>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
const { aiChat } = useRuntimeConfig().public
|
|
3
|
+
const route = useRoute()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div v-if="aiChat.enable" class="hidden md:block">
|
|
8
|
+
<UButton
|
|
9
|
+
v-if="route.path === '/'"
|
|
10
|
+
to="/docs"
|
|
11
|
+
label="Get Started"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
trailing
|
|
14
|
+
icon="i-lucide-arrow-right"
|
|
15
|
+
/>
|
|
16
|
+
<AiChat v-else />
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { omit } from '@movk/core'
|
|
3
|
-
import colors from 'tailwindcss/colors'
|
|
4
2
|
import { useClipboard } from '@vueuse/core'
|
|
5
|
-
import { themeIcons } from '../../utils/theme'
|
|
6
3
|
|
|
7
4
|
const appConfig = useAppConfig()
|
|
8
5
|
const colorMode = useColorMode()
|
|
9
|
-
const site = useSiteConfig()
|
|
10
6
|
|
|
11
7
|
const { track } = useAnalytics()
|
|
12
8
|
|
|
@@ -21,199 +17,26 @@ watch(open, (isOpen) => {
|
|
|
21
17
|
const { copy: copyCSS, copied: copiedCSS } = useClipboard()
|
|
22
18
|
const { copy: copyAppConfig, copied: copiedAppConfig } = useClipboard()
|
|
23
19
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
window.localStorage.setItem(`${site.name}-ui-primary`, appConfig.ui.colors.primary)
|
|
45
|
-
setBlackAsPrimary(false)
|
|
46
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'primary', value: option })
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
|
|
51
|
-
const radius = computed({
|
|
52
|
-
get() {
|
|
53
|
-
return appConfig.theme.radius
|
|
54
|
-
},
|
|
55
|
-
set(option) {
|
|
56
|
-
appConfig.theme.radius = option
|
|
57
|
-
window.localStorage.setItem(`${site.name}-ui-radius`, String(appConfig.theme.radius))
|
|
58
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'radius', value: option })
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const modes = [
|
|
63
|
-
{ label: 'light', icon: 'i-lucide-sun' },
|
|
64
|
-
{ label: 'dark', icon: 'i-lucide-moon' },
|
|
65
|
-
{ label: 'system', icon: 'i-lucide-monitor' }
|
|
66
|
-
]
|
|
67
|
-
const mode = computed({
|
|
68
|
-
get() {
|
|
69
|
-
return colorMode.value
|
|
70
|
-
},
|
|
71
|
-
set(option) {
|
|
72
|
-
colorMode.preference = option
|
|
73
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'color mode', value: option })
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
function setBlackAsPrimary(value: boolean) {
|
|
78
|
-
appConfig.theme.blackAsPrimary = value
|
|
79
|
-
window.localStorage.setItem(`${site.name}-ui-black-as-primary`, String(value))
|
|
80
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'black as primary', value })
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const fonts = ['Public Sans', 'DM Sans', 'Geist', 'Inter', 'Poppins', 'Outfit', 'Raleway']
|
|
84
|
-
const font = computed({
|
|
85
|
-
get() {
|
|
86
|
-
return appConfig.theme.font
|
|
87
|
-
},
|
|
88
|
-
set(option) {
|
|
89
|
-
appConfig.theme.font = option
|
|
90
|
-
window.localStorage.setItem(`${site.name}-ui-font`, appConfig.theme.font)
|
|
91
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'font', value: option })
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const icons = [{
|
|
96
|
-
label: 'Lucide',
|
|
97
|
-
icon: 'i-lucide-feather',
|
|
98
|
-
value: 'lucide'
|
|
99
|
-
}, {
|
|
100
|
-
label: 'Phosphor',
|
|
101
|
-
icon: 'i-ph-phosphor-logo',
|
|
102
|
-
value: 'phosphor'
|
|
103
|
-
}, {
|
|
104
|
-
label: 'Tabler',
|
|
105
|
-
icon: 'i-tabler-brand-tabler',
|
|
106
|
-
value: 'tabler'
|
|
107
|
-
}]
|
|
108
|
-
const icon = computed({
|
|
109
|
-
get() {
|
|
110
|
-
return appConfig.theme.icons
|
|
111
|
-
},
|
|
112
|
-
set(option) {
|
|
113
|
-
appConfig.theme.icons = option
|
|
114
|
-
appConfig.ui.icons = themeIcons[option as keyof typeof themeIcons] as any
|
|
115
|
-
window.localStorage.setItem(`${site.name}-ui-icons`, appConfig.theme.icons)
|
|
116
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'icons', value: option })
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
const hasCSSChanges = computed(() => {
|
|
121
|
-
return appConfig.theme.radius !== 0.25
|
|
122
|
-
|| appConfig.theme.blackAsPrimary
|
|
123
|
-
|| appConfig.theme.font !== 'Public Sans'
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
const hasAppConfigChanges = computed(() => {
|
|
127
|
-
return appConfig.ui.colors.primary !== 'green'
|
|
128
|
-
|| appConfig.ui.colors.neutral !== 'slate'
|
|
129
|
-
|| appConfig.theme.icons !== 'lucide'
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
function exportCSS() {
|
|
133
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Exported', { type: 'css' })
|
|
134
|
-
|
|
135
|
-
const lines = [
|
|
136
|
-
'@import "tailwindcss";',
|
|
137
|
-
'@import "@nuxt/ui";'
|
|
138
|
-
]
|
|
139
|
-
|
|
140
|
-
if (appConfig.theme.font !== 'Public Sans') {
|
|
141
|
-
lines.push('', '@theme {', ` --font-sans: '${appConfig.theme.font}', sans-serif;`, '}')
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const rootLines: string[] = []
|
|
145
|
-
if (appConfig.theme.radius !== 0.25) {
|
|
146
|
-
rootLines.push(` --ui-radius: ${appConfig.theme.radius}rem;`)
|
|
147
|
-
}
|
|
148
|
-
if (appConfig.theme.blackAsPrimary) {
|
|
149
|
-
rootLines.push(' --ui-primary: black;')
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (rootLines.length) {
|
|
153
|
-
lines.push('', ':root {', ...rootLines, '}')
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (appConfig.theme.blackAsPrimary) {
|
|
157
|
-
lines.push('', '.dark {', ' --ui-primary: white;', '}')
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
copyCSS(lines.join('\n'))
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function exportAppConfig() {
|
|
164
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Exported', { type: 'appConfig' })
|
|
165
|
-
|
|
166
|
-
const config: Record<string, any> = {}
|
|
167
|
-
|
|
168
|
-
if (appConfig.ui.colors.primary !== 'green' || appConfig.ui.colors.neutral !== 'slate') {
|
|
169
|
-
config.ui = { colors: {} }
|
|
170
|
-
if (appConfig.ui.colors.primary !== 'green') {
|
|
171
|
-
config.ui.colors.primary = appConfig.ui.colors.primary
|
|
172
|
-
}
|
|
173
|
-
if (appConfig.ui.colors.neutral !== 'slate') {
|
|
174
|
-
config.ui.colors.neutral = appConfig.ui.colors.neutral
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (appConfig.theme.icons !== 'lucide') {
|
|
179
|
-
const iconSet = appConfig.theme.icons
|
|
180
|
-
const icons = themeIcons[iconSet as keyof typeof themeIcons]
|
|
181
|
-
config.ui = config.ui || {}
|
|
182
|
-
config.ui.icons = icons
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const configString = JSON.stringify(config, null, 2)
|
|
186
|
-
.replace(/"([^"]+)":/g, '$1:')
|
|
187
|
-
.replace(/"/g, '\'')
|
|
188
|
-
|
|
189
|
-
const output = `export default defineAppConfig(${configString})`
|
|
190
|
-
|
|
191
|
-
copyAppConfig(output)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function resetTheme() {
|
|
195
|
-
if (appConfig.vercelAnalytics?.debug) track('Theme Reset')
|
|
196
|
-
|
|
197
|
-
// Reset without triggering individual tracking events
|
|
198
|
-
appConfig.ui.colors.primary = 'green'
|
|
199
|
-
window.localStorage.removeItem(`${site.name}-ui-primary`)
|
|
200
|
-
|
|
201
|
-
appConfig.ui.colors.neutral = 'slate'
|
|
202
|
-
window.localStorage.removeItem(`${site.name}-ui-neutral`)
|
|
203
|
-
|
|
204
|
-
appConfig.theme.radius = 0.25
|
|
205
|
-
window.localStorage.removeItem(`${site.name}-ui-radius`)
|
|
206
|
-
|
|
207
|
-
appConfig.theme.font = 'Public Sans'
|
|
208
|
-
window.localStorage.removeItem(`${site.name}-ui-font`)
|
|
209
|
-
|
|
210
|
-
appConfig.theme.icons = 'lucide'
|
|
211
|
-
appConfig.ui.icons = themeIcons.lucide as any
|
|
212
|
-
window.localStorage.removeItem(`${site.name}-ui-icons`)
|
|
213
|
-
|
|
214
|
-
appConfig.theme.blackAsPrimary = false
|
|
215
|
-
window.localStorage.removeItem(`${site.name}-ui-black-as-primary`)
|
|
216
|
-
}
|
|
20
|
+
const {
|
|
21
|
+
neutralColors,
|
|
22
|
+
neutral,
|
|
23
|
+
primaryColors,
|
|
24
|
+
primary,
|
|
25
|
+
setBlackAsPrimary,
|
|
26
|
+
radiuses,
|
|
27
|
+
radius,
|
|
28
|
+
fonts,
|
|
29
|
+
font,
|
|
30
|
+
icon,
|
|
31
|
+
icons,
|
|
32
|
+
modes,
|
|
33
|
+
mode,
|
|
34
|
+
hasCSSChanges,
|
|
35
|
+
hasAppConfigChanges,
|
|
36
|
+
exportCSS,
|
|
37
|
+
exportAppConfig,
|
|
38
|
+
resetTheme
|
|
39
|
+
} = useTheme()
|
|
217
40
|
</script>
|
|
218
41
|
|
|
219
42
|
<template>
|
|
@@ -418,8 +241,8 @@ function resetTheme() {
|
|
|
418
241
|
size="sm"
|
|
419
242
|
label="main.css"
|
|
420
243
|
class="flex-1 text-[11px]"
|
|
421
|
-
:icon="copiedCSS ?
|
|
422
|
-
@click="exportCSS"
|
|
244
|
+
:icon="copiedCSS ? appConfig.ui.icons.copyCheck : appConfig.ui.icons.copy"
|
|
245
|
+
@click="copyCSS(exportCSS())"
|
|
423
246
|
/>
|
|
424
247
|
<UButton
|
|
425
248
|
v-if="hasAppConfigChanges"
|
|
@@ -427,9 +250,9 @@ function resetTheme() {
|
|
|
427
250
|
variant="soft"
|
|
428
251
|
size="sm"
|
|
429
252
|
label="app.config.ts"
|
|
430
|
-
:icon="copiedAppConfig ?
|
|
253
|
+
:icon="copiedAppConfig ? appConfig.ui.icons.copyCheck : appConfig.ui.icons.copy"
|
|
431
254
|
class="flex-1 text-[11px]"
|
|
432
|
-
@click="exportAppConfig"
|
|
255
|
+
@click="copyAppConfig(exportAppConfig())"
|
|
433
256
|
/>
|
|
434
257
|
<UTooltip text="Reset theme">
|
|
435
258
|
<UButton
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface FaqItem {
|
|
2
|
+
category: string
|
|
3
|
+
items: string[]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function useFaq() {
|
|
7
|
+
const faqQuestions: FaqItem[] = [
|
|
8
|
+
{
|
|
9
|
+
category: 'MCP 工具使用',
|
|
10
|
+
items: [
|
|
11
|
+
'如何查询所有可用的文档页面?',
|
|
12
|
+
'如何获取特定文档页面的完整内容?',
|
|
13
|
+
'什么时候应该使用 list-pages 而不是 get-page?'
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
faqQuestions
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHighlighter } from 'shiki'
|
|
2
|
+
import type { HighlighterGeneric } from 'shiki'
|
|
3
|
+
import { createJavaScriptRegexEngine } from 'shiki/engine-javascript.mjs'
|
|
4
|
+
|
|
5
|
+
let highlighter: HighlighterGeneric<any, any> | null = null
|
|
6
|
+
|
|
7
|
+
let promise: Promise<HighlighterGeneric<any, any>> | null = null
|
|
8
|
+
|
|
9
|
+
export const useHighlighter = async () => {
|
|
10
|
+
if (!promise) {
|
|
11
|
+
promise = createHighlighter({
|
|
12
|
+
langs: ['vue', 'js', 'ts', 'css', 'html', 'json', 'yaml', 'markdown', 'bash'],
|
|
13
|
+
themes: ['material-theme-palenight', 'material-theme-lighter'],
|
|
14
|
+
engine: createJavaScriptRegexEngine()
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
if (!highlighter) {
|
|
18
|
+
highlighter = await promise
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return highlighter
|
|
22
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { themeIcons } from '../utils/theme'
|
|
2
|
+
import { omit } from '@movk/core'
|
|
3
|
+
import colors from 'tailwindcss/colors'
|
|
4
|
+
|
|
5
|
+
export function useTheme() {
|
|
6
|
+
const appConfig = useAppConfig()
|
|
7
|
+
const colorMode = useColorMode()
|
|
8
|
+
const site = useSiteConfig()
|
|
9
|
+
const { track } = useAnalytics()
|
|
10
|
+
|
|
11
|
+
const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
|
|
12
|
+
const neutral = computed({
|
|
13
|
+
get() {
|
|
14
|
+
return appConfig.ui.colors.neutral
|
|
15
|
+
},
|
|
16
|
+
set(option) {
|
|
17
|
+
appConfig.ui.colors.neutral = option
|
|
18
|
+
window.localStorage.setItem(`${site.name}-ui-neutral`, appConfig.ui.colors.neutral)
|
|
19
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'neutral', value: option })
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const colorsToOmit = ['inherit', 'current', 'transparent', 'black', 'white', ...neutralColors]
|
|
24
|
+
const primaryColors = Object.keys(omit(colors, colorsToOmit as any))
|
|
25
|
+
const primary = computed({
|
|
26
|
+
get() {
|
|
27
|
+
return appConfig.ui.colors.primary
|
|
28
|
+
},
|
|
29
|
+
set(option) {
|
|
30
|
+
appConfig.ui.colors.primary = option
|
|
31
|
+
window.localStorage.setItem(`${site.name}-ui-primary`, appConfig.ui.colors.primary)
|
|
32
|
+
setBlackAsPrimary(false)
|
|
33
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'primary', value: option })
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
|
|
38
|
+
const radius = computed({
|
|
39
|
+
get() {
|
|
40
|
+
return appConfig.theme.radius
|
|
41
|
+
},
|
|
42
|
+
set(option) {
|
|
43
|
+
appConfig.theme.radius = option
|
|
44
|
+
window.localStorage.setItem(`${site.name}-ui-radius`, String(appConfig.theme.radius))
|
|
45
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'radius', value: option })
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const fonts = ['Public Sans', 'DM Sans', 'Geist', 'Inter', 'Poppins', 'Outfit', 'Raleway']
|
|
50
|
+
const font = computed({
|
|
51
|
+
get() {
|
|
52
|
+
return appConfig.theme.font
|
|
53
|
+
},
|
|
54
|
+
set(option) {
|
|
55
|
+
appConfig.theme.font = option
|
|
56
|
+
window.localStorage.setItem(`${site.name}-ui-font`, appConfig.theme.font)
|
|
57
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'font', value: option })
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const icons = [{
|
|
62
|
+
label: 'Lucide',
|
|
63
|
+
icon: 'i-lucide-feather',
|
|
64
|
+
value: 'lucide'
|
|
65
|
+
}, {
|
|
66
|
+
label: 'Phosphor',
|
|
67
|
+
icon: 'i-ph-phosphor-logo',
|
|
68
|
+
value: 'phosphor'
|
|
69
|
+
}, {
|
|
70
|
+
label: 'Tabler',
|
|
71
|
+
icon: 'i-tabler-brand-tabler',
|
|
72
|
+
value: 'tabler'
|
|
73
|
+
}]
|
|
74
|
+
const icon = computed({
|
|
75
|
+
get() {
|
|
76
|
+
return appConfig.theme.icons
|
|
77
|
+
},
|
|
78
|
+
set(option) {
|
|
79
|
+
appConfig.theme.icons = option
|
|
80
|
+
appConfig.ui.icons = themeIcons[option as keyof typeof themeIcons] as any
|
|
81
|
+
window.localStorage.setItem(`${site.name}-ui-icons`, appConfig.theme.icons)
|
|
82
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'icons', value: option })
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const modes = [
|
|
87
|
+
{ label: 'light', icon: appConfig.ui.icons.light },
|
|
88
|
+
{ label: 'dark', icon: appConfig.ui.icons.dark },
|
|
89
|
+
{ label: 'system', icon: appConfig.ui.icons.system }
|
|
90
|
+
]
|
|
91
|
+
const mode = computed({
|
|
92
|
+
get() {
|
|
93
|
+
return colorMode.value
|
|
94
|
+
},
|
|
95
|
+
set(option) {
|
|
96
|
+
colorMode.preference = option
|
|
97
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Changed', { setting: 'color mode', value: option })
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
function setBlackAsPrimary(value: boolean) {
|
|
102
|
+
appConfig.theme.blackAsPrimary = value
|
|
103
|
+
window.localStorage.setItem(`${site.name}-ui-black-as-primary`, String(value))
|
|
104
|
+
if (appConfig.vercelAnalytics?.debug && value) track('Theme Changed', { setting: 'black as primary', value })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hasCSSChanges = computed(() => {
|
|
108
|
+
return appConfig.theme.radius !== 0.25
|
|
109
|
+
|| appConfig.theme.blackAsPrimary
|
|
110
|
+
|| appConfig.theme.font !== 'Public Sans'
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const hasAppConfigChanges = computed(() => {
|
|
114
|
+
return appConfig.ui.colors.primary !== 'green'
|
|
115
|
+
|| appConfig.ui.colors.neutral !== 'slate'
|
|
116
|
+
|| appConfig.theme.icons !== 'lucide'
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
function exportCSS(): string {
|
|
120
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Exported', { type: 'css' })
|
|
121
|
+
|
|
122
|
+
const lines = [
|
|
123
|
+
'@import "tailwindcss";',
|
|
124
|
+
'@import "@nuxt/ui";'
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
if (appConfig.theme.font !== 'Public Sans') {
|
|
128
|
+
lines.push('', '@theme {', ` --font-sans: '${appConfig.theme.font}', sans-serif;`, '}')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const rootLines: string[] = []
|
|
132
|
+
if (appConfig.theme.radius !== 0.25) {
|
|
133
|
+
rootLines.push(` --ui-radius: ${appConfig.theme.radius}rem;`)
|
|
134
|
+
}
|
|
135
|
+
if (appConfig.theme.blackAsPrimary) {
|
|
136
|
+
rootLines.push(' --ui-primary: black;')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (rootLines.length) {
|
|
140
|
+
lines.push('', ':root {', ...rootLines, '}')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (appConfig.theme.blackAsPrimary) {
|
|
144
|
+
lines.push('', '.dark {', ' --ui-primary: white;', '}')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return lines.join('\n')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function exportAppConfig(): string {
|
|
151
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Exported', { type: 'appConfig' })
|
|
152
|
+
|
|
153
|
+
const config: Record<string, any> = {}
|
|
154
|
+
|
|
155
|
+
if (appConfig.ui.colors.primary !== 'green' || appConfig.ui.colors.neutral !== 'slate') {
|
|
156
|
+
config.ui = { colors: {} }
|
|
157
|
+
if (appConfig.ui.colors.primary !== 'green') {
|
|
158
|
+
config.ui.colors.primary = appConfig.ui.colors.primary
|
|
159
|
+
}
|
|
160
|
+
if (appConfig.ui.colors.neutral !== 'slate') {
|
|
161
|
+
config.ui.colors.neutral = appConfig.ui.colors.neutral
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (appConfig.theme.icons !== 'lucide') {
|
|
166
|
+
const iconSet = appConfig.theme.icons
|
|
167
|
+
const icons = themeIcons[iconSet as keyof typeof themeIcons]
|
|
168
|
+
config.ui = config.ui || {}
|
|
169
|
+
config.ui.icons = icons
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const configString = JSON.stringify(config, null, 2)
|
|
173
|
+
.replace(/"([^"]+)":/g, '$1:')
|
|
174
|
+
.replace(/"/g, '\'')
|
|
175
|
+
|
|
176
|
+
return `export default defineAppConfig(${configString})`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resetTheme() {
|
|
180
|
+
if (appConfig.vercelAnalytics?.debug) track('Theme Reset')
|
|
181
|
+
|
|
182
|
+
// Reset without triggering individual tracking events
|
|
183
|
+
appConfig.ui.colors.primary = 'green'
|
|
184
|
+
window.localStorage.removeItem(`${site.name}-ui-primary`)
|
|
185
|
+
|
|
186
|
+
appConfig.ui.colors.neutral = 'slate'
|
|
187
|
+
window.localStorage.removeItem(`${site.name}-ui-neutral`)
|
|
188
|
+
|
|
189
|
+
appConfig.theme.radius = 0.25
|
|
190
|
+
window.localStorage.removeItem(`${site.name}-ui-radius`)
|
|
191
|
+
|
|
192
|
+
appConfig.theme.font = 'Public Sans'
|
|
193
|
+
window.localStorage.removeItem(`${site.name}-ui-font`)
|
|
194
|
+
|
|
195
|
+
appConfig.theme.icons = 'lucide'
|
|
196
|
+
appConfig.ui.icons = themeIcons.lucide as any
|
|
197
|
+
window.localStorage.removeItem(`${site.name}-ui-icons`)
|
|
198
|
+
|
|
199
|
+
appConfig.theme.blackAsPrimary = false
|
|
200
|
+
window.localStorage.removeItem(`${site.name}-ui-black-as-primary`)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
neutralColors,
|
|
205
|
+
neutral,
|
|
206
|
+
primaryColors,
|
|
207
|
+
primary,
|
|
208
|
+
setBlackAsPrimary,
|
|
209
|
+
radiuses,
|
|
210
|
+
radius,
|
|
211
|
+
fonts,
|
|
212
|
+
font,
|
|
213
|
+
icon,
|
|
214
|
+
icons,
|
|
215
|
+
modes,
|
|
216
|
+
mode,
|
|
217
|
+
hasCSSChanges,
|
|
218
|
+
hasAppConfigChanges,
|
|
219
|
+
exportCSS,
|
|
220
|
+
exportAppConfig,
|
|
221
|
+
resetTheme
|
|
222
|
+
}
|
|
223
|
+
}
|
package/app/layouts/docs.vue
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { ContentNavigationItem } from '@nuxt/content'
|
|
3
|
-
|
|
4
|
-
const route = useRoute()
|
|
5
|
-
|
|
6
|
-
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
|
7
|
-
|
|
8
|
-
const { navigationByCategory } = useNavigation(navigation!)
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
1
|
<template>
|
|
12
2
|
<UMain class="relative">
|
|
13
3
|
<HeroBackground />
|
|
@@ -16,11 +6,8 @@ const { navigationByCategory } = useNavigation(navigation!)
|
|
|
16
6
|
<UPage>
|
|
17
7
|
<template #left>
|
|
18
8
|
<UPageAside>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
:navigation="navigationByCategory"
|
|
22
|
-
highlight
|
|
23
|
-
/>
|
|
9
|
+
<DocsAsideLeftTop />
|
|
10
|
+
<DocsAsideLeftBody />
|
|
24
11
|
</UPageAside>
|
|
25
12
|
</template>
|
|
26
13
|
|
|
@@ -76,7 +76,7 @@ export function transformerIconHighlight(options: TransformerIconHighlightOption
|
|
|
76
76
|
tagName: 'i',
|
|
77
77
|
properties: {
|
|
78
78
|
class: 'shiki-icon-highlight',
|
|
79
|
-
style: `--shiki-icon-url: url(
|
|
79
|
+
style: `--shiki-icon-url: url(${iconUrl})`
|
|
80
80
|
},
|
|
81
81
|
children: []
|
|
82
82
|
}
|