@helping-ai-workflow/md2doc 2.12.0 → 3.0.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/md2doc.js CHANGED
@@ -1080,18 +1080,35 @@ ${itemsHtml}
1080
1080
  // `.content { min-width: 0; flex: 1 1 auto; }` block, which
1081
1081
  // test/md2doc.test.js matches verbatim).
1082
1082
  //
1083
- // Why it exists: the gutter buttons hang OUTSIDE the content box, and the
1084
- // table row grip straddles the table's left border (half of it outside
1085
- // too — see the .ed-te-grip-row comment above). Without real padding the
1086
- // two fight over the same few pixels, which is what forced the earlier
1087
- // (now reverted) "inset the row grip into the table" hack that ended up
1088
- // covering the first cell's text. 56px of content padding plus moving the
1089
- // gutter buttons out (⠿ to left:-36px, + to left:-54px) separates them
1090
- // properly: the gutter pair occupies [contentLeft-40, contentLeft-4]
1091
- // (v2.11.1; it was [contentLeft-54, contentLeft-18] until spec §4.2's own
1092
- // numbers were restored) and the 20px-wide row grip straddles contentLeft at
1093
- // [contentLeft-10, contentLeft+10] — an 8px gap, and the grip's inner half
1094
- // stays within the cell's own 14px padding, so it never touches cell text.
1083
+ // Why it exists: the gutter buttons hang OUTSIDE the content box. 56px of
1084
+ // content padding plus moving the gutter buttons out puts the pair at
1085
+ // [contentLeft-40, contentLeft-4] (v2.11.1; it was
1086
+ // [contentLeft-54, contentLeft-18] until spec §4.2's own numbers were
1087
+ // restored), clear of .sidebar-splitter — see the .content rule below.
1088
+ //
1089
+ // GEOMETRY, AS MEASURED (v2.12.0/S4 T1; the previous version of this
1090
+ // comment claimed "an 8px gap" between the gutter and the table row grip, a
1091
+ // number left over from the v2.11.0 pair position that v2.11.1
1092
+ // invalidated — the real gap was MINUS 6px, i.e. an overlap):
1093
+ // ⠿ [contentLeft-22, contentLeft-4] (--ed-gutter-btn wide)
1094
+ // row grip [contentLeft-10, contentLeft+10] (--ed-te-grip-row-w wide,
1095
+ // centred on the table's left border)
1096
+ // The grip straddling the table's left border overlapped the ⠿'s right 6px
1097
+ // and — being position: fixed, z-index 7, on document.body — won the hit
1098
+ // test there. v2.12.0/S4 T1's fix moved the grip entirely inside the table
1099
+ // instead (its left edge on the border), which fixed the overlap but
1100
+ // broke the row/column symmetry the user expected (the column grip still
1101
+ // straddles the table's TOP border) — reported as a v3.0.1 regression.
1102
+ // v3.0.1 fixes it at the source instead: the ⠿/+ pair itself moves
1103
+ // further LEFT by --ed-gutter-shift below, which removes the overlap
1104
+ // without moving the grip at all, so the grip can straddle the border
1105
+ // again (see updateTableEdgeGrips() in lib/editor/client.js). The
1106
+ // clearance that remains is exactly --ed-gutter-gap, because the ⠿'s right
1107
+ // edge and the grip's left edge are both derived from it. The grip's
1108
+ // straddling half (10px) sits well inside the first cell's default 14px
1109
+ // left padding, so no extra edit-mode padding is needed any more — the
1110
+ // v2.10.1-era --ed-te-cell-pad-left token this comment used to describe is
1111
+ // gone.
1095
1112
  // (The padding was 48px until §4.2's hit-test conflict 1 was fixed; see the
1096
1113
  // .content rule in editModeLayoutCss below for why it is 56px now.)
1097
1114
  // S1 Task 5: the ordered-list ordinal is drawn by a CSS counter, and the
@@ -1147,11 +1164,14 @@ ${itemsHtml}
1147
1164
  further in, to spec §4.2's own [contentLeft-40, contentLeft-4] — the
1148
1165
  clearance is 16px at this padding rather than 2px. The padding stays at
1149
1166
  56px: nothing asks for the text column to move, and this measurement is
