md-preview-cli-plus 1.0.1 → 1.0.3

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/.editorconfig ADDED
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
package/README.md CHANGED
@@ -8,26 +8,55 @@ Markdown 实时预览 + 导出工具
8
8
 
9
9
  CLI + Web + Markdown 三位一体
10
10
 
11
- 由于CLI对兼容性要求高,选择 CommonJS 模块化方式
11
+ 由于CLI对系统兼容性要求高,选择 CommonJS 模块化方式
12
12
 
13
13
  # Getting Started
14
14
 
15
15
  ```bash
16
+ #for repeated use
17
+ npm i md-preview-cli-plus
16
18
  npx md-preview --help #help
17
- npx md-preview README.md --port 4000 --theme dark --export ./666.html #无需下载 用完删除 不占空间
19
+ npx md-preview README.md --port 5533 --theme dark --export ./666.html #example
20
+
21
+ #run it once
22
+ npx md-preview-cli-plus --help
23
+ npx md-preview-cli-plus <file_path> [options]
18
24
  ```
25
+
19
26
  # Features
20
- 1. **Build CLI Tool:** Use `commander` for argument parsing, help messages (`--help`), versioning (`-V` / `--version`), and error handling.
21
- 2. **Watch File Changes:** Leverage `chokidar` for efficient, cross-platform file watching.
22
- 3. **Open Files:** Use `open` to launch files or apps across platforms.
23
- 4. **Colorful Terminal:** Integrate `chalk` for vibrant console output. 🌈
24
- 5. **Hot Reloading:** Wrap `socket.io` in a `useSocket()` function to watch file changes and trigger browser refresh (similar to Vite).
25
- 6. **HTML Exporting:** Add `--export <path>` to save rendered HTML output to a file.
26
- 7. **Theme Support:** Provide multi-theme styling via `--theme=dark` or similar options and support custom themes.
27
- 8. **Plugin Architecture:** Support custom parser plugins.
28
- 9. **Packaging & Publishing:** Publish as an NPM package; allow usage via `npx md-preview`.
29
- 10. **Syntax Highlighting:** Use `highlight.js` to render beautifully highlighted code blocks.
27
+
28
+ **CLI Construction** Built using `commander` for argument parsing, help information (`--help`), version control (`-V`, `--version`), and robust error handling.
29
+
30
+ **File Watching** Utilizes `chokidar` for efficient, cross-platform file watching with low resource consumption.
31
+
32
+ **File Opening** Uses `open` to launch files or applications across platforms with high compatibility.
33
+
34
+ **Colorful Terminal Output** Powered by `chalk` for vibrant CLI messages 🌈, and `highlight.js` for syntax highlighting in code blocks.
35
+
36
+ **Hot Module Replacement (HMR)** Similar to Vite's HMR: wraps `socket.io` for both client and server.
37
+
38
+ - The browser uses `useSocket()` to listen for server messages.
39
+ - On notification, the page auto-refreshes.
40
+ - The server watches file changes and pushes updates to the browser.
41
+
42
+ **HTML Export Feature** Supports `--export` flag to save rendered content as an HTML file.
43
+
44
+ **Theme Support** Enables theme switching via `--theme=dark` and supports custom themes placed in the `styles/` folder. Reference implementation available in `default.css`.
45
+
46
+ **Plugin System** Highly flexible plugin architecture with lifecycle hooks.
47
+
48
+ - Supports custom plugins
49
+ - Configuration via `.previewrc` file and CLI arguments
50
+ - CLI arguments take precedence over config file
51
+
52
+ **Packaging & Distribution** Published as an npm package.
53
+
54
+ - Supports direct usage via `npx md-preview-cli ...` without installation.
55
+
56
+ **Fault Tolerance** If a plugin throws an error, it will be ignored to prevent breaking the core functionality.
57
+
30
58
  # 🔌 Plugin System
59
+
31
60
  1. **Multiple Integration Options:**
32
61
  - Pass plugin path via CLI arguments.
33
62
  - Place plugins inside a directory (e.g. `plugins/`) and use a configuration file `.previewrc`.
@@ -36,55 +65,47 @@ npx md-preview README.md --port 4000 --theme dark --export ./666.html #无需下
36
65
  - `init`
37
66
  - `beforeRender`
