@helping-ai-workflow/md2doc 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/md2doc.js +466 -5
  2. package/package.json +2 -2
package/lib/md2doc.js CHANGED
@@ -1095,8 +1095,10 @@ const html = `<!DOCTYPE html>
1095
1095
  flex: 1 1 auto;
1096
1096
  min-height: 0;
1097
1097
  overflow-y: auto;
1098
- /* Horizontal peek: no scrollbar, position driven by shift+wheel only. */
1099
- overflow-x: hidden;
1098
+ /* Horizontal peek: thin native scrollbar (drag/track-click/touchpad) plus
1099
+ the shift+wheel handler; appears only when a title actually overflows. */
1100
+ overflow-x: auto;
1101
+ scrollbar-width: thin;
1100
1102
  }
1101
1103
  .toc ul {
1102
1104
  list-style: none;
@@ -1362,6 +1364,31 @@ const html = `<!DOCTYPE html>
1362
1364
  cursor: pointer;
1363
1365
  }
1364
1366
  .lightbox-bar button:hover { background: rgba(255, 255, 255, 0.18); }
1367
+ .lightbox-bar button.is-active {
1368
+ background: rgba(147, 197, 253, 0.35);
1369
+ box-shadow: inset 0 0 0 1px #93c5fd;
1370
+ }
1371
+ .lightbox-sep {
1372
+ width: 1px;
1373
+ align-self: stretch;
1374
+ margin: 4px 2px;
1375
+ background: rgba(255, 255, 255, 0.25);
1376
+ }
1377
+ /* Annotation overlay: absolute twin of the artwork, same viewBox, so shapes
1378
+ live in image coordinates and ride every zoom for free. Must undo the
1379
+ .lightbox-canvas > * block/white-background defaults. */
1380
+ .lightbox-canvas > .lightbox-anno {
1381
+ position: absolute;
1382
+ inset: 0;
1383
+ width: 100%;
1384
+ height: 100%;
1385
+ background: transparent;
1386
+ pointer-events: none;
1387
+ }
1388
+ .lightbox-stage[data-anno-cursor="draw"],
1389
+ .lightbox-stage[data-anno-cursor="draw"] .lightbox-anno { cursor: crosshair; }
1390
+ .lightbox-anno g[data-anno-id] { cursor: move; }
1391
+ .lightbox-anno [data-anno-handle] { cursor: nwse-resize; }
1365
1392
  .lightbox-zoom-value {
1366
1393
  min-width: 56px;
1367
1394
  text-align: center;
@@ -1382,7 +1409,7 @@ const html = `<!DOCTYPE html>
1382
1409
  overscroll-behavior: contain;
1383
1410
  }
1384
1411
  .lightbox-stage[data-panning] { cursor: grabbing; }
1385
- .lightbox-canvas { margin: 0 auto; }
1412
+ .lightbox-canvas { margin: 0 auto; position: relative; }
1386
1413
  /* Sized in px by the runtime; the child follows, so the scroll extent grows
1387
1414
  with the zoom. A CSS transform would scale the pixels and leave the scroll
1388
1415
  area at the original size, stranding the edges out of reach. */
@@ -2156,6 +2183,21 @@ ${mermaidInitTag}` : ''}
2156
2183
  var lightboxNaturalH = 1;
2157
2184
  var lightboxReturnScroll = 0;
2158
2185
 
2186
+ // Annotation layer state. Shapes are keyed per source node in memory only:
2187
+ // close/reopen keeps them, reload starts clean (they are reading-session
2188
+ // scratch, and localStorage keys would go stale when the doc regenerates).
2189
+ var ANNO_NS = 'http://www.w3.org/2000/svg';
2190
+ var annoStore = (typeof WeakMap === 'function') ? new WeakMap() : null;
2191
+ var annoShapes = [];
2192
+ var annoSvg = null;
2193
+ var annoMode = null; // null | 'f' | 'e' | 'r' | 'm'
2194
+ var annoSelected = null;
2195
+ var annoDraft = null;
2196
+ var annoUndoStack = [];
2197
+ var annoRedoStack = [];
2198
+ var annoStrokeW = 3;
2199
+ var annoToolButtons = {};
2200
+
2159
2201
  function lightboxIsOpen() {
2160
2202
  return !!lightboxEl && !lightboxEl.hidden;
2161
2203
  }
