instruckt 0.4.13 → 0.4.16
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/README.md +23 -3
- package/dist/instruckt.cjs.js +1196 -94
- package/dist/instruckt.cjs.js.map +1 -1
- package/dist/instruckt.d.mts +28 -8
- package/dist/instruckt.d.ts +28 -8
- package/dist/instruckt.esm.js +1196 -94
- package/dist/instruckt.esm.js.map +1 -1
- package/dist/instruckt.iife.js +99 -42
- package/dist/instruckt.iife.js.map +1 -1
- package/package.json +4 -1
package/dist/instruckt.esm.js
CHANGED
|
@@ -79,15 +79,6 @@ var InstrucktApi = class {
|
|
|
79
79
|
if (!res.ok) throw new Error(`instruckt: failed to update annotation (${res.status})`);
|
|
80
80
|
return toCamelCase(await res.json());
|
|
81
81
|
}
|
|
82
|
-
async addReply(annotationId, content, role = "human") {
|
|
83
|
-
const res = await fetch(`${this.endpoint}/annotations/${annotationId}/reply`, {
|
|
84
|
-
method: "POST",
|
|
85
|
-
headers: headers(),
|
|
86
|
-
body: JSON.stringify({ role, content })
|
|
87
|
-
});
|
|
88
|
-
if (!res.ok) throw new Error(`instruckt: failed to add reply (${res.status})`);
|
|
89
|
-
return toCamelCase(await res.json());
|
|
90
|
-
}
|
|
91
82
|
};
|
|
92
83
|
|
|
93
84
|
// src/ui/styles.ts
|
|
@@ -163,6 +154,24 @@ var TOOLBAR_CSS = (
|
|
|
163
154
|
}
|
|
164
155
|
.btn svg { display: block; }
|
|
165
156
|
.btn:hover { background: var(--ik-bg2); color: var(--ik-text); }
|
|
157
|
+
.btn[data-tooltip]::before {
|
|
158
|
+
content: attr(data-tooltip);
|
|
159
|
+
position: absolute;
|
|
160
|
+
right: calc(100% + 8px);
|
|
161
|
+
top: 50%;
|
|
162
|
+
transform: translateY(-50%);
|
|
163
|
+
white-space: nowrap;
|
|
164
|
+
font-size: 11px;
|
|
165
|
+
padding: 4px 8px;
|
|
166
|
+
border-radius: 6px;
|
|
167
|
+
background: var(--ik-text);
|
|
168
|
+
color: var(--ik-bg);
|
|
169
|
+
pointer-events: none;
|
|
170
|
+
opacity: 0;
|
|
171
|
+
transition: opacity .1s ease;
|
|
172
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
173
|
+
}
|
|
174
|
+
.btn[data-tooltip]:hover::before { opacity: 1; }
|
|
166
175
|
.btn.active { background: var(--ik-accent); color: #fff; }
|
|
167
176
|
.btn.active:hover { background: var(--ik-accent-h); }
|
|
168
177
|
|
|
@@ -202,24 +211,7 @@ var TOOLBAR_CSS = (
|
|
|
202
211
|
box-shadow: var(--ik-shadow);
|
|
203
212
|
border-radius: 8px;
|
|
204
213
|
}
|
|
205
|
-
/*
|
|
206
|
-
.clear-all-btn::before {
|
|
207
|
-
content: attr(data-tooltip);
|
|
208
|
-
position: absolute;
|
|
209
|
-
right: calc(100% + 6px);
|
|
210
|
-
top: 50%;
|
|
211
|
-
transform: translateY(-50%);
|
|
212
|
-
white-space: nowrap;
|
|
213
|
-
font-size: 11px;
|
|
214
|
-
padding: 4px 8px;
|
|
215
|
-
border-radius: 6px;
|
|
216
|
-
background: var(--ik-text);
|
|
217
|
-
color: var(--ik-bg);
|
|
218
|
-
pointer-events: none;
|
|
219
|
-
opacity: 0;
|
|
220
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
221
|
-
}
|
|
222
|
-
.clear-all-btn:hover::before { opacity: 1; }
|
|
214
|
+
/* clear-all tooltip inherits from .btn[data-tooltip]::before */
|
|
223
215
|
/* Invisible bridge so hover doesn't break crossing the gap */
|
|
224
216
|
.clear-all-btn::after {
|
|
225
217
|
content: '';
|
|
@@ -358,6 +350,61 @@ var POPUP_CSS = (
|
|
|
358
350
|
.chip.important.sel { background:#f97316; border-color:#f97316; }
|
|
359
351
|
.chip.suggestion.sel{ background:#22c55e; border-color:#22c55e; }
|
|
360
352
|
|
|
353
|
+
.screenshot-slot { margin-bottom: 10px; }
|
|
354
|
+
|
|
355
|
+
.btn-capture {
|
|
356
|
+
display: flex;
|
|
357
|
+
align-items: center;
|
|
358
|
+
gap: 6px;
|
|
359
|
+
width: 100%;
|
|
360
|
+
padding: 8px 10px;
|
|
361
|
+
border: 1px dashed var(--ik-border);
|
|
362
|
+
border-radius: 6px;
|
|
363
|
+
background: var(--ik-bg2);
|
|
364
|
+
color: var(--ik-muted);
|
|
365
|
+
font-size: 12px;
|
|
366
|
+
font-family: inherit;
|
|
367
|
+
cursor: pointer;
|
|
368
|
+
transition: border-color .15s, color .15s;
|
|
369
|
+
}
|
|
370
|
+
.btn-capture:hover {
|
|
371
|
+
border-color: var(--ik-accent);
|
|
372
|
+
color: var(--ik-accent);
|
|
373
|
+
}
|
|
374
|
+
.btn-capture svg { flex-shrink: 0; }
|
|
375
|
+
|
|
376
|
+
.screenshot-preview {
|
|
377
|
+
position: relative;
|
|
378
|
+
border-radius: 6px;
|
|
379
|
+
overflow: hidden;
|
|
380
|
+
border: 1px solid var(--ik-border);
|
|
381
|
+
margin-bottom: 10px;
|
|
382
|
+
}
|
|
383
|
+
.screenshot-preview img {
|
|
384
|
+
display: block;
|
|
385
|
+
width: 100%;
|
|
386
|
+
max-height: 200px;
|
|
387
|
+
object-fit: contain;
|
|
388
|
+
background: var(--ik-bg2);
|
|
389
|
+
}
|
|
390
|
+
.screenshot-remove {
|
|
391
|
+
position: absolute;
|
|
392
|
+
top: 4px; right: 4px;
|
|
393
|
+
width: 20px; height: 20px;
|
|
394
|
+
border-radius: 50%;
|
|
395
|
+
border: none;
|
|
396
|
+
background: rgba(0,0,0,.6);
|
|
397
|
+
color: #fff;
|
|
398
|
+
font-size: 12px;
|
|
399
|
+
line-height: 1;
|
|
400
|
+
cursor: pointer;
|
|
401
|
+
display: flex;
|
|
402
|
+
align-items: center;
|
|
403
|
+
justify-content: center;
|
|
404
|
+
padding: 0;
|
|
405
|
+
}
|
|
406
|
+
.screenshot-remove:hover { background: #ef4444; }
|
|
407
|
+
|
|
361
408
|
textarea {
|
|
362
409
|
width:100%; min-height:80px; resize:vertical;
|
|
363
410
|
border:1px solid var(--ik-border); border-radius:6px;
|
|
@@ -408,7 +455,6 @@ textarea::placeholder { color:var(--ik-muted); }
|
|
|
408
455
|
border-radius:4px; padding:2px 6px;
|
|
409
456
|
}
|
|
410
457
|
.status-badge.pending { background:rgba(99,102,241,.15); color:var(--ik-accent); }
|
|
411
|
-
.status-badge.acknowledged { background:rgba(249,115,22,.15); color:#f97316; }
|
|
412
458
|
.status-badge.resolved { background:rgba(34,197,94,.15); color:#22c55e; }
|
|
413
459
|
.status-badge.dismissed { background:var(--ik-bg2); color:var(--ik-muted); }
|
|
414
460
|
`
|
|
@@ -421,28 +467,29 @@ var MARKER_CSS = (
|
|
|
421
467
|
z-index: 2147483645;
|
|
422
468
|
width: 24px; height: 24px;
|
|
423
469
|
border-radius: 50%;
|
|
424
|
-
background: #6366f1;
|
|
470
|
+
background: var(--ik-marker-default, #6366f1);
|
|
425
471
|
color: #fff;
|
|
426
472
|
font-size: 11px; font-weight: 700;
|
|
427
473
|
display: flex; align-items: center; justify-content: center;
|
|
428
474
|
cursor: pointer;
|
|
429
|
-
box-shadow: 0 2px 8px
|
|
475
|
+
box-shadow: 0 2px 8px color-mix(in srgb, var(--ik-marker-default, #6366f1) 40%, transparent);
|
|
430
476
|
transition: transform .15s ease;
|
|
431
477
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
432
478
|
pointer-events: all;
|
|
433
479
|
user-select: none;
|
|
434
480
|
}
|
|
435
481
|
.ik-marker:hover { transform: scale(1.15); }
|
|
436
|
-
.ik-marker.
|
|
437
|
-
.ik-marker.dismissed { background: #71717a; box-shadow: 0 2px 8px rgba(0,0,0,.2); }
|
|
438
|
-
.ik-marker.acknowledged { background: #f97316; box-shadow: 0 2px 8px rgba(249,115,22,.4); }
|
|
482
|
+
.ik-marker.has-screenshot { background: var(--ik-marker-screenshot, #22c55e); box-shadow: 0 2px 8px color-mix(in srgb, var(--ik-marker-screenshot, #22c55e) 40%, transparent); }
|
|
483
|
+
.ik-marker.dismissed { background: var(--ik-marker-dismissed, #71717a); box-shadow: 0 2px 8px rgba(0,0,0,.2); }
|
|
439
484
|
`
|
|
440
485
|
);
|
|
441
|
-
function injectGlobalStyles() {
|
|
486
|
+
function injectGlobalStyles(colors) {
|
|
442
487
|
if (document.getElementById("instruckt-global")) return;
|
|
488
|
+
const vars = colors ? `:root {${colors.default ? ` --ik-marker-default: ${colors.default};` : ""}${colors.screenshot ? ` --ik-marker-screenshot: ${colors.screenshot};` : ""}${colors.dismissed ? ` --ik-marker-dismissed: ${colors.dismissed};` : ""} }
|
|
489
|
+
` : "";
|
|
443
490
|
const style = document.createElement("style");
|
|
444
491
|
style.id = "instruckt-global";
|
|
445
|
-
style.textContent = GLOBAL_CSS + MARKER_CSS;
|
|
492
|
+
style.textContent = vars + GLOBAL_CSS + MARKER_CSS;
|
|
446
493
|
document.head.appendChild(style);
|
|
447
494
|
}
|
|
448
495
|
|
|
@@ -454,10 +501,11 @@ var ICONS = {
|
|
|
454
501
|
check: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`,
|
|
455
502
|
clear: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`,
|
|
456
503
|
minimize: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="7 13 12 18 17 13"/><line x1="12" y1="6" x2="12" y2="18"/></svg>`,
|
|
504
|
+
screenshot: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>`,
|
|
457
505
|
logo: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>`
|
|
458
506
|
};
|
|
459
507
|
var Toolbar = class {
|
|
460
|
-
constructor(position, callbacks) {
|
|
508
|
+
constructor(position, callbacks, keys) {
|
|
461
509
|
this.position = position;
|
|
462
510
|
this.callbacks = callbacks;
|
|
463
511
|
this.fabBadge = null;
|
|
@@ -467,11 +515,12 @@ var Toolbar = class {
|
|
|
467
515
|
this.totalCount = 0;
|
|
468
516
|
this.dragging = false;
|
|
469
517
|
this.dragOffset = { x: 0, y: 0 };
|
|
518
|
+
this.keys = keys != null ? keys : {};
|
|
470
519
|
this.build();
|
|
471
520
|
this.setupDrag();
|
|
472
521
|
}
|
|
473
522
|
build() {
|
|
474
|
-
var _a;
|
|
523
|
+
var _a, _b, _c, _d, _e;
|
|
475
524
|
this.host = document.createElement("div");
|
|
476
525
|
this.host.setAttribute("data-instruckt", "toolbar");
|
|
477
526
|
this.shadow = this.host.attachShadow({ mode: "open" });
|
|
@@ -480,16 +529,20 @@ var Toolbar = class {
|
|
|
480
529
|
this.shadow.appendChild(style);
|
|
481
530
|
this.toolbarEl = document.createElement("div");
|
|
482
531
|
this.toolbarEl.className = "toolbar";
|
|
483
|
-
|
|
532
|
+
const k = this.keys;
|
|
533
|
+
this.annotateBtn = this.makeBtn(ICONS.annotate, `Annotate elements (${((_a = k.annotate) != null ? _a : "A").toUpperCase()})`, () => {
|
|
484
534
|
const next = !this.annotateActive;
|
|
485
535
|
this.setAnnotateActive(next);
|
|
486
536
|
this.callbacks.onToggleAnnotate(next);
|
|
487
537
|
});
|
|
488
|
-
this.freezeBtn = this.makeBtn(ICONS.freeze,
|
|
538
|
+
this.freezeBtn = this.makeBtn(ICONS.freeze, `Freeze page (${((_b = k.freeze) != null ? _b : "F").toUpperCase()})`, () => {
|
|
489
539
|
const next = !this.freezeActive;
|
|
490
540
|
this.setFreezeActive(next);
|
|
491
541
|
this.callbacks.onFreezeAnimations(next);
|
|
492
542
|
});
|
|
543
|
+
const screenshotBtn = this.makeBtn(ICONS.screenshot, `Screenshot region (${((_c = k.screenshot) != null ? _c : "C").toUpperCase()})`, () => {
|
|
544
|
+
this.callbacks.onScreenshot();
|
|
545
|
+
});
|
|
493
546
|
this.copyBtn = this.makeBtn(ICONS.copy, "Copy annotations as markdown", () => {
|
|
494
547
|
this.callbacks.onCopy();
|
|
495
548
|
this.copyBtn.innerHTML = ICONS.check;
|
|
@@ -499,22 +552,20 @@ var Toolbar = class {
|
|
|
499
552
|
});
|
|
500
553
|
const clearWrap = document.createElement("div");
|
|
501
554
|
clearWrap.className = "clear-wrap";
|
|
502
|
-
const clearBtn = this.makeBtn(ICONS.clear,
|
|
503
|
-
var _a2,
|
|
504
|
-
(
|
|
555
|
+
const clearBtn = this.makeBtn(ICONS.clear, `Clear this page (${((_d = k.clearPage) != null ? _d : "X").toUpperCase()})`, () => {
|
|
556
|
+
var _a2, _b2;
|
|
557
|
+
(_b2 = (_a2 = this.callbacks).onClearPage) == null ? void 0 : _b2.call(_a2);
|
|
505
558
|
});
|
|
506
559
|
clearBtn.classList.add("danger-btn");
|
|
507
560
|
const clearAllBtn = this.makeBtn(
|
|
508
561
|
`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>`,
|
|
509
562
|
"Delete all instructions.",
|
|
510
563
|
() => {
|
|
511
|
-
var _a2,
|
|
512
|
-
return (
|
|
564
|
+
var _a2, _b2;
|
|
565
|
+
return (_b2 = (_a2 = this.callbacks).onClearAll) == null ? void 0 : _b2.call(_a2);
|
|
513
566
|
}
|
|
514
567
|
);
|
|
515
568
|
clearAllBtn.classList.add("danger-btn", "clear-all-btn");
|
|
516
|
-
clearAllBtn.removeAttribute("title");
|
|
517
|
-
clearAllBtn.setAttribute("data-tooltip", "Delete all instructions.");
|
|
518
569
|
clearWrap.appendChild(clearBtn);
|
|
519
570
|
clearWrap.appendChild(clearAllBtn);
|
|
520
571
|
const minimizeBtn = this.makeBtn(ICONS.minimize, "Minimize toolbar", () => {
|
|
@@ -528,6 +579,7 @@ var Toolbar = class {
|
|
|
528
579
|
};
|
|
529
580
|
this.toolbarEl.append(
|
|
530
581
|
this.annotateBtn,
|
|
582
|
+
screenshotBtn,
|
|
531
583
|
mkDiv(),
|
|
532
584
|
this.freezeBtn,
|
|
533
585
|
mkDiv(),
|
|
@@ -552,14 +604,14 @@ var Toolbar = class {
|
|
|
552
604
|
this.host.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
553
605
|
this.host.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
554
606
|
this.applyPosition();
|
|
555
|
-
const root = (
|
|
607
|
+
const root = (_e = document.getElementById("instruckt-root")) != null ? _e : document.body;
|
|
556
608
|
root.appendChild(this.host);
|
|
557
609
|
}
|
|
558
|
-
makeBtn(iconHtml,
|
|
610
|
+
makeBtn(iconHtml, tooltip, onClick) {
|
|
559
611
|
const btn = document.createElement("button");
|
|
560
612
|
btn.className = "btn";
|
|
561
|
-
btn.
|
|
562
|
-
btn.setAttribute("aria-label",
|
|
613
|
+
btn.setAttribute("data-tooltip", tooltip);
|
|
614
|
+
btn.setAttribute("aria-label", tooltip);
|
|
563
615
|
btn.innerHTML = iconHtml;
|
|
564
616
|
btn.addEventListener("click", (e) => {
|
|
565
617
|
e.stopPropagation();
|
|
@@ -710,7 +762,953 @@ var ElementHighlight = class {
|
|
|
710
762
|
}
|
|
711
763
|
};
|
|
712
764
|
|
|
765
|
+
// node_modules/html-to-image/es/util.js
|
|
766
|
+
function resolveUrl(url, baseUrl) {
|
|
767
|
+
if (url.match(/^[a-z]+:\/\//i)) {
|
|
768
|
+
return url;
|
|
769
|
+
}
|
|
770
|
+
if (url.match(/^\/\//)) {
|
|
771
|
+
return window.location.protocol + url;
|
|
772
|
+
}
|
|
773
|
+
if (url.match(/^[a-z]+:/i)) {
|
|
774
|
+
return url;
|
|
775
|
+
}
|
|
776
|
+
const doc = document.implementation.createHTMLDocument();
|
|
777
|
+
const base = doc.createElement("base");
|
|
778
|
+
const a = doc.createElement("a");
|
|
779
|
+
doc.head.appendChild(base);
|
|
780
|
+
doc.body.appendChild(a);
|
|
781
|
+
if (baseUrl) {
|
|
782
|
+
base.href = baseUrl;
|
|
783
|
+
}
|
|
784
|
+
a.href = url;
|
|
785
|
+
return a.href;
|
|
786
|
+
}
|
|
787
|
+
var uuid = /* @__PURE__ */ (() => {
|
|
788
|
+
let counter = 0;
|
|
789
|
+
const random = () => (
|
|
790
|
+
// eslint-disable-next-line no-bitwise
|
|
791
|
+
`0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4)
|
|
792
|
+
);
|
|
793
|
+
return () => {
|
|
794
|
+
counter += 1;
|
|
795
|
+
return `u${random()}${counter}`;
|
|
796
|
+
};
|
|
797
|
+
})();
|
|
798
|
+
function toArray(arrayLike) {
|
|
799
|
+
const arr = [];
|
|
800
|
+
for (let i = 0, l = arrayLike.length; i < l; i++) {
|
|
801
|
+
arr.push(arrayLike[i]);
|
|
802
|
+
}
|
|
803
|
+
return arr;
|
|
804
|
+
}
|
|
805
|
+
var styleProps = null;
|
|
806
|
+
function getStyleProperties(options = {}) {
|
|
807
|
+
if (styleProps) {
|
|
808
|
+
return styleProps;
|
|
809
|
+
}
|
|
810
|
+
if (options.includeStyleProperties) {
|
|
811
|
+
styleProps = options.includeStyleProperties;
|
|
812
|
+
return styleProps;
|
|
813
|
+
}
|
|
814
|
+
styleProps = toArray(window.getComputedStyle(document.documentElement));
|
|
815
|
+
return styleProps;
|
|
816
|
+
}
|
|
817
|
+
function px(node, styleProperty) {
|
|
818
|
+
const win = node.ownerDocument.defaultView || window;
|
|
819
|
+
const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
|
|
820
|
+
return val ? parseFloat(val.replace("px", "")) : 0;
|
|
821
|
+
}
|
|
822
|
+
function getNodeWidth(node) {
|
|
823
|
+
const leftBorder = px(node, "border-left-width");
|
|
824
|
+
const rightBorder = px(node, "border-right-width");
|
|
825
|
+
return node.clientWidth + leftBorder + rightBorder;
|
|
826
|
+
}
|
|
827
|
+
function getNodeHeight(node) {
|
|
828
|
+
const topBorder = px(node, "border-top-width");
|
|
829
|
+
const bottomBorder = px(node, "border-bottom-width");
|
|
830
|
+
return node.clientHeight + topBorder + bottomBorder;
|
|
831
|
+
}
|
|
832
|
+
function getImageSize(targetNode, options = {}) {
|
|
833
|
+
const width = options.width || getNodeWidth(targetNode);
|
|
834
|
+
const height = options.height || getNodeHeight(targetNode);
|
|
835
|
+
return { width, height };
|
|
836
|
+
}
|
|
837
|
+
function getPixelRatio() {
|
|
838
|
+
let ratio;
|
|
839
|
+
let FINAL_PROCESS;
|
|
840
|
+
try {
|
|
841
|
+
FINAL_PROCESS = process;
|
|
842
|
+
} catch (e) {
|
|
843
|
+
}
|
|
844
|
+
const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
|
|
845
|
+
if (val) {
|
|
846
|
+
ratio = parseInt(val, 10);
|
|
847
|
+
if (Number.isNaN(ratio)) {
|
|
848
|
+
ratio = 1;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return ratio || window.devicePixelRatio || 1;
|
|
852
|
+
}
|
|
853
|
+
var canvasDimensionLimit = 16384;
|
|
854
|
+
function checkCanvasDimensions(canvas) {
|
|
855
|
+
if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
|
|
856
|
+
if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
|
|
857
|
+
if (canvas.width > canvas.height) {
|
|
858
|
+
canvas.height *= canvasDimensionLimit / canvas.width;
|
|
859
|
+
canvas.width = canvasDimensionLimit;
|
|
860
|
+
} else {
|
|
861
|
+
canvas.width *= canvasDimensionLimit / canvas.height;
|
|
862
|
+
canvas.height = canvasDimensionLimit;
|
|
863
|
+
}
|
|
864
|
+
} else if (canvas.width > canvasDimensionLimit) {
|
|
865
|
+
canvas.height *= canvasDimensionLimit / canvas.width;
|
|
866
|
+
canvas.width = canvasDimensionLimit;
|
|
867
|
+
} else {
|
|
868
|
+
canvas.width *= canvasDimensionLimit / canvas.height;
|
|
869
|
+
canvas.height = canvasDimensionLimit;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function createImage(url) {
|
|
874
|
+
return new Promise((resolve, reject) => {
|
|
875
|
+
const img = new Image();
|
|
876
|
+
img.onload = () => {
|
|
877
|
+
img.decode().then(() => {
|
|
878
|
+
requestAnimationFrame(() => resolve(img));
|
|
879
|
+
});
|
|
880
|
+
};
|
|
881
|
+
img.onerror = reject;
|
|
882
|
+
img.crossOrigin = "anonymous";
|
|
883
|
+
img.decoding = "async";
|
|
884
|
+
img.src = url;
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
async function svgToDataURL(svg) {
|
|
888
|
+
return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`);
|
|
889
|
+
}
|
|
890
|
+
async function nodeToDataURL(node, width, height) {
|
|
891
|
+
const xmlns = "http://www.w3.org/2000/svg";
|
|
892
|
+
const svg = document.createElementNS(xmlns, "svg");
|
|
893
|
+
const foreignObject = document.createElementNS(xmlns, "foreignObject");
|
|
894
|
+
svg.setAttribute("width", `${width}`);
|
|
895
|
+
svg.setAttribute("height", `${height}`);
|
|
896
|
+
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
897
|
+
foreignObject.setAttribute("width", "100%");
|
|
898
|
+
foreignObject.setAttribute("height", "100%");
|
|
899
|
+
foreignObject.setAttribute("x", "0");
|
|
900
|
+
foreignObject.setAttribute("y", "0");
|
|
901
|
+
foreignObject.setAttribute("externalResourcesRequired", "true");
|
|
902
|
+
svg.appendChild(foreignObject);
|
|
903
|
+
foreignObject.appendChild(node);
|
|
904
|
+
return svgToDataURL(svg);
|
|
905
|
+
}
|
|
906
|
+
var isInstanceOfElement = (node, instance) => {
|
|
907
|
+
if (node instanceof instance)
|
|
908
|
+
return true;
|
|
909
|
+
const nodePrototype = Object.getPrototypeOf(node);
|
|
910
|
+
if (nodePrototype === null)
|
|
911
|
+
return false;
|
|
912
|
+
return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
// node_modules/html-to-image/es/clone-pseudos.js
|
|
916
|
+
function formatCSSText(style) {
|
|
917
|
+
const content = style.getPropertyValue("content");
|
|
918
|
+
return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
|
|
919
|
+
}
|
|
920
|
+
function formatCSSProperties(style, options) {
|
|
921
|
+
return getStyleProperties(options).map((name) => {
|
|
922
|
+
const value = style.getPropertyValue(name);
|
|
923
|
+
const priority = style.getPropertyPriority(name);
|
|
924
|
+
return `${name}: ${value}${priority ? " !important" : ""};`;
|
|
925
|
+
}).join(" ");
|
|
926
|
+
}
|
|
927
|
+
function getPseudoElementStyle(className, pseudo, style, options) {
|
|
928
|
+
const selector = `.${className}:${pseudo}`;
|
|
929
|
+
const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
|
|
930
|
+
return document.createTextNode(`${selector}{${cssText}}`);
|
|
931
|
+
}
|
|
932
|
+
function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
|
|
933
|
+
const style = window.getComputedStyle(nativeNode, pseudo);
|
|
934
|
+
const content = style.getPropertyValue("content");
|
|
935
|
+
if (content === "" || content === "none") {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const className = uuid();
|
|
939
|
+
try {
|
|
940
|
+
clonedNode.className = `${clonedNode.className} ${className}`;
|
|
941
|
+
} catch (err) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const styleElement = document.createElement("style");
|
|
945
|
+
styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
|
|
946
|
+
clonedNode.appendChild(styleElement);
|
|
947
|
+
}
|
|
948
|
+
function clonePseudoElements(nativeNode, clonedNode, options) {
|
|
949
|
+
clonePseudoElement(nativeNode, clonedNode, ":before", options);
|
|
950
|
+
clonePseudoElement(nativeNode, clonedNode, ":after", options);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// node_modules/html-to-image/es/mimes.js
|
|
954
|
+
var WOFF = "application/font-woff";
|
|
955
|
+
var JPEG = "image/jpeg";
|
|
956
|
+
var mimes = {
|
|
957
|
+
woff: WOFF,
|
|
958
|
+
woff2: WOFF,
|
|
959
|
+
ttf: "application/font-truetype",
|
|
960
|
+
eot: "application/vnd.ms-fontobject",
|
|
961
|
+
png: "image/png",
|
|
962
|
+
jpg: JPEG,
|
|
963
|
+
jpeg: JPEG,
|
|
964
|
+
gif: "image/gif",
|
|
965
|
+
tiff: "image/tiff",
|
|
966
|
+
svg: "image/svg+xml",
|
|
967
|
+
webp: "image/webp"
|
|
968
|
+
};
|
|
969
|
+
function getExtension(url) {
|
|
970
|
+
const match = /\.([^./]*?)$/g.exec(url);
|
|
971
|
+
return match ? match[1] : "";
|
|
972
|
+
}
|
|
973
|
+
function getMimeType(url) {
|
|
974
|
+
const extension = getExtension(url).toLowerCase();
|
|
975
|
+
return mimes[extension] || "";
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// node_modules/html-to-image/es/dataurl.js
|
|
979
|
+
function getContentFromDataUrl(dataURL) {
|
|
980
|
+
return dataURL.split(/,/)[1];
|
|
981
|
+
}
|
|
982
|
+
function isDataUrl(url) {
|
|
983
|
+
return url.search(/^(data:)/) !== -1;
|
|
984
|
+
}
|
|
985
|
+
function makeDataUrl(content, mimeType) {
|
|
986
|
+
return `data:${mimeType};base64,${content}`;
|
|
987
|
+
}
|
|
988
|
+
async function fetchAsDataURL(url, init2, process2) {
|
|
989
|
+
const res = await fetch(url, init2);
|
|
990
|
+
if (res.status === 404) {
|
|
991
|
+
throw new Error(`Resource "${res.url}" not found`);
|
|
992
|
+
}
|
|
993
|
+
const blob = await res.blob();
|
|
994
|
+
return new Promise((resolve, reject) => {
|
|
995
|
+
const reader = new FileReader();
|
|
996
|
+
reader.onerror = reject;
|
|
997
|
+
reader.onloadend = () => {
|
|
998
|
+
try {
|
|
999
|
+
resolve(process2({ res, result: reader.result }));
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
reject(error);
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
reader.readAsDataURL(blob);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
var cache = {};
|
|
1008
|
+
function getCacheKey(url, contentType, includeQueryParams) {
|
|
1009
|
+
let key = url.replace(/\?.*/, "");
|
|
1010
|
+
if (includeQueryParams) {
|
|
1011
|
+
key = url;
|
|
1012
|
+
}
|
|
1013
|
+
if (/ttf|otf|eot|woff2?/i.test(key)) {
|
|
1014
|
+
key = key.replace(/.*\//, "");
|
|
1015
|
+
}
|
|
1016
|
+
return contentType ? `[${contentType}]${key}` : key;
|
|
1017
|
+
}
|
|
1018
|
+
async function resourceToDataURL(resourceUrl, contentType, options) {
|
|
1019
|
+
const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
|
|
1020
|
+
if (cache[cacheKey] != null) {
|
|
1021
|
+
return cache[cacheKey];
|
|
1022
|
+
}
|
|
1023
|
+
if (options.cacheBust) {
|
|
1024
|
+
resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
|
|
1025
|
+
}
|
|
1026
|
+
let dataURL;
|
|
1027
|
+
try {
|
|
1028
|
+
const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => {
|
|
1029
|
+
if (!contentType) {
|
|
1030
|
+
contentType = res.headers.get("Content-Type") || "";
|
|
1031
|
+
}
|
|
1032
|
+
return getContentFromDataUrl(result);
|
|
1033
|
+
});
|
|
1034
|
+
dataURL = makeDataUrl(content, contentType);
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
dataURL = options.imagePlaceholder || "";
|
|
1037
|
+
let msg = `Failed to fetch resource: ${resourceUrl}`;
|
|
1038
|
+
if (error) {
|
|
1039
|
+
msg = typeof error === "string" ? error : error.message;
|
|
1040
|
+
}
|
|
1041
|
+
if (msg) {
|
|
1042
|
+
console.warn(msg);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
cache[cacheKey] = dataURL;
|
|
1046
|
+
return dataURL;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// node_modules/html-to-image/es/clone-node.js
|
|
1050
|
+
async function cloneCanvasElement(canvas) {
|
|
1051
|
+
const dataURL = canvas.toDataURL();
|
|
1052
|
+
if (dataURL === "data:,") {
|
|
1053
|
+
return canvas.cloneNode(false);
|
|
1054
|
+
}
|
|
1055
|
+
return createImage(dataURL);
|
|
1056
|
+
}
|
|
1057
|
+
async function cloneVideoElement(video, options) {
|
|
1058
|
+
if (video.currentSrc) {
|
|
1059
|
+
const canvas = document.createElement("canvas");
|
|
1060
|
+
const ctx = canvas.getContext("2d");
|
|
1061
|
+
canvas.width = video.clientWidth;
|
|
1062
|
+
canvas.height = video.clientHeight;
|
|
1063
|
+
ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
1064
|
+
const dataURL2 = canvas.toDataURL();
|
|
1065
|
+
return createImage(dataURL2);
|
|
1066
|
+
}
|
|
1067
|
+
const poster = video.poster;
|
|
1068
|
+
const contentType = getMimeType(poster);
|
|
1069
|
+
const dataURL = await resourceToDataURL(poster, contentType, options);
|
|
1070
|
+
return createImage(dataURL);
|
|
1071
|
+
}
|
|
1072
|
+
async function cloneIFrameElement(iframe, options) {
|
|
1073
|
+
var _a;
|
|
1074
|
+
try {
|
|
1075
|
+
if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
|
|
1076
|
+
return await cloneNode(iframe.contentDocument.body, options, true);
|
|
1077
|
+
}
|
|
1078
|
+
} catch (_b) {
|
|
1079
|
+
}
|
|
1080
|
+
return iframe.cloneNode(false);
|
|
1081
|
+
}
|
|
1082
|
+
async function cloneSingleNode(node, options) {
|
|
1083
|
+
if (isInstanceOfElement(node, HTMLCanvasElement)) {
|
|
1084
|
+
return cloneCanvasElement(node);
|
|
1085
|
+
}
|
|
1086
|
+
if (isInstanceOfElement(node, HTMLVideoElement)) {
|
|
1087
|
+
return cloneVideoElement(node, options);
|
|
1088
|
+
}
|
|
1089
|
+
if (isInstanceOfElement(node, HTMLIFrameElement)) {
|
|
1090
|
+
return cloneIFrameElement(node, options);
|
|
1091
|
+
}
|
|
1092
|
+
return node.cloneNode(isSVGElement(node));
|
|
1093
|
+
}
|
|
1094
|
+
var isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
|
|
1095
|
+
var isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
|
|
1096
|
+
async function cloneChildren(nativeNode, clonedNode, options) {
|
|
1097
|
+
var _a, _b;
|
|
1098
|
+
if (isSVGElement(clonedNode)) {
|
|
1099
|
+
return clonedNode;
|
|
1100
|
+
}
|
|
1101
|
+
let children = [];
|
|
1102
|
+
if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
|
|
1103
|
+
children = toArray(nativeNode.assignedNodes());
|
|
1104
|
+
} else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
|
|
1105
|
+
children = toArray(nativeNode.contentDocument.body.childNodes);
|
|
1106
|
+
} else {
|
|
1107
|
+
children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
|
|
1108
|
+
}
|
|
1109
|
+
if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
|
|
1110
|
+
return clonedNode;
|
|
1111
|
+
}
|
|
1112
|
+
await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => {
|
|
1113
|
+
if (clonedChild) {
|
|
1114
|
+
clonedNode.appendChild(clonedChild);
|
|
1115
|
+
}
|
|
1116
|
+
}), Promise.resolve());
|
|
1117
|
+
return clonedNode;
|
|
1118
|
+
}
|
|
1119
|
+
function cloneCSSStyle(nativeNode, clonedNode, options) {
|
|
1120
|
+
const targetStyle = clonedNode.style;
|
|
1121
|
+
if (!targetStyle) {
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
1125
|
+
if (sourceStyle.cssText) {
|
|
1126
|
+
targetStyle.cssText = sourceStyle.cssText;
|
|
1127
|
+
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
1128
|
+
} else {
|
|
1129
|
+
getStyleProperties(options).forEach((name) => {
|
|
1130
|
+
let value = sourceStyle.getPropertyValue(name);
|
|
1131
|
+
if (name === "font-size" && value.endsWith("px")) {
|
|
1132
|
+
const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
|
|
1133
|
+
value = `${reducedFont}px`;
|
|
1134
|
+
}
|
|
1135
|
+
if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") {
|
|
1136
|
+
value = "block";
|
|
1137
|
+
}
|
|
1138
|
+
if (name === "d" && clonedNode.getAttribute("d")) {
|
|
1139
|
+
value = `path(${clonedNode.getAttribute("d")})`;
|
|
1140
|
+
}
|
|
1141
|
+
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function cloneInputValue(nativeNode, clonedNode) {
|
|
1146
|
+
if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
|
|
1147
|
+
clonedNode.innerHTML = nativeNode.value;
|
|
1148
|
+
}
|
|
1149
|
+
if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
|
|
1150
|
+
clonedNode.setAttribute("value", nativeNode.value);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function cloneSelectValue(nativeNode, clonedNode) {
|
|
1154
|
+
if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
|
|
1155
|
+
const clonedSelect = clonedNode;
|
|
1156
|
+
const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value"));
|
|
1157
|
+
if (selectedOption) {
|
|
1158
|
+
selectedOption.setAttribute("selected", "");
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
function decorate(nativeNode, clonedNode, options) {
|
|
1163
|
+
if (isInstanceOfElement(clonedNode, Element)) {
|
|
1164
|
+
cloneCSSStyle(nativeNode, clonedNode, options);
|
|
1165
|
+
clonePseudoElements(nativeNode, clonedNode, options);
|
|
1166
|
+
cloneInputValue(nativeNode, clonedNode);
|
|
1167
|
+
cloneSelectValue(nativeNode, clonedNode);
|
|
1168
|
+
}
|
|
1169
|
+
return clonedNode;
|
|
1170
|
+
}
|
|
1171
|
+
async function ensureSVGSymbols(clone, options) {
|
|
1172
|
+
const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : [];
|
|
1173
|
+
if (uses.length === 0) {
|
|
1174
|
+
return clone;
|
|
1175
|
+
}
|
|
1176
|
+
const processedDefs = {};
|
|
1177
|
+
for (let i = 0; i < uses.length; i++) {
|
|
1178
|
+
const use = uses[i];
|
|
1179
|
+
const id = use.getAttribute("xlink:href");
|
|
1180
|
+
if (id) {
|
|
1181
|
+
const exist = clone.querySelector(id);
|
|
1182
|
+
const definition = document.querySelector(id);
|
|
1183
|
+
if (!exist && definition && !processedDefs[id]) {
|
|
1184
|
+
processedDefs[id] = await cloneNode(definition, options, true);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const nodes = Object.values(processedDefs);
|
|
1189
|
+
if (nodes.length) {
|
|
1190
|
+
const ns = "http://www.w3.org/1999/xhtml";
|
|
1191
|
+
const svg = document.createElementNS(ns, "svg");
|
|
1192
|
+
svg.setAttribute("xmlns", ns);
|
|
1193
|
+
svg.style.position = "absolute";
|
|
1194
|
+
svg.style.width = "0";
|
|
1195
|
+
svg.style.height = "0";
|
|
1196
|
+
svg.style.overflow = "hidden";
|
|
1197
|
+
svg.style.display = "none";
|
|
1198
|
+
const defs = document.createElementNS(ns, "defs");
|
|
1199
|
+
svg.appendChild(defs);
|
|
1200
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1201
|
+
defs.appendChild(nodes[i]);
|
|
1202
|
+
}
|
|
1203
|
+
clone.appendChild(svg);
|
|
1204
|
+
}
|
|
1205
|
+
return clone;
|
|
1206
|
+
}
|
|
1207
|
+
async function cloneNode(node, options, isRoot) {
|
|
1208
|
+
if (!isRoot && options.filter && !options.filter(node)) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode, options)).then((clonedNode) => ensureSVGSymbols(clonedNode, options));
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// node_modules/html-to-image/es/embed-resources.js
|
|
1215
|
+
var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
|
|
1216
|
+
var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
|
|
1217
|
+
var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
|
|
1218
|
+
function toRegex(url) {
|
|
1219
|
+
const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
|
|
1220
|
+
return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
|
|
1221
|
+
}
|
|
1222
|
+
function parseURLs(cssText) {
|
|
1223
|
+
const urls = [];
|
|
1224
|
+
cssText.replace(URL_REGEX, (raw, quotation, url) => {
|
|
1225
|
+
urls.push(url);
|
|
1226
|
+
return raw;
|
|
1227
|
+
});
|
|
1228
|
+
return urls.filter((url) => !isDataUrl(url));
|
|
1229
|
+
}
|
|
1230
|
+
async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
|
|
1231
|
+
try {
|
|
1232
|
+
const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
|
|
1233
|
+
const contentType = getMimeType(resourceURL);
|
|
1234
|
+
let dataURL;
|
|
1235
|
+
if (getContentFromUrl) {
|
|
1236
|
+
const content = await getContentFromUrl(resolvedURL);
|
|
1237
|
+
dataURL = makeDataUrl(content, contentType);
|
|
1238
|
+
} else {
|
|
1239
|
+
dataURL = await resourceToDataURL(resolvedURL, contentType, options);
|
|
1240
|
+
}
|
|
1241
|
+
return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
}
|
|
1244
|
+
return cssText;
|
|
1245
|
+
}
|
|
1246
|
+
function filterPreferredFontFormat(str, { preferredFontFormat }) {
|
|
1247
|
+
return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => {
|
|
1248
|
+
while (true) {
|
|
1249
|
+
const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
|
|
1250
|
+
if (!format) {
|
|
1251
|
+
return "";
|
|
1252
|
+
}
|
|
1253
|
+
if (format === preferredFontFormat) {
|
|
1254
|
+
return `src: ${src};`;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
function shouldEmbed(url) {
|
|
1260
|
+
return url.search(URL_REGEX) !== -1;
|
|
1261
|
+
}
|
|
1262
|
+
async function embedResources(cssText, baseUrl, options) {
|
|
1263
|
+
if (!shouldEmbed(cssText)) {
|
|
1264
|
+
return cssText;
|
|
1265
|
+
}
|
|
1266
|
+
const filteredCSSText = filterPreferredFontFormat(cssText, options);
|
|
1267
|
+
const urls = parseURLs(filteredCSSText);
|
|
1268
|
+
return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// node_modules/html-to-image/es/embed-images.js
|
|
1272
|
+
async function embedProp(propName, node, options) {
|
|
1273
|
+
var _a;
|
|
1274
|
+
const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
|
|
1275
|
+
if (propValue) {
|
|
1276
|
+
const cssString = await embedResources(propValue, null, options);
|
|
1277
|
+
node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
|
|
1278
|
+
return true;
|
|
1279
|
+
}
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
async function embedBackground(clonedNode, options) {
|
|
1283
|
+
;
|
|
1284
|
+
await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
|
|
1285
|
+
await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
|
|
1286
|
+
}
|
|
1287
|
+
async function embedImageNode(clonedNode, options) {
|
|
1288
|
+
const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
|
|
1289
|
+
if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
|
|
1293
|
+
const dataURL = await resourceToDataURL(url, getMimeType(url), options);
|
|
1294
|
+
await new Promise((resolve, reject) => {
|
|
1295
|
+
clonedNode.onload = resolve;
|
|
1296
|
+
clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
|
|
1297
|
+
try {
|
|
1298
|
+
resolve(options.onImageErrorHandler(...attributes));
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
reject(error);
|
|
1301
|
+
}
|
|
1302
|
+
} : reject;
|
|
1303
|
+
const image = clonedNode;
|
|
1304
|
+
if (image.decode) {
|
|
1305
|
+
image.decode = resolve;
|
|
1306
|
+
}
|
|
1307
|
+
if (image.loading === "lazy") {
|
|
1308
|
+
image.loading = "eager";
|
|
1309
|
+
}
|
|
1310
|
+
if (isImageElement) {
|
|
1311
|
+
clonedNode.srcset = "";
|
|
1312
|
+
clonedNode.src = dataURL;
|
|
1313
|
+
} else {
|
|
1314
|
+
clonedNode.href.baseVal = dataURL;
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
async function embedChildren(clonedNode, options) {
|
|
1319
|
+
const children = toArray(clonedNode.childNodes);
|
|
1320
|
+
const deferreds = children.map((child) => embedImages(child, options));
|
|
1321
|
+
await Promise.all(deferreds).then(() => clonedNode);
|
|
1322
|
+
}
|
|
1323
|
+
async function embedImages(clonedNode, options) {
|
|
1324
|
+
if (isInstanceOfElement(clonedNode, Element)) {
|
|
1325
|
+
await embedBackground(clonedNode, options);
|
|
1326
|
+
await embedImageNode(clonedNode, options);
|
|
1327
|
+
await embedChildren(clonedNode, options);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// node_modules/html-to-image/es/apply-style.js
|
|
1332
|
+
function applyStyle(node, options) {
|
|
1333
|
+
const { style } = node;
|
|
1334
|
+
if (options.backgroundColor) {
|
|
1335
|
+
style.backgroundColor = options.backgroundColor;
|
|
1336
|
+
}
|
|
1337
|
+
if (options.width) {
|
|
1338
|
+
style.width = `${options.width}px`;
|
|
1339
|
+
}
|
|
1340
|
+
if (options.height) {
|
|
1341
|
+
style.height = `${options.height}px`;
|
|
1342
|
+
}
|
|
1343
|
+
const manual = options.style;
|
|
1344
|
+
if (manual != null) {
|
|
1345
|
+
Object.keys(manual).forEach((key) => {
|
|
1346
|
+
style[key] = manual[key];
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
return node;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// node_modules/html-to-image/es/embed-webfonts.js
|
|
1353
|
+
var cssFetchCache = {};
|
|
1354
|
+
async function fetchCSS(url) {
|
|
1355
|
+
let cache2 = cssFetchCache[url];
|
|
1356
|
+
if (cache2 != null) {
|
|
1357
|
+
return cache2;
|
|
1358
|
+
}
|
|
1359
|
+
const res = await fetch(url);
|
|
1360
|
+
const cssText = await res.text();
|
|
1361
|
+
cache2 = { url, cssText };
|
|
1362
|
+
cssFetchCache[url] = cache2;
|
|
1363
|
+
return cache2;
|
|
1364
|
+
}
|
|
1365
|
+
async function embedFonts(data, options) {
|
|
1366
|
+
let cssText = data.cssText;
|
|
1367
|
+
const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
|
|
1368
|
+
const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
|
|
1369
|
+
const loadFonts = fontLocs.map(async (loc) => {
|
|
1370
|
+
let url = loc.replace(regexUrl, "$1");
|
|
1371
|
+
if (!url.startsWith("https://")) {
|
|
1372
|
+
url = new URL(url, data.url).href;
|
|
1373
|
+
}
|
|
1374
|
+
return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
|
|
1375
|
+
cssText = cssText.replace(loc, `url(${result})`);
|
|
1376
|
+
return [loc, result];
|
|
1377
|
+
});
|
|
1378
|
+
});
|
|
1379
|
+
return Promise.all(loadFonts).then(() => cssText);
|
|
1380
|
+
}
|
|
1381
|
+
function parseCSS(source) {
|
|
1382
|
+
if (source == null) {
|
|
1383
|
+
return [];
|
|
1384
|
+
}
|
|
1385
|
+
const result = [];
|
|
1386
|
+
const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
|
|
1387
|
+
let cssText = source.replace(commentsRegex, "");
|
|
1388
|
+
const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi");
|
|
1389
|
+
while (true) {
|
|
1390
|
+
const matches = keyframesRegex.exec(cssText);
|
|
1391
|
+
if (matches === null) {
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
result.push(matches[0]);
|
|
1395
|
+
}
|
|
1396
|
+
cssText = cssText.replace(keyframesRegex, "");
|
|
1397
|
+
const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
|
|
1398
|
+
const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})";
|
|
1399
|
+
const unifiedRegex = new RegExp(combinedCSSRegex, "gi");
|
|
1400
|
+
while (true) {
|
|
1401
|
+
let matches = importRegex.exec(cssText);
|
|
1402
|
+
if (matches === null) {
|
|
1403
|
+
matches = unifiedRegex.exec(cssText);
|
|
1404
|
+
if (matches === null) {
|
|
1405
|
+
break;
|
|
1406
|
+
} else {
|
|
1407
|
+
importRegex.lastIndex = unifiedRegex.lastIndex;
|
|
1408
|
+
}
|
|
1409
|
+
} else {
|
|
1410
|
+
unifiedRegex.lastIndex = importRegex.lastIndex;
|
|
1411
|
+
}
|
|
1412
|
+
result.push(matches[0]);
|
|
1413
|
+
}
|
|
1414
|
+
return result;
|
|
1415
|
+
}
|
|
1416
|
+
async function getCSSRules(styleSheets, options) {
|
|
1417
|
+
const ret = [];
|
|
1418
|
+
const deferreds = [];
|
|
1419
|
+
styleSheets.forEach((sheet) => {
|
|
1420
|
+
if ("cssRules" in sheet) {
|
|
1421
|
+
try {
|
|
1422
|
+
toArray(sheet.cssRules || []).forEach((item, index) => {
|
|
1423
|
+
if (item.type === CSSRule.IMPORT_RULE) {
|
|
1424
|
+
let importIndex = index + 1;
|
|
1425
|
+
const url = item.href;
|
|
1426
|
+
const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
|
|
1427
|
+
try {
|
|
1428
|
+
sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length);
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
console.error("Error inserting rule from remote css", {
|
|
1431
|
+
rule,
|
|
1432
|
+
error
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
})).catch((e) => {
|
|
1436
|
+
console.error("Error loading remote css", e.toString());
|
|
1437
|
+
});
|
|
1438
|
+
deferreds.push(deferred);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
} catch (e) {
|
|
1442
|
+
const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
|
|
1443
|
+
if (sheet.href != null) {
|
|
1444
|
+
deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
|
|
1445
|
+
inline.insertRule(rule, inline.cssRules.length);
|
|
1446
|
+
})).catch((err) => {
|
|
1447
|
+
console.error("Error loading remote stylesheet", err);
|
|
1448
|
+
}));
|
|
1449
|
+
}
|
|
1450
|
+
console.error("Error inlining remote css file", e);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
return Promise.all(deferreds).then(() => {
|
|
1455
|
+
styleSheets.forEach((sheet) => {
|
|
1456
|
+
if ("cssRules" in sheet) {
|
|
1457
|
+
try {
|
|
1458
|
+
toArray(sheet.cssRules || []).forEach((item) => {
|
|
1459
|
+
ret.push(item);
|
|
1460
|
+
});
|
|
1461
|
+
} catch (e) {
|
|
1462
|
+
console.error(`Error while reading CSS rules from ${sheet.href}`, e);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
return ret;
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
function getWebFontRules(cssRules) {
|
|
1470
|
+
return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src")));
|
|
1471
|
+
}
|
|
1472
|
+
async function parseWebFontRules(node, options) {
|
|
1473
|
+
if (node.ownerDocument == null) {
|
|
1474
|
+
throw new Error("Provided element is not within a Document");
|
|
1475
|
+
}
|
|
1476
|
+
const styleSheets = toArray(node.ownerDocument.styleSheets);
|
|
1477
|
+
const cssRules = await getCSSRules(styleSheets, options);
|
|
1478
|
+
return getWebFontRules(cssRules);
|
|
1479
|
+
}
|
|
1480
|
+
function normalizeFontFamily(font) {
|
|
1481
|
+
return font.trim().replace(/["']/g, "");
|
|
1482
|
+
}
|
|
1483
|
+
function getUsedFonts(node) {
|
|
1484
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
1485
|
+
function traverse(node2) {
|
|
1486
|
+
const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
|
|
1487
|
+
fontFamily.split(",").forEach((font) => {
|
|
1488
|
+
fonts.add(normalizeFontFamily(font));
|
|
1489
|
+
});
|
|
1490
|
+
Array.from(node2.children).forEach((child) => {
|
|
1491
|
+
if (child instanceof HTMLElement) {
|
|
1492
|
+
traverse(child);
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
traverse(node);
|
|
1497
|
+
return fonts;
|
|
1498
|
+
}
|
|
1499
|
+
async function getWebFontCSS(node, options) {
|
|
1500
|
+
const rules = await parseWebFontRules(node, options);
|
|
1501
|
+
const usedFonts = getUsedFonts(node);
|
|
1502
|
+
const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
|
|
1503
|
+
const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
|
|
1504
|
+
return embedResources(rule.cssText, baseUrl, options);
|
|
1505
|
+
}));
|
|
1506
|
+
return cssTexts.join("\n");
|
|
1507
|
+
}
|
|
1508
|
+
async function embedWebFonts(clonedNode, options) {
|
|
1509
|
+
const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
|
|
1510
|
+
if (cssText) {
|
|
1511
|
+
const styleNode = document.createElement("style");
|
|
1512
|
+
const sytleContent = document.createTextNode(cssText);
|
|
1513
|
+
styleNode.appendChild(sytleContent);
|
|
1514
|
+
if (clonedNode.firstChild) {
|
|
1515
|
+
clonedNode.insertBefore(styleNode, clonedNode.firstChild);
|
|
1516
|
+
} else {
|
|
1517
|
+
clonedNode.appendChild(styleNode);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// node_modules/html-to-image/es/index.js
|
|
1523
|
+
async function toSvg(node, options = {}) {
|
|
1524
|
+
const { width, height } = getImageSize(node, options);
|
|
1525
|
+
const clonedNode = await cloneNode(node, options, true);
|
|
1526
|
+
await embedWebFonts(clonedNode, options);
|
|
1527
|
+
await embedImages(clonedNode, options);
|
|
1528
|
+
applyStyle(clonedNode, options);
|
|
1529
|
+
const datauri = await nodeToDataURL(clonedNode, width, height);
|
|
1530
|
+
return datauri;
|
|
1531
|
+
}
|
|
1532
|
+
async function toCanvas(node, options = {}) {
|
|
1533
|
+
const { width, height } = getImageSize(node, options);
|
|
1534
|
+
const svg = await toSvg(node, options);
|
|
1535
|
+
const img = await createImage(svg);
|
|
1536
|
+
const canvas = document.createElement("canvas");
|
|
1537
|
+
const context = canvas.getContext("2d");
|
|
1538
|
+
const ratio = options.pixelRatio || getPixelRatio();
|
|
1539
|
+
const canvasWidth = options.canvasWidth || width;
|
|
1540
|
+
const canvasHeight = options.canvasHeight || height;
|
|
1541
|
+
canvas.width = canvasWidth * ratio;
|
|
1542
|
+
canvas.height = canvasHeight * ratio;
|
|
1543
|
+
if (!options.skipAutoScale) {
|
|
1544
|
+
checkCanvasDimensions(canvas);
|
|
1545
|
+
}
|
|
1546
|
+
canvas.style.width = `${canvasWidth}`;
|
|
1547
|
+
canvas.style.height = `${canvasHeight}`;
|
|
1548
|
+
if (options.backgroundColor) {
|
|
1549
|
+
context.fillStyle = options.backgroundColor;
|
|
1550
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
1551
|
+
}
|
|
1552
|
+
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
1553
|
+
return canvas;
|
|
1554
|
+
}
|
|
1555
|
+
async function toPng(node, options = {}) {
|
|
1556
|
+
const canvas = await toCanvas(node, options);
|
|
1557
|
+
return canvas.toDataURL();
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// src/ui/screenshot.ts
|
|
1561
|
+
async function captureElement(el) {
|
|
1562
|
+
try {
|
|
1563
|
+
return await toPng(el, {
|
|
1564
|
+
cacheBust: true,
|
|
1565
|
+
pixelRatio: 2,
|
|
1566
|
+
skipFonts: true,
|
|
1567
|
+
filter: (node) => {
|
|
1568
|
+
var _a, _b;
|
|
1569
|
+
if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
|
|
1570
|
+
if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
|
|
1571
|
+
const href = (_b = node.getAttribute("href")) != null ? _b : "";
|
|
1572
|
+
if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
|
|
1573
|
+
}
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
} catch (e) {
|
|
1578
|
+
return null;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
async function captureRegion(rect) {
|
|
1582
|
+
try {
|
|
1583
|
+
const full = await toPng(document.body, {
|
|
1584
|
+
cacheBust: true,
|
|
1585
|
+
pixelRatio: 2,
|
|
1586
|
+
skipFonts: true,
|
|
1587
|
+
filter: (node) => {
|
|
1588
|
+
var _a, _b;
|
|
1589
|
+
if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
|
|
1590
|
+
if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
|
|
1591
|
+
const href = (_b = node.getAttribute("href")) != null ? _b : "";
|
|
1592
|
+
if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
|
|
1593
|
+
}
|
|
1594
|
+
return true;
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
return await cropImage(full, rect);
|
|
1598
|
+
} catch (e) {
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
function cropImage(dataUrl, rect) {
|
|
1603
|
+
return new Promise((resolve, reject) => {
|
|
1604
|
+
const img = new Image();
|
|
1605
|
+
img.onload = () => {
|
|
1606
|
+
const ratio = 2;
|
|
1607
|
+
const canvas = document.createElement("canvas");
|
|
1608
|
+
canvas.width = rect.width * ratio;
|
|
1609
|
+
canvas.height = rect.height * ratio;
|
|
1610
|
+
const ctx = canvas.getContext("2d");
|
|
1611
|
+
ctx.drawImage(
|
|
1612
|
+
img,
|
|
1613
|
+
rect.x * ratio,
|
|
1614
|
+
rect.y * ratio,
|
|
1615
|
+
rect.width * ratio,
|
|
1616
|
+
rect.height * ratio,
|
|
1617
|
+
0,
|
|
1618
|
+
0,
|
|
1619
|
+
rect.width * ratio,
|
|
1620
|
+
rect.height * ratio
|
|
1621
|
+
);
|
|
1622
|
+
resolve(canvas.toDataURL("image/png"));
|
|
1623
|
+
};
|
|
1624
|
+
img.onerror = reject;
|
|
1625
|
+
img.src = dataUrl;
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
function selectRegion() {
|
|
1629
|
+
return new Promise((resolve) => {
|
|
1630
|
+
const overlay = document.createElement("div");
|
|
1631
|
+
Object.assign(overlay.style, {
|
|
1632
|
+
position: "fixed",
|
|
1633
|
+
inset: "0",
|
|
1634
|
+
zIndex: "2147483647",
|
|
1635
|
+
cursor: "crosshair",
|
|
1636
|
+
background: "rgba(0,0,0,0.1)"
|
|
1637
|
+
});
|
|
1638
|
+
overlay.setAttribute("data-instruckt", "region-select");
|
|
1639
|
+
const box = document.createElement("div");
|
|
1640
|
+
Object.assign(box.style, {
|
|
1641
|
+
position: "fixed",
|
|
1642
|
+
border: "2px dashed #6366f1",
|
|
1643
|
+
background: "rgba(99,102,241,0.08)",
|
|
1644
|
+
borderRadius: "4px",
|
|
1645
|
+
display: "none",
|
|
1646
|
+
pointerEvents: "none"
|
|
1647
|
+
});
|
|
1648
|
+
overlay.appendChild(box);
|
|
1649
|
+
let startX = 0, startY = 0, dragging = false;
|
|
1650
|
+
const onMouseDown = (e) => {
|
|
1651
|
+
startX = e.clientX;
|
|
1652
|
+
startY = e.clientY;
|
|
1653
|
+
dragging = true;
|
|
1654
|
+
box.style.display = "block";
|
|
1655
|
+
updateBox(e);
|
|
1656
|
+
};
|
|
1657
|
+
const onMouseMove = (e) => {
|
|
1658
|
+
if (!dragging) return;
|
|
1659
|
+
updateBox(e);
|
|
1660
|
+
};
|
|
1661
|
+
const updateBox = (e) => {
|
|
1662
|
+
const x = Math.min(startX, e.clientX);
|
|
1663
|
+
const y = Math.min(startY, e.clientY);
|
|
1664
|
+
const w = Math.abs(e.clientX - startX);
|
|
1665
|
+
const h = Math.abs(e.clientY - startY);
|
|
1666
|
+
Object.assign(box.style, {
|
|
1667
|
+
left: `${x}px`,
|
|
1668
|
+
top: `${y}px`,
|
|
1669
|
+
width: `${w}px`,
|
|
1670
|
+
height: `${h}px`
|
|
1671
|
+
});
|
|
1672
|
+
};
|
|
1673
|
+
const onMouseUp = (e) => {
|
|
1674
|
+
if (!dragging) return;
|
|
1675
|
+
dragging = false;
|
|
1676
|
+
const x = Math.min(startX, e.clientX);
|
|
1677
|
+
const y = Math.min(startY, e.clientY);
|
|
1678
|
+
const w = Math.abs(e.clientX - startX);
|
|
1679
|
+
const h = Math.abs(e.clientY - startY);
|
|
1680
|
+
cleanup();
|
|
1681
|
+
if (w < 10 || h < 10) {
|
|
1682
|
+
resolve(null);
|
|
1683
|
+
} else {
|
|
1684
|
+
resolve(new DOMRect(x, y, w, h));
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
const onKeyDown = (e) => {
|
|
1688
|
+
if (e.key === "Escape") {
|
|
1689
|
+
cleanup();
|
|
1690
|
+
resolve(null);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
const cleanup = () => {
|
|
1694
|
+
overlay.remove();
|
|
1695
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
1696
|
+
};
|
|
1697
|
+
overlay.addEventListener("mousedown", onMouseDown);
|
|
1698
|
+
overlay.addEventListener("mousemove", onMouseMove);
|
|
1699
|
+
overlay.addEventListener("mouseup", onMouseUp);
|
|
1700
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
1701
|
+
document.body.appendChild(overlay);
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
|
|
713
1705
|
// src/ui/popup.ts
|
|
1706
|
+
function screenshotUrl(screenshot, endpoint) {
|
|
1707
|
+
if (!screenshot) return null;
|
|
1708
|
+
if (screenshot.startsWith("data:")) return screenshot;
|
|
1709
|
+
const base = endpoint != null ? endpoint : "/instruckt";
|
|
1710
|
+
return `${base}/${screenshot}`;
|
|
1711
|
+
}
|
|
714
1712
|
function esc(s) {
|
|
715
1713
|
return String(s != null ? s : "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
716
1714
|
}
|
|
@@ -726,7 +1724,7 @@ var AnnotationPopup = class {
|
|
|
726
1724
|
}
|
|
727
1725
|
// ── New annotation popup ──────────────────────────────────────
|
|
728
1726
|
showNew(pending, callbacks) {
|
|
729
|
-
var _a;
|
|
1727
|
+
var _a, _b;
|
|
730
1728
|
this.destroy();
|
|
731
1729
|
this.host = document.createElement("div");
|
|
732
1730
|
this.host.setAttribute("data-instruckt", "popup");
|
|
@@ -739,23 +1737,56 @@ var AnnotationPopup = class {
|
|
|
739
1737
|
popup.className = "popup";
|
|
740
1738
|
const fwBadge = pending.framework ? `<div class="fw-badge">${esc(pending.framework.component)}</div>` : "";
|
|
741
1739
|
const selText = pending.selectedText ? `<div class="selected-text">"${esc(pending.selectedText.slice(0, 80))}"</div>` : "";
|
|
1740
|
+
const hasScreenshot = !!pending.screenshot;
|
|
742
1741
|
popup.innerHTML = `
|
|
743
1742
|
<div class="header">
|
|
744
1743
|
<span class="element-tag" title="${esc(pending.elementPath)}">${esc(pending.elementLabel)}</span>
|
|
745
1744
|
<button class="close-btn" title="Cancel (Esc)">\u2715</button>
|
|
746
1745
|
</div>
|
|
747
1746
|
${fwBadge}${selText}
|
|
748
|
-
<
|
|
1747
|
+
<div class="screenshot-slot">${hasScreenshot ? `<div class="screenshot-preview"><img src="${pending.screenshot}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>` : `<button class="btn-capture" data-action="capture"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> Capture screenshot</button>`}</div>
|
|
1748
|
+
<textarea placeholder="${hasScreenshot ? "Add a note (optional)" : "What needs to change here?"}" rows="3"></textarea>
|
|
749
1749
|
<div class="actions">
|
|
750
1750
|
<button class="btn-secondary" data-action="cancel">Cancel</button>
|
|
751
|
-
<button class="btn-primary" data-action="submit" disabled>Add note</button>
|
|
1751
|
+
<button class="btn-primary" data-action="submit" ${hasScreenshot ? "" : "disabled"}>Add note</button>
|
|
752
1752
|
</div>
|
|
753
1753
|
`;
|
|
1754
|
+
let currentScreenshot = (_a = pending.screenshot) != null ? _a : null;
|
|
754
1755
|
const textarea = popup.querySelector("textarea");
|
|
755
1756
|
const submitBtn = popup.querySelector('[data-action="submit"]');
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1757
|
+
const screenshotSlot = popup.querySelector(".screenshot-slot");
|
|
1758
|
+
const updateSubmitState = () => {
|
|
1759
|
+
submitBtn.disabled = !currentScreenshot && textarea.value.trim().length === 0;
|
|
1760
|
+
};
|
|
1761
|
+
const attachScreenshotEvents = () => {
|
|
1762
|
+
const captureBtn = screenshotSlot.querySelector('[data-action="capture"]');
|
|
1763
|
+
captureBtn == null ? void 0 : captureBtn.addEventListener("click", async () => {
|
|
1764
|
+
captureBtn.textContent = "Capturing...";
|
|
1765
|
+
const dataUrl = await captureElement(pending.element);
|
|
1766
|
+
if (dataUrl) {
|
|
1767
|
+
currentScreenshot = dataUrl;
|
|
1768
|
+
screenshotSlot.innerHTML = `<div class="screenshot-preview"><img src="${dataUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>`;
|
|
1769
|
+
textarea.placeholder = "Add a note (optional)";
|
|
1770
|
+
attachScreenshotEvents();
|
|
1771
|
+
updateSubmitState();
|
|
1772
|
+
} else {
|
|
1773
|
+
captureBtn.textContent = "Capture failed";
|
|
1774
|
+
setTimeout(() => {
|
|
1775
|
+
if (captureBtn.parentElement) captureBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> Capture screenshot`;
|
|
1776
|
+
}, 1500);
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
const removeBtn = screenshotSlot.querySelector(".screenshot-remove");
|
|
1780
|
+
removeBtn == null ? void 0 : removeBtn.addEventListener("click", () => {
|
|
1781
|
+
currentScreenshot = null;
|
|
1782
|
+
screenshotSlot.innerHTML = `<button class="btn-capture" data-action="capture"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> Capture screenshot</button>`;
|
|
1783
|
+
textarea.placeholder = "What needs to change here?";
|
|
1784
|
+
attachScreenshotEvents();
|
|
1785
|
+
updateSubmitState();
|
|
1786
|
+
});
|
|
1787
|
+
};
|
|
1788
|
+
attachScreenshotEvents();
|
|
1789
|
+
textarea.addEventListener("input", updateSubmitState);
|
|
759
1790
|
textarea.addEventListener("keydown", (e) => {
|
|
760
1791
|
e.stopPropagation();
|
|
761
1792
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
@@ -777,18 +1808,18 @@ var AnnotationPopup = class {
|
|
|
777
1808
|
});
|
|
778
1809
|
submitBtn.addEventListener("click", () => {
|
|
779
1810
|
const comment = textarea.value.trim();
|
|
780
|
-
if (!comment) return;
|
|
781
|
-
callbacks.onSubmit({ comment });
|
|
1811
|
+
if (!comment && !currentScreenshot) return;
|
|
1812
|
+
callbacks.onSubmit({ comment: comment || "(screenshot)", screenshot: currentScreenshot != null ? currentScreenshot : void 0 });
|
|
782
1813
|
this.destroy();
|
|
783
1814
|
});
|
|
784
1815
|
this.shadow.appendChild(popup);
|
|
785
|
-
((
|
|
1816
|
+
((_b = document.getElementById("instruckt-root")) != null ? _b : document.body).appendChild(this.host);
|
|
786
1817
|
this.positionHost(pending.x, pending.y);
|
|
787
1818
|
this.setupOutsideClick();
|
|
788
1819
|
textarea.focus();
|
|
789
1820
|
}
|
|
790
1821
|
// ── Edit existing annotation ──────────────────────────────────
|
|
791
|
-
showEdit(annotation, callbacks) {
|
|
1822
|
+
showEdit(annotation, callbacks, endpoint) {
|
|
792
1823
|
var _a;
|
|
793
1824
|
this.destroy();
|
|
794
1825
|
this.host = document.createElement("div");
|
|
@@ -801,19 +1832,28 @@ var AnnotationPopup = class {
|
|
|
801
1832
|
const popup = document.createElement("div");
|
|
802
1833
|
popup.className = "popup";
|
|
803
1834
|
const fwBadge = annotation.framework ? `<div class="fw-badge">${esc(annotation.framework.component)}</div>` : "";
|
|
1835
|
+
const ssUrl = screenshotUrl(annotation.screenshot, endpoint);
|
|
1836
|
+
const screenshotPreview = ssUrl ? `<div class="screenshot-preview screenshot-slot"><img src="${ssUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>` : "";
|
|
1837
|
+
const commentText = annotation.comment === "(screenshot)" ? "" : annotation.comment;
|
|
804
1838
|
popup.innerHTML = `
|
|
805
1839
|
<div class="header">
|
|
806
1840
|
<span class="element-tag" title="${esc(annotation.elementPath)}">${esc(annotation.element)}</span>
|
|
807
1841
|
<button class="close-btn">\u2715</button>
|
|
808
1842
|
</div>
|
|
809
|
-
${fwBadge}
|
|
810
|
-
<textarea rows="3">${esc(
|
|
1843
|
+
${fwBadge}${screenshotPreview}
|
|
1844
|
+
<textarea rows="3">${esc(commentText)}</textarea>
|
|
811
1845
|
<div class="actions">
|
|
812
1846
|
<button class="btn-danger" data-action="delete">Remove</button>
|
|
813
1847
|
<button class="btn-primary" data-action="save">Save</button>
|
|
814
1848
|
</div>
|
|
815
1849
|
`;
|
|
816
1850
|
popup.querySelector(".close-btn").addEventListener("click", () => this.destroy());
|
|
1851
|
+
const ssRemoveBtn = popup.querySelector(".screenshot-remove");
|
|
1852
|
+
ssRemoveBtn == null ? void 0 : ssRemoveBtn.addEventListener("click", () => {
|
|
1853
|
+
callbacks.onSave(annotation, annotation.comment);
|
|
1854
|
+
const slot = popup.querySelector(".screenshot-slot");
|
|
1855
|
+
if (slot) slot.remove();
|
|
1856
|
+
});
|
|
817
1857
|
const textarea = popup.querySelector("textarea");
|
|
818
1858
|
const saveBtn = popup.querySelector('[data-action="save"]');
|
|
819
1859
|
const deleteBtn = popup.querySelector('[data-action="delete"]');
|
|
@@ -908,9 +1948,10 @@ var AnnotationMarkers = class {
|
|
|
908
1948
|
return;
|
|
909
1949
|
}
|
|
910
1950
|
const el = document.createElement("div");
|
|
911
|
-
|
|
1951
|
+
const ssClass = annotation.screenshot ? " has-screenshot" : "";
|
|
1952
|
+
el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
|
|
912
1953
|
el.textContent = String(index);
|
|
913
|
-
el.title = annotation.comment.slice(0, 60);
|
|
1954
|
+
el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
|
|
914
1955
|
el.style.pointerEvents = "all";
|
|
915
1956
|
el.style.left = `${annotation.x / 100 * window.innerWidth}px`;
|
|
916
1957
|
el.style.top = `${annotation.y - window.scrollY}px`;
|
|
@@ -928,13 +1969,13 @@ var AnnotationMarkers = class {
|
|
|
928
1969
|
this.updateStyle(marker.el, annotation);
|
|
929
1970
|
}
|
|
930
1971
|
updateStyle(el, annotation) {
|
|
931
|
-
|
|
932
|
-
el.
|
|
1972
|
+
const ssClass = annotation.screenshot ? " has-screenshot" : "";
|
|
1973
|
+
el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
|
|
1974
|
+
el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
|
|
933
1975
|
}
|
|
934
1976
|
statusClass(status) {
|
|
935
1977
|
if (status === "resolved") return "resolved";
|
|
936
1978
|
if (status === "dismissed") return "dismissed";
|
|
937
|
-
if (status === "acknowledged") return "acknowledged";
|
|
938
1979
|
return "";
|
|
939
1980
|
}
|
|
940
1981
|
/** Reposition all markers (e.g. after scroll or resize) */
|
|
@@ -1269,7 +2310,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1269
2310
|
e.stopImmediatePropagation();
|
|
1270
2311
|
};
|
|
1271
2312
|
this.boundClick = (e) => {
|
|
1272
|
-
var _a, _b, _c
|
|
2313
|
+
var _a, _b, _c;
|
|
1273
2314
|
const target = e.target;
|
|
1274
2315
|
if (this.isInstruckt(target)) return;
|
|
1275
2316
|
e.preventDefault();
|
|
@@ -1283,6 +2324,8 @@ var _Instruckt = class _Instruckt {
|
|
|
1283
2324
|
const nearbyText = getNearbyText(target) || void 0;
|
|
1284
2325
|
const boundingBox = getPageBoundingBox(target);
|
|
1285
2326
|
const framework = (_b = this.detectFramework(target)) != null ? _b : void 0;
|
|
2327
|
+
(_c = this.highlight) == null ? void 0 : _c.show(target);
|
|
2328
|
+
this.highlightLocked = true;
|
|
1286
2329
|
const pending = {
|
|
1287
2330
|
element: target,
|
|
1288
2331
|
elementPath,
|
|
@@ -1296,21 +2339,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1296
2339
|
nearbyText,
|
|
1297
2340
|
framework
|
|
1298
2341
|
};
|
|
1299
|
-
|
|
1300
|
-
this.highlightLocked = true;
|
|
1301
|
-
(_d = this.popup) == null ? void 0 : _d.showNew(pending, {
|
|
1302
|
-
onSubmit: (result) => {
|
|
1303
|
-
var _a2;
|
|
1304
|
-
this.highlightLocked = false;
|
|
1305
|
-
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
1306
|
-
this.submitAnnotation(pending, result.comment);
|
|
1307
|
-
},
|
|
1308
|
-
onCancel: () => {
|
|
1309
|
-
var _a2;
|
|
1310
|
-
this.highlightLocked = false;
|
|
1311
|
-
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
1312
|
-
}
|
|
1313
|
-
});
|
|
2342
|
+
this.showAnnotationPopup(pending);
|
|
1314
2343
|
};
|
|
1315
2344
|
this.config = __spreadValues({
|
|
1316
2345
|
adapters: ["livewire", "vue", "svelte", "react"],
|
|
@@ -1322,7 +2351,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1322
2351
|
this.init();
|
|
1323
2352
|
}
|
|
1324
2353
|
init() {
|
|
1325
|
-
injectGlobalStyles();
|
|
2354
|
+
injectGlobalStyles(this.config.colors);
|
|
1326
2355
|
if (this.config.theme !== "auto") {
|
|
1327
2356
|
document.documentElement.setAttribute("data-instruckt-theme", this.config.theme);
|
|
1328
2357
|
}
|
|
@@ -1333,11 +2362,12 @@ var _Instruckt = class _Instruckt {
|
|
|
1333
2362
|
onFreezeAnimations: (frozen) => {
|
|
1334
2363
|
this.setFrozen(frozen);
|
|
1335
2364
|
},
|
|
2365
|
+
onScreenshot: () => this.startRegionCapture(),
|
|
1336
2366
|
onCopy: () => this.copyToClipboard(true),
|
|
1337
2367
|
onClearPage: () => this.clearPage(),
|
|
1338
2368
|
onClearAll: () => this.clearEverything(),
|
|
1339
2369
|
onMinimize: (min) => this.onMinimize(min)
|
|
1340
|
-
});
|
|
2370
|
+
}, this.config.keys);
|
|
1341
2371
|
this.highlight = new ElementHighlight();
|
|
1342
2372
|
this.popup = new AnnotationPopup();
|
|
1343
2373
|
this.markers = new AnnotationMarkers((annotation) => this.onMarkerClick(annotation));
|
|
@@ -1350,7 +2380,6 @@ var _Instruckt = class _Instruckt {
|
|
|
1350
2380
|
setTimeout(() => this.reattach(), 0);
|
|
1351
2381
|
});
|
|
1352
2382
|
this.loadAnnotations();
|
|
1353
|
-
this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
|
|
1354
2383
|
this.syncMarkers();
|
|
1355
2384
|
}
|
|
1356
2385
|
makeToolbarCallbacks() {
|
|
@@ -1361,6 +2390,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1361
2390
|
onFreezeAnimations: (frozen) => {
|
|
1362
2391
|
this.setFrozen(frozen);
|
|
1363
2392
|
},
|
|
2393
|
+
onScreenshot: () => this.startRegionCapture(),
|
|
1364
2394
|
onCopy: () => this.copyToClipboard(true),
|
|
1365
2395
|
onClearPage: () => this.clearPage(),
|
|
1366
2396
|
onClearAll: () => this.clearEverything(),
|
|
@@ -1384,7 +2414,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1384
2414
|
if (wasMinimized) this.markers.setVisible(false);
|
|
1385
2415
|
const existing = document.getElementById("instruckt-global");
|
|
1386
2416
|
if (existing) existing.remove();
|
|
1387
|
-
injectGlobalStyles();
|
|
2417
|
+
injectGlobalStyles(this.config.colors);
|
|
1388
2418
|
this.syncMarkers();
|
|
1389
2419
|
if (wasAnnotating && !wasMinimized) this.setAnnotating(true);
|
|
1390
2420
|
}
|
|
@@ -1429,6 +2459,16 @@ var _Instruckt = class _Instruckt {
|
|
|
1429
2459
|
} catch (e) {
|
|
1430
2460
|
}
|
|
1431
2461
|
}
|
|
2462
|
+
/** Start or stop polling based on whether there are active annotations */
|
|
2463
|
+
updatePolling() {
|
|
2464
|
+
const hasActive = this.totalActiveCount() > 0;
|
|
2465
|
+
if (hasActive && !this.pollTimer) {
|
|
2466
|
+
this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
|
|
2467
|
+
} else if (!hasActive && this.pollTimer) {
|
|
2468
|
+
clearInterval(this.pollTimer);
|
|
2469
|
+
this.pollTimer = null;
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
1432
2472
|
/** Poll API for status changes (e.g. agent resolved via MCP) */
|
|
1433
2473
|
async pollForChanges() {
|
|
1434
2474
|
try {
|
|
@@ -1465,6 +2505,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1465
2505
|
}
|
|
1466
2506
|
(_c = this.toolbar) == null ? void 0 : _c.setAnnotationCount(this.pageAnnotations().length);
|
|
1467
2507
|
(_d = this.toolbar) == null ? void 0 : _d.setTotalCount(this.totalActiveCount());
|
|
2508
|
+
this.updatePolling();
|
|
1468
2509
|
}
|
|
1469
2510
|
annotationPageKey(a) {
|
|
1470
2511
|
try {
|
|
@@ -1594,6 +2635,22 @@ var _Instruckt = class _Instruckt {
|
|
|
1594
2635
|
`;
|
|
1595
2636
|
document.head.appendChild(this.frozenStyleEl);
|
|
1596
2637
|
}
|
|
2638
|
+
showAnnotationPopup(pending) {
|
|
2639
|
+
var _a;
|
|
2640
|
+
(_a = this.popup) == null ? void 0 : _a.showNew(pending, {
|
|
2641
|
+
onSubmit: (result) => {
|
|
2642
|
+
var _a2;
|
|
2643
|
+
this.highlightLocked = false;
|
|
2644
|
+
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
2645
|
+
this.submitAnnotation(pending, result.comment, result.screenshot);
|
|
2646
|
+
},
|
|
2647
|
+
onCancel: () => {
|
|
2648
|
+
var _a2;
|
|
2649
|
+
this.highlightLocked = false;
|
|
2650
|
+
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
2651
|
+
}
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
1597
2654
|
attachAnnotateListeners() {
|
|
1598
2655
|
document.addEventListener("mousemove", this.boundMouseMove);
|
|
1599
2656
|
document.addEventListener("mouseleave", this.boundMouseLeave);
|
|
@@ -1614,6 +2671,39 @@ var _Instruckt = class _Instruckt {
|
|
|
1614
2671
|
if (!el || !(el instanceof Element)) return false;
|
|
1615
2672
|
return el.closest("[data-instruckt]") !== null;
|
|
1616
2673
|
}
|
|
2674
|
+
// ── Region screenshot ────────────────────────────────────────
|
|
2675
|
+
async startRegionCapture() {
|
|
2676
|
+
var _a, _b;
|
|
2677
|
+
const wasAnnotating = this.isAnnotating;
|
|
2678
|
+
if (wasAnnotating) this.setAnnotating(false);
|
|
2679
|
+
const rect = await selectRegion();
|
|
2680
|
+
if (!rect) {
|
|
2681
|
+
if (wasAnnotating) this.setAnnotating(true);
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
const screenshot = await captureRegion(rect);
|
|
2685
|
+
if (!screenshot) {
|
|
2686
|
+
if (wasAnnotating) this.setAnnotating(true);
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
const centerX = rect.x + rect.width / 2;
|
|
2690
|
+
const centerY = rect.y + rect.height / 2;
|
|
2691
|
+
const target = (_a = document.elementFromPoint(centerX, centerY)) != null ? _a : document.body;
|
|
2692
|
+
const pending = {
|
|
2693
|
+
element: target,
|
|
2694
|
+
elementPath: getElementSelector(target),
|
|
2695
|
+
elementName: getElementName(target),
|
|
2696
|
+
elementLabel: getElementLabel(target),
|
|
2697
|
+
cssClasses: getCssClasses(target),
|
|
2698
|
+
boundingBox: getPageBoundingBox(target),
|
|
2699
|
+
x: centerX,
|
|
2700
|
+
y: centerY,
|
|
2701
|
+
nearbyText: getNearbyText(target) || void 0,
|
|
2702
|
+
screenshot,
|
|
2703
|
+
framework: (_b = this.detectFramework(target)) != null ? _b : void 0
|
|
2704
|
+
};
|
|
2705
|
+
this.showAnnotationPopup(pending);
|
|
2706
|
+
}
|
|
1617
2707
|
// ── Framework detection ───────────────────────────────────────
|
|
1618
2708
|
detectFramework(el) {
|
|
1619
2709
|
var _a;
|
|
@@ -1637,7 +2727,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1637
2727
|
return null;
|
|
1638
2728
|
}
|
|
1639
2729
|
// ── Submit ────────────────────────────────────────────────────
|
|
1640
|
-
async submitAnnotation(pending, comment) {
|
|
2730
|
+
async submitAnnotation(pending, comment, screenshot) {
|
|
1641
2731
|
var _a, _b;
|
|
1642
2732
|
const payload = {
|
|
1643
2733
|
x: pending.x / window.innerWidth * 100,
|
|
@@ -1649,6 +2739,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1649
2739
|
boundingBox: pending.boundingBox,
|
|
1650
2740
|
selectedText: pending.selectedText,
|
|
1651
2741
|
nearbyText: pending.nearbyText,
|
|
2742
|
+
screenshot,
|
|
1652
2743
|
intent: "fix",
|
|
1653
2744
|
severity: "important",
|
|
1654
2745
|
framework: pending.framework,
|
|
@@ -1661,7 +2752,6 @@ var _Instruckt = class _Instruckt {
|
|
|
1661
2752
|
annotation = __spreadProps(__spreadValues({}, payload), {
|
|
1662
2753
|
id: crypto.randomUUID(),
|
|
1663
2754
|
status: "pending",
|
|
1664
|
-
thread: [],
|
|
1665
2755
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1666
2756
|
});
|
|
1667
2757
|
}
|
|
@@ -1690,7 +2780,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1690
2780
|
}
|
|
1691
2781
|
this.removeAnnotation(a.id);
|
|
1692
2782
|
}
|
|
1693
|
-
});
|
|
2783
|
+
}, this.config.endpoint);
|
|
1694
2784
|
}
|
|
1695
2785
|
onAnnotationUpdated(updated) {
|
|
1696
2786
|
const idx = this.annotations.findIndex((a) => a.id === updated.id);
|
|
@@ -1732,19 +2822,24 @@ var _Instruckt = class _Instruckt {
|
|
|
1732
2822
|
}
|
|
1733
2823
|
// ── Keyboard ──────────────────────────────────────────────────
|
|
1734
2824
|
onKeydown(e) {
|
|
1735
|
-
var _a;
|
|
2825
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1736
2826
|
if ((_a = this.toolbar) == null ? void 0 : _a.isMinimized()) return;
|
|
1737
2827
|
const target = e.target;
|
|
1738
2828
|
if (["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) return;
|
|
1739
2829
|
if (target.closest('[contenteditable="true"]')) return;
|
|
1740
2830
|
if (this.isInstruckt(target)) return;
|
|
1741
|
-
|
|
2831
|
+
const keys = (_b = this.config.keys) != null ? _b : {};
|
|
2832
|
+
const noMod = !e.metaKey && !e.ctrlKey && !e.altKey;
|
|
2833
|
+
if (e.key === ((_c = keys.annotate) != null ? _c : "a") && noMod) {
|
|
1742
2834
|
this.setAnnotating(!this.isAnnotating);
|
|
1743
2835
|
}
|
|
1744
|
-
if (e.key ===
|
|
2836
|
+
if (e.key === ((_d = keys.freeze) != null ? _d : "f") && noMod) {
|
|
1745
2837
|
this.setFrozen(!this.isFrozen);
|
|
1746
2838
|
}
|
|
1747
|
-
if (e.key ===
|
|
2839
|
+
if (e.key === ((_e = keys.screenshot) != null ? _e : "c") && noMod) {
|
|
2840
|
+
this.startRegionCapture();
|
|
2841
|
+
}
|
|
2842
|
+
if (e.key === ((_f = keys.clearPage) != null ? _f : "x") && noMod) {
|
|
1748
2843
|
this.clearPage();
|
|
1749
2844
|
}
|
|
1750
2845
|
if (e.key === "Escape") {
|
|
@@ -1819,6 +2914,13 @@ No open annotations.`;
|
|
|
1819
2914
|
} else if (a.nearbyText) {
|
|
1820
2915
|
lines.push(`- Text: "${a.nearbyText.slice(0, 100)}"`);
|
|
1821
2916
|
}
|
|
2917
|
+
if (a.screenshot) {
|
|
2918
|
+
if (!a.screenshot.startsWith("data:")) {
|
|
2919
|
+
lines.push(`- Screenshot: \`storage/app/_instruckt/${a.screenshot}\``);
|
|
2920
|
+
} else {
|
|
2921
|
+
lines.push(`- Screenshot: attached`);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
1822
2924
|
lines.push("");
|
|
1823
2925
|
});
|
|
1824
2926
|
}
|