38
67
  - `afterRender`
68
+
39
69
  # 功能细化
40
- 1. 构建CLI 采用commander 解析参数、增加帮助信息--help、版本控制-V --version、错误处理
41
70
 
71
+ 1. 构建CLI 采用commander 解析参数、增加帮助信息--help、版本控制-V --version、错误处理
42
72
  2. 文件监听变化 chokidar 跨平台兼容性好 低耗能
43
-
44
- 3. 打开open 打开各种文件程序 跨平台兼容性好
45
-
46
- 4. 打造彩色终端 🌈chalk
47
-
48
- 5. 热刷新页面:封装socket.io为useSocket()监听变化并通知刷新页面(类似 Vite)
49
-
73
+ 3. 打开open 打开各种文件程序 跨平台兼容性好
74
+ 4. 打造彩色终端 🌈chalk,引入代码高亮 highlight.js
75
+ 5. 热刷新HMR:(类似 Vite)封装socket.io客户端和服务端,浏览器使用useSocket()监听服务端消息,有通知则刷新页面,服务端监听文件变化并通知浏览器页面更新
50
76
  6. 导出 HTML 功能:增加 --export 参数保存 HTML 文件
51
-
52
- 7. 多主题支持:通过 --theme=dark 选择不同样式、支持自定义主题
53
-
77
+ 7. 多主题支持:通过 --theme=dark 选择不同样式、支持自定义主题(放在styles文件夹下,代码参考default.css)
54
78
  8. 插件机制:支持自定义插件及其生命周期钩子、高灵活度配置,支持插件配置文件.previewrc+CLI参数优先级控制
55
-
56
- 9. 打包发布:发布为 NPM 包,支持 npx md-preview
57
-
58
- 10. 引入代码高亮 highlight.js
59
-
60
- 11. 后续:
61
-
62
- 结合图床、插件沙箱机制、多语言支持i18n(自动识别/--lang参数切换)、vitest单元测试、WEB UI模式(拖拽+实时编辑)、项目文档VitePress
79
+ 9. 打包发布:发布为 NPM 包,支持 `npx md-preview-cli ...`无需下载,直接使用
80
+ 10. 容错机制:如果某插件有错误,无视该插件;
81
+ 11. 后续:结合图床、富文本编辑、插件沙箱机制、vitest单元测试
63
82
 
64
83
  # 插件机制
65
84
 
66
85
  1. 任选其一:可通过命令行传插件地址;也可以将插件放入某个目录下(例如plugins/)并设置插件配置文件`.previewrc`;如有冲突,则`.previewrc`配置优先级小于命令行参数
67
86
  2. 支持自定义插件,插件系统生命周期钩子 init、beforeRender/afterRender
87
+
68
88
  # 项目结构
89
+
69
90
  ```bash
70
- # 旧设定
71
91
  md-preview/
72
92
  ├── bin/
73
93
  │ └── cli.js # CLI 命令入口
74
94
  ├── lib/
75
- │ ├── plugins.js # 插件加载器
95
+ │ ├── pluginManager.js # 插件加载器
76
96
  │ ├── preview.js # 启动本地服务
77
97
  │ └── renderMarkdown.js # Markdown 渲染函数
98
+ │ └── loadConfig.js # 读取配置文件 .previewrc
99
+ │ └── loader.js # 读取某文件夹下所有插件
78
100
  ├── public/
79
- │ └── template.html # HTML 模板页面
101
+ │ └── hmrServer.js #hmr socket服务器
102
+ │ └── useSocket.js #hmr socket客户端
80
103
  ├── styles/
81
- │ └── style.css # 样式(可替换主题)
104
+ │ └── default.css # 默认主题样式
105
+ │ └── dark.css # 暗黑主题样式
82
106
  ├── README.md
83
107
  ├── package.json
84
108
  └── .gitignore
85
109
  plugins/
86
- ├── copyright-plugin.js # 示例插件
87
- ```
88
- ```js
89
- console.log("hello world")
110
+ ├── copyright-plugin.js # 示例插件 影响最底部文字 可直接删除
90
111
  ```
package/bin/cli.js CHANGED
@@ -1,59 +1,55 @@
1
1
  #!/usr/bin/env node
2
2
  //const inquirer=require('inquirer')
