juice-email-cli 2.3.4 → 2.3.5

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
@@ -351,34 +351,46 @@ edm/
351
351
 
352
352
  ## Windows 右键菜单
353
353
 
354
- ### 文件右键(.html / .htm
354
+ ### .html / .htm 文件右键
355
355
 
356
356
  ```
357
357
  📧 用 juice 生成邮件 HTML
358
- ├── 📄 作为模板,生成邮件 HTML → juice -f %1(后台执行)
359
- ├── 🧩 作为片段,拼接邮件 HTML → juice -s %1(交互选择模板)
360
- ├── 📦 查看资源列表 → juice view
361
- ├── 📋 浏览资源库 → juice view -i
358
+ ├── 📄 作为模板,生成邮件 HTML → juice -f %1(后台执行)
359
+ ├── 🧩 作为片段,拼接邮件 HTML → juice -s %1(交互选择模板)
360
+ ├── 📋 查看可用资源 → juice view
361
+ ├── 📦 拷贝全部资源 → juice init --all
362
+ ├── 📥 选择资源拷贝 → juice init
362
363
  └── 📂 打开 PowerShell ← 仅已安装 pwsh 时出现
363
364
  ```
364
365
 
365
- ### 文件右键(.yaml / .yml
366
+ ### .yaml / .yml 文件右键
366
367
 
367
368
  ```
368
369
  📧 用 juice 生成邮件 HTML
369
- ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c %1(交互选择品牌/模板/片段)
370
- ├── 📦 查看资源列表 → juice view
371
- ├── 📋 浏览资源库 → juice view -i
370
+ ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c %1(交互选择品牌/模板/片段)
371
+ ├── 📋 查看可用资源
372
+ ├── 📦 拷贝全部资源
373
+ ├── 📥 选择资源拷贝
372
374
  └── 📂 打开 PowerShell
373
375
  ```
374
376
 
375
- ### 文件夹 / 空白处右键
377
+ ### 文件夹右键
376
378
 
377
379
  ```
378
- 📧 juice 邮件工具
379
- ├── 📥 从资源库拷贝到此处 → juice init
380
- ├── 📦 查看资源列表 → juice view
381
- ├── 📋 浏览资源库 → juice view -i
380
+ 📧 juice 生成邮件 HTML
381
+ ├── 📋 查看可用资源
382
+ ├── 📦 拷贝全部资源
383
+ ├── 📥 选择资源拷贝
384
+ └── 📂 在此打开终端
385
+ ```
386
+
387
+ ### 空白处右键
388
+
389
+ ```
390
+ 📧 用 juice 生成邮件 HTML
391
+ ├── 📋 查看可用资源
392
+ ├── 📦 拷贝全部资源
393
+ ├── 📥 选择资源拷贝
382
394
  └── 📂 在此打开终端
383
395
  ```
384
396
 
package/bin/juice.js CHANGED
@@ -67,11 +67,13 @@ program
67
67
  juice --install (当前用户,无需管理员)
68
68
  juice --uninstall (卸载右键菜单)
69
69
 
70
- 菜单结构(所有文件类型统一):
70
+ 菜单结构:
71
71
  📧 用 juice 生成邮件 HTML
72
- ├── 📄 作为模板,生成邮件 HTML → juice -f %1(后台执行)
73
- ├── 🧩 作为片段,拼接邮件 HTML → juice -s %1(交互选择模板)
74
- ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c %1(交互选择品牌/模板/片段)
72
+ ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)
73
+ ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)
74
+ ├── 📋 查看可用资源 → juice view
75
+ ├── 📦 拷贝全部资源 → juice init --all
76
+ ├── 📥 选择资源拷贝 → juice init
75
77
  └── 📂 打开 PowerShell (仅已安装 pwsh 时出现)
76
78
 
77
79
  更多信息:https://gitee.com/siriussupreme/juice-cli
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juice-email-cli",
3
- "version": "2.3.4",
3
+ "version": "2.3.5",
4
4
  "description": "CLI tool to generate email-client-compatible HTML with juice (CSS inlining) + Mustache templating + minification",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -6,20 +6,30 @@
