@v1hz/md2docx 2.7.0 → 2.7.1

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 CHANGED
@@ -23,35 +23,12 @@
23
23
  - **表格与图片题注** — "表 1"、"图 1:标题"
24
24
  - **图片尺寸限制** — 等比缩小超限图片
25
25
  - **Mermaid 渲染** — beautiful-mermaid + resvg-wasm → 高 DPI PNG
26
- - **Word 样式** — 受控语义化配置 / 完整底层样式 / 从 DOCX 提取
26
+ - **Word 样式** — 语义化配置 / 完整底层样式 / 从 DOCX 提取
27
27
  - **预设系统** — 可切换、可继承默认值的配置与样式预设
28
28
  - **Markdown 格式化** — 只运行预处理流水线
29
+ - **桌面图形界面** — Electron + Vue 3,支持文件拖放、批量转换、预设编辑、转换历史、自动更新
29
30
  - **集中缓存** — `~/.md2docx/`,工作目录保持干净
30
- - **双构建** — npm CLI + Windows 单文件 EXE
31
-
32
- ---
33
-
34
- ## 桌面应用
35
-
36
- 仓库包含基于 Electron + Vue 3 + Vite + Tailwind CSS 的桌面端(早期开发阶段)。应用图标以立体 Markdown 卡片、转换箭头和 Word 文档构成,在标题栏小尺寸与桌面大图标下保持一致识别。标题栏使用 `titleBarStyle: "hidden"` 自定义内容区域并在各平台保留原生窗口控制按钮。系统拖入的文件通过 preload 中的 `webUtils.getPathForFile()` 解析本地路径。桌面转换复用完整 CLI 流水线与预设,支持源文件目录、指定文件夹或每次询问三种输出位置,以及覆盖、自动重命名或每次询问三种同名文件策略。成功文件会从队列收入标题栏转换记录;点击记录按钮后,每个文件都可独立打开、定位或重新加入文件列表,之后按当前转换设置再次转换。设置以居中模态窗口展示,可检测 Pandoc,并通过可视化编辑器调整转换规则、图片与图表行为和语义化 Word 样式;打开时弹窗以短暂的淡入、上移动画进入,标题栏设置按钮同时显示蓝色激活状态,并遵守系统的“减少动态效果”偏好。内置 `default` 只读,自定义时保存为新的用户预设,用户预设可直接编辑。设置中的选择器使用应用内统一菜单,支持鼠标和完整键盘操作,不依赖平台原生网页下拉样式。Windows 安装版启动后会静默检查 GitHub Release 更新;发现版本时显示非阻塞通知条,下载期间可继续工作,完成后由用户选择重新启动。设置页也可手动检查;便携版不会尝试自动安装。复制、重命名和删除位于更多菜单,未保存的编辑在离开前会确认。偏好与记录仅保存在本机。旧版扁平桌面设置会继续生效,并在下次保存时迁移到共享格式。转换记录最多保留 30 条;`Ctrl/⌘ + Enter` 可直接开始转换。
37
-
38
- 主界面右侧始终提供转换设置侧边栏,即使文件列表为空也可提前调整。它与预设编辑器使用同一套字段和视觉样式;选择预设会重置侧边栏并同步默认预设,随后直接调整只影响桌面转换,不会改写预设。主界面右上角的固定侧边栏按钮或 `Ctrl+Alt+B` 可显示/隐藏它;按钮不会随侧边栏展开移动,设置与显示状态会自动保存在本机,并作为配置快照应用于下一批文件。开发模式为每次 `bun run dev` 使用独立的 Chromium 会话缓存,避免残留开发实例争用缓存目录。
39
-
40
- 主界面右上角另有纯图标编辑按钮,可在文件列表与 Markdown 文本编辑器之间切换。用户可直接粘贴文本、设置文档名称并使用当前侧边栏配置转换;文本由主进程安全物化为应用私有目录中的临时 Markdown 文件,再进入与文件转换相同的完整流水线。编辑按钮属于文件工作区,会在侧边栏展开时随工作区边缘左移,而侧边栏按钮保持固定。输出位置设置为“源文件目录”时,文本编辑器转换会弹出 DOCX 保存位置选择;从转换记录重新加入源文件时会自动返回文件列表。
41
-
42
- ```bash
43
- bun run build:electron # 构建主进程与 preload
44
- bun run electron # 构建 Electron 与前端并启动
45
- bun run dev # 启动前端开发服务器与 Electron
46
- bun run pack # 构建支持自动更新的 Windows 安装版
47
- bun run pack:portable # 构建不自动安装更新的便携版
48
- ```
49
-
50
- 开发模式会自动显示一次更新通知预览;关闭后可在设置底部点“预览更新”再次查看。预览下载仅模拟进度,不访问更新服务器,也不会退出或安装。
51
-
52
- ## AI 辅助
53
-
54
- [`SKILL.md`](SKILL.md) 供 AI 代码助手引用,使其正确使用 md2docx CLI。
31
+ - **双构建** — npm CLI + Windows 单文件 EXE + 桌面安装包
55
32
 