3
- const { program } = require('commander')
4
- const path = require('path')
5
- const startPreview = require('../lib/preview')
6
- const pluginManager = require('../lib/pluginManager')
7
- const loadConfig=require('../lib/loadConfig')
8
- const loadPlugins=require('../lib/loader')
3
+ const { program } = require('commander');
4
+ const path = require('path');
5
+ const startPreview = require('../lib/preview');
6
+ const pluginManager = require('../lib/pluginManager');
7
+ const loadConfig = require('../lib/loadConfig');
8
+ const loadPlugins = require('../lib/loader');
9
9
 
10
10
  //帮助信息自动生成,不需要手动写
11
11
  //默认命令触发条件就是和基本信息分开
12
- let file_d='path to the Markdown file'
13
- let port_d='specify preview port'
14
- let theme_d='choose theme style: default/dark'
15
- let export_d='export HTML to a given path (optional)'
16
- let plugin_d='load your plugins'
12
+ let file_d = 'path to the Markdown file';
13
+ let port_d = 'specify preview port';
14
+ let theme_d = 'choose theme style: default/dark';
15
+ let export_d = 'export HTML to a given path (optional)';
16
+ let plugin_d = 'load your plugins';
17
+
18
+ program.name('md-preview').description('Markdown real-time preview CLI').version('1.0.0');
17
19
 
18
20
  program
19
- .name('md-preview')
20
- .description('Markdown real-time preview CLI')
21
- .version('1.0.0')
22
-
23
- program
24
- .argument('<file>',`${file_d}` )
25
- .option('--port <port>',`${port_d}` , 3000)
26
- .option('--theme <theme>',`${theme_d}` , 'default')
27
- .option('--export <path>',`${export_d}` , null)
28
- //修改CLI支持--plugin参数 可多个
29
- .option('--plugin <path...>', `${plugin_d}`)//...是commander里的rest参数
30
- .action((file, options) => {
31
- //console.log(options)
32
-
33
- //调用加载配置并合并参数
34
- // 加载 .previewrc
35
- const rc = loadConfig()
36
-
37
- // 合并 theme 设置(命令行优先生效)
38
- options.theme = options.theme || rc.theme || 'default'
39
-
40
- // 加载插件(来自 CLI 参数 + .previewrc + plugins/ 目录)
41
- options.plugin = Array.from(new Set([
42
- ...(loadPlugins(rc.pluginsFolder) || []),
43
- ...(options.plugin || [])
44
- ]));
45
- //console.log(options.plugin)
46
- // 注册所有传入的插件
47
- if (options.plugin && options.plugin.length) {
48
- for (const pluginPath of options.plugin) {
49
- const abs = path.resolve(process.cwd(), pluginPath)
50
- const plugin = require(abs)
51
- pluginManager.use(plugin)
52
- }
53
- }
54
-
55
- const absolutePath = path.resolve(process.cwd(), file);
56
- startPreview(absolutePath, options,pluginManager);
57
- });
58
-
59
- program.parse() // 默认使用 process.argv
21
+ .argument('<file>', `${file_d}`)
22
+ .option('--port <port>', `${port_d}`, 7777)
23
+ .option('--theme <theme>', `${theme_d}`, 'default')
24
+ .option('--export <path>', `${export_d}`, null)
25
+ //修改CLI支持--plugin参数 可多个
26
+ .option('--plugin <path...>', `${plugin_d}`) //...是commander里的rest参数
27
+ .action((file, options) => {
28
+ //console.log(options)
29
+
30
+ //调用加载配置并合并参数
31
+ // 加载 .previewrc
32
+ const rc = loadConfig();
33
+
34
+ // 合并 theme 设置(命令行优先生效)
35
+ options.theme = options.theme || rc.theme || 'default';
36
+
37
+ // 加载插件(来自 CLI 参数 + .previewrc + plugins/ 目录)
38
+ options.plugin = Array.from(
39
+ new Set([...(loadPlugins(rc.pluginsFolder) || []), ...(options.plugin || [])])
40
+ );
41
+ //console.log(options.plugin)
42
+ // 注册所有传入的插件
43
+ if (options.plugin && options.plugin.length) {
44
+ for (const pluginPath of options.plugin) {
45
+ const abs = path.resolve(process.cwd(), pluginPath);
46
+ const plugin = require(abs);
47
+ pluginManager.use(plugin); //集合起来统一注册
48
+ }
49
+ }
50
+
51
+ const absolutePath = path.resolve(process.cwd(), file);
52
+ startPreview(absolutePath, options, pluginManager);
53
+ });
54
+
55
+ program.parse(); // 默认使用 process.argv
package/lib/loadConfig.js CHANGED
@@ -1,21 +1,20 @@
1
1
  //配置读取模块 lib/loadConfig.js