6
6
  * 使用 ExtendedSubCommandsKey 实现级联菜单(参考 WinRAR 和 Windows Defender 的实现)。
7
7
  * 子命令存储在 Classes 根下的共享容器中,各文件类型父菜单通过 ExtendedSubCommandsKey 引用。
8
8
  *
9
- * 与旧版 SubCommands + CommandStore 方案的区别:
10
- * - 不依赖 CommandStore(HKCU CommandStore 在某些 Windows 版本下不被正确解析)
11
- * - ExtendedSubCommandsKey 指向 Classes 根下的相对路径,子命令内联存储
12
- *
13
- * 菜单结构(按文件类型区分):
9
+ * 菜单结构:
14
10
  * .html / .htm:
15
11
  * 📧 用 juice 生成邮件 HTML
16
12
  * ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)
17
13
  * ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)
14
+ * ├── 📋 查看可用资源 → juice view
15
+ * ├── 📦 拷贝全部资源 → juice init --all
16
+ * ├── 📥 选择资源拷贝 → juice init
18
17
  * └── 📂 打开 PowerShell (可选)
19
18
  *
20
19
  * .yaml / .yml:
21
20
  * 📧 用 juice 生成邮件 HTML
22
- * └── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)
21
+ * ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)
22
+ * ├── 📋 查看可用资源
23
+ * ├── 📦 拷贝全部资源
24
+ * ├── 📥 选择资源拷贝
25
+ * └── 📂 打开 PowerShell (可选)
26
+ *
27
+ * 文件夹 / 空白处:
28
+ * 📧 用 juice 生成邮件 HTML
29
+ * ├── 📋 查看可用资源
30
+ * ├── 📦 拷贝全部资源
31
+ * ├── 📥 选择资源拷贝
32
+ * └── 📂 在此打开终端 (可选)
23
33
  */
24
34
 
25
35
  const { spawnSync } = require('child_process');
@@ -103,7 +113,6 @@ const SUBCMDS = {
103
113
  snippet: 'JuiceEmail.Snippet',
104
114
  config: 'JuiceEmail.WithConfig',
105
115
  viewEdm: 'JuiceEmail.ViewEdm',
106
- viewEdmInt:'JuiceEmail.ViewEdmInteractive',
107
116
  initEdm: 'JuiceEmail.InitEdm',
108
117
  pwsh: 'JuiceEmail.OpenPwsh',
109
118
  };
@@ -118,7 +127,6 @@ const SUB_CMDS_CONTAINER_BG = 'JuiceEmail.SubCommands.Bg';
118
127
 
119
128
  // ─── 旧版残留清理 ─────────────────────────────────────────────────────────────
120
129
 
