dsh-md-plus 0.3.1 → 0.4.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/lib/client.js CHANGED
@@ -25,14 +25,201 @@ window.__ModuleLoader__.load({
25
25
  var ZOOM_MIN_MODAL = 0.1;
26
26
  var ZOOM_MIN_EPSILON = 1e-3;
27
27
  var EXPAND_CLASS = "md-plus-expand";
28
+ var THEME_CLASS = "md-plus-theme";
29
+ var RETRY_CLASS = "md-plus-retry";
30
+ var THEME_KEY = "dsh-md-plus-mermaid-theme";
31
+ var THEME_ORDER = ["blue", "neutral", "forest", "contrast"];
32
+ var THEME_LABELS = {
33
+ zh: { blue: "\u84DD\u8272", neutral: "\u4E2D\u6027", forest: "\u68EE\u6797", contrast: "\u9AD8\u5BF9\u6BD4" },
34
+ en: { blue: "Blue", neutral: "Neutral", forest: "Forest", contrast: "High contrast" }
35
+ };
36
+ function detectLang() {
37
+ var declared = (document.documentElement.lang || "").toLowerCase();
38
+ if (declared.startsWith("zh")) return "zh";
39
+ if (declared.startsWith("en")) return "en";
40
+ var nav = (window.navigator.language || "").toLowerCase();
41
+ return nav.startsWith("zh") ? "zh" : "en";
42
+ }
43
+ var TEXT = {
44
+ zh: {
45
+ wrapTitle: "\u4E0D\u6362\u884C\uFF08\u6A2A\u5411\u6EDA\u52A8\uFF09",
46
+ wrapAria: "\u5207\u6362\u4EE3\u7801\u6362\u884C",
47
+ zoomOut: "\u7F29\u5C0F",
48
+ zoomOutAria: "\u7F29\u5C0F\u6D41\u7A0B\u56FE",
49
+ zoomIn: "\u653E\u5927",
50
+ zoomInAria: "\u653E\u5927\u6D41\u7A0B\u56FE",
51
+ toggle: "\u5207\u6362\u56FE\u5F62 / \u4EE3\u7801",
52
+ toggleAria: "\u5207\u6362\u56FE\u5F62\u4E0E\u4EE3\u7801",
53
+ fullscreen: "\u5168\u5C4F\u67E5\u770B",
54
+ fullscreenAria: "\u5168\u5C4F\u67E5\u770B\u6D41\u7A0B\u56FE",
55
+ theme: "\u5207\u6362 Mermaid \u914D\u8272",
56
+ themeAria: "\u5207\u6362 Mermaid \u914D\u8272",
57
+ themePrefix: "\u914D\u8272\uFF1A",
58
+ foldOpen: "\u5C55\u5F00",
59
+ foldClose: "\u6536\u8D77",
60
+ retry: "\u91CD\u8BD5",
61
+ retryAria: "\u91CD\u65B0\u6E32\u67D3\u56FE\u8868",
62
+ renderFailed: "mermaid \u6E32\u67D3\u5931\u8D25\uFF1A",
63
+ diagram: "\u6D41\u7A0B\u56FE",
64
+ diagramAria: "\u6D41\u7A0B\u56FE\u5168\u5C4F\u67E5\u770B",
65
+ fit: "\u9002\u5E94\u6574\u56FE",
66
+ downloadSvg: "\u4E0B\u8F7D SVG",
67
+ downloadPng: "\u4E0B\u8F7D PNG",
68
+ close: "\u5173\u95ED",
69
+ tableCsv: "CSV",
70
+ tableMd: "Markdown",
71
+ copyCsvTitle: "\u590D\u5236\u8868\u683C\u4E3A CSV",
72
+ copyMdTitle: "\u590D\u5236\u8868\u683C\u4E3A Markdown",
73
+ copied: "\u5DF2\u590D\u5236",
74
+ copyFailed: "\u590D\u5236\u5931\u8D25",
75
+ imageViewer: "\u56FE\u7247\u9884\u89C8",
76
+ imageClose: "\u5173\u95ED\u56FE\u7247\u9884\u89C8",
77
+ settingsOpen: "\u6253\u5F00 Markdown+ \u8BBE\u7F6E",
78
+ settingsTitle: "Markdown+ \u8BBE\u7F6E",
79
+ settingsTheme: "Mermaid \u914D\u8272",
80
+ settingsFontSize: "\u4EE3\u7801\u5B57\u53F7",
81
+ settingsFontSizeHint: "\u7559\u7A7A\u6216 0 \u8868\u793A\u8DDF\u968F\u5BBF\u4E3B",
82
+ settingsView: "\u56FE\u8868\u9ED8\u8BA4\u89C6\u56FE",
83
+ settingsViewDiagram: "\u56FE\u5F62",
84
+ settingsViewCode: "\u4EE3\u7801",
85
+ settingsFold: "\u957F\u5185\u5BB9\u6298\u53E0\u9608\u503C",
86
+ settingsFoldCode: "\u4EE3\u7801\u5757",
87
+ settingsFoldTable: "\u8868\u683C",
88
+ settingsFoldDiagram: "\u56FE\u8868",
89
+ settingsFoldCodeHint: "\u5185\u5BB9\u9AD8\u5EA6\u8D85\u8FC7\u6B64\u503C\u540E\u6298\u53E0",
90
+ settingsFoldTableHint: "\u8868\u683C\u9AD8\u5EA6\u8D85\u8FC7\u6B64\u503C\u540E\u6298\u53E0",
91
+ settingsFoldDiagramHint: "\u56FE\u5F62\u9AD8\u5EA6\u8D85\u8FC7\u6B64\u503C\u540E\u6298\u53E0",
92
+ settingsFoldHint: "\u5355\u4F4D px\uFF0C0 \u8868\u793A\u4E0D\u6298\u53E0",
93
+ settingsSave: "\u4FDD\u5B58",
94
+ settingsReset: "\u6062\u590D\u9ED8\u8BA4",
95
+ settingsClose: "\u5173\u95ED"
96
+ },
97
+ en: {
98
+ wrapTitle: "No wrap (horizontal scroll)",
99
+ wrapAria: "Toggle code wrapping",
100
+ zoomOut: "Zoom out",
101
+ zoomOutAria: "Zoom out diagram",
102
+ zoomIn: "Zoom in",
103
+ zoomInAria: "Zoom in diagram",
104
+ toggle: "Switch diagram / code",
105
+ toggleAria: "Switch diagram and code",
106
+ fullscreen: "Fullscreen",
107
+ fullscreenAria: "View diagram fullscreen",
108
+ theme: "Change Mermaid theme",
109
+ themeAria: "Change Mermaid theme",
110
+ themePrefix: "Theme: ",
111
+ foldOpen: "Expand",
112
+ foldClose: "Collapse",
113
+ retry: "Retry",
114
+ retryAria: "Retry diagram",
115
+ renderFailed: "Mermaid render failed: ",
116
+ diagram: "Diagram",
117
+ diagramAria: "Diagram fullscreen viewer",
118
+ fit: "Fit",
119
+ downloadSvg: "Download SVG",
120
+ downloadPng: "Download PNG",
121
+ close: "Close",
122
+ tableCsv: "CSV",
123
+ tableMd: "Markdown",
124
+ copyCsvTitle: "Copy table as CSV",
125
+ copyMdTitle: "Copy table as Markdown",
126
+ copied: "Copied",
127
+ copyFailed: "Copy failed",
128
+ imageViewer: "Image preview",
129
+ imageClose: "Close image preview",
130
+ settingsOpen: "Open Markdown+ settings",
131
+ settingsTitle: "Markdown+ settings",
132
+ settingsTheme: "Mermaid theme",
133
+ settingsFontSize: "Code font size",
134
+ settingsFontSizeHint: "Leave empty or 0 to follow the host",
135
+ settingsView: "Default diagram view",
136
+ settingsViewDiagram: "Diagram",
137
+ settingsViewCode: "Code",
138
+ settingsFold: "Fold thresholds",
139
+ settingsFoldCode: "Code block",
140
+ settingsFoldTable: "Table",
141
+ settingsFoldDiagram: "Diagram",
142
+ settingsFoldCodeHint: "Fold when the block is taller than this",
143
+ settingsFoldTableHint: "Fold when the table is taller than this",
144
+ settingsFoldDiagramHint: "Fold when the diagram is taller than this",
145
+ settingsFoldHint: "Pixels; 0 disables folding",
146
+ settingsSave: "Save",
147
+ settingsReset: "Reset to defaults",
148
+ settingsClose: "Close"
149
+ }
150
+ };
151
+ function t(key) {
152
+ var lang = detectLang();
153
+ return TEXT[lang][key] ?? TEXT.en[key] ?? key;
154
+ }
28
155
  var MODAL_CLASS = "md-plus-mmd-modal";
29
156
  var DONE_ATTR = "data-md-plus";
30
157
  var KEY_ATTR = "data-md-plus-key";
31
158
  var WRAP_ATTR = "data-md-plus-wrap";
32
159
  var MMD_ATTR = "data-md-plus-mmd";
160
+ var FOLD_ATTR = "data-md-plus-fold";
161
+ var FOLD_CLASS = "md-plus-fold";
162
+ var FOLD_BTN_CLASS = "md-plus-fold-toggle";
163
+ var FOLD_LIMITS = { code: 480, table: 520, diagram: 720 };
164
+ var TABLE_TOOLS_CLASS = "md-plus-table-tools";
165
+ var TABLE_DONE_ATTR = "data-md-plus-table";
166
+ var IMG_MODAL_CLASS = "md-plus-img-modal";
167
+ var NATIVE_IMAGE_BUTTON = 'button[class*="imageButton"]';
168
+ var SETTINGS_BTN_CLASS = "md-plus-settings-btn";
169
+ var SETTINGS_MODAL_CLASS = "md-plus-settings-modal";
170
+ var SETTINGS_KEY = "dsh-md-plus-settings";
171
+ var DEFAULT_SETTINGS = {
172
+ codeFontSize: 0,
173
+ diagramView: "diagram",
174
+ foldCode: FOLD_LIMITS.code,
175
+ foldTable: FOLD_LIMITS.table,
176
+ foldDiagram: FOLD_LIMITS.diagram
177
+ };
33
178
  var STYLE_ATTR = "data-md-plus-style";
34
179
  var RUNTIME_ATTR = "data-md-plus-runtime";
35
180
  var DEBOUNCE_MS = 160;
181
+ function readSettings() {
182
+ var settings = { ...DEFAULT_SETTINGS };
183
+ try {
184
+ var raw = window.localStorage.getItem(SETTINGS_KEY);
185
+ var saved = raw === null ? null : JSON.parse(raw);
186
+ if (saved !== null && typeof saved === "object") {
187
+ var size = Number(saved.codeFontSize);
188
+ if (Number.isFinite(size) && size >= 10 && size <= 20) settings.codeFontSize = Math.round(size);
189
+ if (saved.diagramView === "code" || saved.diagramView === "diagram") {
190
+ settings.diagramView = saved.diagramView;
191
+ }
192
+ for (var key of ["foldCode", "foldTable", "foldDiagram"]) {
193
+ var value = Number(saved[key]);
194
+ if (Number.isFinite(value) && value >= 0) settings[key] = Math.min(4e3, Math.round(value));
195
+ }
196
+ }
197
+ } catch {
198
+ }
199
+ return settings;
200
+ }
201
+ function saveSettings(next) {
202
+ try {
203
+ window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(next));
204
+ } catch {
205
+ }
206
+ }
207
+ function foldLimit(kind) {
208
+ var settings = readSettings();
209
+ if (kind === "code") return settings.foldCode;
210
+ if (kind === "table") return settings.foldTable;
211
+ return settings.foldDiagram;
212
+ }
213
+ function applyCodeFontSize() {
214
+ var size = readSettings().codeFontSize;
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
+ }
222
+ }
36
223
  var DSH_LANGS = /* @__PURE__ */ new Set([
37
224
  "typescript",
38
225
  "ts",
@@ -144,7 +331,10 @@ window.__ModuleLoader__.load({
144
331
  "gradle"
145
332
  ]);
146
333
  var MMD_LANGS = /* @__PURE__ */ new Set(["mermaid", "mermaidjs", "mmd"]);
147
- 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;
148
338
  function icon(paths) {
149
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>";
150
340
  return 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '")';
@@ -170,12 +360,21 @@ window.__ModuleLoader__.load({
170
360
  var ICON_EXPAND = icon(
171
361
  "<path d='M8 3H3v5'/><path d='M16 3h5v5'/><path d='M3 16v5h5'/><path d='M21 16v5h-5'/>"
172
362
  );
363
+ var ICON_THEME = icon(
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'/>"
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
+ );
369
+ var ICON_GEAR = icon(
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'/>"
371
+ );
173
372
  var TABLE = '[class*="_markdown_"] div:has(> table)';
174
373
  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";
175
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";
176
375
  var CSS = [
177
376
  // ── 卡片:整块一个底色,顶栏不再是独立色带 ──────────────────────
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 + "}",
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 + "}",
179
378
  // bannerWrap 的原底色是 --dsw-alias-bg-base(#fff),会跟卡片灰拉开一条带
180
379
  ".md-code-block > div:has(> " + BANNER + "){background:transparent}",
181
380
  // ── 头部:收紧,并把空着的左边填上 ─────────────────────────────
@@ -187,14 +386,20 @@ window.__ModuleLoader__.load({
187
386
  ".md-code-block " + BANNER + " > :first-child::before{content:'';flex:none;width:14px;height:14px;background:currentColor;color:var(--dsw-alias-label-tertiary);-webkit-mask:var(--md-plus-icon) center/contain no-repeat;mask:var(--md-plus-icon) center/contain no-repeat}",
188
387
  // 没有语言时补个占位名,否则整行左边就是一片空白
189
388
  ".md-code-block " + BANNER + " > :first-child:empty::after{content:'Plain text'}",
389
+ ":lang(zh) .md-code-block " + BANNER + " > :first-child:empty::after{content:'\u7EAF\u6587\u672C'}",
190
390
  // ── 间距:头部与首行代码之间补 6px ──────────────────────────────
191
391
  // pre 的行高本身就在首行文字上方留了约半行空白;补 6px 后实测间距约 17px
192
392
  // (原来给 0 时只有 11px,贴着看太挤)
193
- ".md-code-block pre{padding:6px 16px 16px}",
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}",
194
397
  // ── 头部按钮:文字换成图标 ──────────────────────────────────────
195
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}",
196
399
  // font-size:0 只藏住「复制 / 已复制」的字面,可访问名仍在
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}",
400
+ ".md-code-block " + BANNER + " ." + BTN_CLASS + "::before{content:'';width:15px;height:15px;background:currentColor}",
401
+ // 旧版宿主的复制按钮只有文字,没有 SVG 图标,才需要补一枚;新版已有 SVG,不再叠加
402
+ ".md-code-block " + BANNER + " button:last-child: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}",
198
403
  ".md-code-block " + BANNER + " button:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,rgb(127 127 127 / 12%))}",
199
404
  ".md-code-block " + BANNER + " button:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill,#4c6ef5);outline-offset:1px}",
200
405
  // ── 普通块:不换行切换 ──────────────────────────────────────────
@@ -206,6 +411,8 @@ window.__ModuleLoader__.load({
206
411
  ".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}",
207
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}",
208
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}",
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}",
209
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%))}",
210
417
  // 已经到最小 / 最大时,对应那颗按钮变淡且不可点
211
418
  "[" + ZOOM_ATTR + '="min"] .' + ZOOM_OUT_CLASS + ",[" + ZOOM_ATTR + '="max"] .' + ZOOM_IN_CLASS + "{opacity:.3;pointer-events:none}",
@@ -234,6 +441,37 @@ window.__ModuleLoader__.load({
234
441
  "." + MODAL_CLASS + "-viewport{flex:1;min-height:0}",
235
442
  "." + MODAL_CLASS + "-stage{display:inline-flex;align-items:center;justify-content:center;min-height:0;flex:none}",
236
443
  "." + MODAL_CLASS + " svg{display:block}",
444
+ // ── 长内容折叠:代码块、表格、图表卡共用一套按钮 ───────────────
445
+ "." + FOLD_CLASS + "{position:relative}",
446
+ "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"]{max-height:var(--md-plus-fold-max-height);overflow:hidden}',
447
+ "." + 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}",
448
+ "." + FOLD_CLASS + "[" + FOLD_ATTR + '="on"] .' + FOLD_BTN_CLASS + "{position:absolute;left:50%;bottom:8px;transform:translateX(-50%);margin:0}",
449
+ // ── 图片点击放大 ────────────────────────────────────────────────
450
+ '[class*="_markdown_"] img{cursor:zoom-in}',
451
+ "." + IMG_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 / 72%)}",
452
+ "." + IMG_MODAL_CLASS + " img{max-width:92vw;max-height:86vh;border-radius:8px;background:#fff;box-shadow:0 24px 80px rgb(0 0 0 / 35%)}",
453
+ "." + IMG_MODAL_CLASS + "-close{position:absolute;top:calc(var(--dsh-windows-titlebar-height,0px) + 18px);right:24px;width:32px;height:32px;padding:0;border:0;border-radius:999px;background:rgb(255 255 255 / 14%);color:#fff;font:20px/1 var(--dsw-font-family-sans,system-ui,sans-serif);cursor:pointer}",
454
+ "." + IMG_MODAL_CLASS + "-close:hover{background:rgb(255 255 255 / 24%)}",
455
+ // ── 设置面板 ────────────────────────────────────────────────────
456
+ ":root{--md-plus-icon-gear:" + ICON_GEAR + "}",
457
+ "." + SETTINGS_BTN_CLASS + "{position:fixed;right:16px;bottom:76px;z-index:2147482000;display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:1px solid var(--dsw-alias-border-secondary,rgb(127 127 127 / 18%));border-radius:999px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-tertiary);opacity:.45;cursor:pointer;box-shadow:0 2px 10px rgb(0 0 0 / 12%);transition:opacity .12s ease,color .12s ease}",
458
+ "." + SETTINGS_BTN_CLASS + ":hover{opacity:1;color:var(--dsw-alias-label-primary)}",
459
+ "." + 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}",
460
+ "." + 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%)}",
461
+ "." + 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%)}",
462
+ "." + 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)}",
463
+ "." + SETTINGS_MODAL_CLASS + "-row{display:flex;align-items:center;gap:12px;margin:11px 0}",
464
+ "." + 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)}",
465
+ "." + SETTINGS_MODAL_CLASS + "-label-title{display:block}",
466
+ "." + 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)}",
467
+ "." + 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)}",
468
+ "." + 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)}",
469
+ "." + 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)}",
470
+ "." + SETTINGS_MODAL_CLASS + "-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}",
471
+ "." + 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}",
472
+ "." + SETTINGS_MODAL_CLASS + "-actions button:not(." + SETTINGS_MODAL_CLASS + "-primary):hover{background:var(--dsw-alias-interactive-bg-active,rgb(76 110 245 / 12%))}",
473
+ "." + SETTINGS_MODAL_CLASS + "-actions ." + SETTINGS_MODAL_CLASS + "-primary{background:var(--dsw-alias-button-primary-fill,#4c6ef5);color:var(--dsw-alias-label-primary-foreground,#fff)}",
474
+ "." + SETTINGS_MODAL_CLASS + "-actions ." + SETTINGS_MODAL_CLASS + "-primary:hover{background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,#4c6ef5))}",
237
475
  // 高亮宿主不参与布局,让里面的 <pre> 直接吃到卡片样式
