md2ui 1.0.7 → 1.0.9

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/bin/md2ui.js CHANGED
@@ -1,61 +1,84 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // 子命令路由:build 命令走独立的 SSG 构建流程
4
+ const subCommand = process.argv[2]
5
+ if (subCommand === 'build') {
6
+ await import('./build.js')
7
+ process.exit(0)
8
+ }
9
+
3
10
  import { createServer } from 'vite'
4
11
  import { fileURLToPath } from 'url'
5
12
  import { dirname, resolve } from 'path'
6
13
  import fs from 'fs'
14
+ import { exec } from 'child_process'
15
+ import { pathToFileURL } from 'url'
7
16
 
8
- // 获取 CLI 工具所在目录(即 md2ui 包的安装目录)
17
+ // 获取 CLI 工具所在目录
9
18
  const __filename = fileURLToPath(import.meta.url)
10
19
  const __dirname = dirname(__filename)
11
20
  const pkgRoot = resolve(__dirname, '..')
12
21
 
13
- // 导入共享配置
14
- const { config } = await import(resolve(pkgRoot, 'src/config.js'))
15
-
16
22
  // 用户执行命令的目录
17
23
  const userDir = process.cwd()
18
24
 
19
- // 解析命令行参数
20
- const args = process.argv.slice(2)
21
- let port = config.defaultPort
25
+ // 默认配置
26
+ const defaultConfig = {
27
+ title: 'md2ui',
28
+ port: 3000,
29
+ folderExpanded: false,
30
+ github: '',
31
+ footer: '',
32
+ themeColor: '#3eaf7c'
33
+ }
22
34
 