@@ -2207,6 +2249,377 @@ ${mermaidInitTag}` : ''}
2207
2249
  setLightboxZoom(lightboxZoom * factor, anchorX, anchorY);
2208
2250
  }
2209
2251
 
2252
+ // ── Lightbox annotation layer ─────────────────────────────────────────────
2253
+ function annoGeomOf(shape) {
2254
+ if (shape.type === 'path') return { pts: shape.pts.map(function (p) { return [p[0], p[1]]; }) };
2255
+ var geom = {};
2256
+ Object.keys(shape).forEach(function (k) { if (k !== 'type') geom[k] = shape[k]; });
2257
+ return geom;
2258
+ }
2259
+
2260
+ function annoSetGeom(shape, geom) {
2261
+ if (shape.type === 'path') shape.pts = geom.pts.map(function (p) { return [p[0], p[1]]; });
2262
+ else Object.keys(geom).forEach(function (k) { shape[k] = geom[k]; });
2263
+ }
2264
+
2265
+ function annoBBox(shape) {
2266
+ if (shape.type === 'rect') return { x: shape.x, y: shape.y, w: shape.w, h: shape.h };
2267
+ if (shape.type === 'ellipse') return { x: shape.cx - shape.rx, y: shape.cy - shape.ry, w: shape.rx * 2, h: shape.ry * 2 };
2268
+ if (shape.type === 'line' || shape.type === 'arrow') {
2269
+ return {
2270
+ x: Math.min(shape.x1, shape.x2), y: Math.min(shape.y1, shape.y2),
2271
+ w: Math.abs(shape.x2 - shape.x1), h: Math.abs(shape.y2 - shape.y1),
2272
+ };
2273
+ }
2274
+ var xs = shape.pts.map(function (p) { return p[0]; });
2275
+ var ys = shape.pts.map(function (p) { return p[1]; });
2276
+ var x = Math.min.apply(null, xs);
2277
+ var y = Math.min.apply(null, ys);
2278
+ return { x: x, y: y, w: Math.max.apply(null, xs) - x, h: Math.max.apply(null, ys) - y };
2279
+ }
2280
+
2281
+ function annoApplyBBox(shape, from, to) {
2282
+ var sx = from.w > 0.01 ? to.w / from.w : 1;
2283
+ var sy = from.h > 0.01 ? to.h / from.h : 1;
2284
+ if (shape.type === 'rect') {
2285
+ shape.x = to.x; shape.y = to.y; shape.w = to.w; shape.h = to.h;
2286
+ } else if (shape.type === 'ellipse') {
2287
+ shape.cx = to.x + to.w / 2; shape.cy = to.y + to.h / 2;
2288
+ shape.rx = to.w / 2; shape.ry = to.h / 2;
2289
+ } else if (shape.type === 'line' || shape.type === 'arrow') {
2290
+ var x1 = to.x + (shape.x1 - from.x) * sx;
2291
+ var y1 = to.y + (shape.y1 - from.y) * sy;
2292
+ var x2 = to.x + (shape.x2 - from.x) * sx;
2293
+ var y2 = to.y + (shape.y2 - from.y) * sy;
2294
+ shape.x1 = x1; shape.y1 = y1; shape.x2 = x2; shape.y2 = y2;
2295
+ } else {
2296
+ shape.pts = shape.pts.map(function (p) {
2297
+ return [to.x + (p[0] - from.x) * sx, to.y + (p[1] - from.y) * sy];
2298
+ });
2299
+ }
2300
+ }
2301
+
2302
+ function annoPathD(pts) {
2303
+ return pts.map(function (p, i) {
2304
+ return (i === 0 ? 'M' : 'L') + p[0].toFixed(1) + ' ' + p[1].toFixed(1);
2305
+ }).join(' ');
2306
+ }
2307
+
2308
+ function annoShapeEl(shape) {
2309
+ var tag = (shape.type === 'line' || shape.type === 'arrow') ? 'line' : shape.type;
2310
+ var el = document.createElementNS(ANNO_NS, tag);
2311
+ if (shape.type === 'line' || shape.type === 'arrow') {
2312
+ el.setAttribute('x1', shape.x1.toFixed(1)); el.setAttribute('y1', shape.y1.toFixed(1));
2313
+ el.setAttribute('x2', shape.x2.toFixed(1)); el.setAttribute('y2', shape.y2.toFixed(1));
2314
+ el.setAttribute('stroke-linecap', 'round');
2315
+ if (shape.type === 'arrow') el.setAttribute('marker-end', 'url(#anno-arrow-head)');
2316
+ } else if (shape.type === 'rect') {
2317
+ el.setAttribute('x', shape.x.toFixed(1)); el.setAttribute('y', shape.y.toFixed(1));
2318
+ el.setAttribute('width', Math.max(0.1, shape.w).toFixed(1));
2319
+ el.setAttribute('height', Math.max(0.1, shape.h).toFixed(1));
2320
+ el.setAttribute('fill', 'transparent');
2321
+ } else if (shape.type === 'ellipse') {
2322
+ el.setAttribute('cx', shape.cx.toFixed(1)); el.setAttribute('cy', shape.cy.toFixed(1));
2323
+ el.setAttribute('rx', Math.max(0.1, shape.rx).toFixed(1));
2324
+ el.setAttribute('ry', Math.max(0.1, shape.ry).toFixed(1));
2325
+ el.setAttribute('fill', 'transparent');
2326
+ } else {
2327
+ el.setAttribute('d', annoPathD(shape.pts));
2328
+ el.setAttribute('fill', 'none');
2329
+ el.setAttribute('stroke-linejoin', 'round');
2330
+ el.setAttribute('stroke-linecap', 'round');
2331
+ }
2332
+ el.setAttribute('stroke', '#ef4444');
2333
+ el.setAttribute('stroke-width', String(annoStrokeW));
2334
+ return el;
2335
+ }
2336
+
2337
+ function annoEnsureDefs() {
2338
+ var defs = document.createElementNS(ANNO_NS, 'defs');
2339
+ var marker = document.createElementNS(ANNO_NS, 'marker');
2340
+ marker.setAttribute('id', 'anno-arrow-head');
2341
+ marker.setAttribute('markerWidth', '8');
2342
+ marker.setAttribute('markerHeight', '8');
2343
+ marker.setAttribute('refX', '6.4');
2344
+ marker.setAttribute('refY', '3');
2345
+ marker.setAttribute('orient', 'auto');
2346
+ var tip = document.createElementNS(ANNO_NS, 'path');
2347
+ tip.setAttribute('d', 'M0 0 L7 3 L0 6 Z');
2348
+ tip.setAttribute('fill', '#ef4444');
2349
+ marker.appendChild(tip);
2350
+ defs.appendChild(marker);
2351
+ annoSvg.appendChild(defs);
2352
+ }
2353
+
2354
+ function annoRedraw() {
2355
+ if (!annoSvg) return;
2356
+ while (annoSvg.firstChild) annoSvg.removeChild(annoSvg.firstChild);
2357
+ annoEnsureDefs();
2358
+ annoShapes.forEach(function (shape, index) {
2359
+ var g = document.createElementNS(ANNO_NS, 'g');
2360
+ g.setAttribute('data-anno-id', String(index));
2361
+ g.appendChild(annoShapeEl(shape));
2362
+ if (shape.type === 'path' || shape.type === 'line' || shape.type === 'arrow') {
2363
+ // A 3px stroke is an unclickable target — give strokes a fat invisible twin.
2364
+ var hit = document.createElementNS(ANNO_NS, shape.type === 'path' ? 'path' : 'line');
2365
+ if (shape.type === 'path') {
2366
+ hit.setAttribute('d', annoPathD(shape.pts));
2367
+ } else {
2368
+ hit.setAttribute('x1', shape.x1); hit.setAttribute('y1', shape.y1);
2369
+ hit.setAttribute('x2', shape.x2); hit.setAttribute('y2', shape.y2);
2370
+ }
2371
+ hit.setAttribute('fill', 'none');
2372
+ hit.setAttribute('stroke', 'transparent');
2373
+ hit.setAttribute('stroke-width', String(annoStrokeW * 5));
2374
+ hit.setAttribute('data-anno-hit', '');
2375
+ g.appendChild(hit);
2376
+ }
2377
+ annoSvg.appendChild(g);
2378
+ });
2379
+ if (annoSelected && annoShapes.indexOf(annoSelected) !== -1) {
2380
+ var box = annoBBox(annoSelected);
2381
+ var ui = document.createElementNS(ANNO_NS, 'rect');
2382
+ ui.setAttribute('data-anno-ui', '');
2383
+ ui.setAttribute('x', box.x); ui.setAttribute('y', box.y);
2384
+ ui.setAttribute('width', Math.max(0.1, box.w)); ui.setAttribute('height', Math.max(0.1, box.h));
2385
+ ui.setAttribute('fill', 'none');
2386
+ ui.setAttribute('stroke', '#3b82f6');
2387
+ ui.setAttribute('stroke-width', String(Math.max(1, annoStrokeW / 2)));
2388
+ ui.setAttribute('stroke-dasharray', (annoStrokeW * 2) + ' ' + annoStrokeW);
2389
+ annoSvg.appendChild(ui);
2390
+ var hs = annoStrokeW * 3;
2391
+ [[box.x, box.y], [box.x + box.w, box.y], [box.x, box.y + box.h], [box.x + box.w, box.y + box.h]]
2392
+ .forEach(function (corner, i) {
2393
+ var h = document.createElementNS(ANNO_NS, 'rect');
2394
+ h.setAttribute('data-anno-ui', '');
2395
+ h.setAttribute('data-anno-handle', String(i));
2396
+ h.setAttribute('x', corner[0] - hs / 2); h.setAttribute('y', corner[1] - hs / 2);
2397
+ h.setAttribute('width', hs); h.setAttribute('height', hs);
2398
+ h.setAttribute('fill', '#3b82f6');
2399
+ annoSvg.appendChild(h);
2400
+ });
2401
+ }
2402
+ }
2403
+
2404
+ function annoPushOp(op) {
2405
+ annoUndoStack.push(op);
2406
+ annoRedoStack.length = 0;
2407
+ }
2408
+
2409
+ function annoApplyOp(op, reverse) {
2410
+ if (op.kind === 'add') {
2411
+ if (reverse) annoShapes.splice(annoShapes.indexOf(op.shape), 1);
2412
+ else annoShapes.splice(Math.min(op.index, annoShapes.length), 0, op.shape);
2413
+ } else if (op.kind === 'del') {
2414
+ if (reverse) annoShapes.splice(Math.min(op.index, annoShapes.length), 0, op.shape);
2415
+ else annoShapes.splice(annoShapes.indexOf(op.shape), 1);
2416
+ } else if (op.kind === 'geom') {
2417
+ annoSetGeom(op.shape, reverse ? op.before : op.after);
2418
+ } else if (op.kind === 'clear') {
2419
+ if (reverse) op.shapes.forEach(function (s) { annoShapes.push(s); });
2420
+ else annoShapes.length = 0;
2421
+ }
2422
+ }
2423
+
2424
+ function annoUndo() {
2425
+ var op = annoUndoStack.pop();
2426
+ if (!op) return;
2427
+ annoApplyOp(op, true);
2428
+ annoRedoStack.push(op);
2429
+ annoSelected = null;
2430
+ annoRedraw();
2431
+ }
2432
+
2433
+ function annoRedo() {
2434
+ var op = annoRedoStack.pop();
2435
+ if (!op) return;
2436
+ annoApplyOp(op, false);
2437
+ annoUndoStack.push(op);
2438
+ annoSelected = null;
2439
+ annoRedraw();
2440
+ }
2441
+
2442
+ function annoClearAll() {
2443
+ if (!annoShapes.length) return;
2444
+ annoPushOp({ kind: 'clear', shapes: annoShapes.slice() });
2445
+ annoShapes.length = 0;
2446
+ annoSelected = null;
2447
+ annoRedraw();
2448
+ }
2449
+
2450
+ function annoDeleteSelected() {
2451
+ if (!annoSelected) return;
2452
+ var index = annoShapes.indexOf(annoSelected);
2453
+ if (index === -1) return;
2454
+ annoPushOp({ kind: 'del', shape: annoSelected, index: index });
2455
+ annoShapes.splice(index, 1);
2456
+ annoSelected = null;
2457
+ annoRedraw();
2458
+ }
2459
+
2460
+ function setAnnoMode(mode) {
2461
+ annoMode = (annoMode === mode) ? null : mode;
2462
+ if (annoMode !== 'm') annoSelected = null;
2463
+ Object.keys(annoToolButtons).forEach(function (key) {
2464
+ annoToolButtons[key].classList.toggle('is-active', key === annoMode);
2465
+ });
2466
+ if (annoSvg) annoSvg.style.pointerEvents = annoMode ? 'auto' : 'none';
2467
+ if (lightboxStage) {
2468
+ if (annoMode === 'f' || annoMode === 'e' || annoMode === 'r') {
2469
+ lightboxStage.setAttribute('data-anno-cursor', 'draw');
2470
+ } else {
2471
+ lightboxStage.removeAttribute('data-anno-cursor');
2472
+ }
2473
+ }
2474
+ annoRedraw();
2475
+ }
2476
+
2477
+ function annoPoint(event) {
2478
+ var rect = annoSvg.getBoundingClientRect();
2479
+ var scale = rect.width > 0 ? lightboxNaturalW / rect.width : 1;
2480
+ return { x: (event.clientX - rect.left) * scale, y: (event.clientY - rect.top) * scale };
2481
+ }
2482
+
2483
+ function annoCancelDraft() {
2484
+ if (!annoDraft) return;
2485
+ if (annoDraft.el && annoDraft.el.parentNode) annoDraft.el.parentNode.removeChild(annoDraft.el);
2486
+ annoDraft = null;
2487
+ }
2488
+
2489
+ function annoPointerDown(event) {
2490
+ if (!annoMode || event.button !== 0) return;
2491
+ event.preventDefault();
2492
+ event.stopPropagation();
2493
+ if (annoSvg.setPointerCapture) {
2494
+ try { annoSvg.setPointerCapture(event.pointerId); } catch (e) { /* detached */ }
2495
+ }
2496
+ var pt = annoPoint(event);
2497
+ if (annoMode === 'm') {
2498
+ var handle = event.target.closest ? event.target.closest('[data-anno-handle]') : null;
2499
+ var group = event.target.closest ? event.target.closest('g[data-anno-id]') : null;
2500
+ if (handle && annoSelected) {
2501
+ var corner = Number(handle.getAttribute('data-anno-handle'));
2502
+ var box = annoBBox(annoSelected);
2503
+ annoDraft = {
2504
+ kind: 'resize', shape: annoSelected, corner: corner,
2505
+ startBox: box, before: annoGeomOf(annoSelected), moved: false,
2506
+ anchor: {
2507
+ x: (corner === 0 || corner === 2) ? box.x + box.w : box.x,
2508
+ y: (corner === 0 || corner === 1) ? box.y + box.h : box.y,
2509
+ },
2510
+ };
2511
+ } else if (group) {
2512
+ annoSelected = annoShapes[Number(group.getAttribute('data-anno-id'))] || null;
2513
+ annoDraft = annoSelected ? {
2514
+ kind: 'move', shape: annoSelected, start: pt,
2515
+ startBox: annoBBox(annoSelected), before: annoGeomOf(annoSelected), moved: false,
2516
+ } : null;
2517
+ annoRedraw();
2518
+ } else {
2519
+ annoSelected = null;
2520
+ annoRedraw();
2521
+ }
2522
+ return;
2523
+ }
2524
+ var shape;
2525
+ if (annoMode === 'f') shape = { type: 'path', pts: [[pt.x, pt.y]] };
2526
+ else if (annoMode === 'e') shape = { type: 'ellipse', cx: pt.x, cy: pt.y, rx: 0, ry: 0 };
2527
+ else if (annoMode === 'l') shape = { type: 'line', x1: pt.x, y1: pt.y, x2: pt.x, y2: pt.y };
2528
+ else if (annoMode === 'a') shape = { type: 'arrow', x1: pt.x, y1: pt.y, x2: pt.x, y2: pt.y };
2529
+ else shape = { type: 'rect', x: pt.x, y: pt.y, w: 0, h: 0 };
2530
+ annoDraft = { kind: 'draw', shape: shape, start: pt, el: annoShapeEl(shape), clientDist: 0, lastClient: [event.clientX, event.clientY] };
2531
+ annoSvg.appendChild(annoDraft.el);
2532
+ }
2533
+
2534
+ function annoUpdateDraftEl() {
2535
+ var d = annoDraft;
2536
+ var fresh = annoShapeEl(d.shape);
2537
+ d.el.parentNode.replaceChild(fresh, d.el);
2538
+ d.el = fresh;
2539
+ }
2540
+
2541
+ function annoPointerMove(event) {
2542
+ if (!annoDraft) return;
2543
+ var pt = annoPoint(event);
2544
+ var d = annoDraft;
2545
+ if (d.kind === 'draw') {
2546
+ d.clientDist += Math.abs(event.clientX - d.lastClient[0]) + Math.abs(event.clientY - d.lastClient[1]);
2547
+ d.lastClient = [event.clientX, event.clientY];
2548
+ if (d.shape.type === 'path') {
2549
+ d.shape.pts.push([pt.x, pt.y]);
2550
+ } else if (d.shape.type === 'line' || d.shape.type === 'arrow') {
2551
+ d.shape.x2 = pt.x; d.shape.y2 = pt.y;
2552
+ } else if (d.shape.type === 'ellipse') {
2553
+ d.shape.cx = (d.start.x + pt.x) / 2; d.shape.cy = (d.start.y + pt.y) / 2;
2554
+ d.shape.rx = Math.abs(pt.x - d.start.x) / 2; d.shape.ry = Math.abs(pt.y - d.start.y) / 2;
2555
+ } else {
2556
+ d.shape.x = Math.min(d.start.x, pt.x); d.shape.y = Math.min(d.start.y, pt.y);
2557
+ d.shape.w = Math.abs(pt.x - d.start.x); d.shape.h = Math.abs(pt.y - d.start.y);
2558
+ }
2559
+ annoUpdateDraftEl();
2560
+ return;
2561
+ }
2562
+ d.moved = true;
2563
+ if (d.kind === 'move') {
2564
+ var box = d.startBox;
2565
+ annoApplyBBox(d.shape, annoBBox(d.shape), {
2566
+ x: box.x + (pt.x - d.start.x), y: box.y + (pt.y - d.start.y), w: box.w, h: box.h,
2567
+ });
2568
+ } else {
2569
+ var ax = d.anchor.x;
2570
+ var ay = d.anchor.y;
2571
+ annoApplyBBox(d.shape, annoBBox(d.shape), {
2572
+ x: Math.min(ax, pt.x), y: Math.min(ay, pt.y),
2573
+ w: Math.max(1, Math.abs(pt.x - ax)), h: Math.max(1, Math.abs(pt.y - ay)),
2574
+ });
2575
+ }
2576
+ annoRedraw();
2577
+ }
2578
+
2579
+ function annoPointerUp() {
2580
+ if (!annoDraft) return;
2581
+ var d = annoDraft;
2582
+ annoDraft = null;
2583
+ if (d.kind === 'draw') {
2584
+ if (d.el.parentNode) d.el.parentNode.removeChild(d.el);
2585
+ var tooSmall = d.clientDist < 4 ||
2586
+ (d.shape.type === 'path' && d.shape.pts.length < 2);
2587
+ if (!tooSmall) {
2588
+ annoPushOp({ kind: 'add', shape: d.shape, index: annoShapes.length });
2589
+ annoShapes.push(d.shape);
2590
+ }
2591
+ annoRedraw();
2592
+ return;
2593
+ }
2594
+ if (d.moved) {
2595
+ annoPushOp({ kind: 'geom', shape: d.shape, before: d.before, after: annoGeomOf(d.shape) });
2596
+ }
2597
+ annoRedraw();
2598
+ }
2599
+
2600
+ function annoSetup(sourceNode) {
2601
+ annoShapes = [];
2602
+ if (annoStore) {
2603
+ annoShapes = annoStore.get(sourceNode);
2604
+ if (!annoShapes) { annoShapes = []; annoStore.set(sourceNode, annoShapes); }
2605
+ }
2606
+ annoUndoStack = [];
2607
+ annoRedoStack = [];
2608
+ annoSelected = null;
2609
+ annoCancelDraft();
2610
+ annoStrokeW = Math.max(3, Math.round(Math.max(lightboxNaturalW, lightboxNaturalH) / 300));
2611
+ annoSvg = document.createElementNS(ANNO_NS, 'svg');
2612
+ annoSvg.setAttribute('class', 'lightbox-anno');
2613
+ annoSvg.setAttribute('viewBox', '0 0 ' + lightboxNaturalW + ' ' + lightboxNaturalH);
2614
+ annoSvg.addEventListener('pointerdown', annoPointerDown);
2615
+ annoSvg.addEventListener('pointermove', annoPointerMove);
2616
+ annoSvg.addEventListener('pointerup', annoPointerUp);
2617
+ annoSvg.addEventListener('pointercancel', annoPointerUp);
2618
+ lightboxCanvas.appendChild(annoSvg);
2619
+ annoMode = null;
2620
+ setAnnoMode(null);
2621
+ }
2622
+
2210
2623
  function buildLightbox() {
2211
2624
  if (lightboxEl) return;
2212
2625
  lightboxEl = document.createElement('div');
@@ -2219,7 +2632,7 @@ ${mermaidInitTag}` : ''}
2219
2632
  bar.className = 'lightbox-bar';
