dsh-md-plus 0.4.0 → 0.4.2

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/CHANGELOG.md CHANGED
@@ -1,6 +1,28 @@
1
1
  # 更新日志
2
2
 
3
- ## 0.4.0 —— 未发布
3
+ ## 0.4.2 —— 未发布
4
+
5
+ - 修复鼠标悬浮表格或折叠内容时吞掉页面滚轮:移除表格容器的 `overscroll-behavior` containment,折叠态改为保留横向滚动、纵向 `overflow-y: clip`,并覆盖 DSH 宿主 `.tableScroll` 自带的横向 containment。
6
+ - 修复宽表横向滚动条出现 / 消失时鼠标移入移出表格造成的上下跳动:取消 hover 阶段的 padding 切换,固定底部留白。
7
+ - 修复宽表横向滚动时「展开 / 收起」按钮跟着表格内容移动:按钮中心固定跟随表格可视区域。
8
+ - 修复宽表横向滚动时右上角 CSV / Markdown 操作按钮跟表格内容移动:操作组固定在表格可视区域右上角。
9
+ - 新增 `docs/table-wheel-demo.html`:可直接检查短表、默认折叠长表(含横向滚动)和展开长表三种滚轮行为。
10
+ - 测试补到 73 条,新增表格和折叠内容不建立吞滚轮滚动容器的回归用例。
11
+
12
+ ## 0.4.1 —— 2026-10-30
13
+
14
+ - 修复 DSH 代码块头部只显示占位文案时读不到真实围栏语言、补高亮失效的问题:现在会从宿主的 React fiber 读取真实 `lang`,读不到再退回正文嗅探,并把头部语言名显示成真实语言。
15
+ - 修复 `graph = nx.Graph()`、`pie = 3.14`、`timeline = []` 等普通代码被误判成 mermaid 图的问题:只嗅探第一行有效内容,并排除赋值和调用形态。
16
+ - 修复代码块折叠在 DSH 上不触发:DSH 的代码内容容器是 `display:contents`,插件没有可测量的盒子;现在给代码内容建立真实的块级盒模型。
17
+ - 修复表格吸顶:给表格容器增加纵向滚动上限,表头在容器内滚动时保持可见。
18
+ - 修复卸载/重载后高亮副本、折叠按钮和 `data-md-plus*` 属性残留,避免同一份代码显示两遍,重装后也能重新处理。
19
+ - 修复鼠标滚轮在 1 倍图表上拦截页面滚动:未放大且未按住 Ctrl/Meta 时放行;全屏查看仍可滚轮缩放。
20
+ - 修复切换主题后代码视图中的 mermaid 图不重画的问题。
21
+ - 修复设置面板「恢复默认」把折叠阈值写成 0、代码字号与宿主行高不匹配、字号输入越界后静默回退的问题。
22
+ - 修复 mermaid 渲染失败后仍显示无效缩放/全屏按钮的问题,失败块现在显示「重试」;运行时脚本加载失败也不再永久缓存失败结果。
23
+ - 测试补到 72 条,新增 React fiber 语言识别、mermaid 误判、代码折叠盒模型、卸载清理、滚轮放行、代码视图主题重画、设置默认值等回归用例。
24
+
25
+ ## 0.4.0 —— 2026-10-28
4
26
 
5
27
  - Mermaid 全屏工具栏新增「下载 SVG / 下载 PNG」,可以直接把图带进文档或演示文稿。
6
28
  - 代码块、表格和 Mermaid 图卡增加长内容折叠:超过阈值显示「展开 / 收起」,避免长日志和长表格撑满聊天。
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  - **代码块**:统一成完整卡片,头部显示 `</>` 图标和语言名(无语言显示 `Plain text`),保留宿主原生的换行与复制按钮,顶栏与卡片同色。
10
10
  - **Mermaid**:围栏渲染成 SVG,可在「图形 / 代码」间切换;支持全屏查看(滚轮缩放、拖拽、双击适应、Esc 关闭)、四套配色,以及下载 SVG / PNG。
11
11
  - **长内容折叠**:代码块、表格、图表超过阈值自动折叠,底部出现「展开 / 收起」,长日志和长表格不会撑满聊天。
12
- - **表格**:浅色 / 深色两套样式,表头吸顶;每张表上方右侧有「CSV / Markdown」复制按钮。
12
+ - **表格**:浅色 / 深色两套样式,表头吸顶,宽表支持表内横向滚动;每张表上方右侧有「CSV / Markdown」复制按钮。
13
13
  - **图片放大**:只在旧宿主没有原生预览按钮时兜底,点击图片打开暗色预览,Esc 或点遮罩关闭;新版 DSH 的原生预览、头像、图标、代码块和链接里的图片都不接管。
14
14
  - **设置面板**:右下角齿轮可调 Mermaid 配色、代码字号、图表默认视图和三类折叠阈值,阈值下方会注明按高度判断。设置存在 `localStorage`,界面文案跟随页面 `<html lang>` 切换中英文。
15
15
 
@@ -27,18 +27,18 @@ dsh plugin --profile desktop remove dsh-md-plus
27
27
  ```powershell
28
28
  npm install # 顺带跑 prepare → build
29
29
  npm run build # 产出 lib/ 下四个文件
30
- npm run check # 语法检查 + 65 条测试(先 build 再跑,check 不含 build)
30
+ npm run check # 语法检查 + 73 条测试(先 build 再跑,check 不含 build)
31
31
  npm run preview # 生成 preview.html(含真渲出来的 mermaid 图)
32
32
  npm run shot # 用 headless Chrome 把 preview.html 截成 docs/preview.png
33
33
  ```