2
2
 
3
- const fs = require('fs')
4
- const path = require('path')
3
+ const fs = require('fs');
4
+ const path = require('path');
5
5
 
6
6
  function loadConfig() {
7
- const configPath = path.resolve(process.cwd(), '.previewrc')
8
- if (!fs.existsSync(configPath)) return {}
7
+ const configPath = path.resolve(process.cwd(), '.previewrc');
8
+ if (!fs.existsSync(configPath)) return {};
9
9
 
10
10
  try {
11
- const raw = fs.readFileSync(configPath, 'utf-8')
12
- const config = JSON.parse(raw)
13
- return config
11
+ const raw = fs.readFileSync(configPath, 'utf-8');
12
+ const config = JSON.parse(raw);
13
+ return config;
14
14
  } catch (e) {
15
- console.warn('⚠️ Failed to load .previewrc :', e.message)
16
- return {}
15
+ console.warn('⚠️ Failed to load .previewrc :', e.message);
16
+ return {};
17
17
  }
18
18
  }
19
19
 
20
- module.exports = loadConfig
21
-
20
+ module.exports = loadConfig;
package/lib/loader.js CHANGED
@@ -1,30 +1,30 @@
1
- //loader.js
1
+ //loader.js
2
2
  //把配置的插件都加载放入并返回
3
- const path = require('path')
4
- const fs = require('fs')
3
+ const path = require('path');
4
+ const fs = require('fs');
5
5
 
6
6
  function loadPlugins(folder_path) {
7
- let plugins = []
8
- //把folder_path中每个目录里的插件注册完
9
- for(const folder of folder_path){
10
- const pluginsDir = path.resolve(process.cwd(), folder);
11
-
12
- fs.readdirSync(pluginsDir).forEach(file => {
13
- const fullPath = path.join(pluginsDir, file);
7
+ let plugins = [];
8
+ //把folder_path目录下的插件注册完
9
+ if (Array.isArray(folder_path)) {
10
+ for (const folder of folder_path) {
11
+ const pluginsDir = path.resolve(process.cwd(), folder);
14
12
 
15
- // 只加载 .js 文件
16
- if (fs.statSync(fullPath).isFile() && file.endsWith('.js')) {
13
+ fs.readdirSync(pluginsDir).forEach((file) => {
14
+ const fullPath = path.join(pluginsDir, file);
15
+
16
+ // 只加载 .js 文件
17
+ if (fs.statSync(fullPath).isFile() && file.endsWith('.js')) {
17
18
  //const plugin = require(fullPath);
18
- plugins.push(fullPath)
19
-
20
- }else console.warn(`⚠️ Plugin not found at ${fullPath}`)
21
- });
19
+ plugins.push(fullPath);
20
+ } else console.warn(`⚠️ Plugin not found at ${fullPath}`);
21
+ });
22
+ }
23
+ } else {
24
+ console.log(`⚠️ folder_path is not an array, please check your config.`);
22
25
  }
23
-
24
- return plugins
25
- }
26
26
 
27
- module.exports = loadPlugins
27
+ return plugins;
28
+ }
28
29
 
29
-
30
-
30
+ module.exports = loadPlugins;
@@ -1,10 +1,10 @@
1
1
  //lib/pluginManager.js
2
- const fs = require('fs')
3
- const path = require('path')
2
+ const fs = require('fs');
3
+ const path = require('path');
4
4
 