1150
- the one the §4.2 conflict-1 guard is written against.) The 20px table row grip
1151
- still straddles contentLeft at [contentLeft-10, contentLeft+10], so its
1152
- 8px gap to ⠿ is unchanged. Guarded by the elementFromPoint(splitter.right
1153
- - 2, y) assertion §4.2 asks for, in
1154
- test/editor-client-runtime.test.js. */
1167
+ the one the §4.2 conflict-1 guard is written against.) v3.0.1 shifts the
1168
+ pair a further --ed-gutter-shift (10px) left, to [contentLeft-50,
1169
+ contentLeft-14], so the table row grip can straddle the table's own left
1170
+ border again instead of sitting inside it — see the --ed-gutter-shift
1171
+ comment above .ed-handle below. That still leaves 6px of clearance from
1172
+ splitterRight (contentLeft-56), so the conflict-1 guard still holds.
1173
+ Guarded by the elementFromPoint(splitter.right - 2, y) assertion §4.2
1174
+ asks for, in test/editor-client-runtime.test.js. */
1155
1175
  .content { padding-left: 56px; }
1156
1176
 
1157
1177
  /* S1: every block is one full-width row, whatever its type or depth. The
@@ -1221,24 +1241,41 @@ ${itemsHtml}
1221
1241
  [contentLeft-54, contentLeft-18], which with 56px of padding sits wholly
1222
1242
  inside the content column (2px clear of .sidebar-splitter's right edge —
1223
1243
  see the padding rule's own comment for the measurement), so + never
1224
- reaches a negative viewport x and never covers the splitter. The 20px
1225
- table row grip still straddles contentLeft at
1226
- [contentLeft-10, contentLeft+10] - an 8px gap, unchanged. */
1244
+ reaches a negative viewport x and never covers the splitter. As of
1245
+ v3.0.1 the table row grip straddles the table's left border again
1246
+ (centred on it, [tableLeft-10, tableLeft+10] — see updateTableEdgeGrips()
1247
+ in lib/editor/client.js), and the pair itself has moved left by
1248
+ --ed-gutter-shift below so the clearance from the ⠿'s right edge is
1249
+ still exactly --ed-gutter-gap and cannot drift: both edges are derived
1250
+ from it. */
1227
1251
  /* Stated ONCE, as tokens, because §4.2's three numbers are not independent:
1228
1252
  the gutter is exactly two buttons wide plus the deliberate right-hand
1229
- breathing gap, and the hover zone below is exactly the gutter. Writing any
1230
- of them as a second literal is how the corridor comes back. */
1253
+ breathing gap, and the hover zone below is exactly the gutter plus
1254
+ whatever --ed-gutter-shift moved the pair by. Writing any of them as a
1255
+ second literal is how the corridor comes back. */
1231
1256
  :root {
1232
1257
  --ed-gutter-btn: 18px;
1233
1258
  --ed-gutter-gap: 4px;
1234
1259
  --ed-gutter-w: calc(var(--ed-gutter-btn) * 2 + var(--ed-gutter-gap));
1235
1260
  }
1261
+ /* v3.0.1: the pair moves LEFT by --ed-gutter-shift so the table row grip
1262
+ can straddle the table's left border again (see updateTableEdgeGrips()
1263
+ in lib/editor/client.js) while still keeping --ed-gutter-gap between the
1264
+ ⠿'s right edge and the grip's left edge. Written as a token because the
1265
+ two rules below and the acceptance test all derive from it. Measured at
1266
+ 1400x900 (README.md, first table): pre-shift ⠿=[390,408] +=[372,390]
1267
+ tableLeft=412; a 10px shift lands ⠿=[380,398] +=[362,380], grip
1268
+ (straddling, 20px wide, centred on tableLeft) = [402,422], leaving
1269
+ exactly --ed-gutter-gap (4px) between the ⠿'s right edge (398) and the
1270
+ grip's left edge (402), and +'s left edge (362) stays well clear of 0. */
1271
+ :root { --ed-gutter-shift: 10px; }
1236
1272
  .ed-handle {
1237
- left: calc(-1 * (var(--ed-gutter-btn) + var(--ed-gutter-gap))); top: 0;
1273
+ left: calc(-1 * (var(--ed-gutter-btn) + var(--ed-gutter-gap) + var(--ed-gutter-shift)));
1274
+ top: 0;
1238
1275
  width: var(--ed-gutter-btn);
1239
1276
  }