2220
2633
  var hint = document.createElement('span');
2221
2634
  hint.className = 'lightbox-hint';
2222
- hint.textContent = 'scroll to pan · shift+scroll sideways · ctrl+scroll to zoom · drag to pan · esc to close';
2635
+ hint.textContent = 'ctrl+scroll zoom · drag pan · f/e/r/l/a draw · m select · del remove · ctrl+z undo · esc close';
2223
2636
  lightboxZoomValue = document.createElement('span');
2224
2637
  lightboxZoomValue.className = 'lightbox-zoom-value';
2225
2638
  lightboxZoomValue.setAttribute('data-lightbox-zoom-value', '');
@@ -2229,6 +2642,25 @@ ${mermaidInitTag}` : ''}
2229
2642
  bar.appendChild(lightboxButton('+', 'Zoom in', function () { zoomLightboxBy(LIGHTBOX_STEP); }));
2230
2643
  bar.appendChild(lightboxButton('Fit', 'Fit to window', function () { setLightboxZoom(lightboxFitZoom); }));
2231
2644
  bar.appendChild(lightboxButton('1:1', 'Actual size', function () { setLightboxZoom(1); }));
2645
+ var sep = document.createElement('span');
2646
+ sep.className = 'lightbox-sep';
2647
+ bar.appendChild(sep);
2648
+ [
2649
+ ['f', '✎', 'Freehand (f)'],
2650
+ ['e', '○', 'Ellipse (e)'],
2651
+ ['r', '▭', 'Rectangle (r)'],
2652
+ ['l', '╱', 'Line (l)'],
2653
+ ['a', '↗', 'Arrow (a)'],
2654
+ ['m', '✥', 'Select / move (m)'],
2655
+ ].forEach(function (tool) {
2656
+ var button = lightboxButton(tool[1], tool[2], function () { setAnnoMode(tool[0]); });
2657
+ button.setAttribute('data-anno-tool', tool[0]);
2658
+ annoToolButtons[tool[0]] = button;
2659
+ bar.appendChild(button);
2660
+ });
2661
+ var clearButton = lightboxButton('Clear', 'Clear all annotations', annoClearAll);
2662
+ clearButton.setAttribute('data-anno-clear', '');
2663
+ bar.appendChild(clearButton);
2232
2664
  bar.appendChild(lightboxButton('✕', 'Close', closeLightbox));
2233
2665
 
2234
2666
  lightboxStage = document.createElement('div');
@@ -2259,6 +2691,8 @@ ${mermaidInitTag}` : ''}
2259
2691
 
