dsh-md-plus 0.4.4 → 0.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/lib/client.js CHANGED
@@ -18,6 +18,8 @@ window.__ModuleLoader__.load({
18
18
  var BTN_CLASS = "md-plus-btn";
19
19
  var ZOOM_IN_CLASS = "md-plus-zoom-in";
20
20
  var ZOOM_OUT_CLASS = "md-plus-zoom-out";
21
+ var COPY_SOURCE_CLASS = "md-plus-copy-source";
22
+ var DOWNLOAD_CLASS = "md-plus-download";
21
23
  var ZOOM_ATTR = "data-md-plus-zoom";
22
24
  var ZOOM_STEP = 1.25;
23
25
  var ZOOM_MAX = 6;
@@ -52,6 +54,10 @@ window.__ModuleLoader__.load({
52
54
  toggleAria: "\u5207\u6362\u56FE\u5F62\u4E0E\u4EE3\u7801",
53
55
  fullscreen: "\u5168\u5C4F\u67E5\u770B",
54
56
  fullscreenAria: "\u5168\u5C4F\u67E5\u770B\u6D41\u7A0B\u56FE",
57
+ copySource: "\u590D\u5236\u56FE\u6E90",
58
+ copySourceAria: "\u590D\u5236 Mermaid \u56FE\u6E90",
59
+ downloadCode: "\u4E0B\u8F7D\u4EE3\u7801",
60
+ downloadCodeAria: "\u4E0B\u8F7D\u4EE3\u7801\u4E3A\u6587\u4EF6",
55
61
  theme: "\u5207\u6362 Mermaid \u914D\u8272",
56
62
  themeAria: "\u5207\u6362 Mermaid \u914D\u8272",
57
63
  themePrefix: "\u914D\u8272\uFF1A",
@@ -65,11 +71,16 @@ window.__ModuleLoader__.load({
65
71
  fit: "\u9002\u5E94\u6574\u56FE",
66
72
  downloadSvg: "\u4E0B\u8F7D SVG",
67
73
  downloadPng: "\u4E0B\u8F7D PNG",
74
+ exportFailed: "\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u6539\u7528 SVG",
68
75
  close: "\u5173\u95ED",
69
76
  tableCsv: "CSV",
77
+ tableTsv: "TSV",
70
78
  tableMd: "Markdown",
79
+ tableHtml: "\u5BCC\u6587\u672C",
71
80
  copyCsvTitle: "\u590D\u5236\u8868\u683C\u4E3A CSV",
81
+ copyTsvTitle: "\u590D\u5236\u8868\u683C\u4E3A TSV",
72
82
  copyMdTitle: "\u590D\u5236\u8868\u683C\u4E3A Markdown",
83
+ copyHtmlTitle: "\u590D\u5236\u8868\u683C\u4E3A\u5BCC\u6587\u672C",
73
84
  copied: "\u5DF2\u590D\u5236",
74
85
  copyFailed: "\u590D\u5236\u5931\u8D25",
75
86
  imageViewer: "\u56FE\u7247\u9884\u89C8",
@@ -83,6 +94,8 @@ window.__ModuleLoader__.load({
83
94
  settingsViewDiagram: "\u56FE\u5F62",
84
95
  settingsViewCode: "\u4EE3\u7801",
85
96
  settingsFold: "\u957F\u5185\u5BB9\u6298\u53E0\u9608\u503C",
97
+ settingsFoldGroup: "\u6298\u53E0\u5185\u5BB9",
98
+ settingsFileGroup: "\u8BBE\u7F6E\u6587\u4EF6",
86
99
  settingsFoldCode: "\u4EE3\u7801\u5757",
87
100
  settingsFoldTable: "\u8868\u683C",
88
101
  settingsFoldDiagram: "\u56FE\u8868",
@@ -90,6 +103,15 @@ window.__ModuleLoader__.load({
90
103
  settingsFoldTableHint: "\u8868\u683C\u9AD8\u5EA6\u8D85\u8FC7\u6B64\u503C\u540E\u6298\u53E0",
91
104
  settingsFoldDiagramHint: "\u56FE\u5F62\u9AD8\u5EA6\u8D85\u8FC7\u6B64\u503C\u540E\u6298\u53E0",
92
105
  settingsFoldHint: "\u5355\u4F4D px\uFF0C0 \u8868\u793A\u4E0D\u6298\u53E0",
106
+ settingsFreezeRows: "\u51BB\u7ED3\u884C\u6570",
107
+ settingsFreezeRowsHint: "\u9ED8\u8BA4\u51BB\u7ED3 1 \u884C\u8868\u5934\uFF0C0 \u8868\u793A\u4E0D\u51BB\u7ED3",
108
+ settingsFreezeCols: "\u51BB\u7ED3\u5217\u6570",
109
+ settingsFreezeColsHint: "\u9ED8\u8BA4 0 \u5217\uFF0C\u6309\u9700\u6307\u5B9A",
110
+ foldExpandAll: "\u5168\u90E8\u5C55\u5F00",
111
+ foldCollapseAll: "\u5168\u90E8\u6536\u8D77",
112
+ settingsExport: "\u5BFC\u51FA\u8BBE\u7F6E",
113
+ settingsImport: "\u5BFC\u5165\u8BBE\u7F6E",
114
+ settingsImportFailed: "\u5BFC\u5165\u5931\u8D25",
93
115
  settingsSave: "\u4FDD\u5B58",
94
116
  settingsReset: "\u6062\u590D\u9ED8\u8BA4",
95
117
  settingsClose: "\u5173\u95ED"
@@ -105,6 +127,10 @@ window.__ModuleLoader__.load({
105
127
  toggleAria: "Switch diagram and code",
106
128
  fullscreen: "Fullscreen",
107
129
  fullscreenAria: "View diagram fullscreen",
130
+ copySource: "Copy source",
131
+ copySourceAria: "Copy Mermaid source",
132
+ downloadCode: "Download code",
133
+ downloadCodeAria: "Download code as a file",
108
134
  theme: "Change Mermaid theme",
109
135
  themeAria: "Change Mermaid theme",
110
136
  themePrefix: "Theme: ",
@@ -118,11 +144,16 @@ window.__ModuleLoader__.load({
118
144
  fit: "Fit",
119
145
  downloadSvg: "Download SVG",
120
146
  downloadPng: "Download PNG",
147
+ exportFailed: "Export failed; try SVG instead",
121
148
  close: "Close",
122
149
  tableCsv: "CSV",
150
+ tableTsv: "TSV",
123
151
  tableMd: "Markdown",
152
+ tableHtml: "Rich",
124
153
  copyCsvTitle: "Copy table as CSV",
154
+ copyTsvTitle: "Copy table as TSV",
125
155
  copyMdTitle: "Copy table as Markdown",
156
+ copyHtmlTitle: "Copy table as rich text",
126
157
  copied: "Copied",
127
158
  copyFailed: "Copy failed",
128
159
  imageViewer: "Image preview",
@@ -136,6 +167,8 @@ window.__ModuleLoader__.load({
136
167
  settingsViewDiagram: "Diagram",
137
168
  settingsViewCode: "Code",
138
169
  settingsFold: "Fold thresholds",
170
+ settingsFoldGroup: "Fold content",
171
+ settingsFileGroup: "Settings file",
139
172
  settingsFoldCode: "Code block",
140
173
  settingsFoldTable: "Table",
141
174
  settingsFoldDiagram: "Diagram",
@@ -143,6 +176,15 @@ window.__ModuleLoader__.load({
143
176
  settingsFoldTableHint: "Fold when the table is taller than this",
144
177
  settingsFoldDiagramHint: "Fold when the diagram is taller than this",
145
178
  settingsFoldHint: "Pixels; 0 disables folding",
179
+ settingsFreezeRows: "Freeze rows",
180
+ settingsFreezeRowsHint: "Defaults to 1 header row; 0 disables freezing",
181
+ settingsFreezeCols: "Freeze columns",
182
+ settingsFreezeColsHint: "Defaults to 0 columns; set as needed",
183
+ foldExpandAll: "Expand all",
184
+ foldCollapseAll: "Collapse all",
185
+ settingsExport: "Export settings",
186
+ settingsImport: "Import settings",
187
+ settingsImportFailed: "Import failed",
146
188
  settingsSave: "Save",
147
189
  settingsReset: "Reset to defaults",
148
190
  settingsClose: "Close"
@@ -154,7 +196,6 @@ window.__ModuleLoader__.load({
154
196
  }
155
197
  var MODAL_CLASS = "md-plus-mmd-modal";
156
198
  var DONE_ATTR = "data-md-plus";
157
- var KEY_ATTR = "data-md-plus-key";
158
199
  var WRAP_ATTR = "data-md-plus-wrap";
159
200
  var MMD_ATTR = "data-md-plus-mmd";
160
201
  var FOLD_ATTR = "data-md-plus-fold";
@@ -163,6 +204,9 @@ window.__ModuleLoader__.load({
163
204
  var FOLD_LIMITS = { code: 480, table: 520, diagram: 720 };
164
205
  var TABLE_TOOLS_CLASS = "md-plus-table-tools";
165
206
  var TABLE_DONE_ATTR = "data-md-plus-table";
207
+ var FREEZE_ROW_CLASS = "md-plus-freeze-row";
208
+ var FREEZE_COL_CLASS = "md-plus-freeze-col";
209
+ var FREEZE_CROSS_CLASS = "md-plus-freeze-cross";
166
210
  var IMG_MODAL_CLASS = "md-plus-img-modal";
167
211
  var NATIVE_IMAGE_BUTTON = 'button[class*="imageButton"]';
168
212
  var SETTINGS_BTN_CLASS = "md-plus-settings-btn";
@@ -173,14 +217,19 @@ window.__ModuleLoader__.load({
173
217
  diagramView: "diagram",
174
218
  foldCode: FOLD_LIMITS.code,
175
219
  foldTable: FOLD_LIMITS.table,
176
- foldDiagram: FOLD_LIMITS.diagram
220
+ foldDiagram: FOLD_LIMITS.diagram,
221
+ freezeRows: 1,
222
+ freezeCols: 0
177
223
  };
178
224
  var STYLE_ATTR = "data-md-plus-style";
179
225
  var RUNTIME_ATTR = "data-md-plus-runtime";
180
226
  var DEBOUNCE_MS = 160;
181
227
  var memorySettings = null;
182
228
  var memoryTheme = null;
229
+ var settingsCache = null;
230
+ var themeCache = null;
183
231
  function readSettings() {
232
+ if (settingsCache !== null) return { ...settingsCache };
184
233
  var settings = { ...DEFAULT_SETTINGS };
185
234
  try {
186
235
  var raw = window.localStorage.getItem(SETTINGS_KEY);
@@ -195,15 +244,23 @@ window.__ModuleLoader__.load({
195
244
  var value = Number(saved[key]);
196
245
  if (Number.isFinite(value) && value >= 0) settings[key] = Math.min(4e3, Math.round(value));
197
246
  }
247
+ for (var freezeKey of ["freezeRows", "freezeCols"]) {
248
+ var freezeValue = Number(saved[freezeKey]);
249
+ if (saved[freezeKey] !== void 0 && Number.isFinite(freezeValue) && freezeValue >= 0) {
250
+ settings[freezeKey] = Math.min(5, Math.round(freezeValue));
251
+ }
252
+ }
198
253
  }
199
254
  memorySettings = { ...settings };
200
255
  } catch {
201
- if (memorySettings !== null) return { ...DEFAULT_SETTINGS, ...memorySettings };
256
+ if (memorySettings !== null) settings = { ...DEFAULT_SETTINGS, ...memorySettings };
202
257
  }
203
- return settings;
258
+ settingsCache = { ...settings };
259
+ return { ...settingsCache };
204
260
  }
205
261
  function saveSettings(next) {
206
262
  memorySettings = { ...next };
263
+ settingsCache = { ...next };
207
264
  try {
208
265
  window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(next));
209
266
  } catch {
@@ -350,6 +407,9 @@ window.__ModuleLoader__.load({
350
407
  var ICON_COPY = icon(
351
408
  "<rect x='9' y='9' width='12' height='12' rx='2.5'/><path d='M6 15H5.5A2.5 2.5 0 0 1 3 12.5v-7A2.5 2.5 0 0 1 5.5 3h7A2.5 2.5 0 0 1 15 5.5V6'/>"
352
409
  );
410
+ var ICON_DOWNLOAD = icon(
411
+ "<path d='M12 3v12'/><path d='m7 10 5 5 5-5'/><path d='M5 21h14'/>"
412
+ );
353
413
  var ICON_NOWRAP = icon(
354
414
  "<path d='M3 12h12.5'/><path d='M12.5 8.5 16.5 12l-4 3.5'/><path d='M20 6v12'/>"
355
415
  );
@@ -379,7 +439,7 @@ window.__ModuleLoader__.load({
379
439
  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";
380
440
  var CSS = [
381
441
  // ── 卡片:整块一个底色,顶栏不再是独立色带 ──────────────────────
382
- ".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 + "}",
442
+ ".md-code-block{--md-plus-mmd-min-height:200px;--md-plus-icon:" + ICON_CODE + ";--md-plus-icon-copy:" + ICON_COPY + ";--md-plus-icon-download:" + ICON_DOWNLOAD + ";--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 + "}",
383
443
  // bannerWrap 的原底色是 --dsw-alias-bg-base(#fff),会跟卡片灰拉开一条带
384
444
  ".md-code-block > div:has(> " + BANNER + "){background:transparent}",
385
445
  // ── 头部:收紧,并把空着的左边填上 ─────────────────────────────
@@ -395,8 +455,10 @@ window.__ModuleLoader__.load({
395
455
  // ── 间距:头部与首行代码之间补 6px ──────────────────────────────
396
456
  // pre 的行高本身就在首行文字上方留了约半行空白;补 6px 后实测间距约 17px
397
457
  // (原来给 0 时只有 11px,贴着看太挤)
398
- ".md-code-block pre{padding:6px 16px 16px;font-size:var(--md-plus-code-font-size,inherit)}",
399
- ".md-code-block pre{line-height:var(--md-plus-code-line-height,inherit)}",
458
+ // 未设字号时不能回退 inherit:pre 的父级是正文,会把宿主的代码字号
459
+ // (--dsh-code-font-size)盖成正文大小;回退到宿主变量才是真「跟随宿主」。
460
+ ".md-code-block pre{padding:6px 16px 16px;font-size:var(--md-plus-code-font-size,var(--dsh-code-font-size,11px))}",
461
+ ".md-code-block pre{line-height:var(--md-plus-code-line-height,calc(19px + var(--dsh-code-font-delta,0px)))}",
400
462
  // DSH 0.2.1 把内容容器设成 display:contents,折叠需要真实的盒子才能量高度和裁剪
401
463
  ".md-code-block > [data-code-block-content]{display:block}",
402
464
  // ── 头部按钮:文字换成图标 ──────────────────────────────────────
@@ -417,6 +479,8 @@ window.__ModuleLoader__.load({
417
479
  ".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}",
418
480
  ".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}",
419
481
  ".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}",
482
+ ".md-code-block " + BANNER + " ." + COPY_SOURCE_CLASS + "::before{-webkit-mask:var(--md-plus-icon-copy) center/contain no-repeat;mask:var(--md-plus-icon-copy) center/contain no-repeat}",
483
+ ".md-code-block " + BANNER + " ." + DOWNLOAD_CLASS + "::before{-webkit-mask:var(--md-plus-icon-download) center/contain no-repeat;mask:var(--md-plus-icon-download) center/contain no-repeat}",
420
484
  ".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}",
421
485
  "[" + 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%))}",
422
486
  // 已经到最小 / 最大时,对应那颗按钮变淡且不可点
@@ -465,19 +529,29 @@ window.__ModuleLoader__.load({
465
529
  "." + SETTINGS_BTN_CLASS + "::before{content:'';width:16px;height:16px;background:currentColor;-webkit-mask:var(--md-plus-icon-gear) center/contain no-repeat;mask:var(--md-plus-icon-gear) center/contain no-repeat}",
466
530
  "." + SETTINGS_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%)}",
467
531
  "." + SETTINGS_MODAL_CLASS + "-panel{width:min(440px,100%);max-height:86vh;overflow:auto;padding:16px 18px 14px;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%)}",
468
- "." + SETTINGS_MODAL_CLASS + "-title{margin:0 0 6px;font:600 14px/22px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-primary)}",
469
- "." + SETTINGS_MODAL_CLASS + "-row{display:flex;align-items:center;gap:12px;margin:11px 0}",
532
+ "." + SETTINGS_MODAL_CLASS + "-title{margin:0 0 10px;font:600 14px/22px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-primary)}",
533
+ "." + SETTINGS_MODAL_CLASS + "-row{display:flex;align-items:center;gap:12px;min-height:36px;margin:0 0 10px}",
470
534
  "." + SETTINGS_MODAL_CLASS + "-label{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0;font:13px/20px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-primary)}",
471
535
  "." + SETTINGS_MODAL_CLASS + "-label-title{display:block}",
472
536
  "." + SETTINGS_MODAL_CLASS + "-label-hint{display:block;font:11px/16px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-tertiary)}",
473
537
  "." + SETTINGS_MODAL_CLASS + "-hint{margin:-4px 0 8px;font:11px/16px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-tertiary)}",
538
+ "." + SETTINGS_MODAL_CLASS + "-section{margin:14px 0 8px;font:600 12px/18px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-secondary)}",
474
539
  "." + SETTINGS_MODAL_CLASS + " input[type=number]{width:78px;padding:4px 6px;border:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 22%));border-radius:6px;background:var(--dsw-alias-bg-layer-2,rgb(127 127 127 / 6%));color:var(--dsw-alias-label-primary);font:13px/18px var(--dsw-font-family-sans,system-ui,sans-serif)}",
475
540
  "." + SETTINGS_MODAL_CLASS + " select{padding:4px 6px;border:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 22%));border-radius:6px;background:var(--dsw-alias-bg-layer-2,rgb(127 127 127 / 6%));color:var(--dsw-alias-label-primary);font:13px/18px var(--dsw-font-family-sans,system-ui,sans-serif)}",
476
- "." + SETTINGS_MODAL_CLASS + "-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}",
477
- "." + SETTINGS_MODAL_CLASS + "-actions button{appearance:none;border:0;border-radius:6px;padding:5px 12px;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}",
478
- "." + SETTINGS_MODAL_CLASS + "-actions button:not(." + SETTINGS_MODAL_CLASS + "-primary):hover{background:var(--dsw-alias-interactive-bg-active,rgb(76 110 245 / 12%))}",
541
+ "." + SETTINGS_MODAL_CLASS + "-footer{margin-top:18px;padding-top:14px;border-top:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 18%))}",
542
+ "." + SETTINGS_MODAL_CLASS + "-utilities{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px 18px;margin:0 0 14px}",
543
+ "." + SETTINGS_MODAL_CLASS + "-fold-actions,." + SETTINGS_MODAL_CLASS + "-transfer-actions{display:flex;align-items:center;gap:8px}",
544
+ "." + SETTINGS_MODAL_CLASS + "-utility-label{font:11px/16px var(--dsw-font-family-sans,system-ui,sans-serif);color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
545
+ "." + SETTINGS_MODAL_CLASS + "-segment{display:inline-flex;align-items:center;gap:1px;padding:2px;border:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 18%));border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgb(127 127 127 / 5%))}",
546
+ "." + SETTINGS_MODAL_CLASS + "-segment button{appearance:none;border:0;border-radius:6px;padding:4px 8px;background:transparent;color:var(--dsw-alias-label-secondary);font:12px/18px var(--dsw-font-family-sans,system-ui,sans-serif);cursor:pointer}",
547
+ "." + SETTINGS_MODAL_CLASS + "-segment button:hover{background:var(--dsw-alias-interactive-bg-hover,rgb(127 127 127 / 12%));color:var(--dsw-alias-label-primary)}",
548
+ "." + SETTINGS_MODAL_CLASS + "-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-top:0}",
549
+ "." + SETTINGS_MODAL_CLASS + "-actions button{appearance:none;border:0;border-radius:7px;min-height:30px;padding:5px 12px;background:transparent;color:var(--dsw-alias-label-secondary);font:12px/18px var(--dsw-font-family-sans,system-ui,sans-serif);cursor:pointer}",
550
+ "." + SETTINGS_MODAL_CLASS + "-actions button:first-child{margin-right:auto;color:var(--dsw-alias-label-tertiary)}",
551
+ "." + SETTINGS_MODAL_CLASS + "-actions button:not(." + SETTINGS_MODAL_CLASS + "-primary):hover{background:var(--dsw-alias-interactive-bg-hover,rgb(127 127 127 / 12%));color:var(--dsw-alias-label-primary)}",
479
552
  "." + SETTINGS_MODAL_CLASS + "-actions ." + SETTINGS_MODAL_CLASS + "-primary{background:var(--dsw-alias-button-primary-fill,#4c6ef5);color:var(--dsw-alias-label-primary-foreground,#fff)}",
480
553
  "." + SETTINGS_MODAL_CLASS + "-actions ." + SETTINGS_MODAL_CLASS + "-primary:hover{background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,#4c6ef5))}",
554
+ "." + SETTINGS_MODAL_CLASS + "-fold-actions button:focus-visible,." + SETTINGS_MODAL_CLASS + "-transfer-actions button:focus-visible,." + SETTINGS_MODAL_CLASS + "-actions button:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4c6ef5);outline-offset:1px}",
481
555
  // 高亮宿主不参与布局,让里面的 <pre> 直接吃到卡片样式
482
556
  "." + HOST_CLASS + "{display:contents}",
483
557
  // 藏掉纯文本回退,只留着色结果;原节点仍在,复制读的还是它
@@ -513,8 +587,15 @@ window.__ModuleLoader__.load({
513
587
  TABLE + " > table tr:last-child td{border-bottom:0}",
514
588
  // 宽表横向扫读时给个落点
515
589
  TABLE + " > table tbody tr:hover td{background:var(--md-plus-tbl-hover)}",
516
- // 长表往下滚时表头留在视口顶部;底色不透明才不会透出下面的行
517
- TABLE + " > table thead th{position:sticky;top:0;z-index:1}",
590
+ // 冻结行 / 列:具体偏移由客户端按实际行列尺寸算好,支持设置多行多列
591
+ TABLE + " > table ." + FREEZE_ROW_CLASS + "{position:sticky;z-index:2;box-shadow:0 1px 0 var(--md-plus-tbl-line)}",
592
+ TABLE + " > table th." + FREEZE_ROW_CLASS + "{background:var(--md-plus-tbl-head)}",
593
+ TABLE + " > table td." + FREEZE_ROW_CLASS + "{background:var(--dsw-alias-bg-base,#fff)}",
594
+ TABLE + " > table ." + FREEZE_COL_CLASS + "{position:sticky;z-index:1;box-shadow:1px 0 0 var(--md-plus-tbl-line)}",
595
+ TABLE + " > table th." + FREEZE_COL_CLASS + "{background:var(--md-plus-tbl-head)}",
596
+ TABLE + " > table td." + FREEZE_COL_CLASS + "{background:var(--dsw-alias-bg-base,#fff)}",
597
+ TABLE + " > table ." + FREEZE_CROSS_CLASS + "{z-index:3}",
598
+ TABLE + " > table tbody tr:hover td." + FREEZE_ROW_CLASS + "," + TABLE + " > table tbody tr:hover td." + FREEZE_COL_CLASS + "{background:var(--md-plus-tbl-hover)}",
518
599
  // 复制按钮放在表尾右侧:不压住表头文字,表格折叠时也能随展开一起出现
519
600
  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}",
520
601
  TABLE + " > table > caption." + TABLE_TOOLS_CLASS + " > span{position:absolute;right:0;top:0;display:flex;gap:0;white-space:nowrap}",
@@ -531,14 +612,26 @@ window.__ModuleLoader__.load({
531
612
  // overscroll-behavior:auto 时才会把纵向滚轮交还给页面。
532
613
  // 这里同时写明 min/max-height:基础表格规则的 max-height 特异性更高,不补会压过设置值;
533
614
  // 最小高度保证极低阈值下「展开」按钮仍完整可见。
534
- TABLE + "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"]{min-height:48px;max-height:var(--md-plus-fold-max-height);overflow-x:auto;overflow-y:clip}'
615
+ TABLE + "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"]{min-height:48px;max-height:var(--md-plus-fold-max-height);overflow-x:auto;overflow-y:clip}',
616
+ // 展开态的「收起」按钮在表尾文档流里,纵向保持滚到底才出现;
617
+ // 横向用 sticky 钉在可视区中间,宽表左右滚动时不会跟着表内容跑掉。
618
+ // 折叠态按钮是绝对定位,横向跟随由 sync() 写 left 完成。
619
+ TABLE + " ." + FOLD_BTN_CLASS + "{position:sticky;left:50%;transform:translateX(-50%)}"
535
620
  ].join("\n");
536
621
  var scripts = /* @__PURE__ */ new Map();
537
622
  var pending = /* @__PURE__ */ new Set();
538
623
  var foldSyncs = /* @__PURE__ */ new WeakMap();
624
+ var foldTargets = /* @__PURE__ */ new Set();
625
+ var foldResizeObserver = null;
626
+ var foldResizeHandler = null;
627
+ var diagramKindCache = /* @__PURE__ */ new WeakMap();
628
+ var cacheKeys = /* @__PURE__ */ new WeakMap();
539
629
  var timer = null;
540
630
  var rescanQueued = false;
541
631
  var injected = /* @__PURE__ */ new Set();
632
+ var tableFreezeObserver = null;
633
+ var tableFreezeTargets = /* @__PURE__ */ new Set();
634
+ var tableFreezeState = /* @__PURE__ */ new WeakMap();
542
635
  var diagramSeq = 0;
543
636
  var disposed = false;
544
637
  function injectStyle() {
@@ -668,7 +761,7 @@ window.__ModuleLoader__.load({
668
761
  var single = MMD_SINGLE.exec(value);
669
762
  return single === null ? null : single[1];
670
763
  }
671
- function diagramKind(block) {
764
+ function computeDiagramKind(block) {
672
765
  var lang = readLang(block);
673
766
  if (MMD_LANGS.has(lang)) return "diagram";
674
767
  if (lang !== "") return null;
@@ -676,12 +769,22 @@ window.__ModuleLoader__.load({
676
769
  if (pre === null) return null;
677
770
  return sniffDiagramType(pre.textContent ?? "") === null ? null : "diagram";
678
771
  }
772
+ function diagramKind(block) {
773
+ if (diagramKindCache.has(block)) return diagramKindCache.get(block);
774
+ var kind = computeDiagramKind(block);
775
+ diagramKindCache.set(block, kind);
776
+ return kind;
777
+ }
679
778
  function isOurs(node) {
680
779
  var element = node?.nodeType === 1 ? node : node?.parentElement;
681
780
  return element?.closest?.(
682
- "." + HOST_CLASS + ", ." + BTN_CLASS + ", ." + MMD_CLASS + ", ." + FOLD_BTN_CLASS + ", ." + TABLE_TOOLS_CLASS + ", ." + IMG_MODAL_CLASS + ", ." + SETTINGS_BTN_CLASS + ", ." + SETTINGS_MODAL_CLASS
781
+ "." + HOST_CLASS + ", ." + BTN_CLASS + ", ." + MMD_CLASS + ", ." + FOLD_BTN_CLASS + ", ." + TABLE_TOOLS_CLASS + ", ." + IMG_MODAL_CLASS + ", ." + MODAL_CLASS + ", ." + SETTINGS_BTN_CLASS + ", ." + SETTINGS_MODAL_CLASS
683
782
  ) != null;
684
783
  }
784
+ function closestStreaming(node) {
785
+ var element = node?.nodeType === 1 ? node : node?.parentElement;
786
+ return element?.closest?.("[data-streaming]") ?? null;
787
+ }
685
788
  function schedule(block) {
686
789
  if (disposed) return;
687
790
  if (block !== void 0) pending.add(block);
@@ -692,6 +795,12 @@ window.__ModuleLoader__.load({
692
795
  function flush() {
693
796
  timer = null;
694
797
  if (disposed) return;
798
+ for (const node of injected) {
799
+ if (!node.isConnected) injected.delete(node);
800
+ }
801
+ cleanupFoldTargets();
802
+ cleanupTableFreezeTargets();
803
+ diagramKindCache = /* @__PURE__ */ new WeakMap();
695
804
  if (rescanQueued) {
696
805
  rescanQueued = false;
697
806
  for (const block of document.querySelectorAll(BLOCK)) pending.add(block);
@@ -699,6 +808,7 @@ window.__ModuleLoader__.load({
699
808
  var batch = Array.from(pending);
700
809
  pending.clear();
701
810
  for (const block of batch) {
811
+ if (closestStreaming(block) !== null) continue;
702
812
  decorate(block);
703
813
  void enhance(block);
704
814
  }
@@ -711,6 +821,7 @@ window.__ModuleLoader__.load({
711
821
  redrawDiagrams();
712
822
  return;
713
823
  }
824
+ if (closestStreaming(record.target) !== null) continue;
714
825
  if (isOurs(record.target)) continue;
715
826
  var added = record.addedNodes;
716
827
  var oursOnly = added.length > 0;
@@ -727,22 +838,27 @@ window.__ModuleLoader__.load({
727
838
  function redrawDiagrams() {
728
839
  for (const block of document.querySelectorAll("[" + MMD_ATTR + "]")) {
729
840
  if (block.getAttribute(MMD_ATTR) === "failed") continue;
730
- block.removeAttribute(KEY_ATTR);
841
+ cacheKeys.delete(block);
731
842
  pending.add(block);
732
843
  }
733
844
  schedule();
734
845
  }
735
- var PLAIN_BUTTONS = [
736
- {
737
- cls: TOOL_CLASS,
738
- titleKey: "wrapTitle",
739
- ariaKey: "wrapAria",
740
- run: (block) => {
741
- if (block.getAttribute(WRAP_ATTR) === "on") block.removeAttribute(WRAP_ATTR);
742
- else block.setAttribute(WRAP_ATTR, "on");
743
- }
846
+ var WRAP_BUTTON = {
847
+ cls: TOOL_CLASS,
848
+ titleKey: "wrapTitle",
849
+ ariaKey: "wrapAria",
850
+ run: (block) => {
851
+ if (block.getAttribute(WRAP_ATTR) === "on") block.removeAttribute(WRAP_ATTR);
852
+ else block.setAttribute(WRAP_ATTR, "on");
744
853
  }
745
- ];
854
+ };
855
+ var DOWNLOAD_BUTTON = {
856
+ cls: DOWNLOAD_CLASS,
857
+ titleKey: "downloadCode",
858
+ ariaKey: "downloadCodeAria",
859
+ run: (block) => downloadCode(block)
860
+ };
861
+ var PLAIN_BUTTONS = [WRAP_BUTTON, DOWNLOAD_BUTTON];
746
862
  var MMD_BUTTONS = [
747
863
  { cls: ZOOM_OUT_CLASS, titleKey: "zoomOut", ariaKey: "zoomOutAria", run: (block) => zoomBy(block, 1 / ZOOM_STEP) },
748
864
  { cls: ZOOM_IN_CLASS, titleKey: "zoomIn", ariaKey: "zoomInAria", run: (block) => zoomBy(block, ZOOM_STEP) },
@@ -771,6 +887,12 @@ window.__ModuleLoader__.load({
771
887
  titleKey: "theme",
772
888
  ariaKey: "themeAria",
773
889
  run: (block) => cycleMermaidTheme(block)
890
+ },
891
+ {
892
+ cls: COPY_SOURCE_CLASS,
893
+ titleKey: "copySource",
894
+ ariaKey: "copySourceAria",
895
+ run: (block, button) => copyDiagramSource(block, button)
774
896
  }
775
897
  ];
776
898
  var FAILED_BUTTONS = [
@@ -779,7 +901,7 @@ window.__ModuleLoader__.load({
779
901
  titleKey: "retry",
780
902
  ariaKey: "retryAria",
781
903
  run: (block) => {
782
- block.removeAttribute(KEY_ATTR);
904
+ cacheKeys.delete(block);
783
905
  block.removeAttribute(MMD_ATTR);
784
906
  block.querySelector("." + MMD_CLASS + "-error")?.remove();
785
907
  void enhance(block);
@@ -804,7 +926,8 @@ window.__ModuleLoader__.load({
804
926
  var label = readBannerLabel(block);
805
927
  var lang = readLang(block);
806
928
  if (lang !== "" && isPlaceholderLabel(label)) paintLanguageLabel(block, lang);
807
- var specs = block.getAttribute(MMD_ATTR) === "failed" ? FAILED_BUTTONS : diagramKind(block) !== null ? MMD_BUTTONS : findNativeWrap(nativeButtons) !== null ? [] : PLAIN_BUTTONS;
929
+ var hasNativeWrap = findNativeWrap(nativeButtons) !== null;
930
+ var specs = block.getAttribute(MMD_ATTR) === "failed" ? FAILED_BUTTONS : diagramKind(block) !== null ? MMD_BUTTONS : hasNativeWrap ? [DOWNLOAD_BUTTON] : PLAIN_BUTTONS;
808
931
  for (const existing of action.querySelectorAll("." + BTN_CLASS)) {
809
932
  if (specs.some((spec) => existing.classList.contains(spec.cls))) continue;
810
933
  existing.remove();
@@ -822,7 +945,7 @@ window.__ModuleLoader__.load({
822
945
  created.title = themeText;
823
946
  created.setAttribute("aria-label", themeText);
824
947
  }
825
- created.addEventListener("click", () => spec.run(block));
948
+ created.addEventListener("click", () => spec.run(block, created));
826
949
  action.insertBefore(created, copy);
827
950
  injected.add(created);
828
951
  }
@@ -832,13 +955,13 @@ window.__ModuleLoader__.load({
832
955
  if (host === null) return false;
833
956
  if (content.querySelector("pre.shiki:not(.md-plus-pre)") === null) return false;
834
957
  host.remove();
835
- block.removeAttribute(KEY_ATTR);
958
+ cacheKeys.delete(block);
836
959
  block.removeAttribute(DONE_ATTR);
837
960
  return true;
838
961
  }
839
962
  async function enhance(block) {
840
963
  if (!block.isConnected) return;
841
- if (block.closest("[data-streaming]") !== null) return;
964
+ if (closestStreaming(block) !== null) return;
842
965
  var content = block.querySelector(CONTENT);
843
966
  if (content === null) return;
844
967
  if (releaseTakenOver(block, content)) return;
@@ -858,7 +981,7 @@ window.__ModuleLoader__.load({
858
981
  var code = pre.textContent ?? "";
859
982
  if (code.trim() === "") return;
860
983
  var key = lang + "\0" + code;
861
- if (block.getAttribute(KEY_ATTR) === key) return;
984
+ if (cacheKeys.get(block) === key) return;
862
985
  var html;
863
986
  try {
864
987
  html = await api.highlight(code, lang);
@@ -883,21 +1006,27 @@ window.__ModuleLoader__.load({
883
1006
  content.querySelector("." + HOST_CLASS)?.remove();
884
1007
  resetFold(content);
885
1008
  pre.after(host);
886
- block.setAttribute(KEY_ATTR, key);
1009
+ cacheKeys.set(block, key);
887
1010
  block.setAttribute(DONE_ATTR, "done");
888
1011
  scheduleFold(content, foldLimit("code"));
889
1012
  }
890
1013
  function getMermaidTheme() {
1014
+ if (themeCache !== null) return themeCache;
891
1015
  try {
892
1016
  var saved = window.localStorage.getItem(THEME_KEY);
893
1017
  if (saved !== null && THEME_ORDER.indexOf(saved) !== -1) {
894
1018
  memoryTheme = saved;
1019
+ themeCache = saved;
895
1020
  return saved;
896
1021
  }
897
1022
  memoryTheme = null;
898
1023
  } catch {
899
- if (memoryTheme !== null) return memoryTheme;
1024
+ if (memoryTheme !== null) {
1025
+ themeCache = memoryTheme;
1026
+ return memoryTheme;
1027
+ }
900
1028
  }
1029
+ themeCache = "blue";
901
1030
  return "blue";
902
1031
  }
903
1032
  function themeLabel(name) {
@@ -915,6 +1044,7 @@ window.__ModuleLoader__.load({
915
1044
  var index = THEME_ORDER.indexOf(current);
916
1045
  var next = THEME_ORDER[(index + 1) % THEME_ORDER.length];
917
1046
  memoryTheme = next;
1047
+ themeCache = next;
918
1048
  try {
919
1049
  window.localStorage.setItem(THEME_KEY, next);
920
1050
  } catch {
@@ -926,7 +1056,7 @@ window.__ModuleLoader__.load({
926
1056
  var code = (pre.textContent ?? "").trim();
927
1057
  if (code === "") return;
928
1058
  var key = "mmd\0" + code;
929
- if (block.getAttribute(KEY_ATTR) === key) return;
1059
+ if (cacheKeys.get(block) === key) return;
930
1060
  var api = await loadScript(MMD_URL, MMD_GLOBAL);
931
1061
  if (disposed || api === null) return;
932
1062
  if (!pre.isConnected) return;
@@ -959,7 +1089,7 @@ window.__ModuleLoader__.load({
959
1089
  if (viewport !== null) bindViewport(block, viewport);
960
1090
  pre.before(host);
961
1091
  content.querySelector("." + MMD_CLASS + "-error")?.remove();
962
- block.setAttribute(KEY_ATTR, key);
1092
+ cacheKeys.set(block, key);
963
1093
  var view = block.getAttribute(MMD_ATTR);
964
1094
  if (view !== "code" && view !== "diagram") block.setAttribute(MMD_ATTR, readSettings().diagramView);
965
1095
  zooms.delete(block);
@@ -1072,14 +1202,65 @@ window.__ModuleLoader__.load({
1072
1202
  var sync = foldSyncs.get(target);
1073
1203
  if (sync !== void 0) {
1074
1204
  target.removeEventListener("scroll", sync);
1075
- window.removeEventListener("resize", sync);
1076
1205
  foldSyncs.delete(target);
1077
1206
  }
1207
+ foldTargets.delete(target);
1208
+ if (foldTargets.size === 0) {
1209
+ if (foldResizeObserver !== null) {
1210
+ foldResizeObserver.disconnect();
1211
+ foldResizeObserver = null;
1212
+ }
1213
+ if (foldResizeHandler !== null) {
1214
+ window.removeEventListener("resize", foldResizeHandler);
1215
+ foldResizeHandler = null;
1216
+ }
1217
+ } else if (foldResizeObserver !== null) {
1218
+ foldResizeObserver.unobserve(target);
1219
+ }
1078
1220
  target.removeAttribute(FOLD_ATTR);
1079
1221
  target.classList.remove(FOLD_CLASS);
1080
1222
  target.style.removeProperty("--md-plus-fold-max-height");
1081
1223
  target.querySelector("." + FOLD_BTN_CLASS)?.remove();
1082
1224
  }
1225
+ function watchFoldTarget(target, sync) {
1226
+ foldSyncs.set(target, sync);
1227
+ foldTargets.add(target);
1228
+ if (typeof window.ResizeObserver === "function") {
1229
+ if (foldResizeObserver === null) {
1230
+ foldResizeObserver = new window.ResizeObserver((entries) => {
1231
+ for (const entry of entries) {
1232
+ var entryTarget = entry.target;
1233
+ var entrySync = foldSyncs.get(entryTarget);
1234
+ if (entrySync === void 0) continue;
1235
+ if (!entryTarget.isConnected) {
1236
+ resetFold(entryTarget);
1237
+ continue;
1238
+ }
1239
+ entrySync();
1240
+ }
1241
+ });
1242
+ }
1243
+ foldResizeObserver.observe(target);
1244
+ return;
1245
+ }
1246
+ if (foldResizeHandler === null) {
1247
+ foldResizeHandler = () => {
1248
+ for (const entryTarget of foldTargets) {
1249
+ if (!entryTarget.isConnected) {
1250
+ resetFold(entryTarget);
1251
+ continue;
1252
+ }
1253
+ foldSyncs.get(entryTarget)?.();
1254
+ }
1255
+ };
1256
+ window.addEventListener("resize", foldResizeHandler);
1257
+ }
1258
+ }
1259
+ function cleanupFoldTargets() {
1260
+ for (const target of foldTargets) {
1261
+ if (!target.isConnected) resetFold(target);
1262
+ }
1263
+ }
1083
1264
  function scheduleFold(target, limit) {
1084
1265
  if (target === null) return;
1085
1266
  if (!(limit > 0)) {
@@ -1094,7 +1275,7 @@ window.__ModuleLoader__.load({
1094
1275
  if (target.getAttribute(FOLD_ATTR) !== null) return;
1095
1276
  var schedule2 = window.requestAnimationFrame ?? ((cb) => window.setTimeout(cb, 0));
1096
1277
  schedule2(() => {
1097
- if (!target.isConnected || target.getAttribute(FOLD_ATTR) !== null) return;
1278
+ if (disposed || !target.isConnected || target.getAttribute(FOLD_ATTR) !== null) return;
1098
1279
  if (target.scrollHeight <= limit) {
1099
1280
  target.setAttribute(FOLD_ATTR, "none");
1100
1281
  return;
@@ -1109,12 +1290,15 @@ window.__ModuleLoader__.load({
1109
1290
  button.textContent = t("foldOpen");
1110
1291
  button.setAttribute("aria-expanded", "false");
1111
1292
  var sync = target.hasAttribute(TABLE_DONE_ATTR) ? () => {
1112
- button.style.left = target.scrollLeft + target.clientWidth / 2 + "px";
1293
+ if (target.getAttribute(FOLD_ATTR) === "on") {
1294
+ button.style.left = target.scrollLeft + target.clientWidth / 2 + "px";
1295
+ } else {
1296
+ button.style.removeProperty("left");
1297
+ }
1113
1298
  } : null;
1114
1299
  if (sync !== null) {
1115
- foldSyncs.set(target, sync);
1116
1300
  target.addEventListener("scroll", sync, { passive: true });
1117
- window.addEventListener("resize", sync);
1301
+ watchFoldTarget(target, sync);
1118
1302
  }
1119
1303
  button.addEventListener("click", () => {
1120
1304
  var folded = target.getAttribute(FOLD_ATTR) === "on";
@@ -1129,15 +1313,17 @@ window.__ModuleLoader__.load({
1129
1313
  });
1130
1314
  }
1131
1315
  function scanFolds() {
1316
+ var codeLimit = foldLimit("code");
1317
+ var tableLimit = foldLimit("table");
1132
1318
  for (const block of document.querySelectorAll(BLOCK)) {
1133
- if (block.closest("[data-streaming]") !== null) continue;
1319
+ if (closestStreaming(block) !== null) continue;
1134
1320
  if (diagramKind(block) !== null) continue;
1135
- scheduleFold(block.querySelector(CONTENT), foldLimit("code"));
1321
+ scheduleFold(block.querySelector(CONTENT), codeLimit);
1136
1322
  }
1137
1323
  for (const table of document.querySelectorAll(TABLE)) {
1138
- if (table.closest("[data-streaming]") !== null) continue;
1324
+ if (closestStreaming(table) !== null) continue;
1139
1325
  decorateTable(table);
1140
- scheduleFold(table, foldLimit("table"));
1326
+ scheduleFold(table, tableLimit);
1141
1327
  }
1142
1328
  }
1143
1329
  function cellText(cell) {
@@ -1151,6 +1337,22 @@ window.__ModuleLoader__.load({
1151
1337
  (row) => row.map((value) => /[",\n]/.test(value) ? '"' + value.replace(/"/g, '""') + '"' : value).join(",")
1152
1338
  ).join("\n");
1153
1339
  }
1340
+ function tableToTsv(rows) {
1341
+ return rows.map((row) => row.map((value) => value.replace(/\t/g, " ")).join(" ")).join("\n");
1342
+ }
1343
+ function escapeHtml(text) {
1344
+ return text.replace(
1345
+ /[&<>"']/g,
1346
+ (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character]
1347
+ );
1348
+ }
1349
+ function tableToHtml(rows) {
1350
+ if (rows.length === 0) return "";
1351
+ var cell = (tag, value) => "<" + tag + ">" + escapeHtml(value) + "</" + tag + ">";
1352
+ var header = "<thead><tr>" + rows[0].map((value) => cell("th", value)).join("") + "</tr></thead>";
1353
+ var body = rows.slice(1).map((row) => "<tr>" + row.map((value) => cell("td", value)).join("") + "</tr>");
1354
+ return "<table>" + header + (body.length === 0 ? "" : "<tbody>" + body.join("") + "</tbody>") + "</table>";
1355
+ }
1154
1356
  function tableToMarkdown(rows) {
1155
1357
  if (rows.length === 0) return "";
1156
1358
  var width = Math.max(...rows.map((row) => row.length));
@@ -1159,10 +1361,43 @@ window.__ModuleLoader__.load({
1159
1361
  for (var index = 1; index < rows.length; index += 1) output.push(line(rows[index]));
1160
1362
  return output.join("\n");
1161
1363
  }
1364
+ var CODE_EXTENSIONS = {
1365
+ bash: "sh",
1366
+ batch: "bat",
1367
+ c: "c",
1368
+ cpp: "cpp",
1369
+ csharp: "cs",
1370
+ css: "css",
1371
+ go: "go",
1372
+ html: "html",
1373
+ java: "java",
1374
+ javascript: "js",
1375
+ json: "json",
1376
+ jsx: "jsx",
1377
+ kotlin: "kt",
1378
+ markdown: "md",
1379
+ mermaid: "mmd",
1380
+ php: "php",
1381
+ python: "py",
1382
+ ruby: "rb",
1383
+ rust: "rs",
1384
+ shell: "sh",
1385
+ shellscript: "sh",
1386
+ sql: "sql",
1387
+ swift: "swift",
1388
+ tsx: "tsx",
1389
+ typescript: "ts",
1390
+ xml: "xml",
1391
+ yaml: "yml",
1392
+ yml: "yml"
1393
+ };
1162
1394
  async function copyText(text) {
1163
1395
  if (window.navigator.clipboard !== void 0 && typeof window.navigator.clipboard.writeText === "function") {
1164
- await window.navigator.clipboard.writeText(text);
1165
- return true;
1396
+ try {
1397
+ await window.navigator.clipboard.writeText(text);
1398
+ return true;
1399
+ } catch {
1400
+ }
1166
1401
  }
1167
1402
  var area = document.createElement("textarea");
1168
1403
  area.value = text;
@@ -1179,6 +1414,55 @@ window.__ModuleLoader__.load({
1179
1414
  area.remove();
1180
1415
  return ok;
1181
1416
  }
1417
+ async function copyRichText(text, html) {
1418
+ if (window.navigator.clipboard !== void 0 && typeof window.navigator.clipboard.write === "function" && typeof window.ClipboardItem === "function") {
1419
+ try {
1420
+ await window.navigator.clipboard.write([
1421
+ new window.ClipboardItem({
1422
+ "text/plain": new Blob([text], { type: "text/plain" }),
1423
+ "text/html": new Blob([html], { type: "text/html" })
1424
+ })
1425
+ ]);
1426
+ return true;
1427
+ } catch {
1428
+ }
1429
+ }
1430
+ return copyText(text);
1431
+ }
1432
+ async function copyDiagramSource(block, button) {
1433
+ var pre = block.querySelector(CONTENT + " pre");
1434
+ if (pre === null) return;
1435
+ var ok = false;
1436
+ try {
1437
+ ok = await copyText(pre.textContent ?? "");
1438
+ } catch {
1439
+ ok = false;
1440
+ }
1441
+ if (button !== void 0) {
1442
+ var original = button.title;
1443
+ var label = ok ? t("copied") : t("copyFailed");
1444
+ button.title = label;
1445
+ button.setAttribute("aria-label", label);
1446
+ window.setTimeout(() => {
1447
+ button.title = original;
1448
+ button.setAttribute("aria-label", original);
1449
+ }, 1200);
1450
+ }
1451
+ }
1452
+ function downloadCode(block) {
1453
+ var pre = block.querySelector(CONTENT + " pre");
1454
+ if (pre === null) return;
1455
+ var lang = readLang(block);
1456
+ var ext = CODE_EXTENSIONS[lang] ?? "txt";
1457
+ try {
1458
+ downloadBlob(
1459
+ new Blob([pre.textContent ?? ""], { type: "text/plain;charset=utf-8" }),
1460
+ "code-snippet." + ext
1461
+ );
1462
+ } catch (error) {
1463
+ console.warn("[dsh-md-plus] \u4EE3\u7801\u4E0B\u8F7D\u5931\u8D25", error);
1464
+ }
1465
+ }
1182
1466
  function flashButton(button, text) {
1183
1467
  var original = button.getAttribute("data-md-plus-label") ?? button.textContent;
1184
1468
  button.setAttribute("data-md-plus-label", original);
@@ -1187,9 +1471,88 @@ window.__ModuleLoader__.load({
1187
1471
  button.textContent = original;
1188
1472
  }, 1200);
1189
1473
  }
1474
+ function clearTableFreeze(table) {
1475
+ for (const cell of table.querySelectorAll(
1476
+ "." + FREEZE_ROW_CLASS + ",." + FREEZE_COL_CLASS + ",." + FREEZE_CROSS_CLASS
1477
+ )) {
1478
+ cell.classList.remove(FREEZE_ROW_CLASS, FREEZE_COL_CLASS, FREEZE_CROSS_CLASS);
1479
+ cell.style.removeProperty("top");
1480
+ cell.style.removeProperty("left");
1481
+ }
1482
+ }
1483
+ function observeTableFreeze(table) {
1484
+ if (tableFreezeTargets.has(table)) return;
1485
+ tableFreezeTargets.add(table);
1486
+ if (typeof window.ResizeObserver !== "function") return;
1487
+ if (tableFreezeObserver === null) {
1488
+ tableFreezeObserver = new window.ResizeObserver((entries) => {
1489
+ for (const entry of entries) {
1490
+ if (!entry.target.isConnected) {
1491
+ cleanupTableFreezeTargets();
1492
+ continue;
1493
+ }
1494
+ applyTableFreeze(entry.target);
1495
+ }
1496
+ });
1497
+ }
1498
+ tableFreezeObserver.observe(table);
1499
+ }
1500
+ function applyTableFreeze(table) {
1501
+ var dataTable = table.querySelector("table");
1502
+ if (dataTable === null) return;
1503
+ var settings = readSettings();
1504
+ var freezeRows = Math.max(0, Math.min(5, Number(settings.freezeRows) || 0));
1505
+ var freezeCols = Math.max(0, Math.min(5, Number(settings.freezeCols) || 0));
1506
+ var width = dataTable.scrollWidth;
1507
+ var height = dataTable.scrollHeight;
1508
+ var hasFrozenCells = dataTable.querySelector("." + FREEZE_ROW_CLASS + ",." + FREEZE_COL_CLASS) !== null;
1509
+ var state = tableFreezeState.get(table);
1510
+ if (state !== void 0 && state.rows === freezeRows && state.cols === freezeCols && state.width === width && state.height === height && (freezeRows === 0 && freezeCols === 0 || hasFrozenCells)) {
1511
+ return;
1512
+ }
1513
+ clearTableFreeze(table);
1514
+ tableFreezeState.set(table, { rows: freezeRows, cols: freezeCols, width, height });
1515
+ if (freezeRows === 0 && freezeCols === 0) return;
1516
+ var rows = Array.from(dataTable.rows);
1517
+ var top = 0;
1518
+ for (var rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
1519
+ var row = rows[rowIndex];
1520
+ var cells = Array.from(row.cells);
1521
+ var frozenRow = rowIndex < freezeRows;
1522
+ if (frozenRow) {
1523
+ for (const cell2 of cells) cell2.classList.add(FREEZE_ROW_CLASS);
1524
+ }
1525
+ var left = 0;
1526
+ for (var cellIndex = 0; cellIndex < cells.length; cellIndex += 1) {
1527
+ var cell = cells[cellIndex];
1528
+ if (frozenRow) cell.style.top = top + "px";
1529
+ if (cellIndex < freezeCols) {
1530
+ cell.classList.add(FREEZE_COL_CLASS);
1531
+ if (frozenRow) cell.classList.add(FREEZE_CROSS_CLASS);
1532
+ cell.style.left = left + "px";
1533
+ left += cell.getBoundingClientRect().width;
1534
+ }
1535
+ }
1536
+ if (frozenRow) top += row.getBoundingClientRect().height;
1537
+ }
1538
+ observeTableFreeze(table);
1539
+ }
1540
+ function cleanupTableFreezeTargets() {
1541
+ for (const table of tableFreezeTargets) {
1542
+ if (table.isConnected) continue;
1543
+ if (tableFreezeObserver !== null) tableFreezeObserver.unobserve(table);
1544
+ tableFreezeTargets.delete(table);
1545
+ tableFreezeState.delete(table);
1546
+ }
1547
+ }
1548
+ function applyTableFreezeSettings() {
1549
+ tableFreezeState = /* @__PURE__ */ new WeakMap();
1550
+ for (const table of document.querySelectorAll(TABLE)) applyTableFreeze(table);
1551
+ }
1190
1552
  function decorateTable(table) {
1191
1553
  var dataTable = table.querySelector("table");
1192
1554
  if (dataTable === null) return;
1555
+ applyTableFreeze(table);
1193
1556
  var first = dataTable.firstElementChild;
1194
1557
  if (first !== null && first.classList.contains(TABLE_TOOLS_CLASS)) return;
1195
1558
  table.setAttribute(TABLE_DONE_ATTR, "1");
@@ -1197,11 +1560,13 @@ window.__ModuleLoader__.load({
1197
1560
  tools.className = TABLE_TOOLS_CLASS;
1198
1561
  var group = document.createElement("span");
1199
1562
  group.className = TABLE_TOOLS_CLASS + "-group";
1200
- for (const [kind, label, titleKey, convert] of [
1201
- ["csv", t("tableCsv"), "copyCsvTitle", tableToCsv],
1202
- ["md", t("tableMd"), "copyMdTitle", tableToMarkdown]
1563
+ for (const [kind, label, titleKey, convert, rich] of [
1564
+ ["csv", t("tableCsv"), "copyCsvTitle", tableToCsv, false],
1565
+ ["tsv", t("tableTsv"), "copyTsvTitle", tableToTsv, false],
1566
+ ["md", t("tableMd"), "copyMdTitle", tableToMarkdown, false],
1567
+ ["html", t("tableHtml"), "copyHtmlTitle", tableToHtml, true]
1203
1568
  ]) {
1204
- var button = document.createElement("button");
1569
+ const button = document.createElement("button");
1205
1570
  button.type = "button";
1206
1571
  button.textContent = label;
1207
1572
  button.title = t(titleKey);
@@ -1209,7 +1574,8 @@ window.__ModuleLoader__.load({
1209
1574
  button.addEventListener("click", async () => {
1210
1575
  var ok = false;
1211
1576
  try {
1212
- ok = await copyText(convert(tableRows(dataTable)));
1577
+ var rows = tableRows(dataTable);
1578
+ ok = rich ? await copyRichText(tableToTsv(rows), convert(rows)) : await copyText(convert(rows));
1213
1579
  } catch {
1214
1580
  ok = false;
1215
1581
  }
@@ -1220,6 +1586,48 @@ window.__ModuleLoader__.load({
1220
1586
  tools.appendChild(group);
1221
1587
  dataTable.insertBefore(tools, dataTable.firstChild);
1222
1588
  }
1589
+ var scrollLocks = 0;
1590
+ var previousRootOverflow = "";
1591
+ function lockPageScroll() {
1592
+ if (scrollLocks === 0) {
1593
+ previousRootOverflow = document.documentElement.style.overflow;
1594
+ document.documentElement.style.overflow = "hidden";
1595
+ }
1596
+ scrollLocks += 1;
1597
+ }
1598
+ function unlockPageScroll() {
1599
+ if (scrollLocks === 0) return;
1600
+ scrollLocks -= 1;
1601
+ if (scrollLocks === 0) {
1602
+ document.documentElement.style.overflow = previousRootOverflow;
1603
+ previousRootOverflow = "";
1604
+ }
1605
+ }
1606
+ function trapFocus(container) {
1607
+ function onKey(event) {
1608
+ if (event.key !== "Tab") return;
1609
+ var candidates = Array.from(
1610
+ container.querySelectorAll(
1611
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
1612
+ )
1613
+ ).filter((node) => node.offsetParent !== null || node === document.activeElement);
1614
+ if (candidates.length === 0) {
1615
+ event.preventDefault();
1616
+ return;
1617
+ }
1618
+ var first = candidates[0];
1619
+ var last = candidates[candidates.length - 1];
1620
+ if (event.shiftKey && document.activeElement === first) {
1621
+ event.preventDefault();
1622
+ last.focus();
1623
+ } else if (!event.shiftKey && document.activeElement === last) {
1624
+ event.preventDefault();
1625
+ first.focus();
1626
+ }
1627
+ }
1628
+ container.addEventListener("keydown", onKey, true);
1629
+ return () => container.removeEventListener("keydown", onKey, true);
1630
+ }
1223
1631
  function openImage(source, alt) {
1224
1632
  if (document.querySelector("." + IMG_MODAL_CLASS) !== null) return;
1225
1633
  var overlay = document.createElement("div");
@@ -1236,12 +1644,15 @@ window.__ModuleLoader__.load({
1236
1644
  closeButton.textContent = "\xD7";
1237
1645
  closeButton.setAttribute("aria-label", t("imageClose"));
1238
1646
  var previousFocus = document.activeElement;
1647
+ var releaseFocus = null;
1239
1648
  var closed = false;
1240
1649
  function closeViewer() {
1241
1650
  if (closed) return;
1242
1651
  closed = true;
1652
+ releaseFocus?.();
1243
1653
  document.removeEventListener("keydown", onKey, true);
1244
1654
  overlay.remove();
1655
+ unlockPageScroll();
1245
1656
  if (previousFocus !== null && typeof previousFocus.focus === "function") previousFocus.focus();
1246
1657
  }
1247
1658
  function onKey(event) {
@@ -1257,7 +1668,9 @@ window.__ModuleLoader__.load({
1257
1668
  document.addEventListener("keydown", onKey, true);
1258
1669
  overlay.appendChild(image);
1259
1670
  overlay.appendChild(closeButton);
1671
+ lockPageScroll();
1260
1672
  document.body.appendChild(overlay);
1673
+ releaseFocus = trapFocus(overlay);
1261
1674
  closeButton.focus();
1262
1675
  }
1263
1676
  function onDocumentClick(event) {
@@ -1300,13 +1713,57 @@ window.__ModuleLoader__.load({
1300
1713
  return input;
1301
1714
  }
1302
1715
  function rescanFolds() {
1303
- for (const node of document.querySelectorAll("[" + FOLD_ATTR + "]")) resetFold(node);
1716
+ diagramKindCache = /* @__PURE__ */ new WeakMap();
1717
+ var codeLimit = foldLimit("code");
1718
+ var tableLimit = foldLimit("table");
1719
+ var diagramLimit = foldLimit("diagram");
1720
+ for (const node of document.querySelectorAll("[" + FOLD_ATTR + "]")) {
1721
+ if (node.classList.contains(MMD_CLASS + "-viewport")) continue;
1722
+ var limit = node.hasAttribute(TABLE_DONE_ATTR) ? tableLimit : codeLimit;
1723
+ if (node.getAttribute(FOLD_ATTR) === "off" && limit > 0) {
1724
+ node.style.setProperty("--md-plus-fold-max-height", limit + "px");
1725
+ continue;
1726
+ }
1727
+ resetFold(node);
1728
+ }
1304
1729
  for (const viewport of document.querySelectorAll("." + MMD_CLASS + "-viewport")) {
1730
+ if (viewport.getAttribute(FOLD_ATTR) === "off" && diagramLimit > 0) {
1731
+ viewport.style.setProperty("--md-plus-fold-max-height", diagramLimit + "px");
1732
+ continue;
1733
+ }
1305
1734
  resetFold(viewport);
1306
- scheduleFold(viewport, foldLimit("diagram"));
1735
+ scheduleFold(viewport, diagramLimit);
1307
1736
  }
1308
1737
  scanFolds();
1309
1738
  }
1739
+ function setFoldExpanded(target, expanded) {
1740
+ var state = target.getAttribute(FOLD_ATTR);
1741
+ if (state !== "on" && state !== "off") return;
1742
+ target.setAttribute(FOLD_ATTR, expanded ? "off" : "on");
1743
+ if (target.hasAttribute(TABLE_DONE_ATTR)) target.scrollTop = 0;
1744
+ var button = target.querySelector("." + FOLD_BTN_CLASS);
1745
+ if (button !== null) {
1746
+ button.textContent = expanded ? t("foldClose") : t("foldOpen");
1747
+ button.setAttribute("aria-expanded", expanded ? "true" : "false");
1748
+ }
1749
+ foldSyncs.get(target)?.();
1750
+ }
1751
+ function setAllFolds(expanded) {
1752
+ for (const target of document.querySelectorAll("[" + FOLD_ATTR + "]")) {
1753
+ setFoldExpanded(target, expanded);
1754
+ }
1755
+ }
1756
+ function exportSettings() {
1757
+ try {
1758
+ var payload = { settings: readSettings(), mermaidTheme: getMermaidTheme() };
1759
+ downloadBlob(
1760
+ new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" }),
1761
+ "dsh-md-plus-settings.json"
1762
+ );
1763
+ } catch (error) {
1764
+ console.warn("[dsh-md-plus] \u8BBE\u7F6E\u5BFC\u51FA\u5931\u8D25", error);
1765
+ }
1766
+ }
1310
1767
  function openSettings() {
1311
1768
  if (document.querySelector("." + SETTINGS_MODAL_CLASS) !== null) return;
1312
1769
  var current = readSettings();
@@ -1331,11 +1788,9 @@ window.__ModuleLoader__.load({
1331
1788
  themeSelect.value = getMermaidTheme();
1332
1789
  panel.appendChild(settingRow(t("settingsTheme"), themeSelect));
1333
1790
  var fontInput = numberInput(current.codeFontSize, 10, 20);
1334
- panel.appendChild(settingRow(t("settingsFontSize"), fontInput));
1335
- var fontHint = document.createElement("p");
1336
- fontHint.className = SETTINGS_MODAL_CLASS + "-hint";
1337
- fontHint.textContent = t("settingsFontSizeHint");
1338
- panel.appendChild(fontHint);
1791
+ panel.appendChild(
1792
+ settingRow(t("settingsFontSize"), fontInput, t("settingsFontSizeHint"))
1793
+ );
1339
1794
  var viewSelect = document.createElement("select");
1340
1795
  for (const [value, label] of [
1341
1796
  ["diagram", t("settingsViewDiagram")],
@@ -1349,7 +1804,7 @@ window.__ModuleLoader__.load({
1349
1804
  viewSelect.value = current.diagramView;
1350
1805
  panel.appendChild(settingRow(t("settingsView"), viewSelect));
1351
1806
  var foldHint = document.createElement("p");
1352
- foldHint.className = SETTINGS_MODAL_CLASS + "-hint";
1807
+ foldHint.className = SETTINGS_MODAL_CLASS + "-section";
1353
1808
  foldHint.textContent = t("settingsFold") + " \xB7 " + t("settingsFoldHint");
1354
1809
  panel.appendChild(foldHint);
1355
1810
  var foldCode = numberInput(current.foldCode, 0, 4e3);
@@ -1358,6 +1813,56 @@ window.__ModuleLoader__.load({
1358
1813
  panel.appendChild(settingRow(t("settingsFoldCode"), foldCode, t("settingsFoldCodeHint")));
1359
1814
  panel.appendChild(settingRow(t("settingsFoldTable"), foldTable, t("settingsFoldTableHint")));
1360
1815
  panel.appendChild(settingRow(t("settingsFoldDiagram"), foldDiagram, t("settingsFoldDiagramHint")));
1816
+ var freezeRowsInput = numberInput(current.freezeRows, 0, 5);
1817
+ var freezeColsInput = numberInput(current.freezeCols, 0, 5);
1818
+ panel.appendChild(
1819
+ settingRow(t("settingsFreezeRows"), freezeRowsInput, t("settingsFreezeRowsHint"))
1820
+ );
1821
+ panel.appendChild(
1822
+ settingRow(t("settingsFreezeCols"), freezeColsInput, t("settingsFreezeColsHint"))
1823
+ );
1824
+ var foldActions = document.createElement("div");
1825
+ foldActions.className = SETTINGS_MODAL_CLASS + "-fold-actions";
1826
+ foldActions.setAttribute("role", "group");
1827
+ foldActions.setAttribute("aria-label", t("settingsFoldGroup"));
1828
+ var foldLabel = document.createElement("span");
1829
+ foldLabel.className = SETTINGS_MODAL_CLASS + "-utility-label";
1830
+ foldLabel.textContent = t("settingsFoldGroup");
1831
+ var foldSegment = document.createElement("span");
1832
+ foldSegment.className = SETTINGS_MODAL_CLASS + "-segment";
1833
+ var expandAllButton = document.createElement("button");
1834
+ expandAllButton.type = "button";
1835
+ expandAllButton.textContent = t("foldExpandAll");
1836
+ var collapseAllButton = document.createElement("button");
1837
+ collapseAllButton.type = "button";
1838
+ collapseAllButton.textContent = t("foldCollapseAll");
1839
+ foldSegment.appendChild(expandAllButton);
1840
+ foldSegment.appendChild(collapseAllButton);
1841
+ foldActions.appendChild(foldLabel);
1842
+ foldActions.appendChild(foldSegment);
1843
+ var transferActions = document.createElement("div");
1844
+ transferActions.className = SETTINGS_MODAL_CLASS + "-transfer-actions";
1845
+ transferActions.setAttribute("role", "group");
1846
+ transferActions.setAttribute("aria-label", t("settingsFileGroup"));
1847
+ var transferLabel = document.createElement("span");
1848
+ transferLabel.className = SETTINGS_MODAL_CLASS + "-utility-label";
1849
+ transferLabel.textContent = t("settingsFileGroup");
1850
+ var transferSegment = document.createElement("span");
1851
+ transferSegment.className = SETTINGS_MODAL_CLASS + "-segment";
1852
+ var exportButton = document.createElement("button");
1853
+ exportButton.type = "button";
1854
+ exportButton.textContent = t("settingsExport");
1855
+ var importButton = document.createElement("button");
1856
+ importButton.type = "button";
1857
+ importButton.textContent = t("settingsImport");
1858
+ var fileInput = document.createElement("input");
1859
+ fileInput.type = "file";
1860
+ fileInput.accept = ".json,application/json";
1861
+ fileInput.style.display = "none";
1862
+ transferSegment.appendChild(exportButton);
1863
+ transferSegment.appendChild(importButton);
1864
+ transferActions.appendChild(transferLabel);
1865
+ transferActions.appendChild(transferSegment);
1361
1866
  var actions = document.createElement("div");
1362
1867
  actions.className = SETTINGS_MODAL_CLASS + "-actions";
1363
1868
  var resetButton = document.createElement("button");
@@ -1373,15 +1878,27 @@ window.__ModuleLoader__.load({
1373
1878
  actions.appendChild(resetButton);
1374
1879
  actions.appendChild(closeButton);
1375
1880
  actions.appendChild(saveButton);
1376
- panel.appendChild(actions);
1881
+ var utilities = document.createElement("div");
1882
+ utilities.className = SETTINGS_MODAL_CLASS + "-utilities";
1883
+ utilities.appendChild(foldActions);
1884
+ utilities.appendChild(transferActions);
1885
+ var footer = document.createElement("div");
1886
+ footer.className = SETTINGS_MODAL_CLASS + "-footer";
1887
+ footer.appendChild(utilities);
1888
+ footer.appendChild(actions);
1889
+ panel.appendChild(footer);
1890
+ panel.appendChild(fileInput);
1377
1891
  overlay.appendChild(panel);
1378
1892
  var previousFocus = document.activeElement;
1893
+ var releaseFocus = null;
1379
1894
  var closed = false;
1380
1895
  function closeSettings() {
1381
1896
  if (closed) return;
1382
1897
  closed = true;
1898
+ releaseFocus?.();
1383
1899
  document.removeEventListener("keydown", onKey, true);
1384
1900
  overlay.remove();
1901
+ unlockPageScroll();
1385
1902
  if (previousFocus !== null && typeof previousFocus.focus === "function") previousFocus.focus();
1386
1903
  }
1387
1904
  function onKey(event) {
@@ -1399,6 +1916,48 @@ window.__ModuleLoader__.load({
1399
1916
  if (!Number.isFinite(value) || value <= 0) return 0;
1400
1917
  return Math.min(20, Math.max(10, Math.round(value)));
1401
1918
  }
1919
+ function importSettingsFile(file) {
1920
+ file.text().then((text) => {
1921
+ var parsed = JSON.parse(text);
1922
+ var source = parsed !== null && typeof parsed === "object" && parsed.settings !== null && typeof parsed.settings === "object" ? parsed.settings : parsed;
1923
+ if (source === null || typeof source !== "object") throw new Error("\u8BBE\u7F6E\u683C\u5F0F\u65E0\u6548");
1924
+ var font = Number(source.codeFontSize);
1925
+ if (Number.isFinite(font)) {
1926
+ fontInput.value = font > 0 ? String(Math.min(20, Math.max(10, Math.round(font)))) : "";
1927
+ }
1928
+ for (const [key, input, max] of [
1929
+ ["foldCode", foldCode, 4e3],
1930
+ ["foldTable", foldTable, 4e3],
1931
+ ["foldDiagram", foldDiagram, 4e3],
1932
+ ["freezeRows", freezeRowsInput, 5],
1933
+ ["freezeCols", freezeColsInput, 5]
1934
+ ]) {
1935
+ var value = Number(source[key]);
1936
+ if (Number.isFinite(value) && value >= 0) {
1937
+ input.value = String(Math.min(max, Math.round(value)));
1938
+ }
1939
+ }
1940
+ if (source.diagramView === "code" || source.diagramView === "diagram") {
1941
+ viewSelect.value = source.diagramView;
1942
+ }
1943
+ var theme = parsed !== null && typeof parsed === "object" ? parsed.mermaidTheme : null;
1944
+ if (typeof theme === "string" && THEME_ORDER.indexOf(theme) !== -1) {
1945
+ themeSelect.value = theme;
1946
+ }
1947
+ saveButton.click();
1948
+ }).catch(() => {
1949
+ flashButton(importButton, t("settingsImportFailed"));
1950
+ });
1951
+ }
1952
+ exportButton.addEventListener("click", exportSettings);
1953
+ importButton.addEventListener("click", () => fileInput.click());
1954
+ fileInput.addEventListener("change", () => {
1955
+ var file = fileInput.files?.[0];
1956
+ if (file !== null && file !== void 0) importSettingsFile(file);
1957
+ fileInput.value = "";
1958
+ });
1959
+ expandAllButton.addEventListener("click", () => setAllFolds(true));
1960
+ collapseAllButton.addEventListener("click", () => setAllFolds(false));
1402
1961
  resetButton.addEventListener("click", () => {
1403
1962
  themeSelect.value = "blue";
1404
1963
  fontInput.value = "";
@@ -1406,24 +1965,36 @@ window.__ModuleLoader__.load({
1406
1965
  foldCode.value = String(DEFAULT_SETTINGS.foldCode);
1407
1966
  foldTable.value = String(DEFAULT_SETTINGS.foldTable);
1408
1967
  foldDiagram.value = String(DEFAULT_SETTINGS.foldDiagram);
1968
+ freezeRowsInput.value = String(DEFAULT_SETTINGS.freezeRows);
1969
+ freezeColsInput.value = String(DEFAULT_SETTINGS.freezeCols);
1409
1970
  });
1410
1971
  saveButton.addEventListener("click", () => {
1411
- saveSettings({
1972
+ var previous = readSettings();
1973
+ var previousTheme = getMermaidTheme();
1974
+ var nextTheme = THEME_ORDER.indexOf(themeSelect.value) === -1 ? "blue" : themeSelect.value;
1975
+ var next = {
1412
1976
  codeFontSize: readFontSize(fontInput),
1413
1977
  diagramView: viewSelect.value === "code" ? "code" : "diagram",
1414
1978
  foldCode: readNumber(foldCode, DEFAULT_SETTINGS.foldCode),
1415
1979
  foldTable: readNumber(foldTable, DEFAULT_SETTINGS.foldTable),
1416
- foldDiagram: readNumber(foldDiagram, DEFAULT_SETTINGS.foldDiagram)
1417
- });
1418
- memoryTheme = themeSelect.value;
1980
+ foldDiagram: readNumber(foldDiagram, DEFAULT_SETTINGS.foldDiagram),
1981
+ freezeRows: readNumber(freezeRowsInput, DEFAULT_SETTINGS.freezeRows, 5),
1982
+ freezeCols: readNumber(freezeColsInput, DEFAULT_SETTINGS.freezeCols, 5)
1983
+ };
1984
+ saveSettings(next);
1985
+ memoryTheme = nextTheme;
1986
+ themeCache = nextTheme;
1419
1987
  try {
1420
- window.localStorage.setItem(THEME_KEY, themeSelect.value);
1988
+ window.localStorage.setItem(THEME_KEY, nextTheme);
1421
1989
  } catch {
1422
1990
  }
1423
1991
  applyCodeFontSize();
1992
+ applyTableFreezeSettings();
1424
1993
  refreshThemeButtons();
1425
- redrawDiagrams();
1426
- rescanFolds();
1994
+ if (previousTheme !== nextTheme) redrawDiagrams();
1995
+ if (previous.codeFontSize !== next.codeFontSize || previous.foldCode !== next.foldCode || previous.foldTable !== next.foldTable || previous.foldDiagram !== next.foldDiagram) {
1996
+ rescanFolds();
1997
+ }
1427
1998
  closeSettings();
1428
1999
  });
1429
2000
  closeButton.addEventListener("click", closeSettings);
@@ -1431,12 +2002,18 @@ window.__ModuleLoader__.load({
1431
2002
  if (event.target === overlay) closeSettings();
1432
2003
  });
1433
2004
  document.addEventListener("keydown", onKey, true);
2005
+ lockPageScroll();
1434
2006
  document.body.appendChild(overlay);
2007
+ releaseFocus = trapFocus(overlay);
1435
2008
  saveButton.focus();
1436
2009
  }
1437
2010
  function ensureSettingsButton() {
1438
- if (document.querySelector("." + SETTINGS_BTN_CLASS) !== null) return;
1439
- if (document.querySelector('[class*="_markdown_"], .md-code-block') === null) return;
2011
+ var existing = document.querySelector("." + SETTINGS_BTN_CLASS);
2012
+ if (document.querySelector('[class*="_markdown_"], .md-code-block') === null) {
2013
+ existing?.remove();
2014
+ return;
2015
+ }
2016
+ if (existing !== null) return;
1440
2017
  var button = document.createElement("button");
1441
2018
  button.type = "button";
1442
2019
  button.className = SETTINGS_BTN_CLASS;
@@ -1490,16 +2067,23 @@ window.__ModuleLoader__.load({
1490
2067
  link.remove();
1491
2068
  window.setTimeout(() => URL.revokeObjectURL(url), 1e3);
1492
2069
  }
1493
- function downloadSvg(svg) {
2070
+ function exportFailed(button, error) {
2071
+ console.warn("[dsh-md-plus] \u56FE\u8868\u5BFC\u51FA\u5931\u8D25", error);
2072
+ if (button !== void 0) flashButton(button, t("exportFailed"));
2073
+ }
2074
+ function downloadSvg(svg, button) {
1494
2075
  try {
1495
2076
  downloadBlob(new Blob([serializeSvg(svg)], { type: "image/svg+xml;charset=utf-8" }), "mermaid-diagram.svg");
1496
2077
  } catch (error) {
1497
- console.warn("[dsh-md-plus] SVG \u5BFC\u51FA\u5931\u8D25", error);
2078
+ exportFailed(button, error);
1498
2079
  }
1499
2080
  }
1500
- function downloadPng(svg) {
2081
+ function downloadPng(svg, button) {
1501
2082
  var size = svgSize(svg);
1502
- if (size.width <= 0 || size.height <= 0) return;
2083
+ if (size.width <= 0 || size.height <= 0) {
2084
+ exportFailed(button, new Error("\u56FE\u8868\u5C3A\u5BF8\u65E0\u6548"));
2085
+ return;
2086
+ }
1503
2087
  var image = new window.Image();
1504
2088
  var objectUrl = "";
1505
2089
  image.onload = function() {
@@ -1513,16 +2097,17 @@ window.__ModuleLoader__.load({
1513
2097
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
1514
2098
  canvas.toBlob((blob) => {
1515
2099
  if (blob !== null) downloadBlob(blob, "mermaid-diagram.png");
2100
+ else exportFailed(button, new Error("canvas \u672A\u751F\u6210\u56FE\u7247"));
1516
2101
  }, "image/png");
1517
2102
  } catch (error) {
1518
- console.warn("[dsh-md-plus] PNG \u5BFC\u51FA\u5931\u8D25", error);
2103
+ exportFailed(button, error);
1519
2104
  } finally {
1520
2105
  if (objectUrl !== "") URL.revokeObjectURL(objectUrl);
1521
2106
  }
1522
2107
  };
1523
2108
  image.onerror = function() {
1524
2109
  if (objectUrl !== "") URL.revokeObjectURL(objectUrl);
1525
- console.warn("[dsh-md-plus] PNG \u5BFC\u51FA\u5931\u8D25\uFF1ASVG \u65E0\u6CD5\u8F7D\u5165");
2110
+ exportFailed(button, new Error("SVG \u65E0\u6CD5\u8F7D\u5165"));
1526
2111
  };
1527
2112
  objectUrl = URL.createObjectURL(new Blob([serializeSvg(svg)], { type: "image/svg+xml;charset=utf-8" }));
1528
2113
  image.src = objectUrl;
@@ -1595,15 +2180,16 @@ window.__ModuleLoader__.load({
1595
2180
  panel.appendChild(body);
1596
2181
  overlay.appendChild(panel);
1597
2182
  var previousFocus = document.activeElement;
1598
- var previousOverflow = document.documentElement.style.overflow;
2183
+ var releaseFocus = null;
1599
2184
  var closed = false;
1600
2185
  var reset = () => fitToView(overlay);
1601
2186
  function closeViewer() {
1602
2187
  if (closed) return;
1603
2188
  closed = true;
2189
+ releaseFocus?.();
1604
2190
  document.removeEventListener("keydown", onKey, true);
1605
2191
  overlay.remove();
1606
- document.documentElement.style.overflow = previousOverflow;
2192
+ unlockPageScroll();
1607
2193
  zooms.delete(overlay);
1608
2194
  if (previousFocus !== null && typeof previousFocus.focus === "function") {
1609
2195
  previousFocus.focus();
@@ -1619,16 +2205,17 @@ window.__ModuleLoader__.load({
1619
2205
  if (event.target === overlay) closeViewer();
1620
2206
  });
1621
2207
  fitButton.addEventListener("click", reset);
1622
- svgButton.addEventListener("click", () => downloadSvg(svg));
1623
- pngButton.addEventListener("click", () => downloadPng(svg));
2208
+ svgButton.addEventListener("click", () => downloadSvg(svg, svgButton));
2209
+ pngButton.addEventListener("click", () => downloadPng(svg, pngButton));
1624
2210
  zoomOut.addEventListener("click", () => zoomBy(overlay, 1 / ZOOM_STEP));
1625
2211
  zoomIn.addEventListener("click", () => zoomBy(overlay, ZOOM_STEP));
1626
2212
  closeButton.addEventListener("click", closeViewer);
1627
2213
  document.addEventListener("keydown", onKey, true);
1628
2214
  zooms.set(overlay, { scale: 1, x: 0, y: 0, min: ZOOM_MIN_MODAL });
1629
2215
  bindViewport(overlay, viewport, reset, true);
1630
- document.documentElement.style.overflow = "hidden";
2216
+ lockPageScroll();
1631
2217
  document.body.appendChild(overlay);
2218
+ releaseFocus = trapFocus(overlay);
1632
2219
  closeButton.focus();
1633
2220
  var schedule2 = window.requestAnimationFrame ?? ((cb) => window.setTimeout(cb, 0));
1634
2221
  schedule2(reset);
@@ -1640,7 +2227,7 @@ window.__ModuleLoader__.load({
1640
2227
  var reason = (error?.message ?? String(error)).split("\n")[0] ?? "";
1641
2228
  note.textContent = t("renderFailed") + reason;
1642
2229
  content.appendChild(note);
1643
- block.setAttribute(KEY_ATTR, key);
2230
+ cacheKeys.set(block, key);
1644
2231
  block.setAttribute(MMD_ATTR, "failed");
1645
2232
  decorate(block);
1646
2233
  console.warn("[dsh-md-plus] mermaid \u6E32\u67D3\u5931\u8D25", error);
@@ -1649,8 +2236,11 @@ window.__ModuleLoader__.load({
1649
2236
  if (typeof document === "undefined") return;
1650
2237
  ctx.effect(() => {
1651
2238
  disposed = false;
2239
+ settingsCache = null;
2240
+ themeCache = null;
1652
2241
  injectStyle();
1653
2242
  applyCodeFontSize();
2243
+ applyTableFreezeSettings();
1654
2244
  ensureSettingsButton();
1655
2245
  var root = document.body ?? document.documentElement;
1656
2246
  document.addEventListener("click", onDocumentClick, true);
@@ -1671,6 +2261,15 @@ window.__ModuleLoader__.load({
1671
2261
  }
1672
2262
  pending.clear();
1673
2263
  rescanQueued = false;
2264
+ for (const target of [...foldTargets]) resetFold(target);
2265
+ for (const table of [...tableFreezeTargets]) clearTableFreeze(table);
2266
+ tableFreezeTargets.clear();
2267
+ if (tableFreezeObserver !== null) {
2268
+ tableFreezeObserver.disconnect();
2269
+ tableFreezeObserver = null;
2270
+ }
2271
+ tableFreezeState = /* @__PURE__ */ new WeakMap();
2272
+ for (const table of document.querySelectorAll(TABLE)) clearTableFreeze(table);
1674
2273
  observer.disconnect();
1675
2274
  document.removeEventListener("click", onDocumentClick, true);
1676
2275
  for (const node of injected) node.remove();
@@ -1680,7 +2279,7 @@ window.__ModuleLoader__.load({
1680
2279
  for (const node of document.querySelectorAll("." + FOLD_BTN_CLASS)) node.remove();
1681
2280
  for (const node of document.querySelectorAll("[" + FOLD_ATTR + "]")) resetFold(node);
1682
2281
  for (const node of document.querySelectorAll("." + TABLE_TOOLS_CLASS)) node.remove();
1683
- for (const attr of [DONE_ATTR, KEY_ATTR, WRAP_ATTR, MMD_ATTR, TABLE_DONE_ATTR, "data-md-plus-lang", ZOOM_ATTR]) {
2282
+ for (const attr of [DONE_ATTR, WRAP_ATTR, MMD_ATTR, TABLE_DONE_ATTR, "data-md-plus-lang", ZOOM_ATTR]) {
1684
2283
  for (const node of document.querySelectorAll("[" + attr + "]")) node.removeAttribute(attr);
1685
2284
  }
1686
2285
  for (const node of document.querySelectorAll("[data-md-plus-original-label]")) {
@@ -1692,7 +2291,9 @@ window.__ModuleLoader__.load({
1692
2291
  )) {
1693
2292
  node.remove();
1694
2293
  }
1695
- document.documentElement.style.overflow = "";
2294
+ scrollLocks = 0;
2295
+ document.documentElement.style.overflow = previousRootOverflow;
2296
+ previousRootOverflow = "";
1696
2297
  document.documentElement.style.removeProperty("--md-plus-code-font-size");
1697
2298
  document.documentElement.style.removeProperty("--md-plus-code-line-height");
1698
2299
  document.querySelector("style[" + STYLE_ATTR + "]")?.remove();
@@ -1700,6 +2301,10 @@ window.__ModuleLoader__.load({
1700
2301
  script.remove();
1701
2302
  }
1702
2303
  scripts.clear();
2304
+ settingsCache = null;
2305
+ themeCache = null;
2306
+ diagramKindCache = /* @__PURE__ */ new WeakMap();
2307
+ cacheKeys = /* @__PURE__ */ new WeakMap();
1703
2308
  delete window[HL_GLOBAL];
1704
2309
  delete window[MMD_GLOBAL];
1705
2310
  };