23
- for (let i = 0; i < args.length; i++) {
24
- if (args[i] === '-p' || args[i] === '--port') {
25
- port = parseInt(args[i + 1]) || config.defaultPort
35
+ // 加载用户配置文件(md2ui.config.js .md2uirc.json)
36
+ async function loadUserConfig() {
37
+ // 尝试 md2ui.config.js
38
+ const jsPath = resolve(userDir, 'md2ui.config.js')
39
+ if (fs.existsSync(jsPath)) {
40
+ try {
41
+ const mod = await import(pathToFileURL(jsPath).href)
42
+ console.log(' 配置文件: md2ui.config.js\n')
43
+ return mod.default || mod
44
+ } catch (e) {
45
+ console.warn(' 配置文件加载失败:', e.message, '\n')
46
+ }
26
47
  }
48
+ // 尝试 .md2uirc.json
49
+ const jsonPath = resolve(userDir, '.md2uirc.json')
50
+ if (fs.existsSync(jsonPath)) {
51
+ try {
52
+ const raw = fs.readFileSync(jsonPath, 'utf-8')
53
+ console.log(' 配置文件: .md2uirc.json\n')
54
+ return JSON.parse(raw)
55
+ } catch (e) {
56
+ console.warn(' 配置文件加载失败:', e.message, '\n')
57
+ }
58
+ }
59
+ return {}
27
60
  }
28
61
 
29
- // 虚拟模块插件:动态提供用户目录的文档列表
30
- function docsPlugin() {
31
- const virtualModuleId = 'virtual:user-docs'
32
- const resolvedVirtualModuleId = '\0' + virtualModuleId
33
-
34
- return {
35
- name: 'user-docs-plugin',
36
- resolveId(id) {
37
- if (id === virtualModuleId) {
38
- return resolvedVirtualModuleId
39
- }
40
- },
41
- load(id) {
42
- if (id === resolvedVirtualModuleId) {
43
- // 扫描用户目录下的 md 文件
44
- const docs = scanDocs(userDir)
45
- return `export default ${JSON.stringify(docs)}`
46
- }
62
+ // 解析命令行参数
63
+ function parseArgs() {
64
+ const args = process.argv.slice(2)
65
+ const result = {}
66
+ for (let i = 0; i < args.length; i++) {
67
+ if (args[i] === '-p' || args[i] === '--port') {
68
+ result.port = parseInt(args[i + 1]) || undefined
69
+ i++
47
70
  }
48
71
  }
72
+ return result
49
73
  }
50
74
 
51
75
  // 扫描目录下的 md 文件
52
- function scanDocs(dir, basePath = '', level = 0) {
76
+ function scanDocs(dir, basePath = '', level = 0, folderExpanded = false) {
53
77
  const items = []
54
-
55
78
  if (!fs.existsSync(dir)) return items
56
-
79
+
57
80
  const entries = fs.readdirSync(dir, { withFileTypes: true })
58
- .filter(e => !e.name.startsWith('.') && e.name !== 'node_modules')
81
+ .filter(e => e.name !== 'node_modules')
59
82
  .sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'))
60
83
 
61
84
  for (const entry of entries) {
@@ -63,8 +86,7 @@ function scanDocs(dir, basePath = '', level = 0) {
63
86
  const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name
64
87
 
65
88
  if (entry.isDirectory()) {
66
- const children = scanDocs(fullPath, relativePath, level + 1)
67
- // 只有包含 md 文件的文件夹才加入目录
89
+ const children = scanDocs(fullPath, relativePath, level + 1, folderExpanded)
68
90
  if (children.length > 0) {
69
91
  const match = entry.name.match(/^(\d+)-(.+)$/)
70
92
  items.push({
@@ -73,7 +95,7 @@ function scanDocs(dir, basePath = '', level = 0) {
73
95
  order: match ? parseInt(match[1]) : 999,
74
96
  type: 'folder',
75
97
  level,
76
- expanded: config.folderExpanded,
98
+ expanded: folderExpanded,
77
99
  children
78
100
  })
79
101
  }
@@ -91,25 +113,43 @@ function scanDocs(dir, basePath = '', level = 0) {
91
113
  }
92
114
  }
93
115
 
94
- // 按 order 排序
95
116
  items.sort((a, b) => a.order - b.order)
96
117
  return items
97
118
  }
98
119
 
99
- // 提供用户目录 md 文件的中间件
100
- function serveUserDocs() {
120
+ // 检查目录下是否有 md 文件
121
+ function hasMdFiles(dir) {
122
+ if (!fs.existsSync(dir)) return false
123
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
124
+ .filter(e => e.name !== 'node_modules')
125
+ for (const entry of entries) {
126
+ if (entry.isFile() && entry.name.endsWith('.md')) return true
127
+ if (entry.isDirectory() && hasMdFiles(resolve(dir, entry.name))) return true
128
+ }
129
+ return false
130
+ }
131
+
132
+ // Vite 插件:提供用户文档 API + 配置 API + 热更新
133
+ function md2uiPlugin(siteConfig) {
101
134
  return {
102
- name: 'serve-user-docs',
135
+ name: 'md2ui-server',
103
136
  configureServer(server) {
137
+ // API 中间件
104
138
  server.middlewares.use((req, res, next) => {
105
- // 提供文档列表 API
139
+ // 文档列表 API
106
140
  if (req.url === '/@user-docs-list') {
107
- const docs = scanDocs(userDir)
141
+ const docs = scanDocs(userDir, '', 0, siteConfig.folderExpanded)
108
142
  res.setHeader('Content-Type', 'application/json; charset=utf-8')
109
143
  res.end(JSON.stringify(docs))
110
144
  return
111
145
  }
112
- // 提供文档内容
146
+ // 站点配置 API
147
+ if (req.url === '/@site-config') {
148
+ res.setHeader('Content-Type', 'application/json; charset=utf-8')
149
+ res.end(JSON.stringify(siteConfig))
150
+ return
151
+ }
152
+ // 文档内容
113
153
  if (req.url?.startsWith('/@user-docs/')) {
114
154
  const filePath = resolve(userDir, decodeURIComponent(req.url.replace('/@user-docs/', '')))
115
155
  if (fs.existsSync(filePath)) {
@@ -120,52 +160,61 @@ function serveUserDocs() {
120
160
  }
121
161
  next()
122
162
  })
123
- }
124
- }
125
- }
126
163
 
127
- // 检查目录下是否有 md 文件
128
- function hasMdFiles(dir) {
129
- if (!fs.existsSync(dir)) return false
130
-
131
- const entries = fs.readdirSync(dir, { withFileTypes: true })
132
- .filter(e => !e.name.startsWith('.') && e.name !== 'node_modules')
133
-
134
- for (const entry of entries) {
135
- if (entry.isFile() && entry.name.endsWith('.md')) {
136
- return true
137
- }
138
- if (entry.isDirectory()) {
139
- if (hasMdFiles(resolve(dir, entry.name))) {
140
- return true
164
+ // SPA fallback
165
+ return () => {
166
+ server.middlewares.use((req, res, next) => {
167
+ const url = req.url?.split('?')[0] || ''
168
+ if (url.startsWith('/@') || url.startsWith('/src/') || url.startsWith('/node_modules/') || url.match(/\.\w+$/)) {
169
+ next()
170
+ return
171
+ }
172
+ req.url = '/index.html'
173
+ next()
174
+ })
141
175
  }
142
176
  }
143
177
  }
144
- return false
145
178
  }
146
179
 
147
180
  async function start() {
148
181
  console.log(`\n md2ui - Markdown 文档预览工具\n`)
149
182
  console.log(` 扫描目录: ${userDir}\n`)
150
183
 
151
- // 检查是否有 md 文件
152
184
  if (!hasMdFiles(userDir)) {
153
185
  console.log(' 当前目录下没有找到 Markdown 文件 (.md)\n')
154
186
  console.log(' 请在包含 .md 文件的目录中运行此命令\n')
155
187
  process.exit(1)
156
188
  }
157
189
 
190
+ // 加载配置
191
+ const userConfig = await loadUserConfig()
192
+ const cliArgs = parseArgs()
193
+ const siteConfig = { ...defaultConfig, ...userConfig, ...cliArgs }
194
+
158
195
  const server = await createServer({
159
196
  root: pkgRoot,
160
197
  configFile: resolve(pkgRoot, 'vite.config.js'),
161
- plugins: [docsPlugin(), serveUserDocs()],
198
+ plugins: [md2uiPlugin(siteConfig)],
162
199
  server: {
163
- port
200
+ port: siteConfig.port
164
201
  }
165
202
  })
166
203
 
167
204
  await server.listen()
168
205
  server.printUrls()
206
+
207
+ if (siteConfig.title !== defaultConfig.title) {
208
+ console.log(` 站点标题: ${siteConfig.title}`)
209
+ }
210
+ console.log('')
211
+
212
+ // 自动打开浏览器
213
+ const address = server.httpServer.address()
214
+ const url = `http://localhost:${address.port}`
215
+ const platform = process.platform
216
+ const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open'
217
+ exec(`${cmd} ${url}`)
169
218
  }
170
219
 
171
220
  start().catch(console.error)
package/index.html CHANGED
@@ -6,9 +6,51 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg">
7
7
  <title>md2ui - Markdown 文档渲染系统</title>
8
8
  <meta name="description" content="将本地 Markdown 文档转换为美观的 HTML 页面">
9
+ <!-- 内联骨架屏样式,防止白屏闪烁 -->
10
+ <style>
11
+ * { margin: 0; padding: 0; box-sizing: border-box; }
12
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif; background: #ffffff; }
13
+ .app-skeleton {
14
+ display: flex;
15
+ flex-direction: column;
16
+ height: 100vh;
17
+ }
18
+ .skeleton-topbar {
19
+ height: 49px;
20
+ background: #f6f8fa;
21
+ border-bottom: 1px solid #d0d7de;
22
+ flex-shrink: 0;
23
+ }
24
+ .skeleton-body {
25
+ display: flex;
26
+ flex: 1;
27
+ overflow: hidden;
28
+ }
29
+ .skeleton-sidebar {
30
+ width: 260px;
31
+ background: #f6f8fa;
32
+ border-right: 1px solid #d0d7de;
33
+ flex-shrink: 0;
34
+ }
35
+ .skeleton-content {
36
+ flex: 1;
37
+ }
38
+ @media (max-width: 768px) {
39
+ .skeleton-sidebar { display: none; }
40
+ }
41
+ </style>
9
42
  </head>
10
43
  <body>
11
- <div id="app"></div>
44
+ <div id="app">
45
+ <!-- 骨架屏:Vue 挂载后自动替换 -->
46
+ <div class="app-skeleton">
47
+ <div class="skeleton-topbar"></div>
48
+ <div class="skeleton-body">
49
+ <div class="skeleton-sidebar"></div>
50
+ <div class="skeleton-content"></div>
51
+ </div>
52
+ </div>
53
+ </div>
12
54
  <script type="module" src="/src/main.js"></script>
13
55
  </body>
14
56
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md2ui",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "description": "将本地 Markdown 文档转换为美观的 HTML 页面",
6
6
  "author": "",
@@ -33,14 +33,15 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@vitejs/plugin-vue": "^5.0.0",
36
- "crypto-js": "^4.2.0",
36
+ "flexsearch": "^0.8.212",
37
+ "github-slugger": "^2.0.0",
38
+ "highlight.js": "^11.11.1",
39
+ "jsdom": "^28.1.0",
37
40
  "lucide-vue-next": "^0.556.0",
38
41
  "marked": "^11.1.1",
39
42
  "mermaid": "^10.6.1",
43
+ "minisearch": "^7.2.0",
40
44
  "vite": "^5.0.0",
41
45
  "vue": "^3.4.0"
42
- },
43
- "devDependencies": {
44
- "@types/crypto-js": "^4.2.2"
45
46
  }
46
47
  }