2260
2692
  lightboxStage.addEventListener('mousedown', function (event) {
2261
2693
  if (event.button !== 0) return;
2694
+ // With a tool armed the overlay owns pointer events over the artwork.
2695
+ if (annoMode && event.target && event.target.closest && event.target.closest('.lightbox-anno')) return;
2262
2696
  panning = true;
2263
2697
  panDistance = 0;
2264
2698
  panX = event.clientX;
@@ -2289,6 +2723,7 @@ ${mermaidInitTag}` : ''}
2289
2723
  });
2290
2724
  lightboxStage.addEventListener('dblclick', function (event) {
2291
2725
  event.preventDefault();
2726
+ if (annoMode && event.target && event.target.closest && event.target.closest('.lightbox-anno')) return;
2292
2727
  setLightboxZoom(Math.abs(lightboxZoom - 1) < 0.001 ? lightboxFitZoom : 1);
2293
2728
  });
2294
2729
  }
@@ -2329,6 +2764,7 @@ ${mermaidInitTag}` : ''}
2329
2764
  lightboxCanvas.removeChild(lightboxCanvas.firstChild);
2330
2765
  }
2331
2766
  lightboxCanvas.appendChild(clone);
2767
+ annoSetup(node);
2332
2768
 
2333
2769
  lightboxReturnScroll = window.scrollY;