121
- // 旧版 HKLM 父菜单路径(v1 使用 HKCR/HKLM,需管理员权限写入)
122
130
  const LEGACY_HKLM_ROOTS = [
123
131
  'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.html\\shell',
124
132
  'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.htm\\shell',
@@ -126,14 +134,9 @@ const LEGACY_HKLM_ROOTS = [
126
134
  'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.yml\\shell',
127
135
  ];
128
136
 
129
- // 旧版 CommandStore 子命令路径(v1-v2.1.12 使用 SubCommands + CommandStore 方案)
130
137
  const LEGACY_HKCU_CMDSTORE = 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell';
131
138
  const LEGACY_HKLM_CMDSTORE = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell';
132
139
 
133
- /**
134
- * 清理旧版本残留在 HKLM 的父菜单(v1 使用 HKCR/HKLM,需要管理员权限)
135
- * 没有管理员权限时静默失败,不影响后续 HKCU 写入
136
- */
137
140
  function cleanLegacyHklmParents() {
138
141
  if (!isWindows) return;
139
142
  for (const root of LEGACY_HKLM_ROOTS) {
@@ -141,28 +144,24 @@ function cleanLegacyHklmParents() {
141
144
  }
142
145
  }
143
146
 
144
- /**
145
- * 清理旧版本 CommandStore 子命令残留(HKCU + HKLM)
146
- */
147
147
  function cleanLegacyCommandStore() {
148
148
  if (!isWindows) return;
149
149
  for (const name of Object.values(SUBCMDS)) {
150
150
  regDelete(`${LEGACY_HKCU_CMDSTORE}\\${name}`);
151
151
  regDelete(`${LEGACY_HKLM_CMDSTORE}\\${name}`);
152
152
  }
153
+ // Also clean up old Dir CommandStore entries from v2.3.x SubCommands attempt
154
+ for (const name of ['JuiceEmailDir.View', 'JuiceEmailDir.CopyAll', 'JuiceEmailDir.CopyPick', 'JuiceEmailDir.Pwsh']) {
155
+ regDelete(`${LEGACY_HKCU_CMDSTORE}\\${name}`);
156
+ regDelete(`${LEGACY_HKLM_CMDSTORE}\\${name}`);
157
+ }
153
158
  }
154
159
 
155
160
  // ─── PowerShell 包装 ──────────────────────────────────────────────────────────
156
161
 
157
- /**
158
- * 为交互式命令包一层 PowerShell:
159
- * - 成功:窗口自动关闭
160
- * - 失败:窗口保持开启,显示重新执行的命令
161
- */
162
162
  function wrapInteractive(nodePath, scriptPath, cliArgs) {
163
163
  const node = nodePath.replace(/'/g, "''");
164
164
  const script = scriptPath.replace(/'/g, "''");
165
- // %1 由 Windows Explorer 在 CreateProcess 前展开为实际文件路径
166
165
  const ps = [
167
166
  `& '${node}' '${script}' ${cliArgs}`,
168
167
  `if ($LASTEXITCODE) {`,
@@ -200,122 +199,106 @@ function resolvePwsh() {
200
199
  /**
201
200
  * 向指定容器注册子命令
202
201
  * @param {string} containerPath - 容器完整注册表路径
203
- * @param {'html'|'yaml'} kind - 子命令集合类型
202
+ * @param {'html'|'yaml'|'dir'|'bg'} kind - 子命令集合类型
204
203
  */
205
204
  function registerSubCommands(containerPath, kind, nodePath, scriptPath, iconPath, pwshPath) {
205
+ const node = nodePath.replace(/'/g, "''");
206
+ const script = scriptPath.replace(/'/g, "''");
207
+
208
+ // Shared resource commands (added to all containers)
209
+ function regResourceCmds(dirPrefix) {
210
+ const cd = dirPrefix || '';
211
+
212
+ // 📋 查看可用资源
213
+ const vKey = `${containerPath}\\shell\\JuiceEmail.ViewRes`;
214
+ regAdd(vKey, 'MUIVerb', 'REG_SZ', '📋 查看可用资源');
215
+ regAdd(vKey, 'Icon', 'REG_SZ', iconPath);
216
+ const vPs = `${cd}& '${node}' '${script}' view; Write-Host ''; Read-Host 'Press Enter to close'`;
217
+ regAdd(`${vKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${vPs}"`);
218
+
219
+ // 📦 拷贝全部资源
220
+ const aKey = `${containerPath}\\shell\\JuiceEmail.CopyAll`;
221
+ regAdd(aKey, 'MUIVerb', 'REG_SZ', '📦 拷贝全部资源');
222
+ regAdd(aKey, 'Icon', 'REG_SZ', iconPath);
223
+ const aPs = `${cd}& '${node}' '${script}' init --all .; Write-Host ''; Read-Host 'Press Enter to close'`;
224
+ regAdd(`${aKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${aPs}"`);
225
+
226
+ // 📥 选择资源拷贝
227
+ const pKey = `${containerPath}\\shell\\JuiceEmail.CopyPick`;
228
+ regAdd(pKey, 'MUIVerb', 'REG_SZ', '📥 选择资源拷贝');
229
+ regAdd(pKey, 'Icon', 'REG_SZ', iconPath);
230
+ const pPs = `${cd}& '${node}' '${script}' init; Write-Host ''; Read-Host 'Press Enter to close'`;
231
+ regAdd(`${pKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${pPs}"`);
232
+ }
233
+
206
234
  if (kind === 'html') {
207
- // HTML:作为模板生成
208
235
  const genKey = `${containerPath}\\shell\\${SUBCMDS.generate}`;
209
236
  regAdd(genKey, 'MUIVerb', 'REG_SZ', '📄 作为模板,生成邮件 HTML');
210
237
  regAdd(genKey, 'Icon', 'REG_SZ', iconPath);
211
238
  regAdd(`${genKey}\\command`, '', 'REG_SZ', `"${nodePath}" "${scriptPath}" -f %1`);
212
239
 
213
- // HTML:作为片段拼接
214
240
  const snipKey = `${containerPath}\\shell\\${SUBCMDS.snippet}`;
215
241
  regAdd(snipKey, 'MUIVerb', 'REG_SZ', '🧩 作为片段,拼接邮件 HTML');
216
242
  regAdd(snipKey, 'Icon', 'REG_SZ', iconPath);
217
243
  regAdd(`${snipKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, '-s %1'));
218
- } else {
219
- // YAML:作为配置拼接
244
+
245
+ regResourceCmds(`Set-Location (Split-Path '%1'); `);
246
+
247
+ if (pwshPath) {
248
+ const pwshKey = `${containerPath}\\shell\\${SUBCMDS.pwsh}`;
249
+ regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 打开 PowerShell');
250
+ regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
251
+ regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
252
+ `"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`);
253
+ }
254
+ } else if (kind === 'yaml') {
220
255
  const cfgKey = `${containerPath}\\shell\\${SUBCMDS.config}`;
221
256
  regAdd(cfgKey, 'MUIVerb', 'REG_SZ', '⚙️ 作为配置,拼接邮件 HTML');
222
257
  regAdd(cfgKey, 'Icon', 'REG_SZ', iconPath);
223
258
  regAdd(`${cfgKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, '-c %1'));
224
- }
225
-
226
- // Common items for file-type containers (html + yaml)
227
- if (kind === 'html' || kind === 'yaml') {
228
- const viewKey = `${containerPath}\\shell\\${SUBCMDS.viewEdm}`;
229
- regAdd(viewKey, 'MUIVerb', 'REG_SZ', '📦 查看资源列表');
230
- regAdd(viewKey, 'Icon', 'REG_SZ', iconPath);
231
- regAdd(`${viewKey}\\command`, '', 'REG_SZ', wrapWithPause(nodePath, scriptPath, 'view'));
232
-
233
- const viewIntKey = `${containerPath}\\shell\\${SUBCMDS.viewEdmInt}`;
234
- regAdd(viewIntKey, 'MUIVerb', 'REG_SZ', '📋 浏览资源库');
235
- regAdd(viewIntKey, 'Icon', 'REG_SZ', iconPath);
236
- regAdd(`${viewIntKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, 'view -i'));
237
- }
238
259
 
239
- // pwsh 始终显示(可选)
240
- if (pwshPath) {
241
- const pwshKey = `${containerPath}\\shell\\${SUBCMDS.pwsh}`;
242
- regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 打开 PowerShell');
243
- regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
244
- regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
245
- `"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`);
246
- }
247
- }
260
+ regResourceCmds(`Set-Location (Split-Path '%1'); `);
248
261
 
249
- // ─── 非交互命令包装(始终暂停以便用户查看输出)─────────────────────────────────
250
-
251
- function wrapWithPause(nodePath, scriptPath, cliArgs) {
252
- const node = nodePath.replace(/'/g, "''");
253
- const script = scriptPath.replace(/'/g, "''");
254
- const ps = [
255
- `& '${node}' '${script}' ${cliArgs}`,
256
- `Write-Host ''`,
257
- `Read-Host 'Press Enter to close'`,
258
- ].join('; ');
259
- return `powershell.exe -Command "${ps}"`;
260
- }
261
-
262
- // ─── Directory / Background 菜单注册 ──────────────────────────────────────────
263
-
264
- /**
265
- * Register Directory and Background right-click menus using SubCommands
266
- * + CommandStore (same format as file-type menus for consistent UX).
267
- */
268
- function registerDirBgMenus(nodePath, scriptPath, iconPath, pwshPath) {
269
- const node = nodePath.replace(/'/g, "''");
270
- const script = scriptPath.replace(/'/g, "''");
271
-
272
- // CommandStore subcommand names
273
- const DIR_VIEW = 'JuiceEmailDir.View';
274
- const DIR_COPYALL = 'JuiceEmailDir.CopyAll';
275
- const DIR_COPYPICK= 'JuiceEmailDir.CopyPick';
276
- const DIR_PWSH = 'JuiceEmailDir.Pwsh';
277
-
278
- // Helper: register a CommandStore entry
279
- function regCmd(name, label, psCmd) {
280
- const cmdKey = `${HKCU_SHELL}\\${name}`;
281
- regAdd(cmdKey, 'MUIVerb', 'REG_SZ', label);
282
- regAdd(cmdKey, 'Icon', 'REG_SZ', iconPath);
283
- // Always pause so user can see output
284
- const fullPs = `${psCmd}; Write-Host ''; Read-Host 'Press Enter to close'`;
285
- regAdd(`${cmdKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${fullPs}"`);
286
- }
287
-
288
- // View: non-interactive, no directory dependency
289
- regCmd(DIR_VIEW, '📋 查看可用资源', `& '${node}' '${script}' view`);
290
-
291
- // CopyAll: copy entire EDM to clicked directory
292
- regCmd(DIR_COPYALL, '📦 拷贝全部资源', `& '${node}' '${script}' init --all '%V'`);
293
-
294
- // CopyPick: interactive, cd to directory first so output lands there
295
- regCmd(DIR_COPYPICK, '📥 选择资源拷贝', `Set-Location '%V'; & '${node}' '${script}' init`);
296
-
297
- // Pwsh
298
- if (pwshPath) {
299
- const pwshKey = `${HKCU_SHELL}\\${DIR_PWSH}`;
300
- regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 在此打开终端');
301
- regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
302
- regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
303
- `"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath '%V'"`);
304
- }
305
-
306
- // Build SubCommands string
307
- const subCmds = [DIR_VIEW, DIR_COPYALL, DIR_COPYPICK];
308
- if (pwshPath) subCmds.push(DIR_PWSH);
309
-
310
- // Parent keys: Directory + Directory\Background
311
- const roots = [
312
- `${HKCU_SHELL}\\Directory\\shell`,
313
- `${HKCU_SHELL}\\Directory\\Background\\shell`,
314
- ];
315
- for (const root of roots) {
316
- regAdd(`${root}\\${PARENT_KEY_NAME}`, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML');
317
- regAdd(`${root}\\${PARENT_KEY_NAME}`, 'Icon', 'REG_SZ', iconPath);
318
- regAdd(`${root}\\${PARENT_KEY_NAME}`, 'SubCommands', 'REG_SZ', subCmds.join(';'));
262
+ if (pwshPath) {
263
+ const pwshKey = `${containerPath}\\shell\\${SUBCMDS.pwsh}`;
264
+ regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 打开 PowerShell');
265
+ regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
266
+ regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
267
+ `"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`);
268
+ }
269
+ } else if (kind === 'dir' || kind === 'bg') {
270
+ // Directory / Background: cd to the target directory first
271
+ const dirRef = kind === 'dir' ? '%1' : '%V';
272
+ const cd = `Set-Location '${dirRef}'; `;
273
+
274
+ // 📋 查看可用资源
275
+ const vKey = `${containerPath}\\shell\\JuiceEmail.ViewRes`;
276
+ regAdd(vKey, 'MUIVerb', 'REG_SZ', '📋 查看可用资源');
277
+ regAdd(vKey, 'Icon', 'REG_SZ', iconPath);
278
+ const vPs = `${cd}& '${node}' '${script}' view; Write-Host ''; Read-Host 'Press Enter to close'`;
279
+ regAdd(`${vKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${vPs}"`);
280
+
281
+ // 📦 拷贝全部资源
282
+ const aKey = `${containerPath}\\shell\\JuiceEmail.CopyAll`;
283
+ regAdd(aKey, 'MUIVerb', 'REG_SZ', '📦 拷贝全部资源');
284
+ regAdd(aKey, 'Icon', 'REG_SZ', iconPath);
285
+ const aPs = `${cd}& '${node}' '${script}' init --all .; Write-Host ''; Read-Host 'Press Enter to close'`;
286
+ regAdd(`${aKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${aPs}"`);
287
+
288
+ // 📥 选择资源拷贝
289
+ const pKey = `${containerPath}\\shell\\JuiceEmail.CopyPick`;
290
+ regAdd(pKey, 'MUIVerb', 'REG_SZ', '📥 选择资源拷贝');
291
+ regAdd(pKey, 'Icon', 'REG_SZ', iconPath);
292
+ const pPs = `${cd}& '${node}' '${script}' init; Write-Host ''; Read-Host 'Press Enter to close'`;
293
+ regAdd(`${pKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${pPs}"`);
294
+
295
+ if (pwshPath) {
296
+ const pwshKey = `${containerPath}\\shell\\JuiceEmail.Pwsh`;
297
+ regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 在此打开终端');
298
+ regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
299
+ regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
300
+ `"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath '${dirRef}'"`);
301
+ }
319
302
  }
320
303
  }
321
304
 
@@ -329,7 +312,7 @@ async function registerContextMenu() {
329
312
 
330
313
  console.log(chalk.cyan('\n 注册 juice 右键菜单(当前用户,无需管理员权限)...\n'));
331
314
 
332
- // 清理所有旧版残留,避免新旧方案冲突
315
+ // 清理所有旧版残留
333
316
  cleanLegacyHklmParents();
334
317
  cleanLegacyCommandStore();
335
318
 
@@ -340,22 +323,27 @@ async function registerContextMenu() {
340
323
 
341
324
  let ok = true;
342
325
 
343
- // 先删除旧容器再重建,避免注册残留旧子命令(如旧版 HTML 容器的 WithConfig)
326
+ // 先删除旧容器再重建
344
327
  console.log(chalk.gray(' 清理旧版菜单...'));
345
328
  regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`);
346
329
  regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`);
347
330
  regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`);
348
331
  regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`);
332
+ // Clean up old individual Dir/Bg entries from v2.3.x
333
+ for (const root of [`${HKCU_SHELL}\\Directory\\shell`, `${HKCU_SHELL}\\Directory\\Background\\shell`]) {
334
+ regDelete(`${root}\\${PARENT_KEY_NAME}`);
335
+ for (const suffix of ['Init', 'View', 'Browse', 'Pwsh']) {
336
+ regDelete(`${root}\\JuiceEmail.${suffix}`);
337
+ }
338
+ }
349
339
 
350
- // HTML 容器:generate + snippet + pwsh
340
+ // HTML 容器
351
341
  console.log(chalk.gray(' 注册 .html 文件菜单...'));
352
- const htmlContainer = `${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`;
353
- registerSubCommands(htmlContainer, 'html', nodePath, scriptPath, iconPath, pwshPath);
342
+ registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`, 'html', nodePath, scriptPath, iconPath, pwshPath);
354
343
 
355
- // YAML 容器:config + pwsh
344
+ // YAML 容器
356
345
  console.log(chalk.gray(' 注册 .yaml 文件菜单...'));
357
- const yamlContainer = `${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`;
358
- registerSubCommands(yamlContainer, 'yaml', nodePath, scriptPath, iconPath, pwshPath);
346
+ registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`, 'yaml', nodePath, scriptPath, iconPath, pwshPath);
359
347
 
360
348
  // .html / .htm → 引用 HTML 容器
361
349
  for (const root of HTML_ROOTS) {
@@ -373,21 +361,22 @@ async function registerContextMenu() {
373
361
  regAdd(parentKey, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_YAML);
374
362
  }
375
363
 
376
- // Directory / Background → 子菜单(SubCommands + CommandStore)
364
+ // Directory / Background → 各自独立容器
377
365
  console.log(chalk.gray(' 注册文件夹/空白处菜单...'));
378
- // Clean up old individual entries from v2.3.x before registering new format
379
- for (const root of [`${HKCU_SHELL}\\Directory\\shell`, `${HKCU_SHELL}\\Directory\\Background\\shell`]) {
380
- regDelete(`${root}\\${PARENT_KEY_NAME}`);
381
- regDelete(`${root}\\JuiceEmail.Init`);
382
- regDelete(`${root}\\JuiceEmail.View`);
383
- regDelete(`${root}\\JuiceEmail.Browse`);
384
- regDelete(`${root}\\JuiceEmail.Pwsh`);
385
- }
386
- // Clean up old CommandStore entries for Dir menu (from any prior version)
387
- for (const name of ['JuiceEmailDir.View', 'JuiceEmailDir.CopyAll', 'JuiceEmailDir.CopyPick', 'JuiceEmailDir.Pwsh']) {
388
- regDelete(`${HKCU_SHELL}\\${name}`);
389
- }
390
- registerDirBgMenus(nodePath, scriptPath, iconPath, pwshPath);
366
+ registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`, 'dir', nodePath, scriptPath, iconPath, pwshPath);
367
+ registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`, 'bg', nodePath, scriptPath, iconPath, pwshPath);
368
+
369
+ // Directory → 引用 Dir 容器
370
+ const dirParent = `${HKCU_SHELL}\\Directory\\shell\\${PARENT_KEY_NAME}`;
371
+ ok = regAdd(dirParent, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
372
+ regAdd(dirParent, 'Icon', 'REG_SZ', iconPath);
373
+ regAdd(dirParent, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_DIR);
374
+
375
+ // Directory\Background 引用 Bg 容器
376
+ const bgParent = `${HKCU_SHELL}\\Directory\\Background\\shell\\${PARENT_KEY_NAME}`;
377
+ ok = regAdd(bgParent, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
378
+ regAdd(bgParent, 'Icon', 'REG_SZ', iconPath);
379
+ regAdd(bgParent, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_BG);
391
380
 
392
381
  if (!ok) {
393
382
  console.log(chalk.yellow(' ⚠ 部分注册表项写入失败,右键菜单可能不完整。\n'));
@@ -400,20 +389,22 @@ async function registerContextMenu() {
400
389
  ` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
401
390
  ` ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)\n` +
402
391
  ` ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)\n` +
403
- ` ├── 📦 查看资源列表 → juice view\n` +
404
- ` ├── 📋 浏览资源库 → juice view -i\n` +
392
+ ` ├── 📋 查看可用资源 → juice view\n` +
393
+ ` ├── 📦 拷贝全部资源 → juice init --all\n` +
394
+ ` ├── 📥 选择资源拷贝 → juice init\n` +
405
395
  (pwshPath ? ` └── 📂 打开 PowerShell\n` : '') +
406
396
  `\n ${chalk.bold('.yaml / .yml')} 文件右键:\n` +
407
397
  ` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
408
398
  ` ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)\n` +
409
- ` ├── 📦 查看资源列表 → juice view\n` +
410
- ` ├── 📋 浏览资源库 → juice view -i\n` +
399
+ ` ├── 📋 查看可用资源\n` +
400
+ ` ├── 📦 拷贝全部资源\n` +
401
+ ` ├── 📥 选择资源拷贝\n` +
411
402
  (pwshPath ? ` └── 📂 打开 PowerShell\n` : '') +
412
403
  `\n ${chalk.bold('文件夹 / 空白处')} 右键:\n` +
413
404
  ` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
414
- ` ├── 📋 查看可用资源 → juice view\n` +
415
- ` ├── 📦 拷贝全部资源 → juice init --all\n` +
416
- ` ├── 📥 选择资源拷贝 → juice init\n` +
405
+ ` ├── 📋 查看可用资源\n` +
406
+ ` ├── 📦 拷贝全部资源\n` +
407
+ ` ├── 📥 选择资源拷贝\n` +
417
408
  (pwshPath ? ` └── 📂 在此打开终端\n` : '') +
418
409
  '\n' +
419
410
  chalk.gray(' 注意:如菜单未出现,请重启文件资源管理器(explorer.exe)。\n')
@@ -432,44 +423,32 @@ async function unregisterContextMenu() {
432
423
 
433
424
  let removed = 0;
434
425
 
435
- // 清理 HKCU 父菜单(文件类型)
426
+ // 清理文件类型父菜单
436
427
  const allRoots = [
437
428
  ...HTML_ROOTS,
438
429
  ...YAML_ROOTS,
430
+ `${HKCU_SHELL}\\Directory\\shell`,
431
+ `${HKCU_SHELL}\\Directory\\Background\\shell`,
439
432
  ];
440
433
  for (const root of allRoots) {
441
434
  if (regDelete(`${root}\\${PARENT_KEY_NAME}`)) removed++;
442
435
  }
443
436
 
444
- // 清理 Directory / Background 父菜单 + 旧版独立条目
445
- const dirBgRoots = [
446
- `${HKCU_SHELL}\\Directory\\shell`,
447
- `${HKCU_SHELL}\\Directory\\Background\\shell`,
448
- ];
449
- // New parent key + old individual entries from v2.3.x
450
- const dirBgKeys = [PARENT_KEY_NAME, 'JuiceEmail.Init', 'JuiceEmail.View', 'JuiceEmail.Browse', 'JuiceEmail.Pwsh'];
451
- for (const root of dirBgRoots) {
452
- for (const key of dirBgKeys) {
453
- if (regDelete(`${root}\\${key}`)) removed++;
437
+ // 清理旧版 Dir/Bg 独立条目 (v2.3.x individual entries)
438
+ for (const root of [`${HKCU_SHELL}\\Directory\\shell`, `${HKCU_SHELL}\\Directory\\Background\\shell`]) {
439
+ for (const suffix of ['Init', 'View', 'Browse', 'Pwsh']) {
440
+ if (regDelete(`${root}\\JuiceEmail.${suffix}`)) removed++;
454
441
  }
455
442
  }
456
- // CommandStore entries for Dir menus
457
- for (const name of ['JuiceEmailDir.View', 'JuiceEmailDir.CopyAll', 'JuiceEmailDir.CopyPick', 'JuiceEmailDir.Pwsh']) {
458
- if (regDelete(`${HKCU_SHELL}\\${name}`)) removed++;
459
- }
460
443
 
461
- // 清理所有子命令容器(含旧版 Dir/Bg 容器)
462
- if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`)) removed++;
463
- if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`)) removed++;
464
- if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`)) removed++;
465
- if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`)) removed++;
466
- if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`)) removed++;
467
- if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`)) removed++;
468
-
469
- // 清理旧版共享容器(v2.1.14 之前只有一个容器)
444
+ // 清理所有容器(含旧版残留)
445
+ for (const container of [SUB_CMDS_CONTAINER_HTML, SUB_CMDS_CONTAINER_YAML, SUB_CMDS_CONTAINER_DIR, SUB_CMDS_CONTAINER_BG]) {
446
+ if (regDelete(`${HKCU_SHELL}\\${container}`)) removed++;
447
+ }
448
+ // 旧版共享容器 (v2.1.14)
470
449
  regDelete(`${HKCU_SHELL}\\JuiceEmail.SubCommands`);
471
450
 
472
- // 清理旧版残留(HKLM 父菜单 + CommandStore)
451
+ // 旧版残留(HKLM 父菜单 + CommandStore)
473
452
  cleanLegacyHklmParents();
474
453
  cleanLegacyCommandStore();
475
454