1240
1277
  .ed-insert {
1241
- left: calc(-1 * var(--ed-gutter-w)); top: 0;
1278
+ left: calc(-1 * (var(--ed-gutter-w) + var(--ed-gutter-shift))); top: 0;
1242
1279
  width: var(--ed-gutter-btn);
1243
1280
  }
1244
1281
  /* v2.11.1: the gutter's HOVER ZONE, and it is the reason the pair could move
@@ -1267,9 +1304,17 @@ ${itemsHtml}
1267
1304
 
1268
1305
  One absolutely-positioned pseudo-element spanning the whole gutter for the
1269
1306
  block's whole height makes :hover continuously true from the text out
1270
- past the +, at every Y. Its width is the --ed-gutter-w token the pair's
1271
- own offsets are built from, so the two cannot drift apart — if they do,
1272
- the corridor comes straight back.
1307
+ past the +, at every Y. Its span is derived from the SAME two tokens the
1308
+ pair's own offsets are: --ed-gutter-w (the pair's own width, the gutter
1309
+ proper) plus --ed-gutter-shift (how far v3.0.1 moved the pair left to
1310
+ clear the straddling table row grip). Both edges therefore track the
1311
+ pair automatically — the zone's left edge is exactly the +'s left edge
1312
+ and its right edge is the block's, with no literal repeated anywhere —
1313
+ so the two cannot drift apart. If they ever do, the corridor comes
1314
+ straight back: v3.0.1 shifted the pair to [blockLeft-50, blockLeft-14]
1315
+ while this rule still read the un-shifted --ed-gutter-w alone, leaving
1316
+ it spanning [blockLeft-40, blockLeft] — 10px short on the left of the
1317
+ very buttons it exists to keep reachable.
1273
1318
  .ed-block is already position:relative, and position: absolute keeps
1274
1319
  this out of the li row's flex flow. It is deliberately NOT
1275
1320
  pointer-events: none: that would stop it being hit-tested, which is the
@@ -1281,8 +1326,8 @@ ${itemsHtml}
1281
1326
  literal.) */
1282
1327
  .ed-block::before {
1283
1328
  content: ""; position: absolute;
1284
- left: calc(-1 * var(--ed-gutter-w)); top: 0;
1285
- width: var(--ed-gutter-w); height: 100%;
1329
+ left: calc(-1 * (var(--ed-gutter-w) + var(--ed-gutter-shift))); top: 0;
1330
+ width: calc(var(--ed-gutter-w) + var(--ed-gutter-shift)); height: 100%;
1286
1331
  }`;
1287
1332
 
1288
1333
  const html = `<!DOCTYPE html>
@@ -2162,7 +2207,11 @@ ${itemsHtml}
2162
2207
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
2163
2208
  }
2164
2209
  .ed-handle-menu-btn {
2165
- min-width: 132px; height: 26px; padding: 0 10px;
2210
+ /* v3.0.1: the icon sits FLUSH LEFT and the label sits one space-width
2211
+ after it (user request: 「符號靠左對齊,文字跟符號間距一個空格」).
2212
+ The right padding is untouched — it is what keeps the 轉換成 row's
2213
+ chevron off the panel edge. */
2214
+ min-width: 132px; height: 26px; padding: 0 10px 0 6px;
2166
2215
  border: 1px solid rgba(255, 255, 255, 0.25); border-radius: 6px;
2167
2216
  background: rgba(255, 255, 255, 0.08); color: inherit;
2168
2217
  font: inherit; font-size: 12px; line-height: 1;
@@ -2173,8 +2222,10 @@ ${itemsHtml}
2173
2222
  width, so S2's one-left-edge / monotonic-top invariants are untouched.
2174
2223
  The submenu's own buttons carry this class and no icon; a lone text node
2175
2224
  is an anonymous flex item at flex-start, i.e. exactly where text-align
2176
- put it. */
2177
- display: flex; align-items: center; gap: 8px;
2225
+ put it.
2226
+ v3.0.1: gap dropped from 8px to 4px — one space-width at this 12px
2227
+ font size — now that every row (both menu levels) carries an icon. */
2228
+ display: flex; align-items: center; gap: 4px;
2178
2229
  }