34
34
 
35
35
  ## 已知限制
36
36
 
37
- - DSH 0.2.0-rc.2 上,头部语言名由宿主决定:不认这门语言就显示占位文案「代码块」,插件读不到围栏语言,补高亮基本失效。mermaid 因此改看正文里的类型声明来识别。
37
+ - DSH 0.2.1-rc.4 的头部语言名由宿主决定:不认识的围栏只显示占位文案「代码块」。插件会从宿主的 React fiber 里兜底读取真实语言,读不到时才退回正文嗅探;宿主改 React 结构后这条兜底可能失效。
38
38
  - `DSH_LANGS` 是 DSH 语言表的快照(108 条)。宿主加语言后这份快照会落后,落后只会多一次无效处理,不会出现双份高亮。
39
39
  - mermaid 12 的新图种(`usecase-beta`、`agentflow-beta`)标识符只认字母数字下划线,中文得写进标签:`actor Customer("用户")` 可以,`actor 用户` 会解析失败。
40
40
  - 高亮约 6.4 MB、mermaid 约 5.0 MB,按需下载,第一次用到时多花约 100ms 解析。
41
- - 依赖宿主 `.md-code-block`、`[data-code-block-content]`、`[data-code-block-banner]`、`data-streaming` 等钩子,DSH 改版后要同步调整 `src/client.js`。
41
+ - 依赖宿主 `.md-code-block`、`[data-code-block-content]`、`[data-code-block-banner]`、`data-streaming` 等钩子,以及 React fiber 里的 `lang`;DSH 改版后要同步调整 `src/client.js`。
42
42
 
43
43
  目前只影响代码块、mermaid、表格、正文图片和显示设置,不改 Markdown 的解析结果。
44
44
 
