dsh-md-plus 0.2.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # 更新日志
2
2
 
3
+ ## 0.3.1 —— 2026-10-28
4
+
5
+ - 修掉宿主原生按钮已有 SVG 图标时,插件又叠加一层复制图标造成的重复显示;插件按钮仍插在原生换行与复制按钮之间。
6
+ - 测试补到 55 条,新增全屏打开、尺寸恢复、Esc 关闭与重复图标回归用例。
7
+
8
+ ## 0.3.0 —— 2026-10-28
9
+
10
+ - mermaid 卡片新增「全屏查看」:复杂时序图、长流程图不再挤在聊天栏里看。覆盖层默认适应整图,支持滚轮缩放、拖拽、双击适应和 Esc 关闭。
11
+ - 全屏里的缩放范围与聊天内分开:聊天内保持 1–6 倍,全屏可以缩到 10%,方便先看全局再放大细节。
12
+ - 测试补到 54 条,新增全屏打开、尺寸恢复与 Esc 关闭用例。
13
+
3
14
  ## 0.2.1 —— 2026-10-16
4
15
 
5
16
  - 修掉「同一份代码显示两遍」:`DSH_LANGS` 还是老版本的语言表,`dart`、`scala`、`vue`、`powershell`、`csv`、`diff` 等 66 个别名被当成「DSH 不认识」,插件抢先补了一层高亮;随后 DSH 的懒加载语法到位、React 把 plain `<pre>` 换成自己的高亮节点,插件那份副本成了野节点留在原地,于是两份都在。现在语言表对齐 108 条别名。
package/README.md CHANGED
@@ -25,6 +25,8 @@ DSH 原生的头部是「小灰字 + 文字复制按钮」,没写语言时左
25
25
 
26
26
  mermaid 围栏由插件渲染成 SVG,头部右侧可在「图形 / 代码」之间切换,复制按钮复制的仍是图源。配色和节点圆角在 `src/mermaid-theme.js`。
27
27
 
28
+ 复杂时序图、长流程图可以点右上角的全屏图标:在覆盖层里默认「适应整图」,也支持滚轮缩放、拖拽和双击适应,按 Esc 关闭。聊天内的普通缩放仍然保留。
29
+
28
30
  认图先看头部语言名(`mermaid` / `mmd` / `mermaidjs`),认不出再看正文里的类型声明(`flowchart`、`graph`、`sequenceDiagram` 等)。0.2.0-rc.2 上头部是占位文案,走的就是正文这条。
29
31
 
30
32
  ### 表格样式