2179
2230
  /* Notion's own sizing. The stroke colour is NOT set here on purpose: the
2180
2231
  markup carries stroke="currentColor" (lib/editor/client.js), so the icon
@@ -2182,8 +2233,20 @@ ${itemsHtml}
2182
2233
  retheme for free. A hex here would break that silently. (No backticks in
2183
2234
  this comment: it lives inside a JS template literal.)
2184
2235
  flex: 0 0 auto because a 16px-wide flex item next to a nowrap label
2185
- would otherwise be the thing that shrinks. */
2186
- .ed-menu-icon { flex: 0 0 auto; width: 16px; height: 16px; }
2236
+ would otherwise be the thing that shrinks.
2237
+ v3.0.1: the selector is ".ed-handle-menu-btn .ed-menu-icon", not the
2238
+ bare class — the gutter menu is appended INTO a ".ed-block"
2239
+ (lib/editor/client.js's blockEl.appendChild(gutterMenu)), which is a
2240
+ descendant of <main class="content">, and ".content svg" above
2241
+ carries "margin: 0 auto" at specificity (0,1,1) (one class, one
2242
+ type). The bare ".ed-menu-icon" is only (0,1,0) and LOSES that fight
2243
+ regardless of source order, so every icon picked up centering
2244
+ auto-margins instead of sitting flush left — measured on this branch
2245
+ as an icon offset that swung with label length (11px-40px) instead
2246
+ of a constant one. Scoping under ".ed-handle-menu-btn" raises this
2247
+ to (0,2,0), which wins on class count alone. (No backticks in this
2248
+ comment: it lives inside a JS template literal.) */
2249
+ .ed-handle-menu-btn .ed-menu-icon { flex: 0 0 auto; width: 16px; height: 16px; margin: 0; }
2187
2250
  /* The 轉換成 submenu carries BOTH classes, so it inherits every rule above
2188
2251
  and only overrides the horizontal offset. It is a CHILD of the menu, and
2189
2252
  the menu is itself position:absolute, so the menu's own padding box is
@@ -2296,17 +2359,54 @@ ${itemsHtml}
2296
2359
  }
2297
2360
  .ed-te-drop-indicator[hidden] { display: none; }
2298
2361
  .ed-te-row-dragging { opacity: 0.4; }
2362
+ /* S4 Task 2 (spec §4.5): the block drag's own singleton drop indicator,
2363
+ shown while a block is being dragged by its ⠿. Same 3px blue rule as the
2364
+ table's, on a SEPARATE node rather than sharing '.ed-te-drop-indicator':
2365
+ that one is shared by the row and column axes and each of them has to
2366
+ rewrite the OTHER axis's dimension on every move or a stale value leaks
2367
+ in — a third writer multiplies that coupling. It is still ONE node for the
2368
+ whole document (the Global Constraint is "never one per boundary"), and
2369
+ the separate class is what lets a test assert that a block drag raised
2370
+ the BLOCK line rather than table chrome that happened to be on screen. */
2371
+ .ed-block-drop-indicator {
2372
+ position: fixed; z-index: 8; height: 3px; border-radius: 2px;
2373
+ background: #3b82f6; pointer-events: none;
2374
+ }
2375
+ .ed-block-drop-indicator[hidden] { display: none; }
2376
+ /* S4 Task 2b (user request, 2026-09-01): the drag's feedback on the SOURCE
2377
+ side. lib/editor/client.js puts '.ed-block-dragging' on <html> the moment
2378
+ a ⠿ drag engages and takes it off in teardownBlockDrag(), which every one
2379
+ of the five abort paths funnels through. On <html> rather than on the
2380
+ dragged block because the plan forbids mutating anything inside
2381
+ '.content' during a drag — that invariant is the only reason Escape /
2382
+ pointercancel / window blur can all clean up with nothing to revert.
2383
+ The universal selector plus !important is what the rule needs to be worth
2384
+ having: '.ed-handle' (the button the gesture starts on), '.ed-handle-menu-btn'
2385
+ and every table grip carry their own 'cursor: pointer', and a plain
2386
+ inherited cursor loses to all of them. */
2387
+ html.ed-block-dragging, html.ed-block-dragging * { cursor: grabbing !important; }
2299
2388
  /* Notion-style grip handles: replace the original invisible TE_EDGE_PX=8
2300
2389
  proximity zone (user-acceptance feedback: unusably small, no visible
2301
2390
  affordance) with two real, adequately-sized (>=18x24px) click/drag
2302
2391
  targets. '.ed-te-grip-row' is a vertical 6-dot handle shown at the LEFT
2303
2392
  EDGE of the hovered row -- every row, the HEADER included (spec 3.10:
2304
- the header is draggable too), STRADDLING the table's left border (its
2305
- centreline ON that border), which is the same rule '.ed-te-grip-col'
2306
- uses on the table's TOP border -- one geometry for both axes, no
2307
- per-row-type special case. The block's own gutter is kept clear of it by
2308
- the edit-mode-only layout chunk emitted near the end of this stylesheet,
2309
- not by insetting the grip; '.ed-te-grip-col' is a horizontal 6-dot
2393
+ the header is draggable too), STRADDLING the table's left border,
2394
+ centred on it (spec 4.2 hit-test conflict 2). v2.12.0/S4 T1 tried moving
2395
+ it entirely inside the table instead (left edge on the border) to stop it
2396
+ winning the hit test against the block's own ⠿ — MEASURED at 1400x900,
2397
+ the straddling grip occupied [tableLeft-10, tableLeft+10] while
2398
+ '.ed-handle' occupied [blockLeft-22, blockLeft-4] with tableLeft ==
2399
+ blockLeft, so the grip (position: fixed, z-index 7, on document.body)
2400
+ won the hit test for the RIGHT 6px of every ⠿ whose Y band it reached —
2401
+ but that broke the row/column symmetry the user expected ('.ed-te-grip-col'
2402
+ still straddles the table's TOP border, because nothing of the page's
2403
+ own chrome lives up there), and was reported as a regression. v3.0.1
2404
+ restores the straddle and instead moves the ⠿/+ pair itself further left
2405
+ by --ed-gutter-shift, which removes the overlap at its source (see the
2406
+ edit-mode-only layout chunk near the end of this stylesheet); the row
2407
+ grip's straddling half sits well inside the first cell's default 14px
2408
+ padding, so no extra padding token is needed either.
2409
+ '.ed-te-grip-col' is a horizontal 6-dot
2310
2410
  handle shown just ABOVE the hovered column (every column). Dots are plain <span>s laid out via CSS grid with
2311
2411
  place-content: center, so the dot cluster stays compact/centered
2312
2412
  regardless of the button's own (larger, hit-target-sized) box — no
@@ -2325,8 +2425,15 @@ ${itemsHtml}
2325
2425
  .ed-te-grip:hover { background: rgba(0, 0, 0, 0.06); }
2326
2426
  .ed-te-grip-dot { width: 3px; height: 3px; border-radius: 50%; background: #9ca3af; }
2327
2427
  .ed-te-grip:hover .ed-te-grip-dot { background: #6b7280; }
2428
+ /* spec §4.2 hit-test conflict 2: the row grip's width is a TOKEN because
2429
+ lib/editor/client.js's updateTableEdgeGrips() reads it (via
2430
+ gripRowWidthPx()) to CENTRE the grip on the table's left border —
2431
+ tableRect.left - gw / 2. Written once here, read once there, so the CSS
2432
+ width and the JS position can never drift apart the way they did when
2433
+ the position was a hard-coded literal 20. */
2434
+ :root { --ed-te-grip-row-w: 20px; }
2328
2435
  .ed-te-grip-row {
2329
- width: 20px; height: 28px; cursor: grab; z-index: 7;
2436
+ width: var(--ed-te-grip-row-w); height: 28px; cursor: grab; z-index: 7;
2330
2437
  grid-template-columns: repeat(2, 3px); grid-template-rows: repeat(3, 3px);
2331
2438
  gap: 3px 4px;
2332
2439
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helping-ai-workflow/md2doc",
3
- "version": "2.12.0",
3
+ "version": "3.0.1",
4
4
  "description": "Markdown → HTML / PDF renderer with WaveDrom, Mermaid, and Graphviz support",
5
5
  "keywords": [
6
6
  "markdown",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "scripts": {
38
38
  "preinstall": "node scripts/preinstall.js",
39
- "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"
39
+ "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"
40
40
  },
41
41
  "repository": {
42
42
  "type": "git",