56
33
  ---
57
34
 
@@ -65,18 +42,22 @@ npm install -g @v1hz/md2docx
65
42
  npx @v1hz/md2docx report.md
66
43
  ```
67
44
 
45
+ Windows 桌面用户也可下载安装版或便携版(见 [Releases](https://github.com/WXY-V1hZ/md2docx/releases))。
46
+
47
+ ---
48
+
68
49
  ## 快速开始
69
50
 
70
51
  ```bash
71
- md2docx report.md # 仅路径
52
+ md2docx report.md # 仅路径,默认输出 ./report.docx
72
53
  md2docx -f report.md -o output.docx # 带选项(必须有 --file)
73
54
  md2docx -f report.md -c config.json --style-raw raw.json --style-config style-config.json
74
55
  md2docx preset save --name academic --config config.json
75
56
  md2docx preset use academic
76
57
  md2docx format -f report.md # 只格式化
77
- md2docx export config # 导出配置
58
+ md2docx export config # 导出默认配置
78
59
  md2docx export style-raw -f template.docx # 从 DOCX 提取样式
79
- md2docx clean # 清除缓存
60
+ md2docx clean # 清除预处理/资源/样式缓存
80
61
  ```
81
62
 
82
63
  所有写文件命令默认覆盖已有输出。
@@ -94,8 +75,6 @@ md2docx preset list|use <name>|save --name <name> [选项]
94
75
  md2docx clean
95
76
  ```
96
77
 
97
- ### 转换选项
98
-
99
78
  | 参数 | 说明 |
100
79
  | ----------------------- | ---------------------------- |
101
80
  | `<markdown>` | 位置参数,仅无其他选项时可用 |
@@ -104,11 +83,11 @@ md2docx clean
104
83
  | `-c, --config <path>` | 配置 JSON |
105
84
  | `--style-raw <path>` | 底层 Word 样式 JSON |
106
85
  | `--style-config <path>` | 语义化样式配置 JSON |
107
- | `-o, --output <path>` | DOCX 输出 |
86
+ | `-o, --output <path>` | DOCX 输出(format 为 `.md`) |
108
87
 
109
- 位置参数不能与 `--file` 或其他选项混用。`format --file` 必填,支持 `--preset`/`--config`/`--output`,不接受样式参数,不调用 Pandoc。
88
+ 位置参数不能与 `--file` 或其他选项混用。`format` 必须提供 `--file`,不接受样式参数,不调用 Pandoc。
110
89
 
111
- ### 默认输出
90
+ ### 默认输出路径
112
91
 