238
476
  "." + HOST_CLASS + "{display:contents}",
239
477
  // 藏掉纯文本回退,只留着色结果;原节点仍在,复制读的还是它
@@ -242,12 +480,12 @@ window.__ModuleLoader__.load({
242
480
  // 抓手用结构选择器而不是 DSH 的哈希类名:外层是「唯一子元素是 table 的 div」。
243
481
  // 前面挂 [class*="_markdown_"] 有两个作用——只认 markdown 渲染出来的表格,
244
482
  // 同时把特异性顶到 (0,1,4),压过 DSH 自己的 .tableScroll th (0,1,1)。
245
- TABLE + "{margin-block:16px;" + TABLE_VARS + "}",
483
+ TABLE + "{position:relative;margin-block:16px;max-height:min(70vh,720px);overflow-y:auto;overscroll-behavior-y:contain;" + TABLE_VARS + "}",
246
484
  "body[data-ds-dark-theme] " + TABLE + "{" + TABLE_VARS_DARK + "}",
247
485
  // 整张表一个 1px 边框 + 小圆角。
248
486
  // 注意必须是 separate:collapse 模式下 Chrome 会忽略单元格的圆角,
249
487
  // 表头的灰底会在四角戳出直角。border-spacing 归零所以不会有缝。
250
- TABLE + " > table{border:1px solid var(--md-plus-tbl-line);border-collapse:separate;border-spacing:0;border-radius:var(--md-plus-tbl-radius);overflow:hidden}",
488
+ TABLE + " > table{border:1px solid var(--md-plus-tbl-line);border-collapse:separate;border-spacing:0;border-radius:var(--md-plus-tbl-radius)}",
251
489
  // 表头:浅灰底 + 加粗深色字(DSH 原本没有底色)
252
490
  TABLE + " > table th{background:var(--md-plus-tbl-head);color:var(--md-plus-tbl-head-fg);font-weight:600;padding:10px 16px;border-bottom:1px solid var(--md-plus-tbl-line);border-top:0}",
253
491
  TABLE + " > table td{padding:10px 16px;border-bottom:1px solid var(--md-plus-tbl-line)}",
@@ -266,6 +504,12 @@ window.__ModuleLoader__.load({
266
504
  TABLE + " > table tr:last-child td{border-bottom:0}",
267
505
  // 宽表横向扫读时给个落点
268
506
  TABLE + " > table tbody tr:hover td{background:var(--md-plus-tbl-hover)}",
507
+ // 长表往下滚时表头留在视口顶部;底色不透明才不会透出下面的行
508
+ TABLE + " > table thead th{position:sticky;top:0;z-index:1}",
509
+ // 复制按钮放在表尾右侧:不压住表头文字,表格折叠时也能随展开一起出现
510
+ TABLE + " > table > caption." + TABLE_TOOLS_CLASS + "{caption-side:top;text-align:right;padding:0 0 6px}",
511
+ 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}",
512
+ TABLE + " > table > caption." + TABLE_TOOLS_CLASS + " button:hover{color:var(--dsw-alias-label-primary);background:var(--md-plus-tbl-hover)}",
269
513
  // 宽表(≥4 列)不收着不行。宿主给这类表加了 .md-table-wide,并让它「出血」:
270
514
  // 包裹层撑到面板宽,再用 padding-left 把表推到正文栏左侧。那个 padding 会吃掉
271
515
  // 可用宽度,结果是表格比正文宽出一截、右边还被裁掉,而滚动条只在鼠标悬停时
@@ -300,37 +544,130 @@ window.__ModuleLoader__.load({
300
544
  script.src = url;
301
545
  script.async = true;
302
546
  script.setAttribute(RUNTIME_ATTR, "1");
303
- script.addEventListener("load", () => resolve(window[globalName] ?? null), { once: true });
304
- script.addEventListener("error", () => resolve(null), { once: true });
547
+ script.addEventListener(
548
+ "load",
549
+ () => {
550
+ var api = window[globalName];
551
+ if (api === void 0) scripts.delete(url);
552
+ resolve(api ?? null);
553
+ },
554
+ { once: true }
555
+ );
556
+ script.addEventListener(
557
+ "error",
558
+ () => {
559
+ scripts.delete(url);
560
+ script.remove();
561
+ resolve(null);
562
+ },
563
+ { once: true }
564
+ );
305
565
  document.head.appendChild(script);
306
566
  });
307
567
  scripts.set(url, promise);
308
568
  return promise;
309
569
  }
310
- function readLang(block) {
570
+ function readBannerLabel(block) {
311
571
  var banner = block.querySelector(BANNER);
312
- var info = banner?.firstElementChild;
572
+ var info = banner?.querySelector('[class*="_language_"]') ?? banner?.firstElementChild;
313
573
  return (info?.textContent ?? "").trim().toLowerCase();
314
574
  }
575
+ var PLACEHOLDER_LABELS = /* @__PURE__ */ new Set(["", "\u4EE3\u7801\u5757", "code block", "code", "plain text", "plaintext", "\u7EAF\u6587\u672C"]);
576
+ var FIBER_KEY = "__reactFiber$";
577
+ function isPlaceholderLabel(label) {
578
+ return PLACEHOLDER_LABELS.has(label);
579
+ }
580
+ function readFiberLang(block) {
581
+ var info = null;
582
+ var nodes = [block, block.querySelector(CONTENT)];
583
+ for (var node of nodes) {
584
+ if (node === null || node === void 0) continue;
585
+ var names;
586
+ try {
587
+ names = Object.getOwnPropertyNames(node);
588
+ } catch {
589
+ continue;
590
+ }
591
+ for (var name of names) {
592
+ if (name.lastIndexOf(FIBER_KEY, 0) !== 0) continue;
593
+ var fiber = node[name];
594
+ for (var depth = 0; fiber !== null && fiber !== void 0 && depth < 24; depth += 1) {
595
+ var props = fiber.memoizedProps;
596
+ if (props !== null && typeof props === "object" && Object.prototype.hasOwnProperty.call(props, "lang")) {
597
+ var value = typeof props.lang === "string" ? props.lang.trim().toLowerCase() : "";
598
+ info = { hasLang: value !== "", lang: value };
599
+ break;
600
+ }
601
+ fiber = fiber.return;
602
+ }
603
+ if (info !== null) break;
604
+ }
605
+ if (info !== null) break;
606
+ }
607
+ return info;
608
+ }
609
+ function readLang(block) {
610
+ var label = readBannerLabel(block);
611
+ if (!isPlaceholderLabel(label)) return label;
612
+ var info = readFiberLang(block);
613
+ return info === null ? "" : info.lang;
614
+ }
615
+ function paintLanguageLabel(block, lang) {
616
+ if (lang === "") return;
617
+ var banner = block.querySelector(BANNER);
618
+ var heading = banner?.firstElementChild;
619
+ if (heading === null || heading === void 0) return;
620
+ var target = banner.querySelector('[class*="_language_"]') ?? heading;
621
+ if ((target.textContent ?? "").trim().toLowerCase() === lang) return;
622
+ if (!target.hasAttribute("data-md-plus-original-label")) {
623
+ target.setAttribute("data-md-plus-original-label", target.textContent ?? "");
624
+ }
625
+ target.textContent = lang;
626
+ target.setAttribute("data-md-plus-lang", lang);
627
+ }
315
628
  function sniffDiagramType(code) {
316
- var text = code;
629
+ var lines = code.split("\n");
630
+ var start = 0;
631
+ while (start < lines.length) {
632
+ var line = lines[start].trim();
633
+ if (line === "" || line.startsWith("%%")) {
634
+ start += 1;
635
+ continue;
636
+ }
637
+ break;
638
+ }
639
+ var text = lines.slice(start).join("\n");
317
640
  if (text.startsWith("---")) {
318
641
  var blank = text.indexOf("\n");
319
642
  var close = blank < 0 ? -1 : text.indexOf("\n---", blank);
320
643
  text = close < 0 ? "" : text.slice(close + 4);
321
644
  }
322
- var body = text.split("\n").filter((line) => !line.trimStart().startsWith("%%")).join("\n");
323
- return MMD_TYPE.exec(body)?.[1] ?? null;
645
+ var first = text.split("\n").find((line2) => {
646
+ var value2 = line2.trim();
647
+ return value2 !== "" && !value2.startsWith("%%");
648
+ });
649
+ if (first === void 0) return null;
650
+ var value = first.trim();
651
+ var flow = MMD_FLOW.exec(value);
652
+ if (flow !== null) return flow[1];
653
+ if (MMD_PIE.test(value)) return "pie";
654
+ if (MMD_GIT.test(value)) return "gitGraph";
655
+ var single = MMD_SINGLE.exec(value);
656
+ return single === null ? null : single[1];
324
657
  }
325
658
  function diagramKind(block) {
326
- if (MMD_LANGS.has(readLang(block))) return "diagram";
659
+ var lang = readLang(block);
660
+ if (MMD_LANGS.has(lang)) return "diagram";
661
+ if (lang !== "") return null;
327
662
  var pre = block.querySelector(CONTENT + " pre");
328
663
  if (pre === null) return null;
329
664
  return sniffDiagramType(pre.textContent ?? "") === null ? null : "diagram";
330
665
  }
331
666
  function isOurs(node) {
332
667
  var element = node?.nodeType === 1 ? node : node?.parentElement;
333
- return element?.closest?.("." + HOST_CLASS + ", ." + BTN_CLASS + ", ." + MMD_CLASS) != null;
668
+ return element?.closest?.(
669
+ "." + HOST_CLASS + ", ." + BTN_CLASS + ", ." + MMD_CLASS + ", ." + FOLD_BTN_CLASS + ", ." + TABLE_TOOLS_CLASS + ", ." + IMG_MODAL_CLASS + ", ." + SETTINGS_BTN_CLASS + ", ." + SETTINGS_MODAL_CLASS
670
+ ) != null;
334
671
  }
335
672
  function schedule(block) {
336
673
  if (block !== void 0) pending.add(block);
@@ -350,6 +687,8 @@ window.__ModuleLoader__.load({
350
687
  decorate(block);
351
688
  void enhance(block);
352
689
  }
690
+ scanFolds();
691
+ ensureSettingsButton();
353
692
  }
354
693
  function onMutations(records) {
355
694
  for (const record of records) {
@@ -371,7 +710,8 @@ window.__ModuleLoader__.load({
371
710
  }
372
711
  }
373
712
  function redrawDiagrams() {
374
- for (const block of document.querySelectorAll("[" + MMD_ATTR + '="diagram"]')) {
713
+ for (const block of document.querySelectorAll("[" + MMD_ATTR + "]")) {
714
+ if (block.getAttribute(MMD_ATTR) === "failed") continue;
375
715
  block.removeAttribute(KEY_ATTR);
376
716
  pending.add(block);
377
717
  }
@@ -380,8 +720,8 @@ window.__ModuleLoader__.load({
380
720
  var PLAIN_BUTTONS = [
381
721
  {
382
722
  cls: TOOL_CLASS,
383
- title: "\u4E0D\u6362\u884C\uFF08\u6A2A\u5411\u6EDA\u52A8\uFF09",
384
- aria: "\u5207\u6362\u4EE3\u7801\u6362\u884C",
723
+ titleKey: "wrapTitle",
724
+ ariaKey: "wrapAria",
385
725
  run: (block) => {
386
726
  if (block.getAttribute(WRAP_ATTR) === "on") block.removeAttribute(WRAP_ATTR);
387
727
  else block.setAttribute(WRAP_ATTR, "on");
@@ -389,24 +729,56 @@ window.__ModuleLoader__.load({
389
729
  }
390
730
  ];
391
731
  var MMD_BUTTONS = [
392
- { cls: ZOOM_OUT_CLASS, title: "\u7F29\u5C0F", aria: "\u7F29\u5C0F\u6D41\u7A0B\u56FE", run: (block) => zoomBy(block, 1 / ZOOM_STEP) },
393
- { cls: ZOOM_IN_CLASS, title: "\u653E\u5927", aria: "\u653E\u5927\u6D41\u7A0B\u56FE", run: (block) => zoomBy(block, ZOOM_STEP) },
732
+ { cls: ZOOM_OUT_CLASS, titleKey: "zoomOut", ariaKey: "zoomOutAria", run: (block) => zoomBy(block, 1 / ZOOM_STEP) },
733
+ { cls: ZOOM_IN_CLASS, titleKey: "zoomIn", ariaKey: "zoomInAria", run: (block) => zoomBy(block, ZOOM_STEP) },
394
734
  {
395
735
  cls: TOGGLE_CLASS,
396
- title: "\u5207\u6362\u56FE\u5F62 / \u4EE3\u7801",
397
- aria: "\u5207\u6362\u56FE\u5F62\u4E0E\u4EE3\u7801",
736
+ titleKey: "toggle",
737
+ ariaKey: "toggleAria",
398
738
  run: (block) => {
399
739
  var next = block.getAttribute(MMD_ATTR) === "code" ? "diagram" : "code";
400
740
  block.setAttribute(MMD_ATTR, next);
741
+ var viewport = block.querySelector("." + MMD_CLASS + "-viewport");
742
+ if (viewport !== null) {
743
+ resetFold(viewport);
744
+ scheduleFold(viewport, foldLimit("diagram"));
745
+ }
401
746
  }
402
747
  },
403
748
  {
404
749
  cls: EXPAND_CLASS,
405
- title: "\u5168\u5C4F\u67E5\u770B",
406
- aria: "\u5168\u5C4F\u67E5\u770B\u6D41\u7A0B\u56FE",
750
+ titleKey: "fullscreen",
751
+ ariaKey: "fullscreenAria",
407
752
  run: (block) => openFullscreen(block)
753
+ },
754
+ {
755
+ cls: THEME_CLASS,
756
+ titleKey: "theme",
757
+ ariaKey: "themeAria",
758
+ run: (block) => cycleMermaidTheme(block)
759
+ }
760
+ ];
761
+ var FAILED_BUTTONS = [
762
+ {
763
+ cls: RETRY_CLASS,
764
+ titleKey: "retry",
765
+ ariaKey: "retryAria",
766
+ run: (block) => {
767
+ block.removeAttribute(KEY_ATTR);
768
+ block.removeAttribute(MMD_ATTR);
769
+ block.querySelector("." + MMD_CLASS + "-error")?.remove();
770
+ void enhance(block);
771
+ }
408
772
  }
409
773
  ];
774
+ function findNativeWrap(buttons) {
775
+ if (buttons === void 0) return null;
776
+ for (const button of buttons) {
777
+ var label = (button.getAttribute("aria-label") ?? "") + " " + (button.title ?? "") + " " + (button.textContent ?? "");
778
+ if (/换行|wrap/i.test(label)) return button;
779
+ }
780
+ return buttons.length >= 2 ? buttons[0] : null;
781
+ }
410
782
  function decorate(block) {
411
783
  if (!block.isConnected) return;
412
784
  var banner = block.querySelector(BANNER);
@@ -414,7 +786,10 @@ window.__ModuleLoader__.load({
414
786
  var copy = nativeButtons === void 0 || nativeButtons.length === 0 ? null : nativeButtons[nativeButtons.length - 1];
415
787
  var action = copy?.parentElement;
416
788
  if (action === null || action === void 0) return;
417
- var specs = readLang(block) !== "" && diagramKind(block) !== null ? MMD_BUTTONS : PLAIN_BUTTONS;
789
+ var label = readBannerLabel(block);
790
+ var lang = readLang(block);
791
+ if (lang !== "" && isPlaceholderLabel(label)) paintLanguageLabel(block, lang);
792
+ var specs = block.getAttribute(MMD_ATTR) === "failed" ? FAILED_BUTTONS : diagramKind(block) !== null ? MMD_BUTTONS : findNativeWrap(nativeButtons) !== null ? [] : PLAIN_BUTTONS;
418
793
  for (const existing of action.querySelectorAll("." + BTN_CLASS)) {
419
794
  if (specs.some((spec) => existing.classList.contains(spec.cls))) continue;
420
795
  existing.remove();
@@ -425,8 +800,13 @@ window.__ModuleLoader__.load({
425
800
  var created = document.createElement("button");
426
801
  created.type = "button";
427
802
  created.className = BTN_CLASS + " " + spec.cls;
428
- created.title = spec.title;
429
- created.setAttribute("aria-label", spec.aria);
803
+ created.title = t(spec.titleKey);
804
+ created.setAttribute("aria-label", t(spec.ariaKey));
805
+ if (spec.cls === THEME_CLASS) {
806
+ var themeText = themeLabel(getMermaidTheme());
807
+ created.title = themeText;
808
+ created.setAttribute("aria-label", themeText);
809
+ }
430
810
  created.addEventListener("click", () => spec.run(block));
431
811
  action.insertBefore(created, copy);
432
812
  injected.add(created);
@@ -450,11 +830,12 @@ window.__ModuleLoader__.load({
450
830
  var pre = content.querySelector("pre");
451
831
  if (pre === null || pre.classList.contains("shiki")) return;
452
832
  var lang = readLang(block);
453
- if (lang === "") return;
833
+ if (lang !== "" && isPlaceholderLabel(readBannerLabel(block))) paintLanguageLabel(block, lang);
454
834
  if (diagramKind(block) !== null) {
455
835
  await renderDiagram(block, content, pre);
456
836
  return;
457
837
  }
838
+ if (lang === "") return;
458
839
  if (DSH_LANGS.has(lang)) return;
459
840
  var api = await loadScript(HL_URL, HL_GLOBAL);
460
841
  if (api === null || !api.supports(lang)) return;
@@ -484,9 +865,40 @@ window.__ModuleLoader__.load({
484
865
  if (shikiPre === null) return;
485
866
  shikiPre.classList.add("md-plus-pre");
486
867
  content.querySelector("." + HOST_CLASS)?.remove();
868
+ resetFold(content);
487
869
  pre.after(host);
488
870
  block.setAttribute(KEY_ATTR, key);
489
871
  block.setAttribute(DONE_ATTR, "done");
872
+ scheduleFold(content, foldLimit("code"));
873
+ }
874
+ function getMermaidTheme() {
875
+ try {
876
+ var saved = window.localStorage.getItem(THEME_KEY);
877
+ if (saved !== null && THEME_ORDER.indexOf(saved) !== -1) return saved;
878
+ } catch {
879
+ }
880
+ return "blue";
881
+ }
882
+ function themeLabel(name) {
883
+ return t("themePrefix") + (THEME_LABELS[detectLang()][name] ?? name);
884
+ }
885
+ function refreshThemeButtons() {
886
+ var label = themeLabel(getMermaidTheme());
887
+ for (const button of document.querySelectorAll("." + THEME_CLASS)) {
888
+ button.title = label;
889
+ button.setAttribute("aria-label", label);
890
+ }
891
+ }
892
+ function cycleMermaidTheme(block) {
893
+ var current = getMermaidTheme();
894
+ var index = THEME_ORDER.indexOf(current);
895
+ var next = THEME_ORDER[(index + 1) % THEME_ORDER.length];
896
+ try {
897
+ window.localStorage.setItem(THEME_KEY, next);
898
+ } catch {
899
+ }
900
+ refreshThemeButtons();
901
+ redrawDiagrams();
490
902
  }
491
903
  async function renderDiagram(block, content, pre) {
492
904
  var code = (pre.textContent ?? "").trim();
@@ -499,7 +911,7 @@ window.__ModuleLoader__.load({
499
911
  var svg;
500
912
  try {
501
913
  var dark = document.body.hasAttribute("data-ds-dark-theme");
502
- svg = await api.render("dsh-md-plus-mmd-" + ++diagramSeq, code, dark);
914
+ svg = await api.render("dsh-md-plus-mmd-" + ++diagramSeq, code, dark, getMermaidTheme());
503
915
  } catch (error) {
504
916
  failDiagram(block, content, pre, key, error);
505
917
  return;
@@ -524,9 +936,12 @@ window.__ModuleLoader__.load({
524
936
  pre.before(host);
525
937
  content.querySelector("." + MMD_CLASS + "-error")?.remove();
526
938
  block.setAttribute(KEY_ATTR, key);
527
- if (block.getAttribute(MMD_ATTR) !== "code") block.setAttribute(MMD_ATTR, "diagram");
939
+ var view = block.getAttribute(MMD_ATTR);
940
+ if (view !== "code" && view !== "diagram") block.setAttribute(MMD_ATTR, readSettings().diagramView);
528
941
  zooms.delete(block);
529
942
  paint(block);
943
+ scheduleFold(viewport, foldLimit("diagram"));
944
+ decorate(block);
530
945
  }
531
946
  var zooms = /* @__PURE__ */ new WeakMap();
532
947
  function zoomState(block) {
@@ -577,11 +992,12 @@ window.__ModuleLoader__.load({
577
992
  clampPan(block, state);
578
993
  paint(block);
579
994
  }
580
- function bindViewport(block, viewport, resetView) {
995
+ function bindViewport(block, viewport, resetView, alwaysZoom) {
581
996
  var drag = null;
582
997
  viewport.addEventListener(
583
998
  "wheel",
584
999
  (event) => {
1000
+ if (!alwaysZoom && !event.ctrlKey && !event.metaKey && zoomState(block).scale <= 1.001) return;
585
1001
  event.preventDefault();
586
1002
  zoomBy(block, event.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP, event.clientX, event.clientY);
587
1003
  },
@@ -627,6 +1043,362 @@ window.__ModuleLoader__.load({
627
1043
  paint(block);
628
1044
  });
629
1045
  }
1046
+ function resetFold(target) {
1047
+ if (target === null) return;
1048
+ target.removeAttribute(FOLD_ATTR);
1049
+ target.classList.remove(FOLD_CLASS);
1050
+ target.style.removeProperty("--md-plus-fold-max-height");
1051
+ target.querySelector("." + FOLD_BTN_CLASS)?.remove();
1052
+ }
1053
+ function scheduleFold(target, limit) {
1054
+ if (target === null) return;
1055
+ if (!(limit > 0)) {
1056
+ resetFold(target);
1057
+ target.setAttribute(FOLD_ATTR, "none");
1058
+ return;
1059
+ }
1060
+ if (target.getAttribute(FOLD_ATTR) === "none") {
1061
+ if (target.scrollHeight <= limit) return;
1062
+ target.removeAttribute(FOLD_ATTR);
1063
+ }
1064
+ if (target.getAttribute(FOLD_ATTR) !== null) return;
1065
+ var schedule2 = window.requestAnimationFrame ?? ((cb) => window.setTimeout(cb, 0));
1066
+ schedule2(() => {
1067
+ if (!target.isConnected || target.getAttribute(FOLD_ATTR) !== null) return;
1068
+ if (target.scrollHeight <= limit) {
1069
+ target.setAttribute(FOLD_ATTR, "none");
1070
+ return;
1071
+ }
1072
+ target.setAttribute(FOLD_ATTR, "on");
1073
+ target.classList.add(FOLD_CLASS);
1074
+ target.style.setProperty("--md-plus-fold-max-height", limit + "px");
1075
+ var button = document.createElement("button");
1076
+ button.type = "button";
1077
+ button.className = FOLD_BTN_CLASS;
1078
+ button.textContent = t("foldOpen");
1079
+ button.setAttribute("aria-expanded", "false");
1080
+ button.addEventListener("click", () => {
1081
+ var folded = target.getAttribute(FOLD_ATTR) === "on";
1082
+ target.setAttribute(FOLD_ATTR, folded ? "off" : "on");
1083
+ button.textContent = folded ? t("foldClose") : t("foldOpen");
1084
+ button.setAttribute("aria-expanded", folded ? "true" : "false");
1085
+ });
1086
+ target.appendChild(button);
1087
+ });
1088
+ }
1089
+ function scanFolds() {
1090
+ for (const block of document.querySelectorAll(BLOCK)) {
1091
+ if (block.closest("[data-streaming]") !== null) continue;
1092
+ if (diagramKind(block) !== null) continue;
1093
+ scheduleFold(block.querySelector(CONTENT), foldLimit("code"));
1094
+ }
1095
+ for (const table of document.querySelectorAll(TABLE)) {
1096
+ if (table.closest("[data-streaming]") !== null) continue;
1097
+ decorateTable(table);
1098
+ scheduleFold(table, foldLimit("table"));
1099
+ }
1100
+ }
1101
+ function cellText(cell) {
1102
+ return (cell.textContent ?? "").replace(/\s+/g, " ").trim();
1103
+ }
1104
+ function tableRows(table) {
1105
+ return Array.from(table.rows).map((row) => Array.from(row.cells).map(cellText));
1106
+ }
1107
+ function tableToCsv(rows) {
1108
+ return rows.map(
1109
+ (row) => row.map((value) => /[",\n]/.test(value) ? '"' + value.replace(/"/g, '""') + '"' : value).join(",")
1110
+ ).join("\n");
1111
+ }
1112
+ function tableToMarkdown(rows) {
1113
+ if (rows.length === 0) return "";
1114
+ var width = Math.max(...rows.map((row) => row.length));
1115
+ var line = (row) => "| " + row.concat(Array(width - row.length).fill("")).map((value) => value.replace(/\|/g, "\\|")).join(" | ") + " |";
1116
+ var output = [line(rows[0]), "| " + Array(width).fill("---").join(" | ") + " |"];
1117
+ for (var index = 1; index < rows.length; index += 1) output.push(line(rows[index]));
1118
+ return output.join("\n");
1119
+ }
1120
+ async function copyText(text) {
1121
+ if (window.navigator.clipboard !== void 0 && typeof window.navigator.clipboard.writeText === "function") {
1122
+ await window.navigator.clipboard.writeText(text);
1123
+ return true;
1124
+ }
1125
+ var area = document.createElement("textarea");
1126
+ area.value = text;
1127
+ area.style.position = "fixed";
1128
+ area.style.opacity = "0";
1129
+ document.body.appendChild(area);
1130
+ area.select();
1131
+ var ok = false;
1132
+ try {
1133
+ ok = document.execCommand("copy");
1134
+ } catch {
1135
+ ok = false;
1136
+ }
1137
+ area.remove();
1138
+ return ok;
1139
+ }
1140
+ function flashButton(button, text) {
1141
+ var original = button.getAttribute("data-md-plus-label") ?? button.textContent;
1142
+ button.setAttribute("data-md-plus-label", original);
1143
+ button.textContent = text;
1144
+ window.setTimeout(() => {
1145
+ button.textContent = original;
1146
+ }, 1200);
1147
+ }
1148
+ function decorateTable(table) {
1149
+ var dataTable = table.querySelector("table");
1150
+ if (dataTable === null) return;
1151
+ var first = dataTable.firstElementChild;
1152
+ if (first !== null && first.classList.contains(TABLE_TOOLS_CLASS)) return;
1153
+ table.setAttribute(TABLE_DONE_ATTR, "1");
1154
+ var tools = document.createElement("caption");
1155
+ tools.className = TABLE_TOOLS_CLASS;
1156
+ for (const [kind, label, titleKey, convert] of [
1157
+ ["csv", t("tableCsv"), "copyCsvTitle", tableToCsv],
1158
+ ["md", t("tableMd"), "copyMdTitle", tableToMarkdown]
1159
+ ]) {
1160
+ var button = document.createElement("button");
1161
+ button.type = "button";
1162
+ button.textContent = label;
1163
+ button.title = t(titleKey);
1164
+ button.setAttribute("aria-label", t(titleKey));
1165
+ button.addEventListener("click", async () => {
1166
+ var ok = false;
1167
+ try {
1168
+ ok = await copyText(convert(tableRows(dataTable)));
1169
+ } catch {
1170
+ ok = false;
1171
+ }
1172
+ flashButton(button, ok ? t("copied") : t("copyFailed"));
1173
+ });
1174
+ tools.appendChild(button);
1175
+ }
1176
+ dataTable.insertBefore(tools, dataTable.firstChild);
1177
+ }
1178
+ function openImage(source, alt) {
1179
+ if (document.querySelector("." + IMG_MODAL_CLASS) !== null) return;
1180
+ var overlay = document.createElement("div");
1181
+ overlay.className = IMG_MODAL_CLASS;
1182
+ overlay.setAttribute("role", "dialog");
1183
+ overlay.setAttribute("aria-modal", "true");
1184
+ overlay.setAttribute("aria-label", t("imageViewer"));
1185
+ var image = document.createElement("img");
1186
+ image.src = source;
1187
+ image.alt = alt;
1188
+ var closeButton = document.createElement("button");
1189
+ closeButton.type = "button";
1190
+ closeButton.className = IMG_MODAL_CLASS + "-close";
1191
+ closeButton.textContent = "\xD7";
1192
+ closeButton.setAttribute("aria-label", t("imageClose"));
1193
+ var previousFocus = document.activeElement;
1194
+ var closed = false;
1195
+ function closeViewer() {
1196
+ if (closed) return;
1197
+ closed = true;
1198
+ document.removeEventListener("keydown", onKey, true);
1199
+ overlay.remove();
1200
+ if (previousFocus !== null && typeof previousFocus.focus === "function") previousFocus.focus();
1201
+ }
1202
+ function onKey(event) {
1203
+ if (event.key === "Escape") {
1204
+ event.preventDefault();
1205
+ closeViewer();
1206
+ }
1207
+ }
1208
+ overlay.addEventListener("pointerdown", (event) => {
1209
+ if (event.target === overlay) closeViewer();
1210
+ });
1211
+ closeButton.addEventListener("click", closeViewer);
1212
+ document.addEventListener("keydown", onKey, true);
1213
+ overlay.appendChild(image);
1214
+ overlay.appendChild(closeButton);
1215
+ document.body.appendChild(overlay);
1216
+ closeButton.focus();
1217
+ }
1218
+ function onDocumentClick(event) {
1219
+ if (event.button !== void 0 && event.button !== 0) return;
1220
+ var target = event.target;
1221
+ var image = target?.closest?.("img");
1222
+ if (image === null || image === void 0) return;
1223
+ if (image.closest("." + IMG_MODAL_CLASS + ", .md-code-block, ." + SETTINGS_MODAL_CLASS) !== null) return;
1224
+ if (image.closest('[class*="_markdown_"]') === null) return;
1225
+ if (image.closest(NATIVE_IMAGE_BUTTON) !== null || image.closest("a") !== null) return;
1226
+ event.preventDefault();
1227
+ openImage(image.currentSrc || image.src, image.alt || "");
1228
+ }
1229
+ function settingRow(labelText, control, hintText) {
1230
+ var row = document.createElement("div");
1231
+ row.className = SETTINGS_MODAL_CLASS + "-row";
1232
+ var label = document.createElement("span");
1233
+ label.className = SETTINGS_MODAL_CLASS + "-label";
1234
+ var title = document.createElement("span");
1235
+ title.className = SETTINGS_MODAL_CLASS + "-label-title";
1236
+ title.textContent = labelText;
1237
+ label.appendChild(title);
1238
+ if (hintText !== void 0) {
1239
+ var hint = document.createElement("span");
1240
+ hint.className = SETTINGS_MODAL_CLASS + "-label-hint";
1241
+ hint.textContent = hintText;
1242
+ label.appendChild(hint);
1243
+ }
1244
+ row.appendChild(label);
1245
+ row.appendChild(control);
1246
+ return row;
1247
+ }
1248
+ function numberInput(value, min, max) {
1249
+ var input = document.createElement("input");
1250
+ input.type = "number";
1251
+ input.min = String(min);
1252
+ input.max = String(max);
1253
+ input.value = value > 0 ? String(value) : "";
1254
+ input.placeholder = "0";
1255
+ return input;
1256
+ }
1257
+ function rescanFolds() {
1258
+ for (const node of document.querySelectorAll("[" + FOLD_ATTR + "]")) resetFold(node);
1259
+ for (const viewport of document.querySelectorAll("." + MMD_CLASS + "-viewport")) {
1260
+ resetFold(viewport);
1261
+ scheduleFold(viewport, foldLimit("diagram"));
1262
+ }
1263
+ scanFolds();
1264
+ }
1265
+ function openSettings() {
1266
+ if (document.querySelector("." + SETTINGS_MODAL_CLASS) !== null) return;
1267
+ var current = readSettings();
1268
+ var overlay = document.createElement("div");
1269
+ overlay.className = SETTINGS_MODAL_CLASS;
1270
+ overlay.setAttribute("role", "dialog");
1271
+ overlay.setAttribute("aria-modal", "true");
1272
+ overlay.setAttribute("aria-label", t("settingsTitle"));
1273
+ var panel = document.createElement("div");
1274
+ panel.className = SETTINGS_MODAL_CLASS + "-panel";
1275
+ var title = document.createElement("h2");
1276
+ title.className = SETTINGS_MODAL_CLASS + "-title";
1277
+ title.textContent = t("settingsTitle");
1278
+ panel.appendChild(title);
1279
+ var themeSelect = document.createElement("select");
1280
+ for (const name of THEME_ORDER) {
1281
+ var option = document.createElement("option");
1282
+ option.value = name;
1283
+ option.textContent = THEME_LABELS[detectLang()][name] ?? name;
1284
+ themeSelect.appendChild(option);
1285
+ }
1286
+ themeSelect.value = getMermaidTheme();
1287
+ panel.appendChild(settingRow(t("settingsTheme"), themeSelect));
1288
+ var fontInput = numberInput(current.codeFontSize, 10, 20);
1289
+ panel.appendChild(settingRow(t("settingsFontSize"), fontInput));
1290
+ var fontHint = document.createElement("p");
1291
+ fontHint.className = SETTINGS_MODAL_CLASS + "-hint";
1292
+ fontHint.textContent = t("settingsFontSizeHint");
1293
+ panel.appendChild(fontHint);
1294
+ var viewSelect = document.createElement("select");
1295
+ for (const [value, label] of [
1296
+ ["diagram", t("settingsViewDiagram")],
1297
+ ["code", t("settingsViewCode")]
1298
+ ]) {
1299
+ var viewOption = document.createElement("option");
1300
+ viewOption.value = value;
1301
+ viewOption.textContent = label;
1302
+ viewSelect.appendChild(viewOption);
1303
+ }
1304
+ viewSelect.value = current.diagramView;
1305
+ panel.appendChild(settingRow(t("settingsView"), viewSelect));
1306
+ var foldHint = document.createElement("p");
1307
+ foldHint.className = SETTINGS_MODAL_CLASS + "-hint";
1308
+ foldHint.textContent = t("settingsFold") + " \xB7 " + t("settingsFoldHint");
1309
+ panel.appendChild(foldHint);
1310
+ var foldCode = numberInput(current.foldCode, 0, 4e3);
1311
+ var foldTable = numberInput(current.foldTable, 0, 4e3);
1312
+ var foldDiagram = numberInput(current.foldDiagram, 0, 4e3);
1313
+ panel.appendChild(settingRow(t("settingsFoldCode"), foldCode, t("settingsFoldCodeHint")));
1314
+ panel.appendChild(settingRow(t("settingsFoldTable"), foldTable, t("settingsFoldTableHint")));
1315
+ panel.appendChild(settingRow(t("settingsFoldDiagram"), foldDiagram, t("settingsFoldDiagramHint")));
1316
+ var actions = document.createElement("div");
1317
+ actions.className = SETTINGS_MODAL_CLASS + "-actions";
1318
+ var resetButton = document.createElement("button");
1319
+ resetButton.type = "button";
1320
+ resetButton.textContent = t("settingsReset");
1321
+ var closeButton = document.createElement("button");
1322
+ closeButton.type = "button";
1323
+ closeButton.textContent = t("settingsClose");
1324
+ var saveButton = document.createElement("button");
1325
+ saveButton.type = "button";
1326
+ saveButton.className = SETTINGS_MODAL_CLASS + "-primary";
1327
+ saveButton.textContent = t("settingsSave");
1328
+ actions.appendChild(resetButton);
1329
+ actions.appendChild(closeButton);
1330
+ actions.appendChild(saveButton);
1331
+ panel.appendChild(actions);
1332
+ overlay.appendChild(panel);
1333
+ var previousFocus = document.activeElement;
1334
+ var closed = false;
1335
+ function closeSettings() {
1336
+ if (closed) return;
1337
+ closed = true;
1338
+ document.removeEventListener("keydown", onKey, true);
1339
+ overlay.remove();
1340
+ if (previousFocus !== null && typeof previousFocus.focus === "function") previousFocus.focus();
1341
+ }
1342
+ function onKey(event) {
1343
+ if (event.key === "Escape") {
1344
+ event.preventDefault();
1345
+ closeSettings();
1346
+ }
1347
+ }
1348
+ function readNumber(input, fallback, max) {
1349
+ var value = Number(input.value);
1350
+ return Number.isFinite(value) && value >= 0 ? Math.min(max ?? 4e3, Math.round(value)) : fallback;
1351
+ }
1352
+ function readFontSize(input) {
1353
+ var value = Number(input.value);
1354
+ if (!Number.isFinite(value) || value <= 0) return 0;
1355
+ return Math.min(20, Math.max(10, Math.round(value)));
1356
+ }
1357
+ resetButton.addEventListener("click", () => {
1358
+ themeSelect.value = "blue";
1359
+ fontInput.value = "";
1360
+ viewSelect.value = DEFAULT_SETTINGS.diagramView;
1361
+ foldCode.value = String(DEFAULT_SETTINGS.foldCode);
1362
+ foldTable.value = String(DEFAULT_SETTINGS.foldTable);
1363
+ foldDiagram.value = String(DEFAULT_SETTINGS.foldDiagram);
1364
+ });
1365
+ saveButton.addEventListener("click", () => {
1366
+ saveSettings({
1367
+ codeFontSize: readFontSize(fontInput),
1368
+ diagramView: viewSelect.value === "code" ? "code" : "diagram",
1369
+ foldCode: readNumber(foldCode, DEFAULT_SETTINGS.foldCode),
1370
+ foldTable: readNumber(foldTable, DEFAULT_SETTINGS.foldTable),
1371
+ foldDiagram: readNumber(foldDiagram, DEFAULT_SETTINGS.foldDiagram)
1372
+ });
1373
+ try {
1374
+ window.localStorage.setItem(THEME_KEY, themeSelect.value);
1375
+ } catch {
1376
+ }
1377
+ applyCodeFontSize();
1378
+ refreshThemeButtons();
1379
+ redrawDiagrams();
1380
+ rescanFolds();
1381
+ closeSettings();
1382
+ });
1383
+ closeButton.addEventListener("click", closeSettings);
1384
+ overlay.addEventListener("pointerdown", (event) => {
1385
+ if (event.target === overlay) closeSettings();
1386
+ });
1387
+ document.addEventListener("keydown", onKey, true);
1388
+ document.body.appendChild(overlay);
1389
+ saveButton.focus();
1390
+ }
1391
+ function ensureSettingsButton() {
1392
+ if (document.querySelector("." + SETTINGS_BTN_CLASS) !== null) return;
1393
+ if (document.querySelector('[class*="_markdown_"], .md-code-block') === null) return;
1394
+ var button = document.createElement("button");
1395
+ button.type = "button";
1396
+ button.className = SETTINGS_BTN_CLASS;
1397
+ button.title = t("settingsOpen");
1398
+ button.setAttribute("aria-label", t("settingsOpen"));
1399
+ button.addEventListener("click", openSettings);
1400
+ document.body.appendChild(button);
1401
+ }
630
1402
  function fitToView(scope) {
631
1403
  var stage = stageOf(scope);
632
1404
  if (stage === null) return;
@@ -651,6 +1423,64 @@ window.__ModuleLoader__.load({
651
1423
  var rect = svg.getBoundingClientRect();
652
1424
  return { width: rect.width, height: rect.height };
653
1425
  }
1426
+ function serializeSvg(svg) {
1427
+ var clone = svg.cloneNode(true);
1428
+ var size = svgSize(svg);
1429
+ clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
1430
+ if (!clone.getAttribute("xmlns:xlink")) {
1431
+ clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
1432
+ }
1433
+ if (size.width > 0) clone.setAttribute("width", String(size.width));
1434
+ if (size.height > 0) clone.setAttribute("height", String(size.height));
1435
+ return '<?xml version="1.0" encoding="UTF-8"?>\n' + new XMLSerializer().serializeToString(clone);
1436
+ }
1437
+ function downloadBlob(blob, filename) {
1438
+ var url = URL.createObjectURL(blob);
1439
+ var link = document.createElement("a");
1440
+ link.href = url;
1441
+ link.download = filename;
1442
+ document.body.appendChild(link);
1443
+ link.click();
1444
+ link.remove();
1445
+ window.setTimeout(() => URL.revokeObjectURL(url), 1e3);
1446
+ }
1447
+ function downloadSvg(svg) {
1448
+ try {
1449
+ downloadBlob(new Blob([serializeSvg(svg)], { type: "image/svg+xml;charset=utf-8" }), "mermaid-diagram.svg");
1450
+ } catch (error) {
1451
+ console.warn("[dsh-md-plus] SVG \u5BFC\u51FA\u5931\u8D25", error);
1452
+ }
1453
+ }
1454
+ function downloadPng(svg) {
1455
+ var size = svgSize(svg);
1456
+ if (size.width <= 0 || size.height <= 0) return;
1457
+ var image = new window.Image();
1458
+ var objectUrl = "";
1459
+ image.onload = function() {
1460
+ try {
1461
+ var scale = Math.min(2, 4096 / Math.max(size.width, size.height));
1462
+ var canvas = document.createElement("canvas");
1463
+ canvas.width = Math.max(1, Math.round(size.width * scale));
1464
+ canvas.height = Math.max(1, Math.round(size.height * scale));
1465
+ var context = canvas.getContext("2d");
1466
+ if (context === null) throw new Error("canvas \u4E0D\u53EF\u7528");
1467
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
1468
+ canvas.toBlob((blob) => {
1469
+ if (blob !== null) downloadBlob(blob, "mermaid-diagram.png");
1470
+ }, "image/png");
1471
+ } catch (error) {
1472
+ console.warn("[dsh-md-plus] PNG \u5BFC\u51FA\u5931\u8D25", error);
1473
+ } finally {
1474
+ if (objectUrl !== "") URL.revokeObjectURL(objectUrl);
1475
+ }
1476
+ };
1477
+ image.onerror = function() {
1478
+ if (objectUrl !== "") URL.revokeObjectURL(objectUrl);
1479
+ console.warn("[dsh-md-plus] PNG \u5BFC\u51FA\u5931\u8D25\uFF1ASVG \u65E0\u6CD5\u8F7D\u5165");
1480
+ };
1481
+ objectUrl = URL.createObjectURL(new Blob([serializeSvg(svg)], { type: "image/svg+xml;charset=utf-8" }));
1482
+ image.src = objectUrl;
1483
+ }
654
1484
  function openFullscreen(block) {
655
1485
  if (document.querySelector("." + MODAL_CLASS) !== null) return;
656
1486
  var svg = block.querySelector("." + MMD_CLASS + " svg");
@@ -660,31 +1490,39 @@ window.__ModuleLoader__.load({
660
1490
  overlay.className = MODAL_CLASS;
661
1491
  overlay.setAttribute("role", "dialog");
662
1492
  overlay.setAttribute("aria-modal", "true");
663
- overlay.setAttribute("aria-label", "\u6D41\u7A0B\u56FE\u5168\u5C4F\u67E5\u770B");
1493
+ overlay.setAttribute("aria-label", t("diagramAria"));
664
1494
  var panel = document.createElement("div");
665
1495
  panel.className = MODAL_CLASS + "-panel";
666
1496
  var bar = document.createElement("div");
667
1497
  bar.className = MODAL_CLASS + "-bar";
668
1498
  var title = document.createElement("div");
669
1499
  title.className = MODAL_CLASS + "-title";
670
- title.textContent = "\u6D41\u7A0B\u56FE";
1500
+ title.textContent = t("diagram");
671
1501
  var actions = document.createElement("div");
672
1502
  actions.className = MODAL_CLASS + "-actions";
673
1503
  var fitButton = document.createElement("button");
674
1504
  fitButton.type = "button";
675
- fitButton.textContent = "\u9002\u5E94\u6574\u56FE";
1505
+ fitButton.textContent = t("fit");
1506
+ var svgButton = document.createElement("button");
1507
+ svgButton.type = "button";
1508
+ svgButton.textContent = t("downloadSvg");
1509
+ var pngButton = document.createElement("button");
1510
+ pngButton.type = "button";
1511
+ pngButton.textContent = t("downloadPng");
676
1512
  var zoomOut = document.createElement("button");
677
1513
  zoomOut.type = "button";
678
1514
  zoomOut.textContent = "\u2212";
679
- zoomOut.setAttribute("aria-label", "\u7F29\u5C0F");
1515
+ zoomOut.setAttribute("aria-label", t("zoomOutAria"));
680
1516
  var zoomIn = document.createElement("button");
681
1517
  zoomIn.type = "button";
682
1518
  zoomIn.textContent = "+";
683
- zoomIn.setAttribute("aria-label", "\u653E\u5927");
1519
+ zoomIn.setAttribute("aria-label", t("zoomInAria"));
684
1520
  var closeButton = document.createElement("button");
685
1521
  closeButton.type = "button";
686
- closeButton.textContent = "\u5173\u95ED";
1522
+ closeButton.textContent = t("close");
687
1523
  actions.appendChild(fitButton);
1524
+ actions.appendChild(svgButton);
1525
+ actions.appendChild(pngButton);
688
1526
  actions.appendChild(zoomOut);
689
1527
  actions.appendChild(zoomIn);
690
1528
  actions.appendChild(closeButton);
@@ -735,12 +1573,14 @@ window.__ModuleLoader__.load({
735
1573
  if (event.target === overlay) closeViewer();
736
1574
  });
737
1575
  fitButton.addEventListener("click", reset);
1576
+ svgButton.addEventListener("click", () => downloadSvg(svg));
1577
+ pngButton.addEventListener("click", () => downloadPng(svg));
738
1578
  zoomOut.addEventListener("click", () => zoomBy(overlay, 1 / ZOOM_STEP));
739
1579
  zoomIn.addEventListener("click", () => zoomBy(overlay, ZOOM_STEP));
740
1580
  closeButton.addEventListener("click", closeViewer);
741
1581
  document.addEventListener("keydown", onKey, true);
742
1582
  zooms.set(overlay, { scale: 1, x: 0, y: 0, min: ZOOM_MIN_MODAL });
743
- bindViewport(overlay, viewport, reset);
1583
+ bindViewport(overlay, viewport, reset, true);
744
1584
  document.documentElement.style.overflow = "hidden";
745
1585
  document.body.appendChild(overlay);
746
1586
  closeButton.focus();
@@ -752,17 +1592,21 @@ window.__ModuleLoader__.load({
752
1592
  var note = content.querySelector("." + MMD_CLASS + "-error") ?? document.createElement("p");
753
1593
  note.className = MMD_CLASS + "-error";
754
1594
  var reason = (error?.message ?? String(error)).split("\n")[0] ?? "";
755
- note.textContent = "mermaid \u6E32\u67D3\u5931\u8D25\uFF1A" + reason;
1595
+ note.textContent = t("renderFailed") + reason;
756
1596
  content.appendChild(note);
757
1597
  block.setAttribute(KEY_ATTR, key);
758
1598
  block.setAttribute(MMD_ATTR, "failed");
1599
+ decorate(block);
759
1600
  console.warn("[dsh-md-plus] mermaid \u6E32\u67D3\u5931\u8D25", error);
760
1601
  }
761
1602
  function apply0(ctx) {
762
1603
  if (typeof document === "undefined") return;
763
1604
  ctx.effect(() => {
764
1605
  injectStyle();
1606
+ applyCodeFontSize();
1607
+ ensureSettingsButton();
765
1608
  var root = document.body ?? document.documentElement;
1609
+ document.addEventListener("click", onDocumentClick, true);
766
1610
  var observer = new MutationObserver(onMutations);
767
1611
  observer.observe(root, {
768
1612
  childList: true,
@@ -774,14 +1618,29 @@ window.__ModuleLoader__.load({
774
1618
  schedule();
775
1619
  return () => {
776
1620
  observer.disconnect();
1621
+ document.removeEventListener("click", onDocumentClick, true);
777
1622
  for (const node of injected) node.remove();
778
1623
  injected.clear();
1624
+ for (const node of document.querySelectorAll("." + BTN_CLASS)) node.remove();
1625
+ for (const node of document.querySelectorAll("." + HOST_CLASS)) node.remove();
1626
+ for (const node of document.querySelectorAll("." + FOLD_BTN_CLASS)) node.remove();
1627
+ for (const node of document.querySelectorAll("[" + FOLD_ATTR + "]")) resetFold(node);
1628
+ for (const node of document.querySelectorAll("." + TABLE_TOOLS_CLASS)) node.remove();
1629
+ for (const attr of [DONE_ATTR, KEY_ATTR, WRAP_ATTR, MMD_ATTR, TABLE_DONE_ATTR, "data-md-plus-lang"]) {
1630
+ for (const node of document.querySelectorAll("[" + attr + "]")) node.removeAttribute(attr);
1631
+ }
1632
+ for (const node of document.querySelectorAll("[data-md-plus-original-label]")) {
1633
+ node.textContent = node.getAttribute("data-md-plus-original-label") ?? node.textContent;
1634
+ node.removeAttribute("data-md-plus-original-label");
1635
+ }
779
1636
  for (const node of document.querySelectorAll(
780
- "." + MMD_CLASS + ", ." + MMD_CLASS + "-error, ." + MODAL_CLASS
1637
+ "." + MMD_CLASS + ", ." + MMD_CLASS + "-error, ." + MODAL_CLASS + ", ." + IMG_MODAL_CLASS + ", ." + SETTINGS_BTN_CLASS + ", ." + SETTINGS_MODAL_CLASS
781
1638
  )) {
782
1639
  node.remove();
783
1640
  }
784
1641
  document.documentElement.style.overflow = "";
1642
+ document.documentElement.style.removeProperty("--md-plus-code-font-size");
1643
+ document.documentElement.style.removeProperty("--md-plus-code-line-height");
785
1644
  document.querySelector("style[" + STYLE_ATTR + "]")?.remove();
786
1645
  for (const script of document.querySelectorAll("script[" + RUNTIME_ATTR + "]")) {
787
1646
  script.remove();