@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.
- package/lib/md2doc.js +466 -5
- 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:
|
|
1099
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|