@helping-ai-workflow/md2doc 2.6.1 → 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 +462 -3
- package/package.json +2 -2
package/lib/md2doc.js
CHANGED
|
@@ -1364,6 +1364,31 @@ const html = `<!DOCTYPE html>
|
|
|
1364
1364
|
cursor: pointer;
|
|
1365
1365
|
}
|
|
1366
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; }
|
|
1367
1392
|
.lightbox-zoom-value {
|
|
1368
1393
|
min-width: 56px;
|
|
1369
1394
|
text-align: center;
|
|
@@ -1384,7 +1409,7 @@ const html = `<!DOCTYPE html>
|
|
|
1384
1409
|
overscroll-behavior: contain;
|
|
1385
1410
|
}
|
|
1386
1411
|
.lightbox-stage[data-panning] { cursor: grabbing; }
|
|
1387
|
-
.lightbox-canvas { margin: 0 auto; }
|
|
1412
|
+
.lightbox-canvas { margin: 0 auto; position: relative; }
|
|
1388
1413
|
/* Sized in px by the runtime; the child follows, so the scroll extent grows
|
|
1389
1414
|
with the zoom. A CSS transform would scale the pixels and leave the scroll
|
|
1390
1415
|
area at the original size, stranding the edges out of reach. */
|
|
@@ -2158,6 +2183,21 @@ ${mermaidInitTag}` : ''}
|
|
|
2158
2183
|
var lightboxNaturalH = 1;
|
|
2159
2184
|
var lightboxReturnScroll = 0;
|
|
2160
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
|
+
|
|
2161
2201
|
function lightboxIsOpen() {
|
|
2162
2202
|
return !!lightboxEl && !lightboxEl.hidden;
|
|
2163
2203
|
}
|
|
@@ -2209,6 +2249,377 @@ ${mermaidInitTag}` : ''}
|
|
|
2209
2249
|
setLightboxZoom(lightboxZoom * factor, anchorX, anchorY);
|
|
2210
2250
|
}
|
|
2211
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
|
+
|
|
2212
2623
|
function buildLightbox() {
|
|
2213
2624
|
if (lightboxEl) return;
|
|
2214
2625
|
lightboxEl = document.createElement('div');
|
|
@@ -2221,7 +2632,7 @@ ${mermaidInitTag}` : ''}
|
|
|
2221
2632
|
bar.className = 'lightbox-bar';
|
|
2222
2633
|
var hint = document.createElement('span');
|
|
2223
2634
|
hint.className = 'lightbox-hint';
|
|
2224
|
-
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';
|
|
2225
2636
|
lightboxZoomValue = document.createElement('span');
|
|
2226
2637
|
lightboxZoomValue.className = 'lightbox-zoom-value';
|
|
2227
2638
|
lightboxZoomValue.setAttribute('data-lightbox-zoom-value', '');
|
|
@@ -2231,6 +2642,25 @@ ${mermaidInitTag}` : ''}
|
|
|
2231
2642
|
bar.appendChild(lightboxButton('+', 'Zoom in', function () { zoomLightboxBy(LIGHTBOX_STEP); }));
|
|
2232
2643
|
bar.appendChild(lightboxButton('Fit', 'Fit to window', function () { setLightboxZoom(lightboxFitZoom); }));
|
|
2233
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);
|
|
2234
2664
|
bar.appendChild(lightboxButton('✕', 'Close', closeLightbox));
|
|
2235
2665
|
|
|
2236
2666
|
lightboxStage = document.createElement('div');
|
|
@@ -2261,6 +2691,8 @@ ${mermaidInitTag}` : ''}
|
|
|
2261
2691
|
|
|
2262
2692
|
lightboxStage.addEventListener('mousedown', function (event) {
|
|
2263
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;
|
|
2264
2696
|
panning = true;
|
|
2265
2697
|
panDistance = 0;
|
|
2266
2698
|
panX = event.clientX;
|
|
@@ -2291,6 +2723,7 @@ ${mermaidInitTag}` : ''}
|
|
|
2291
2723
|
});
|
|
2292
2724
|
lightboxStage.addEventListener('dblclick', function (event) {
|
|
2293
2725
|
event.preventDefault();
|
|
2726
|
+
if (annoMode && event.target && event.target.closest && event.target.closest('.lightbox-anno')) return;
|
|
2294
2727
|
setLightboxZoom(Math.abs(lightboxZoom - 1) < 0.001 ? lightboxFitZoom : 1);
|
|
2295
2728
|
});
|
|
2296
2729
|
}
|
|
@@ -2331,6 +2764,7 @@ ${mermaidInitTag}` : ''}
|
|
|
2331
2764
|
lightboxCanvas.removeChild(lightboxCanvas.firstChild);
|
|
2332
2765
|
}
|
|
2333
2766
|
lightboxCanvas.appendChild(clone);
|
|
2767
|
+
annoSetup(node);
|
|
2334
2768
|
|
|
2335
2769
|
lightboxReturnScroll = window.scrollY;
|
|
2336
2770
|
suppressAnchorCapture = true;
|
|
@@ -2347,6 +2781,10 @@ ${mermaidInitTag}` : ''}
|
|
|
2347
2781
|
if (!lightboxIsOpen()) return;
|
|
2348
2782
|
lightboxEl.hidden = true;
|
|
2349
2783
|
document.body.removeAttribute('data-lightbox-open');
|
|
2784
|
+
annoCancelDraft();
|
|
2785
|
+
annoSvg = null;
|
|
2786
|
+
annoMode = null;
|
|
2787
|
+
setAnnoMode(null);
|
|
2350
2788
|
while (lightboxCanvas.firstChild) {
|
|
2351
2789
|
lightboxCanvas.removeChild(lightboxCanvas.firstChild);
|
|
2352
2790
|
}
|
|
@@ -2372,7 +2810,28 @@ ${mermaidInitTag}` : ''}
|
|
|
2372
2810
|
|
|
2373
2811
|
document.addEventListener('keydown', function (event) {
|
|
2374
2812
|
if (!lightboxIsOpen()) return;
|
|
2375
|
-
|
|
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
|
+
}
|
|
2376
2835
|
if (event.key === '+' || event.key === '=') { event.preventDefault(); zoomLightboxBy(LIGHTBOX_STEP); return; }
|
|
2377
2836
|
if (event.key === '-' || event.key === '_') { event.preventDefault(); zoomLightboxBy(1 / LIGHTBOX_STEP); return; }
|
|
2378
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",
|