5
5
  class PluginManager {
6
6
  constructor() {
7
- this.plugins = []
7
+ this.plugins = [];
8
8
  }
9
9
  // 注册插件
10
10
  use(plugin) {
@@ -12,47 +12,47 @@ class PluginManager {
12
12
  // 调用 init 钩子
13
13
  if (typeof plugin.init === 'function') {
14
14
  try {
15
- plugin.init()
15
+ plugin.init();
16
16
  } catch (e) {
17
- console.warn(`Failed to init plugin: ${plugin.name} `, e)
17
+ console.warn(`Failed to init plugin: ${plugin.name} `, e);
18
18
  }
19
19
  console.log(`✅ Loaded plugin: ${plugin.name}`);
20
20
  }
21
- this.plugins.push(plugin)
21
+ this.plugins.push(plugin);
22
22
  } else {
23
- console.warn(`Invalid plugin:${plugin}`)
23
+ console.warn(`Invalid plugin:${plugin}`);
24
24
  }
25
- return this // 支持链式调用
25
+ return this; // 支持链式调用
26
26
  }
27
27
 
28
- allBeforeRender(content,theme='default'){
29
- try{
30
- // 插件处理原始内容
31
- for (const plugin of this.plugins) {
28
+ allBeforeRender(content, theme = 'default') {
29
+ try {
30
+ // 插件处理原始内容
31
+ for (const plugin of this.plugins) {
32
32
  if (plugin.beforeRender) {
33
- content = plugin.beforeRender(content, { theme })
33
+ content = plugin.beforeRender(content, { theme });
34
34
  }
35
- }
36
- return content;
37
- }catch(err){
38
- console.error(`❌ Failed to use the plugin: ${err.message}`)
35
+ }
36
+ return content;
37
+ } catch (err) {
38
+ console.error(`❌ Failed to use the plugin: ${err.message}`);
39
39
  }
40
40
  }
41
41
 
42
- allAfterRender(html,theme='default'){
43
- // 插件处理 HTML 内容
44
- try{
45
- for (const plugin of this.plugins) {
46
- if (plugin.afterRender) {
47
- html = plugin.afterRender(html, { theme })
48
- }
42
+ allAfterRender(html, theme = 'default') {
43
+ // 插件处理 HTML 内容
44
+ try {
45
+ for (const plugin of this.plugins) {
46
+ if (plugin.afterRender) {
47
+ html = plugin.afterRender(html, { theme });
48
+ }
49
+ }
50
+ return html;
51
+ } catch (err) {
52
+ console.error(`❌ Failed to use the plugin:${err.message}`);
49
53
  }
50
- return html;
51
- }catch(err){
52
- console.error(`❌ Failed to use the plugin:${err.message}`)
53
54
  }
54
- }
55
55
  // 渲染流程,交给插件机制外部
56
56
  }
57
57
 
58
- module.exports = new PluginManager()
58
+ module.exports = new PluginManager();
package/lib/preview.js CHANGED
@@ -1,46 +1,35 @@
1
- const express = require('express')
2
- const fs = require('fs')
3
- const path = require('path')
4
- const {Server} =require('socket.io')
5
- const chokidar = require('chokidar')
6
- const markdownToHtml = require('./renderMarkdown')
7
- const open = require('open')
8
- const chalk = require('chalk')
1
+ const express = require('express');
2
+ const fs = require('fs');
3
+ const markdownToHtml = require('./renderMarkdown');
4
+ const hmrServer = require('../public/hmrServer');
5
+ const open = require('open');
6
+ const chalk = require('chalk');
7
+ const path = require('path');
9
8
 
10
- function startPreview(filePath, options,pluginManager) {
11
- const app = express()
12
- const port = options.port || 3000
9
+ function startPreview(filePath, options, pluginManager) {
10
+ const app = express();
11
+ const port = options.port || 3000;
13
12
 
14
13
  app.get('/', (req, res) => {
15
- let html = markdownToHtml(filePath, options.theme,options.port,pluginManager)
16
- html=pluginManager.allAfterRender(html,options.theme)
17
- if(options.export){
14
+ let html = markdownToHtml(filePath, options.theme, options.port, pluginManager);
15
+ html = pluginManager.allAfterRender(html, options.theme) ?? html;
16
+ if (options.export) {
18
17
  const exportPath = path.resolve(process.cwd(), options.export);
19
18
  fs.writeFileSync(exportPath, html);
20
19
  console.log(chalk.green(`✅ HTML exported to: ${exportPath}`));
21
20
  }
22
- res.send(html)
23
- })
21
+ res.send(html);
22
+ });
24
23
 
25
- const watcher = chokidar.watch(filePath)
26
-
27
- app.use(express.static(path.join(__dirname, '../styles')))
28
- app.use(express.static(path.join(__dirname, '../public')))
24
+ app.use(express.static(path.join(__dirname, '../styles')));
25
+ app.use(express.static(path.join(__dirname, '../public')));
29
26
 
30
- const server=app.listen(port, () => {
31
- console.log(chalk.cyan(`🚀 Local preview started at http://localhost:${port}`))
32
- open(`http://localhost:${port}`)
33
- })//返回http.Server对象
34
- const io = new Server(server, {
35
- cors: { origin: '*' }
36
- })
37
- io.on('connection', (socket) => {
38
- //console.log('connecting...')
39
- })
40
- watcher.on('change', () => {
41
- console.log(chalk.green('📄 Markdown file updated, refreshing page automatically.'))
42
- io.emit('update')
43
- })
27
+ const server = app.listen(port, () => {
28
+ console.log(chalk.cyan(`🚀 Local preview started at http://localhost:${port}`));
29
+ open(`http://localhost:${port}`);
30
+ }); //返回http.Server对象
31
+
32
+ hmrServer(server, filePath);
44
33
  }
45
34
 
46
- module.exports = startPreview
35
+ module.exports = startPreview;
@@ -1,17 +1,17 @@
1
- const fs = require('fs')
2
- const markdownIt = require('markdown-it')
1
+ const fs = require('fs');
2
+ const markdownIt = require('markdown-it');
3
3
 
4
- function markdownToHtml(filePath, theme = 'default',port,pluginManager) {
4
+ function markdownToHtml(filePath, theme = 'default', port, pluginManager) {
5
5
  const md = new markdownIt({
6
6
  html: true,
7
- linkify: true,//自动识别链接文本
8
- typographer: true//排版符号美化
9
- })
7
+ linkify: true, //自动识别链接文本
8
+ typographer: true, //排版符号美化
9
+ });
10
10
 
11
- let markdownContent = fs.readFileSync(filePath, 'utf-8')
12
- markdownContent=pluginManager.allBeforeRender(markdownContent,theme)
13
-
14
- let htmlContent = md.render(markdownContent)
11
+ let markdownContent = fs.readFileSync(filePath, 'utf-8');
12
+ markdownContent = pluginManager.allBeforeRender(markdownContent, theme) ?? markdownContent;
13
+
14
+ let htmlContent = md.render(markdownContent);
15
15
 
16
16
  return `
17
17
  <!DOCTYPE html>
@@ -43,7 +43,6 @@ function markdownToHtml(filePath, theme = 'default',port,pluginManager) {
43
43
  </script>
44
44
  </body>
45
45
  </html>`;
46
-
47
46
  }
48
47
 
49
- module.exports = markdownToHtml
48
+ module.exports = markdownToHtml;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md-preview-cli-plus",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "md real-time preview + export",
5
5
  "keywords": [
6
6
  "markdown",
@@ -24,7 +24,12 @@
24
24
  "md-preview": "./bin/cli.js"
25
25
  },
26
26
  "scripts": {
27
- "start": "node bin/cli.js"
27
+ "start": "node bin/cli.js",
28
+ "lint": "eslint . --ext .js,.ts",
29
+ "prepare": "husky"
30
+ },
31
+ "lint-staged": {
32
+ "*.js": ["prettier --write"]
28
33
  },
29
34
  "dependencies": {
30
35
  "chalk": "^4.1.2",
@@ -32,8 +37,15 @@
32
37
  "commander": "^14.0.0",
33
38
  "express": "^5.1.0",
34
39
  "markdown-it": "^14.1.0",
35
- "mongoose": "^8.16.3",
36
40
  "open": "^8.4.0",
37
41
  "socket.io": "^4.8.1"
42
+ },
43
+ "devDependencies": {
44
+ "eslint": "^9.32.0",
45
+ "eslint-config-prettier": "^10.1.8",
46
+ "eslint-plugin-prettier": "^5.5.3",
47
+ "husky": "^9.1.7",
48
+ "lint-staged": "^16.1.2",
49
+ "prettier": "^3.6.2"
38
50
  }
39
51
  }
@@ -1,14 +1,14 @@
1
1
  //This is also a sample plugin.
2
2
  module.exports = {
3
3
  name: 'copyright-plugin',
4
-
4
+
5
5
  // 在渲染前调用,可修改原始内容
6
6
  beforeRender(content, options) {
7
- return content
7
+ return content;
8
8
  },
9
9
 
10
10
  // 渲染为 HTML 后调用,可修改 HTML 字符串
11
11
  afterRender(html, options) {
12
- return html + `<footer><p style="text-align:center;color:#aaa;">© 70v-Yoyo </p></footer>`
13
- }
14
- }
12
+ return html + `<footer><p style="text-align:center;color:#aaa;">© 2025 </p></footer>`;
13
+ },
14
+ };
@@ -0,0 +1,18 @@
1
+ const { Server } = require('socket.io');
2
+ const chokidar = require('chokidar');
3
+ const chalk = require('chalk');
4
+
5
+ function hmrServer(server, filePath) {
6
+ const watcher = chokidar.watch(filePath);
7
+ const io = new Server(server, {
8
+ cors: { origin: '*' },
9
+ });
10
+ io.on('connection', (socket) => {
11
+ //console.log('connect successfully')
12
+ });
13
+ watcher.on('change', () => {
14
+ console.log(chalk.green('📄 Markdown file updated, refreshing page automatically.'));
15
+ io.emit('update');
16
+ });
17
+ }
18
+ module.exports = hmrServer;
@@ -1,24 +1,24 @@
1
1
  // useSocket.js(浏览器端模块,支持原生或 Vite 项目)
