@movk/nuxt-docs 1.12.0 → 1.12.2

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 ADDED
@@ -0,0 +1,316 @@
1
+ [![Movk Nuxt Docs](https://docs.mhaibaraai.cn/og-image.png)](https://docs.mhaibaraai.cn/)
2
+
3
+ > 基于 Nuxt 4 的现代文档主题,集成组件自动化文档、AI 聊天助手、MCP Server 和完整的开发者体验优化
4
+
5
+ [![Install MCP in Cursor](https://docs.mhaibaraai.cn/mcp/badge.svg)](https://docs.mhaibaraai.cn/mcp/deeplink)
6
+ [![Install MCP in VS Code](https://docs.mhaibaraai.cn/mcp/badge.svg?ide=vscode)](https://docs.mhaibaraai.cn/mcp/deeplink?ide=vscode)
7
+
8
+ [![npm version][npm-version-src]][npm-version-href]
9
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
10
+ [![License][license-src]][license-href]
11
+ [![Nuxt][nuxt-src]][nuxt-href]
12
+
13
+ 使用此主题可以快速构建美观、专业、智能的文档网站,内置组件文档自动生成、AI 聊天助手、MCP Server 支持、SEO 优化、暗黑模式、全文搜索等功能。
14
+
15
+ - 📖 [在线文档](https://docs.mhaibaraai.cn/)
16
+
17
+ ## ✨ 特性
18
+
19
+ 此主题集成了一系列旨在优化文档管理体验的强大功能:
20
+
21
+ ### 🤖 AI 增强体验
22
+
23
+ <div style="padding: 40px 0; display: flex; justify-content: center;">
24
+ <img src="https://docs.mhaibaraai.cn/ai/AiChat.png" alt="AiChat" width="400">
25
+ </div>
26
+
27
+ - **AI 聊天助手** - 内置智能文档助手,基于 Vercel AI SDK 支持多种 LLM 模型(Mistral、Qwen、OpenRouter)
28
+ - **MCP Server 支持** - 集成 Model Context Protocol 服务器,为 AI 助手提供结构化的文档访问能力
29
+ - **LLM 优化** - 通过 `nuxt-llms` 模块自动生成 `llms.txt` 和 `llms-full.txt`,为 AI 工具提供优化的文档索引
30
+ - **流式响应** - 支持 AI 响应流式输出和代码高亮,配合 `shiki-stream` 实现实时语法高亮渲染
31
+
32
+ ### 🧩 自动化文档生成
33
+
34
+ - **组件元数据自动提取** - 基于 `nuxt-component-meta` 自动提取 Vue 组件的 Props、Slots、Emits 定义
35
+ - **交互式示例展示** - 通过 `ComponentExample` 组件自动加载和渲染组件示例,支持代码高亮和实时预览
36
+ - **Git 提交历史集成** - 使用 `CommitChangelog` 和 `PageLastCommit` 组件自动展示文件的提交历史记录
37
+ - **类型定义高亮** - 智能解析 TypeScript 类型定义,支持内联类型高亮和类型导航
38
+
39
+ ### 🎨 开发者体验
40
+
41
+ - ⚡ **基于 Nuxt 4** - 充分利用最新的 Nuxt 框架,实现卓越性能
42
+ - 🎨 **采用 Nuxt UI** - 集成全面的 UI 组件库,开箱即用
43
+ - 📝 **MDC 语法增强** - 支持 Markdown 与 Vue 组件的无缝集成
44
+ - 📊 **Mermaid 图表** - 内置 Mermaid 支持,渲染流程图、时序图、类图等可视化图表,支持自动主题切换和全屏查看
45
+ - 🔍 **全文搜索** - 基于 Nuxt Content 的 `ContentSearch` 组件,支持键盘快捷键(⌘K)
46
+ - 🌙 **暗黑模式** - 支持亮色/暗色主题切换
47
+ - 📱 **响应式设计** - 移动优先的响应式布局
48
+ - 🚀 **SEO 优化** - 内置 SEO 优化功能
49
+ - 🎯 **TypeScript 支持** - 完整的 TypeScript 类型支持
50
+
51
+ ## 🚀 快速开始
52
+
53
+ ### 使用模板创建项目
54
+
55
+ 根据您的需求选择合适的模板:
56
+
57
+ #### 📚 完整文档站点(推荐)
58
+
59
+ 适合构建完整的文档网站,包含 ESLint、TypeScript 检查等开发工具。
60
+
61
+ ```bash
62
+ # 使用完整模板创建新项目
63
+ npx nuxi init -t gh:mhaibaraai/movk-nuxt-docs/templates/default my-docs
64
+ cd my-docs
65
+ pnpm dev
66
+ ```
67
+
68
+ #### 📦 模块文档站点(精简)
69
+
70
+ 适合为 npm 包或库快速创建文档,内置 Release 页面展示版本发布历史,无额外开发工具。
71
+
72
+ ```bash
73
+ # 使用模块模板创建新项目
74
+ npx nuxi init -t gh:mhaibaraai/movk-nuxt-docs/templates/module my-module-docs
75
+ cd my-module-docs
76
+ pnpm dev
77
+ ```
78
+
79
+ 访问 `http://localhost:3000` 查看你的文档网站。
80
+
81
+ ### AI 助手 Skill
82
+
83
+ 为你的 AI 助手(Cursor、Claude Code 等)添加 Movk Nuxt Docs 专业知识,加速文档编写:
84
+
85
+ ```bash
86
+ npx skills add mhaibaraai/movk-nuxt-docs
87
+ ```
88
+
89
+ 此 Skill 为 AI 助手提供 Movk Nuxt Docs 专业知识,帮助你更高效地编写文档:
90
+
91
+ - 📝 MDC 组件用法和现成模板
92
+ - 🎨 中文文档写作规范和内容结构模式
93
+ - 🔧 nuxt.config.ts 和 app.config.ts 配置参考
94
+ - 📚 入门页、功能介绍页等常用页面模板
95
+
96
+ ### 作为 Layer 使用
97
+
98
+ 在现有 Nuxt 项目中使用 Movk Nuxt Docs 作为 layer:
99
+
100
+ ```bash [Terminal]
101
+ pnpm add @movk/nuxt-docs better-sqlite3 tailwindcss
102
+ ```
103
+
104
+ 在 `nuxt.config.ts` 中配置:
105
+
106
+ ```ts [nuxt.config.ts]
107
+ export default defineNuxtConfig({
108
+ + extends: ['@movk/nuxt-docs']
109
+ })
110
+ ```
111
+
112
+ ## 📁 项目结构
113
+
114
+ ### 模板项目结构
115
+
116
+ 使用模板创建的项目结构(以 `default` 模板为例):
117
+
118
+ ```bash
119
+ my-docs/
120
+ ├── app/
121
+ │ └── composables/ # 自定义 Composables
122
+ ├── content/ # Markdown 内容
123
+ │ ├── index.md # 首页
124
+ │ └── docs/ # 文档页面
125
+ ├── public/ # 静态资源
126
+ ├── nuxt.config.ts # Nuxt 配置
127
+ ├── tsconfig.json # TypeScript 配置
128
+ ├── package.json # 依赖与脚本
129
+ ├── .env.example # 环境变量示例
130
+ └── pnpm-workspace.yaml # pnpm 工作区配置
131
+ ```
132
+
133
+ ### Monorepo 结构
134
+
135
+ 本仓库采用 monorepo 结构:
136
+
137
+ ```bash
138
+ movk-nuxt-docs/
139
+ ├── docs/ # 官方文档站点
140
+ ├── layer/ # @movk/nuxt-docs layer 包
141
+ ├── templates/
142
+ │ ├── default/ # 完整文档站点模板
143
+ │ └── module/ # 模块文档站点模板(精简)
144
+ └── scripts/ # 构建脚本
145
+ ```
146
+
147
+ ## 📝 内容编写
148
+
149
+ ### 基础 Markdown
150
+
151
+ ```md [md]
152
+ ---
153
+ title: 页面标题
154
+ description: 页面描述
155
+ ---
156
+
157
+ # 标题
158
+
159
+ 这是一段普通的文本内容。
160
+
161
+ ## 二级标题
162
+
163
+ - 列表项 1
164
+ - 列表项 2
165
+ ```
166
+
167
+ ### MDC 语法
168
+
169
+ ```md [md]
170
+ ::card
171
+ ---
172
+ title: 卡片标题
173
+ icon: i-lucide-rocket
174
+ ---
175
+ 卡片内容
176
+ ::
177
+ ```
178
+
179
+ 了解更多关于 MDC 语法,请查看 [Nuxt Content 文档](https://content.nuxt.com/docs/files/markdown#mdc-syntax)。
180
+
181
+ ### Mermaid 图表
182
+
183
+ 使用 ` ```mermaid ` 代码块渲染可视化图表,支持流程图、时序图、类图等多种图表类型:
184
+
185
+ ````md [md]
186
+ ```mermaid
187
+ graph TD
188
+ A[开始] --> B{是否有效?}
189
+ B -->|是| C[处理数据]
190
+ B -->|否| D[显示错误]
191
+ C --> E[完成]
192
+ D --> E
193
+ ```
194
+ ````
195
+
196
+ **主要特性:**
197
+ - 🎨 自动主题切换(深色/浅色模式)
198
+ - 🔄 懒加载(仅在可见时渲染)
199
+ - 📋 一键复制图表代码
200
+ - 🖼️ 全屏查看功能
201
+ - 🔒 安全渲染(DOMPurify 清理)
202
+
203
+ **支持的图表类型:**
204
+ - **流程图**(`flowchart`/`graph`):用于展示流程和决策
205
+ ![Mermaid 流程图示例](https://docs.mhaibaraai.cn/mermaid/mermaid-flowchart.png)
206
+ - **时序图**(`sequenceDiagram`):用于展示交互时序
207
+ ![Mermaid 时序图示例](https://docs.mhaibaraai.cn/mermaid/mermaid-sequence.png)
208
+ - **类图**(`classDiagram`):用于展示类关系
209
+ - **状态图**(`stateDiagram`):用于展示状态转换
210
+ - **甘特图**(`gantt`):用于展示项目时间线
211
+ - **饼图**(`pie`):用于展示数据占比
212
+ - **Git 图**(`gitGraph`):用于展示分支历史
213
+ - 以及更多 [Mermaid 支持的图表类型](https://mermaid.js.org/intro/)
214
+
215
+ **带文件名的图表:**
216
+
217
+ ````md [md]
218
+ ```mermaid [auth-flow.mmd]
219
+ sequenceDiagram
220
+ participant U as 用户
221
+ participant A as 认证服务
222
+ participant D as 数据库
223
+ U->>A: 登录请求
224
+ A->>D: 验证凭证
225
+ D-->>A: 返回用户信息
226
+ A-->>U: 返回 Token
227
+ ```
228
+ ````
229
+
230
+ ## 🛠️ 开发
231
+
232
+ ### 本地开发
233
+
234
+ ```bash [Terminal]
235
+ # 克隆项目
236
+ git clone https://github.com/mhaibaraai/movk-nuxt-docs.git
237
+ # 进入项目目录
238
+ cd movk-nuxt-docs
239
+ # 安装依赖
240
+ pnpm install
241
+ # 启动开发服务器
242
+ pnpm dev
243
+ ```
244
+
245
+ 开发服务器将在 `http://localhost:3000` 启动。
246
+
247
+ ### 构建生产版本
248
+
249
+ ```bash [Terminal]
250
+ # 构建应用
251
+ pnpm build
252
+ # 本地预览生产构建
253
+ pnpm preview
254
+ ```
255
+
256
+ ### 发布
257
+
258
+ ```bash [Terminal]
259
+ # 发布 layer 到 npm
260
+ pnpm release:layer
261
+ # 发布完整项目
262
+ pnpm release
263
+ ```
264
+
265
+ ## ⚡ 技术栈
266
+
267
+ 本项目基于以下优秀的开源项目构建:
268
+
269
+ ### 核心框架
270
+
271
+ - [Nuxt 4](https://nuxt.com/) - Web 框架
272
+ - [Nuxt Content](https://content.nuxt.com/) - 基于文件的 CMS
273
+ - [Nuxt UI](https://ui.nuxt.com/) - UI 组件库
274
+ - [Tailwind CSS 4](https://tailwindcss.com/) - CSS 框架
275
+
276
+ ### AI 集成
277
+
278
+ - [Vercel AI SDK](https://sdk.vercel.ai/) - AI 集成框架
279
+ - [Nuxt LLMs](https://github.com/nuxt-content/nuxt-llms) - LLM 优化
280
+ - [@nuxtjs/mcp-toolkit](https://github.com/nuxt-modules/mcp-toolkit) - MCP Server 支持
281
+ - [Shiki](https://shiki.style/) - 代码语法高亮
282
+ - [Shiki Stream](https://github.com/antfu/shiki-stream) - 流式代码高亮
283
+
284
+ ### 功能增强
285
+
286
+ - [Nuxt Component Meta](https://github.com/nuxt-content/nuxt-component-meta) - 组件元数据提取
287
+ - [Nuxt Image](https://image.nuxt.com/) - 图片优化
288
+ - [Nuxt SEO](https://nuxtseo.com/) - SEO 优化
289
+ - [Octokit](https://github.com/octokit/rest.js) - GitHub API 集成
290
+
291
+ ## 📖 文档
292
+
293
+ 访问 [Movk Nuxt Docs 文档](https://docs.mhaibaraai.cn/) 了解详细的使用指南和 API 文档。
294
+
295
+ ## 🙏 致谢
296
+
297
+ 本项目基于以下优秀项目构建或受其启发:
298
+
299
+ - [Docus](https://docus.dev/) - 由 Nuxt Content 团队开发的文档主题
300
+ - [Nuxt UI Docs Template](https://docs-template.nuxt.dev/) - Nuxt UI 官方文档模板
301
+
302
+ ## 📄 许可证
303
+
304
+ [MIT](./LICENSE) License © 2024-PRESENT [YiXuan](https://github.com/mhaibaraai)
305
+
306
+
307
+ <!-- Badges -->
308
+
309
+ [npm-version-src]: https://img.shields.io/npm/v/@movk/nuxt-docs/latest.svg?style=flat&colorA=020420&colorB=00DC82
310
+ [npm-version-href]: https://npmjs.com/package/@movk/nuxt-docs
311
+ [npm-downloads-src]: https://img.shields.io/npm/dm/@movk/nuxt-docs.svg?style=flat&colorA=020420&colorB=00DC82
312
+ [npm-downloads-href]: https://npm.chart.dev/@movk/nuxt-docs
313
+ [license-src]: https://img.shields.io/badge/License-MIT-blue.svg
314
+ [license-href]: https://npmjs.com/package/@movk/nuxt-docs
315
+ [nuxt-src]: https://img.shields.io/badge/Nuxt-4-00DC82?logo=nuxt.js&logoColor=fff
316
+ [nuxt-href]: https://nuxt.com
package/app/app.config.ts CHANGED
@@ -108,7 +108,8 @@ export default defineAppConfig({
108
108
  streaming: 'i-lucide-chevron-down',
109
109
  providers: {
110
110
  deepseek: 'i-ri-deepseek-fill',
111
- alibaba: 'i-simple-icons-alibabacloud'
111
+ alibaba: 'i-simple-icons-alibabacloud',
112
+ zai: 'i-simple-icons:zig'
112
113
  }
113
114
  }
114
115
  }
package/app/app.vue CHANGED
@@ -64,8 +64,8 @@ provide('navigation', rootNavigation)
64
64
  <ClientOnly>
65
65
  <LazyUContentSearch :files="files" :navigation="rootNavigation" :fuse="{ resultLimit: 1000 }" />
66
66
  <template v-if="isAiChatEnabled">
67
- <LazyAiChatFloatingInput />
68
67
  <LazyAiChatPanel />
68
+ <LazyAiChatFloatingInput />
69
69
  </template>
70
70
  </ClientOnly>
71
71
  </template>
@@ -22,19 +22,3 @@
22
22
  :root {
23
23
  --ui-container: var(--container-8xl);
24
24
  }
25
-
26
- /* Shiki icon highlight transformer styles */
27
- .shiki-icon-highlight {
28
- display: inline-block;
29
- width: 1.25em;
30
- height: 1.25em;
31
- vertical-align: -0.25em;
32
- margin-right: 0.125em;
33
- background-color: var(--ui-text-highlighted);
34
- -webkit-mask-repeat: no-repeat;
35
- mask-repeat: no-repeat;
36
- -webkit-mask-size: 100% 100%;
37
- mask-size: 100% 100%;
38
- -webkit-mask-image: var(--shiki-icon-url);
39
- mask-image: var(--shiki-icon-url);
40
- }
@@ -32,7 +32,7 @@ export interface AiChatModuleOptions {
32
32
  models?: string[]
33
33
  }
34
34
 
35
- const log = logger.withTag('movk-nuxt-docs:ai-assistant')
35
+ const log = logger.withTag('movk-nuxt-docs')
36
36
 
37
37
  export default defineNuxtModule<AiChatModuleOptions>({
38
38
  meta: {
@@ -77,7 +77,7 @@ export default defineNuxtModule<AiChatModuleOptions>({
77
77
  }
78
78
 
79
79
  if (!hasApiKey) {
80
- log.warn('[ai-chat] Module disabled: no API key found in environment variables.')
80
+ log.warn('[movk-nuxt-docs] Ai Chat Module disabled: no API key found in environment variables.')
81
81
  return
82
82
  }
83
83
 
@@ -119,7 +119,7 @@ export default defineNuxtModule<AiChatModuleOptions>({
119
119
  handler: resolve('./runtime/server/api/ai-chat')
120
120
  })
121
121
  } else {
122
- log.info(`[ai-chat] Using custom handler, skipping default handler registration`)
122
+ log.info(`[movk-nuxt-docs] Using custom handler, skipping default handler registration`)
123
123
  }
124
124
  }
125
125
  })
@@ -61,39 +61,48 @@ defineShortcuts(shortcuts)
61
61
  :animate="{ y: 0, opacity: 1 }"
62
62
  :exit="{ y: 100, opacity: 0 }"
63
63
  :transition="{ duration: 0.2, ease: 'easeOut' }"
64
- class="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 px-4"
64
+ class="fixed inset-x-0 z-10 px-4 sm:px-80 bottom-[max(1.5rem,env(safe-area-inset-bottom))]"
65
65
  style="will-change: transform"
66
66
  >
67
- <form @submit.prevent="handleSubmit">
68
- <UInput
69
- ref="inputRef"
70
- v-model="input"
71
- :placeholder="aiChat.texts.placeholder"
72
- size="lg"
73
- :ui="{
74
- root: 'w-72 py-0.5 focus-within:w-96 transition-all duration-300 ease-out',
75
- base: 'bg-default/80 backdrop-blur-xl shadow-lg',
76
- trailing: 'pe-2'
77
- }"
78
- @keydown.enter.exact.prevent="handleSubmit"
79
- >
80
- <template #trailing>
81
- <div class="flex items-center gap-2">
82
- <div class="hidden sm:!flex items-center gap-1">
83
- <UKbd v-for="key in shortcutDisplayKeys" :key="key" :value="key" />
84
- </div>
67
+ <form
68
+ class="flex w-full justify-center"
69
+ @submit.prevent="handleSubmit"
70
+ >
71
+ <div class="w-full max-w-96">
72
+ <UInput
73
+ ref="inputRef"
74
+ v-model="input"
75
+ :placeholder="aiChat.texts.placeholder"
76
+ size="lg"
77
+ maxlength="1000"
78
+ :ui="{
79
+ root: 'group w-full! min-w-0 sm:max-w-96 transition-all duration-300 ease-out [@media(hover:hover)]:hover:scale-105 [@media(hover:hover)]:focus-within:scale-105',
80
+ base: 'bg-default shadow-lg text-base',
81
+ trailing: 'pe-2'
82
+ }"
83
+ @keydown.enter.exact.prevent="handleSubmit"
84
+ >
85
+ <template #trailing>
86
+ <div class="flex items-center gap-2">
87
+ <div class="hidden sm:flex group-focus-within:hidden items-center gap-1">
88
+ <UKbd
89
+ v-for="key in shortcutDisplayKeys"
90
+ :key="key"
91
+ :value="key"
92
+ />
93
+ </div>
85
94
 
86
- <UButton
87
- aria-label="Send Message"
88
- type="submit"
89
- icon="i-lucide-arrow-up"
90
- color="primary"
91
- size="xs"
92
- :disabled="!input.trim()"
93
- />
94
- </div>
95
- </template>
96
- </UInput>
95
+ <UButton
96
+ type="submit"
97
+ icon="i-lucide-arrow-up"
98
+ color="primary"
99
+ size="xs"
100
+ :disabled="!input.trim()"
101
+ />
102
+ </div>
103
+ </template>
104
+ </UInput>
105
+ </div>
97
106
  </form>
98
107
  </motion.div>
99
108
  </AnimatePresence>
@@ -121,13 +121,10 @@ onMounted(() => {
121
121
  <template>
122
122
  <DefineChatContent v-slot="{ showExpandButton = true }">
123
123
  <div class="flex h-full flex-col">
124
- <div class="flex h-16 shrink-0 items-center justify-between border-b border-muted/50 px-4">
124
+ <div class="flex h-16 shrink-0 items-center justify-between border-b border-default px-4">
125
125
  <span class="font-medium text-highlighted">{{ aiChat.texts.title }}</span>
126
126
  <div class="flex items-center gap-1">
127
- <UTooltip
128
- v-if="showExpandButton"
129
- :text="isExpanded ? aiChat.texts.collapse : aiChat.texts.expand"
130
- >
127
+ <UTooltip v-if="showExpandButton" :text="isExpanded ? aiChat.texts.collapse : aiChat.texts.expand">
131
128
  <UButton
132
129
  :icon="isExpanded ? 'i-lucide-minimize-2' : 'i-lucide-maximize-2'"
133
130
  color="neutral"
@@ -138,10 +135,7 @@ onMounted(() => {
138
135
  @click="toggleExpanded"
139
136
  />
140
137
  </UTooltip>
141
- <UTooltip
142
- v-if="chat.messages.length > 0"
143
- :text="aiChat.texts.clearChat"
144
- >
138
+ <UTooltip v-if="chat.messages.length > 0" :text="aiChat.texts.clearChat">
145
139
  <UButton
146
140
  :icon="aiChat.icons.clearChat"
147
141
  color="neutral"
@@ -183,7 +177,11 @@ onMounted(() => {
183
177
  v-for="(part, index) in message.parts"
184
178
  :key="`${message.id}-${part.type}-${index}${'state' in part ? `-${part.state}` : ''}`"
185
179
  >
186
- <AiChatReasoning v-if="part.type === 'reasoning'" :text="part.text" :is-streaming="part.state !== 'done'" />
180
+ <AiChatReasoning
181
+ v-if="part.type === 'reasoning'"
182
+ :text="part.text"
183
+ :is-streaming="part.state !== 'done'"
184
+ />
187
185
 
188
186
  <MDCCached
189
187
  v-else-if="part.type === 'text'"
@@ -217,19 +215,10 @@ onMounted(() => {
217
215
  </template>
218
216
  </UChatMessages>
219
217
 
220
- <div
221
- v-else
222
- class="p-4"
223
- >
224
- <div
225
- v-if="!faqQuestions?.length"
226
- class="flex h-full flex-col items-center justify-center py-12 text-center"
227
- >
218
+ <div v-else class="p-4">
219
+ <div v-if="!faqQuestions?.length" class="flex h-full flex-col items-center justify-center py-12 text-center">
228
220
  <div class="mb-4 flex size-12 items-center justify-center rounded-full bg-primary/10">
229
- <UIcon
230
- name="i-lucide-message-circle-question"
231
- class="size-6 text-primary"
232
- />
221
+ <UIcon name="i-lucide-message-circle-question" class="size-6 text-primary" />
233
222
  </div>
234
223
  <h3 class="mb-2 text-base font-medium text-highlighted">
235
224
  {{ aiChat.texts.askAnything }}
@@ -253,17 +242,16 @@ onMounted(() => {
253
242
  <UChatPrompt
254
243
  v-model="input"
255
244
  :rows="2"
256
- class="text-sm"
257
- variant="subtle"
258
245
  :placeholder="aiChat.texts.placeholder"
246
+ maxlength="1000"
259
247
  :ui="{
260
248
  root: 'shadow-none!',
261
- body: '*:p-0! *:rounded-none!'
249
+ body: '*:p-0! *:rounded-none! *:text-sm!'
262
250
  }"
263
251
  @submit="handleSubmit"
264
252
  >
265
253
  <template #footer>
266
- <div class="hidden items-center divide-x divide-muted/50 sm:flex">
254
+ <div class="flex items-center gap-1 text-xs text-muted">
267
255
  <AiChatModelSelect v-model="model" />
268
256
  <div class="flex gap-1 justify-between items-center px-1 text-xs text-muted">
269
257
  <span>{{ aiChat.texts.lineBreak }}</span>
@@ -281,22 +269,25 @@ onMounted(() => {
281
269
  />
282
270
  </template>
283
271
  </UChatPrompt>
272
+ <div class="mt-1 flex text-xs text-dimmed items-center justify-between">
273
+ <span>刷新时聊天会被清除</span>
274
+ <span>
275
+ {{ input.length }}/1000
276
+ </span>
277
+ </div>
284
278
  </div>
285
279
  </div>
286
280
  </DefineChatContent>
287
281
 
288
282
  <aside
289
283
  v-if="!isMobile"
290
- class="fixed top-0 z-50 h-dvh overflow-hidden border-l border-muted/50 bg-default/95 backdrop-blur-xl transition-[right,width] duration-200 ease-linear will-change-[right,width]"
284
+ class="left-auto! fixed top-0 z-50 h-dvh overflow-hidden border-l border-default bg-default/95 backdrop-blur-xl transition-[right,width] duration-200 ease-linear will-change-[right,width]"
291
285
  :style="{
292
286
  width: `${panelWidth}px`,
293
287
  right: isOpen ? '0' : `-${panelWidth}px`
294
288
  }"
295
289
  >
296
- <div
297
- class="h-full transition-[width] duration-200 ease-linear"
298
- :style="{ width: `${panelWidth}px` }"
299
- >
290
+ <div class="h-full transition-[width] duration-200 ease-linear" :style="{ width: `${panelWidth}px` }">
300
291
  <ReuseChatContent :show-expand-button="true" />
301
292
  </div>
302
293
  </aside>
@@ -304,8 +295,6 @@ onMounted(() => {
304
295
  <USlideover
305
296
  v-else
306
297
  v-model:open="isOpen"
307
- title=" "
308
- description=" "
309
298
  side="right"
310
299
  :ui="{
311
300
  content: 'ring-0 bg-default'
@@ -1,16 +1,28 @@
1
- import { createHighlighter } from 'shiki'
2
- import type { HighlighterGeneric } from 'shiki'
3
- import { createJavaScriptRegexEngine } from 'shiki/engine-javascript.mjs'
1
+ import { createHighlighterCore } from '@shikijs/core'
2
+ import type { HighlighterCore } from '@shikijs/core'
3
+ import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'
4
4
 
5
- let highlighter: HighlighterGeneric<any, any> | null = null
6
-
7
- let promise: Promise<HighlighterGeneric<any, any>> | null = null
5
+ let highlighter: HighlighterCore | null = null
6
+ let promise: Promise<HighlighterCore> | null = null
8
7
 
9
8
  export const useHighlighter = async () => {
10
9
  if (!promise) {
11
- promise = createHighlighter({
12
- langs: ['vue', 'js', 'ts', 'css', 'html', 'json', 'yaml', 'markdown', 'bash'],
13
- themes: ['material-theme-palenight', 'material-theme-lighter'],
10
+ promise = createHighlighterCore({
11
+ langs: [
12
+ import('@shikijs/langs/vue'),
13
+ import('@shikijs/langs/javascript'),
14
+ import('@shikijs/langs/typescript'),
15
+ import('@shikijs/langs/css'),
16
+ import('@shikijs/langs/html'),
17
+ import('@shikijs/langs/json'),
18
+ import('@shikijs/langs/yaml'),
19
+ import('@shikijs/langs/markdown'),
20
+ import('@shikijs/langs/bash')
21
+ ],
22
+ themes: [
23
+ import('@shikijs/themes/material-theme-palenight'),
24
+ import('@shikijs/themes/material-theme-lighter')
25
+ ],
14
26
  engine: createJavaScriptRegexEngine()
15
27
  })
16
28
  }
package/modules/config.ts CHANGED
@@ -18,7 +18,8 @@ export default defineNuxtModule({
18
18
  const meta = await getPackageJsonMetadata(dir)
19
19
  const gitInfo = await getLocalGitInfo(dir) || getGitEnv()
20
20
 
21
- const siteName = nuxt.options?.site?.name || meta.name || gitInfo?.name || ''
21
+ const site = nuxt.options.site
22
+ const siteName = (site && site.name) || meta.name || gitInfo?.name || ''
22
23
 
23
24
  nuxt.options.site = defu(nuxt.options.site, {
24
25
  url,
package/nuxt.config.ts CHANGED
@@ -1,21 +1,20 @@
1
- import { defineNuxtConfig } from 'nuxt/config'
2
1
  import pkg from './package.json'
2
+ import { createResolver } from '@nuxt/kit'
3
3
 
4
- // WASM runtime imports that Rollup should not attempt to resolve
5
- const WASM_EXTERNALS = ['env', 'wasi_snapshot_preview1']
6
-
7
- function mergeExternals(existing: unknown, additions: string[]): string[] {
8
- if (Array.isArray(existing)) return [...existing, ...additions]
9
- if (typeof existing === 'string') return [existing, ...additions]
10
- return additions
11
- }
4
+ const { resolve } = createResolver(import.meta.url)
12
5
 
13
6
  export default defineNuxtConfig({
14
7
  modules: [
8
+ resolve('./modules/config'),
9
+ resolve('./modules/routing'),
10
+ resolve('./modules/md-rewrite'),
11
+ resolve('./modules/component-example'),
12
+ resolve('./modules/css'),
15
13
  '@nuxt/ui',
16
14
  '@nuxt/content',
17
15
  '@nuxt/image',
18
16
  '@nuxt/a11y',
17
+ '@nuxtjs/robots',
19
18
  '@nuxtjs/mcp-toolkit',
20
19
  '@vueuse/nuxt',
21
20
  'nuxt-component-meta',
@@ -50,7 +49,8 @@ export default defineNuxtConfig({
50
49
 
51
50
  mdc: {
52
51
  highlight: {
53
- noApiRoute: false
52
+ noApiRoute: false,
53
+ shikiEngine: 'javascript'
54
54
  }
55
55
  },
56
56
 
@@ -76,9 +76,22 @@ export default defineNuxtConfig({
76
76
  crawlLinks: true,
77
77
  failOnError: false,
78
78
  autoSubfolderIndex: false
79
+ },
80
+ compatibilityDate: {
81
+ // Don't generate observability routes for now
82
+ vercel: '2025-07-14'
83
+ }
84
+ },
85
+
86
+ vite: {
87
+ build: {
88
+ sourcemap: false,
89
+ chunkSizeWarningLimit: 1024
79
90
  }
80
91
  },
81
92
 
93
+ telemetry: false,
94
+
82
95
  hooks: {
83
96
  'vite:extendConfig': async (config) => {
84
97
  // Ensure optimizeDeps.include exists
@@ -103,28 +116,6 @@ export default defineNuxtConfig({
103
116
  '@movk/nuxt-docs > mermaid > d3',
104
117
  '@movk/nuxt-docs > mermaid > dompurify'
105
118
  )
106
-
107
- // WASM plugin support for Shiki
108
- const [wasm, topLevelAwait] = await Promise.all([
109
- import('vite-plugin-wasm'),
110
- import('vite-plugin-top-level-await')
111
- ])
112
- config.plugins!.push(wasm.default(), topLevelAwait.default() as any)
113
-
114
- const build = config.build || ((config as any).build = {})
115
- build.rollupOptions ??= {}
116
- build.rollupOptions.external = mergeExternals(
117
- build.rollupOptions.external,
118
- WASM_EXTERNALS
119
- )
120
- },
121
-
122
- 'nitro:config': (nitroConfig) => {
123
- nitroConfig.rollupConfig ??= {}
124
- nitroConfig.rollupConfig.external = mergeExternals(
125
- nitroConfig.rollupConfig.external,
126
- WASM_EXTERNALS
127
- )
128
119
  }
129
120
  },
130
121
 
@@ -156,12 +147,12 @@ export default defineNuxtConfig({
156
147
  fonts: {
157
148
  families: [
158
149
  { name: 'Public Sans', provider: 'google', global: true },
159
- { name: 'DM Sans', provider: 'google', global: true },
160
- { name: 'Geist', provider: 'google', global: true },
161
- { name: 'Inter', provider: 'google', global: true },
162
- { name: 'Poppins', provider: 'google', global: true },
163
- { name: 'Outfit', provider: 'google', global: true },
164
- { name: 'Raleway', provider: 'google', global: true }
150
+ { name: 'DM Sans', provider: 'google' },
151
+ { name: 'Geist', provider: 'google' },
152
+ { name: 'Inter', provider: 'google' },
153
+ { name: 'Poppins', provider: 'google' },
154
+ { name: 'Outfit', provider: 'google' },
155
+ { name: 'Raleway', provider: 'google' }
165
156
  ]
166
157
  },
167
158
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/nuxt-docs",
3
3
  "type": "module",
4
- "version": "1.12.0",
4
+ "version": "1.12.2",
5
5
  "private": false,
6
6
  "description": "Modern Nuxt 4 documentation theme with auto-generated component docs, AI chat assistant, MCP server, and complete developer experience optimization.",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
@@ -31,7 +31,7 @@
31
31
  "dependencies": {
32
32
  "@ai-sdk/gateway": "^3.0.39",
33
33
  "@ai-sdk/mcp": "^1.0.19",
34
- "@ai-sdk/vue": "^3.0.77",
34
+ "@ai-sdk/vue": "^3.0.78",
35
35
  "@iconify-json/lucide": "^1.2.89",
36
36
  "@iconify-json/simple-icons": "^1.2.70",
37
37
  "@iconify-json/vscode-icons": "^1.2.40",
@@ -43,13 +43,18 @@
43
43
  "@nuxt/ui": "^4.4.0",
44
44
  "@nuxtjs/mcp-toolkit": "^0.6.3",
45
45
  "@nuxtjs/mdc": "^0.20.1",
46
+ "@nuxtjs/robots": "^5.7.0",
46
47
  "@octokit/rest": "^22.0.1",
47
- "@openrouter/ai-sdk-provider": "^2.1.1",
48
+ "@openrouter/ai-sdk-provider": "^2.2.1",
49
+ "@shikijs/core": "^3.22.0",
50
+ "@shikijs/engine-javascript": "^3.22.0",
51
+ "@shikijs/langs": "^3.22.0",
52
+ "@shikijs/themes": "^3.22.0",
48
53
  "@vercel/analytics": "^1.6.1",
49
54
  "@vercel/speed-insights": "^1.3.1",
50
55
  "@vueuse/core": "^14.2.0",
51
56
  "@vueuse/nuxt": "^14.2.0",
52
- "ai": "^6.0.77",
57
+ "ai": "^6.0.78",
53
58
  "defu": "^6.1.4",
54
59
  "dompurify": "^3.3.1",
55
60
  "exsolve": "^1.0.8",
@@ -58,7 +63,7 @@
58
63
  "minimark": "^0.2.0",
59
64
  "motion-v": "^1.10.2",
60
65
  "nuxt": "^4.3.1",
61
- "nuxt-component-meta": "^0.17.1",
66
+ "nuxt-component-meta": "^0.17.2",
62
67
  "nuxt-llms": "^0.2.0",
63
68
  "nuxt-og-image": "^5.1.13",
64
69
  "ohash": "^2.0.11",
@@ -66,14 +71,13 @@
66
71
  "pkg-types": "^2.3.0",
67
72
  "prettier": "^3.8.1",
68
73
  "scule": "^1.3.0",
69
- "shiki": "^3.22.0",
70
74
  "shiki-stream": "^0.1.4",
71
75
  "tailwind-merge": "^3.4.0",
72
76
  "tailwindcss": "^4.1.18",
73
77
  "ufo": "^1.6.3",
78
+ "unist-util-visit": "^5.1.0",
74
79
  "vue-component-meta": "^3.2.4",
75
- "vite-plugin-top-level-await": "^1.6.0",
76
- "vite-plugin-wasm": "^3.5.0",
77
- "zod": "^4.3.6"
80
+ "zod": "^4.3.6",
81
+ "zod-to-json-schema": "^3.25.1"
78
82
  }
79
83
  }
@@ -6,8 +6,17 @@ export default defineNitroPlugin((nitroApp) => {
6
6
  await transformMDC(event, doc as any)
7
7
  })
8
8
 
9
- // @ts-expect-error - no types available
10
- nitroApp.hooks.hook('llms:generate', (_, { sections }) => {
9
+ nitroApp.hooks.hook('llms:generate', (_, { sections, domain }) => {
10
+ // Transform links except for "Documentation Sets"
11
+ sections.forEach((section) => {
12
+ if (section.title !== 'Documentation Sets') {
13
+ section.links = section.links?.map(link => ({
14
+ ...link,
15
+ href: `${link.href.replace(new RegExp(`^${domain}`), `${domain}/raw`)}.md`
16
+ }))
17
+ }
18
+ })
19
+
11
20
  // Move "Documentation Sets" to the end
12
21
  const docSetIdx = sections.findIndex((s: any) => s.title === 'Documentation Sets')
13
22
  if (docSetIdx !== -1) {
package/utils/meta.ts CHANGED
@@ -1,16 +1,21 @@
1
- // copy from https://github.com/nuxt-content/docus/tree/main/layer/utils
2
1
  import { readFile } from 'node:fs/promises'
3
2
  import { resolve } from 'node:path'
3
+ import { withHttps } from 'ufo'
4
4
 
5
5
  export function inferSiteURL() {
6
6
  // https://github.com/unjs/std-env/issues/59
7
- return (
8
- process.env.NUXT_SITE_URL
9
- || (process.env.NEXT_PUBLIC_VERCEL_URL && `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`) // Vercel
7
+ const url = (
8
+ process.env.NUXT_PUBLIC_SITE_URL // Nuxt public runtime config
9
+ || process.env.NUXT_SITE_URL // Nuxt site config
10
+ || process.env.VERCEL_PROJECT_PRODUCTION_URL // Vercel production URL
11
+ || process.env.VERCEL_BRANCH_URL // Vercel branch URL
12
+ || process.env.VERCEL_URL // Vercel deployment URL
10
13
  || process.env.URL // Netlify
11
14
  || process.env.CI_PAGES_URL // Gitlab Pages
12
15
  || process.env.CF_PAGES_URL // Cloudflare Pages
13
16
  )
17
+
18
+ return url ? withHttps(url) : undefined
14
19
  }
15
20
 
16
21
  export async function getPackageJsonMetadata(dir: string) {
package/app/mdc.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from '@nuxtjs/mdc/config'
2
- import { transformerIconHighlight } from './utils/shiki-transformer-icon-highlight'
3
-
4
- export default defineConfig({
5
- shiki: {
6
- transformers: [
7
- transformerIconHighlight()
8
- ]
9
- }
10
- })
@@ -1,89 +0,0 @@
1
- import type { ShikiTransformer } from 'shiki'
2
-
3
- export interface TransformerIconHighlightOptions {
4
- /**
5
- * Custom function to render the icon HTML
6
- * @default Uses Iconify API with mask mode
7
- */
8
- htmlIcon?: (icon: string) => string
9
- }
10
-
11
- // Common icon collections to validate against (sorted by length descending for proper matching)
12
- const ICON_COLLECTIONS = [
13
- 'simple-icons',
14
- 'vscode-icons',
15
- 'tabler',
16
- 'lucide',
17
- 'logos',
18
- 'ph'
19
- ]
20
-
21
- function parseIconName(text: string): { collection: string, name: string, format: 'i' | 'colon' } | null {
22
- // Strip quotes if present (single, double, or backticks)
23
- let cleanText = text
24
- if (/^['"`].*['"`]$/.test(text)) {
25
- cleanText = text.slice(1, -1)
26
- }
27
-
28
- // Try i-{collection}-{name} format
29
- if (cleanText.startsWith('i-')) {
30
- const rest = cleanText.slice(2)
31
- for (const collection of ICON_COLLECTIONS) {
32
- if (rest.startsWith(`${collection}-`)) {
33
- const name = rest.slice(collection.length + 1)
34
- if (name && /^[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(name)) {
35
- return { collection, name, format: 'i' }
36
- }
37
- }
38
- }
39
- }
40
-
41
- // Try {collection}:{name} format
42
- const colonIndex = cleanText.indexOf(':')
43
- if (colonIndex > 0) {
44
- const collection = cleanText.slice(0, colonIndex)
45
- const name = cleanText.slice(colonIndex + 1)
46
- if (ICON_COLLECTIONS.includes(collection) && name && /^[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(name)) {
47
- return { collection, name, format: 'colon' }
48
- }
49
- }
50
-
51
- return null
52
- }
53
-
54
- export function transformerIconHighlight(options: TransformerIconHighlightOptions = {}): ShikiTransformer {
55
- const { htmlIcon } = options
56
-
57
- return {
58
- name: 'shiki-transformer-icon-highlight',
59
- span(hast, _line, _col, _lineElement, token) {
60
- const text = token.content
61
-
62
- // Try to parse as an icon
63
- const parsed = parseIconName(text)
64
- if (!parsed) return
65
-
66
- const iconIdentifier = `${parsed.collection}:${parsed.name}`
67
- // Add color=black for mask-image to work properly (mask uses luminance)
68
- const iconUrl = `https://api.iconify.design/${iconIdentifier}.svg?color=%23000`
69
-
70
- // Create the icon element as a proper HAST element
71
- const iconElement = htmlIcon
72
- ? { type: 'raw' as const, value: htmlIcon(iconIdentifier) }
73
- : {
74
- type: 'element' as const,
75
- tagName: 'i',
76
- properties: {
77
- class: 'shiki-icon-highlight',
78
- style: `--shiki-icon-url: url(${iconUrl})`
79
- },
80
- children: []
81
- }
82
-
83
- // Prepend the icon to the span content
84
- if (hast.children) {
85
- hast.children.unshift(iconElement)
86
- }
87
- }
88
- }
89
- }