package/lib/client.js CHANGED
@@ -26,6 +26,7 @@ window.__ModuleLoader__.load({
26
26
  var ZOOM_MIN_EPSILON = 1e-3;
27
27
  var EXPAND_CLASS = "md-plus-expand";
28
28
  var THEME_CLASS = "md-plus-theme";
29
+ var RETRY_CLASS = "md-plus-retry";
29
30
  var THEME_KEY = "dsh-md-plus-mermaid-theme";
30
31
  var THEME_ORDER = ["blue", "neutral", "forest", "contrast"];
31
32
  var THEME_LABELS = {
@@ -56,6 +57,8 @@ window.__ModuleLoader__.load({
56
57
  themePrefix: "\u914D\u8272\uFF1A",
57
58
  foldOpen: "\u5C55\u5F00",
58
59
  foldClose: "\u6536\u8D77",
60
+ retry: "\u91CD\u8BD5",
61
+ retryAria: "\u91CD\u65B0\u6E32\u67D3\u56FE\u8868",
59
62
  renderFailed: "mermaid \u6E32\u67D3\u5931\u8D25\uFF1A",
60
63
  diagram: "\u6D41\u7A0B\u56FE",
61
64
  diagramAria: "\u6D41\u7A0B\u56FE\u5168\u5C4F\u67E5\u770B",
@@ -107,6 +110,8 @@ window.__ModuleLoader__.load({
107
110
  themePrefix: "Theme: ",
108
111
  foldOpen: "Expand",
109
112
  foldClose: "Collapse",
113
+ retry: "Retry",
114
+ retryAria: "Retry diagram",
110
115
  renderFailed: "Mermaid render failed: ",
111
116
  diagram: "Diagram",
112
117
  diagramAria: "Diagram fullscreen viewer",
@@ -207,8 +212,13 @@ window.__ModuleLoader__.load({
207
212
  }
208
213
  function applyCodeFontSize() {
209
214
  var size = readSettings().codeFontSize;
210
- if (size > 0) document.documentElement.style.setProperty("--md-plus-code-font-size", size + "px");
211
- else document.documentElement.style.removeProperty("--md-plus-code-font-size");
215
+ if (size > 0) {
216
+ document.documentElement.style.setProperty("--md-plus-code-font-size", size + "px");
217
+ document.documentElement.style.setProperty("--md-plus-code-line-height", Math.round(size * 1.6) + "px");
218
+ } else {
219
+ document.documentElement.style.removeProperty("--md-plus-code-font-size");
220
+ document.documentElement.style.removeProperty("--md-plus-code-line-height");
221
+ }
212
222
  }
213
223
  var DSH_LANGS = /* @__PURE__ */ new Set([
214
224
  "typescript",
@@ -321,7 +331,10 @@ window.__ModuleLoader__.load({
321
331
  "gradle"
322
332
  ]);
323
333
  var MMD_LANGS = /* @__PURE__ */ new Set(["mermaid", "mermaidjs", "mmd"]);
324
- var MMD_TYPE = /^\s*(flowchart|graph|sequenceDiagram|classDiagram|stateDiagram(-v2)?|erDiagram|journey|gantt|pie|mindmap|timeline|gitGraph|quadrantChart|xychart-beta|sankey-beta|block-beta|packet-beta|architecture-beta|usecase-beta|agentflow-beta|C4Context|requirementDiagram|kanban|radar|treemap|venn|ishikawa|wardley|cynefin|treeView|zenuml)\b/im;
334
+ var MMD_FLOW = /^\s*(flowchart|graph)(?:\s+(?:TD|TB|BT|RL|LR))?\s*;?\s*$/i;
335
+ var MMD_PIE = /^\s*pie(?:\s+title\b.*)?\s*;?\s*$/i;
336
+ var MMD_GIT = /^\s*gitGraph(?:\s+(?:LR|TB|BT|RL))?:?\s*;?\s*$/i;
337
+ var MMD_SINGLE = /^\s*(sequenceDiagram|classDiagram|stateDiagram(?:-v2)?|erDiagram|journey|gantt|mindmap|timeline|quadrantChart|xychart-beta|sankey-beta|block-beta|packet-beta|architecture-beta|usecase-beta|agentflow-beta|C4Context|requirementDiagram|kanban|radar|treemap|venn|ishikawa|wardley|cynefin|treeView|zenuml)\s*;?\s*$/i;
325
338
  function icon(paths) {
326
339
  var svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>" + paths + "</svg>";
327
340
  return 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '")';
@@ -350,6 +363,9 @@ window.__ModuleLoader__.load({
350
363
  var ICON_THEME = icon(
351
364
  "<path d='M12 3s5 5.5 5 9a5 5 0 0 1-10 0c0-3.5 5-9 5-9z'/><path d='M9.5 12a2.5 2.5 0 0 0 5 0'/>"
352
365
  );
366
+ var ICON_RETRY = icon(
367
+ "<path d='M20 11a8 8 0 1 0-2.34 5.66'/><path d='M20 4v7h-7'/>"
368
+ );
353
369
  var ICON_GEAR = icon(
354
370
  "<circle cx='12' cy='12' r='3'/><path d='M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09a1.7 1.7 0 0 0-1.11-1.55 1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09a1.7 1.7 0 0 0 1.55-1.11 1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.55 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.14.36.48.6.87.61H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.55 1z'/>"
355
371
  );
@@ -358,7 +374,7 @@ window.__ModuleLoader__.load({
358
374
  var TABLE_VARS_DARK = "--md-plus-tbl-line:#2f2f2f;--md-plus-tbl-head:#1f1f1f;--md-plus-tbl-head-fg:#f9fafb;--md-plus-tbl-hover:#1f1f1f";
359
375
  var CSS = [
360
376
  // ── 卡片:整块一个底色,顶栏不再是独立色带 ──────────────────────
361
- ".md-code-block{--md-plus-mmd-min-height:200px;--md-plus-icon:" + ICON_CODE + ";--md-plus-icon-copy:" + ICON_COPY + ";--md-plus-icon-nowrap:" + ICON_NOWRAP + ";--md-plus-icon-diagram:" + ICON_DIAGRAM + ";--md-plus-icon-zoom-in:" + ICON_ZOOM_IN + ";--md-plus-icon-zoom-out:" + ICON_ZOOM_OUT + ";--md-plus-icon-expand:" + ICON_EXPAND + ";--md-plus-icon-theme:" + ICON_THEME + "}",
377
+ ".md-code-block{--md-plus-mmd-min-height:200px;--md-plus-icon:" + ICON_CODE + ";--md-plus-icon-copy:" + ICON_COPY + ";--md-plus-icon-nowrap:" + ICON_NOWRAP + ";--md-plus-icon-diagram:" + ICON_DIAGRAM + ";--md-plus-icon-zoom-in:" + ICON_ZOOM_IN + ";--md-plus-icon-zoom-out:" + ICON_ZOOM_OUT + ";--md-plus-icon-expand:" + ICON_EXPAND + ";--md-plus-icon-theme:" + ICON_THEME + ";--md-plus-icon-retry:" + ICON_RETRY + "}",
362
378
  // bannerWrap 的原底色是 --dsw-alias-bg-base(#fff),会跟卡片灰拉开一条带
363
379
  ".md-code-block > div:has(> " + BANNER + "){background:transparent}",
364
380
  // ── 头部:收紧,并把空着的左边填上 ─────────────────────────────
@@ -375,6 +391,9 @@ window.__ModuleLoader__.load({
375
391
  // pre 的行高本身就在首行文字上方留了约半行空白;补 6px 后实测间距约 17px
376
392
  // (原来给 0 时只有 11px,贴着看太挤)
377
393
  ".md-code-block pre{padding:6px 16px 16px;font-size:var(--md-plus-code-font-size,inherit)}",
394
+ ".md-code-block pre{line-height:var(--md-plus-code-line-height,inherit)}",
395
+ // DSH 0.2.1 把内容容器设成 display:contents,折叠需要真实的盒子才能量高度和裁剪
396
+ ".md-code-block > [data-code-block-content]{display:block}",
378
397
  // ── 头部按钮:文字换成图标 ──────────────────────────────────────
379
398
  ".md-code-block " + BANNER + " button{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:0;border-radius:6px;font-size:0;cursor:pointer;background:transparent;color:var(--dsw-alias-label-tertiary);transition:background-color .12s ease,color .12s ease}",
380
399
  // font-size:0 只藏住「复制 / 已复制」的字面,可访问名仍在
@@ -393,6 +412,7 @@ window.__ModuleLoader__.load({
393
412
  ".md-code-block " + BANNER + " ." + ZOOM_OUT_CLASS + "::before{-webkit-mask:var(--md-plus-icon-zoom-out) center/contain no-repeat;mask:var(--md-plus-icon-zoom-out) center/contain no-repeat}",
394
413
  ".md-code-block " + BANNER + " ." + EXPAND_CLASS + "::before{-webkit-mask:var(--md-plus-icon-expand) center/contain no-repeat;mask:var(--md-plus-icon-expand) center/contain no-repeat}",
395
414
  ".md-code-block " + BANNER + " ." + THEME_CLASS + "::before{-webkit-mask:var(--md-plus-icon-theme) center/contain no-repeat;mask:var(--md-plus-icon-theme) center/contain no-repeat}",
415
+ ".md-code-block " + BANNER + " ." + RETRY_CLASS + "::before{-webkit-mask:var(--md-plus-icon-retry) center/contain no-repeat;mask:var(--md-plus-icon-retry) center/contain no-repeat}",
396
416
  "[" + MMD_ATTR + '="diagram"] .' + TOGGLE_CLASS + "{color:var(--dsw-alias-button-primary-fill,#4c6ef5);background:var(--dsw-alias-interactive-bg-active,rgb(76 110 245 / 12%))}",
397
417
  // 已经到最小 / 最大时,对应那颗按钮变淡且不可点
398
418
  "[" + ZOOM_ATTR + '="min"] .' + ZOOM_OUT_CLASS + ",[" + ZOOM_ATTR + '="max"] .' + ZOOM_IN_CLASS + "{opacity:.3;pointer-events:none}",
@@ -423,8 +443,9 @@ window.__ModuleLoader__.load({
423
443
  "." + MODAL_CLASS + " svg{display:block}",
424
444
  // ── 长内容折叠:代码块、表格、图表卡共用一套按钮 ───────────────
425
445
  "." + FOLD_CLASS + "{position:relative}",
426
- "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"]{max-height:var(--md-plus-fold-max-height);overflow:hidden}',
427
- "." + FOLD_BTN_CLASS + "{display:block;margin:8px auto 0;padding:4px 12px;border:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 22%));border-radius:999px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary);font:12px/18px var(--dsw-font-family-sans,system-ui,sans-serif);cursor:pointer;box-shadow:0 2px 8px rgb(0 0 0 / 12%);z-index:2}",
446
+ // 用 clip 而不是 hidden:hidden 会建立隐形滚动容器,鼠标在折叠内容上滚轮会被它吞掉。
447
+ "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"]{max-height:var(--md-plus-fold-max-height);overflow:clip}',
448
+ "." + FOLD_BTN_CLASS + "{display:block;white-space:nowrap;margin:8px auto 0;padding:4px 12px;border:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 22%));border-radius:999px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary);font:12px/18px var(--dsw-font-family-sans,system-ui,sans-serif);cursor:pointer;box-shadow:0 2px 8px rgb(0 0 0 / 12%);z-index:2}",
428
449
  "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"] .' + FOLD_BTN_CLASS + "{position:absolute;left:50%;bottom:8px;transform:translateX(-50%);margin:0}",
429
450
  // ── 图片点击放大 ────────────────────────────────────────────────
430
451
  '[class*="_markdown_"] img{cursor:zoom-in}',
@@ -460,7 +481,10 @@ window.__ModuleLoader__.load({
460
481
  // 抓手用结构选择器而不是 DSH 的哈希类名:外层是「唯一子元素是 table 的 div」。
461
482
  // 前面挂 [class*="_markdown_"] 有两个作用——只认 markdown 渲染出来的表格,
462
483
  // 同时把特异性顶到 (0,1,4),压过 DSH 自己的 .tableScroll th (0,1,1)。
463
- TABLE + "{position:relative;margin-block:16px;" + TABLE_VARS + "}",
484
+ // 不要加 overscroll-behavior-y:contain:宿主给表格的外层是 overflow-x:auto,
485
+ // 两轴滚动容器上加 contain 会让没有纵向滚动的短表也吞掉页面滚轮;宿主的
486
+ // overscroll-behavior-x:contain 同样要清掉,Chrome 下它也会连带吞掉纵向滚轮。
487
+ TABLE + "{position:relative;margin-block:16px;max-height:min(70vh,720px);overflow-y:auto;overscroll-behavior:auto;" + TABLE_VARS + "}",
464
488
  "body[data-ds-dark-theme] " + TABLE + "{" + TABLE_VARS_DARK + "}",
465
489
  // 整张表一个 1px 边框 + 小圆角。
466
490
  // 注意必须是 separate:collapse 模式下 Chrome 会忽略单元格的圆角,
@@ -487,23 +511,24 @@ window.__ModuleLoader__.load({
487
511
  // 长表往下滚时表头留在视口顶部;底色不透明才不会透出下面的行
488
512
  TABLE + " > table thead th{position:sticky;top:0;z-index:1}",
489
513
  // 复制按钮放在表尾右侧:不压住表头文字,表格折叠时也能随展开一起出现
490
- TABLE + " > table > caption." + TABLE_TOOLS_CLASS + "{caption-side:top;text-align:right;padding:0 0 6px}",
514
+ TABLE + " > table > caption." + TABLE_TOOLS_CLASS + "{caption-side:top;position:sticky;right:0;width:0;height:30px;margin-left:auto;padding:0;text-align:right;z-index:2}",
515
+ TABLE + " > table > caption." + TABLE_TOOLS_CLASS + " > span{position:absolute;right:0;top:0;display:flex;gap:0;white-space:nowrap}",
491
516
  TABLE + " > table > caption." + TABLE_TOOLS_CLASS + " button{appearance:none;border:1px solid var(--md-plus-tbl-line);border-radius:5px;padding:2px 8px;margin-left:4px;background:transparent;color:var(--dsw-alias-label-secondary);font:11px/16px var(--dsw-font-family-sans,system-ui,sans-serif);cursor:pointer}",
492
517
  TABLE + " > table > caption." + TABLE_TOOLS_CLASS + " button:hover{color:var(--dsw-alias-label-primary);background:var(--md-plus-tbl-hover)}",
493
518
  // 宽表(≥4 列)不收着不行。宿主给这类表加了 .md-table-wide,并让它「出血」:
494
519
  // 包裹层撑到面板宽,再用 padding-left 把表推到正文栏左侧。那个 padding 会吃掉
495
- // 可用宽度,结果是表格比正文宽出一截、右边还被裁掉,而滚动条只在鼠标悬停时
496
- // 才出现——文字表落进这个区间就很难读。这里按回正文栏宽,超出改为表内横滚。
520
+ // 可用宽度,结果是表格比正文宽出一截、右边还被裁掉;这里按回正文栏宽,超出改为表内横滚。
497
521
  // 特异性 (0,2,2),压得住宿主的 .Pg4CGa_body .md-table-wide (0,2,0)。
498
- TABLE + ".md-table-wide{width:100%;max-width:100%;margin-inline:0;padding-inline:0;overflow-x:auto}",
522
+ TABLE + ".md-table-wide{width:100%;max-width:100%;margin-inline:0;padding-inline:0;padding-bottom:0;overflow-x:auto}",
499
523
  // 塞得下就撑满正文栏,塞不下保持自然宽度让外面滚,不把列挤成一列一个字
500
524
  TABLE + ".md-table-wide > table{width:100%;max-width:none;min-width:max-content}",
501
- // 宿主原本是「悬停才出滚动条」,所以悬停时会把预留的滚动条高度收掉;
502
- // 现在滚动条常驻,再收掉就会跳 5px,固定住。
503
- TABLE + ".md-table-wide:hover{padding-bottom:var(--dsh-scrollbar-width,5px)}"
525
+ // 折叠态保留横向滚动,但纵向必须 clip;横向滚动容器只有在
526
+ // overscroll-behavior:auto 时才会把纵向滚轮交还给页面。
527
+ TABLE + "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"]{overflow-x:auto;overflow-y:clip}'
504
528
  ].join("\n");
505
529
  var scripts = /* @__PURE__ */ new Map();
506
530
  var pending = /* @__PURE__ */ new Set();
531
+ var foldSyncs = /* @__PURE__ */ new WeakMap();
507
532
  var timer = null;
508
533
  var rescanQueued = false;
509
534
  var injected = /* @__PURE__ */ new Set();
@@ -524,30 +549,121 @@ window.__ModuleLoader__.load({
524
549
  script.src = url;
525
550
  script.async = true;
526
551
  script.setAttribute(RUNTIME_ATTR, "1");
527
- script.addEventListener("load", () => resolve(window[globalName] ?? null), { once: true });
528
- script.addEventListener("error", () => resolve(null), { once: true });
552
+ script.addEventListener(
553
+ "load",
554
+ () => {
555
+ var api = window[globalName];
556
+ if (api === void 0) scripts.delete(url);
557
+ resolve(api ?? null);
558
+ },
559
+ { once: true }
560
+ );
561
+ script.addEventListener(
562
+ "error",
563
+ () => {
564
+ scripts.delete(url);
565
+ script.remove();
566
+ resolve(null);
567
+ },
568
+ { once: true }
569
+ );
529
570
  document.head.appendChild(script);
530
571
  });
531
572
  scripts.set(url, promise);
532
573
  return promise;
533
574
  }
534
- function readLang(block) {
575
+ function readBannerLabel(block) {
535
576
  var banner = block.querySelector(BANNER);
536
- var info = banner?.firstElementChild;
577
+ var info = banner?.querySelector('[class*="_language_"]') ?? banner?.firstElementChild;
537
578
  return (info?.textContent ?? "").trim().toLowerCase();
538
579
  }
580
+ var PLACEHOLDER_LABELS = /* @__PURE__ */ new Set(["", "\u4EE3\u7801\u5757", "code block", "code", "plain text", "plaintext", "\u7EAF\u6587\u672C"]);
581
+ var FIBER_KEY = "__reactFiber$";
582
+ function isPlaceholderLabel(label) {
583
+ return PLACEHOLDER_LABELS.has(label);
584
+ }
585
+ function readFiberLang(block) {
586
+ var info = null;
587
+ var nodes = [block, block.querySelector(CONTENT)];
588
+ for (var node of nodes) {
589
+ if (node === null || node === void 0) continue;
590
+ var names;
591
+ try {
592
+ names = Object.getOwnPropertyNames(node);
593
+ } catch {
594
+ continue;
595
+ }
596
+ for (var name of names) {
597
+ if (name.lastIndexOf(FIBER_KEY, 0) !== 0) continue;
598
+ var fiber = node[name];
599
+ for (var depth = 0; fiber !== null && fiber !== void 0 && depth < 24; depth += 1) {
600
+ var props = fiber.memoizedProps;
601
+ if (props !== null && typeof props === "object" && Object.prototype.hasOwnProperty.call(props, "lang")) {
602
+ var value = typeof props.lang === "string" ? props.lang.trim().toLowerCase() : "";
603
+ info = { hasLang: value !== "", lang: value };
604
+ break;
605
+ }
606
+ fiber = fiber.return;
607
+ }
608
+ if (info !== null) break;
609
+ }
610
+ if (info !== null) break;
611
+ }
612
+ return info;
613
+ }
614
+ function readLang(block) {
615
+ var label = readBannerLabel(block);
616
+ if (!isPlaceholderLabel(label)) return label;
617
+ var info = readFiberLang(block);
618
+ return info === null ? "" : info.lang;
619
+ }
620
+ function paintLanguageLabel(block, lang) {
621
+ if (lang === "") return;
622
+ var banner = block.querySelector(BANNER);
623
+ var heading = banner?.firstElementChild;
624
+ if (heading === null || heading === void 0) return;
625
+ var target = banner.querySelector('[class*="_language_"]') ?? heading;
626
+ if ((target.textContent ?? "").trim().toLowerCase() === lang) return;
627
+ if (!target.hasAttribute("data-md-plus-original-label")) {
628
+ target.setAttribute("data-md-plus-original-label", target.textContent ?? "");
629
+ }
630
+ target.textContent = lang;
631
+ target.setAttribute("data-md-plus-lang", lang);
632
+ }
539
633
  function sniffDiagramType(code) {
540
- var text = code;
634
+ var lines = code.split("\n");
635
+ var start = 0;
636
+ while (start < lines.length) {
637
+ var line = lines[start].trim();
638
+ if (line === "" || line.startsWith("%%")) {
639
+ start += 1;
640
+ continue;
641
+ }
642
+ break;
643
+ }
644
+ var text = lines.slice(start).join("\n");
541
645
  if (text.startsWith("---")) {
542
646
  var blank = text.indexOf("\n");
543
647
  var close = blank < 0 ? -1 : text.indexOf("\n---", blank);
544
648
  text = close < 0 ? "" : text.slice(close + 4);
545
649
  }
546
- var body = text.split("\n").filter((line) => !line.trimStart().startsWith("%%")).join("\n");
547
- return MMD_TYPE.exec(body)?.[1] ?? null;
650
+ var first = text.split("\n").find((line2) => {
651
+ var value2 = line2.trim();
652
+ return value2 !== "" && !value2.startsWith("%%");
653
+ });
654
+ if (first === void 0) return null;
655
+ var value = first.trim();
656
+ var flow = MMD_FLOW.exec(value);
657
+ if (flow !== null) return flow[1];
658
+ if (MMD_PIE.test(value)) return "pie";
659
+ if (MMD_GIT.test(value)) return "gitGraph";
660
+ var single = MMD_SINGLE.exec(value);
661
+ return single === null ? null : single[1];
548
662
  }
549
663
  function diagramKind(block) {
550
- if (MMD_LANGS.has(readLang(block))) return "diagram";
664
+ var lang = readLang(block);
665
+ if (MMD_LANGS.has(lang)) return "diagram";
666
+ if (lang !== "") return null;
551
667
  var pre = block.querySelector(CONTENT + " pre");
552
668
  if (pre === null) return null;
553
669
  return sniffDiagramType(pre.textContent ?? "") === null ? null : "diagram";
@@ -599,7 +715,8 @@ window.__ModuleLoader__.load({
599
715
  }
600
716
  }
601
717
  function redrawDiagrams() {
602
- for (const block of document.querySelectorAll("[" + MMD_ATTR + '="diagram"]')) {
718
+ for (const block of document.querySelectorAll("[" + MMD_ATTR + "]")) {
719
+ if (block.getAttribute(MMD_ATTR) === "failed") continue;
603
720
  block.removeAttribute(KEY_ATTR);
604
721
  pending.add(block);
605
722
  }
@@ -646,6 +763,19 @@ window.__ModuleLoader__.load({
646
763
  run: (block) => cycleMermaidTheme(block)
647
764
  }
648
765
  ];
766
+ var FAILED_BUTTONS = [
767
+ {
768
+ cls: RETRY_CLASS,
769
+ titleKey: "retry",
770
+ ariaKey: "retryAria",
771
+ run: (block) => {
772
+ block.removeAttribute(KEY_ATTR);
773
+ block.removeAttribute(MMD_ATTR);
774
+ block.querySelector("." + MMD_CLASS + "-error")?.remove();
775
+ void enhance(block);
776
+ }
777
+ }
778
+ ];
649
779
  function findNativeWrap(buttons) {
650
780
  if (buttons === void 0) return null;
651
781
  for (const button of buttons) {
@@ -661,7 +791,10 @@ window.__ModuleLoader__.load({
661
791
  var copy = nativeButtons === void 0 || nativeButtons.length === 0 ? null : nativeButtons[nativeButtons.length - 1];
662
792
  var action = copy?.parentElement;
663
793
  if (action === null || action === void 0) return;
664
- var specs = readLang(block) !== "" && diagramKind(block) !== null ? MMD_BUTTONS : findNativeWrap(nativeButtons) !== null ? [] : PLAIN_BUTTONS;
794
+ var label = readBannerLabel(block);
795
+ var lang = readLang(block);
796
+ if (lang !== "" && isPlaceholderLabel(label)) paintLanguageLabel(block, lang);
797
+ var specs = block.getAttribute(MMD_ATTR) === "failed" ? FAILED_BUTTONS : diagramKind(block) !== null ? MMD_BUTTONS : findNativeWrap(nativeButtons) !== null ? [] : PLAIN_BUTTONS;
665
798
  for (const existing of action.querySelectorAll("." + BTN_CLASS)) {
666
799
  if (specs.some((spec) => existing.classList.contains(spec.cls))) continue;
667
800
  existing.remove();
@@ -675,9 +808,9 @@ window.__ModuleLoader__.load({
675
808
  created.title = t(spec.titleKey);
676
809
  created.setAttribute("aria-label", t(spec.ariaKey));
677
810
  if (spec.cls === THEME_CLASS) {
678
- var label = themeLabel(getMermaidTheme());
679
- created.title = label;
680
- created.setAttribute("aria-label", label);
811
+ var themeText = themeLabel(getMermaidTheme());
812
+ created.title = themeText;
813
+ created.setAttribute("aria-label", themeText);
681
814
  }
682
815
  created.addEventListener("click", () => spec.run(block));
683
816
  action.insertBefore(created, copy);
@@ -702,11 +835,12 @@ window.__ModuleLoader__.load({
702
835
  var pre = content.querySelector("pre");
703
836
  if (pre === null || pre.classList.contains("shiki")) return;
704
837
  var lang = readLang(block);
705
- if (lang === "") return;
838
+ if (lang !== "" && isPlaceholderLabel(readBannerLabel(block))) paintLanguageLabel(block, lang);
706
839
  if (diagramKind(block) !== null) {
707
840
  await renderDiagram(block, content, pre);
708
841
  return;
709
842
  }
843
+ if (lang === "") return;
710
844
  if (DSH_LANGS.has(lang)) return;
711
845
  var api = await loadScript(HL_URL, HL_GLOBAL);
712
846
  if (api === null || !api.supports(lang)) return;
@@ -812,6 +946,7 @@ window.__ModuleLoader__.load({
812
946
  zooms.delete(block);
813
947
  paint(block);
814
948
  scheduleFold(viewport, foldLimit("diagram"));
949
+ decorate(block);
815
950
  }
816
951
  var zooms = /* @__PURE__ */ new WeakMap();
817
952
  function zoomState(block) {
@@ -862,11 +997,12 @@ window.__ModuleLoader__.load({
862
997
  clampPan(block, state);
863
998
  paint(block);
864
999
  }
865
- function bindViewport(block, viewport, resetView) {
1000
+ function bindViewport(block, viewport, resetView, alwaysZoom) {
866
1001
  var drag = null;
867
1002
  viewport.addEventListener(
868
1003
  "wheel",
869
1004
  (event) => {
1005
+ if (!alwaysZoom && !event.ctrlKey && !event.metaKey && zoomState(block).scale <= 1.001) return;
870
1006
  event.preventDefault();
871
1007
  zoomBy(block, event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP, event.clientX, event.clientY);
872
1008
  },
@@ -914,6 +1050,12 @@ window.__ModuleLoader__.load({
914
1050
  }
915
1051
  function resetFold(target) {
916
1052
  if (target === null) return;
1053
+ var sync = foldSyncs.get(target);
1054
+ if (sync !== void 0) {
1055
+ target.removeEventListener("scroll", sync);
1056
+ window.removeEventListener("resize", sync);
1057
+ foldSyncs.delete(target);
1058
+ }
917
1059
  target.removeAttribute(FOLD_ATTR);
918
1060
  target.classList.remove(FOLD_CLASS);
919
1061
  target.style.removeProperty("--md-plus-fold-max-height");
@@ -946,13 +1088,23 @@ window.__ModuleLoader__.load({
946
1088
  button.className = FOLD_BTN_CLASS;
947
1089
  button.textContent = t("foldOpen");
948
1090
  button.setAttribute("aria-expanded", "false");
1091
+ var sync = target.hasAttribute(TABLE_DONE_ATTR) ? () => {
1092
+ button.style.left = target.scrollLeft + target.clientWidth / 2 + "px";
1093
+ } : null;
1094
+ if (sync !== null) {
1095
+ foldSyncs.set(target, sync);
1096
+ target.addEventListener("scroll", sync, { passive: true });
1097
+ window.addEventListener("resize", sync);
1098
+ }
949
1099
  button.addEventListener("click", () => {
950
1100
  var folded = target.getAttribute(FOLD_ATTR) === "on";
951
1101
  target.setAttribute(FOLD_ATTR, folded ? "off" : "on");
952
1102
  button.textContent = folded ? t("foldClose") : t("foldOpen");
953
1103
  button.setAttribute("aria-expanded", folded ? "true" : "false");
1104
+ sync?.();
954
1105
  });
955
1106
  target.appendChild(button);
1107
+ sync?.();
956
1108
  });
957
1109
  }
958
1110
  function scanFolds() {
@@ -1022,6 +1174,8 @@ window.__ModuleLoader__.load({
1022
1174
  table.setAttribute(TABLE_DONE_ATTR, "1");
1023
1175
  var tools = document.createElement("caption");
1024
1176
  tools.className = TABLE_TOOLS_CLASS;
1177
+ var group = document.createElement("span");
1178
+ group.className = TABLE_TOOLS_CLASS + "-group";
1025
1179
  for (const [kind, label, titleKey, convert] of [
1026
1180
  ["csv", t("tableCsv"), "copyCsvTitle", tableToCsv],
1027
1181
  ["md", t("tableMd"), "copyMdTitle", tableToMarkdown]
@@ -1040,8 +1194,9 @@ window.__ModuleLoader__.load({
1040
1194
  }
1041
1195
  flashButton(button, ok ? t("copied") : t("copyFailed"));
1042
1196
  });
1043
- tools.appendChild(button);
1197
+ group.appendChild(button);
1044
1198
  }
1199
+ tools.appendChild(group);
1045
1200
  dataTable.insertBefore(tools, dataTable.firstChild);
1046
1201
  }
1047
1202
  function openImage(source, alt) {
@@ -1214,21 +1369,26 @@ window.__ModuleLoader__.load({
1214
1369
  closeSettings();
1215
1370
  }
1216
1371
  }
1217
- function readNumber(input, fallback) {
1372
+ function readNumber(input, fallback, max) {
1373
+ var value = Number(input.value);
1374
+ return Number.isFinite(value) && value >= 0 ? Math.min(max ?? 4e3, Math.round(value)) : fallback;
1375
+ }
1376
+ function readFontSize(input) {
1218
1377
  var value = Number(input.value);
1219
- return Number.isFinite(value) && value >= 0 ? Math.min(4e3, Math.round(value)) : fallback;
1378
+ if (!Number.isFinite(value) || value <= 0) return 0;
1379
+ return Math.min(20, Math.max(10, Math.round(value)));
1220
1380
  }
1221
1381
  resetButton.addEventListener("click", () => {
1222
1382
  themeSelect.value = "blue";
1223
1383
  fontInput.value = "";
1224
1384
  viewSelect.value = DEFAULT_SETTINGS.diagramView;
1225
- foldCode.value = "";
1226
- foldTable.value = "";
1227
- foldDiagram.value = "";
1385
+ foldCode.value = String(DEFAULT_SETTINGS.foldCode);
1386
+ foldTable.value = String(DEFAULT_SETTINGS.foldTable);
1387
+ foldDiagram.value = String(DEFAULT_SETTINGS.foldDiagram);
1228
1388
  });
1229
1389
  saveButton.addEventListener("click", () => {
1230
1390
  saveSettings({
1231
- codeFontSize: readNumber(fontInput, 0),
1391
+ codeFontSize: readFontSize(fontInput),
1232
1392
  diagramView: viewSelect.value === "code" ? "code" : "diagram",
1233
1393
  foldCode: readNumber(foldCode, DEFAULT_SETTINGS.foldCode),
1234
1394
  foldTable: readNumber(foldTable, DEFAULT_SETTINGS.foldTable),
@@ -1444,7 +1604,7 @@ window.__ModuleLoader__.load({
1444
1604
  closeButton.addEventListener("click", closeViewer);
1445
1605
  document.addEventListener("keydown", onKey, true);
1446
1606
  zooms.set(overlay, { scale: 1, x: 0, y: 0, min: ZOOM_MIN_MODAL });
1447
- bindViewport(overlay, viewport, reset);
1607
+ bindViewport(overlay, viewport, reset, true);
1448
1608
  document.documentElement.style.overflow = "hidden";
1449
1609
  document.body.appendChild(overlay);
1450
1610
  closeButton.focus();
@@ -1460,6 +1620,7 @@ window.__ModuleLoader__.load({
1460
1620
  content.appendChild(note);
1461
1621
  block.setAttribute(KEY_ATTR, key);
1462
1622
  block.setAttribute(MMD_ATTR, "failed");
1623
+ decorate(block);
1463
1624
  console.warn("[dsh-md-plus] mermaid \u6E32\u67D3\u5931\u8D25", error);
1464
1625
  }
1465
1626
  function apply0(ctx) {
@@ -1484,10 +1645,17 @@ window.__ModuleLoader__.load({
1484
1645
  document.removeEventListener("click", onDocumentClick, true);
1485
1646
  for (const node of injected) node.remove();
1486
1647
  injected.clear();
1648
+ for (const node of document.querySelectorAll("." + BTN_CLASS)) node.remove();
1649
+ for (const node of document.querySelectorAll("." + HOST_CLASS)) node.remove();
1650
+ for (const node of document.querySelectorAll("." + FOLD_BTN_CLASS)) node.remove();
1487
1651
  for (const node of document.querySelectorAll("[" + FOLD_ATTR + "]")) resetFold(node);
1488
1652
  for (const node of document.querySelectorAll("." + TABLE_TOOLS_CLASS)) node.remove();
1489
- for (const table of document.querySelectorAll("[" + TABLE_DONE_ATTR + "]")) {
1490
- table.removeAttribute(TABLE_DONE_ATTR);
1653
+ for (const attr of [DONE_ATTR, KEY_ATTR, WRAP_ATTR, MMD_ATTR, TABLE_DONE_ATTR, "data-md-plus-lang"]) {
1654
+ for (const node of document.querySelectorAll("[" + attr + "]")) node.removeAttribute(attr);
1655
+ }
1656
+ for (const node of document.querySelectorAll("[data-md-plus-original-label]")) {
1657
+ node.textContent = node.getAttribute("data-md-plus-original-label") ?? node.textContent;
1658
+ node.removeAttribute("data-md-plus-original-label");
1491
1659
  }
1492
1660
  for (const node of document.querySelectorAll(
1493
1661
  "." + MMD_CLASS + ", ." + MMD_CLASS + "-error, ." + MODAL_CLASS + ", ." + IMG_MODAL_CLASS + ", ." + SETTINGS_BTN_CLASS + ", ." + SETTINGS_MODAL_CLASS
@@ -1496,6 +1664,7 @@ window.__ModuleLoader__.load({
1496
1664
  }
1497
1665
  document.documentElement.style.overflow = "";
1498
1666
  document.documentElement.style.removeProperty("--md-plus-code-font-size");
1667
+ document.documentElement.style.removeProperty("--md-plus-code-line-height");
1499
1668
  document.querySelector("style[" + STYLE_ATTR + "]")?.remove();
1500
1669
  for (const script of document.querySelectorAll("script[" + RUNTIME_ATTR + "]")) {
1501
1670
  script.remove();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-md-plus",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Markdown display enhancements for DeepSeek Harness Web: code block restyle, syntax highlighting for languages DSH does not ship, and built-in mermaid diagrams.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",