113
92
  ```text
114
93
  report.md → ./report.docx
@@ -121,23 +100,29 @@ export style-config → ./style-config.json
121
100
 
122
101
  ---
123
102
 
124
- ## 中间文件
103
+ ## 桌面应用
125
104
 
126
- ```
127
- ~/.md2docx/
128
- ├── settings.json + presets/ # 持久数据,clean 保留
129
- ├── preprocess/<basename>-<hash:12>/ # 预处理 Markdown + Mermaid PNG
130
- ├── resources/default/*.json + *.lua # 物化的内置资源
131
- └── style/<hash:16>.docx # reference DOCX 缓存
132
- ```
105
+ 仓库包含 Electron 桌面端(Windows 安装版支持自动更新)。主要功能:
106
+
107
+ - **文件管理**:拖入 `.md` 文件或粘贴 Markdown 文本进行转换,支持批量
108
+ - **转换侧边栏**(`Ctrl+Alt+B`):实时调整输出目录、命名策略、样式等配置
109
+ - **预设可视化编辑**:创建、编辑、管理预设,内置 `default` 只读,自定义时自动保存为新预设
110
+ - **转换记录**:最多 30 条,可打开输出、定位文件或重新转换
111
+ - **设置面板**:Pandoc 检测、输出设置、更新检查
133
112
 
134
- 预处理目录用输入绝对路径 SHA-256 前 12 位,不同目录的同名文件隔离。Pandoc 读取缓存 Markdown 时以原始 Markdown 目录为优先资源搜索路径。
113
+ ```bash
114
+ bun run build:electron # 构建主进程与 preload
115
+ bun run electron # 构建并启动桌面端
116
+ bun run dev # 开发模式(隔离 Chromium 缓存)
117
+ bun run pack # 构建 Windows NSIS 安装版(含自动更新)
118
+ bun run pack:portable # 构建便携版
119
+ ```
135
120
 
136
121
  ---
137
122
 
138
123
  ## 配置
139
124
 
140
- 内置配置来自 `config/default/config.json`(`config/config.schema.json` 提供校验)。推荐先导出再编辑:
125
+ 内置配置来自 `config/default/config.json`(`config/config.schema.json` 提供校验),推荐先导出再编辑:
141
126
 
142
127
  ```bash
143
128
  md2docx export config
@@ -147,21 +132,21 @@ md2docx -f report.md -c config.json
147
132
  | 配置项 | 说明 | 默认值 |
148
133
  | --------------------------------------------------------- | ----------------------------------------------- | -------------------------------------- |
149
134
  | `detectTitle.enabled / strategy` | 自动设置标题 / first-h1/single-h1/filename/none | `true` / `"first-h1"` |
150
- | `normalizeHeadings.enabled` | 修正标题层级 | `true` |
151
135
  | `numberHeadings.{enabled,detectExisting,useBuiltinRules}` | 标题编号 / 剥离已有编号 | `true` / `true` / `true` |
152
136
  | `figureCaption.{enabled,format,separator}` | 图片题注 / `"图 {n}"` / `":"` | `true` |
153
137
  | `tableCaption.{enabled,format,separator}` | 表格题注 / `"表 {n}"` / `" "` | `true` |
154
138
  | `renderMermaid.{enabled,theme,density}` | Mermaid 渲染 / 主题 / DPI(≥72) | `true` / `"tokyo-night-light"` / `200` |
155
139
  | `imageSize.{enabled,maxWidthCm,maxHeightCm}` | 图片尺寸限制(厘米) | `true` / `12` / `12` |
156
140
  | `removeThematicBreaks.enabled` | 移除分隔符行 | `true` |
141
+ | `normalizeHeadings.enabled` | 修正标题层级 | `true` |
157
142
 
158
- 图片尺寸通过 Lua filter 在 Pandoc 阶段等比缩小,不放大小图。Markdown 已显式设 width/height 时不应用。单图读取失败时警告并继续。
143
+ 图片尺寸限制:超限等比缩小,不放大小图;已显式设 width/height 时不应用;读取失败时警告并继续。
159
144
 
160
145
  ---
161
146
 
162
147
  ## 样式定制
163
148
 
164
- 受控语义化配置(`config/default/style-config.json`)开放以下白名单选项:
149
+ 语义化配置(`config/default/style-config.json`)开放常用 Word 样式白名单:
165
150
 
166
151
  - `body.firstLineIndent` — 正文首行缩进
167
152
  - `body.lineSpacing` — 行距倍数(如 `1.5`)
@@ -171,7 +156,7 @@ md2docx -f report.md -c config.json
171
156
  - `inlineCode.background` — 行内代码背景
172
157
  - `codeBlock.border` — 代码块外框
173
158
 
174
- 字段缺失继承底层样式;`true` 写入完整效果;`false` 写入 Word 关闭值。输入组合规则:
159
+ 输入组合:
175
160
 
176
161
  | 参数 | 行为 |
177
162
  | ------------------- | ----------------------------- |
@@ -183,12 +168,11 @@ md2docx -f report.md -c config.json
183
168
  ```bash
184
169
  md2docx export style-config
185
170
  md2docx -f report.md --style-config style-config.json
186
- # 从现有 DOCX 提取样式
187
171
  md2docx export style-raw -f template.docx
188
172
  md2docx -f report.md --style-raw template_style-raw.json
189
173
  ```
190
174
 
191
- 完整设计见 [`docs/style-config-design.md`](docs/style-config-design.md)。
175
+ 字段缺失继承底层样式;`true` 写入完整效果;`false` 写入 Word 关闭值。详见 [`docs/style-config-design.md`](docs/style-config-design.md)。
192
176
 
193
177
  ---
194
178
 
@@ -200,7 +184,21 @@ Markdown → AST → addTitle → removeThematicBreaks → normalizeHeadings
200
184
  → 序列化 → reference DOCX → Pandoc + Lua filter → DOCX
201
185
  ```
202
186
 
203
- `removeThematicBreaks` 在标题处理之前;`numberTables` 在 Mermaid 渲染之前;`renderMermaid` 在 `numberPictures` 之前。
187
+ 顺序约束:`removeThematicBreaks` 在标题处理前;`numberTables` 在 Mermaid 前;`renderMermaid` 在 `numberPictures` 前。
188
+
189
+ ---
190
+
191
+ ## 中间文件
192
+
193
+ ```
194
+ ~/.md2docx/
195
+ ├── settings.json + presets/ # 持久数据,clean 保留
196
+ ├── preprocess/<basename>-<hash:12>/ # 预处理 Markdown + Mermaid PNG
197
+ ├── resources/default/*.json + *.lua # 物化的内置资源
198
+ └── style/<hash:16>.docx # reference DOCX 缓存
199
+ ```
200
+
201
+ 预处理目录使用输入路径 SHA-256 前 12 位,同名文件在不同目录下隔离。
204
202
 
205
203
  ---
206
204
 
@@ -211,24 +209,20 @@ Markdown → AST → addTitle → removeThematicBreaks → normalizeHeadings
211
209
  ```bash
212
210
  git clone https://github.com/WXY-V1hZ/md2docx.git && cd md2docx
213
211
  bun install
214
-
215
212
  bun run src/index.ts report.md # 从源码运行
216
213
  bun test # 全部测试
217
214
  bun check # tsc + oxlint + oxfmt
218
-
219
215
  bun run build # npm 构建 → dist/index.js + WASM
220
216
  bun run build:exe # Windows 单文件 → dist/md2docx.exe
221
217
  ```
222
218
 
223
- 两种构建互斥(先 `clean:dist`)。`prepack` 自动执行 `bun run build`。
224
-
225
219
  ---
226
220
 
227
221
  ## 常见问题
228
222
 
229
223
  **找不到 Pandoc** — 确认 `pandoc --version` 可用,从 [pandoc.org](https://pandoc.org/installing.html) 安装并重启终端。
230
224
 
231
- **跨目录转换图片缺失** — 相对路径以原始 Markdown 文件为基准解析,顺序优先于调用目录。检查路径大小写、文件存在性和 URL 编码。
225
+ **跨目录转换图片缺失** — 相对路径以原始 Markdown 文件为基准解析。检查路径大小写、文件存在性和 URL 编码。
232
226
 
233
227
  ---
234
228
 
package/dist/index.js CHANGED
@@ -128472,7 +128472,7 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
128472
128472
  // package.json
128473
128473
  var package_default = {
128474
128474
  name: "@v1hz/md2docx",
128475
- version: "2.7.0",
128475
+ version: "2.7.1",
128476
128476
  description: "基于 pandoc 的 Markdown 转 Word 工具,自动格式化 Markdown,支持自定义样式,AI 友好,用户友好。",
128477
128477
  keywords: [
128478
128478
  "converter",
@@ -128603,8 +128603,8 @@ function createProgram(version, actions) {
128603
128603
  presetSaveCommand.exitOverride();
128604
128604
  const formatCommand = program2.command("format").description("预处理并格式化 Markdown").helpOption("-h, --help", "显示帮助").requiredOption("-f, --file <path>", "Markdown 文件").option("--preset <name>", "使用指定预设中的 Markdown 处理配置").option("-c, --config <path>", "自定义配置文件").option("-o, --output <path>", "输出 Markdown 文件").action(actions.format);
128605
128605
  formatCommand.exitOverride();
128606
- const cleanCommand = program2.command("clean").description("清除 ~/.md2docx 中的预处理文件和样式缓存").helpOption("-h, --help", "显示帮助").action(async () => {
128607
- await actions.clean();
128606
+ const cleanCommand = program2.command("clean").description("清除 ~/.md2docx 中的预处理文件和样式缓存").option("--all", "清除所有用户数据(预设、设置和缓存)").helpOption("-h, --help", "显示帮助").action(async (options) => {
128607
+ await actions.clean({ all: options.all ?? false });
128608
128608
  });
128609
128609
  cleanCommand.exitOverride();
128610
128610
  return program2;
@@ -128637,9 +128637,7 @@ function styleTemplateDocx(hash) {
128637
128637
  }
128638
128638
 
128639
128639
  // src/commands/clean.ts
128640
- function cleanIntermediateFiles(paths = { targetDir: TMP_DIR, homeDir: homedir2() }) {
128641
- const home = resolve2(paths.homeDir);
128642
- const target = resolve2(paths.targetDir);
128640
+ function validateTarget(target, home) {
128643
128641
  const expected = resolve2(home, ".md2docx");
128644
128642
  if (target !== expected || dirname(target) !== home) {
128645
128643
  throw new Error(`拒绝清理非预期目录:${target}`);
@@ -128651,6 +128649,18 @@ function cleanIntermediateFiles(paths = { targetDir: TMP_DIR, homeDir: homedir2(
128651
128649
  if (lstatSync(target).isSymbolicLink()) {
128652
128650
  throw new Error(`拒绝清理符号链接目录:${target}`);
128653
128651
  }
128652
+ }
128653
+ function cleanIntermediateFiles(paths = {}) {
128654
+ const home = resolve2(paths.homeDir ?? homedir2());
128655
+ const target = resolve2(paths.targetDir ?? TMP_DIR);
128656
+ validateTarget(target, home);
128657
+ if (!existsSync(target))
128658
+ return;
128659
+ if (paths.all) {
128660
+ rmSync(target, { recursive: true, force: true });
128661
+ console.log(`已清除所有用户数据:${target}`);
128662
+ return;
128663
+ }
128654
128664
  for (const name2 of ["preprocess", "resources", "style"]) {
128655
128665
  const cachePath = resolve2(target, name2);
128656
128666
  if (dirname(cachePath) !== target)
@@ -148017,9 +148027,16 @@ async function convertMarkdown(options, overrides = {}) {
148017
148027
  ...existsSync7(inlineCodeFilter) ? [`--lua-filter=${inlineCodeFilter}`] : [],
148018
148028
  ...imageSizeArgs
148019
148029
  ];
148020
- const { exitCode, stderr } = await runProcess("pandoc", pandocArgs);
148021
- if (exitCode !== 0) {
148022
- throw new Error(`pandoc 转换失败 (exit code ${exitCode}):${stderr.trim()}`);
148030
+ try {
148031
+ const { exitCode, stderr } = await runProcess("pandoc", pandocArgs);
148032
+ if (exitCode !== 0) {
148033
+ throw new Error(`pandoc 转换失败 (exit code ${exitCode}):${stderr.trim()}`);
148034
+ }
148035
+ } catch (error) {
148036
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
148037
+ throw new Error("未找到 pandoc,请安装 pandoc (https://pandoc.org/installing.html) " + "并将其添加到系统 PATH 环境变量后重试");
148038
+ }
148039
+ throw error;
148023
148040
  }
148024
148041
  console.log(`已生成:${output}`);
148025
148042
  }
@@ -151568,9 +151568,16 @@ async function convertMarkdown(options, overrides = {}) {
151568
151568
  ...existsSync6(inlineCodeFilter) ? [`--lua-filter=${inlineCodeFilter}`] : [],
151569
151569
  ...imageSizeArgs
151570
151570
  ];
151571
- const { exitCode, stderr } = await runProcess("pandoc", pandocArgs);
151572
- if (exitCode !== 0) {
151573
- throw new Error(`pandoc 转换失败 (exit code ${exitCode}):${stderr.trim()}`);
151571
+ try {
151572
+ const { exitCode, stderr } = await runProcess("pandoc", pandocArgs);
151573
+ if (exitCode !== 0) {
151574
+ throw new Error(`pandoc 转换失败 (exit code ${exitCode}):${stderr.trim()}`);
151575
+ }
151576
+ } catch (error) {
151577
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
151578
+ throw new Error("未找到 pandoc,请安装 pandoc (https://pandoc.org/installing.html) " + "并将其添加到系统 PATH 环境变量后重试");
151579
+ }
151580
+ throw error;
151574
151581
  }
151575
151582
  console.log(`已生成:${output}`);
151576
151583
  }
@@ -152265,6 +152272,10 @@ function registerIpcHandlers() {
152265
152272
  removeHistoryRecord(recordId);
152266
152273
  });
152267
152274
  ipcMain.handle("convert:start", async (event, value2) => {
152275
+ const pandoc = await detectPandoc();
152276
+ if (!pandoc.available) {
152277
+ throw new Error("PANDOC_NOT_FOUND");
152278
+ }
152268
152279
  const request = await applyDesktopOutputSettings(parseConversionRequest(value2));
152269
152280
  const records = createRecordsFromRequest(request);
152270
152281
  const history = loadHistory();
@@ -152286,19 +152297,7 @@ function registerIpcHandlers() {
152286
152297
  ipcMain.handle("settings:save", (_event, value2) => saveDesktopSettings(value2));
152287
152298
  ipcMain.handle("conversion-settings:load", () => loadDesktopConversionSettings());
152288
152299
  ipcMain.handle("conversion-settings:save", (_event, value2) => saveDesktopConversionSettings(value2));
152289
- ipcMain.handle("pandoc:detect", async () => {
152290
- const executable = findExecutableOnPath("pandoc");
152291
- if (!executable)
152292
- return { available: false, version: null, path: null };
152293
- try {
152294
- const { stdout } = await runCommand(executable, ["--version"]);
152295
- const firstLine = stdout.trim().split(/\r?\n/, 1)[0] ?? "";
152296
- const version = /^pandoc\s+(.+)$/i.exec(firstLine)?.[1]?.trim() || firstLine || null;
152297
- return { available: true, version, path: executable };
152298
- } catch {
152299
- return { available: false, version: null, path: null };
152300
- }
152301
- });
152300
+ ipcMain.handle("pandoc:detect", async () => detectPandoc());
152302
152301
  ipcMain.handle("dialog:selectFolder", async () => {
152303
152302
  if (!mainWindow)
152304
152303
  return null;
@@ -152349,6 +152348,19 @@ function runCommand(command, args, timeoutMs = 5000) {
152349
152348
  });
152350
152349
  });
152351
152350
  }
152351
+ async function detectPandoc() {
152352
+ const executable = findExecutableOnPath("pandoc");
152353
+ if (!executable)
152354
+ return { available: false, version: null, path: null };
152355
+ try {
152356
+ const { stdout } = await runCommand(executable, ["--version"]);
152357
+ const firstLine = stdout.trim().split(/\r?\n/, 1)[0] ?? "";
152358
+ const version = /^pandoc\s+(.+)$/i.exec(firstLine)?.[1]?.trim() || firstLine || null;
152359
+ return { available: true, version, path: executable };
152360
+ } catch {
152361
+ return { available: false, version: null, path: null };
152362
+ }
152363
+ }
152352
152364
  function findExecutableOnPath(command) {
152353
152365
  const pathEntries = process.env.PATH?.split(delimiter) ?? [];
152354
152366
  const extensions = process.platform === "win32" ? process.env.PATHEXT?.split(";") ?? [".EXE", ".CMD", ".BAT"] : [""];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v1hz/md2docx",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "基于 pandoc 的 Markdown 转 Word 工具,自动格式化 Markdown,支持自定义样式,AI 友好,用户友好。",
5
5
  "keywords": [
6
6
  "converter",