@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 +316 -0
- package/app/app.config.ts +2 -1
- package/app/app.vue +1 -1
- package/app/assets/css/main.css +0 -16
- package/modules/ai-chat/index.ts +3 -3
- package/modules/ai-chat/runtime/components/AiChatFloatingInput.vue +39 -30
- package/modules/ai-chat/runtime/components/AiChatPanel.vue +22 -33
- package/modules/ai-chat/runtime/composables/useHighlighter.ts +21 -9
- package/modules/config.ts +2 -1
- package/nuxt.config.ts +29 -38
- package/package.json +13 -9
- package/server/plugins/llms.ts +11 -2
- package/utils/meta.ts +9 -4
- package/app/mdc.config.ts +0 -10
- package/app/utils/shiki-transformer-icon-highlight.ts +0 -89
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
[](https://docs.mhaibaraai.cn/)
|
|
2
|
+
|
|
3
|
+
> 基于 Nuxt 4 的现代文档主题,集成组件自动化文档、AI 聊天助手、MCP Server 和完整的开发者体验优化
|
|
4
|
+
|
|
5
|
+
[](https://docs.mhaibaraai.cn/mcp/deeplink)
|
|
6
|
+
[](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
|
+

|
|
206
|
+
- **时序图**(`sequenceDiagram`):用于展示交互时序
|
|
207
|
+

|
|
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>
|
package/app/assets/css/main.css
CHANGED
|
@@ -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
|
-
}
|
package/modules/ai-chat/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface AiChatModuleOptions {
|
|
|
32
32
|
models?: string[]
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const log = logger.withTag('movk-nuxt-docs
|
|
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('[
|
|
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(`[
|
|
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
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
</
|
|
95
|
-
</
|
|
96
|
-
</
|
|
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-
|
|
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
|
|
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-
|
|
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="
|
|
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-
|
|
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 {
|
|
2
|
-
import type {
|
|
3
|
-
import { createJavaScriptRegexEngine } from '
|
|
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:
|
|
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 =
|
|
12
|
-
langs: [
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
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'
|
|
160
|
-
{ name: 'Geist', provider: 'google'
|
|
161
|
-
{ name: 'Inter', provider: 'google'
|
|
162
|
-
{ name: 'Poppins', provider: 'google'
|
|
163
|
-
{ name: 'Outfit', provider: 'google'
|
|
164
|
-
{ name: 'Raleway', provider: 'google'
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"zod": "^4.3.6"
|
|
80
|
+
"zod": "^4.3.6",
|
|
81
|
+
"zod-to-json-schema": "^3.25.1"
|
|
78
82
|
}
|
|
79
83
|
}
|
package/server/plugins/llms.ts
CHANGED
|
@@ -6,8 +6,17 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
6
6
|
await transformMDC(event, doc as any)
|
|
7
7
|
})
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
8
|
-
process.env.
|
|
9
|
-
||
|
|
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,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
|
-
}
|