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.cjs.js
CHANGED
|
@@ -103,15 +103,6 @@ var InstrucktApi = class {
|
|
|
103
103
|
if (!res.ok) throw new Error(`instruckt: failed to update annotation (${res.status})`);
|
|
104
104
|
return toCamelCase(await res.json());
|
|
105
105
|
}
|
|
106
|
-
async addReply(annotationId, content, role = "human") {
|
|
107
|
-
const res = await fetch(`${this.endpoint}/annotations/${annotationId}/reply`, {
|
|
108
|
-
method: "POST",
|
|
109
|
-
headers: headers(),
|
|
110
|
-
body: JSON.stringify({ role, content })
|
|
111
|
-
});
|
|
112
|
-
if (!res.ok) throw new Error(`instruckt: failed to add reply (${res.status})`);
|
|
113
|
-
return toCamelCase(await res.json());
|
|
114
|
-
}
|
|
115
106
|
};
|
|
116
107
|
|
|
117
108
|
// src/ui/styles.ts
|
|
@@ -187,6 +178,24 @@ var TOOLBAR_CSS = (
|
|
|
187
178
|
}
|
|
188
179
|
.btn svg { display: block; }
|
|
189
180
|
.btn:hover { background: var(--ik-bg2); color: var(--ik-text); }
|
|
181
|
+
.btn[data-tooltip]::before {
|
|
182
|
+
content: attr(data-tooltip);
|
|
183
|
+
position: absolute;
|
|
184
|
+
right: calc(100% + 8px);
|
|
185
|
+
top: 50%;
|
|
186
|
+
transform: translateY(-50%);
|
|
187
|
+
white-space: nowrap;
|
|
188
|
+
font-size: 11px;
|
|
189
|
+
padding: 4px 8px;
|
|
190
|
+
border-radius: 6px;
|
|
191
|
+
background: var(--ik-text);
|
|
192
|
+
color: var(--ik-bg);
|
|
193
|
+
pointer-events: none;
|
|
194
|
+
opacity: 0;
|
|
195
|
+
transition: opacity .1s ease;
|
|
196
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
197
|
+
}
|
|
198
|
+
.btn[data-tooltip]:hover::before { opacity: 1; }
|
|
190
199
|
.btn.active { background: var(--ik-accent); color: #fff; }
|
|
191
200
|
.btn.active:hover { background: var(--ik-accent-h); }
|
|
192
201
|
|
|
@@ -226,24 +235,7 @@ var TOOLBAR_CSS = (
|
|
|
226
235
|
box-shadow: var(--ik-shadow);
|
|
227
236
|
border-radius: 8px;
|
|
228
237
|
}
|
|
229
|
-
/*
|
|
230
|
-
.clear-all-btn::before {
|
|
231
|
-
content: attr(data-tooltip);
|
|
232
|
-
position: absolute;
|
|
233
|
-
right: calc(100% + 6px);
|
|
234
|
-
top: 50%;
|
|
235
|
-
transform: translateY(-50%);
|
|
236
|
-
white-space: nowrap;
|
|
237
|
-
font-size: 11px;
|
|
238
|
-
padding: 4px 8px;
|
|
239
|
-
border-radius: 6px;
|
|
240
|
-
background: var(--ik-text);
|
|
241
|
-
color: var(--ik-bg);
|
|
242
|
-
pointer-events: none;
|
|
243
|
-
opacity: 0;
|
|
244
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
245
|
-
}
|
|
246
|
-
.clear-all-btn:hover::before { opacity: 1; }
|
|
238
|
+
/* clear-all tooltip inherits from .btn[data-tooltip]::before */
|
|
247
239
|
/* Invisible bridge so hover doesn't break crossing the gap */
|
|
248
240
|
.clear-all-btn::after {
|
|
249
241
|
content: '';
|
|
@@ -382,6 +374,61 @@ var POPUP_CSS = (
|
|
|
382
374
|
.chip.important.sel { background:#f97316; border-color:#f97316; }
|
|
383
375
|
.chip.suggestion.sel{ background:#22c55e; border-color:#22c55e; }
|
|
384
376
|
|
|
377
|
+
.screenshot-slot { margin-bottom: 10px; }
|
|
378
|
+
|
|
379
|
+
.btn-capture {
|
|
380
|
+
display: flex;
|
|
381
|
+
align-items: center;
|
|
382
|
+
gap: 6px;
|
|
383
|
+
width: 100%;
|
|
384
|
+
padding: 8px 10px;
|
|
385
|
+
border: 1px dashed var(--ik-border);
|
|
386
|
+
border-radius: 6px;
|
|
387
|
+
background: var(--ik-bg2);
|
|
388
|
+
color: var(--ik-muted);
|
|
389
|
+
font-size: 12px;
|
|
390
|
+
font-family: inherit;
|
|
391
|
+
cursor: pointer;
|
|
392
|
+
transition: border-color .15s, color .15s;
|
|
393
|
+
}
|
|
394
|
+
.btn-capture:hover {
|
|
395
|
+
border-color: var(--ik-accent);
|
|
396
|
+
color: var(--ik-accent);
|
|
397
|
+
}
|
|
398
|
+
.btn-capture svg { flex-shrink: 0; }
|
|
399
|
+
|
|
400
|
+
.screenshot-preview {
|
|
401
|
+
position: relative;
|
|
402
|
+
border-radius: 6px;
|
|
403
|
+
overflow: hidden;
|
|
404
|
+
border: 1px solid var(--ik-border);
|
|
405
|
+
margin-bottom: 10px;
|
|
406
|
+
}
|
|
407
|
+
.screenshot-preview img {
|
|
408
|
+
display: block;
|
|
409
|
+
width: 100%;
|
|
410
|
+
max-height: 200px;
|
|
411
|
+
object-fit: contain;
|
|
412
|
+
background: var(--ik-bg2);
|
|
413
|
+
}
|
|
414
|
+
.screenshot-remove {
|
|
415
|
+
position: absolute;
|
|
416
|
+
top: 4px; right: 4px;
|
|
417
|
+
width: 20px; height: 20px;
|
|
418
|
+
border-radius: 50%;
|
|
419
|
+
border: none;
|
|
420
|
+
background: rgba(0,0,0,.6);
|
|
421
|
+
color: #fff;
|
|
422
|
+
font-size: 12px;
|
|
423
|
+
line-height: 1;
|
|
424
|
+
cursor: pointer;
|
|
425
|
+
display: flex;
|
|
426
|
+
align-items: center;
|
|
427
|
+
justify-content: center;
|
|
428
|
+
padding: 0;
|
|
429
|
+
}
|
|
430
|
+
.screenshot-remove:hover { background: #ef4444; }
|
|
431
|
+
|
|
385
432
|
textarea {
|
|
386
433
|
width:100%; min-height:80px; resize:vertical;
|
|
387
434
|
border:1px solid var(--ik-border); border-radius:6px;
|
|
@@ -432,7 +479,6 @@ textarea::placeholder { color:var(--ik-muted); }
|
|
|
432
479
|
border-radius:4px; padding:2px 6px;
|
|
433
480
|
}
|
|
434
481
|
.status-badge.pending { background:rgba(99,102,241,.15); color:var(--ik-accent); }
|
|
435
|
-
.status-badge.acknowledged { background:rgba(249,115,22,.15); color:#f97316; }
|
|
436
482
|
.status-badge.resolved { background:rgba(34,197,94,.15); color:#22c55e; }
|
|
437
483
|
.status-badge.dismissed { background:var(--ik-bg2); color:var(--ik-muted); }
|
|
438
484
|
`
|
|
@@ -445,28 +491,29 @@ var MARKER_CSS = (
|
|
|
445
491
|
z-index: 2147483645;
|
|
446
492
|
width: 24px; height: 24px;
|
|
447
493
|
border-radius: 50%;
|
|
448
|
-
background: #6366f1;
|
|
494
|
+
background: var(--ik-marker-default, #6366f1);
|
|
449
495
|
color: #fff;
|
|
450
496
|
font-size: 11px; font-weight: 700;
|
|
451
497
|
display: flex; align-items: center; justify-content: center;
|
|
452
498
|
cursor: pointer;
|
|
453
|
-
box-shadow: 0 2px 8px
|
|
499
|
+
box-shadow: 0 2px 8px color-mix(in srgb, var(--ik-marker-default, #6366f1) 40%, transparent);
|
|
454
500
|
transition: transform .15s ease;
|
|
455
501
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
456
502
|
pointer-events: all;
|
|
457
503
|
user-select: none;
|
|
458
504
|
}
|
|
459
505
|
.ik-marker:hover { transform: scale(1.15); }
|
|
460
|
-
.ik-marker.
|
|
461
|
-
.ik-marker.dismissed { background: #71717a; box-shadow: 0 2px 8px rgba(0,0,0,.2); }
|
|
462
|
-
.ik-marker.acknowledged { background: #f97316; box-shadow: 0 2px 8px rgba(249,115,22,.4); }
|
|
506
|
+
.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); }
|
|
507
|
+
.ik-marker.dismissed { background: var(--ik-marker-dismissed, #71717a); box-shadow: 0 2px 8px rgba(0,0,0,.2); }
|
|
463
508
|
`
|
|
464
509
|
);
|
|
465
|
-
function injectGlobalStyles() {
|
|
510
|
+
function injectGlobalStyles(colors) {
|
|
466
511
|
if (document.getElementById("instruckt-global")) return;
|
|
512
|
+
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};` : ""} }
|
|
513
|
+
` : "";
|
|
467
514
|
const style = document.createElement("style");
|
|
468
515
|
style.id = "instruckt-global";
|
|
469
|
-
style.textContent = GLOBAL_CSS + MARKER_CSS;
|
|
516
|
+
style.textContent = vars + GLOBAL_CSS + MARKER_CSS;
|
|
470
517
|
document.head.appendChild(style);
|
|
471
518
|
}
|
|
472
519
|
|
|
@@ -478,10 +525,11 @@ var ICONS = {
|
|
|
478
525
|
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>`,
|
|
479
526
|
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>`,
|
|
480
527
|
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>`,
|
|
528
|
+
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>`,
|
|
481
529
|
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>`
|
|
482
530
|
};
|
|
483
531
|
var Toolbar = class {
|
|
484
|
-
constructor(position, callbacks) {
|
|
532
|
+
constructor(position, callbacks, keys) {
|
|
485
533
|
this.position = position;
|
|
486
534
|
this.callbacks = callbacks;
|
|
487
535
|
this.fabBadge = null;
|
|
@@ -491,11 +539,12 @@ var Toolbar = class {
|
|
|
491
539
|
this.totalCount = 0;
|
|
492
540
|
this.dragging = false;
|
|
493
541
|
this.dragOffset = { x: 0, y: 0 };
|
|
542
|
+
this.keys = keys != null ? keys : {};
|
|
494
543
|
this.build();
|
|
495
544
|
this.setupDrag();
|
|
496
545
|
}
|
|
497
546
|
build() {
|
|
498
|
-
var _a;
|
|
547
|
+
var _a, _b, _c, _d, _e;
|
|
499
548
|
this.host = document.createElement("div");
|
|
500
549
|
this.host.setAttribute("data-instruckt", "toolbar");
|
|
501
550
|
this.shadow = this.host.attachShadow({ mode: "open" });
|
|
@@ -504,16 +553,20 @@ var Toolbar = class {
|
|
|
504
553
|
this.shadow.appendChild(style);
|
|
505
554
|
this.toolbarEl = document.createElement("div");
|
|
506
555
|
this.toolbarEl.className = "toolbar";
|
|
507
|
-
|
|
556
|
+
const k = this.keys;
|
|
557
|
+
this.annotateBtn = this.makeBtn(ICONS.annotate, `Annotate elements (${((_a = k.annotate) != null ? _a : "A").toUpperCase()})`, () => {
|
|
508
558
|
const next = !this.annotateActive;
|
|
509
559
|
this.setAnnotateActive(next);
|
|
510
560
|
this.callbacks.onToggleAnnotate(next);
|
|
511
561
|
});
|
|
512
|
-
this.freezeBtn = this.makeBtn(ICONS.freeze,
|
|
562
|
+
this.freezeBtn = this.makeBtn(ICONS.freeze, `Freeze page (${((_b = k.freeze) != null ? _b : "F").toUpperCase()})`, () => {
|
|
513
563
|
const next = !this.freezeActive;
|
|
514
564
|
this.setFreezeActive(next);
|
|
515
565
|
this.callbacks.onFreezeAnimations(next);
|
|
516
566
|
});
|
|
567
|
+
const screenshotBtn = this.makeBtn(ICONS.screenshot, `Screenshot region (${((_c = k.screenshot) != null ? _c : "C").toUpperCase()})`, () => {
|
|
568
|
+
this.callbacks.onScreenshot();
|
|
569
|
+
});
|
|
517
570
|
this.copyBtn = this.makeBtn(ICONS.copy, "Copy annotations as markdown", () => {
|
|
518
571
|
this.callbacks.onCopy();
|
|
519
572
|
this.copyBtn.innerHTML = ICONS.check;
|
|
@@ -523,22 +576,20 @@ var Toolbar = class {
|
|
|
523
576
|
});
|
|
524
577
|
const clearWrap = document.createElement("div");
|
|
525
578
|
clearWrap.className = "clear-wrap";
|
|
526
|
-
const clearBtn = this.makeBtn(ICONS.clear,
|
|
527
|
-
var _a2,
|
|
528
|
-
(
|
|
579
|
+
const clearBtn = this.makeBtn(ICONS.clear, `Clear this page (${((_d = k.clearPage) != null ? _d : "X").toUpperCase()})`, () => {
|
|
580
|
+
var _a2, _b2;
|
|
581
|
+
(_b2 = (_a2 = this.callbacks).onClearPage) == null ? void 0 : _b2.call(_a2);
|
|
529
582
|
});
|
|
530
583
|
clearBtn.classList.add("danger-btn");
|
|
531
584
|
const clearAllBtn = this.makeBtn(
|
|
532
585
|
`<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>`,
|
|
533
586
|
"Delete all instructions.",
|
|
534
587
|
() => {
|
|
535
|
-
var _a2,
|
|
536
|
-
return (
|
|
588
|
+
var _a2, _b2;
|
|
589
|
+
return (_b2 = (_a2 = this.callbacks).onClearAll) == null ? void 0 : _b2.call(_a2);
|
|
537
590
|
}
|
|
538
591
|
);
|
|
539
592
|
clearAllBtn.classList.add("danger-btn", "clear-all-btn");
|
|
540
|
-
clearAllBtn.removeAttribute("title");
|
|
541
|
-
clearAllBtn.setAttribute("data-tooltip", "Delete all instructions.");
|
|
542
593
|
clearWrap.appendChild(clearBtn);
|
|
543
594
|
clearWrap.appendChild(clearAllBtn);
|
|
544
595
|
const minimizeBtn = this.makeBtn(ICONS.minimize, "Minimize toolbar", () => {
|
|
@@ -552,6 +603,7 @@ var Toolbar = class {
|
|
|
552
603
|
};
|
|
553
604
|
this.toolbarEl.append(
|
|
554
605
|
this.annotateBtn,
|
|
606
|
+
screenshotBtn,
|
|
555
607
|
mkDiv(),
|
|
556
608
|
this.freezeBtn,
|
|
557
609
|
mkDiv(),
|
|
@@ -576,14 +628,14 @@ var Toolbar = class {
|
|
|
576
628
|
this.host.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
577
629
|
this.host.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
578
630
|
this.applyPosition();
|
|
579
|
-
const root = (
|
|
631
|
+
const root = (_e = document.getElementById("instruckt-root")) != null ? _e : document.body;
|
|
580
632
|
root.appendChild(this.host);
|
|
581
633
|
}
|
|
582
|
-
makeBtn(iconHtml,
|
|
634
|
+
makeBtn(iconHtml, tooltip, onClick) {
|
|
583
635
|
const btn = document.createElement("button");
|
|
584
636
|
btn.className = "btn";
|
|
585
|
-
btn.
|
|
586
|
-
btn.setAttribute("aria-label",
|
|
637
|
+
btn.setAttribute("data-tooltip", tooltip);
|
|
638
|
+
btn.setAttribute("aria-label", tooltip);
|
|
587
639
|
btn.innerHTML = iconHtml;
|
|
588
640
|
btn.addEventListener("click", (e) => {
|
|
589
641
|
e.stopPropagation();
|
|
@@ -734,7 +786,953 @@ var ElementHighlight = class {
|
|
|
734
786
|
}
|
|
735
787
|
};
|
|
736
788
|
|
|
789
|
+
// node_modules/html-to-image/es/util.js
|
|
790
|
+
function resolveUrl(url, baseUrl) {
|
|
791
|
+
if (url.match(/^[a-z]+:\/\//i)) {
|
|
792
|
+
return url;
|
|
793
|
+
}
|
|
794
|
+
if (url.match(/^\/\//)) {
|
|
795
|
+
return window.location.protocol + url;
|
|
796
|
+
}
|
|
797
|
+
if (url.match(/^[a-z]+:/i)) {
|
|
798
|
+
return url;
|
|
799
|
+
}
|
|
800
|
+
const doc = document.implementation.createHTMLDocument();
|
|
801
|
+
const base = doc.createElement("base");
|
|
802
|
+
const a = doc.createElement("a");
|
|
803
|
+
doc.head.appendChild(base);
|
|
804
|
+
doc.body.appendChild(a);
|
|
805
|
+
if (baseUrl) {
|
|
806
|
+
base.href = baseUrl;
|
|
807
|
+
}
|
|
808
|
+
a.href = url;
|
|
809
|
+
return a.href;
|
|
810
|
+
}
|
|
811
|
+
var uuid = /* @__PURE__ */ (() => {
|
|
812
|
+
let counter = 0;
|
|
813
|
+
const random = () => (
|
|
814
|
+
// eslint-disable-next-line no-bitwise
|
|
815
|
+
`0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4)
|
|
816
|
+
);
|
|
817
|
+
return () => {
|
|
818
|
+
counter += 1;
|
|
819
|
+
return `u${random()}${counter}`;
|
|
820
|
+
};
|
|
821
|
+
})();
|
|
822
|
+
function toArray(arrayLike) {
|
|
823
|
+
const arr = [];
|
|
824
|
+
for (let i = 0, l = arrayLike.length; i < l; i++) {
|
|
825
|
+
arr.push(arrayLike[i]);
|
|
826
|
+
}
|
|
827
|
+
return arr;
|
|
828
|
+
}
|
|
829
|
+
var styleProps = null;
|
|
830
|
+
function getStyleProperties(options = {}) {
|
|
831
|
+
if (styleProps) {
|
|
832
|
+
return styleProps;
|
|
833
|
+
}
|
|
834
|
+
if (options.includeStyleProperties) {
|
|
835
|
+
styleProps = options.includeStyleProperties;
|
|
836
|
+
return styleProps;
|
|
837
|
+
}
|
|
838
|
+
styleProps = toArray(window.getComputedStyle(document.documentElement));
|
|
839
|
+
return styleProps;
|
|
840
|
+
}
|
|
841
|
+
function px(node, styleProperty) {
|
|
842
|
+
const win = node.ownerDocument.defaultView || window;
|
|
843
|
+
const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
|
|
844
|
+
return val ? parseFloat(val.replace("px", "")) : 0;
|
|
845
|
+
}
|
|
846
|
+
function getNodeWidth(node) {
|
|
847
|
+
const leftBorder = px(node, "border-left-width");
|
|
848
|
+
const rightBorder = px(node, "border-right-width");
|
|
849
|
+
return node.clientWidth + leftBorder + rightBorder;
|
|
850
|
+
}
|
|
851
|
+
function getNodeHeight(node) {
|
|
852
|
+
const topBorder = px(node, "border-top-width");
|
|
853
|
+
const bottomBorder = px(node, "border-bottom-width");
|
|
854
|
+
return node.clientHeight + topBorder + bottomBorder;
|
|
855
|
+
}
|
|
856
|
+
function getImageSize(targetNode, options = {}) {
|
|
857
|
+
const width = options.width || getNodeWidth(targetNode);
|
|
858
|
+
const height = options.height || getNodeHeight(targetNode);
|
|
859
|
+
return { width, height };
|
|
860
|
+
}
|
|
861
|
+
function getPixelRatio() {
|
|
862
|
+
let ratio;
|
|
863
|
+
let FINAL_PROCESS;
|
|
864
|
+
try {
|
|
865
|
+
FINAL_PROCESS = process;
|
|
866
|
+
} catch (e) {
|
|
867
|
+
}
|
|
868
|
+
const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
|
|
869
|
+
if (val) {
|
|
870
|
+
ratio = parseInt(val, 10);
|
|
871
|
+
if (Number.isNaN(ratio)) {
|
|
872
|
+
ratio = 1;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return ratio || window.devicePixelRatio || 1;
|
|
876
|
+
}
|
|
877
|
+
var canvasDimensionLimit = 16384;
|
|
878
|
+
function checkCanvasDimensions(canvas) {
|
|
879
|
+
if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
|
|
880
|
+
if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
|
|
881
|
+
if (canvas.width > canvas.height) {
|
|
882
|
+
canvas.height *= canvasDimensionLimit / canvas.width;
|
|
883
|
+
canvas.width = canvasDimensionLimit;
|
|
884
|
+
} else {
|
|
885
|
+
canvas.width *= canvasDimensionLimit / canvas.height;
|
|
886
|
+
canvas.height = canvasDimensionLimit;
|
|
887
|
+
}
|
|
888
|
+
} else if (canvas.width > canvasDimensionLimit) {
|
|
889
|
+
canvas.height *= canvasDimensionLimit / canvas.width;
|
|
890
|
+
canvas.width = canvasDimensionLimit;
|
|
891
|
+
} else {
|
|
892
|
+
canvas.width *= canvasDimensionLimit / canvas.height;
|
|
893
|
+
canvas.height = canvasDimensionLimit;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function createImage(url) {
|
|
898
|
+
return new Promise((resolve, reject) => {
|
|
899
|
+
const img = new Image();
|
|
900
|
+
img.onload = () => {
|
|
901
|
+
img.decode().then(() => {
|
|
902
|
+
requestAnimationFrame(() => resolve(img));
|
|
903
|
+
});
|
|
904
|
+
};
|
|
905
|
+
img.onerror = reject;
|
|
906
|
+
img.crossOrigin = "anonymous";
|
|
907
|
+
img.decoding = "async";
|
|
908
|
+
img.src = url;
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
async function svgToDataURL(svg) {
|
|
912
|
+
return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`);
|
|
913
|
+
}
|
|
914
|
+
async function nodeToDataURL(node, width, height) {
|
|
915
|
+
const xmlns = "http://www.w3.org/2000/svg";
|
|
916
|
+
const svg = document.createElementNS(xmlns, "svg");
|
|
917
|
+
const foreignObject = document.createElementNS(xmlns, "foreignObject");
|
|
918
|
+
svg.setAttribute("width", `${width}`);
|
|
919
|
+
svg.setAttribute("height", `${height}`);
|
|
920
|
+
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
921
|
+
foreignObject.setAttribute("width", "100%");
|
|
922
|
+
foreignObject.setAttribute("height", "100%");
|
|
923
|
+
foreignObject.setAttribute("x", "0");
|
|
924
|
+
foreignObject.setAttribute("y", "0");
|
|
925
|
+
foreignObject.setAttribute("externalResourcesRequired", "true");
|
|
926
|
+
svg.appendChild(foreignObject);
|
|
927
|
+
foreignObject.appendChild(node);
|
|
928
|
+
return svgToDataURL(svg);
|
|
929
|
+
}
|
|
930
|
+
var isInstanceOfElement = (node, instance) => {
|
|
931
|
+
if (node instanceof instance)
|
|
932
|
+
return true;
|
|
933
|
+
const nodePrototype = Object.getPrototypeOf(node);
|
|
934
|
+
if (nodePrototype === null)
|
|
935
|
+
return false;
|
|
936
|
+
return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
// node_modules/html-to-image/es/clone-pseudos.js
|
|
940
|
+
function formatCSSText(style) {
|
|
941
|
+
const content = style.getPropertyValue("content");
|
|
942
|
+
return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
|
|
943
|
+
}
|
|
944
|
+
function formatCSSProperties(style, options) {
|
|
945
|
+
return getStyleProperties(options).map((name) => {
|
|
946
|
+
const value = style.getPropertyValue(name);
|
|
947
|
+
const priority = style.getPropertyPriority(name);
|
|
948
|
+
return `${name}: ${value}${priority ? " !important" : ""};`;
|
|
949
|
+
}).join(" ");
|
|
950
|
+
}
|
|
951
|
+
function getPseudoElementStyle(className, pseudo, style, options) {
|
|
952
|
+
const selector = `.${className}:${pseudo}`;
|
|
953
|
+
const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
|
|
954
|
+
return document.createTextNode(`${selector}{${cssText}}`);
|
|
955
|
+
}
|
|
956
|
+
function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
|
|
957
|
+
const style = window.getComputedStyle(nativeNode, pseudo);
|
|
958
|
+
const content = style.getPropertyValue("content");
|
|
959
|
+
if (content === "" || content === "none") {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const className = uuid();
|
|
963
|
+
try {
|
|
964
|
+
clonedNode.className = `${clonedNode.className} ${className}`;
|
|
965
|
+
} catch (err) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const styleElement = document.createElement("style");
|
|
969
|
+
styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
|
|
970
|
+
clonedNode.appendChild(styleElement);
|
|
971
|
+
}
|
|
972
|
+
function clonePseudoElements(nativeNode, clonedNode, options) {
|
|
973
|
+
clonePseudoElement(nativeNode, clonedNode, ":before", options);
|
|
974
|
+
clonePseudoElement(nativeNode, clonedNode, ":after", options);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// node_modules/html-to-image/es/mimes.js
|
|
978
|
+
var WOFF = "application/font-woff";
|
|
979
|
+
var JPEG = "image/jpeg";
|
|
980
|
+
var mimes = {
|
|
981
|
+
woff: WOFF,
|
|
982
|
+
woff2: WOFF,
|
|
983
|
+
ttf: "application/font-truetype",
|
|
984
|
+
eot: "application/vnd.ms-fontobject",
|
|
985
|
+
png: "image/png",
|
|
986
|
+
jpg: JPEG,
|
|
987
|
+
jpeg: JPEG,
|
|
988
|
+
gif: "image/gif",
|
|
989
|
+
tiff: "image/tiff",
|
|
990
|
+
svg: "image/svg+xml",
|
|
991
|
+
webp: "image/webp"
|
|
992
|
+
};
|
|
993
|
+
function getExtension(url) {
|
|
994
|
+
const match = /\.([^./]*?)$/g.exec(url);
|
|
995
|
+
return match ? match[1] : "";
|
|
996
|
+
}
|
|
997
|
+
function getMimeType(url) {
|
|
998
|
+
const extension = getExtension(url).toLowerCase();
|
|
999
|
+
return mimes[extension] || "";
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// node_modules/html-to-image/es/dataurl.js
|
|
1003
|
+
function getContentFromDataUrl(dataURL) {
|
|
1004
|
+
return dataURL.split(/,/)[1];
|
|
1005
|
+
}
|
|
1006
|
+
function isDataUrl(url) {
|
|
1007
|
+
return url.search(/^(data:)/) !== -1;
|
|
1008
|
+
}
|
|
1009
|
+
function makeDataUrl(content, mimeType) {
|
|
1010
|
+
return `data:${mimeType};base64,${content}`;
|
|
1011
|
+
}
|
|
1012
|
+
async function fetchAsDataURL(url, init2, process2) {
|
|
1013
|
+
const res = await fetch(url, init2);
|
|
1014
|
+
if (res.status === 404) {
|
|
1015
|
+
throw new Error(`Resource "${res.url}" not found`);
|
|
1016
|
+
}
|
|
1017
|
+
const blob = await res.blob();
|
|
1018
|
+
return new Promise((resolve, reject) => {
|
|
1019
|
+
const reader = new FileReader();
|
|
1020
|
+
reader.onerror = reject;
|
|
1021
|
+
reader.onloadend = () => {
|
|
1022
|
+
try {
|
|
1023
|
+
resolve(process2({ res, result: reader.result }));
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
reject(error);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
reader.readAsDataURL(blob);
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
var cache = {};
|
|
1032
|
+
function getCacheKey(url, contentType, includeQueryParams) {
|
|
1033
|
+
let key = url.replace(/\?.*/, "");
|
|
1034
|
+
if (includeQueryParams) {
|
|
1035
|
+
key = url;
|
|
1036
|
+
}
|
|
1037
|
+
if (/ttf|otf|eot|woff2?/i.test(key)) {
|
|
1038
|
+
key = key.replace(/.*\//, "");
|
|
1039
|
+
}
|
|
1040
|
+
return contentType ? `[${contentType}]${key}` : key;
|
|
1041
|
+
}
|
|
1042
|
+
async function resourceToDataURL(resourceUrl, contentType, options) {
|
|
1043
|
+
const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
|
|
1044
|
+
if (cache[cacheKey] != null) {
|
|
1045
|
+
return cache[cacheKey];
|
|
1046
|
+
}
|
|
1047
|
+
if (options.cacheBust) {
|
|
1048
|
+
resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
|
|
1049
|
+
}
|
|
1050
|
+
let dataURL;
|
|
1051
|
+
try {
|
|
1052
|
+
const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => {
|
|
1053
|
+
if (!contentType) {
|
|
1054
|
+
contentType = res.headers.get("Content-Type") || "";
|
|
1055
|
+
}
|
|
1056
|
+
return getContentFromDataUrl(result);
|
|
1057
|
+
});
|
|
1058
|
+
dataURL = makeDataUrl(content, contentType);
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
dataURL = options.imagePlaceholder || "";
|
|
1061
|
+
let msg = `Failed to fetch resource: ${resourceUrl}`;
|
|
1062
|
+
if (error) {
|
|
1063
|
+
msg = typeof error === "string" ? error : error.message;
|
|
1064
|
+
}
|
|
1065
|
+
if (msg) {
|
|
1066
|
+
console.warn(msg);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
cache[cacheKey] = dataURL;
|
|
1070
|
+
return dataURL;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// node_modules/html-to-image/es/clone-node.js
|
|
1074
|
+
async function cloneCanvasElement(canvas) {
|
|
1075
|
+
const dataURL = canvas.toDataURL();
|
|
1076
|
+
if (dataURL === "data:,") {
|
|
1077
|
+
return canvas.cloneNode(false);
|
|
1078
|
+
}
|
|
1079
|
+
return createImage(dataURL);
|
|
1080
|
+
}
|
|
1081
|
+
async function cloneVideoElement(video, options) {
|
|
1082
|
+
if (video.currentSrc) {
|
|
1083
|
+
const canvas = document.createElement("canvas");
|
|
1084
|
+
const ctx = canvas.getContext("2d");
|
|
1085
|
+
canvas.width = video.clientWidth;
|
|
1086
|
+
canvas.height = video.clientHeight;
|
|
1087
|
+
ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
1088
|
+
const dataURL2 = canvas.toDataURL();
|
|
1089
|
+
return createImage(dataURL2);
|
|
1090
|
+
}
|
|
1091
|
+
const poster = video.poster;
|
|
1092
|
+
const contentType = getMimeType(poster);
|
|
1093
|
+
const dataURL = await resourceToDataURL(poster, contentType, options);
|
|
1094
|
+
return createImage(dataURL);
|
|
1095
|
+
}
|
|
1096
|
+
async function cloneIFrameElement(iframe, options) {
|
|
1097
|
+
var _a;
|
|
1098
|
+
try {
|
|
1099
|
+
if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
|
|
1100
|
+
return await cloneNode(iframe.contentDocument.body, options, true);
|
|
1101
|
+
}
|
|
1102
|
+
} catch (_b) {
|
|
1103
|
+
}
|
|
1104
|
+
return iframe.cloneNode(false);
|
|
1105
|
+
}
|
|
1106
|
+
async function cloneSingleNode(node, options) {
|
|
1107
|
+
if (isInstanceOfElement(node, HTMLCanvasElement)) {
|
|
1108
|
+
return cloneCanvasElement(node);
|
|
1109
|
+
}
|
|
1110
|
+
if (isInstanceOfElement(node, HTMLVideoElement)) {
|
|
1111
|
+
return cloneVideoElement(node, options);
|
|
1112
|
+
}
|
|
1113
|
+
if (isInstanceOfElement(node, HTMLIFrameElement)) {
|
|
1114
|
+
return cloneIFrameElement(node, options);
|
|
1115
|
+
}
|
|
1116
|
+
return node.cloneNode(isSVGElement(node));
|
|
1117
|
+
}
|
|
1118
|
+
var isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
|
|
1119
|
+
var isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
|
|
1120
|
+
async function cloneChildren(nativeNode, clonedNode, options) {
|
|
1121
|
+
var _a, _b;
|
|
1122
|
+
if (isSVGElement(clonedNode)) {
|
|
1123
|
+
return clonedNode;
|
|
1124
|
+
}
|
|
1125
|
+
let children = [];
|
|
1126
|
+
if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
|
|
1127
|
+
children = toArray(nativeNode.assignedNodes());
|
|
1128
|
+
} else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
|
|
1129
|
+
children = toArray(nativeNode.contentDocument.body.childNodes);
|
|
1130
|
+
} else {
|
|
1131
|
+
children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
|
|
1132
|
+
}
|
|
1133
|
+
if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
|
|
1134
|
+
return clonedNode;
|
|
1135
|
+
}
|
|
1136
|
+
await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => {
|
|
1137
|
+
if (clonedChild) {
|
|
1138
|
+
clonedNode.appendChild(clonedChild);
|
|
1139
|
+
}
|
|
1140
|
+
}), Promise.resolve());
|
|
1141
|
+
return clonedNode;
|
|
1142
|
+
}
|
|
1143
|
+
function cloneCSSStyle(nativeNode, clonedNode, options) {
|
|
1144
|
+
const targetStyle = clonedNode.style;
|
|
1145
|
+
if (!targetStyle) {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
1149
|
+
if (sourceStyle.cssText) {
|
|
1150
|
+
targetStyle.cssText = sourceStyle.cssText;
|
|
1151
|
+
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
1152
|
+
} else {
|
|
1153
|
+
getStyleProperties(options).forEach((name) => {
|
|
1154
|
+
let value = sourceStyle.getPropertyValue(name);
|
|
1155
|
+
if (name === "font-size" && value.endsWith("px")) {
|
|
1156
|
+
const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
|
|
1157
|
+
value = `${reducedFont}px`;
|
|
1158
|
+
}
|
|
1159
|
+
if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") {
|
|
1160
|
+
value = "block";
|
|
1161
|
+
}
|
|
1162
|
+
if (name === "d" && clonedNode.getAttribute("d")) {
|
|
1163
|
+
value = `path(${clonedNode.getAttribute("d")})`;
|
|
1164
|
+
}
|
|
1165
|
+
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function cloneInputValue(nativeNode, clonedNode) {
|
|
1170
|
+
if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
|
|
1171
|
+
clonedNode.innerHTML = nativeNode.value;
|
|
1172
|
+
}
|
|
1173
|
+
if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
|
|
1174
|
+
clonedNode.setAttribute("value", nativeNode.value);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
function cloneSelectValue(nativeNode, clonedNode) {
|
|
1178
|
+
if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
|
|
1179
|
+
const clonedSelect = clonedNode;
|
|
1180
|
+
const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value"));
|
|
1181
|
+
if (selectedOption) {
|
|
1182
|
+
selectedOption.setAttribute("selected", "");
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
function decorate(nativeNode, clonedNode, options) {
|
|
1187
|
+
if (isInstanceOfElement(clonedNode, Element)) {
|
|
1188
|
+
cloneCSSStyle(nativeNode, clonedNode, options);
|
|
1189
|
+
clonePseudoElements(nativeNode, clonedNode, options);
|
|
1190
|
+
cloneInputValue(nativeNode, clonedNode);
|
|
1191
|
+
cloneSelectValue(nativeNode, clonedNode);
|
|
1192
|
+
}
|
|
1193
|
+
return clonedNode;
|
|
1194
|
+
}
|
|
1195
|
+
async function ensureSVGSymbols(clone, options) {
|
|
1196
|
+
const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : [];
|
|
1197
|
+
if (uses.length === 0) {
|
|
1198
|
+
return clone;
|
|
1199
|
+
}
|
|
1200
|
+
const processedDefs = {};
|
|
1201
|
+
for (let i = 0; i < uses.length; i++) {
|
|
1202
|
+
const use = uses[i];
|
|
1203
|
+
const id = use.getAttribute("xlink:href");
|
|
1204
|
+
if (id) {
|
|
1205
|
+
const exist = clone.querySelector(id);
|
|
1206
|
+
const definition = document.querySelector(id);
|
|
1207
|
+
if (!exist && definition && !processedDefs[id]) {
|
|
1208
|
+
processedDefs[id] = await cloneNode(definition, options, true);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const nodes = Object.values(processedDefs);
|
|
1213
|
+
if (nodes.length) {
|
|
1214
|
+
const ns = "http://www.w3.org/1999/xhtml";
|
|
1215
|
+
const svg = document.createElementNS(ns, "svg");
|
|
1216
|
+
svg.setAttribute("xmlns", ns);
|
|
1217
|
+
svg.style.position = "absolute";
|
|
1218
|
+
svg.style.width = "0";
|
|
1219
|
+
svg.style.height = "0";
|
|
1220
|
+
svg.style.overflow = "hidden";
|
|
1221
|
+
svg.style.display = "none";
|
|
1222
|
+
const defs = document.createElementNS(ns, "defs");
|
|
1223
|
+
svg.appendChild(defs);
|
|
1224
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1225
|
+
defs.appendChild(nodes[i]);
|
|
1226
|
+
}
|
|
1227
|
+
clone.appendChild(svg);
|
|
1228
|
+
}
|
|
1229
|
+
return clone;
|
|
1230
|
+
}
|
|
1231
|
+
async function cloneNode(node, options, isRoot) {
|
|
1232
|
+
if (!isRoot && options.filter && !options.filter(node)) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
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));
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// node_modules/html-to-image/es/embed-resources.js
|
|
1239
|
+
var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
|
|
1240
|
+
var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
|
|
1241
|
+
var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
|
|
1242
|
+
function toRegex(url) {
|
|
1243
|
+
const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
|
|
1244
|
+
return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
|
|
1245
|
+
}
|
|
1246
|
+
function parseURLs(cssText) {
|
|
1247
|
+
const urls = [];
|
|
1248
|
+
cssText.replace(URL_REGEX, (raw, quotation, url) => {
|
|
1249
|
+
urls.push(url);
|
|
1250
|
+
return raw;
|
|
1251
|
+
});
|
|
1252
|
+
return urls.filter((url) => !isDataUrl(url));
|
|
1253
|
+
}
|
|
1254
|
+
async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
|
|
1255
|
+
try {
|
|
1256
|
+
const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
|
|
1257
|
+
const contentType = getMimeType(resourceURL);
|
|
1258
|
+
let dataURL;
|
|
1259
|
+
if (getContentFromUrl) {
|
|
1260
|
+
const content = await getContentFromUrl(resolvedURL);
|
|
1261
|
+
dataURL = makeDataUrl(content, contentType);
|
|
1262
|
+
} else {
|
|
1263
|
+
dataURL = await resourceToDataURL(resolvedURL, contentType, options);
|
|
1264
|
+
}
|
|
1265
|
+
return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
}
|
|
1268
|
+
return cssText;
|
|
1269
|
+
}
|
|
1270
|
+
function filterPreferredFontFormat(str, { preferredFontFormat }) {
|
|
1271
|
+
return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => {
|
|
1272
|
+
while (true) {
|
|
1273
|
+
const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
|
|
1274
|
+
if (!format) {
|
|
1275
|
+
return "";
|
|
1276
|
+
}
|
|
1277
|
+
if (format === preferredFontFormat) {
|
|
1278
|
+
return `src: ${src};`;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
function shouldEmbed(url) {
|
|
1284
|
+
return url.search(URL_REGEX) !== -1;
|
|
1285
|
+
}
|
|
1286
|
+
async function embedResources(cssText, baseUrl, options) {
|
|
1287
|
+
if (!shouldEmbed(cssText)) {
|
|
1288
|
+
return cssText;
|
|
1289
|
+
}
|
|
1290
|
+
const filteredCSSText = filterPreferredFontFormat(cssText, options);
|
|
1291
|
+
const urls = parseURLs(filteredCSSText);
|
|
1292
|
+
return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// node_modules/html-to-image/es/embed-images.js
|
|
1296
|
+
async function embedProp(propName, node, options) {
|
|
1297
|
+
var _a;
|
|
1298
|
+
const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
|
|
1299
|
+
if (propValue) {
|
|
1300
|
+
const cssString = await embedResources(propValue, null, options);
|
|
1301
|
+
node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
|
|
1302
|
+
return true;
|
|
1303
|
+
}
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
async function embedBackground(clonedNode, options) {
|
|
1307
|
+
;
|
|
1308
|
+
await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
|
|
1309
|
+
await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
|
|
1310
|
+
}
|
|
1311
|
+
async function embedImageNode(clonedNode, options) {
|
|
1312
|
+
const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
|
|
1313
|
+
if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
|
|
1317
|
+
const dataURL = await resourceToDataURL(url, getMimeType(url), options);
|
|
1318
|
+
await new Promise((resolve, reject) => {
|
|
1319
|
+
clonedNode.onload = resolve;
|
|
1320
|
+
clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
|
|
1321
|
+
try {
|
|
1322
|
+
resolve(options.onImageErrorHandler(...attributes));
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
reject(error);
|
|
1325
|
+
}
|
|
1326
|
+
} : reject;
|
|
1327
|
+
const image = clonedNode;
|
|
1328
|
+
if (image.decode) {
|
|
1329
|
+
image.decode = resolve;
|
|
1330
|
+
}
|
|
1331
|
+
if (image.loading === "lazy") {
|
|
1332
|
+
image.loading = "eager";
|
|
1333
|
+
}
|
|
1334
|
+
if (isImageElement) {
|
|
1335
|
+
clonedNode.srcset = "";
|
|
1336
|
+
clonedNode.src = dataURL;
|
|
1337
|
+
} else {
|
|
1338
|
+
clonedNode.href.baseVal = dataURL;
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
async function embedChildren(clonedNode, options) {
|
|
1343
|
+
const children = toArray(clonedNode.childNodes);
|
|
1344
|
+
const deferreds = children.map((child) => embedImages(child, options));
|
|
1345
|
+
await Promise.all(deferreds).then(() => clonedNode);
|
|
1346
|
+
}
|
|
1347
|
+
async function embedImages(clonedNode, options) {
|
|
1348
|
+
if (isInstanceOfElement(clonedNode, Element)) {
|
|
1349
|
+
await embedBackground(clonedNode, options);
|
|
1350
|
+
await embedImageNode(clonedNode, options);
|
|
1351
|
+
await embedChildren(clonedNode, options);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// node_modules/html-to-image/es/apply-style.js
|
|
1356
|
+
function applyStyle(node, options) {
|
|
1357
|
+
const { style } = node;
|
|
1358
|
+
if (options.backgroundColor) {
|
|
1359
|
+
style.backgroundColor = options.backgroundColor;
|
|
1360
|
+
}
|
|
1361
|
+
if (options.width) {
|
|
1362
|
+
style.width = `${options.width}px`;
|
|
1363
|
+
}
|
|
1364
|
+
if (options.height) {
|
|
1365
|
+
style.height = `${options.height}px`;
|
|
1366
|
+
}
|
|
1367
|
+
const manual = options.style;
|
|
1368
|
+
if (manual != null) {
|
|
1369
|
+
Object.keys(manual).forEach((key) => {
|
|
1370
|
+
style[key] = manual[key];
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
return node;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// node_modules/html-to-image/es/embed-webfonts.js
|
|
1377
|
+
var cssFetchCache = {};
|
|
1378
|
+
async function fetchCSS(url) {
|
|
1379
|
+
let cache2 = cssFetchCache[url];
|
|
1380
|
+
if (cache2 != null) {
|
|
1381
|
+
return cache2;
|
|
1382
|
+
}
|
|
1383
|
+
const res = await fetch(url);
|
|
1384
|
+
const cssText = await res.text();
|
|
1385
|
+
cache2 = { url, cssText };
|
|
1386
|
+
cssFetchCache[url] = cache2;
|
|
1387
|
+
return cache2;
|
|
1388
|
+
}
|
|
1389
|
+
async function embedFonts(data, options) {
|
|
1390
|
+
let cssText = data.cssText;
|
|
1391
|
+
const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
|
|
1392
|
+
const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
|
|
1393
|
+
const loadFonts = fontLocs.map(async (loc) => {
|
|
1394
|
+
let url = loc.replace(regexUrl, "$1");
|
|
1395
|
+
if (!url.startsWith("https://")) {
|
|
1396
|
+
url = new URL(url, data.url).href;
|
|
1397
|
+
}
|
|
1398
|
+
return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
|
|
1399
|
+
cssText = cssText.replace(loc, `url(${result})`);
|
|
1400
|
+
return [loc, result];
|
|
1401
|
+
});
|
|
1402
|
+
});
|
|
1403
|
+
return Promise.all(loadFonts).then(() => cssText);
|
|
1404
|
+
}
|
|
1405
|
+
function parseCSS(source) {
|
|
1406
|
+
if (source == null) {
|
|
1407
|
+
return [];
|
|
1408
|
+
}
|
|
1409
|
+
const result = [];
|
|
1410
|
+
const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
|
|
1411
|
+
let cssText = source.replace(commentsRegex, "");
|
|
1412
|
+
const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi");
|
|
1413
|
+
while (true) {
|
|
1414
|
+
const matches = keyframesRegex.exec(cssText);
|
|
1415
|
+
if (matches === null) {
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
result.push(matches[0]);
|
|
1419
|
+
}
|
|
1420
|
+
cssText = cssText.replace(keyframesRegex, "");
|
|
1421
|
+
const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
|
|
1422
|
+
const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})";
|
|
1423
|
+
const unifiedRegex = new RegExp(combinedCSSRegex, "gi");
|
|
1424
|
+
while (true) {
|
|
1425
|
+
let matches = importRegex.exec(cssText);
|
|
1426
|
+
if (matches === null) {
|
|
1427
|
+
matches = unifiedRegex.exec(cssText);
|
|
1428
|
+
if (matches === null) {
|
|
1429
|
+
break;
|
|
1430
|
+
} else {
|
|
1431
|
+
importRegex.lastIndex = unifiedRegex.lastIndex;
|
|
1432
|
+
}
|
|
1433
|
+
} else {
|
|
1434
|
+
unifiedRegex.lastIndex = importRegex.lastIndex;
|
|
1435
|
+
}
|
|
1436
|
+
result.push(matches[0]);
|
|
1437
|
+
}
|
|
1438
|
+
return result;
|
|
1439
|
+
}
|
|
1440
|
+
async function getCSSRules(styleSheets, options) {
|
|
1441
|
+
const ret = [];
|
|
1442
|
+
const deferreds = [];
|
|
1443
|
+
styleSheets.forEach((sheet) => {
|
|
1444
|
+
if ("cssRules" in sheet) {
|
|
1445
|
+
try {
|
|
1446
|
+
toArray(sheet.cssRules || []).forEach((item, index) => {
|
|
1447
|
+
if (item.type === CSSRule.IMPORT_RULE) {
|
|
1448
|
+
let importIndex = index + 1;
|
|
1449
|
+
const url = item.href;
|
|
1450
|
+
const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
|
|
1451
|
+
try {
|
|
1452
|
+
sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length);
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
console.error("Error inserting rule from remote css", {
|
|
1455
|
+
rule,
|
|
1456
|
+
error
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
})).catch((e) => {
|
|
1460
|
+
console.error("Error loading remote css", e.toString());
|
|
1461
|
+
});
|
|
1462
|
+
deferreds.push(deferred);
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
} catch (e) {
|
|
1466
|
+
const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
|
|
1467
|
+
if (sheet.href != null) {
|
|
1468
|
+
deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
|
|
1469
|
+
inline.insertRule(rule, inline.cssRules.length);
|
|
1470
|
+
})).catch((err) => {
|
|
1471
|
+
console.error("Error loading remote stylesheet", err);
|
|
1472
|
+
}));
|
|
1473
|
+
}
|
|
1474
|
+
console.error("Error inlining remote css file", e);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
return Promise.all(deferreds).then(() => {
|
|
1479
|
+
styleSheets.forEach((sheet) => {
|
|
1480
|
+
if ("cssRules" in sheet) {
|
|
1481
|
+
try {
|
|
1482
|
+
toArray(sheet.cssRules || []).forEach((item) => {
|
|
1483
|
+
ret.push(item);
|
|
1484
|
+
});
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
console.error(`Error while reading CSS rules from ${sheet.href}`, e);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
return ret;
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
function getWebFontRules(cssRules) {
|
|
1494
|
+
return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src")));
|
|
1495
|
+
}
|
|
1496
|
+
async function parseWebFontRules(node, options) {
|
|
1497
|
+
if (node.ownerDocument == null) {
|
|
1498
|
+
throw new Error("Provided element is not within a Document");
|
|
1499
|
+
}
|
|
1500
|
+
const styleSheets = toArray(node.ownerDocument.styleSheets);
|
|
1501
|
+
const cssRules = await getCSSRules(styleSheets, options);
|
|
1502
|
+
return getWebFontRules(cssRules);
|
|
1503
|
+
}
|
|
1504
|
+
function normalizeFontFamily(font) {
|
|
1505
|
+
return font.trim().replace(/["']/g, "");
|
|
1506
|
+
}
|
|
1507
|
+
function getUsedFonts(node) {
|
|
1508
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
1509
|
+
function traverse(node2) {
|
|
1510
|
+
const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
|
|
1511
|
+
fontFamily.split(",").forEach((font) => {
|
|
1512
|
+
fonts.add(normalizeFontFamily(font));
|
|
1513
|
+
});
|
|
1514
|
+
Array.from(node2.children).forEach((child) => {
|
|
1515
|
+
if (child instanceof HTMLElement) {
|
|
1516
|
+
traverse(child);
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
traverse(node);
|
|
1521
|
+
return fonts;
|
|
1522
|
+
}
|
|
1523
|
+
async function getWebFontCSS(node, options) {
|
|
1524
|
+
const rules = await parseWebFontRules(node, options);
|
|
1525
|
+
const usedFonts = getUsedFonts(node);
|
|
1526
|
+
const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
|
|
1527
|
+
const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
|
|
1528
|
+
return embedResources(rule.cssText, baseUrl, options);
|
|
1529
|
+
}));
|
|
1530
|
+
return cssTexts.join("\n");
|
|
1531
|
+
}
|
|
1532
|
+
async function embedWebFonts(clonedNode, options) {
|
|
1533
|
+
const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
|
|
1534
|
+
if (cssText) {
|
|
1535
|
+
const styleNode = document.createElement("style");
|
|
1536
|
+
const sytleContent = document.createTextNode(cssText);
|
|
1537
|
+
styleNode.appendChild(sytleContent);
|
|
1538
|
+
if (clonedNode.firstChild) {
|
|
1539
|
+
clonedNode.insertBefore(styleNode, clonedNode.firstChild);
|
|
1540
|
+
} else {
|
|
1541
|
+
clonedNode.appendChild(styleNode);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// node_modules/html-to-image/es/index.js
|
|
1547
|
+
async function toSvg(node, options = {}) {
|
|
1548
|
+
const { width, height } = getImageSize(node, options);
|
|
1549
|
+
const clonedNode = await cloneNode(node, options, true);
|
|
1550
|
+
await embedWebFonts(clonedNode, options);
|
|
1551
|
+
await embedImages(clonedNode, options);
|
|
1552
|
+
applyStyle(clonedNode, options);
|
|
1553
|
+
const datauri = await nodeToDataURL(clonedNode, width, height);
|
|
1554
|
+
return datauri;
|
|
1555
|
+
}
|
|
1556
|
+
async function toCanvas(node, options = {}) {
|
|
1557
|
+
const { width, height } = getImageSize(node, options);
|
|
1558
|
+
const svg = await toSvg(node, options);
|
|
1559
|
+
const img = await createImage(svg);
|
|
1560
|
+
const canvas = document.createElement("canvas");
|
|
1561
|
+
const context = canvas.getContext("2d");
|
|
1562
|
+
const ratio = options.pixelRatio || getPixelRatio();
|
|
1563
|
+
const canvasWidth = options.canvasWidth || width;
|
|
1564
|
+
const canvasHeight = options.canvasHeight || height;
|
|
1565
|
+
canvas.width = canvasWidth * ratio;
|
|
1566
|
+
canvas.height = canvasHeight * ratio;
|
|
1567
|
+
if (!options.skipAutoScale) {
|
|
1568
|
+
checkCanvasDimensions(canvas);
|
|
1569
|
+
}
|
|
1570
|
+
canvas.style.width = `${canvasWidth}`;
|
|
1571
|
+
canvas.style.height = `${canvasHeight}`;
|
|
1572
|
+
if (options.backgroundColor) {
|
|
1573
|
+
context.fillStyle = options.backgroundColor;
|
|
1574
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
1575
|
+
}
|
|
1576
|
+
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
1577
|
+
return canvas;
|
|
1578
|
+
}
|
|
1579
|
+
async function toPng(node, options = {}) {
|
|
1580
|
+
const canvas = await toCanvas(node, options);
|
|
1581
|
+
return canvas.toDataURL();
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// src/ui/screenshot.ts
|
|
1585
|
+
async function captureElement(el) {
|
|
1586
|
+
try {
|
|
1587
|
+
return await toPng(el, {
|
|
1588
|
+
cacheBust: true,
|
|
1589
|
+
pixelRatio: 2,
|
|
1590
|
+
skipFonts: true,
|
|
1591
|
+
filter: (node) => {
|
|
1592
|
+
var _a, _b;
|
|
1593
|
+
if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
|
|
1594
|
+
if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
|
|
1595
|
+
const href = (_b = node.getAttribute("href")) != null ? _b : "";
|
|
1596
|
+
if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
|
|
1597
|
+
}
|
|
1598
|
+
return true;
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
} catch (e) {
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
async function captureRegion(rect) {
|
|
1606
|
+
try {
|
|
1607
|
+
const full = await toPng(document.body, {
|
|
1608
|
+
cacheBust: true,
|
|
1609
|
+
pixelRatio: 2,
|
|
1610
|
+
skipFonts: true,
|
|
1611
|
+
filter: (node) => {
|
|
1612
|
+
var _a, _b;
|
|
1613
|
+
if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
|
|
1614
|
+
if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
|
|
1615
|
+
const href = (_b = node.getAttribute("href")) != null ? _b : "";
|
|
1616
|
+
if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
|
|
1617
|
+
}
|
|
1618
|
+
return true;
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
return await cropImage(full, rect);
|
|
1622
|
+
} catch (e) {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function cropImage(dataUrl, rect) {
|
|
1627
|
+
return new Promise((resolve, reject) => {
|
|
1628
|
+
const img = new Image();
|
|
1629
|
+
img.onload = () => {
|
|
1630
|
+
const ratio = 2;
|
|
1631
|
+
const canvas = document.createElement("canvas");
|
|
1632
|
+
canvas.width = rect.width * ratio;
|
|
1633
|
+
canvas.height = rect.height * ratio;
|
|
1634
|
+
const ctx = canvas.getContext("2d");
|
|
1635
|
+
ctx.drawImage(
|
|
1636
|
+
img,
|
|
1637
|
+
rect.x * ratio,
|
|
1638
|
+
rect.y * ratio,
|
|
1639
|
+
rect.width * ratio,
|
|
1640
|
+
rect.height * ratio,
|
|
1641
|
+
0,
|
|
1642
|
+
0,
|
|
1643
|
+
rect.width * ratio,
|
|
1644
|
+
rect.height * ratio
|
|
1645
|
+
);
|
|
1646
|
+
resolve(canvas.toDataURL("image/png"));
|
|
1647
|
+
};
|
|
1648
|
+
img.onerror = reject;
|
|
1649
|
+
img.src = dataUrl;
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function selectRegion() {
|
|
1653
|
+
return new Promise((resolve) => {
|
|
1654
|
+
const overlay = document.createElement("div");
|
|
1655
|
+
Object.assign(overlay.style, {
|
|
1656
|
+
position: "fixed",
|
|
1657
|
+
inset: "0",
|
|
1658
|
+
zIndex: "2147483647",
|
|
1659
|
+
cursor: "crosshair",
|
|
1660
|
+
background: "rgba(0,0,0,0.1)"
|
|
1661
|
+
});
|
|
1662
|
+
overlay.setAttribute("data-instruckt", "region-select");
|
|
1663
|
+
const box = document.createElement("div");
|
|
1664
|
+
Object.assign(box.style, {
|
|
1665
|
+
position: "fixed",
|
|
1666
|
+
border: "2px dashed #6366f1",
|
|
1667
|
+
background: "rgba(99,102,241,0.08)",
|
|
1668
|
+
borderRadius: "4px",
|
|
1669
|
+
display: "none",
|
|
1670
|
+
pointerEvents: "none"
|
|
1671
|
+
});
|
|
1672
|
+
overlay.appendChild(box);
|
|
1673
|
+
let startX = 0, startY = 0, dragging = false;
|
|
1674
|
+
const onMouseDown = (e) => {
|
|
1675
|
+
startX = e.clientX;
|
|
1676
|
+
startY = e.clientY;
|
|
1677
|
+
dragging = true;
|
|
1678
|
+
box.style.display = "block";
|
|
1679
|
+
updateBox(e);
|
|
1680
|
+
};
|
|
1681
|
+
const onMouseMove = (e) => {
|
|
1682
|
+
if (!dragging) return;
|
|
1683
|
+
updateBox(e);
|
|
1684
|
+
};
|
|
1685
|
+
const updateBox = (e) => {
|
|
1686
|
+
const x = Math.min(startX, e.clientX);
|
|
1687
|
+
const y = Math.min(startY, e.clientY);
|
|
1688
|
+
const w = Math.abs(e.clientX - startX);
|
|
1689
|
+
const h = Math.abs(e.clientY - startY);
|
|
1690
|
+
Object.assign(box.style, {
|
|
1691
|
+
left: `${x}px`,
|
|
1692
|
+
top: `${y}px`,
|
|
1693
|
+
width: `${w}px`,
|
|
1694
|
+
height: `${h}px`
|
|
1695
|
+
});
|
|
1696
|
+
};
|
|
1697
|
+
const onMouseUp = (e) => {
|
|
1698
|
+
if (!dragging) return;
|
|
1699
|
+
dragging = false;
|
|
1700
|
+
const x = Math.min(startX, e.clientX);
|
|
1701
|
+
const y = Math.min(startY, e.clientY);
|
|
1702
|
+
const w = Math.abs(e.clientX - startX);
|
|
1703
|
+
const h = Math.abs(e.clientY - startY);
|
|
1704
|
+
cleanup();
|
|
1705
|
+
if (w < 10 || h < 10) {
|
|
1706
|
+
resolve(null);
|
|
1707
|
+
} else {
|
|
1708
|
+
resolve(new DOMRect(x, y, w, h));
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
const onKeyDown = (e) => {
|
|
1712
|
+
if (e.key === "Escape") {
|
|
1713
|
+
cleanup();
|
|
1714
|
+
resolve(null);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
const cleanup = () => {
|
|
1718
|
+
overlay.remove();
|
|
1719
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
1720
|
+
};
|
|
1721
|
+
overlay.addEventListener("mousedown", onMouseDown);
|
|
1722
|
+
overlay.addEventListener("mousemove", onMouseMove);
|
|
1723
|
+
overlay.addEventListener("mouseup", onMouseUp);
|
|
1724
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
1725
|
+
document.body.appendChild(overlay);
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
|
|
737
1729
|
// src/ui/popup.ts
|
|
1730
|
+
function screenshotUrl(screenshot, endpoint) {
|
|
1731
|
+
if (!screenshot) return null;
|
|
1732
|
+
if (screenshot.startsWith("data:")) return screenshot;
|
|
1733
|
+
const base = endpoint != null ? endpoint : "/instruckt";
|
|
1734
|
+
return `${base}/${screenshot}`;
|
|
1735
|
+
}
|
|
738
1736
|
function esc(s) {
|
|
739
1737
|
return String(s != null ? s : "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
740
1738
|
}
|
|
@@ -750,7 +1748,7 @@ var AnnotationPopup = class {
|
|
|
750
1748
|
}
|
|
751
1749
|
// ── New annotation popup ──────────────────────────────────────
|
|
752
1750
|
showNew(pending, callbacks) {
|
|
753
|
-
var _a;
|
|
1751
|
+
var _a, _b;
|
|
754
1752
|
this.destroy();
|
|
755
1753
|
this.host = document.createElement("div");
|
|
756
1754
|
this.host.setAttribute("data-instruckt", "popup");
|
|
@@ -763,23 +1761,56 @@ var AnnotationPopup = class {
|
|
|
763
1761
|
popup.className = "popup";
|
|
764
1762
|
const fwBadge = pending.framework ? `<div class="fw-badge">${esc(pending.framework.component)}</div>` : "";
|
|
765
1763
|
const selText = pending.selectedText ? `<div class="selected-text">"${esc(pending.selectedText.slice(0, 80))}"</div>` : "";
|
|
1764
|
+
const hasScreenshot = !!pending.screenshot;
|
|
766
1765
|
popup.innerHTML = `
|
|
767
1766
|
<div class="header">
|
|
768
1767
|
<span class="element-tag" title="${esc(pending.elementPath)}">${esc(pending.elementLabel)}</span>
|
|
769
1768
|
<button class="close-btn" title="Cancel (Esc)">\u2715</button>
|
|
770
1769
|
</div>
|
|
771
1770
|
${fwBadge}${selText}
|
|
772
|
-
<
|
|
1771
|
+
<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>
|
|
1772
|
+
<textarea placeholder="${hasScreenshot ? "Add a note (optional)" : "What needs to change here?"}" rows="3"></textarea>
|
|
773
1773
|
<div class="actions">
|
|
774
1774
|
<button class="btn-secondary" data-action="cancel">Cancel</button>
|
|
775
|
-
<button class="btn-primary" data-action="submit" disabled>Add note</button>
|
|
1775
|
+
<button class="btn-primary" data-action="submit" ${hasScreenshot ? "" : "disabled"}>Add note</button>
|
|
776
1776
|
</div>
|
|
777
1777
|
`;
|
|
1778
|
+
let currentScreenshot = (_a = pending.screenshot) != null ? _a : null;
|
|
778
1779
|
const textarea = popup.querySelector("textarea");
|
|
779
1780
|
const submitBtn = popup.querySelector('[data-action="submit"]');
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1781
|
+
const screenshotSlot = popup.querySelector(".screenshot-slot");
|
|
1782
|
+
const updateSubmitState = () => {
|
|
1783
|
+
submitBtn.disabled = !currentScreenshot && textarea.value.trim().length === 0;
|
|
1784
|
+
};
|
|
1785
|
+
const attachScreenshotEvents = () => {
|
|
1786
|
+
const captureBtn = screenshotSlot.querySelector('[data-action="capture"]');
|
|
1787
|
+
captureBtn == null ? void 0 : captureBtn.addEventListener("click", async () => {
|
|
1788
|
+
captureBtn.textContent = "Capturing...";
|
|
1789
|
+
const dataUrl = await captureElement(pending.element);
|
|
1790
|
+
if (dataUrl) {
|
|
1791
|
+
currentScreenshot = dataUrl;
|
|
1792
|
+
screenshotSlot.innerHTML = `<div class="screenshot-preview"><img src="${dataUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>`;
|
|
1793
|
+
textarea.placeholder = "Add a note (optional)";
|
|
1794
|
+
attachScreenshotEvents();
|
|
1795
|
+
updateSubmitState();
|
|
1796
|
+
} else {
|
|
1797
|
+
captureBtn.textContent = "Capture failed";
|
|
1798
|
+
setTimeout(() => {
|
|
1799
|
+
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`;
|
|
1800
|
+
}, 1500);
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
const removeBtn = screenshotSlot.querySelector(".screenshot-remove");
|
|
1804
|
+
removeBtn == null ? void 0 : removeBtn.addEventListener("click", () => {
|
|
1805
|
+
currentScreenshot = null;
|
|
1806
|
+
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>`;
|
|
1807
|
+
textarea.placeholder = "What needs to change here?";
|
|
1808
|
+
attachScreenshotEvents();
|
|
1809
|
+
updateSubmitState();
|
|
1810
|
+
});
|
|
1811
|
+
};
|
|
1812
|
+
attachScreenshotEvents();
|
|
1813
|
+
textarea.addEventListener("input", updateSubmitState);
|
|
783
1814
|
textarea.addEventListener("keydown", (e) => {
|
|
784
1815
|
e.stopPropagation();
|
|
785
1816
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
@@ -801,18 +1832,18 @@ var AnnotationPopup = class {
|
|
|
801
1832
|
});
|
|
802
1833
|
submitBtn.addEventListener("click", () => {
|
|
803
1834
|
const comment = textarea.value.trim();
|
|
804
|
-
if (!comment) return;
|
|
805
|
-
callbacks.onSubmit({ comment });
|
|
1835
|
+
if (!comment && !currentScreenshot) return;
|
|
1836
|
+
callbacks.onSubmit({ comment: comment || "(screenshot)", screenshot: currentScreenshot != null ? currentScreenshot : void 0 });
|
|
806
1837
|
this.destroy();
|
|
807
1838
|
});
|
|
808
1839
|
this.shadow.appendChild(popup);
|
|
809
|
-
((
|
|
1840
|
+
((_b = document.getElementById("instruckt-root")) != null ? _b : document.body).appendChild(this.host);
|
|
810
1841
|
this.positionHost(pending.x, pending.y);
|
|
811
1842
|
this.setupOutsideClick();
|
|
812
1843
|
textarea.focus();
|
|
813
1844
|
}
|
|
814
1845
|
// ── Edit existing annotation ──────────────────────────────────
|
|
815
|
-
showEdit(annotation, callbacks) {
|
|
1846
|
+
showEdit(annotation, callbacks, endpoint) {
|
|
816
1847
|
var _a;
|
|
817
1848
|
this.destroy();
|
|
818
1849
|
this.host = document.createElement("div");
|
|
@@ -825,19 +1856,28 @@ var AnnotationPopup = class {
|
|
|
825
1856
|
const popup = document.createElement("div");
|
|
826
1857
|
popup.className = "popup";
|
|
827
1858
|
const fwBadge = annotation.framework ? `<div class="fw-badge">${esc(annotation.framework.component)}</div>` : "";
|
|
1859
|
+
const ssUrl = screenshotUrl(annotation.screenshot, endpoint);
|
|
1860
|
+
const screenshotPreview = ssUrl ? `<div class="screenshot-preview screenshot-slot"><img src="${ssUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>` : "";
|
|
1861
|
+
const commentText = annotation.comment === "(screenshot)" ? "" : annotation.comment;
|
|
828
1862
|
popup.innerHTML = `
|
|
829
1863
|
<div class="header">
|
|
830
1864
|
<span class="element-tag" title="${esc(annotation.elementPath)}">${esc(annotation.element)}</span>
|
|
831
1865
|
<button class="close-btn">\u2715</button>
|
|
832
1866
|
</div>
|
|
833
|
-
${fwBadge}
|
|
834
|
-
<textarea rows="3">${esc(
|
|
1867
|
+
${fwBadge}${screenshotPreview}
|
|
1868
|
+
<textarea rows="3">${esc(commentText)}</textarea>
|
|
835
1869
|
<div class="actions">
|
|
836
1870
|
<button class="btn-danger" data-action="delete">Remove</button>
|
|
837
1871
|
<button class="btn-primary" data-action="save">Save</button>
|
|
838
1872
|
</div>
|
|
839
1873
|
`;
|
|
840
1874
|
popup.querySelector(".close-btn").addEventListener("click", () => this.destroy());
|
|
1875
|
+
const ssRemoveBtn = popup.querySelector(".screenshot-remove");
|
|
1876
|
+
ssRemoveBtn == null ? void 0 : ssRemoveBtn.addEventListener("click", () => {
|
|
1877
|
+
callbacks.onSave(annotation, annotation.comment);
|
|
1878
|
+
const slot = popup.querySelector(".screenshot-slot");
|
|
1879
|
+
if (slot) slot.remove();
|
|
1880
|
+
});
|
|
841
1881
|
const textarea = popup.querySelector("textarea");
|
|
842
1882
|
const saveBtn = popup.querySelector('[data-action="save"]');
|
|
843
1883
|
const deleteBtn = popup.querySelector('[data-action="delete"]');
|
|
@@ -932,9 +1972,10 @@ var AnnotationMarkers = class {
|
|
|
932
1972
|
return;
|
|
933
1973
|
}
|
|
934
1974
|
const el = document.createElement("div");
|
|
935
|
-
|
|
1975
|
+
const ssClass = annotation.screenshot ? " has-screenshot" : "";
|
|
1976
|
+
el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
|
|
936
1977
|
el.textContent = String(index);
|
|
937
|
-
el.title = annotation.comment.slice(0, 60);
|
|
1978
|
+
el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
|
|
938
1979
|
el.style.pointerEvents = "all";
|
|
939
1980
|
el.style.left = `${annotation.x / 100 * window.innerWidth}px`;
|
|
940
1981
|
el.style.top = `${annotation.y - window.scrollY}px`;
|
|
@@ -952,13 +1993,13 @@ var AnnotationMarkers = class {
|
|
|
952
1993
|
this.updateStyle(marker.el, annotation);
|
|
953
1994
|
}
|
|
954
1995
|
updateStyle(el, annotation) {
|
|
955
|
-
|
|
956
|
-
el.
|
|
1996
|
+
const ssClass = annotation.screenshot ? " has-screenshot" : "";
|
|
1997
|
+
el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
|
|
1998
|
+
el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
|
|
957
1999
|
}
|
|
958
2000
|
statusClass(status) {
|
|
959
2001
|
if (status === "resolved") return "resolved";
|
|
960
2002
|
if (status === "dismissed") return "dismissed";
|
|
961
|
-
if (status === "acknowledged") return "acknowledged";
|
|
962
2003
|
return "";
|
|
963
2004
|
}
|
|
964
2005
|
/** Reposition all markers (e.g. after scroll or resize) */
|
|
@@ -1293,7 +2334,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1293
2334
|
e.stopImmediatePropagation();
|
|
1294
2335
|
};
|
|
1295
2336
|
this.boundClick = (e) => {
|
|
1296
|
-
var _a, _b, _c
|
|
2337
|
+
var _a, _b, _c;
|
|
1297
2338
|
const target = e.target;
|
|
1298
2339
|
if (this.isInstruckt(target)) return;
|
|
1299
2340
|
e.preventDefault();
|
|
@@ -1307,6 +2348,8 @@ var _Instruckt = class _Instruckt {
|
|
|
1307
2348
|
const nearbyText = getNearbyText(target) || void 0;
|
|
1308
2349
|
const boundingBox = getPageBoundingBox(target);
|
|
1309
2350
|
const framework = (_b = this.detectFramework(target)) != null ? _b : void 0;
|
|
2351
|
+
(_c = this.highlight) == null ? void 0 : _c.show(target);
|
|
2352
|
+
this.highlightLocked = true;
|
|
1310
2353
|
const pending = {
|
|
1311
2354
|
element: target,
|
|
1312
2355
|
elementPath,
|
|
@@ -1320,21 +2363,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1320
2363
|
nearbyText,
|
|
1321
2364
|
framework
|
|
1322
2365
|
};
|
|
1323
|
-
|
|
1324
|
-
this.highlightLocked = true;
|
|
1325
|
-
(_d = this.popup) == null ? void 0 : _d.showNew(pending, {
|
|
1326
|
-
onSubmit: (result) => {
|
|
1327
|
-
var _a2;
|
|
1328
|
-
this.highlightLocked = false;
|
|
1329
|
-
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
1330
|
-
this.submitAnnotation(pending, result.comment);
|
|
1331
|
-
},
|
|
1332
|
-
onCancel: () => {
|
|
1333
|
-
var _a2;
|
|
1334
|
-
this.highlightLocked = false;
|
|
1335
|
-
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
1336
|
-
}
|
|
1337
|
-
});
|
|
2366
|
+
this.showAnnotationPopup(pending);
|
|
1338
2367
|
};
|
|
1339
2368
|
this.config = __spreadValues({
|
|
1340
2369
|
adapters: ["livewire", "vue", "svelte", "react"],
|
|
@@ -1346,7 +2375,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1346
2375
|
this.init();
|
|
1347
2376
|
}
|
|
1348
2377
|
init() {
|
|
1349
|
-
injectGlobalStyles();
|
|
2378
|
+
injectGlobalStyles(this.config.colors);
|
|
1350
2379
|
if (this.config.theme !== "auto") {
|
|
1351
2380
|
document.documentElement.setAttribute("data-instruckt-theme", this.config.theme);
|
|
1352
2381
|
}
|
|
@@ -1357,11 +2386,12 @@ var _Instruckt = class _Instruckt {
|
|
|
1357
2386
|
onFreezeAnimations: (frozen) => {
|
|
1358
2387
|
this.setFrozen(frozen);
|
|
1359
2388
|
},
|
|
2389
|
+
onScreenshot: () => this.startRegionCapture(),
|
|
1360
2390
|
onCopy: () => this.copyToClipboard(true),
|
|
1361
2391
|
onClearPage: () => this.clearPage(),
|
|
1362
2392
|
onClearAll: () => this.clearEverything(),
|
|
1363
2393
|
onMinimize: (min) => this.onMinimize(min)
|
|
1364
|
-
});
|
|
2394
|
+
}, this.config.keys);
|
|
1365
2395
|
this.highlight = new ElementHighlight();
|
|
1366
2396
|
this.popup = new AnnotationPopup();
|
|
1367
2397
|
this.markers = new AnnotationMarkers((annotation) => this.onMarkerClick(annotation));
|
|
@@ -1374,7 +2404,6 @@ var _Instruckt = class _Instruckt {
|
|
|
1374
2404
|
setTimeout(() => this.reattach(), 0);
|
|
1375
2405
|
});
|
|
1376
2406
|
this.loadAnnotations();
|
|
1377
|
-
this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
|
|
1378
2407
|
this.syncMarkers();
|
|
1379
2408
|
}
|
|
1380
2409
|
makeToolbarCallbacks() {
|
|
@@ -1385,6 +2414,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1385
2414
|
onFreezeAnimations: (frozen) => {
|
|
1386
2415
|
this.setFrozen(frozen);
|
|
1387
2416
|
},
|
|
2417
|
+
onScreenshot: () => this.startRegionCapture(),
|
|
1388
2418
|
onCopy: () => this.copyToClipboard(true),
|
|
1389
2419
|
onClearPage: () => this.clearPage(),
|
|
1390
2420
|
onClearAll: () => this.clearEverything(),
|
|
@@ -1408,7 +2438,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1408
2438
|
if (wasMinimized) this.markers.setVisible(false);
|
|
1409
2439
|
const existing = document.getElementById("instruckt-global");
|
|
1410
2440
|
if (existing) existing.remove();
|
|
1411
|
-
injectGlobalStyles();
|
|
2441
|
+
injectGlobalStyles(this.config.colors);
|
|
1412
2442
|
this.syncMarkers();
|
|
1413
2443
|
if (wasAnnotating && !wasMinimized) this.setAnnotating(true);
|
|
1414
2444
|
}
|
|
@@ -1453,6 +2483,16 @@ var _Instruckt = class _Instruckt {
|
|
|
1453
2483
|
} catch (e) {
|
|
1454
2484
|
}
|
|
1455
2485
|
}
|
|
2486
|
+
/** Start or stop polling based on whether there are active annotations */
|
|
2487
|
+
updatePolling() {
|
|
2488
|
+
const hasActive = this.totalActiveCount() > 0;
|
|
2489
|
+
if (hasActive && !this.pollTimer) {
|
|
2490
|
+
this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
|
|
2491
|
+
} else if (!hasActive && this.pollTimer) {
|
|
2492
|
+
clearInterval(this.pollTimer);
|
|
2493
|
+
this.pollTimer = null;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
1456
2496
|
/** Poll API for status changes (e.g. agent resolved via MCP) */
|
|
1457
2497
|
async pollForChanges() {
|
|
1458
2498
|
try {
|
|
@@ -1489,6 +2529,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1489
2529
|
}
|
|
1490
2530
|
(_c = this.toolbar) == null ? void 0 : _c.setAnnotationCount(this.pageAnnotations().length);
|
|
1491
2531
|
(_d = this.toolbar) == null ? void 0 : _d.setTotalCount(this.totalActiveCount());
|
|
2532
|
+
this.updatePolling();
|
|
1492
2533
|
}
|
|
1493
2534
|
annotationPageKey(a) {
|
|
1494
2535
|
try {
|
|
@@ -1618,6 +2659,22 @@ var _Instruckt = class _Instruckt {
|
|
|
1618
2659
|
`;
|
|
1619
2660
|
document.head.appendChild(this.frozenStyleEl);
|
|
1620
2661
|
}
|
|
2662
|
+
showAnnotationPopup(pending) {
|
|
2663
|
+
var _a;
|
|
2664
|
+
(_a = this.popup) == null ? void 0 : _a.showNew(pending, {
|
|
2665
|
+
onSubmit: (result) => {
|
|
2666
|
+
var _a2;
|
|
2667
|
+
this.highlightLocked = false;
|
|
2668
|
+
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
2669
|
+
this.submitAnnotation(pending, result.comment, result.screenshot);
|
|
2670
|
+
},
|
|
2671
|
+
onCancel: () => {
|
|
2672
|
+
var _a2;
|
|
2673
|
+
this.highlightLocked = false;
|
|
2674
|
+
(_a2 = this.highlight) == null ? void 0 : _a2.hide();
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
1621
2678
|
attachAnnotateListeners() {
|
|
1622
2679
|
document.addEventListener("mousemove", this.boundMouseMove);
|
|
1623
2680
|
document.addEventListener("mouseleave", this.boundMouseLeave);
|
|
@@ -1638,6 +2695,39 @@ var _Instruckt = class _Instruckt {
|
|
|
1638
2695
|
if (!el || !(el instanceof Element)) return false;
|
|
1639
2696
|
return el.closest("[data-instruckt]") !== null;
|
|
1640
2697
|
}
|
|
2698
|
+
// ── Region screenshot ────────────────────────────────────────
|
|
2699
|
+
async startRegionCapture() {
|
|
2700
|
+
var _a, _b;
|
|
2701
|
+
const wasAnnotating = this.isAnnotating;
|
|
2702
|
+
if (wasAnnotating) this.setAnnotating(false);
|
|
2703
|
+
const rect = await selectRegion();
|
|
2704
|
+
if (!rect) {
|
|
2705
|
+
if (wasAnnotating) this.setAnnotating(true);
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
const screenshot = await captureRegion(rect);
|
|
2709
|
+
if (!screenshot) {
|
|
2710
|
+
if (wasAnnotating) this.setAnnotating(true);
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
const centerX = rect.x + rect.width / 2;
|
|
2714
|
+
const centerY = rect.y + rect.height / 2;
|
|
2715
|
+
const target = (_a = document.elementFromPoint(centerX, centerY)) != null ? _a : document.body;
|
|
2716
|
+
const pending = {
|
|
2717
|
+
element: target,
|
|
2718
|
+
elementPath: getElementSelector(target),
|
|
2719
|
+
elementName: getElementName(target),
|
|
2720
|
+
elementLabel: getElementLabel(target),
|
|
2721
|
+
cssClasses: getCssClasses(target),
|
|
2722
|
+
boundingBox: getPageBoundingBox(target),
|
|
2723
|
+
x: centerX,
|
|
2724
|
+
y: centerY,
|
|
2725
|
+
nearbyText: getNearbyText(target) || void 0,
|
|
2726
|
+
screenshot,
|
|
2727
|
+
framework: (_b = this.detectFramework(target)) != null ? _b : void 0
|
|
2728
|
+
};
|
|
2729
|
+
this.showAnnotationPopup(pending);
|
|
2730
|
+
}
|
|
1641
2731
|
// ── Framework detection ───────────────────────────────────────
|
|
1642
2732
|
detectFramework(el) {
|
|
1643
2733
|
var _a;
|
|
@@ -1661,7 +2751,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1661
2751
|
return null;
|
|
1662
2752
|
}
|
|
1663
2753
|
// ── Submit ────────────────────────────────────────────────────
|
|
1664
|
-
async submitAnnotation(pending, comment) {
|
|
2754
|
+
async submitAnnotation(pending, comment, screenshot) {
|
|
1665
2755
|
var _a, _b;
|
|
1666
2756
|
const payload = {
|
|
1667
2757
|
x: pending.x / window.innerWidth * 100,
|
|
@@ -1673,6 +2763,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1673
2763
|
boundingBox: pending.boundingBox,
|
|
1674
2764
|
selectedText: pending.selectedText,
|
|
1675
2765
|
nearbyText: pending.nearbyText,
|
|
2766
|
+
screenshot,
|
|
1676
2767
|
intent: "fix",
|
|
1677
2768
|
severity: "important",
|
|
1678
2769
|
framework: pending.framework,
|
|
@@ -1685,7 +2776,6 @@ var _Instruckt = class _Instruckt {
|
|
|
1685
2776
|
annotation = __spreadProps(__spreadValues({}, payload), {
|
|
1686
2777
|
id: crypto.randomUUID(),
|
|
1687
2778
|
status: "pending",
|
|
1688
|
-
thread: [],
|
|
1689
2779
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1690
2780
|
});
|
|
1691
2781
|
}
|
|
@@ -1714,7 +2804,7 @@ var _Instruckt = class _Instruckt {
|
|
|
1714
2804
|
}
|
|
1715
2805
|
this.removeAnnotation(a.id);
|
|
1716
2806
|
}
|
|
1717
|
-
});
|
|
2807
|
+
}, this.config.endpoint);
|
|
1718
2808
|
}
|
|
1719
2809
|
onAnnotationUpdated(updated) {
|
|
1720
2810
|
const idx = this.annotations.findIndex((a) => a.id === updated.id);
|
|
@@ -1756,19 +2846,24 @@ var _Instruckt = class _Instruckt {
|
|
|
1756
2846
|
}
|
|
1757
2847
|
// ── Keyboard ──────────────────────────────────────────────────
|
|
1758
2848
|
onKeydown(e) {
|
|
1759
|
-
var _a;
|
|
2849
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1760
2850
|
if ((_a = this.toolbar) == null ? void 0 : _a.isMinimized()) return;
|
|
1761
2851
|
const target = e.target;
|
|
1762
2852
|
if (["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) return;
|
|
1763
2853
|
if (target.closest('[contenteditable="true"]')) return;
|
|
1764
2854
|
if (this.isInstruckt(target)) return;
|
|
1765
|
-
|
|
2855
|
+
const keys = (_b = this.config.keys) != null ? _b : {};
|
|
2856
|
+
const noMod = !e.metaKey && !e.ctrlKey && !e.altKey;
|
|
2857
|
+
if (e.key === ((_c = keys.annotate) != null ? _c : "a") && noMod) {
|
|
1766
2858
|
this.setAnnotating(!this.isAnnotating);
|
|
1767
2859
|
}
|
|
1768
|
-
if (e.key ===
|
|
2860
|
+
if (e.key === ((_d = keys.freeze) != null ? _d : "f") && noMod) {
|
|
1769
2861
|
this.setFrozen(!this.isFrozen);
|
|
1770
2862
|
}
|
|
1771
|
-
if (e.key ===
|
|
2863
|
+
if (e.key === ((_e = keys.screenshot) != null ? _e : "c") && noMod) {
|
|
2864
|
+
this.startRegionCapture();
|
|
2865
|
+
}
|
|
2866
|
+
if (e.key === ((_f = keys.clearPage) != null ? _f : "x") && noMod) {
|
|
1772
2867
|
this.clearPage();
|
|
1773
2868
|
}
|
|
1774
2869
|
if (e.key === "Escape") {
|
|
@@ -1843,6 +2938,13 @@ No open annotations.`;
|
|
|
1843
2938
|
} else if (a.nearbyText) {
|
|
1844
2939
|
lines.push(`- Text: "${a.nearbyText.slice(0, 100)}"`);
|
|
1845
2940
|
}
|
|
2941
|
+
if (a.screenshot) {
|
|
2942
|
+
if (!a.screenshot.startsWith("data:")) {
|
|
2943
|
+
lines.push(`- Screenshot: \`storage/app/_instruckt/${a.screenshot}\``);
|
|
2944
|
+
} else {
|
|
2945
|
+
lines.push(`- Screenshot: attached`);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
1846
2948
|
lines.push("");
|
|
1847
2949
|
});
|
|
1848
2950
|
}
|