2334
2770
  suppressAnchorCapture = true;
@@ -2345,6 +2781,10 @@ ${mermaidInitTag}` : ''}
2345
2781
  if (!lightboxIsOpen()) return;
2346
2782
  lightboxEl.hidden = true;
2347
2783
  document.body.removeAttribute('data-lightbox-open');
2784
+ annoCancelDraft();
2785
+ annoSvg = null;
2786
+ annoMode = null;
2787
+ setAnnoMode(null);
2348
2788
  while (lightboxCanvas.firstChild) {
2349
2789
  lightboxCanvas.removeChild(lightboxCanvas.firstChild);
2350
2790
  }
@@ -2370,7 +2810,28 @@ ${mermaidInitTag}` : ''}
2370
2810
 
2371
2811
  document.addEventListener('keydown', function (event) {
2372
2812
  if (!lightboxIsOpen()) return;
2373
- if (event.key === 'Escape') { event.preventDefault(); closeLightbox(); return; }
2813
+ var key = event.key.toLowerCase();
2814
+ if ((event.ctrlKey || event.metaKey) && key === 'z' && !event.shiftKey) {
2815
+ event.preventDefault(); annoUndo(); return;
2816
+ }
2817
+ if ((event.ctrlKey || event.metaKey) && (key === 'y' || (key === 'z' && event.shiftKey))) {
2818
+ event.preventDefault(); annoRedo(); return;
2819
+ }
2820
+ if (event.ctrlKey || event.metaKey || event.altKey) return;
2821
+ if (event.key === 'Escape') {
2822
+ event.preventDefault();
2823
+ // Layered: cancel an in-progress draw, then drop the selection, then close.
2824
+ if (annoDraft) { annoCancelDraft(); annoRedraw(); return; }
2825
+ if (annoSelected) { annoSelected = null; annoRedraw(); return; }
2826
+ closeLightbox();
2827
+ return;
2828
+ }
2829
+ if (key === 'f' || key === 'e' || key === 'r' || key === 'l' || key === 'a' || key === 'm') {
2830
+ event.preventDefault(); setAnnoMode(key); return;
2831
+ }
2832
+ if (event.key === 'Delete' || event.key === 'Backspace') {
2833
+ event.preventDefault(); annoDeleteSelected(); return;
2834
+ }
2374
2835
  if (event.key === '+' || event.key === '=') { event.preventDefault(); zoomLightboxBy(LIGHTBOX_STEP); return; }
2375
2836
  if (event.key === '-' || event.key === '_') { event.preventDefault(); zoomLightboxBy(1 / LIGHTBOX_STEP); return; }
2376
2837
  if (event.key === '0') { event.preventDefault(); setLightboxZoom(lightboxFitZoom); return; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helping-ai-workflow/md2doc",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
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/images.test.js && node test/scroll-anchor.test.js && node test/lightbox.test.js && node test/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js"
39
+ "test": "node test/md2doc.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/reader-panels.test.js && node test/cli.test.js && node test/code-operator.test.js"
40
40
  },
41
41
  "repository": {
42
42
  "type": "git",