dsh-long-plugins 2.4.5 → 2.5.0

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/client/client.js CHANGED
@@ -3445,7 +3445,6 @@ window.__ModuleLoader__.load({
3445
3445
  // 「dsh-long」设置区:各模块开关(独立、移动端干净),避免把开关塞进 RA-Span
3446
3446
  const DSH_LONG_MODULES = [
3447
3447
  ['glass', 'RA-Span'],
3448
- ['turnRuler', '会话导航(轮次刻尺)'],
3449
3448
  ['uploadAttach', '附件上传(回形针)'],
3450
3449
  ['uploadDragDrop', '附件拖放上传'],
3451
3450
  ['uploadPaste', '附件粘贴上传'],
@@ -3935,7 +3934,6 @@ window.__ModuleLoader__.load({
3935
3934
  try {
3936
3935
  const tk = dshDark() ? 'dark' : 'light'
3937
3936
  root.setAttribute('data-dsh-theme', tk)
3938
- root.setAttribute('data-dsh-turnruler', ((c.modules && c.modules.turnRuler) !== false) ? 'on' : 'off')
3939
3937
  window.__dshLongModules = c.modules || {}
3940
3938
  try { localStorage.setItem('dsh-long:modules', JSON.stringify(window.__dshLongModules)) } catch (e) {}
3941
3939
  const bt = bgTint(tk)
@@ -4036,7 +4034,6 @@ window.__ModuleLoader__.load({
4036
4034
  ...tokenUsagePlugin.inject,
4037
4035
  ...mobilePlugin.inject,
4038
4036
  ...workspaceFilesPlugin.inject,
4039
- ...turnRulerPlugin.inject,
4040
4037
  ...glassPlugin.inject,
4041
4038
  ]))
4042
4039
 
@@ -4074,7 +4071,6 @@ window.__ModuleLoader__.load({
4074
4071
  safeApply('token-usage', (c) => tokenUsagePlugin.apply(c)) // 余额 chip 开关在其内部; MOBILE_CSS 始终注入
4075
4072
  if (modEnabled('mobile')) safeApply('mobile-hamburger', (c) => mobilePlugin.apply(c))
4076
4073
  if (modEnabled('workspace')) safeApply('workspace-files', (c) => workspaceFilesPlugin.apply(c))
4077
- if (modEnabled('turnRuler')) safeApply('turn-ruler', (c) => turnRulerPlugin.apply(c))
4078
4074
  if (modEnabled('glass')) safeApply('glass', (c) => glassPlugin.apply(c))
4079
4075
  }
4080
4076
 
package/dsh.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-long-plugins",
3
3
  "description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, Markdown-to-Word (md2docx), and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Markdown 转 Word(md2docx)、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。",
4
- "version": "2.4.5",
4
+ "version": "2.4.6",
5
5
  "entry": {
6
6
  "name": "dsh-long-plugins",
7
7
  "inject": [
package/lib/index.js CHANGED
@@ -1134,6 +1134,10 @@ export function createHandlers(options = {}) {
1134
1134
  // Markdown → 直接渲染成 HTML(真实效果),失败回退源码文本。
1135
1135
  editable = true; rawText = buffer.toString("utf8");
1136
1136
  body = `<article class="md">${markdownToHtml(rawText)}</article>`;
1137
+ } else if (/\.(html?|xhtml)$/i.test(name)) {
1138
+ // HTML/HTM → 直接渲染成实际效果(iframe + srcdoc, 保留其样式/脚本)
1139
+ editable = true; rawText = buffer.toString("utf8");
1140
+ body = `<iframe class="html" srcDoc="${escapeHtml(rawText)}"></iframe>`;
1137
1141
  } else if (/\.(json|ya?ml|py|js|mjs|cjs|ts|sh|css|html?|xml|csv|ini|conf|env|toml|sql|rs|go|c|h|cpp|java|kt|swift|rb|php|vue|jsx|tsx)$/i.test(name)) {
1138
1142
  editable = true; rawText = buffer.toString("utf8");
1139
1143
  body = `<pre class="text">${escapeHtml(rawText)}</pre>`;
@@ -2553,6 +2557,7 @@ function previewPageHtml(name, rel, size, downloadHref, body, inlineHref = "", e
2553
2557
  body.maximized .md { max-width:none; }
2554
2558
  .image { max-width:100%; border-radius:8px; }
2555
2559
  .pdf { display:block; width:100%; height:86vh; border:1px solid var(--lp-border); border-radius:8px; background:#fff; }
2560
+ .html { display:block; width:100%; height:86vh; border:1px solid var(--lp-border); border-radius:8px; background:#fff; }
2556
2561
  .office { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
2557
2562
  .unsupported { color:#f59e0b; text-align:center; padding:40px 0; }
2558
2563
  /* 放大模式:顶栏常驻(固定悬浮),内容占满视口、PDF 全高 */
@@ -2560,6 +2565,7 @@ function previewPageHtml(name, rel, size, downloadHref, body, inlineHref = "", e
2560
2565
  body.maximized .content { max-width:none; padding:0; margin:0; height:100vh; padding-top:53px; }
2561
2566
  body.maximized .md { padding:24px 32px 48px; max-width:820px; margin:0 auto; }
2562
2567
  body.maximized .pdf { height:calc(100vh - 53px); border:none; border-radius:0; }
2568
+ body.maximized .html { height:calc(100vh - 53px); border:none; border-radius:0; }
2563
2569
  body.maximized .text { height:calc(100vh - 53px); border:none; border-radius:0; overflow:auto; }
2564
2570
  body.maximized .office { height:calc(100vh - 53px); overflow:auto; }
2565
2571
  body.maximized .image { max-width:100vw; max-height:100vh; object-fit:contain; }
@@ -2644,72 +2650,6 @@ export async function apply(ctx, config = {}) {
2644
2650
 
2645
2651
  const handlers = createHandlers({ trustedHosts, onError, excludedWorkspaceNames: config.excludedWorkspaceNames });
2646
2652
 
2647
- // ---- md2docx 工具: Markdown → Word (.docx, 带页码) ----
2648
- // 调用 md2docx.py (pandoc-free, python-docx + 页脚 PAGE 字段)。
2649
- // 默认脚本随插件包分发(lib/md2docx.py),换机器也可靠;可用 config.md2docxScript 覆盖。
2650
- const PACKAGE_DIR = dirname(fileURLToPath(import.meta.url));
2651
- const MD2DOCX_SCRIPT = resolve(config.md2docxScript ?? join(PACKAGE_DIR, "md2docx.py"));
2652
- const runScript = (script, args) => new Promise((resolvePromise) => {
2653
- const child = spawn("python3", [script, ...args], { stdio: ["ignore", "pipe", "pipe"] });
2654
- let stdout = "", stderr = "";
2655
- child.stdout.on("data", (d) => { stdout += d.toString(); });
2656
- child.stderr.on("data", (d) => { stderr += d.toString(); });
2657
- child.on("error", (error) => resolvePromise({ ok: false, error: String(error), stdout, stderr }));
2658
- child.on("close", (code) => resolvePromise({ ok: code === 0, code, stdout, stderr }));
2659
- });
2660
- ctx.tools.register(defineTool({
2661
- name: "md2docx",
2662
- description: "Convert a Markdown file to a styled Word (.docx) document with a page-number footer. Ships a bundled python-docx script (lib/md2docx.py) that renders headings, tables, bold/italic, and lists; the docx includes a footer '第 N 页' field that updates when opened in Word or exported to PDF. Requires python3 and python-docx installed on the host. Override the script path via config.md2docxScript if needed.",
2663
- parameters: {
2664
- input: {
2665
- type: "string",
2666
- required: true,
2667
- description: "Absolute path to the input .md file."
2668
- },
2669
- output: {
2670
- type: "string",
2671
- description: "Optional absolute path for the output .docx. Defaults to the input path with a .docx extension."
2672
- }
2673
- },
2674
- output: {
2675
- schema: {
2676
- type: "object",
2677
- additionalProperties: false,
2678
- properties: {
2679
- ok: { type: "boolean", required: true },
2680
- docxPath: { type: "string" },
2681
- error: { type: "string" }
2682
- }
2683
- },
2684
- render: (_args, value) => [{
2685
- type: "text",
2686
- text: value && value.ok === true
2687
- ? `已生成 Word 文档:${value.docxPath}\n(含页码页脚,Word/另存 PDF 时自动更新)`
2688
- : `md2docx 失败:${value?.error ?? "未知错误"}`
2689
- }]
2690
- },
2691
- // 声明交付物:DSH 从 presentCall 的 locations 识别本工具产出的文件,
2692
- // 从而把 docx 渲染成可点击的交付物卡片(消息里的文件引用也能打开)。
2693
- presentCall: (args) => {
2694
- const inPath = resolve(String(args.input ?? ""));
2695
- const outPath = args.output ? resolve(String(args.output)) : inPath.replace(/\.md$/i, ".docx");
2696
- return {
2697
- card: "generic",
2698
- title: "md2docx",
2699
- kind: "edit",
2700
- locations: [{ path: outPath }]
2701
- };
2702
- },
2703
- async execute(args) {
2704
- const inPath = resolve(String(args.input ?? ""));
2705
- const outPath = args.output ? resolve(String(args.output)) : inPath.replace(/\.md$/i, ".docx");
2706
- const result = await runScript(MD2DOCX_SCRIPT, [inPath, outPath]);
2707
- if (!result.ok) {
2708
- return { ok: false, error: (result.stderr || result.stdout || String(result.error)).trim() || `md2docx failed (exit ${result.code})` };
2709
- }
2710
- return { ok: true, docxPath: outPath };
2711
- }
2712
- }));
2713
2653
 
2714
2654
  await sweepUploadTemps(handlers.root);
2715
2655
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-long-plugins",
3
- "version": "2.4.5",
4
- "description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, Markdown-to-Word (md2docx), and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Markdown 转 Word(md2docx)、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。",
3
+ "version": "2.5.0",
4
+ "description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。Office 读取/生成已独立到 dsh-office-reader。",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "lib/index.js",
@@ -12,7 +12,6 @@
12
12
  },
13
13
  "files": [
14
14
  "lib",
15
- "lib/md2docx.py",
16
15
  "client",
17
16
  "!client/*.bak",
18
17
  "cordis.patch.yml",
@@ -91,6 +91,29 @@ fi
91
91
  9. 设置面板「技能管理」「上传文件」「输出文件」的 🔍 搜索框:点击后弹出框紧贴按钮下方、滚动不脱离。
92
92
  10. **「技能管理」区(全局技能 / 工作区技能两 tab)**:全局技能列 `<DSH_HOME>/skills`;工作区技能扫描工作区根下各子目录的 `.dsh/skills`(无 `.dsh/skills` 则该工作区组为空)。若该区不出现/工作区技能为空,可能 DSH `settings.section` 槽或工作区结构变化——复核。
93
93
 
94
+ ## 插件依赖 DSH 内部接口(升级脆弱清单)⚠️
95
+ dsh-long-plugins 大量依赖 DSH 内部接口/结构,DSH 升级改动后这些会**静默失效**(不注册/不显示/报错)。升级后逐项复核:
96
+
97
+ | 依赖 | 用途 | 升级失效表现 |
98
+ |---|---|---|
99
+ | `settings.section` 槽 | 上传文件/输出文件/技能管理/dsh-long/RA-Span 设置区 | 对应设置区不出现 |
100
+ | `conversation.composer.dock` 槽 | 余额 chip | 余额不显示 |
101
+ | `conversation.input.dock` 槽 | 待发送文件栏、拖放/粘贴上传 | 待发送栏/拖放失效 |
102
+ | `conversation.input.left` 槽 | 附件上传(回形针) | 回形针消失 |
103
+ | `conversation.session.header.actions` 槽 | 会话历史/工作区按钮 | 按钮消失 |
104
+ | `shell.overlay` 槽 | 文件浏览器浮层 | 文件浮层打不开 |
105
+ | `data-slot-conversation="..."` 属性 | 余额 chip / dock 样式定位 | 对应样式失效 |
106
+ | `ctx.webServer.register` | 所有路由(上传/工作区/技能/预览/glass/补丁状态) | 路由 404/无响应 |
107
+ | `ctx.inputTriggers.registerSource` | 文件引用 codec(`@`) | 附件引用失效 |
108
+ | `ctx.locale.register` | 多语言 | 文案不翻译 |
109
+ | `window.__ModuleLoader__.load` | 插件客户端加载 | 插件整个不加载 |
110
+ | `useSessions` / 会话 store | 工作区按钮拿 cwd / 当前工作区 | 工作区按钮/当前工作区失效 |
111
+ | 混淆类名 `wSkVaW_/uV2eYG_/o3BgMG_/CY-8Ka_/QWLzLg_/M8wy4a_/lXshSW_/7yHdaG_/VOzbGW_/p-xYUq_/osXY9a_` | RA-Span 玻璃观感/排版 | 观感/配色/排版错(见"RA-Span 升级后需重新校准") |
112
+ | DSH 设置面板 DOM(`VOzbGW` 面板) | 各设置区在其内渲染 | 设置区排版乱 |
113
+ | DSH 核心 `dsh-client-connection` | 心跳补丁(补丁3) | 补丁被覆盖→心跳失效(需重打) |
114
+
115
+ > 处置:某项失效 → 让用户右键抓新元素 class/槽名/接口,更新插件对应选择器/槽名/调用;或集成 DSH 新 API。**稳定项**:插件自己的路由表、`data-slot` 状态栏(部分)、注入 `<head>` 的 CSS(部分)。
116
+
94
117
  ## 硬性安全边界(必须遵守)
95
118
  - **不主动 push / 打 tag / 发 Release**;需要发布版本时停下用 ask_user_question 等确认。
96
119
  - 升级会重启 DSH 服务,可能中断当前会话——先向用户说明再执行。
package/lib/md2docx.py DELETED
@@ -1,210 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """Generic Markdown -> styled .docx converter with a page-number footer.
4
-
5
- Usage:
6
- python3 md2docx.py <input.md> [output.docx]
7
-
8
- If output.docx is omitted, it defaults to <input-name>.docx next to the input.
9
-
10
- Renders headings (h1-h3), bold/italic inline, tables (pipe syntax),
11
- ordered/unordered lists, blockquotes, and horizontal rules. Adds a centered
12
- footer with an auto-updating PAGE field (updates when opened in Word or
13
- exported to PDF).
14
-
15
- Requires: python3 + python-docx (`pip install python-docx`).
16
- """
17
- import re
18
- import sys
19
- import os
20
-
21
- from docx import Document
22
- from docx.shared import Pt, RGBColor
23
- from docx.enum.text import WD_ALIGN_PARAGRAPH
24
- from docx.enum.table import WD_TABLE_ALIGNMENT
25
- from docx.oxml.ns import qn
26
- from docx.oxml import OxmlElement
27
-
28
-
29
- def die(msg):
30
- print(f"md2docx: {msg}", file=sys.stderr)
31
- sys.exit(1)
32
-
33
-
34
- if len(sys.argv) < 2:
35
- die("usage: md2docx.py <input.md> [output.docx]")
36
-
37
- SRC = os.path.abspath(sys.argv[1])
38
- if not os.path.isfile(SRC):
39
- die(f"input file not found: {SRC}")
40
- OUT = (
41
- os.path.abspath(sys.argv[2])
42
- if len(sys.argv) > 2
43
- else os.path.splitext(SRC)[0] + ".docx"
44
- )
45
-
46
- with open(SRC, encoding="utf-8") as f:
47
- lines = f.read().splitlines()
48
-
49
-
50
- def add_page_number(paragraph):
51
- """Insert an auto-updating '第 N 页' page field into a footer paragraph."""
52
- run = paragraph.add_run("第 ")
53
- set_east_asia(run)
54
- fld = OxmlElement("w:fldChar")
55
- fld.set(qn("w:fldCharType"), "begin")
56
- instr = OxmlElement("w:instrText")
57
- instr.set(qn("xml:space"), "preserve")
58
- instr.text = " PAGE "
59
- sep = OxmlElement("w:fldChar")
60
- sep.set(qn("w:fldCharType"), "separate")
61
- t = OxmlElement("w:t")
62
- t.text = "1"
63
- end = OxmlElement("w:fldChar")
64
- end.set(qn("w:fldCharType"), "end")
65
- r = paragraph.add_run()
66
- set_east_asia(r)
67
- for el in (fld, instr, sep, t, end):
68
- r._r.append(el)
69
- run2 = paragraph.add_run(" 页")
70
- set_east_asia(run2)
71
-
72
-
73
- doc = Document()
74
- doc.add_heading(os.path.splitext(os.path.basename(SRC))[0], level=0)
75
-
76
-
77
- # --- base styles ---
78
- normal = doc.styles["Normal"]
79
- normal.font.name = "Calibri"
80
- normal.font.size = Pt(10.5)
81
- normal._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
82
-
83
-
84
- def set_east_asia(run):
85
- run.font.name = "Calibri"
86
- r = run._element
87
- rPr = r.get_or_add_rPr()
88
- rf = rPr.find(qn("w:rFonts"))
89
- if rf is None:
90
- rf = OxmlElement("w:rFonts")
91
- rPr.append(rf)
92
- rf.set(qn("w:eastAsia"), "微软雅黑")
93
-
94
-
95
- def add_runs_with_bold(par, text):
96
- """Add text to paragraph, honoring **bold** and *italic* markers."""
97
- for tok in re.split(r"(\*\*.*?\*\*|\*.*?\*)", text):
98
- if not tok:
99
- continue
100
- if tok.startswith("**") and tok.endswith("**") and len(tok) > 4:
101
- r = par.add_run(tok[2:-2])
102
- r.bold = True
103
- elif tok.startswith("*") and tok.endswith("*") and len(tok) > 2:
104
- r = par.add_run(tok[1:-1])
105
- r.italic = True
106
- else:
107
- r = par.add_run(tok)
108
- set_east_asia(r)
109
-
110
-
111
- def add_body_paragraph(text, style=None):
112
- p = doc.add_paragraph(style=style)
113
- add_runs_with_bold(p, text)
114
- return p
115
-
116
-
117
- def flush_table():
118
- global table_rows, in_table
119
- if not table_rows:
120
- in_table = False
121
- return
122
- ncols = max(len(r) for r in table_rows)
123
- tbl = doc.add_table(rows=len(table_rows), cols=ncols)
124
- tbl.style = "Light Grid Accent 1"
125
- tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
126
- for ri, row in enumerate(table_rows):
127
- for ci in range(ncols):
128
- cell = tbl.cell(ri, ci)
129
- cell.text = ""
130
- cp = cell.paragraphs[0]
131
- text = row[ci] if ci < len(row) else ""
132
- add_runs_with_bold(cp, text)
133
- if ri == 0:
134
- for run in cp.runs:
135
- run.bold = True
136
- tbl.autofit = True
137
- table_rows = []
138
- in_table = False
139
-
140
-
141
- table_rows = []
142
- in_table = False
143
-
144
- i = 0
145
- while i < len(lines):
146
- stripped = lines[i].strip()
147
- if not stripped:
148
- i += 1
149
- continue
150
- if re.fullmatch(r"-{3,}", stripped):
151
- flush_table()
152
- doc.add_paragraph()
153
- i += 1
154
- continue
155
- if in_table and "|" in stripped and re.fullmatch(r"\|?[\s:|-]+\|?", stripped):
156
- i += 1
157
- continue
158
- if stripped.startswith("|"):
159
- in_table = True
160
- table_rows.append([c.strip() for c in stripped.strip("|").split("|")])
161
- i += 1
162
- continue
163
- if in_table:
164
- flush_table()
165
- if stripped.startswith("### "):
166
- h = doc.add_heading(level=3)
167
- add_runs_with_bold(h, stripped[4:])
168
- i += 1
169
- continue
170
- if stripped.startswith("## "):
171
- h = doc.add_heading(level=2)
172
- add_runs_with_bold(h, stripped[3:])
173
- i += 1
174
- continue
175
- if stripped.startswith("# "):
176
- h = doc.add_heading(level=1)
177
- add_runs_with_bold(h, stripped[2:])
178
- i += 1
179
- continue
180
- if stripped.startswith("> "):
181
- p = doc.add_paragraph(style="Intense Quote")
182
- add_runs_with_bold(p, stripped[2:] + " ")
183
- i += 1
184
- continue
185
- m = re.match(r"^(\d+)\.\s+(.*)$", stripped)
186
- if m:
187
- p = doc.add_paragraph(style="List Number")
188
- add_runs_with_bold(p, m.group(2))
189
- i += 1
190
- continue
191
- if stripped.startswith("- "):
192
- p = doc.add_paragraph(style="List Bullet")
193
- add_runs_with_bold(p, stripped[2:])
194
- i += 1
195
- continue
196
- p = doc.add_paragraph()
197
- add_runs_with_bold(p, stripped)
198
- i += 1
199
-
200
- if in_table:
201
- flush_table()
202
-
203
- # --- footer with page number field ---
204
- footer = doc.sections[0].footer
205
- fp = footer.paragraphs[0]
206
- fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
207
- add_page_number(fp)
208
-
209
- doc.save(OUT)
210
- print("Saved:", OUT)