@helping-ai-workflow/md2doc 3.2.0 → 3.2.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.
@@ -11,7 +11,8 @@
11
11
  })(typeof self !== 'undefined' ? self : this, function () {
12
12
 
13
13
  // Pure state machine + lines[]<->string conversion for the "追加 4 全文原始碼
14
- // 逃生口" (whole-document source escape hatch): 編輯 ⇄ 原始碼 ⇄ 預覽.
14
+ // 逃生口" (whole-document source escape hatch): 編輯 ⇄ 原始碼.
15
+ // v3.2.1: 預覽(preview)已移除,見下方 MODES 上的說明。
15
16
  //
16
17
  // EOL representation: `eol` is always a single literal separator string --
17
18
  // '\n', '\r\n' or '\r' -- the same convention server.js already establishes
@@ -31,12 +32,21 @@
31
32
  // 2. `\r\n` is listed before the two single-character alternatives, so a
32
33
  // CRLF pair is always consumed as ONE separator, never split into a
33
34
  // spurious blank line between a lone \r and a lone \n.
34
- const MODES = ['edit', 'source', 'preview'];
35
+ const MODES = ['edit', 'source'];
35
36
 
36
- // Three-state cycle: edit -> source -> preview -> edit. An unrecognized
37
- // mode (defensive: a caller passing something stale) falls back to the
38
- // first state rather than throwing, matching how the rest of this module
39
- // prefers a safe default over a thrown exception for malformed input.
37
+ // v3.2.1: 兩態循環 edit <-> source。preview 移除 —— 實測它相對 edit 只做
38
+ // 兩件事(applyPreviewEditability(false) 與兩行隱藏 ⠿/+ 的 CSS),卻擋在
39
+ // source 回 edit 的路上,而且 22 顆工具列按鈕中 13 顆在它底下仍然寫得進磁碟。
40
+ //
41
+ // 為什麼是移除而不是加閘門:一個「列舉可及面」推導出來的唯讀閘門必然漏掉
42
+ // 一整族 —— 切換前就已經開著的 ⠿ 選單在切換後仍是 140x118、display:flex,
43
+ // 其 16 個項目每一個都寫得進磁碟。閘門要約 8 個守衛點跨 2 個檔案才擋得住,
44
+ // 而 preview 本身沒有任何 edit 給不了的能力。
45
+ //
46
+ // An unrecognized mode (defensive: a caller passing something stale) falls
47
+ // back to the first state rather than throwing, matching how the rest of
48
+ // this module prefers a safe default over a thrown exception for malformed
49
+ // input.
40
50
  function next(mode) {
41
51
  const idx = MODES.indexOf(mode);
42
52
  return MODES[(idx + 1) % MODES.length];
@@ -48,7 +48,13 @@ const BUTTON_DEFS = [
48
48
 
49
49
  // view
50
50
  { id: 'outline', group: 'view', label: '☰', icon: '☰', title: '大綱', toggle: false },
51
- { id: 'preview', group: 'view', label: '👁', icon: '👁', title: '預覽', toggle: true },
51
+ // v3.2.1: 這顆是 edit <-> source 的兩態切換(preview 模式已移除)。id 保留
52
+ // 為 'preview' 是刻意的 —— 每一支選擇器、journey 案例與 runtime 測試都指名
53
+ // 它,改名只換得一個更好聽的字串。label / title 不是烘死值:deriveState()
54
+ // 每次都依當前 mode 覆寫成【下一個】狀態的名字,這裡的值只是 edit 模式的
55
+ // 起始值。toggle: false —— 兩態下 aria-pressed 講不出真話(按鈕講的是去處,
56
+ // 不是目前所在),真話由會變的 label / title 承擔。
57
+ { id: 'preview', group: 'view', label: 'M↓', icon: 'M↓', title: '切換到原始碼模式', toggle: false },
52
58
  ];
53
59
 
54
60
  const BUTTONS = Object.freeze(BUTTON_DEFS.map((b) => Object.freeze(Object.assign({}, b))));
@@ -76,11 +82,18 @@ function deriveState(ctx) {
76
82
  state[b.id] = { active: false, disabled: false };
77
83
  }
78
84
 
79
- // preview is the tri-state edit/source/preview toggle: "pressed" whenever
80
- // the current mode is not plain editing. It stays enabled in source mode
81
- // via the mode==='source' override below (do not gate this on inList/
82
- // hasBlock — it is the escape hatch OUT of those states).
83
- state.preview.active = mode !== 'edit';
85
+ // v3.2.1: the mode button names the NEXT state, never the current one.
86
+ // Under the old tri-state cycle it was permanently labelled 「預覽」 and
87
+ // reported aria-pressed=true in BOTH non-edit modes, so pressing it twice
88
+ // landed in source with no grips and an interface that said nothing about
89
+ // where you were — the reported "stuck" defect. It carries no `active`
90
+ // now: with two states the honest signal is the label, and the current
91
+ // mode is spelled out in .ed-toolbar-status by the client.
92
+ // It stays enabled in source mode via the mode==='source' override below
93
+ // (do not gate this on inList/hasBlock — it is the escape hatch OUT of
94
+ // those states).
95
+ state.preview.label = mode === 'source' ? '✎' : 'M↓';
96
+ state.preview.title = mode === 'source' ? '切換到編輯模式' : '切換到原始碼模式';
84
97
 
85
98
  // headings: dropdown whose label tracks the current heading depth.
86
99
  state.headings.label = blockType === 'heading' ? 'H' + headingDepth : 'H';
@@ -145,8 +158,10 @@ function deriveState(ctx) {
145
158
  }
146
159
 
147
160
  // mode 'source' is the full-document raw-source escape hatch: everything
148
- // is disabled except the toggle that got you there (preview). This
149
- // overrides every disabled flag computed above.
161
+ // is disabled except the mode button that got you there (id 'preview',
162
+ // which in v3.2.1 is the edit <-> source toggle). This overrides every
163
+ // disabled flag computed above, and is the ONLY way back out — do not
164
+ // narrow it.
150
165
  if (mode === 'source') {
151
166
  for (const b of BUTTONS) {
152
167
  state[b.id].disabled = b.id !== 'preview';
package/lib/md2doc.js CHANGED
@@ -2590,16 +2590,18 @@ ${itemsHtml}
2590
2590
  row's overflow-x: auto scrolls (measured identical rect at scrollLeft
2591
2591
  0 / mid / max, where the absolute alternative moved by the exact scroll
2592
2592
  delta). References no gutter-alignment custom property.
2593
- H2 fix: pointer-events: none below is LOAD-BEARING while this slot
2594
- ships empty, not decorative. At narrow viewports .ed-toolbar's own
2593
+ H2 fix: pointer-events: none below is LOAD-BEARING, not decorative.
2594
+ v3.2.1 filled this slot with the current mode's name ('編輯' / '原始碼'),
2595
+ so it no longer ships empty — but that content is inert TEXT, never a
2596
+ control, which is exactly what keeps the rule below sufficient. At narrow viewports .ed-toolbar's own
2595
2597
  overflow-x: auto lets the button row scroll to a rest position where
2596
2598
  this fixed 7rem box's footprint physically covers three real buttons
2597
2599
  (image / outline / preview, measured at 420px width, scrollLeft at its
2598
2600
  max 221) — document.elementFromPoint() over any of them resolves to
2599
2601
  this span, not the button underneath, and a real click on it never
2600
2602
  reaches the button's listener. Harmless today only because the span
2601
- carries no content to interact with. WHOEVER WIRES UP TWO-WAY SYNC
2602
- (§3) AND PUTS REAL CONTENT OR CONTROLS HERE MUST remove or scope this
2603
+ carries nothing to interact with. WHOEVER PUTS A CONTROL HERE (or
2604
+ wires up two-way sync, §3) MUST remove or scope this
2603
2605
  rule AND independently solve the overlap (e.g. reflow the slot in-flow
2604
2606
  at narrow widths, or shrink/hide it below some width) — simply
2605
2607
  deleting this line without doing that reopens the swallowed-click
@@ -2636,8 +2638,6 @@ ${itemsHtml}
2636
2638
  border: 1px solid #d0d7de; border-radius: 8px; resize: vertical;
2637
2639
  white-space: pre; overflow-wrap: normal;
2638
2640
  }
2639
- body[data-ed-mode="preview"] .ed-handle,
2640
- body[data-ed-mode="preview"] .ed-insert { display: none !important; }
2641
2641
  .ed-editing { position: relative; }
2642
2642
  .ed-raw {
2643
2643
  display: block; width: 100%; font-family: monospace; font-size: 13px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helping-ai-workflow/md2doc",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, and Graphviz support",
5
5
  "keywords": [
6
6
  "markdown",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "scripts": {
39
39
  "preinstall": "node scripts/preinstall.js",
40
- "test": "node test/md2doc.test.js && node test/blockmap.test.js && node test/images.test.js && node test/scroll-anchor.test.js && node test/lightbox.test.js && node test/lightbox-anno.test.js && node test/lightbox-anno-style.test.js && node test/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js && node test/convert-md.test.js && node test/render-api.test.js && node test/lineops.test.js && node test/indent-clamp.test.js && node test/selection.test.js && node test/editmode-render.test.js && node test/editmode-wavedrom-reinit.test.js && node test/editor-server.test.js && node test/cli-edit.test.js && node test/open-viewer.test.js && node test/editor-client.test.js && node test/editor-client-runtime.test.js && node test/roundtrip.test.js && node test/byte-stability.test.js && node test/editor-server-throw.test.js && node test/editor-reader-rebind.test.js && node test/inline-md.test.js && node test/table-md.test.js && node test/gate-compat.test.js && node test/history.test.js && node test/list-md.test.js && node test/table-width.test.js && node test/toolbar-model.test.js && node test/docsource.test.js && node test/asset.test.js && node test/paste-md.test.js && node test/patchmap.test.js"
40
+ "test": "node test/md2doc.test.js && node test/blockmap.test.js && node test/images.test.js && node test/scroll-anchor.test.js && node test/lightbox.test.js && node test/lightbox-anno.test.js && node test/lightbox-anno-style.test.js && node test/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js && node test/convert-md.test.js && node test/render-api.test.js && node test/lineops.test.js && node test/indent-clamp.test.js && node test/selection.test.js && node test/editmode-render.test.js && node test/editmode-wavedrom-reinit.test.js && node test/editor-server.test.js && node test/cli-edit.test.js && node test/open-viewer.test.js && node test/editor-client.test.js && node test/editor-client-runtime.test.js && node test/roundtrip.test.js && node test/byte-stability.test.js && node test/editor-server-throw.test.js && node test/editor-reader-rebind.test.js && node test/inline-md.test.js && node test/table-md.test.js && node test/gate-compat.test.js && node test/history.test.js && node test/list-md.test.js && node test/table-width.test.js && node test/toolbar-model.test.js && node test/docsource.test.js && node test/asset.test.js && node test/paste-md.test.js && node test/patchmap.test.js && node test/editor-journey.test.js && node test/detach-census.test.js"
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",