@helping-ai-workflow/md2doc 2.6.1 → 2.8.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 +662 -3
- package/package.json +2 -2
package/lib/md2doc.js
CHANGED
|
@@ -1364,6 +1364,59 @@ 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-swatch {
|
|
1372
|
+
width: 16px;
|
|
1373
|
+
height: 16px;
|
|
1374
|
+
min-width: 16px;
|
|
1375
|
+
padding: 0;
|
|
1376
|
+
border-radius: 50%;
|
|
1377
|
+
border: 2px solid rgba(255, 255, 255, 0.4);
|
|
1378
|
+
cursor: pointer;
|
|
1379
|
+
align-self: center;
|
|
1380
|
+
}
|
|
1381
|
+
.lightbox-swatch.is-active {
|
|
1382
|
+
border-color: #ffffff;
|
|
1383
|
+
box-shadow: 0 0 0 2px #93c5fd;
|
|
1384
|
+
}
|
|
1385
|
+
/* Inline annotation overlay left behind after closing the lightbox:
|
|
1386
|
+
same-viewBox svg over the source figure, in-memory only. */
|
|
1387
|
+
.anno-inline-wrap {
|
|
1388
|
+
position: relative;
|
|
1389
|
+
display: inline-block;
|
|
1390
|
+
max-width: 100%;
|
|
1391
|
+
}
|
|
1392
|
+
.anno-inline {
|
|
1393
|
+
position: absolute;
|
|
1394
|
+
inset: 0;
|
|
1395
|
+
width: 100%;
|
|
1396
|
+
height: 100%;
|
|
1397
|
+
pointer-events: none;
|
|
1398
|
+
}
|
|
1399
|
+
.lightbox-sep {
|
|
1400
|
+
width: 1px;
|
|
1401
|
+
align-self: stretch;
|
|
1402
|
+
margin: 4px 2px;
|
|
1403
|
+
background: rgba(255, 255, 255, 0.25);
|
|
1404
|
+
}
|
|
1405
|
+
/* Annotation overlay: absolute twin of the artwork, same viewBox, so shapes
|
|
1406
|
+
live in image coordinates and ride every zoom for free. Must undo the
|
|
1407
|
+
.lightbox-canvas > * block/white-background defaults. */
|
|
1408
|
+
.lightbox-canvas > .lightbox-anno {
|
|
1409
|
+
position: absolute;
|
|
1410
|
+
inset: 0;
|
|
1411
|
+
width: 100%;
|
|
1412
|
+
height: 100%;
|
|
1413
|
+
background: transparent;
|
|
1414
|
+
pointer-events: none;
|
|
1415
|
+
}
|
|
1416
|
+
.lightbox-stage[data-anno-cursor="draw"],
|
|
1417
|
+
.lightbox-stage[data-anno-cursor="draw"] .lightbox-anno { cursor: crosshair; }
|
|
1418
|
+
.lightbox-anno g[data-anno-id] { cursor: move; }
|
|
1419
|
+
.lightbox-anno [data-anno-handle] { cursor: nwse-resize; }
|
|
1367
1420
|
.lightbox-zoom-value {
|
|
1368
1421
|
min-width: 56px;
|
|
1369
1422
|
text-align: center;
|
|
@@ -1384,7 +1437,7 @@ const html = `<!DOCTYPE html>
|
|
|
1384
1437
|
overscroll-behavior: contain;
|
|
1385
1438
|
}
|
|
1386
1439
|
.lightbox-stage[data-panning] { cursor: grabbing; }
|
|
1387
|
-
.lightbox-canvas { margin: 0 auto; }
|
|
1440
|
+
.lightbox-canvas { margin: 0 auto; position: relative; }
|
|
1388
1441
|
/* Sized in px by the runtime; the child follows, so the scroll extent grows
|
|
1389
1442
|
with the zoom. A CSS transform would scale the pixels and leave the scroll
|
|
1390
1443
|
area at the original size, stranding the edges out of reach. */
|
|
@@ -2158,6 +2211,29 @@ ${mermaidInitTag}` : ''}
|
|
|
2158
2211
|
var lightboxNaturalH = 1;
|
|
2159
2212
|
var lightboxReturnScroll = 0;
|
|
2160
2213
|
|
|
2214
|
+
// Annotation layer state. Shapes are keyed per source node in memory only:
|
|
2215
|
+
// close/reopen keeps them, reload starts clean (they are reading-session
|
|
2216
|
+
// scratch, and localStorage keys would go stale when the doc regenerates).
|
|
2217
|
+
var ANNO_NS = 'http://www.w3.org/2000/svg';
|
|
2218
|
+
var ANNO_COLORS = ['#ef4444', '#3b82f6', '#22c55e', '#f59e0b', '#111827'];
|
|
2219
|
+
var ANNO_WIDTHS = [0.6, 1, 1.8];
|
|
2220
|
+
var annoStore = (typeof WeakMap === 'function') ? new WeakMap() : null;
|
|
2221
|
+
var annoShapes = [];
|
|
2222
|
+
var annoSvg = null;
|
|
2223
|
+
var annoMode = null; // null | 'f' | 'e' | 'r' | 'l' | 'a' | 'm'
|
|
2224
|
+
var annoSelected = null;
|
|
2225
|
+
var annoDraft = null;
|
|
2226
|
+
var annoUndoStack = [];
|
|
2227
|
+
var annoRedoStack = [];
|
|
2228
|
+
var annoStrokeW = 3;
|
|
2229
|
+
var annoColor = ANNO_COLORS[0];
|
|
2230
|
+
var annoWidthScale = 1;
|
|
2231
|
+
var annoToolButtons = {};
|
|
2232
|
+
var annoStyleButtons = [];
|
|
2233
|
+
var annoSourceNode = null;
|
|
2234
|
+
var annoCloneEl = null;
|
|
2235
|
+
var annoBaseSrc = null;
|
|
2236
|
+
|
|
2161
2237
|
function lightboxIsOpen() {
|
|
2162
2238
|
return !!lightboxEl && !lightboxEl.hidden;
|
|
2163
2239
|
}
|
|
@@ -2209,6 +2285,518 @@ ${mermaidInitTag}` : ''}
|
|
|
2209
2285
|
setLightboxZoom(lightboxZoom * factor, anchorX, anchorY);
|
|
2210
2286
|
}
|
|
2211
2287
|
|
|
2288
|
+
// ── Lightbox annotation layer ─────────────────────────────────────────────
|
|
2289
|
+
function annoGeomOf(shape) {
|
|
2290
|
+
if (shape.type === 'path') return { pts: shape.pts.map(function (p) { return [p[0], p[1]]; }) };
|
|
2291
|
+
var geom = {};
|
|
2292
|
+
Object.keys(shape).forEach(function (k) { if (k !== 'type') geom[k] = shape[k]; });
|
|
2293
|
+
return geom;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
function annoSetGeom(shape, geom) {
|
|
2297
|
+
if (shape.type === 'path') shape.pts = geom.pts.map(function (p) { return [p[0], p[1]]; });
|
|
2298
|
+
else Object.keys(geom).forEach(function (k) { shape[k] = geom[k]; });
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
function annoBBox(shape) {
|
|
2302
|
+
if (shape.type === 'rect') return { x: shape.x, y: shape.y, w: shape.w, h: shape.h };
|
|
2303
|
+
if (shape.type === 'ellipse') return { x: shape.cx - shape.rx, y: shape.cy - shape.ry, w: shape.rx * 2, h: shape.ry * 2 };
|
|
2304
|
+
if (shape.type === 'line' || shape.type === 'arrow') {
|
|
2305
|
+
return {
|
|
2306
|
+
x: Math.min(shape.x1, shape.x2), y: Math.min(shape.y1, shape.y2),
|
|
2307
|
+
w: Math.abs(shape.x2 - shape.x1), h: Math.abs(shape.y2 - shape.y1),
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
var xs = shape.pts.map(function (p) { return p[0]; });
|
|
2311
|
+
var ys = shape.pts.map(function (p) { return p[1]; });
|
|
2312
|
+
var x = Math.min.apply(null, xs);
|
|
2313
|
+
var y = Math.min.apply(null, ys);
|
|
2314
|
+
return { x: x, y: y, w: Math.max.apply(null, xs) - x, h: Math.max.apply(null, ys) - y };
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function annoApplyBBox(shape, from, to) {
|
|
2318
|
+
var sx = from.w > 0.01 ? to.w / from.w : 1;
|
|
2319
|
+
var sy = from.h > 0.01 ? to.h / from.h : 1;
|
|
2320
|
+
if (shape.type === 'rect') {
|
|
2321
|
+
shape.x = to.x; shape.y = to.y; shape.w = to.w; shape.h = to.h;
|
|
2322
|
+
} else if (shape.type === 'ellipse') {
|
|
2323
|
+
shape.cx = to.x + to.w / 2; shape.cy = to.y + to.h / 2;
|
|
2324
|
+
shape.rx = to.w / 2; shape.ry = to.h / 2;
|
|
2325
|
+
} else if (shape.type === 'line' || shape.type === 'arrow') {
|
|
2326
|
+
var x1 = to.x + (shape.x1 - from.x) * sx;
|
|
2327
|
+
var y1 = to.y + (shape.y1 - from.y) * sy;
|
|
2328
|
+
var x2 = to.x + (shape.x2 - from.x) * sx;
|
|
2329
|
+
var y2 = to.y + (shape.y2 - from.y) * sy;
|
|
2330
|
+
shape.x1 = x1; shape.y1 = y1; shape.x2 = x2; shape.y2 = y2;
|
|
2331
|
+
} else {
|
|
2332
|
+
shape.pts = shape.pts.map(function (p) {
|
|
2333
|
+
return [to.x + (p[0] - from.x) * sx, to.y + (p[1] - from.y) * sy];
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
function annoPathD(pts) {
|
|
2339
|
+
return pts.map(function (p, i) {
|
|
2340
|
+
return (i === 0 ? 'M' : 'L') + p[0].toFixed(1) + ' ' + p[1].toFixed(1);
|
|
2341
|
+
}).join(' ');
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
function annoShapeEl(shape) {
|
|
2345
|
+
var tag = (shape.type === 'line' || shape.type === 'arrow') ? 'line' : shape.type;
|
|
2346
|
+
var el = document.createElementNS(ANNO_NS, tag);
|
|
2347
|
+
var color = shape.color || ANNO_COLORS[0];
|
|
2348
|
+
if (shape.type === 'line' || shape.type === 'arrow') {
|
|
2349
|
+
el.setAttribute('x1', shape.x1.toFixed(1)); el.setAttribute('y1', shape.y1.toFixed(1));
|
|
2350
|
+
el.setAttribute('x2', shape.x2.toFixed(1)); el.setAttribute('y2', shape.y2.toFixed(1));
|
|
2351
|
+
el.setAttribute('stroke-linecap', 'round');
|
|
2352
|
+
if (shape.type === 'arrow') el.setAttribute('marker-end', 'url(#anno-arrow-' + color.slice(1) + ')');
|
|
2353
|
+
} else if (shape.type === 'rect') {
|
|
2354
|
+
el.setAttribute('x', shape.x.toFixed(1)); el.setAttribute('y', shape.y.toFixed(1));
|
|
2355
|
+
el.setAttribute('width', Math.max(0.1, shape.w).toFixed(1));
|
|
2356
|
+
el.setAttribute('height', Math.max(0.1, shape.h).toFixed(1));
|
|
2357
|
+
el.setAttribute('fill', 'transparent');
|
|
2358
|
+
} else if (shape.type === 'ellipse') {
|
|
2359
|
+
el.setAttribute('cx', shape.cx.toFixed(1)); el.setAttribute('cy', shape.cy.toFixed(1));
|
|
2360
|
+
el.setAttribute('rx', Math.max(0.1, shape.rx).toFixed(1));
|
|
2361
|
+
el.setAttribute('ry', Math.max(0.1, shape.ry).toFixed(1));
|
|
2362
|
+
el.setAttribute('fill', 'transparent');
|
|
2363
|
+
} else {
|
|
2364
|
+
el.setAttribute('d', annoPathD(shape.pts));
|
|
2365
|
+
el.setAttribute('fill', 'none');
|
|
2366
|
+
el.setAttribute('stroke-linejoin', 'round');
|
|
2367
|
+
el.setAttribute('stroke-linecap', 'round');
|
|
2368
|
+
}
|
|
2369
|
+
el.setAttribute('stroke', color);
|
|
2370
|
+
el.setAttribute('stroke-width', (annoStrokeW * (shape.ws || 1)).toFixed(1));
|
|
2371
|
+
return el;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
function annoEnsureDefs(svg) {
|
|
2375
|
+
var defs = document.createElementNS(ANNO_NS, 'defs');
|
|
2376
|
+
ANNO_COLORS.forEach(function (color) {
|
|
2377
|
+
var marker = document.createElementNS(ANNO_NS, 'marker');
|
|
2378
|
+
// Same id + content in every overlay svg, so document-wide url(#) lookups
|
|
2379
|
+
// always resolve to an identical marker.
|
|
2380
|
+
marker.setAttribute('id', 'anno-arrow-' + color.slice(1));
|
|
2381
|
+
marker.setAttribute('markerWidth', '8');
|
|
2382
|
+
marker.setAttribute('markerHeight', '8');
|
|
2383
|
+
marker.setAttribute('refX', '6.4');
|
|
2384
|
+
marker.setAttribute('refY', '3');
|
|
2385
|
+
marker.setAttribute('orient', 'auto');
|
|
2386
|
+
var tip = document.createElementNS(ANNO_NS, 'path');
|
|
2387
|
+
tip.setAttribute('d', 'M0 0 L7 3 L0 6 Z');
|
|
2388
|
+
tip.setAttribute('fill', color);
|
|
2389
|
+
marker.appendChild(tip);
|
|
2390
|
+
defs.appendChild(marker);
|
|
2391
|
+
});
|
|
2392
|
+
svg.appendChild(defs);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
function annoRedraw() {
|
|
2396
|
+
if (!annoSvg) return;
|
|
2397
|
+
while (annoSvg.firstChild) annoSvg.removeChild(annoSvg.firstChild);
|
|
2398
|
+
annoEnsureDefs(annoSvg);
|
|
2399
|
+
annoShapes.forEach(function (shape, index) {
|
|
2400
|
+
var g = document.createElementNS(ANNO_NS, 'g');
|
|
2401
|
+
g.setAttribute('data-anno-id', String(index));
|
|
2402
|
+
g.appendChild(annoShapeEl(shape));
|
|
2403
|
+
if (shape.type === 'path' || shape.type === 'line' || shape.type === 'arrow') {
|
|
2404
|
+
// A 3px stroke is an unclickable target — give strokes a fat invisible twin.
|
|
2405
|
+
var hit = document.createElementNS(ANNO_NS, shape.type === 'path' ? 'path' : 'line');
|
|
2406
|
+
if (shape.type === 'path') {
|
|
2407
|
+
hit.setAttribute('d', annoPathD(shape.pts));
|
|
2408
|
+
} else {
|
|
2409
|
+
hit.setAttribute('x1', shape.x1); hit.setAttribute('y1', shape.y1);
|
|
2410
|
+
hit.setAttribute('x2', shape.x2); hit.setAttribute('y2', shape.y2);
|
|
2411
|
+
}
|
|
2412
|
+
hit.setAttribute('fill', 'none');
|
|
2413
|
+
hit.setAttribute('stroke', 'transparent');
|
|
2414
|
+
hit.setAttribute('stroke-width', String(annoStrokeW * 5));
|
|
2415
|
+
hit.setAttribute('data-anno-hit', '');
|
|
2416
|
+
g.appendChild(hit);
|
|
2417
|
+
}
|
|
2418
|
+
annoSvg.appendChild(g);
|
|
2419
|
+
});
|
|
2420
|
+
if (annoSelected && annoShapes.indexOf(annoSelected) !== -1) {
|
|
2421
|
+
var box = annoBBox(annoSelected);
|
|
2422
|
+
var ui = document.createElementNS(ANNO_NS, 'rect');
|
|
2423
|
+
ui.setAttribute('data-anno-ui', '');
|
|
2424
|
+
ui.setAttribute('x', box.x); ui.setAttribute('y', box.y);
|
|
2425
|
+
ui.setAttribute('width', Math.max(0.1, box.w)); ui.setAttribute('height', Math.max(0.1, box.h));
|
|
2426
|
+
ui.setAttribute('fill', 'none');
|
|
2427
|
+
ui.setAttribute('stroke', '#3b82f6');
|
|
2428
|
+
ui.setAttribute('stroke-width', String(Math.max(1, annoStrokeW / 2)));
|
|
2429
|
+
ui.setAttribute('stroke-dasharray', (annoStrokeW * 2) + ' ' + annoStrokeW);
|
|
2430
|
+
annoSvg.appendChild(ui);
|
|
2431
|
+
var hs = annoStrokeW * 3;
|
|
2432
|
+
[[box.x, box.y], [box.x + box.w, box.y], [box.x, box.y + box.h], [box.x + box.w, box.y + box.h]]
|
|
2433
|
+
.forEach(function (corner, i) {
|
|
2434
|
+
var h = document.createElementNS(ANNO_NS, 'rect');
|
|
2435
|
+
h.setAttribute('data-anno-ui', '');
|
|
2436
|
+
h.setAttribute('data-anno-handle', String(i));
|
|
2437
|
+
h.setAttribute('x', corner[0] - hs / 2); h.setAttribute('y', corner[1] - hs / 2);
|
|
2438
|
+
h.setAttribute('width', hs); h.setAttribute('height', hs);
|
|
2439
|
+
h.setAttribute('fill', '#3b82f6');
|
|
2440
|
+
annoSvg.appendChild(h);
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
function annoPushOp(op) {
|
|
2446
|
+
annoUndoStack.push(op);
|
|
2447
|
+
annoRedoStack.length = 0;
|
|
2448
|
+
annoBake();
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
function annoApplyOp(op, reverse) {
|
|
2452
|
+
if (op.kind === 'add') {
|
|
2453
|
+
if (reverse) annoShapes.splice(annoShapes.indexOf(op.shape), 1);
|
|
2454
|
+
else annoShapes.splice(Math.min(op.index, annoShapes.length), 0, op.shape);
|
|
2455
|
+
} else if (op.kind === 'del') {
|
|
2456
|
+
if (reverse) annoShapes.splice(Math.min(op.index, annoShapes.length), 0, op.shape);
|
|
2457
|
+
else annoShapes.splice(annoShapes.indexOf(op.shape), 1);
|
|
2458
|
+
} else if (op.kind === 'geom') {
|
|
2459
|
+
annoSetGeom(op.shape, reverse ? op.before : op.after);
|
|
2460
|
+
} else if (op.kind === 'style') {
|
|
2461
|
+
var style = reverse ? op.before : op.after;
|
|
2462
|
+
op.shape.color = style.color;
|
|
2463
|
+
op.shape.ws = style.ws;
|
|
2464
|
+
} else if (op.kind === 'clear') {
|
|
2465
|
+
if (reverse) op.shapes.forEach(function (s) { annoShapes.push(s); });
|
|
2466
|
+
else annoShapes.length = 0;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
function annoUndo() {
|
|
2471
|
+
var op = annoUndoStack.pop();
|
|
2472
|
+
if (!op) return;
|
|
2473
|
+
annoApplyOp(op, true);
|
|
2474
|
+
annoRedoStack.push(op);
|
|
2475
|
+
annoSelected = null;
|
|
2476
|
+
annoRedraw();
|
|
2477
|
+
annoBake();
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
function annoRedo() {
|
|
2481
|
+
var op = annoRedoStack.pop();
|
|
2482
|
+
if (!op) return;
|
|
2483
|
+
annoApplyOp(op, false);
|
|
2484
|
+
annoUndoStack.push(op);
|
|
2485
|
+
annoSelected = null;
|
|
2486
|
+
annoRedraw();
|
|
2487
|
+
annoBake();
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
function annoClearAll() {
|
|
2491
|
+
if (!annoShapes.length) return;
|
|
2492
|
+
annoPushOp({ kind: 'clear', shapes: annoShapes.slice() });
|
|
2493
|
+
annoShapes.length = 0;
|
|
2494
|
+
annoSelected = null;
|
|
2495
|
+
annoRedraw();
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
function annoDeleteSelected() {
|
|
2499
|
+
if (!annoSelected) return;
|
|
2500
|
+
var index = annoShapes.indexOf(annoSelected);
|
|
2501
|
+
if (index === -1) return;
|
|
2502
|
+
annoPushOp({ kind: 'del', shape: annoSelected, index: index });
|
|
2503
|
+
annoShapes.splice(index, 1);
|
|
2504
|
+
annoSelected = null;
|
|
2505
|
+
annoRedraw();
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
function setAnnoStyle(patch) {
|
|
2509
|
+
if (patch.color) annoColor = patch.color;
|
|
2510
|
+
if (patch.ws) annoWidthScale = patch.ws;
|
|
2511
|
+
annoStyleButtons.forEach(function (entry) {
|
|
2512
|
+
entry.button.classList.toggle('is-active',
|
|
2513
|
+
entry.kind === 'color' ? entry.value === annoColor : entry.value === annoWidthScale);
|
|
2514
|
+
});
|
|
2515
|
+
// With a selection, the pickers restyle it (undoably); otherwise they only
|
|
2516
|
+
// set the style for the next shape.
|
|
2517
|
+
if (annoSelected) {
|
|
2518
|
+
var beforeStyle = { color: annoSelected.color, ws: annoSelected.ws };
|
|
2519
|
+
annoSelected.color = patch.color || annoSelected.color;
|
|
2520
|
+
annoSelected.ws = patch.ws || annoSelected.ws;
|
|
2521
|
+
annoPushOp({
|
|
2522
|
+
kind: 'style', shape: annoSelected,
|
|
2523
|
+
before: beforeStyle,
|
|
2524
|
+
after: { color: annoSelected.color, ws: annoSelected.ws },
|
|
2525
|
+
});
|
|
2526
|
+
annoRedraw();
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// Re-bake the shown raster clone (image + shapes composited to a PNG data
|
|
2531
|
+
// URI) so a native right-click "Copy image" carries the annotations.
|
|
2532
|
+
function annoShapesSvgMarkup() {
|
|
2533
|
+
var svg = document.createElementNS(ANNO_NS, 'svg');
|
|
2534
|
+
svg.setAttribute('xmlns', ANNO_NS);
|
|
2535
|
+
svg.setAttribute('viewBox', '0 0 ' + lightboxNaturalW + ' ' + lightboxNaturalH);
|
|
2536
|
+
svg.setAttribute('width', lightboxNaturalW);
|
|
2537
|
+
svg.setAttribute('height', lightboxNaturalH);
|
|
2538
|
+
annoEnsureDefs(svg);
|
|
2539
|
+
annoShapes.forEach(function (shape) { svg.appendChild(annoShapeEl(shape)); });
|
|
2540
|
+
return new XMLSerializer().serializeToString(svg);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
function annoComposite(drawBase, scale, done) {
|
|
2544
|
+
var canvas = document.createElement('canvas');
|
|
2545
|
+
canvas.width = lightboxNaturalW * scale;
|
|
2546
|
+
canvas.height = lightboxNaturalH * scale;
|
|
2547
|
+
var ctx = canvas.getContext('2d');
|
|
2548
|
+
drawBase(ctx, canvas, function () {
|
|
2549
|
+
if (!annoShapes.length) { done(canvas); return; }
|
|
2550
|
+
var overlay = new Image();
|
|
2551
|
+
overlay.onload = function () {
|
|
2552
|
+
ctx.drawImage(overlay, 0, 0, canvas.width, canvas.height);
|
|
2553
|
+
done(canvas);
|
|
2554
|
+
};
|
|
2555
|
+
overlay.onerror = function () { done(canvas); };
|
|
2556
|
+
overlay.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(annoShapesSvgMarkup());
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
function annoBake() {
|
|
2561
|
+
if (!annoCloneEl || annoCloneEl.tagName !== 'IMG' || !annoBaseSrc) return;
|
|
2562
|
+
var expected = annoCloneEl;
|
|
2563
|
+
annoComposite(function (ctx, canvas, next) {
|
|
2564
|
+
var base = new Image();
|
|
2565
|
+
base.onload = function () { ctx.drawImage(base, 0, 0, canvas.width, canvas.height); next(); };
|
|
2566
|
+
base.onerror = function () { next(); };
|
|
2567
|
+
base.src = annoBaseSrc;
|
|
2568
|
+
}, 1, function (canvas) {
|
|
2569
|
+
if (annoCloneEl !== expected) return; // lightbox moved on to another image
|
|
2570
|
+
try { annoCloneEl.src = canvas.toDataURL('image/png'); }
|
|
2571
|
+
catch (e) { /* tainted canvas — keep the plain image */ }
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
function annoCopyImage() {
|
|
2576
|
+
var clone = annoCloneEl;
|
|
2577
|
+
if (!clone) return;
|
|
2578
|
+
var isImg = clone.tagName === 'IMG';
|
|
2579
|
+
annoComposite(function (ctx, canvas, next) {
|
|
2580
|
+
ctx.fillStyle = '#ffffff';
|
|
2581
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
2582
|
+
var base = new Image();
|
|
2583
|
+
base.onload = function () { ctx.drawImage(base, 0, 0, canvas.width, canvas.height); next(); };
|
|
2584
|
+
base.onerror = function () { next(); };
|
|
2585
|
+
base.src = isImg ? (annoBaseSrc || clone.src)
|
|
2586
|
+
: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(new XMLSerializer().serializeToString(clone));
|
|
2587
|
+
}, isImg ? 1 : 2, function (canvas) {
|
|
2588
|
+
try {
|
|
2589
|
+
canvas.toBlob(function (blob) {
|
|
2590
|
+
if (!blob || typeof ClipboardItem === 'undefined' || !navigator.clipboard || !navigator.clipboard.write) return;
|
|
2591
|
+
navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]).catch(function () {});
|
|
2592
|
+
}, 'image/png');
|
|
2593
|
+
} catch (e) { /* clipboard unavailable */ }
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// After Esc the drawings stay visible on the inline figure: a same-viewBox
|
|
2598
|
+
// overlay svg over the source element. In-memory only — gone on reload.
|
|
2599
|
+
function annoSyncInline() {
|
|
2600
|
+
var node = annoSourceNode;
|
|
2601
|
+
if (!node) return;
|
|
2602
|
+
var visual = lightboxSourceOf(node);
|
|
2603
|
+
if (!visual || !visual.parentNode) return;
|
|
2604
|
+
var wrap = visual.closest ? visual.closest('.anno-inline-wrap') : null;
|
|
2605
|
+
var shapes = annoShapes;
|
|
2606
|
+
if (!shapes.length) {
|
|
2607
|
+
if (wrap) {
|
|
2608
|
+
var old = wrap.querySelector('svg.anno-inline');
|
|
2609
|
+
if (old) wrap.removeChild(old);
|
|
2610
|
+
}
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
if (!wrap) {
|
|
2614
|
+
wrap = document.createElement('span');
|
|
2615
|
+
wrap.className = 'anno-inline-wrap';
|
|
2616
|
+
visual.parentNode.insertBefore(wrap, visual);
|
|
2617
|
+
wrap.appendChild(visual);
|
|
2618
|
+
}
|
|
2619
|
+
var overlay = wrap.querySelector('svg.anno-inline');
|
|
2620
|
+
if (!overlay) {
|
|
2621
|
+
overlay = document.createElementNS(ANNO_NS, 'svg');
|
|
2622
|
+
overlay.setAttribute('class', 'anno-inline');
|
|
2623
|
+
wrap.appendChild(overlay);
|
|
2624
|
+
}
|
|
2625
|
+
overlay.setAttribute('viewBox', '0 0 ' + lightboxNaturalW + ' ' + lightboxNaturalH);
|
|
2626
|
+
while (overlay.firstChild) overlay.removeChild(overlay.firstChild);
|
|
2627
|
+
annoEnsureDefs(overlay);
|
|
2628
|
+
shapes.forEach(function (shape) { overlay.appendChild(annoShapeEl(shape)); });
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
function setAnnoMode(mode) {
|
|
2632
|
+
annoMode = (annoMode === mode) ? null : mode;
|
|
2633
|
+
if (annoMode !== 'm') annoSelected = null;
|
|
2634
|
+
Object.keys(annoToolButtons).forEach(function (key) {
|
|
2635
|
+
annoToolButtons[key].classList.toggle('is-active', key === annoMode);
|
|
2636
|
+
});
|
|
2637
|
+
if (annoSvg) annoSvg.style.pointerEvents = annoMode ? 'auto' : 'none';
|
|
2638
|
+
if (lightboxStage) {
|
|
2639
|
+
if (annoMode === 'f' || annoMode === 'e' || annoMode === 'r') {
|
|
2640
|
+
lightboxStage.setAttribute('data-anno-cursor', 'draw');
|
|
2641
|
+
} else {
|
|
2642
|
+
lightboxStage.removeAttribute('data-anno-cursor');
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
annoRedraw();
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
function annoPoint(event) {
|
|
2649
|
+
var rect = annoSvg.getBoundingClientRect();
|
|
2650
|
+
var scale = rect.width > 0 ? lightboxNaturalW / rect.width : 1;
|
|
2651
|
+
return { x: (event.clientX - rect.left) * scale, y: (event.clientY - rect.top) * scale };
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
function annoCancelDraft() {
|
|
2655
|
+
if (!annoDraft) return;
|
|
2656
|
+
if (annoDraft.el && annoDraft.el.parentNode) annoDraft.el.parentNode.removeChild(annoDraft.el);
|
|
2657
|
+
annoDraft = null;
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
function annoPointerDown(event) {
|
|
2661
|
+
if (!annoMode || event.button !== 0) return;
|
|
2662
|
+
event.preventDefault();
|
|
2663
|
+
event.stopPropagation();
|
|
2664
|
+
if (annoSvg.setPointerCapture) {
|
|
2665
|
+
try { annoSvg.setPointerCapture(event.pointerId); } catch (e) { /* detached */ }
|
|
2666
|
+
}
|
|
2667
|
+
var pt = annoPoint(event);
|
|
2668
|
+
if (annoMode === 'm') {
|
|
2669
|
+
var handle = event.target.closest ? event.target.closest('[data-anno-handle]') : null;
|
|
2670
|
+
var group = event.target.closest ? event.target.closest('g[data-anno-id]') : null;
|
|
2671
|
+
if (handle && annoSelected) {
|
|
2672
|
+
var corner = Number(handle.getAttribute('data-anno-handle'));
|
|
2673
|
+
var box = annoBBox(annoSelected);
|
|
2674
|
+
annoDraft = {
|
|
2675
|
+
kind: 'resize', shape: annoSelected, corner: corner,
|
|
2676
|
+
startBox: box, before: annoGeomOf(annoSelected), moved: false,
|
|
2677
|
+
anchor: {
|
|
2678
|
+
x: (corner === 0 || corner === 2) ? box.x + box.w : box.x,
|
|
2679
|
+
y: (corner === 0 || corner === 1) ? box.y + box.h : box.y,
|
|
2680
|
+
},
|
|
2681
|
+
};
|
|
2682
|
+
} else if (group) {
|
|
2683
|
+
annoSelected = annoShapes[Number(group.getAttribute('data-anno-id'))] || null;
|
|
2684
|
+
annoDraft = annoSelected ? {
|
|
2685
|
+
kind: 'move', shape: annoSelected, start: pt,
|
|
2686
|
+
startBox: annoBBox(annoSelected), before: annoGeomOf(annoSelected), moved: false,
|
|
2687
|
+
} : null;
|
|
2688
|
+
annoRedraw();
|
|
2689
|
+
} else {
|
|
2690
|
+
annoSelected = null;
|
|
2691
|
+
annoRedraw();
|
|
2692
|
+
}
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
var shape;
|
|
2696
|
+
if (annoMode === 'f') shape = { type: 'path', pts: [[pt.x, pt.y]] };
|
|
2697
|
+
else if (annoMode === 'e') shape = { type: 'ellipse', cx: pt.x, cy: pt.y, rx: 0, ry: 0 };
|
|
2698
|
+
else if (annoMode === 'l') shape = { type: 'line', x1: pt.x, y1: pt.y, x2: pt.x, y2: pt.y };
|
|
2699
|
+
else if (annoMode === 'a') shape = { type: 'arrow', x1: pt.x, y1: pt.y, x2: pt.x, y2: pt.y };
|
|
2700
|
+
else shape = { type: 'rect', x: pt.x, y: pt.y, w: 0, h: 0 };
|
|
2701
|
+
shape.color = annoColor;
|
|
2702
|
+
shape.ws = annoWidthScale;
|
|
2703
|
+
annoDraft = { kind: 'draw', shape: shape, start: pt, el: annoShapeEl(shape), clientDist: 0, lastClient: [event.clientX, event.clientY] };
|
|
2704
|
+
annoSvg.appendChild(annoDraft.el);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
function annoUpdateDraftEl() {
|
|
2708
|
+
var d = annoDraft;
|
|
2709
|
+
var fresh = annoShapeEl(d.shape);
|
|
2710
|
+
d.el.parentNode.replaceChild(fresh, d.el);
|
|
2711
|
+
d.el = fresh;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
function annoPointerMove(event) {
|
|
2715
|
+
if (!annoDraft) return;
|
|
2716
|
+
var pt = annoPoint(event);
|
|
2717
|
+
var d = annoDraft;
|
|
2718
|
+
if (d.kind === 'draw') {
|
|
2719
|
+
d.clientDist += Math.abs(event.clientX - d.lastClient[0]) + Math.abs(event.clientY - d.lastClient[1]);
|
|
2720
|
+
d.lastClient = [event.clientX, event.clientY];
|
|
2721
|
+
if (d.shape.type === 'path') {
|
|
2722
|
+
d.shape.pts.push([pt.x, pt.y]);
|
|
2723
|
+
} else if (d.shape.type === 'line' || d.shape.type === 'arrow') {
|
|
2724
|
+
d.shape.x2 = pt.x; d.shape.y2 = pt.y;
|
|
2725
|
+
} else if (d.shape.type === 'ellipse') {
|
|
2726
|
+
d.shape.cx = (d.start.x + pt.x) / 2; d.shape.cy = (d.start.y + pt.y) / 2;
|
|
2727
|
+
d.shape.rx = Math.abs(pt.x - d.start.x) / 2; d.shape.ry = Math.abs(pt.y - d.start.y) / 2;
|
|
2728
|
+
} else {
|
|
2729
|
+
d.shape.x = Math.min(d.start.x, pt.x); d.shape.y = Math.min(d.start.y, pt.y);
|
|
2730
|
+
d.shape.w = Math.abs(pt.x - d.start.x); d.shape.h = Math.abs(pt.y - d.start.y);
|
|
2731
|
+
}
|
|
2732
|
+
annoUpdateDraftEl();
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
d.moved = true;
|
|
2736
|
+
if (d.kind === 'move') {
|
|
2737
|
+
var box = d.startBox;
|
|
2738
|
+
annoApplyBBox(d.shape, annoBBox(d.shape), {
|
|
2739
|
+
x: box.x + (pt.x - d.start.x), y: box.y + (pt.y - d.start.y), w: box.w, h: box.h,
|
|
2740
|
+
});
|
|
2741
|
+
} else {
|
|
2742
|
+
var ax = d.anchor.x;
|
|
2743
|
+
var ay = d.anchor.y;
|
|
2744
|
+
annoApplyBBox(d.shape, annoBBox(d.shape), {
|
|
2745
|
+
x: Math.min(ax, pt.x), y: Math.min(ay, pt.y),
|
|
2746
|
+
w: Math.max(1, Math.abs(pt.x - ax)), h: Math.max(1, Math.abs(pt.y - ay)),
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
annoRedraw();
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
function annoPointerUp() {
|
|
2753
|
+
if (!annoDraft) return;
|
|
2754
|
+
var d = annoDraft;
|
|
2755
|
+
annoDraft = null;
|
|
2756
|
+
if (d.kind === 'draw') {
|
|
2757
|
+
if (d.el.parentNode) d.el.parentNode.removeChild(d.el);
|
|
2758
|
+
var tooSmall = d.clientDist < 4 ||
|
|
2759
|
+
(d.shape.type === 'path' && d.shape.pts.length < 2);
|
|
2760
|
+
if (!tooSmall) {
|
|
2761
|
+
annoPushOp({ kind: 'add', shape: d.shape, index: annoShapes.length });
|
|
2762
|
+
annoShapes.push(d.shape);
|
|
2763
|
+
}
|
|
2764
|
+
annoRedraw();
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
if (d.moved) {
|
|
2768
|
+
annoPushOp({ kind: 'geom', shape: d.shape, before: d.before, after: annoGeomOf(d.shape) });
|
|
2769
|
+
}
|
|
2770
|
+
annoRedraw();
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
function annoSetup(sourceNode) {
|
|
2774
|
+
annoSourceNode = sourceNode;
|
|
2775
|
+
annoCloneEl = lightboxCanvas.firstElementChild;
|
|
2776
|
+
annoBaseSrc = (annoCloneEl && annoCloneEl.tagName === 'IMG') ? annoCloneEl.src : null;
|
|
2777
|
+
annoShapes = [];
|
|
2778
|
+
if (annoStore) {
|
|
2779
|
+
annoShapes = annoStore.get(sourceNode);
|
|
2780
|
+
if (!annoShapes) { annoShapes = []; annoStore.set(sourceNode, annoShapes); }
|
|
2781
|
+
}
|
|
2782
|
+
annoUndoStack = [];
|
|
2783
|
+
annoRedoStack = [];
|
|
2784
|
+
annoSelected = null;
|
|
2785
|
+
annoCancelDraft();
|
|
2786
|
+
annoStrokeW = Math.max(3, Math.round(Math.max(lightboxNaturalW, lightboxNaturalH) / 300));
|
|
2787
|
+
annoSvg = document.createElementNS(ANNO_NS, 'svg');
|
|
2788
|
+
annoSvg.setAttribute('class', 'lightbox-anno');
|
|
2789
|
+
annoSvg.setAttribute('viewBox', '0 0 ' + lightboxNaturalW + ' ' + lightboxNaturalH);
|
|
2790
|
+
annoSvg.addEventListener('pointerdown', annoPointerDown);
|
|
2791
|
+
annoSvg.addEventListener('pointermove', annoPointerMove);
|
|
2792
|
+
annoSvg.addEventListener('pointerup', annoPointerUp);
|
|
2793
|
+
annoSvg.addEventListener('pointercancel', annoPointerUp);
|
|
2794
|
+
lightboxCanvas.appendChild(annoSvg);
|
|
2795
|
+
annoMode = null;
|
|
2796
|
+
setAnnoMode(null);
|
|
2797
|
+
if (annoShapes.length) annoBake();
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2212
2800
|
function buildLightbox() {
|
|
2213
2801
|
if (lightboxEl) return;
|
|
2214
2802
|
lightboxEl = document.createElement('div');
|
|
@@ -2221,7 +2809,7 @@ ${mermaidInitTag}` : ''}
|
|
|
2221
2809
|
bar.className = 'lightbox-bar';
|
|
2222
2810
|
var hint = document.createElement('span');
|
|
2223
2811
|
hint.className = 'lightbox-hint';
|
|
2224
|
-
hint.textContent = 'scroll
|
|
2812
|
+
hint.textContent = 'ctrl+scroll zoom · drag pan · f/e/r/l/a draw · m select · del remove · ctrl+z undo · esc close';
|
|
2225
2813
|
lightboxZoomValue = document.createElement('span');
|
|
2226
2814
|
lightboxZoomValue.className = 'lightbox-zoom-value';
|
|
2227
2815
|
lightboxZoomValue.setAttribute('data-lightbox-zoom-value', '');
|
|
@@ -2231,6 +2819,44 @@ ${mermaidInitTag}` : ''}
|
|
|
2231
2819
|
bar.appendChild(lightboxButton('+', 'Zoom in', function () { zoomLightboxBy(LIGHTBOX_STEP); }));
|
|
2232
2820
|
bar.appendChild(lightboxButton('Fit', 'Fit to window', function () { setLightboxZoom(lightboxFitZoom); }));
|
|
2233
2821
|
bar.appendChild(lightboxButton('1:1', 'Actual size', function () { setLightboxZoom(1); }));
|
|
2822
|
+
var sep = document.createElement('span');
|
|
2823
|
+
sep.className = 'lightbox-sep';
|
|
2824
|
+
bar.appendChild(sep);
|
|
2825
|
+
[
|
|
2826
|
+
['f', '✎', 'Freehand (f)'],
|
|
2827
|
+
['e', '○', 'Ellipse (e)'],
|
|
2828
|
+
['r', '▭', 'Rectangle (r)'],
|
|
2829
|
+
['l', '╱', 'Line (l)'],
|
|
2830
|
+
['a', '↗', 'Arrow (a)'],
|
|
2831
|
+
['m', '✥', 'Select / move (m)'],
|
|
2832
|
+
].forEach(function (tool) {
|
|
2833
|
+
var button = lightboxButton(tool[1], tool[2], function () { setAnnoMode(tool[0]); });
|
|
2834
|
+
button.setAttribute('data-anno-tool', tool[0]);
|
|
2835
|
+
annoToolButtons[tool[0]] = button;
|
|
2836
|
+
bar.appendChild(button);
|
|
2837
|
+
});
|
|
2838
|
+
ANNO_COLORS.forEach(function (color) {
|
|
2839
|
+
var swatch = lightboxButton('', 'Stroke color ' + color, function () { setAnnoStyle({ color: color }); });
|
|
2840
|
+
swatch.className = 'lightbox-swatch';
|
|
2841
|
+
swatch.style.background = color;
|
|
2842
|
+
swatch.setAttribute('data-anno-color', color);
|
|
2843
|
+
if (color === annoColor) swatch.classList.add('is-active');
|
|
2844
|
+
annoStyleButtons.push({ kind: 'color', value: color, button: swatch });
|
|
2845
|
+
bar.appendChild(swatch);
|
|
2846
|
+
});
|
|
2847
|
+
[['S', ANNO_WIDTHS[0]], ['M', ANNO_WIDTHS[1]], ['L', ANNO_WIDTHS[2]]].forEach(function (width) {
|
|
2848
|
+
var button = lightboxButton(width[0], 'Stroke width ' + width[0], function () { setAnnoStyle({ ws: width[1] }); });
|
|
2849
|
+
button.setAttribute('data-anno-width', String(width[1]));
|
|
2850
|
+
if (width[1] === annoWidthScale) button.classList.add('is-active');
|
|
2851
|
+
annoStyleButtons.push({ kind: 'width', value: width[1], button: button });
|
|
2852
|
+
bar.appendChild(button);
|
|
2853
|
+
});
|
|
2854
|
+
var copyButton = lightboxButton('⧉', 'Copy image with annotations', annoCopyImage);
|
|
2855
|
+
copyButton.setAttribute('data-anno-copy', '');
|
|
2856
|
+
bar.appendChild(copyButton);
|
|
2857
|
+
var clearButton = lightboxButton('Clear', 'Clear all annotations', annoClearAll);
|
|
2858
|
+
clearButton.setAttribute('data-anno-clear', '');
|
|
2859
|
+
bar.appendChild(clearButton);
|
|
2234
2860
|
bar.appendChild(lightboxButton('✕', 'Close', closeLightbox));
|
|
2235
2861
|
|
|
2236
2862
|
lightboxStage = document.createElement('div');
|
|
@@ -2261,6 +2887,8 @@ ${mermaidInitTag}` : ''}
|
|
|
2261
2887
|
|
|
2262
2888
|
lightboxStage.addEventListener('mousedown', function (event) {
|
|
2263
2889
|
if (event.button !== 0) return;
|
|
2890
|
+
// With a tool armed the overlay owns pointer events over the artwork.
|
|
2891
|
+
if (annoMode && event.target && event.target.closest && event.target.closest('.lightbox-anno')) return;
|
|
2264
2892
|
panning = true;
|
|
2265
2893
|
panDistance = 0;
|
|
2266
2894
|
panX = event.clientX;
|
|
@@ -2291,6 +2919,7 @@ ${mermaidInitTag}` : ''}
|
|
|
2291
2919
|
});
|
|
2292
2920
|
lightboxStage.addEventListener('dblclick', function (event) {
|
|
2293
2921
|
event.preventDefault();
|
|
2922
|
+
if (annoMode && event.target && event.target.closest && event.target.closest('.lightbox-anno')) return;
|
|
2294
2923
|
setLightboxZoom(Math.abs(lightboxZoom - 1) < 0.001 ? lightboxFitZoom : 1);
|
|
2295
2924
|
});
|
|
2296
2925
|
}
|
|
@@ -2331,6 +2960,7 @@ ${mermaidInitTag}` : ''}
|
|
|
2331
2960
|
lightboxCanvas.removeChild(lightboxCanvas.firstChild);
|
|
2332
2961
|
}
|
|
2333
2962
|
lightboxCanvas.appendChild(clone);
|
|
2963
|
+
annoSetup(node);
|
|
2334
2964
|
|
|
2335
2965
|
lightboxReturnScroll = window.scrollY;
|
|
2336
2966
|
suppressAnchorCapture = true;
|
|
@@ -2347,6 +2977,14 @@ ${mermaidInitTag}` : ''}
|
|
|
2347
2977
|
if (!lightboxIsOpen()) return;
|
|
2348
2978
|
lightboxEl.hidden = true;
|
|
2349
2979
|
document.body.removeAttribute('data-lightbox-open');
|
|
2980
|
+
annoCancelDraft();
|
|
2981
|
+
annoSyncInline();
|
|
2982
|
+
annoSourceNode = null;
|
|
2983
|
+
annoCloneEl = null;
|
|
2984
|
+
annoBaseSrc = null;
|
|
2985
|
+
annoSvg = null;
|
|
2986
|
+
annoMode = null;
|
|
2987
|
+
setAnnoMode(null);
|
|
2350
2988
|
while (lightboxCanvas.firstChild) {
|
|
2351
2989
|
lightboxCanvas.removeChild(lightboxCanvas.firstChild);
|
|
2352
2990
|
}
|
|
@@ -2372,7 +3010,28 @@ ${mermaidInitTag}` : ''}
|
|
|
2372
3010
|
|
|
2373
3011
|
document.addEventListener('keydown', function (event) {
|
|
2374
3012
|
if (!lightboxIsOpen()) return;
|
|
2375
|
-
|
|
3013
|
+
var key = event.key.toLowerCase();
|
|
3014
|
+
if ((event.ctrlKey || event.metaKey) && key === 'z' && !event.shiftKey) {
|
|
3015
|
+
event.preventDefault(); annoUndo(); return;
|
|
3016
|
+
}
|
|
3017
|
+
if ((event.ctrlKey || event.metaKey) && (key === 'y' || (key === 'z' && event.shiftKey))) {
|
|
3018
|
+
event.preventDefault(); annoRedo(); return;
|
|
3019
|
+
}
|
|
3020
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
3021
|
+
if (event.key === 'Escape') {
|
|
3022
|
+
event.preventDefault();
|
|
3023
|
+
// Layered: cancel an in-progress draw, then drop the selection, then close.
|
|
3024
|
+
if (annoDraft) { annoCancelDraft(); annoRedraw(); return; }
|
|
3025
|
+
if (annoSelected) { annoSelected = null; annoRedraw(); return; }
|
|
3026
|
+
closeLightbox();
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
if (key === 'f' || key === 'e' || key === 'r' || key === 'l' || key === 'a' || key === 'm') {
|
|
3030
|
+
event.preventDefault(); setAnnoMode(key); return;
|
|
3031
|
+
}
|
|
3032
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
3033
|
+
event.preventDefault(); annoDeleteSelected(); return;
|
|
3034
|
+
}
|
|
2376
3035
|
if (event.key === '+' || event.key === '=') { event.preventDefault(); zoomLightboxBy(LIGHTBOX_STEP); return; }
|
|
2377
3036
|
if (event.key === '-' || event.key === '_') { event.preventDefault(); zoomLightboxBy(1 / LIGHTBOX_STEP); return; }
|
|
2378
3037
|
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.8.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/lightbox-anno-style.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",
|