2
- import { io } from 'https://cdn.socket.io/4.7.5/socket.io.esm.min.js'
2
+ import { io } from 'https://cdn.socket.io/4.7.5/socket.io.esm.min.js';
3
3
 
4
4
  export function useSocket({ serverUrl, onUpdate }) {
5
- const socket = io(serverUrl)
5
+ const socket = io(serverUrl);
6
6
 
7
7
  socket.on('connect', () => {
8
- console.log('✅ 已连接 Socket.IO 服务')
9
- })
8
+ console.log('✅ 已连接 Socket.IO 服务');
9
+ });
10
10
 
11
11
  socket.on('disconnect', () => {
12
- console.warn('⚠️ Socket 断开连接')
13
- })
12
+ console.warn('⚠️ Socket 断开连接');
13
+ });
14
14
 
15
15
  // 接收后端推送的 HTML 内容
16
16
  socket.on('update', (htmlContent) => {
17
- console.log('📥 收到 update 事件')
17
+ console.log('📥 收到 update 事件');
18
18
  if (typeof onUpdate === 'function') {
19
- onUpdate(htmlContent)
19
+ onUpdate(htmlContent);
20
20
  }
21
- })
21
+ });
22
22
 
23
- return socket
23
+ return socket;
24
24
  }
package/styles/dark.css CHANGED
@@ -6,7 +6,12 @@ body {
6
6
  padding: 2rem;
7
7
  }
8
8
 
9
- h1, h2, h3, h4, h5, h6 {
9
+ h1,
10
+ h2,
11
+ h3,
12
+ h4,
13
+ h5,
14
+ h6 {
10
15
  color: #ffffff;
11
16
  border-bottom: 1px solid #333;
12
17
  }
@@ -16,7 +21,8 @@ a {
16
21
  text-decoration: none;
17
22
  }
18
23
 
19
- code, pre {
24
+ code,
25
+ pre {
20
26
  background-color: #2d2d2d;
21
27
  color: #f8f8f2;
22
28
  padding: 0.2em 0.4em;
@@ -35,7 +41,8 @@ table {
35
41
  width: 100%;
36
42
  }
37
43
 
38
- th, td {
44
+ th,
45
+ td {
39
46
  border: 1px solid #444;
40
47
  padding: 0.5em;
41
48
  }
@@ -7,7 +7,9 @@ body {
7
7
  color: #333;
8
8
  }
9
9
 
10
- h1, h2, h3 {
10
+ h1,
11
+ h2,
12
+ h3 {
11
13
  border-bottom: 1px solid #ddd;
12
14
  padding-bottom: 4px;
13
15
  }