reflet-sdk 0.6.2 → 0.6.4
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/CHANGELOG.md +26 -0
- package/README.md +3 -1
- package/dist/{devtools-layer-DOECMj0S.js → devtools-layer-DgmM3RhW.js} +92 -28
- package/dist/devtools-layer-DgmM3RhW.js.map +1 -0
- package/dist/devtools-next.js +1 -1
- package/dist/devtools-vite.js +1 -1
- package/dist/feedback.d.ts +1 -0
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +778 -783
- package/dist/feedback.js.map +1 -1
- package/dist/{handler-C9tdKcl0.js → handler-BSNmdHXv.js} +11 -11
- package/dist/handler-BSNmdHXv.js.map +1 -0
- package/dist/{z-index-BL0SGZEP.js → react-context-Caq1fodf.js} +13 -12
- package/dist/react-context-Caq1fodf.js.map +1 -0
- package/dist/react.js +1 -1
- package/dist/{picker-DCoAFX5y.js → submit-Cd-g1Dtg.js} +228 -124
- package/dist/submit-Cd-g1Dtg.js.map +1 -0
- package/package.json +1 -1
- package/dist/devtools-layer-DOECMj0S.js.map +0 -1
- package/dist/handler-C9tdKcl0.js.map +0 -1
- package/dist/picker-DCoAFX5y.js.map +0 -1
- package/dist/z-index-BL0SGZEP.js.map +0 -1
|
@@ -172,6 +172,14 @@ function buildSnapdomOptions(options) {
|
|
|
172
172
|
reconcile: true
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
+
/** snapdom's `clip: "viewport"` box, same fallbacks: the layout viewport minus classic (Windows, Linux) scrollbars. */
|
|
176
|
+
function capturedViewportSize() {
|
|
177
|
+
const root = document.documentElement;
|
|
178
|
+
return {
|
|
179
|
+
height: root.clientHeight || window.innerHeight,
|
|
180
|
+
width: root.clientWidth || window.innerWidth
|
|
181
|
+
};
|
|
182
|
+
}
|
|
175
183
|
function fitWithin(size, maxWidth) {
|
|
176
184
|
if (size.width <= maxWidth) return {
|
|
177
185
|
height: size.height,
|
|
@@ -809,7 +817,7 @@ function collectPageContext(options) {
|
|
|
809
817
|
}
|
|
810
818
|
//#endregion
|
|
811
819
|
//#region src/feedback/types.ts
|
|
812
|
-
const SDK_VERSION = "0.6.
|
|
820
|
+
const SDK_VERSION = "0.6.4";
|
|
813
821
|
const DEFAULT_WIDGET_LABELS = {
|
|
814
822
|
annotateHint: "Draw on the screenshot to point at the problem.",
|
|
815
823
|
attachElement: "Attach this element",
|
|
@@ -835,6 +843,7 @@ const DEFAULT_WIDGET_LABELS = {
|
|
|
835
843
|
minimize: "Minimize feedback",
|
|
836
844
|
moreOptions: "More options",
|
|
837
845
|
moveFeedback: "Move feedback",
|
|
846
|
+
openScreenshot: "Open screenshot",
|
|
838
847
|
pickAnother: "Pick another",
|
|
839
848
|
pickElement: "Point at an element",
|
|
840
849
|
pickElementHint: "Pick the element you are talking about",
|
|
@@ -1038,6 +1047,46 @@ function BlurIcon() {
|
|
|
1038
1047
|
}), /* @__PURE__ */ jsx("path", { d: "M8 8h8v8H8z" })] });
|
|
1039
1048
|
}
|
|
1040
1049
|
//#endregion
|
|
1050
|
+
//#region src/feedback/ui/keyboard.ts
|
|
1051
|
+
const APPLE_PLATFORM = /Mac|iPhone|iPad/;
|
|
1052
|
+
const LATIN_LETTER_OR_DIGIT = /^[a-z0-9]$/i;
|
|
1053
|
+
const LETTER = /^[a-z]$/;
|
|
1054
|
+
const DIGIT = /^\d$/;
|
|
1055
|
+
const IME_PROCESSING_KEY_CODE = 229;
|
|
1056
|
+
function isApplePlatform(platform = typeof navigator === "undefined" ? "" : navigator.platform) {
|
|
1057
|
+
return APPLE_PLATFORM.test(platform);
|
|
1058
|
+
}
|
|
1059
|
+
function physicalCodeFor(key) {
|
|
1060
|
+
if (LETTER.test(key)) return `Key${key.toUpperCase()}`;
|
|
1061
|
+
return DIGIT.test(key) ? `Digit${key}` : null;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* ⌥F types "ƒ" on macOS and Ctrl+C types "с" on a Cyrillic layout: when the
|
|
1065
|
+
* layout produced no Latin character, the physical key is what was meant.
|
|
1066
|
+
*/
|
|
1067
|
+
function pressesKey(event, key) {
|
|
1068
|
+
if (event.key.toLowerCase() === key) return true;
|
|
1069
|
+
return !LATIN_LETTER_OR_DIGIT.test(event.key) && event.code !== "" && event.code === physicalCodeFor(key);
|
|
1070
|
+
}
|
|
1071
|
+
function matchesHotkey(event, hotkey, platform) {
|
|
1072
|
+
const parts = hotkey.toLowerCase().split("+").map((part) => part.trim());
|
|
1073
|
+
const key = parts.at(-1);
|
|
1074
|
+
if (!(key && pressesKey(event, key))) return false;
|
|
1075
|
+
const isApple = isApplePlatform(platform);
|
|
1076
|
+
const wantsMod = parts.includes("mod");
|
|
1077
|
+
const expected = {
|
|
1078
|
+
alt: parts.includes("alt") || parts.includes("option"),
|
|
1079
|
+
ctrl: parts.includes("ctrl") || wantsMod && !isApple,
|
|
1080
|
+
meta: parts.includes("meta") || parts.includes("cmd") || wantsMod && isApple,
|
|
1081
|
+
shift: parts.includes("shift")
|
|
1082
|
+
};
|
|
1083
|
+
return event.altKey === expected.alt && event.ctrlKey === expected.ctrl && event.metaKey === expected.meta && event.shiftKey === expected.shift;
|
|
1084
|
+
}
|
|
1085
|
+
/** Enter that sends, not the Enter that confirms an IME composition (Japanese, Chinese, Korean input). */
|
|
1086
|
+
function isSubmitEnter(event) {
|
|
1087
|
+
return event.key === "Enter" && !event.shiftKey && !event.isComposing && event.keyCode !== IME_PROCESSING_KEY_CODE;
|
|
1088
|
+
}
|
|
1089
|
+
//#endregion
|
|
1041
1090
|
//#region src/feedback/ui/widget-events.ts
|
|
1042
1091
|
const REPLAYED_EVENTS = [
|
|
1043
1092
|
"keydown",
|
|
@@ -1510,104 +1559,6 @@ function useFloatingPosition(corner = "bottom-right", isPanelOpen = false, { anc
|
|
|
1510
1559
|
};
|
|
1511
1560
|
}
|
|
1512
1561
|
//#endregion
|
|
1513
|
-
//#region src/feedback/core/submission/upload-screenshots.ts
|
|
1514
|
-
async function uploadImage(transport, image) {
|
|
1515
|
-
const { uploadUrl } = await transport.getScreenshotUploadUrl();
|
|
1516
|
-
return transport.uploadImage(uploadUrl, image);
|
|
1517
|
-
}
|
|
1518
|
-
async function prepareScreenshot(transport, screenshot, index) {
|
|
1519
|
-
const { image, annotations, context } = screenshot;
|
|
1520
|
-
const annotated = annotations.length > 0 ? await renderAnnotatedImage(image, annotations) : null;
|
|
1521
|
-
if (annotations.length > 0 && !annotated) throw new Error("Could not render your annotations. Please try again.");
|
|
1522
|
-
try {
|
|
1523
|
-
const storageId = await uploadImage(transport, image);
|
|
1524
|
-
return {
|
|
1525
|
-
annotatedStorageId: annotated ? await uploadImage(transport, annotated) : void 0,
|
|
1526
|
-
annotations: annotations.length > 0 ? toWireAnnotations(annotations) : void 0,
|
|
1527
|
-
captureSource: "widget",
|
|
1528
|
-
filename: `screenshot-${index + 1}.png`,
|
|
1529
|
-
height: image.height,
|
|
1530
|
-
mimeType: image.mimeType,
|
|
1531
|
-
pageUrl: context.url,
|
|
1532
|
-
size: image.blob.size,
|
|
1533
|
-
storageId,
|
|
1534
|
-
width: image.width
|
|
1535
|
-
};
|
|
1536
|
-
} finally {
|
|
1537
|
-
releaseCapture(annotated);
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
async function uploadScreenshots(transport, submission) {
|
|
1541
|
-
return (await Promise.allSettled(submission.screenshots.map((screenshot, index) => prepareScreenshot(transport, screenshot, index)))).map((result) => {
|
|
1542
|
-
if (result.status === "rejected") throw result.reason;
|
|
1543
|
-
return result.value;
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
//#endregion
|
|
1547
|
-
//#region src/feedback/core/submit.ts
|
|
1548
|
-
const MAX_TITLE_LENGTH = 100;
|
|
1549
|
-
const NEWLINE = /\r?\n/;
|
|
1550
|
-
function deriveTitle(message) {
|
|
1551
|
-
const firstLine = message.split(NEWLINE).map((line) => line.trim()).find(Boolean) ?? "";
|
|
1552
|
-
if (firstLine.length <= MAX_TITLE_LENGTH) return firstLine;
|
|
1553
|
-
const clipped = firstLine.slice(0, 99);
|
|
1554
|
-
const lastSpace = clipped.lastIndexOf(" ");
|
|
1555
|
-
return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`;
|
|
1556
|
-
}
|
|
1557
|
-
function buildDescription(params) {
|
|
1558
|
-
const { email, isAnonymous, message } = params;
|
|
1559
|
-
const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
|
|
1560
|
-
return `${message.trim()}${contact}`;
|
|
1561
|
-
}
|
|
1562
|
-
async function attachPreparedScreenshots(transport, result) {
|
|
1563
|
-
const results = await Promise.allSettled(result.pendingScreenshots.map((screenshot) => transport.saveScreenshot({
|
|
1564
|
-
...screenshot,
|
|
1565
|
-
feedbackId: result.feedbackId
|
|
1566
|
-
})));
|
|
1567
|
-
const pendingScreenshots = result.pendingScreenshots.filter((_, index) => results[index]?.status === "rejected");
|
|
1568
|
-
return {
|
|
1569
|
-
feedbackId: result.feedbackId,
|
|
1570
|
-
pendingScreenshots
|
|
1571
|
-
};
|
|
1572
|
-
}
|
|
1573
|
-
async function createReportWithScreenshots(transport, submission, pendingScreenshots) {
|
|
1574
|
-
const { feedbackId } = await transport.create({
|
|
1575
|
-
context: submission.context,
|
|
1576
|
-
description: buildDescription(submission),
|
|
1577
|
-
title: deriveTitle(submission.message)
|
|
1578
|
-
});
|
|
1579
|
-
return attachPreparedScreenshots(transport, {
|
|
1580
|
-
feedbackId,
|
|
1581
|
-
pendingScreenshots
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
|
-
async function submitWidgetFeedback(transport, submission) {
|
|
1585
|
-
if (!deriveTitle(submission.message)) throw new Error("Please describe your feedback before sending it.");
|
|
1586
|
-
return createReportWithScreenshots(transport, submission, await uploadScreenshots(transport, submission));
|
|
1587
|
-
}
|
|
1588
|
-
function isStorageResponse(value) {
|
|
1589
|
-
return typeof value === "object" && value !== null && typeof Reflect.get(value, "storageId") === "string";
|
|
1590
|
-
}
|
|
1591
|
-
async function uploadImageBlob(uploadUrl, image) {
|
|
1592
|
-
const response = await fetch(uploadUrl, {
|
|
1593
|
-
body: image.blob,
|
|
1594
|
-
headers: { "Content-Type": image.mimeType },
|
|
1595
|
-
method: "POST"
|
|
1596
|
-
});
|
|
1597
|
-
if (!response.ok) throw new Error(`Screenshot upload failed (${response.status})`);
|
|
1598
|
-
const payload = await response.json();
|
|
1599
|
-
if (!isStorageResponse(payload)) throw new Error("Screenshot upload returned no storage id");
|
|
1600
|
-
return payload.storageId;
|
|
1601
|
-
}
|
|
1602
|
-
function createFeedbackTransport(client) {
|
|
1603
|
-
return {
|
|
1604
|
-
create: (params) => client.create(params),
|
|
1605
|
-
getScreenshotUploadUrl: () => client.getScreenshotUploadUrl(),
|
|
1606
|
-
saveScreenshot: (params) => client.saveScreenshot(params),
|
|
1607
|
-
uploadImage: uploadImageBlob
|
|
1608
|
-
};
|
|
1609
|
-
}
|
|
1610
|
-
//#endregion
|
|
1611
1562
|
//#region src/feedback/ui/floating/element-walk.ts
|
|
1612
1563
|
const ARROW_SIBLING = {
|
|
1613
1564
|
ArrowLeft: "previousElementSibling",
|
|
@@ -1665,7 +1616,6 @@ function stepAim(from, key, backwards) {
|
|
|
1665
1616
|
//#region src/feedback/ui/picker.tsx
|
|
1666
1617
|
const LABEL_HEIGHT = 24;
|
|
1667
1618
|
const LABEL_GAP = 6;
|
|
1668
|
-
const NOTE_WIDTH = 300;
|
|
1669
1619
|
const NOTE_GAP = 10;
|
|
1670
1620
|
const EDGE_GAP = 12;
|
|
1671
1621
|
function elementUnder(x, y) {
|
|
@@ -1682,35 +1632,68 @@ function describeTarget(element) {
|
|
|
1682
1632
|
region: describeRegion(element)
|
|
1683
1633
|
};
|
|
1684
1634
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1635
|
+
function noteCardPosition(rect, view, card) {
|
|
1636
|
+
const viewLeft = view.left + EDGE_GAP;
|
|
1637
|
+
const viewRight = view.left + view.width - EDGE_GAP;
|
|
1638
|
+
const viewTop = view.top + EDGE_GAP;
|
|
1639
|
+
const viewBottom = view.top + view.height - EDGE_GAP;
|
|
1640
|
+
const fitLeft = (left) => Math.max(viewLeft, Math.min(left, viewRight - card.width));
|
|
1641
|
+
const fitTop = (top) => Math.max(viewTop, Math.min(top, viewBottom - card.height));
|
|
1642
|
+
const below = rect.bottom + NOTE_GAP;
|
|
1643
|
+
if (below >= viewTop && below + card.height <= viewBottom) return {
|
|
1644
|
+
left: fitLeft(rect.left),
|
|
1645
|
+
top: below
|
|
1646
|
+
};
|
|
1647
|
+
const above = rect.top - NOTE_GAP - card.height;
|
|
1648
|
+
if (above >= viewTop && rect.top <= viewBottom) return {
|
|
1649
|
+
left: fitLeft(rect.left),
|
|
1650
|
+
top: above
|
|
1651
|
+
};
|
|
1652
|
+
const besideTop = fitTop(rect.top);
|
|
1653
|
+
const right = rect.right + NOTE_GAP;
|
|
1654
|
+
if (right + card.width <= viewRight) return {
|
|
1655
|
+
left: right,
|
|
1656
|
+
top: besideTop
|
|
1657
|
+
};
|
|
1658
|
+
const left = rect.left - NOTE_GAP - card.width;
|
|
1659
|
+
if (left >= viewLeft) return {
|
|
1696
1660
|
left,
|
|
1697
|
-
top:
|
|
1661
|
+
top: besideTop
|
|
1698
1662
|
};
|
|
1699
1663
|
return {
|
|
1700
|
-
|
|
1701
|
-
|
|
1664
|
+
left: fitLeft(rect.left),
|
|
1665
|
+
top: fitTop(Math.min(rect.bottom, viewBottom) - card.height)
|
|
1702
1666
|
};
|
|
1703
1667
|
}
|
|
1704
|
-
function
|
|
1668
|
+
function labelTop(rect, view) {
|
|
1669
|
+
const above = rect.top - LABEL_HEIGHT - LABEL_GAP;
|
|
1670
|
+
if (above >= view.top) return above;
|
|
1671
|
+
const below = rect.bottom + LABEL_GAP;
|
|
1672
|
+
if (below + LABEL_HEIGHT <= view.top + view.height) return below;
|
|
1673
|
+
return Math.max(rect.top, view.top) + LABEL_GAP;
|
|
1674
|
+
}
|
|
1675
|
+
function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
|
|
1705
1676
|
const [target, setTarget] = useState(null);
|
|
1706
1677
|
const [pinned, setPinned] = useState(null);
|
|
1707
1678
|
const [note, setNote] = useState("");
|
|
1679
|
+
const [noteSize, setNoteSize] = useState({
|
|
1680
|
+
height: 0,
|
|
1681
|
+
width: 0
|
|
1682
|
+
});
|
|
1708
1683
|
const pickerRef = useRef(null);
|
|
1684
|
+
const noteCardRef = useRef(null);
|
|
1709
1685
|
const noteRef = useRef(null);
|
|
1710
1686
|
const pinnedRef = useRef(false);
|
|
1711
1687
|
const [view, setView] = useState(visibleViewport);
|
|
1712
1688
|
const aimRef = useRef(null);
|
|
1713
1689
|
useEffect(() => onViewportChange(() => setView(visibleViewport())), []);
|
|
1690
|
+
useLayoutEffect(() => {
|
|
1691
|
+
const card = noteCardRef.current;
|
|
1692
|
+
if (pinned && card) setNoteSize({
|
|
1693
|
+
height: card.offsetHeight,
|
|
1694
|
+
width: card.offsetWidth
|
|
1695
|
+
});
|
|
1696
|
+
}, [pinned]);
|
|
1714
1697
|
useEffect(() => {
|
|
1715
1698
|
pinnedRef.current = pinned !== null;
|
|
1716
1699
|
if (pinned) noteRef.current?.focus({ preventScroll: true });
|
|
@@ -1748,7 +1731,8 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1748
1731
|
const onPointerUp = (event) => {
|
|
1749
1732
|
if (startsInWidget(event)) return;
|
|
1750
1733
|
swallow(event);
|
|
1751
|
-
|
|
1734
|
+
const isPrimaryPress = event.button === 0;
|
|
1735
|
+
if (pinnedRef.current || !isPrimaryPress) return;
|
|
1752
1736
|
const element = elementUnder(event.clientX, event.clientY);
|
|
1753
1737
|
if (element) pin(element, event.shiftKey);
|
|
1754
1738
|
};
|
|
@@ -1770,13 +1754,21 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1770
1754
|
}
|
|
1771
1755
|
onCancel();
|
|
1772
1756
|
};
|
|
1757
|
+
const copyAimed = (event) => {
|
|
1758
|
+
const aimed = aimRef.current;
|
|
1759
|
+
if (!(copy && aimed && matchesHotkey(event, "mod+c"))) return false;
|
|
1760
|
+
swallow(event);
|
|
1761
|
+
copy.onCopy(aimed, "");
|
|
1762
|
+
return true;
|
|
1763
|
+
};
|
|
1773
1764
|
const onKeyDown = (event) => {
|
|
1765
|
+
if (event.isComposing) return;
|
|
1774
1766
|
if (event.key === "Escape") {
|
|
1775
1767
|
swallow(event);
|
|
1776
1768
|
onEscape();
|
|
1777
1769
|
return;
|
|
1778
1770
|
}
|
|
1779
|
-
if (pinnedRef.current || startsInWidget(event)) return;
|
|
1771
|
+
if (pinnedRef.current || startsInWidget(event) || copyAimed(event)) return;
|
|
1780
1772
|
if (event.key === "Enter") {
|
|
1781
1773
|
swallow(event);
|
|
1782
1774
|
if (aimRef.current) pin(aimRef.current, event.shiftKey);
|
|
@@ -1802,6 +1794,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1802
1794
|
window.addEventListener("mousedown", swallow, true);
|
|
1803
1795
|
window.addEventListener("mouseup", swallow, true);
|
|
1804
1796
|
window.addEventListener("click", swallow, true);
|
|
1797
|
+
window.addEventListener("contextmenu", swallow, true);
|
|
1805
1798
|
const stopListeningToKeys = listenToKeydown(pickerRef.current, onKeyDown, true);
|
|
1806
1799
|
window.addEventListener("scroll", onScroll, {
|
|
1807
1800
|
capture: true,
|
|
@@ -1816,14 +1809,19 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1816
1809
|
window.removeEventListener("mousedown", swallow, true);
|
|
1817
1810
|
window.removeEventListener("mouseup", swallow, true);
|
|
1818
1811
|
window.removeEventListener("click", swallow, true);
|
|
1812
|
+
window.removeEventListener("contextmenu", swallow, true);
|
|
1819
1813
|
stopListeningToKeys();
|
|
1820
1814
|
window.removeEventListener("scroll", onScroll, true);
|
|
1821
1815
|
document.body.style.cursor = previousCursor;
|
|
1822
1816
|
};
|
|
1823
|
-
}, [
|
|
1817
|
+
}, [
|
|
1818
|
+
copy,
|
|
1819
|
+
inspect,
|
|
1820
|
+
onCancel
|
|
1821
|
+
]);
|
|
1824
1822
|
const rect = (pinned ?? target)?.rect;
|
|
1825
|
-
const labelAbove = rect ? rect.top > 30 : true;
|
|
1826
1823
|
return /* @__PURE__ */ jsxs("div", {
|
|
1824
|
+
className: "picker",
|
|
1827
1825
|
ref: pickerRef,
|
|
1828
1826
|
children: [
|
|
1829
1827
|
rect && /* @__PURE__ */ jsx("div", {
|
|
@@ -1837,7 +1835,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1837
1835
|
}),
|
|
1838
1836
|
rect && target && !pinned && /* @__PURE__ */ jsxs("div", {
|
|
1839
1837
|
className: "picker-label",
|
|
1840
|
-
style: { translate: `${Math.max(4, rect.left)}px ${
|
|
1838
|
+
style: { translate: `${Math.max(4, rect.left)}px ${labelTop(rect, view)}px` },
|
|
1841
1839
|
children: [/* @__PURE__ */ jsx("strong", { children: target.componentName ? `<${target.componentName}>` : target.label }), target.region && /* @__PURE__ */ jsx("span", { children: target.region })]
|
|
1842
1840
|
}),
|
|
1843
1841
|
pinned ? /* @__PURE__ */ jsxs("form", {
|
|
@@ -1846,7 +1844,8 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1846
1844
|
event.preventDefault();
|
|
1847
1845
|
onPick(pinned.element, note.trim());
|
|
1848
1846
|
},
|
|
1849
|
-
|
|
1847
|
+
ref: noteCardRef,
|
|
1848
|
+
style: noteCardPosition(pinned.rect, view, noteSize),
|
|
1850
1849
|
children: [
|
|
1851
1850
|
/* @__PURE__ */ jsxs("p", {
|
|
1852
1851
|
className: "picker-note-target",
|
|
@@ -1857,7 +1856,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1857
1856
|
maxLength: MAX_SELECTION_COMMENT_LENGTH,
|
|
1858
1857
|
onChange: (event) => setNote(event.target.value),
|
|
1859
1858
|
onKeyDown: (event) => {
|
|
1860
|
-
if (event.
|
|
1859
|
+
if (isSubmitEnter(event.nativeEvent)) {
|
|
1861
1860
|
event.preventDefault();
|
|
1862
1861
|
onPick(pinned.element, note.trim());
|
|
1863
1862
|
}
|
|
@@ -1885,6 +1884,12 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1885
1884
|
type: "button",
|
|
1886
1885
|
children: inspect.label
|
|
1887
1886
|
}),
|
|
1887
|
+
copy && /* @__PURE__ */ jsx("button", {
|
|
1888
|
+
className: "picker-cancel",
|
|
1889
|
+
onClick: () => copy.onCopy(pinned.element, note.trim()),
|
|
1890
|
+
type: "button",
|
|
1891
|
+
children: copy.label
|
|
1892
|
+
}),
|
|
1888
1893
|
/* @__PURE__ */ jsx("button", {
|
|
1889
1894
|
"aria-label": labels.attachElement,
|
|
1890
1895
|
className: "submit",
|
|
@@ -1905,6 +1910,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1905
1910
|
/* @__PURE__ */ jsx("kbd", { children: "Tab" }),
|
|
1906
1911
|
/* @__PURE__ */ jsx("kbd", { children: "Enter" }),
|
|
1907
1912
|
inspect && /* @__PURE__ */ jsx("kbd", { children: "⇧ Click" }),
|
|
1913
|
+
copy && /* @__PURE__ */ jsx("kbd", { children: isApplePlatform() ? "⌘C" : "Ctrl C" }),
|
|
1908
1914
|
/* @__PURE__ */ jsx("kbd", { children: "Esc" })
|
|
1909
1915
|
]
|
|
1910
1916
|
}), /* @__PURE__ */ jsx("button", {
|
|
@@ -1918,6 +1924,104 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
|
|
|
1918
1924
|
});
|
|
1919
1925
|
}
|
|
1920
1926
|
//#endregion
|
|
1921
|
-
|
|
1927
|
+
//#region src/feedback/core/submission/upload-screenshots.ts
|
|
1928
|
+
async function uploadImage(transport, image) {
|
|
1929
|
+
const { uploadUrl } = await transport.getScreenshotUploadUrl();
|
|
1930
|
+
return transport.uploadImage(uploadUrl, image);
|
|
1931
|
+
}
|
|
1932
|
+
async function prepareScreenshot(transport, screenshot, index) {
|
|
1933
|
+
const { image, annotations, context } = screenshot;
|
|
1934
|
+
const annotated = annotations.length > 0 ? await renderAnnotatedImage(image, annotations) : null;
|
|
1935
|
+
if (annotations.length > 0 && !annotated) throw new Error("Could not render your annotations. Please try again.");
|
|
1936
|
+
try {
|
|
1937
|
+
const storageId = await uploadImage(transport, image);
|
|
1938
|
+
return {
|
|
1939
|
+
annotatedStorageId: annotated ? await uploadImage(transport, annotated) : void 0,
|
|
1940
|
+
annotations: annotations.length > 0 ? toWireAnnotations(annotations) : void 0,
|
|
1941
|
+
captureSource: "widget",
|
|
1942
|
+
filename: `screenshot-${index + 1}.png`,
|
|
1943
|
+
height: image.height,
|
|
1944
|
+
mimeType: image.mimeType,
|
|
1945
|
+
pageUrl: context.url,
|
|
1946
|
+
size: image.blob.size,
|
|
1947
|
+
storageId,
|
|
1948
|
+
width: image.width
|
|
1949
|
+
};
|
|
1950
|
+
} finally {
|
|
1951
|
+
releaseCapture(annotated);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
async function uploadScreenshots(transport, submission) {
|
|
1955
|
+
return (await Promise.allSettled(submission.screenshots.map((screenshot, index) => prepareScreenshot(transport, screenshot, index)))).map((result) => {
|
|
1956
|
+
if (result.status === "rejected") throw result.reason;
|
|
1957
|
+
return result.value;
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
//#endregion
|
|
1961
|
+
//#region src/feedback/core/submit.ts
|
|
1962
|
+
const MAX_TITLE_LENGTH = 100;
|
|
1963
|
+
const NEWLINE = /\r?\n/;
|
|
1964
|
+
function deriveTitle(message) {
|
|
1965
|
+
const firstLine = message.split(NEWLINE).map((line) => line.trim()).find(Boolean) ?? "";
|
|
1966
|
+
if (firstLine.length <= MAX_TITLE_LENGTH) return firstLine;
|
|
1967
|
+
const clipped = firstLine.slice(0, 99);
|
|
1968
|
+
const lastSpace = clipped.lastIndexOf(" ");
|
|
1969
|
+
return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`;
|
|
1970
|
+
}
|
|
1971
|
+
function buildDescription(params) {
|
|
1972
|
+
const { email, isAnonymous, message } = params;
|
|
1973
|
+
const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
|
|
1974
|
+
return `${message.trim()}${contact}`;
|
|
1975
|
+
}
|
|
1976
|
+
async function attachPreparedScreenshots(transport, result) {
|
|
1977
|
+
const results = await Promise.allSettled(result.pendingScreenshots.map((screenshot) => transport.saveScreenshot({
|
|
1978
|
+
...screenshot,
|
|
1979
|
+
feedbackId: result.feedbackId
|
|
1980
|
+
})));
|
|
1981
|
+
const pendingScreenshots = result.pendingScreenshots.filter((_, index) => results[index]?.status === "rejected");
|
|
1982
|
+
return {
|
|
1983
|
+
feedbackId: result.feedbackId,
|
|
1984
|
+
pendingScreenshots
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
async function createReportWithScreenshots(transport, submission, pendingScreenshots) {
|
|
1988
|
+
const { feedbackId } = await transport.create({
|
|
1989
|
+
context: submission.context,
|
|
1990
|
+
description: buildDescription(submission),
|
|
1991
|
+
title: deriveTitle(submission.message)
|
|
1992
|
+
});
|
|
1993
|
+
return attachPreparedScreenshots(transport, {
|
|
1994
|
+
feedbackId,
|
|
1995
|
+
pendingScreenshots
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
async function submitWidgetFeedback(transport, submission) {
|
|
1999
|
+
if (!deriveTitle(submission.message)) throw new Error("Please describe your feedback before sending it.");
|
|
2000
|
+
return createReportWithScreenshots(transport, submission, await uploadScreenshots(transport, submission));
|
|
2001
|
+
}
|
|
2002
|
+
function isStorageResponse(value) {
|
|
2003
|
+
return typeof value === "object" && value !== null && typeof Reflect.get(value, "storageId") === "string";
|
|
2004
|
+
}
|
|
2005
|
+
async function uploadImageBlob(uploadUrl, image) {
|
|
2006
|
+
const response = await fetch(uploadUrl, {
|
|
2007
|
+
body: image.blob,
|
|
2008
|
+
headers: { "Content-Type": image.mimeType },
|
|
2009
|
+
method: "POST"
|
|
2010
|
+
});
|
|
2011
|
+
if (!response.ok) throw new Error(`Screenshot upload failed (${response.status})`);
|
|
2012
|
+
const payload = await response.json();
|
|
2013
|
+
if (!isStorageResponse(payload)) throw new Error("Screenshot upload returned no storage id");
|
|
2014
|
+
return payload.storageId;
|
|
2015
|
+
}
|
|
2016
|
+
function createFeedbackTransport(client) {
|
|
2017
|
+
return {
|
|
2018
|
+
create: (params) => client.create(params),
|
|
2019
|
+
getScreenshotUploadUrl: () => client.getScreenshotUploadUrl(),
|
|
2020
|
+
saveScreenshot: (params) => client.saveScreenshot(params),
|
|
2021
|
+
uploadImage: uploadImageBlob
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
//#endregion
|
|
2025
|
+
export { SquareIcon as A, collectPageContext as B, ChevronDownIcon as C, MoreIcon as D, HighlightIcon as E, UndoIcon as F, resolveComponentStack as G, buildElementSelection as H, isDegenerate as I, captureViewport as J, shortenSourcePath as K, normalizeRect as L, TargetIcon as M, TextIcon as N, PencilIcon as O, TrashIcon as P, DEFAULT_WIDGET_LABELS as R, CheckIcon as S, GripIcon as T, describeElement as U, MAX_SELECTION_COMMENT_LENGTH as V, getFiberFromNode as W, capturedViewportSize as X, capturedFromCanvas as Y, releaseCapture as Z, matchesHotkey as _, uploadImageBlob as a, CameraIcon as b, useFloatingPosition as c, SelectionOutline as d, drawAnnotations as f, isSubmitEnter as g, stopClicksAtHost as h, submitWidgetFeedback as i, Svg as j, SpotlightIcon as k, onViewportChange as l, listenToKeydown as m, createFeedbackTransport as n, ElementPicker as o, listenOnPageAndWidget as p, WIDGET_MARKER as q, deriveTitle as r, isWidgetOwned as s, attachPreparedScreenshots as t, visibleViewport as u, ArrowIcon as v, CloseIcon as w, ChatIcon as x, BlurIcon as y, SDK_VERSION as z };
|
|
1922
2026
|
|
|
1923
|
-
//# sourceMappingURL=
|
|
2027
|
+
//# sourceMappingURL=submit-Cd-g1Dtg.js.map
|