@movk/nuxt-docs 1.3.11 → 1.4.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/app/app.config.ts CHANGED
@@ -2,19 +2,21 @@ import type { ButtonProps } from '@nuxt/ui'
2
2
 
3
3
  export default defineAppConfig({
4
4
  toaster: {
5
- expand: true,
6
- position: 'top-right' as const,
7
- duration: 3000,
8
- max: 5
5
+ position: 'bottom-right' as const,
6
+ duration: 5000,
7
+ max: 5,
8
+ expand: true
9
9
  },
10
10
  theme: {
11
11
  radius: 0.25,
12
- blackAsPrimary: false
12
+ blackAsPrimary: false,
13
+ icons: 'lucide',
14
+ font: 'Public Sans'
13
15
  },
14
16
  ui: {
15
17
  colors: {
16
- primary: 'indigo',
17
- neutral: 'zinc'
18
+ primary: 'green',
19
+ neutral: 'slate'
18
20
  },
19
21
  contentNavigation: {
20
22
  slots: {
package/app/app.vue CHANGED
@@ -13,6 +13,7 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
13
13
  const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
14
14
  const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
15
15
  const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
16
+ const font = computed(() => `:root { --font-sans: '${appConfig.theme.font}', sans-serif; }`)
16
17
 
17
18
  useHead({
18
19
  meta: [
@@ -21,7 +22,8 @@ useHead({
21
22
  ],
22
23
  style: [
23
24
  { innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
24
- { innerHTML: blackAsPrimary, id: 'nuxt-ui-black-as-primary', tagPriority: -2 }
25
+ { innerHTML: blackAsPrimary, id: 'nuxt-ui-black-as-primary', tagPriority: -2 },
26
+ { innerHTML: font, id: 'nuxt-ui-font', tagPriority: -2 }
25
27
  ]
26
28
  })
27
29
 
@@ -55,7 +57,7 @@ provide('navigation', rootNavigation)
55
57
  <Footer />
56
58
 
57
59
  <ClientOnly>
58
- <LazyUContentSearch :files="files" :navigation="rootNavigation" :fuse="{ resultLimit: 1000 }"/>
60
+ <LazyUContentSearch :files="files" :navigation="rootNavigation" :fuse="{ resultLimit: 1000 }" />
59
61
  </ClientOnly>
60
62
  </template>
61
63
  </div>
@@ -55,7 +55,7 @@ const filePath = computed(() => {
55
55
 
56
56
  const { data: commits } = await useLazyFetch<Commit[]>('/api/github/commits', {
57
57
  key: `commit-changelog-${props.name ?? routeName.value}-${props.author ?? 'all'}`,
58
- query: { path: filePath.value, author: props.author }
58
+ query: { path: [filePath.value], author: props.author }
59
59
  })
60
60
 
61
61
  // 格式化提交消息
@@ -1,57 +1,5 @@
1
- <script lang="ts" setup>
2
- const { header } = useAppConfig()
3
- </script>
4
-
5
1
  <template>
6
- <NuxtLink
7
- :to="header.to"
8
- class="flex items-center gap-2 font-bold text-xl text-highlighted min-w-0 focus-visible:outline-primary shrink-0"
9
- :aria-label="header.title"
10
- >
11
- <svg
12
- width="256"
13
- height="256"
14
- viewBox="0 0 256 256"
15
- fill="none"
16
- xmlns="http://www.w3.org/2000/svg"
17
- class="w-auto h-10 shrink-0"
18
- >
19
- <circle
20
- cx="128"
21
- cy="128"
22
- r="120"
23
- fill="var(--ui-primary)"
24
- />
25
- <g fill="white" opacity="0.95">
26
- <path
27
- d="M38.5 175.6 c-1.5 -1 -1.9 -2.7 -1.9 -7.7 0 -5.5 0.4 -7.1 3.1 -11.4 1.8 -2.7 4.8 -6.3 6.8 -7.8 1.9 -1.5 3.5 -3.2 3.5 -3.8 0 -1.2 -5.8 -0.4 -11.5 1.6 -11.7 4.1 -16.3 3.7 -19 -1.5 -2.6 -5 -1.2 -8.4 4.2 -11 6.6 -3.1 10.4 -3.5 14.3 -1.5 2.1 1.1 4.1 1.4 5.9 0.9 1.4 -0.4 3.5 -0.9 4.6 -1.1 4.9 -0.9 12.3 -3.8 16 -6.3 13.7 -9.4 18.6 -11.7 34 -16 6.6 -1.9 11.9 -2.4 31.3 -3.1 l23.4 -0.8 8.4 -8.5 c5.7 -5.8 9.5 -8.8 11.9 -9.5 1.9 -0.5 5.8 -2.4 8.7 -4.1 4.5 -2.8 5.4 -3 7.5 -1.9 2 1.1 2.9 0.9 6.2 -0.9 6.4 -3.6 7.6 -2.5 4.4 4.3 l-1.7 3.5 3.3 4.1 c2.8 3.5 3.2 4.6 2.7 7.7 -0.3 2 0 5.1 0.6 6.8 1.8 5 -0.4 8.9 -6.3 11.6 -2.9 1.4 -4.5 2.6 -4 3.3 0.4 0.5 1.6 3.5 2.6 6.5 2.8 8.8 1.9 8.2 13.5 8.5 10.2 0.2 21.7 1.9 25.3 3.7 2 1 2.3 5.1 0.3 6.7 -0.7 0.6 -5.1 1.5 -9.7 2.1 -4.6 0.5 -12.7 1.9 -17.9 3 -8.5 1.8 -12.8 2 -42 1.9 -17.9 -0.1 -35 -0.3 -38 -0.5 -3 -0.1 -10.7 -0.6 -17.1 -1 -9.7 -0.6 -12.4 -0.4 -17 1.1 -7.2 2.3 -7.3 2.4 -5.5 4.4 2.2 2.5 3.1 7.8 1.6 9.6 -2.7 3.3 -9.3 1.2 -17.4 -5.5 -5.6 -4.7 -8.8 -5 -15.6 -1.7 -4.4 2.2 -4.5 2.3 -4.8 7.6 -0.2 3.7 -0.9 5.9 -2 6.8 -2.3 1.7 -10.3 1.6 -12.7 -0.1z"
28
- />
29
- </g>
30
- <g fill="white" opacity="0.98">
31
- <path
32
- d="M142 209.3 c-0.7 -1.6 -2.3 -5.7 -3.5 -9.3 -1.3 -3.6 -2.6 -7.2 -3 -8.1 -0.4 -1.1 0 -1.8 1.4 -2.2 1.7 -0.4 2.4 0.4 4.3 5.4 1.3 3.2 2.5 5.9 2.8 5.9 0.3 0 1.9 -2.6 3.5 -5.8 2.1 -4 3.7 -5.8 5.3 -6 1.2 -0.2 2.2 -0.1 2.2 0.3 0 0.5 -10.8 21.5 -11.5 22.3 -0.1 0.1 -0.8 -1 -1.5 -2.5z"
33
- />
34
- <path
35
- d="M84.5 201.3 c1 -12.9 1.6 -13.8 4.8 -6.9 1.5 3.1 2.9 5.6 3.3 5.6 0.3 0 3.1 -2.2 6.2 -5 3 -2.7 5.7 -4.8 5.9 -4.7 0.3 0.4 3.1 19.1 2.9 19.4 -0.1 0.2 -1.3 0.4 -2.7 0.5 -2.2 0.3 -2.4 0 -2.7 -4.9 -0.2 -2.9 -0.6 -5.3 -0.9 -5.3 -0.3 0 -2.3 1.4 -4.4 3.1 -3.7 2.9 -4.1 3 -5.8 1.5 -1.7 -1.6 -1.9 -1.6 -2.5 -0.1 -0.3 0.9 -0.6 2.8 -0.6 4.1 0 1.8 -0.5 2.4 -2.1 2.4 -2 0 -2.1 -0.3 -1.4 -9.7z"
36
- />
37
- <path
38
- d="M115.9 209.5 c-5.9 -3.2 -6.4 -12.8 -0.9 -17.7 3.4 -3.1 5.6 -3.4 10.3 -1.5 4.3 1.8 5.9 4 6 7.9 0.1 6.4 -1.2 10.4 -3.9 11.6 -3.3 1.6 -8.3 1.4 -11.5 -0.3z m9.6 -4.5 c2.1 -2.4 2.4 -7.2 0.5 -9.4 -2 -2.5 -7.7 -2.1 -9.1 0.6 -3.9 7.3 3.3 14.7 8.6 8.8z"
39
- />
40
- <path
41
- d="M158 200 c0 -11 0 -11 2.4 -11 2.3 0 2.4 0.3 1.9 3.6 -0.6 3.5 -0.5 3.6 1.6 2.5 1.2 -0.7 2.7 -2 3.3 -3.1 0.9 -1.4 1.5 -1.6 2.6 -0.8 1 0.9 0.6 1.9 -2.5 5 l-3.8 3.9 2.5 1.9 c1.4 1.1 3.7 2.5 5.3 3.2 3 1.4 3.3 2.1 1.4 3.6 -0.9 0.8 -2.4 0.3 -5.7 -1.9 l-4.5 -3.1 -0.3 3.6 c-0.3 2.8 -0.8 3.6 -2.3 3.6 -1.8 0 -1.9 -0.8 -1.9 -11z"
42
- />
43
- </g>
44
- <circle
45
- cx="128"
46
- cy="128"
47
- r="118"
48
- stroke="white"
49
- stroke-width="1.5"
50
- fill="none"
51
- opacity="0.15"
52
- />
53
- </svg>
54
-
55
- <p class="font-medium text-highlighted">{{ header.title }}</p>
2
+ <NuxtLink to="/">
3
+ <UUser :avatar="{ src: 'https://docs.mhaibaraai.cn/avatar.png' }" name="Movk Nuxt Docs" />
56
4
  </NuxtLink>
57
5
  </template>
@@ -1,11 +1,16 @@
1
1
  <script setup lang="ts">
2
2
  import { omit } from '@movk/core'
3
3
  import colors from 'tailwindcss/colors'
4
+ import { useClipboard } from '@vueuse/core'
5
+ import { themeIcons } from '../../utils/theme'
4
6
 
5
7
  const appConfig = useAppConfig()
6
8
  const colorMode = useColorMode()
7
9
  const site = useSiteConfig()
8
10
 
11
+ const { copy: copyCSS, copied: copiedCSS } = useClipboard()
12
+ const { copy: copyAppConfig, copied: copiedAppConfig } = useClipboard()
13
+
9
14
  const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
10
15
  const neutral = computed({
11
16
  get() {
@@ -59,10 +64,131 @@ function setBlackAsPrimary(value: boolean) {
59
64
  appConfig.theme.blackAsPrimary = value
60
65
  window.localStorage.setItem(`${site.name}-ui-black-as-primary`, String(value))
61
66
  }
67
+
68
+ const fonts = ['Public Sans', 'DM Sans', 'Geist', 'Inter', 'Poppins', 'Outfit', 'Raleway']
69
+ const font = computed({
70
+ get() {
71
+ return appConfig.theme.font
72
+ },
73
+ set(option) {
74
+ appConfig.theme.font = option
75
+ window.localStorage.setItem(`${site.name}-ui-font`, appConfig.theme.font)
76
+ }
77
+ })
78
+
79
+ const icons = [{
80
+ label: 'Lucide',
81
+ icon: 'i-lucide-feather',
82
+ value: 'lucide'
83
+ }, {
84
+ label: 'Phosphor',
85
+ icon: 'i-ph-phosphor-logo',
86
+ value: 'phosphor'
87
+ }, {
88
+ label: 'Tabler',
89
+ icon: 'i-tabler-brand-tabler',
90
+ value: 'tabler'
91
+ }]
92
+ const icon = computed({
93
+ get() {
94
+ return appConfig.theme.icons
95
+ },
96
+ set(option) {
97
+ appConfig.theme.icons = option
98
+ appConfig.ui.icons = themeIcons[option as keyof typeof themeIcons] as any
99
+ window.localStorage.setItem(`${site.name}-ui-icons`, appConfig.theme.icons)
100
+ }
101
+ })
102
+
103
+ const hasCSSChanges = computed(() => {
104
+ return appConfig.theme.radius !== 0.25
105
+ || appConfig.theme.blackAsPrimary
106
+ || appConfig.theme.font !== 'Public Sans'
107
+ })
108
+
109
+ const hasAppConfigChanges = computed(() => {
110
+ return appConfig.ui.colors.primary !== 'green'
111
+ || appConfig.ui.colors.neutral !== 'slate'
112
+ || appConfig.theme.icons !== 'lucide'
113
+ })
114
+
115
+ function exportCSS() {
116
+ const lines = [
117
+ '@import "tailwindcss";',
118
+ '@import "@nuxt/ui";'
119
+ ]
120
+
121
+ if (appConfig.theme.font !== 'Public Sans') {
122
+ lines.push('', '@theme {', ` --font-sans: '${appConfig.theme.font}', sans-serif;`, '}')
123
+ }
124
+
125
+ const rootLines: string[] = []
126
+ if (appConfig.theme.radius !== 0.25) {
127
+ rootLines.push(` --ui-radius: ${appConfig.theme.radius}rem;`)
128
+ }
129
+ if (appConfig.theme.blackAsPrimary) {
130
+ rootLines.push(' --ui-primary: black;')
131
+ }
132
+
133
+ if (rootLines.length) {
134
+ lines.push('', ':root {', ...rootLines, '}')
135
+ }
136
+
137
+ if (appConfig.theme.blackAsPrimary) {
138
+ lines.push('', '.dark {', ' --ui-primary: white;', '}')
139
+ }
140
+
141
+ copyCSS(lines.join('\n'))
142
+ }
143
+
144
+ function exportAppConfig() {
145
+ const config: Record<string, any> = {}
146
+
147
+ if (appConfig.ui.colors.primary !== 'green' || appConfig.ui.colors.neutral !== 'slate') {
148
+ config.ui = { colors: {} }
149
+ if (appConfig.ui.colors.primary !== 'green') {
150
+ config.ui.colors.primary = appConfig.ui.colors.primary
151
+ }
152
+ if (appConfig.ui.colors.neutral !== 'slate') {
153
+ config.ui.colors.neutral = appConfig.ui.colors.neutral
154
+ }
155
+ }
156
+
157
+ if (appConfig.theme.icons !== 'lucide') {
158
+ const iconSet = appConfig.theme.icons
159
+ const icons = themeIcons[iconSet as keyof typeof themeIcons]
160
+ config.ui = config.ui || {}
161
+ config.ui.icons = icons
162
+ }
163
+
164
+ const configString = JSON.stringify(config, null, 2)
165
+ .replace(/"([^"]+)":/g, '$1:')
166
+ .replace(/"/g, '\'')
167
+
168
+ const output = `export default defineAppConfig(${configString})`
169
+
170
+ copyAppConfig(output)
171
+ }
172
+
173
+ function resetTheme() {
174
+ primary.value = 'green'
175
+ neutral.value = 'slate'
176
+ radius.value = 0.25
177
+ font.value = 'Public Sans'
178
+ icon.value = 'lucide'
179
+ setBlackAsPrimary(false)
180
+
181
+ window.localStorage.removeItem(`${site.name}-ui-primary`)
182
+ window.localStorage.removeItem(`${site.name}-ui-neutral`)
183
+ window.localStorage.removeItem(`${site.name}-ui-radius`)
184
+ window.localStorage.removeItem(`${site.name}-ui-font`)
185
+ window.localStorage.removeItem(`${site.name}-ui-icons`)
186
+ window.localStorage.removeItem(`${site.name}-ui-black-as-primary`)
187
+ }
62
188
  </script>
63
189
 
64
190
  <template>
65
- <UPopover :ui="{ content: 'w-72 px-6 py-4 flex flex-col gap-4' }">
191
+ <UPopover :ui="{ content: 'w-72 px-6 py-4 flex flex-col gap-4 overflow-y-auto max-h-[calc(100vh-5rem)]' }">
66
192
  <template #default="{ open }">
67
193
  <UButton
68
194
  icon="i-lucide-swatch-book"
@@ -76,8 +202,19 @@ function setBlackAsPrimary(value: boolean) {
76
202
 
77
203
  <template #content>
78
204
  <fieldset>
79
- <legend class="text-[11px] leading-none font-semibold mb-2">
205
+ <legend class="text-[11px] leading-none font-semibold mb-2 select-none flex items-center gap-1">
80
206
  Primary
207
+
208
+ <UButton
209
+ to="https://ui.nuxt.com/docs/getting-started/theme/css-variables#colors"
210
+ size="xs"
211
+ color="neutral"
212
+ variant="link"
213
+ target="_blank"
214
+ icon="i-lucide-circle-help"
215
+ class="p-0 -my-0.5"
216
+ :ui="{ leadingIcon: 'size-3' }"
217
+ />
81
218
  </legend>
82
219
 
83
220
  <div class="grid grid-cols-3 gap-1 -mx-2">
@@ -99,8 +236,19 @@ function setBlackAsPrimary(value: boolean) {
99
236
  </fieldset>
100
237
 
101
238
  <fieldset>
102
- <legend class="text-[11px] leading-none font-semibold mb-2">
239
+ <legend class="text-[11px] leading-none font-semibold mb-2 select-none flex items-center gap-1">
103
240
  Neutral
241
+
242
+ <UButton
243
+ to="https://ui.nuxt.com/docs/getting-started/theme/css-variables#text"
244
+ size="xs"
245
+ color="neutral"
246
+ variant="link"
247
+ target="_blank"
248
+ icon="i-lucide-circle-help"
249
+ class="p-0 -my-0.5"
250
+ :ui="{ leadingIcon: 'size-3' }"
251
+ />
104
252
  </legend>
105
253
 
106
254
  <div class="grid grid-cols-3 gap-1 -mx-2">
@@ -116,8 +264,19 @@ function setBlackAsPrimary(value: boolean) {
116
264
  </fieldset>
117
265
 
118
266
  <fieldset>
119
- <legend class="text-[11px] leading-none font-semibold mb-2">
267
+ <legend class="text-[11px] leading-none font-semibold mb-2 select-none flex items-center gap-1">
120
268
  Radius
269
+
270
+ <UButton
271
+ to="https://ui.nuxt.com/docs/getting-started/theme/css-variables#radius"
272
+ size="xs"
273
+ color="neutral"
274
+ variant="link"
275
+ target="_blank"
276
+ icon="i-lucide-circle-help"
277
+ class="p-0 -my-0.5"
278
+ :ui="{ leadingIcon: 'size-3' }"
279
+ />
121
280
  </legend>
122
281
 
123
282
  <div class="grid grid-cols-5 gap-1 -mx-2">
@@ -133,8 +292,77 @@ function setBlackAsPrimary(value: boolean) {
133
292
  </fieldset>
134
293
 
135
294
  <fieldset>
136
- <legend class="text-[11px] leading-none font-semibold mb-2">
137
- Theme
295
+ <legend class="text-[11px] leading-none font-semibold mb-2 select-none flex items-center gap-1">
296
+ Font
297
+
298
+ <UButton
299
+ to="https://ui.nuxt.com/docs/getting-started/integrations/fonts"
300
+ size="xs"
301
+ color="neutral"
302
+ variant="link"
303
+ target="_blank"
304
+ icon="i-lucide-circle-help"
305
+ class="p-0 -my-0.5"
306
+ :ui="{ leadingIcon: 'size-3' }"
307
+ />
308
+ </legend>
309
+
310
+ <div class="-mx-2">
311
+ <USelect
312
+ v-model="font"
313
+ size="sm"
314
+ color="neutral"
315
+ icon="i-lucide-type"
316
+ :items="fonts"
317
+ class="w-full ring-default rounded-sm hover:bg-elevated/50 text-[11px] data-[state=open]:bg-elevated/50"
318
+ :ui="{ trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200' }"
319
+ />
320
+ </div>
321
+ </fieldset>
322
+
323
+ <fieldset>
324
+ <legend class="text-[11px] leading-none font-semibold mb-2 select-none flex items-center gap-1">
325
+ Icons
326
+
327
+ <UButton
328
+ to="https://ui.nuxt.com/docs/getting-started/integrations/icons"
329
+ size="xs"
330
+ color="neutral"
331
+ variant="link"
332
+ target="_blank"
333
+ icon="i-lucide-circle-help"
334
+ class="p-0 -my-0.5"
335
+ :ui="{ leadingIcon: 'size-3' }"
336
+ />
337
+ </legend>
338
+
339
+ <div class="-mx-2">
340
+ <USelect
341
+ v-model="icon"
342
+ size="sm"
343
+ color="neutral"
344
+ :icon="icons.find(i => i.value === icon)?.icon"
345
+ :items="icons"
346
+ class="w-full ring-default rounded-sm hover:bg-elevated/50 capitalize text-[11px] data-[state=open]:bg-elevated/50"
347
+ :ui="{ item: 'capitalize text-[11px]', trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200' }"
348
+ />
349
+ </div>
350
+ </fieldset>
351
+
352
+ <fieldset>
353
+ <legend class="text-[11px] leading-none font-semibold mb-2 select-none flex items-center gap-1">
354
+ Color Mode
355
+
356
+ <UButton
357
+ to="https://ui.nuxt.com/docs/getting-started/integrations/color-mode"
358
+ size="xs"
359
+ color="neutral"
360
+ variant="link"
361
+ target="_blank"
362
+ icon="i-lucide-circle-help"
363
+ class="p-0 -my-0.5"
364
+ :ui="{ leadingIcon: 'size-3' }"
365
+ />
138
366
  </legend>
139
367
 
140
368
  <div class="grid grid-cols-3 gap-1 -mx-2">
@@ -147,6 +375,45 @@ function setBlackAsPrimary(value: boolean) {
147
375
  />
148
376
  </div>
149
377
  </fieldset>
378
+
379
+ <fieldset v-if="hasCSSChanges || hasAppConfigChanges">
380
+ <legend class="text-[11px] leading-none font-semibold mb-2 select-none">
381
+ Export
382
+ </legend>
383
+
384
+ <div class="flex items-center justify-between gap-1 -mx-2">
385
+ <UButton
386
+ v-if="hasCSSChanges"
387
+ color="neutral"
388
+ variant="soft"
389
+ size="sm"
390
+ label="main.css"
391
+ class="flex-1 text-[11px]"
392
+ :icon="copiedCSS ? 'i-lucide-copy-check' : 'i-lucide-copy'"
393
+ @click="exportCSS"
394
+ />
395
+ <UButton
396
+ v-if="hasAppConfigChanges"
397
+ color="neutral"
398
+ variant="soft"
399
+ size="sm"
400
+ label="app.config.ts"
401
+ :icon="copiedAppConfig ? 'i-lucide-copy-check' : 'i-lucide-copy'"
402
+ class="flex-1 text-[11px]"
403
+ @click="exportAppConfig"
404
+ />
405
+ <UTooltip text="Reset theme">
406
+ <UButton
407
+ color="neutral"
408
+ variant="outline"
409
+ size="sm"
410
+ icon="i-lucide-rotate-ccw"
411
+ class="ms-auto ring-default hover:bg-elevated/50"
412
+ @click="resetTheme"
413
+ />
414
+ </UTooltip>
415
+ </div>
416
+ </fieldset>
150
417
  </template>
151
418
  </UPopover>
152
419
  </template>
package/app/error.vue CHANGED
@@ -18,6 +18,7 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
18
18
  const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
19
19
  const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
20
20
  const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
21
+ const font = computed(() => `:root { --font-sans: '${appConfig.theme.font}', sans-serif; }`)
21
22
 
22
23
  useHead({
23
24
  meta: [
@@ -26,7 +27,8 @@ useHead({
26
27
  ],
27
28
  style: [
28
29
  { innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
29
- { innerHTML: blackAsPrimary, id: 'nuxt-ui-black-as-primary', tagPriority: -2 }
30
+ { innerHTML: blackAsPrimary, id: 'nuxt-ui-black-as-primary', tagPriority: -2 },
31
+ { innerHTML: font, id: 'nuxt-ui-font', tagPriority: -2 }
30
32
  ]
31
33
  })
32
34
 
@@ -1,3 +1,5 @@
1
+ import { themeIcons } from '../utils/theme'
2
+
1
3
  export default defineNuxtPlugin({
2
4
  enforce: 'post',
3
5
  setup() {
@@ -26,12 +28,32 @@ export default defineNuxtPlugin({
26
28
  }
27
29
  }
28
30
 
31
+ function updateFont() {
32
+ const font = localStorage.getItem(`${site.name}-ui-font`)
33
+ if (font) {
34
+ appConfig.theme.font = font
35
+ }
36
+ }
37
+
29
38
  updateColor('primary')
30
39
  updateColor('neutral')
31
40
  updateRadius()
32
41
  updateBlackAsPrimary()
42
+ updateFont()
33
43
  }
34
44
 
45
+ onNuxtReady(() => {
46
+ function updateIcons() {
47
+ const icons = localStorage.getItem(`${site.name}-ui-icons`)
48
+ if (icons) {
49
+ appConfig.theme.icons = icons
50
+ appConfig.ui.icons = themeIcons[icons as keyof typeof themeIcons] as any
51
+ }
52
+ }
53
+
54
+ updateIcons()
55
+ })
56
+
35
57
  if (import.meta.server) {
36
58
  useHead({
37
59
  script: [{
@@ -75,6 +97,13 @@ export default defineNuxtPlugin({
75
97
  document.querySelector('style#nuxt-ui-black-as-primary').innerHTML = '';
76
98
  }
77
99
  `.replace(/\s+/g, ' ')
100
+ }, {
101
+ innerHTML: `
102
+ if (localStorage.getItem('${site.name}-ui-font')) {
103
+ const font = localStorage.getItem('${site.name}-ui-font');
104
+ document.querySelector('style#nuxt-ui-font').innerHTML = ':root { --font-sans: \\'' + font + '\\', sans-serif; }';
105
+ }
106
+ `.replace(/\s+/g, ' ')
78
107
  }]
79
108
  })
80
109
  }
@@ -0,0 +1,136 @@
1
+ export const themeIcons = {
2
+ lucide: {
3
+ arrowDown: 'i-lucide-arrow-down',
4
+ arrowLeft: 'i-lucide-arrow-left',
5
+ arrowRight: 'i-lucide-arrow-right',
6
+ arrowUp: 'i-lucide-arrow-up',
7
+ caution: 'i-lucide-circle-alert',
8
+ check: 'i-lucide-check',
9
+ chevronDoubleLeft: 'i-lucide-chevrons-left',
10
+ chevronDoubleRight: 'i-lucide-chevrons-right',
11
+ chevronDown: 'i-lucide-chevron-down',
12
+ chevronLeft: 'i-lucide-chevron-left',
13
+ chevronRight: 'i-lucide-chevron-right',
14
+ chevronUp: 'i-lucide-chevron-up',
15
+ close: 'i-lucide-x',
16
+ copy: 'i-lucide-copy',
17
+ copyCheck: 'i-lucide-copy-check',
18
+ dark: 'i-lucide-moon',
19
+ drag: 'i-lucide-grip-vertical',
20
+ ellipsis: 'i-lucide-ellipsis',
21
+ error: 'i-lucide-circle-x',
22
+ external: 'i-lucide-arrow-up-right',
23
+ eye: 'i-lucide-eye',
24
+ eyeOff: 'i-lucide-eye-off',
25
+ file: 'i-lucide-file',
26
+ folder: 'i-lucide-folder',
27
+ folderOpen: 'i-lucide-folder-open',
28
+ hash: 'i-lucide-hash',
29
+ info: 'i-lucide-info',
30
+ light: 'i-lucide-sun',
31
+ loading: 'i-lucide-loader-circle',
32
+ menu: 'i-lucide-menu',
33
+ minus: 'i-lucide-minus',
34
+ panelClose: 'i-lucide-panel-left-close',
35
+ panelOpen: 'i-lucide-panel-left-open',
36
+ plus: 'i-lucide-plus',
37
+ reload: 'i-lucide-rotate-ccw',
38
+ search: 'i-lucide-search',
39
+ stop: 'i-lucide-square',
40
+ success: 'i-lucide-circle-check',
41
+ system: 'i-lucide-monitor',
42
+ tip: 'i-lucide-lightbulb',
43
+ upload: 'i-lucide-upload',
44
+ warning: 'i-lucide-triangle-alert'
45
+ },
46
+ phosphor: {
47
+ arrowDown: 'i-ph-arrow-down',
48
+ arrowLeft: 'i-ph-arrow-left',
49
+ arrowRight: 'i-ph-arrow-right',
50
+ arrowUp: 'i-ph-arrow-up',
51
+ caution: 'i-ph-warning-circle',
52
+ check: 'i-ph-check',
53
+ chevronDoubleLeft: 'i-ph-caret-double-left',
54
+ chevronDoubleRight: 'i-ph-caret-double-right',
55
+ chevronDown: 'i-ph-caret-down',
56
+ chevronLeft: 'i-ph-caret-left',
57
+ chevronRight: 'i-ph-caret-right',
58
+ chevronUp: 'i-ph-caret-up',
59
+ close: 'i-ph-x',
60
+ copy: 'i-ph-copy',
61
+ copyCheck: 'i-ph-check-circle',
62
+ dark: 'i-ph-moon',
63
+ drag: 'i-ph-dots-six-vertical',
64
+ ellipsis: 'i-ph-dots-three',
65
+ error: 'i-ph-x-circle',
66
+ external: 'i-ph-arrow-up-right',
67
+ eye: 'i-ph-eye',
68
+ eyeOff: 'i-ph-eye-slash',
69
+ file: 'i-ph-file',
70
+ folder: 'i-ph-folder',
71
+ folderOpen: 'i-ph-folder-open',
72
+ hash: 'i-ph-hash',
73
+ info: 'i-ph-info',
74
+ light: 'i-ph-sun',
75
+ loading: 'i-ph-circle-notch',
76
+ menu: 'i-ph-list',
77
+ minus: 'i-ph-minus',
78
+ panelClose: 'i-ph-caret-left',
79
+ panelOpen: 'i-ph-caret-right',
80
+ plus: 'i-ph-plus',
81
+ reload: 'i-ph-arrow-counter-clockwise',
82
+ search: 'i-ph-magnifying-glass',
83
+ stop: 'i-ph-square',
84
+ success: 'i-ph-check-circle',
85
+ system: 'i-ph-monitor',
86
+ tip: 'i-ph-lightbulb',
87
+ upload: 'i-ph-upload',
88
+ warning: 'i-ph-warning'
89
+ },
90
+ tabler: {
91
+ arrowDown: 'i-tabler-arrow-down',
92
+ arrowLeft: 'i-tabler-arrow-left',
93
+ arrowRight: 'i-tabler-arrow-right',
94
+ arrowUp: 'i-tabler-arrow-up',
95
+ caution: 'i-tabler-alert-square-rounded',
96
+ check: 'i-tabler-check',
97
+ chevronDoubleLeft: 'i-tabler-chevrons-left',
98
+ chevronDoubleRight: 'i-tabler-chevrons-right',
99
+ chevronDown: 'i-tabler-chevron-down',
100
+ chevronLeft: 'i-tabler-chevron-left',
101
+ chevronRight: 'i-tabler-chevron-right',
102
+ chevronUp: 'i-tabler-chevron-up',
103
+ close: 'i-tabler-x',
104
+ copy: 'i-tabler-copy',
105
+ copyCheck: 'i-tabler-copy-check',
106
+ dark: 'i-tabler-moon',
107
+ drag: 'i-tabler-grip-vertical',
108
+ ellipsis: 'i-tabler-dots',
109
+ error: 'i-tabler-square-rounded-x',
110
+ external: 'i-tabler-external-link',
111
+ eye: 'i-tabler-eye',
112
+ eyeOff: 'i-tabler-eye-off',
113
+ file: 'i-tabler-file',
114
+ folder: 'i-tabler-folder',
115
+ folderOpen: 'i-tabler-folder-open',
116
+ hash: 'i-tabler-hash',
117
+ info: 'i-tabler-info-square-rounded',
118
+ light: 'i-tabler-sun',
119
+ loading: 'i-tabler-loader-2',
120
+ menu: 'i-tabler-menu',
121
+ minus: 'i-tabler-minus',
122
+ panelClose: 'i-tabler-layout-sidebar-left-collapse',
123
+ panelOpen: 'i-tabler-layout-sidebar-left-expand',
124
+ plus: 'i-tabler-plus',
125
+ reload: 'i-tabler-reload',
126
+ search: 'i-tabler-search',
127
+ stop: 'i-tabler-player-stop',
128
+ success: 'i-tabler-square-rounded-check',
129
+ system: 'i-tabler-device-desktop',
130
+ tip: 'i-tabler-bulb',
131
+ upload: 'i-tabler-upload',
132
+ warning: 'i-tabler-alert-triangle'
133
+ }
134
+ }
135
+
136
+ export type ThemeIcons = keyof typeof themeIcons
package/nuxt.config.ts CHANGED
@@ -73,6 +73,17 @@ export default defineNuxtConfig({
73
73
  }
74
74
  }
75
75
  },
76
+ fonts: {
77
+ families: [
78
+ { name: 'Public Sans', provider: 'google', global: true },
79
+ { name: 'DM Sans', provider: 'google', global: true },
80
+ { name: 'Geist', provider: 'google', global: true },
81
+ { name: 'Inter', provider: 'google', global: true },
82
+ { name: 'Poppins', provider: 'google', global: true },
83
+ { name: 'Outfit', provider: 'google', global: true },
84
+ { name: 'Raleway', provider: 'google', global: true }
85
+ ]
86
+ },
76
87
  icon: {
77
88
  provider: 'iconify'
78
89
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/nuxt-docs",
3
3
  "type": "module",
4
- "version": "1.3.11",
4
+ "version": "1.4.0",
5
5
  "private": false,
6
6
  "description": "An elegant documentation theme for Nuxt, powered by Nuxt UI and Nuxt Content.",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
@@ -27,15 +27,15 @@
27
27
  "README.md"
28
28
  ],
29
29
  "dependencies": {
30
- "@iconify-json/lucide": "^1.2.77",
31
- "@iconify-json/simple-icons": "^1.2.61",
30
+ "@iconify-json/lucide": "^1.2.81",
31
+ "@iconify-json/simple-icons": "^1.2.63",
32
32
  "@iconify-json/vscode-icons": "^1.2.37",
33
33
  "@movk/core": "^1.0.2",
34
- "@nuxt/content": "^3.8.2",
34
+ "@nuxt/content": "^3.9.0",
35
35
  "@nuxt/image": "^2.0.0",
36
- "@nuxt/kit": "^4.2.1",
37
- "@nuxt/ui": "^4.2.1",
38
- "@nuxtjs/seo": "^3.2.2",
36
+ "@nuxt/kit": "^4.2.2",
37
+ "@nuxt/ui": "^4.3.0",
38
+ "@nuxtjs/seo": "^3.3.0",
39
39
  "@octokit/rest": "^22.0.1",
40
40
  "@vueuse/core": "^14.1.0",
41
41
  "@vueuse/nuxt": "^14.1.0",
@@ -48,9 +48,9 @@
48
48
  "ohash": "^2.0.11",
49
49
  "pathe": "^2.0.3",
50
50
  "pkg-types": "^2.3.0",
51
- "prettier": "^3.7.3",
51
+ "prettier": "^3.7.4",
52
52
  "scule": "^1.3.0",
53
- "tailwindcss": "^4.1.17",
53
+ "tailwindcss": "^4.1.18",
54
54
  "ufo": "^1.6.1"
55
55
  }
56
56
  }
@@ -5,8 +5,10 @@ export default defineCachedEventHandler(async (event) => {
5
5
  return []
6
6
  }
7
7
 
8
- const { path, author } = getQuery(event) as { path: string, author?: string }
9
- if (!path) {
8
+ const { path, author } = getQuery(event) as { path: string | string[], author: string }
9
+ const paths = Array.isArray(path) ? path : [path]
10
+
11
+ if (!paths.length || !paths[0]) {
10
12
  throw createError({
11
13
  statusCode: 400,
12
14
  statusMessage: 'Path is required'
@@ -15,26 +17,41 @@ export default defineCachedEventHandler(async (event) => {
15
17
 
16
18
  const { github } = useAppConfig()
17
19
  const octokit = new Octokit({ auth: process.env.NUXT_GITHUB_TOKEN })
18
- const commits = await octokit.paginate(octokit.rest.repos.listCommits, {
19
- sha: github.branch,
20
- owner: github.owner,
21
- repo: github.name,
22
- path,
23
- since: github.since,
24
- per_page: github.per_page,
25
- until: github.until,
26
- ...(author && { author })
27
- })
28
20
 
29
- return commits.map(commit => ({
30
- sha: commit.sha,
31
- date: commit.commit.author?.date ?? '',
32
- message: (commit.commit.message?.split('\n')[0] ?? '')
33
- }))
21
+ const allCommits = await Promise.all(
22
+ paths.map(path =>
23
+ octokit.paginate(octokit.rest.repos.listCommits, {
24
+ sha: github.branch,
25
+ owner: github.owner,
26
+ repo: github.name,
27
+ path,
28
+ since: github.since,
29
+ per_page: github.per_page,
30
+ until: github.until,
31
+ author
32
+ })
33
+ )
34
+ )
35
+
36
+ const uniqueCommits = new Map<string, { sha: string, date: string, message: string }>()
37
+ for (const commits of allCommits) {
38
+ for (const commit of commits) {
39
+ if (!uniqueCommits.has(commit.sha)) {
40
+ uniqueCommits.set(commit.sha, {
41
+ sha: commit.sha,
42
+ date: commit.commit.author?.date ?? '',
43
+ message: (commit.commit.message?.split('\n')[0] ?? '')
44
+ })
45
+ }
46
+ }
47
+ }
48
+
49
+ return Array.from(uniqueCommits.values()).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
34
50
  }, {
35
51
  maxAge: 60 * 60,
36
52
  getKey: (event) => {
37
53
  const { path, author } = getQuery(event)
38
- return `commits-${path}${author ? `-${author}` : ''}`
54
+ const paths = Array.isArray(path) ? path : [path]
55
+ return `commits-${paths.join(',')}${author ? `-${author}` : ''}`
39
56
  }
40
57
  })
@@ -63,12 +63,14 @@ export default defineCachedEventHandler(async (event) => {
63
63
  const date = commit.commit.author?.date ?? ''
64
64
  const dateFormat = github.dateFormat ?? {}
65
65
  const locale = dateFormat.locale ?? 'zh-CN'
66
+ const timeZone = dateFormat.timeZone ?? 'Asia/Shanghai'
66
67
  const formatOptions: Intl.DateTimeFormatOptions = dateFormat.options ?? {
67
68
  year: 'numeric',
68
69
  month: 'numeric',
69
70
  day: 'numeric',
70
71
  hour: '2-digit',
71
- minute: '2-digit'
72
+ minute: '2-digit',
73
+ timeZone
72
74
  }
73
75
  const dateFormatted = date
74
76
  ? new Date(date).toLocaleDateString(locale, formatOptions)