package/lib/client.js CHANGED
@@ -21,6 +21,11 @@ window.__ModuleLoader__.load({
21
21
  var ZOOM_ATTR = "data-md-plus-zoom";
22
22
  var ZOOM_STEP = 1.25;
23
23
  var ZOOM_MAX = 6;
24
+ var ZOOM_MIN_INLINE = 1;
25
+ var ZOOM_MIN_MODAL = 0.1;
26
+ var ZOOM_MIN_EPSILON = 1e-3;
27
+ var EXPAND_CLASS = "md-plus-expand";
28
+ var MODAL_CLASS = "md-plus-mmd-modal";
24
29
  var DONE_ATTR = "data-md-plus";
25
30
  var KEY_ATTR = "data-md-plus-key";
26
31
  var WRAP_ATTR = "data-md-plus-wrap";
@@ -162,12 +167,15 @@ window.__ModuleLoader__.load({
162
167
  var ICON_ZOOM_OUT = icon(
163
168
  "<circle cx='10.5' cy='10.5' r='7'/><path d='M20.5 20.5 15.6 15.6'/><path d='M7.5 10.5h6'/>"
164
169
  );
170
+ var ICON_EXPAND = icon(
171
+ "<path d='M8 3H3v5'/><path d='M16 3h5v5'/><path d='M3 16v5h5'/><path d='M21 16v5h-5'/>"
172
+ );
165
173
  var TABLE = '[class*="_markdown_"] div:has(> table)';
166
174
  var TABLE_VARS = "--md-plus-tbl-radius:8px;--md-plus-tbl-radius-inner:7px;--md-plus-tbl-line:#ebebeb;--md-plus-tbl-head:#f7f7f7;--md-plus-tbl-head-fg:#181818;--md-plus-tbl-hover:#fafafa";
167
175
  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";
168
176
  var CSS = [
169
177
  // ── 卡片:整块一个底色,顶栏不再是独立色带 ──────────────────────
170
- ".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 + "}",
178
+ ".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 + "}",
171
179
  // bannerWrap 的原底色是 --dsw-alias-bg-base(#fff),会跟卡片灰拉开一条带
172
180
  ".md-code-block > div:has(> " + BANNER + "){background:transparent}",
173
181
  // ── 头部:收紧,并把空着的左边填上 ─────────────────────────────
@@ -186,7 +194,7 @@ window.__ModuleLoader__.load({
186
194
  // ── 头部按钮:文字换成图标 ──────────────────────────────────────
187
195
  ".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}",
188
196
  // font-size:0 只藏住「复制 / 已复制」的字面,可访问名仍在
189
- ".md-code-block " + BANNER + " button::before{content:'';width:15px;height:15px;background:currentColor;-webkit-mask:var(--md-plus-icon-copy) center/contain no-repeat;mask:var(--md-plus-icon-copy) center/contain no-repeat}",
197
+ ".md-code-block " + BANNER + " button:not(:has(svg))::before{content:'';width:15px;height:15px;background:currentColor;-webkit-mask:var(--md-plus-icon-copy) center/contain no-repeat;mask:var(--md-plus-icon-copy) center/contain no-repeat}",
190
198
  ".md-code-block " + BANNER + " button:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,rgb(127 127 127 / 12%))}",
191
199
  ".md-code-block " + BANNER + " button:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4c6ef5);outline-offset:1px}",
192
200
  // ── 普通块:不换行切换 ──────────────────────────────────────────
@@ -197,6 +205,7 @@ window.__ModuleLoader__.load({
197
205
  ".md-code-block " + BANNER + " ." + TOGGLE_CLASS + "::before{-webkit-mask:var(--md-plus-icon-diagram) center/contain no-repeat;mask:var(--md-plus-icon-diagram) center/contain no-repeat}",
198
206
  ".md-code-block " + BANNER + " ." + ZOOM_IN_CLASS + "::before{-webkit-mask:var(--md-plus-icon-zoom-in) center/contain no-repeat;mask:var(--md-plus-icon-zoom-in) center/contain no-repeat}",
199
207
  ".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}",
208
+ ".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}",
200
209
  "[" + 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%))}",
201
210
  // 已经到最小 / 最大时,对应那颗按钮变淡且不可点
202
211
  "[" + ZOOM_ATTR + '="min"] .' + ZOOM_OUT_CLASS + ",[" + ZOOM_ATTR + '="max"] .' + ZOOM_IN_CLASS + "{opacity:.3;pointer-events:none}",
@@ -213,6 +222,18 @@ window.__ModuleLoader__.load({
213
222
  "." + MMD_CLASS + "-stage{transform-origin:0 0;display:flex;flex-direction:column;justify-content:center;min-height:var(--md-plus-mmd-min-height)}",
214
223
  "." + MMD_CLASS + " svg{display:block;margin:0 auto;max-width:100%;height:auto}",
215
224
  "." + MMD_CLASS + "-error{margin:0 16px 12px;padding:8px 12px;border-radius:8px;font:12px/18px var(--ds-font-family-code,monospace);white-space:pre-wrap;color:var(--dsw-alias-state-error-primary,#d33);background:var(--dsw-alias-bg-layer-2,rgb(127 127 127 / 8%))}",
225
+ // ── 全屏查看:复杂时序图不挤在聊天栏里,默认按整图铺满视口 ─────
226
+ "." + MODAL_CLASS + "{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:calc(var(--dsh-windows-titlebar-height,0px) + 16px) 24px 24px;background:rgb(0 0 0 / 48%)}",
227
+ "." + MODAL_CLASS + "-panel{display:flex;flex-direction:column;width:min(1600px,100%);height:min(1000px,100%);overflow:hidden;border:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 18%));border-radius:12px;background:var(--dsw-alias-bg-base,#fff);box-shadow:0 24px 80px rgb(0 0 0 / 25%)}",
228
+ "." + MODAL_CLASS + "-bar{display:flex;align-items:center;gap:8px;flex:none;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 18%))}",
229
+ "." + MODAL_CLASS + "-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:600 13px/18px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-primary)}",
230
+ "." + MODAL_CLASS + "-actions{display:flex;align-items:center;gap:6px;flex:none}",
231
+ "." + MODAL_CLASS + "-actions button{appearance:none;border:0;border-radius:6px;padding:5px 10px;background:var(--dsw-alias-interactive-bg-hover,rgb(127 127 127 / 12%));color:var(--dsw-alias-label-primary);font:12px/18px var(--dsw-font-family-sans,system-ui,sans-serif);cursor:pointer}",
232
+ "." + MODAL_CLASS + "-actions button:hover{background:var(--dsw-alias-interactive-bg-active,rgb(76 110 245 / 12%))}",
233
+ "." + MODAL_CLASS + "-body{display:flex;flex:1;min-height:0;padding:16px}",
234
+ "." + MODAL_CLASS + "-viewport{flex:1;min-height:0}",
235
+ "." + MODAL_CLASS + "-stage{display:inline-flex;align-items:center;justify-content:center;min-height:0;flex:none}",
236
+ "." + MODAL_CLASS + " svg{display:block}",
216
237
  // 高亮宿主不参与布局,让里面的 <pre> 直接吃到卡片样式
217
238
  "." + HOST_CLASS + "{display:contents}",
218
239
  // 藏掉纯文本回退,只留着色结果;原节点仍在,复制读的还是它
@@ -378,12 +399,19 @@ window.__ModuleLoader__.load({
378
399
  var next = block.getAttribute(MMD_ATTR) === "code" ? "diagram" : "code";
379
400
  block.setAttribute(MMD_ATTR, next);
380
401
  }
402
+ },
403
+ {
404
+ cls: EXPAND_CLASS,
405
+ title: "\u5168\u5C4F\u67E5\u770B",
406
+ aria: "\u5168\u5C4F\u67E5\u770B\u6D41\u7A0B\u56FE",
407
+ run: (block) => openFullscreen(block)
381
408
  }
382
409
  ];
383
410
  function decorate(block) {
384
411
  if (!block.isConnected) return;
385
412
  var banner = block.querySelector(BANNER);
386
- var copy = banner?.querySelector("button:not(." + BTN_CLASS + ")");
413
+ var nativeButtons = banner?.querySelectorAll("button:not(." + BTN_CLASS + ")");
414
+ var copy = nativeButtons === void 0 || nativeButtons.length === 0 ? null : nativeButtons[nativeButtons.length - 1];
387
415
  var action = copy?.parentElement;
388
416
  if (action === null || action === void 0) return;
389
417
  var specs = readLang(block) !== "" && diagramKind(block) !== null ? MMD_BUTTONS : PLAIN_BUTTONS;
@@ -504,7 +532,7 @@ window.__ModuleLoader__.load({
504
532
  function zoomState(block) {
505
533
  var state = zooms.get(block);
506
534
  if (state === void 0) {
507
- state = { scale: 1, x: 0, y: 0 };
535
+ state = { scale: 1, x: 0, y: 0, min: ZOOM_MIN_INLINE };
508
536
  zooms.set(block, state);
509
537
  }
510
538
  return state;
@@ -521,7 +549,7 @@ window.__ModuleLoader__.load({
521
549
  stage.parentElement.setAttribute("data-zoomed", state.scale > 1.001 ? "on" : "off");
522
550
  block.setAttribute(
523
551
  ZOOM_ATTR,
524
- state.scale <= 1.001 ? "min" : state.scale >= ZOOM_MAX ? "max" : "mid"
552
+ state.scale <= state.min + ZOOM_MIN_EPSILON ? "min" : state.scale >= ZOOM_MAX ? "max" : "mid"
525
553
  );
526
554
  }
527
555
  function clampPan(block, state) {
@@ -537,7 +565,7 @@ window.__ModuleLoader__.load({
537
565
  var stage = stageOf(block);
538
566
  if (stage === null) return;
539
567
  var state = zoomState(block);
540
- var next = Math.min(ZOOM_MAX, Math.max(1, state.scale * factor));
568
+ var next = Math.min(ZOOM_MAX, Math.max(state.min, state.scale * factor));
541
569
  if (Math.abs(next - state.scale) < 1e-3) return;
542
570
  var box = stage.parentElement.getBoundingClientRect();
543
571
  var anchorX = (clientX ?? box.left + box.width / 2) - box.left;
@@ -549,7 +577,7 @@ window.__ModuleLoader__.load({
549
577
  clampPan(block, state);
550
578
  paint(block);
551
579
  }
552
- function bindViewport(block, viewport) {
580
+ function bindViewport(block, viewport, resetView) {
553
581
  var drag = null;
554
582
  viewport.addEventListener(
555
583
  "wheel",
@@ -591,10 +619,134 @@ window.__ModuleLoader__.load({
591
619
  viewport.addEventListener("pointerup", stopDrag);
592
620
  viewport.addEventListener("pointercancel", stopDrag);
593
621
  viewport.addEventListener("dblclick", () => {
622
+ if (resetView !== void 0) {
623
+ resetView();
624
+ return;
625
+ }
594
626
  zooms.delete(block);
595
627
  paint(block);
596
628
  });
597
629
  }
630
+ function fitToView(scope) {
631
+ var stage = stageOf(scope);
632
+ if (stage === null) return;
633
+ var box = stage.parentElement.getBoundingClientRect();
634
+ var svg = stage.querySelector("svg");
635
+ var width = stage.offsetWidth || parseFloat(svg?.getAttribute("width") ?? "") || 0;
636
+ var height = stage.offsetHeight || parseFloat(svg?.getAttribute("height") ?? "") || 0;
637
+ if (box.width <= 0 || box.height <= 0 || width <= 0 || height <= 0) return;
638
+ var state = zoomState(scope);
639
+ var scale = Math.min(1, (box.width - 32) / width, (box.height - 32) / height);
640
+ state.scale = Math.max(state.min, scale);
641
+ state.x = 0;
642
+ state.y = 0;
643
+ clampPan(scope, state);
644
+ paint(scope);
645
+ }
646
+ function svgSize(svg) {
647
+ var box = (svg.getAttribute("viewBox") ?? "").trim().split(/[\s,]+/).map(Number);
648
+ if (box.length === 4 && box[2] > 0 && box[3] > 0) {
649
+ return { width: box[2], height: box[3] };
650
+ }
651
+ var rect = svg.getBoundingClientRect();
652
+ return { width: rect.width, height: rect.height };
653
+ }
654
+ function openFullscreen(block) {
655
+ if (document.querySelector("." + MODAL_CLASS) !== null) return;
656
+ var svg = block.querySelector("." + MMD_CLASS + " svg");
657
+ if (svg === null) return;
658
+ var size = svgSize(svg);
659
+ var overlay = document.createElement("div");
660
+ overlay.className = MODAL_CLASS;
661
+ overlay.setAttribute("role", "dialog");
662
+ overlay.setAttribute("aria-modal", "true");
663
+ overlay.setAttribute("aria-label", "\u6D41\u7A0B\u56FE\u5168\u5C4F\u67E5\u770B");
664
+ var panel = document.createElement("div");
665
+ panel.className = MODAL_CLASS + "-panel";
666
+ var bar = document.createElement("div");
667
+ bar.className = MODAL_CLASS + "-bar";
668
+ var title = document.createElement("div");
669
+ title.className = MODAL_CLASS + "-title";
670
+ title.textContent = "\u6D41\u7A0B\u56FE";
671
+ var actions = document.createElement("div");
672
+ actions.className = MODAL_CLASS + "-actions";
673
+ var fitButton = document.createElement("button");
674
+ fitButton.type = "button";
675
+ fitButton.textContent = "\u9002\u5E94\u6574\u56FE";
676
+ var zoomOut = document.createElement("button");
677
+ zoomOut.type = "button";
678
+ zoomOut.textContent = "\u2212";
679
+ zoomOut.setAttribute("aria-label", "\u7F29\u5C0F");
680
+ var zoomIn = document.createElement("button");
681
+ zoomIn.type = "button";
682
+ zoomIn.textContent = "+";
683
+ zoomIn.setAttribute("aria-label", "\u653E\u5927");
684
+ var closeButton = document.createElement("button");
685
+ closeButton.type = "button";
686
+ closeButton.textContent = "\u5173\u95ED";
687
+ actions.appendChild(fitButton);
688
+ actions.appendChild(zoomOut);
689
+ actions.appendChild(zoomIn);
690
+ actions.appendChild(closeButton);
691
+ bar.appendChild(title);
692
+ bar.appendChild(actions);
693
+ panel.appendChild(bar);
694
+ var body = document.createElement("div");
695
+ body.className = MODAL_CLASS + "-body";
696
+ var viewport = document.createElement("div");
697
+ viewport.className = MMD_CLASS + "-viewport " + MODAL_CLASS + "-viewport";
698
+ var stage = document.createElement("div");
699
+ stage.className = MMD_CLASS + "-stage " + MODAL_CLASS + "-stage";
700
+ var clone = svg.cloneNode(true);
701
+ if (size.width > 0 && size.height > 0) {
702
+ clone.setAttribute("width", String(size.width));
703
+ clone.setAttribute("height", String(size.height));
704
+ clone.style.width = size.width + "px";
705
+ clone.style.height = size.height + "px";
706
+ }
707
+ clone.style.maxWidth = "none";
708
+ stage.appendChild(clone);
709
+ viewport.appendChild(stage);
710
+ body.appendChild(viewport);
711
+ panel.appendChild(body);
712
+ overlay.appendChild(panel);
713
+ var previousFocus = document.activeElement;
714
+ var previousOverflow = document.documentElement.style.overflow;
715
+ var closed = false;
716
+ var reset = () => fitToView(overlay);
717
+ function closeViewer() {
718
+ if (closed) return;
719
+ closed = true;
720
+ document.removeEventListener("keydown", onKey, true);
721
+ overlay.remove();
722
+ document.documentElement.style.overflow = previousOverflow;
723
+ zooms.delete(overlay);
724
+ if (previousFocus !== null && typeof previousFocus.focus === "function") {
725
+ previousFocus.focus();
726
+ }
727
+ }
728
+ function onKey(event) {
729
+ if (event.key === "Escape") {
730
+ event.preventDefault();
731
+ closeViewer();
732
+ }
733
+ }
734
+ overlay.addEventListener("pointerdown", (event) => {
735
+ if (event.target === overlay) closeViewer();
736
+ });
737
+ fitButton.addEventListener("click", reset);
738
+ zoomOut.addEventListener("click", () => zoomBy(overlay, 1 / ZOOM_STEP));
739
+ zoomIn.addEventListener("click", () => zoomBy(overlay, ZOOM_STEP));
740
+ closeButton.addEventListener("click", closeViewer);
741
+ document.addEventListener("keydown", onKey, true);
742
+ zooms.set(overlay, { scale: 1, x: 0, y: 0, min: ZOOM_MIN_MODAL });
743
+ bindViewport(overlay, viewport, reset);
744
+ document.documentElement.style.overflow = "hidden";
745
+ document.body.appendChild(overlay);
746
+ closeButton.focus();
747
+ var schedule2 = window.requestAnimationFrame ?? ((cb) => window.setTimeout(cb, 0));
748
+ schedule2(reset);
749
+ }
598
750
  function failDiagram(block, content, pre, key, error) {
599
751
  content.querySelector("." + MMD_CLASS)?.remove();
600
752
  var note = content.querySelector("." + MMD_CLASS + "-error") ?? document.createElement("p");
@@ -625,10 +777,11 @@ window.__ModuleLoader__.load({
625
777
  for (const node of injected) node.remove();
626
778
  injected.clear();
627
779
  for (const node of document.querySelectorAll(
628
- "." + MMD_CLASS + ", ." + MMD_CLASS + "-error"
780
+ "." + MMD_CLASS + ", ." + MMD_CLASS + "-error, ." + MODAL_CLASS
629
781
  )) {
630
782
  node.remove();
631
783
  }
784
+ document.documentElement.style.overflow = "";
632
785
  document.querySelector("style[" + STYLE_ATTR + "]")?.remove();
633
786
  for (const script of document.querySelectorAll("script[" + RUNTIME_ATTR + "]")) {
634
787
  script.remove();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-md-plus",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
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",