spaps-issue-reporting-react 0.6.1 → 0.6.3
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 +13 -1
- package/README.md +1 -1
- package/dist/{VoiceCapture-WX7J6BWQ.mjs → VoiceCapture-HLUVONFL.mjs} +18 -0
- package/dist/index.js +161 -75
- package/dist/index.mjs +131 -72
- package/dist/screenshot-capture.js +13 -4
- package/dist/screenshot-capture.mjs +13 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,13 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
- No changes yet.
|
|
6
6
|
|
|
7
|
+
## [0.6.2] - 2026-06-20
|
|
8
|
+
|
|
9
|
+
- Maintenance: align release metadata with the package currently published on
|
|
10
|
+
npm. The registry now serves `spaps-issue-reporting-react@0.6.2`, so
|
|
11
|
+
downstream consumers should install from the registry instead of the earlier
|
|
12
|
+
local proof tarballs.
|
|
13
|
+
|
|
14
|
+
## [0.6.1] - 2026-06-18
|
|
15
|
+
|
|
16
|
+
- Maintenance: align package README metadata and changelog top version with the
|
|
17
|
+
package version.
|
|
18
|
+
|
|
7
19
|
## [0.6.0] - 2026-06-07
|
|
8
20
|
|
|
9
21
|
- Lazy-load voice input (`@elevenlabs/react`) behind a dynamic `import()` so consumers no longer eager-load `livekit-client` (~492KB raw / ~140KB gzip) on first paint. The Scribe voice-capture UI/logic now lives in a code-split `VoiceCapture` chunk that is only fetched when the reporter switches to voice input (`inputMode === "voice"` and voice is permitted); text-only reporting never touches `@elevenlabs`/livekit. Public API (`IssueReportingProvider`, `FloatingIssueReportButton`, `IssueReportingPageConfig`, `ReportableSection`, `useIssueReporting`) and behavior are unchanged.
|
|
10
22
|
- Added opt-in automatic same-origin DOM screenshot capture through `IssueReportingProvider.screenshotCapture`. Capture lazy-loads `html2canvas` only during create submit when enabled, uploads through the host client's `issueReporting.uploadAttachment(file, { filename })`, records coarse `target.metadata.screenshot_capture` status, and continues issue submission without a screenshot if capture/render/upload fails.
|
|
11
23
|
- Added the public `spaps-issue-reporting-react/screenshot-capture` subpath so app-local adapters such as custom ref-area tools can reuse the shared capture, masking, compression, and filename logic without copying generic `html2canvas` code.
|
|
12
24
|
- Added optional `html2canvas >=1.4.0` peer metadata and documented the privacy controls: `data-spaps-screenshot-exclude`, `data-spaps-private`, host `excludeSelectors`, host `maskSelectors`, visible-viewport capture, private SPAPS attachment storage, 10 MiB file limit, and five-attachment cap.
|
|
13
|
-
-
|
|
25
|
+
- Initial release handoff used `/tmp/spaps-issue-reporting-react-0.6.0-normalized.tgz`, with `spaps-types@1.5.0` and `spaps-sdk@1.11.0` already available on npm. The registry publication step has since been superseded by the `0.6.2` release.
|
|
14
26
|
|
|
15
27
|
## [0.4.2] - 2026-06-04
|
|
16
28
|
|
package/README.md
CHANGED
|
@@ -400,7 +400,7 @@ and revisit trigger.
|
|
|
400
400
|
## Metadata
|
|
401
401
|
|
|
402
402
|
- `package_name`: `spaps-issue-reporting-react`
|
|
403
|
-
- `latest_version`: `0.6.
|
|
403
|
+
- `latest_version`: `0.6.2`
|
|
404
404
|
- `minimum_runtime`: `Node.js >=18.0.0`
|
|
405
405
|
- `api_base_url`: `https://api.sweetpotato.dev`
|
|
406
406
|
|
|
@@ -141,6 +141,7 @@ function VoiceCapture({
|
|
|
141
141
|
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = useState(null);
|
|
142
142
|
const [voiceError, setVoiceError] = useState(null);
|
|
143
143
|
const startInFlightRef = useRef(false);
|
|
144
|
+
const mountedRef = useRef(true);
|
|
144
145
|
const lastStartRequestId = useRef(0);
|
|
145
146
|
const lastAppendRequestId = useRef(0);
|
|
146
147
|
const {
|
|
@@ -168,6 +169,12 @@ function VoiceCapture({
|
|
|
168
169
|
useEffect(() => {
|
|
169
170
|
onSubmitMetadataChange(voiceSubmitMetadata);
|
|
170
171
|
}, [onSubmitMetadataChange, voiceSubmitMetadata]);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
mountedRef.current = true;
|
|
174
|
+
return () => {
|
|
175
|
+
mountedRef.current = false;
|
|
176
|
+
};
|
|
177
|
+
}, []);
|
|
171
178
|
useEffect(() => {
|
|
172
179
|
return () => {
|
|
173
180
|
disconnectScribe();
|
|
@@ -185,6 +192,9 @@ function VoiceCapture({
|
|
|
185
192
|
setVoiceError(null);
|
|
186
193
|
try {
|
|
187
194
|
const tokenResult = await createVoiceToken();
|
|
195
|
+
if (!mountedRef.current) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
188
198
|
setVoiceTokenResult(tokenResult);
|
|
189
199
|
setVoiceSubmitMetadata(tokenResult);
|
|
190
200
|
await connectScribe({
|
|
@@ -192,13 +202,21 @@ function VoiceCapture({
|
|
|
192
202
|
modelId: tokenResult.model_id,
|
|
193
203
|
microphone: voice.microphone
|
|
194
204
|
});
|
|
205
|
+
if (!mountedRef.current) {
|
|
206
|
+
disconnectScribe();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
195
209
|
onSelectVoice();
|
|
196
210
|
} catch (startError) {
|
|
211
|
+
if (!mountedRef.current) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
197
214
|
setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
|
|
198
215
|
} finally {
|
|
199
216
|
startInFlightRef.current = false;
|
|
200
217
|
}
|
|
201
218
|
}, [
|
|
219
|
+
disconnectScribe,
|
|
202
220
|
connectScribe,
|
|
203
221
|
createVoiceToken,
|
|
204
222
|
isSubmitting,
|
package/dist/index.js
CHANGED
|
@@ -174,6 +174,7 @@ function VoiceCapture({
|
|
|
174
174
|
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react5.useState)(null);
|
|
175
175
|
const [voiceError, setVoiceError] = (0, import_react5.useState)(null);
|
|
176
176
|
const startInFlightRef = (0, import_react5.useRef)(false);
|
|
177
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
177
178
|
const lastStartRequestId = (0, import_react5.useRef)(0);
|
|
178
179
|
const lastAppendRequestId = (0, import_react5.useRef)(0);
|
|
179
180
|
const {
|
|
@@ -201,6 +202,12 @@ function VoiceCapture({
|
|
|
201
202
|
(0, import_react5.useEffect)(() => {
|
|
202
203
|
onSubmitMetadataChange(voiceSubmitMetadata);
|
|
203
204
|
}, [onSubmitMetadataChange, voiceSubmitMetadata]);
|
|
205
|
+
(0, import_react5.useEffect)(() => {
|
|
206
|
+
mountedRef.current = true;
|
|
207
|
+
return () => {
|
|
208
|
+
mountedRef.current = false;
|
|
209
|
+
};
|
|
210
|
+
}, []);
|
|
204
211
|
(0, import_react5.useEffect)(() => {
|
|
205
212
|
return () => {
|
|
206
213
|
disconnectScribe();
|
|
@@ -218,6 +225,9 @@ function VoiceCapture({
|
|
|
218
225
|
setVoiceError(null);
|
|
219
226
|
try {
|
|
220
227
|
const tokenResult = await createVoiceToken();
|
|
228
|
+
if (!mountedRef.current) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
221
231
|
setVoiceTokenResult(tokenResult);
|
|
222
232
|
setVoiceSubmitMetadata(tokenResult);
|
|
223
233
|
await connectScribe({
|
|
@@ -225,13 +235,21 @@ function VoiceCapture({
|
|
|
225
235
|
modelId: tokenResult.model_id,
|
|
226
236
|
microphone: voice.microphone
|
|
227
237
|
});
|
|
238
|
+
if (!mountedRef.current) {
|
|
239
|
+
disconnectScribe();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
228
242
|
onSelectVoice();
|
|
229
243
|
} catch (startError) {
|
|
244
|
+
if (!mountedRef.current) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
230
247
|
setVoiceError(resolveErrorMessage(startError, "Failed to start voice input."));
|
|
231
248
|
} finally {
|
|
232
249
|
startInFlightRef.current = false;
|
|
233
250
|
}
|
|
234
251
|
}, [
|
|
252
|
+
disconnectScribe,
|
|
235
253
|
connectScribe,
|
|
236
254
|
createVoiceToken,
|
|
237
255
|
isSubmitting,
|
|
@@ -360,7 +378,7 @@ async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
|
|
|
360
378
|
let currentCanvas = canvas;
|
|
361
379
|
let scaled = false;
|
|
362
380
|
for (const scale of [0.85, 0.7, 0.55, 0.4]) {
|
|
363
|
-
currentCanvas = scaleCanvas(
|
|
381
|
+
currentCanvas = scaleCanvas(canvas, scale);
|
|
364
382
|
scaled = true;
|
|
365
383
|
for (const format of fallbackTypes) {
|
|
366
384
|
for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
|
|
@@ -447,9 +465,14 @@ async function captureScreenshot(config, context) {
|
|
|
447
465
|
function resetHtml2CanvasCache() {
|
|
448
466
|
html2canvasPromise = null;
|
|
449
467
|
}
|
|
450
|
-
function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors =
|
|
468
|
+
function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = []) {
|
|
451
469
|
clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
|
|
452
|
-
|
|
470
|
+
for (const selector of [...DEFAULT_MASK_SELECTORS, ...maskSelectors]) {
|
|
471
|
+
try {
|
|
472
|
+
clonedDocument.querySelectorAll(selector).forEach(maskCaptureElement);
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
}
|
|
453
476
|
redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
|
|
454
477
|
}
|
|
455
478
|
function canUseBrowserCapture() {
|
|
@@ -564,7 +587,11 @@ var init_screenshot_capture = __esm({
|
|
|
564
587
|
"option",
|
|
565
588
|
"select",
|
|
566
589
|
"textarea",
|
|
567
|
-
|
|
590
|
+
// Match every editable region, not just the explicit `contenteditable="true"`
|
|
591
|
+
// form. Bare (`contenteditable`), empty-string (`contenteditable=""`), and
|
|
592
|
+
// `contenteditable="plaintext-only"` editors are all editable and must be
|
|
593
|
+
// masked; only the explicitly disabled `contenteditable="false"` is excluded.
|
|
594
|
+
'[contenteditable]:not([contenteditable="false"])'
|
|
568
595
|
];
|
|
569
596
|
SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
|
|
570
597
|
ATTACHMENT_MAX_BYTES2 = 10 * 1024 * 1024;
|
|
@@ -2026,6 +2053,15 @@ function useAttachmentState(existingAttachments) {
|
|
|
2026
2053
|
);
|
|
2027
2054
|
const [validationErrors, setValidationErrors] = (0, import_react7.useState)([]);
|
|
2028
2055
|
const [uploadProgress, setUploadProgress] = (0, import_react7.useState)(INITIAL_UPLOAD_PROGRESS);
|
|
2056
|
+
const pendingFilesRef = (0, import_react7.useRef)(pendingFiles);
|
|
2057
|
+
pendingFilesRef.current = pendingFiles;
|
|
2058
|
+
(0, import_react7.useEffect)(() => {
|
|
2059
|
+
return () => {
|
|
2060
|
+
for (const pf of pendingFilesRef.current) {
|
|
2061
|
+
URL.revokeObjectURL(pf.previewUrl);
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
}, []);
|
|
2029
2065
|
const retainedExistingCount = existingAttachments.filter(
|
|
2030
2066
|
(a) => !removedExistingIds.has(a.id)
|
|
2031
2067
|
).length;
|
|
@@ -2342,19 +2378,26 @@ function IssueReportModeBanner() {
|
|
|
2342
2378
|
if (!reportMode?.isReportMode) {
|
|
2343
2379
|
return null;
|
|
2344
2380
|
}
|
|
2345
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
"
|
|
2350
|
-
{
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2381
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2382
|
+
"div",
|
|
2383
|
+
{
|
|
2384
|
+
"data-spaps-screenshot-exclude": "",
|
|
2385
|
+
className: cn2("fixed inset-x-4 top-4 flex justify-center", Z_BANNER),
|
|
2386
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
|
|
2387
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-medium", children: copy.reportModeTitle }),
|
|
2388
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
|
|
2389
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2390
|
+
"button",
|
|
2391
|
+
{
|
|
2392
|
+
type: "button",
|
|
2393
|
+
className: "rounded-full border border-amber-400 px-3 py-1 font-medium text-amber-900 transition hover:bg-amber-100",
|
|
2394
|
+
onClick: reportMode.cancelReportMode,
|
|
2395
|
+
children: copy.reportModeCancelAction
|
|
2396
|
+
}
|
|
2397
|
+
)
|
|
2398
|
+
] }) })
|
|
2399
|
+
}
|
|
2400
|
+
);
|
|
2358
2401
|
}
|
|
2359
2402
|
function IssueList({
|
|
2360
2403
|
filter,
|
|
@@ -2476,6 +2519,7 @@ function IssueReportPopover({ children }) {
|
|
|
2476
2519
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2477
2520
|
Popover.Content,
|
|
2478
2521
|
{
|
|
2522
|
+
"data-spaps-screenshot-exclude": "",
|
|
2479
2523
|
align: "end",
|
|
2480
2524
|
side: "top",
|
|
2481
2525
|
sideOffset: 12,
|
|
@@ -2635,6 +2679,9 @@ function IssueReportModal() {
|
|
|
2635
2679
|
const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
|
|
2636
2680
|
const [note, setNote] = (0, import_react7.useState)("");
|
|
2637
2681
|
const [submitError, setSubmitError] = (0, import_react7.useState)(null);
|
|
2682
|
+
const submitInFlightRef = (0, import_react7.useRef)(false);
|
|
2683
|
+
const screenshotCacheRef = (0, import_react7.useRef)(null);
|
|
2684
|
+
const [isSubmitInFlight, setIsSubmitInFlight] = (0, import_react7.useState)(false);
|
|
2638
2685
|
const [inputMode, setInputMode] = (0, import_react7.useState)(defaultInputMode);
|
|
2639
2686
|
const [committedTranscript, setCommittedTranscript] = (0, import_react7.useState)("");
|
|
2640
2687
|
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = (0, import_react7.useState)(null);
|
|
@@ -2649,7 +2696,7 @@ function IssueReportModal() {
|
|
|
2649
2696
|
[issue, mode]
|
|
2650
2697
|
);
|
|
2651
2698
|
const attachmentState = useAttachmentState(existingAttachments);
|
|
2652
|
-
const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
|
|
2699
|
+
const isSubmitting = isSubmitInFlight || createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
|
|
2653
2700
|
const resetVoiceCapture = (0, import_react7.useCallback)(() => {
|
|
2654
2701
|
setInputMode(defaultInputMode);
|
|
2655
2702
|
setCommittedTranscript("");
|
|
@@ -2658,6 +2705,7 @@ function IssueReportModal() {
|
|
|
2658
2705
|
setVoiceAppendRequestId(0);
|
|
2659
2706
|
}, [defaultInputMode]);
|
|
2660
2707
|
useIsomorphicLayoutEffect(() => {
|
|
2708
|
+
screenshotCacheRef.current = null;
|
|
2661
2709
|
if (!isOpen) {
|
|
2662
2710
|
resetVoiceCapture();
|
|
2663
2711
|
attachmentState.reset();
|
|
@@ -2691,9 +2739,16 @@ function IssueReportModal() {
|
|
|
2691
2739
|
setInputMode("voice");
|
|
2692
2740
|
setVoiceStartRequestId((current) => current + 1);
|
|
2693
2741
|
};
|
|
2742
|
+
const handleSelectText = () => {
|
|
2743
|
+
setInputMode("text");
|
|
2744
|
+
setVoiceStartRequestId(0);
|
|
2745
|
+
setVoiceAppendRequestId(0);
|
|
2746
|
+
};
|
|
2694
2747
|
const handleAppendTranscript = (transcript) => {
|
|
2695
2748
|
setNote((current) => appendTranscriptToNote(current, transcript));
|
|
2696
2749
|
setInputMode("text");
|
|
2750
|
+
setVoiceStartRequestId(0);
|
|
2751
|
+
setVoiceAppendRequestId(0);
|
|
2697
2752
|
};
|
|
2698
2753
|
const uploadPendingFiles = async () => {
|
|
2699
2754
|
const upload = client.issueReporting.uploadAttachment;
|
|
@@ -2731,40 +2786,54 @@ function IssueReportModal() {
|
|
|
2731
2786
|
if (!target || !isValid || isSubmitting) {
|
|
2732
2787
|
return;
|
|
2733
2788
|
}
|
|
2789
|
+
if (submitInFlightRef.current) {
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
submitInFlightRef.current = true;
|
|
2793
|
+
setIsSubmitInFlight(true);
|
|
2734
2794
|
setSubmitError(null);
|
|
2735
2795
|
try {
|
|
2736
2796
|
const noteForSubmit = normalizedNote;
|
|
2737
2797
|
let newAttachmentIds = [];
|
|
2738
2798
|
let screenshotCaptureMetadata = null;
|
|
2739
2799
|
if (screenshotCapture?.enabled && mode === "create" && canUseAttachments && client.issueReporting.uploadAttachment) {
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2800
|
+
if (screenshotCacheRef.current) {
|
|
2801
|
+
newAttachmentIds.push(screenshotCacheRef.current.attachmentId);
|
|
2802
|
+
screenshotCaptureMetadata = screenshotCacheRef.current.metadata;
|
|
2803
|
+
} else {
|
|
2804
|
+
try {
|
|
2805
|
+
const { captureScreenshot: captureScreenshot2 } = await Promise.resolve().then(() => (init_screenshot_capture(), screenshot_capture_exports));
|
|
2806
|
+
const captureResult = await captureScreenshot2(screenshotCapture, {
|
|
2807
|
+
pageUrl: target.page_url,
|
|
2808
|
+
createMode,
|
|
2809
|
+
inputMode: effectiveInputMode,
|
|
2810
|
+
target: {
|
|
2811
|
+
componentKey: target.component_key,
|
|
2812
|
+
componentLabel: target.component_label,
|
|
2813
|
+
surfaceRef: target.surface_ref,
|
|
2814
|
+
metadata: target.metadata
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2817
|
+
const uploaded = await client.issueReporting.uploadAttachment(
|
|
2818
|
+
captureResult.blob,
|
|
2819
|
+
{ filename: captureResult.filename }
|
|
2820
|
+
);
|
|
2821
|
+
newAttachmentIds.push(uploaded.id);
|
|
2822
|
+
screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
|
|
2823
|
+
screenshotCapture,
|
|
2824
|
+
"attached"
|
|
2825
|
+
);
|
|
2826
|
+
screenshotCacheRef.current = {
|
|
2827
|
+
attachmentId: uploaded.id,
|
|
2828
|
+
metadata: screenshotCaptureMetadata
|
|
2829
|
+
};
|
|
2830
|
+
} catch (captureError) {
|
|
2831
|
+
screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
|
|
2832
|
+
screenshotCapture,
|
|
2833
|
+
"failed"
|
|
2834
|
+
);
|
|
2835
|
+
screenshotCapture.onCaptureError?.(captureError);
|
|
2836
|
+
}
|
|
2768
2837
|
}
|
|
2769
2838
|
}
|
|
2770
2839
|
if (canUseAttachments && attachmentState.pendingFiles.length > 0) {
|
|
@@ -2832,13 +2901,23 @@ function IssueReportModal() {
|
|
|
2832
2901
|
(current) => current.phase === "uploading" ? { ...current, phase: "error", error: message } : current
|
|
2833
2902
|
);
|
|
2834
2903
|
setSubmitError(message);
|
|
2904
|
+
} finally {
|
|
2905
|
+
submitInFlightRef.current = false;
|
|
2906
|
+
setIsSubmitInFlight(false);
|
|
2835
2907
|
}
|
|
2836
2908
|
};
|
|
2837
2909
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Dialog.Portal, { children: [
|
|
2838
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2910
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2911
|
+
Dialog.Overlay,
|
|
2912
|
+
{
|
|
2913
|
+
"data-spaps-screenshot-exclude": "",
|
|
2914
|
+
className: cn2("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY)
|
|
2915
|
+
}
|
|
2916
|
+
),
|
|
2839
2917
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2840
2918
|
Dialog.Content,
|
|
2841
2919
|
{
|
|
2920
|
+
"data-spaps-screenshot-exclude": "",
|
|
2842
2921
|
className: cn2(
|
|
2843
2922
|
"fixed left-1/2 top-1/2 max-h-[90vh] max-w-xl -translate-x-1/2 -translate-y-1/2 overflow-y-auto border border-slate-200 bg-white p-6 focus:outline-none",
|
|
2844
2923
|
Z_MODAL_CONTENT,
|
|
@@ -2884,7 +2963,7 @@ function IssueReportModal() {
|
|
|
2884
2963
|
attachmentValidationErrors: attachmentState.validationErrors,
|
|
2885
2964
|
onRetryHydration: () => void retryModalHydration(),
|
|
2886
2965
|
onClose: handleCloseModal,
|
|
2887
|
-
onSelectText:
|
|
2966
|
+
onSelectText: handleSelectText,
|
|
2888
2967
|
onSelectVoice: () => setInputMode("voice"),
|
|
2889
2968
|
onRequestStartVoiceInput: handleRequestStartVoiceInput,
|
|
2890
2969
|
onRequestAppendTranscript: handleRequestAppendTranscript,
|
|
@@ -2933,35 +3012,42 @@ function FloatingIssueReportButton({
|
|
|
2933
3012
|
}
|
|
2934
3013
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2935
3014
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportModeBanner, {}),
|
|
2936
|
-
!isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2937
|
-
"
|
|
3015
|
+
!isReportMode ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3016
|
+
"div",
|
|
2938
3017
|
{
|
|
2939
|
-
|
|
2940
|
-
"
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
3018
|
+
"data-spaps-screenshot-exclude": "",
|
|
3019
|
+
className: cn2("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName),
|
|
3020
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportPopover, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3021
|
+
"button",
|
|
3022
|
+
{
|
|
3023
|
+
type: "button",
|
|
3024
|
+
"aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
|
|
3025
|
+
onClick: () => isPopoverOpen ? closePopover() : openPopover(),
|
|
3026
|
+
className: cn2(
|
|
3027
|
+
"relative flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-lg transition hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-300",
|
|
3028
|
+
status.isPending && "animate-pulse",
|
|
3029
|
+
className
|
|
3030
|
+
),
|
|
3031
|
+
children: [
|
|
3032
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3033
|
+
import_react6.BugBeetle,
|
|
3034
|
+
{
|
|
3035
|
+
className: cn2("h-6 w-6", getEntryPointClassName(entryPointState)),
|
|
3036
|
+
weight: "fill"
|
|
3037
|
+
}
|
|
3038
|
+
),
|
|
3039
|
+
needsResponse ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3040
|
+
"span",
|
|
3041
|
+
{
|
|
3042
|
+
"data-testid": "issue-report-needs-response-badge",
|
|
3043
|
+
className: "absolute -right-0.5 -top-0.5 h-3 w-3 rounded-full border-2 border-white bg-amber-500"
|
|
3044
|
+
}
|
|
3045
|
+
) : null
|
|
3046
|
+
]
|
|
3047
|
+
}
|
|
3048
|
+
) })
|
|
2963
3049
|
}
|
|
2964
|
-
)
|
|
3050
|
+
) : null,
|
|
2965
3051
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IssueReportModal, {})
|
|
2966
3052
|
] });
|
|
2967
3053
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -839,7 +839,7 @@ var POPOVER_SHADOW = "shadow-[0_18px_48px_rgba(15,23,42,0.18)]";
|
|
|
839
839
|
var MODAL_SHADOW = "shadow-[0_28px_80px_rgba(15,23,42,0.24)]";
|
|
840
840
|
var BADGE_TEXT = "text-[11px]";
|
|
841
841
|
var LABEL_TEXT = "text-[11px]";
|
|
842
|
-
var VoiceCapture = React2.lazy(() => import("./VoiceCapture-
|
|
842
|
+
var VoiceCapture = React2.lazy(() => import("./VoiceCapture-HLUVONFL.mjs"));
|
|
843
843
|
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect2 : React2.useLayoutEffect;
|
|
844
844
|
var SCREENSHOT_CAPTURE_METADATA_KEY = "screenshot_capture";
|
|
845
845
|
function truncate(value, max = 80) {
|
|
@@ -1438,6 +1438,15 @@ function useAttachmentState(existingAttachments) {
|
|
|
1438
1438
|
);
|
|
1439
1439
|
const [validationErrors, setValidationErrors] = useState2([]);
|
|
1440
1440
|
const [uploadProgress, setUploadProgress] = useState2(INITIAL_UPLOAD_PROGRESS);
|
|
1441
|
+
const pendingFilesRef = useRef2(pendingFiles);
|
|
1442
|
+
pendingFilesRef.current = pendingFiles;
|
|
1443
|
+
useEffect2(() => {
|
|
1444
|
+
return () => {
|
|
1445
|
+
for (const pf of pendingFilesRef.current) {
|
|
1446
|
+
URL.revokeObjectURL(pf.previewUrl);
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
}, []);
|
|
1441
1450
|
const retainedExistingCount = existingAttachments.filter(
|
|
1442
1451
|
(a) => !removedExistingIds.has(a.id)
|
|
1443
1452
|
).length;
|
|
@@ -1754,19 +1763,26 @@ function IssueReportModeBanner() {
|
|
|
1754
1763
|
if (!reportMode?.isReportMode) {
|
|
1755
1764
|
return null;
|
|
1756
1765
|
}
|
|
1757
|
-
return /* @__PURE__ */ jsx2(
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
"
|
|
1762
|
-
{
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1766
|
+
return /* @__PURE__ */ jsx2(
|
|
1767
|
+
"div",
|
|
1768
|
+
{
|
|
1769
|
+
"data-spaps-screenshot-exclude": "",
|
|
1770
|
+
className: cn("fixed inset-x-4 top-4 flex justify-center", Z_BANNER),
|
|
1771
|
+
children: /* @__PURE__ */ jsx2("div", { className: "max-w-xl rounded-full border border-amber-300 bg-amber-50/95 px-4 py-3 text-sm text-amber-950 shadow-lg backdrop-blur", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
|
|
1772
|
+
/* @__PURE__ */ jsx2("div", { className: "font-medium", children: copy.reportModeTitle }),
|
|
1773
|
+
/* @__PURE__ */ jsx2("div", { className: "text-amber-900/80", children: copy.reportModeDescription }),
|
|
1774
|
+
/* @__PURE__ */ jsx2(
|
|
1775
|
+
"button",
|
|
1776
|
+
{
|
|
1777
|
+
type: "button",
|
|
1778
|
+
className: "rounded-full border border-amber-400 px-3 py-1 font-medium text-amber-900 transition hover:bg-amber-100",
|
|
1779
|
+
onClick: reportMode.cancelReportMode,
|
|
1780
|
+
children: copy.reportModeCancelAction
|
|
1781
|
+
}
|
|
1782
|
+
)
|
|
1783
|
+
] }) })
|
|
1784
|
+
}
|
|
1785
|
+
);
|
|
1770
1786
|
}
|
|
1771
1787
|
function IssueList({
|
|
1772
1788
|
filter,
|
|
@@ -1888,6 +1904,7 @@ function IssueReportPopover({ children }) {
|
|
|
1888
1904
|
/* @__PURE__ */ jsx2(Popover.Portal, { children: /* @__PURE__ */ jsx2(
|
|
1889
1905
|
Popover.Content,
|
|
1890
1906
|
{
|
|
1907
|
+
"data-spaps-screenshot-exclude": "",
|
|
1891
1908
|
align: "end",
|
|
1892
1909
|
side: "top",
|
|
1893
1910
|
sideOffset: 12,
|
|
@@ -2047,6 +2064,9 @@ function IssueReportModal() {
|
|
|
2047
2064
|
const { createMutation, updateMutation, replyMutation } = useIssueReportingMutations();
|
|
2048
2065
|
const [note, setNote] = useState2("");
|
|
2049
2066
|
const [submitError, setSubmitError] = useState2(null);
|
|
2067
|
+
const submitInFlightRef = useRef2(false);
|
|
2068
|
+
const screenshotCacheRef = useRef2(null);
|
|
2069
|
+
const [isSubmitInFlight, setIsSubmitInFlight] = useState2(false);
|
|
2050
2070
|
const [inputMode, setInputMode] = useState2(defaultInputMode);
|
|
2051
2071
|
const [committedTranscript, setCommittedTranscript] = useState2("");
|
|
2052
2072
|
const [voiceSubmitMetadata, setVoiceSubmitMetadata] = useState2(null);
|
|
@@ -2061,7 +2081,7 @@ function IssueReportModal() {
|
|
|
2061
2081
|
[issue, mode]
|
|
2062
2082
|
);
|
|
2063
2083
|
const attachmentState = useAttachmentState(existingAttachments);
|
|
2064
|
-
const isSubmitting = createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
|
|
2084
|
+
const isSubmitting = isSubmitInFlight || createMutation.isPending || updateMutation.isPending || replyMutation.isPending || attachmentState.uploadProgress.phase === "uploading";
|
|
2065
2085
|
const resetVoiceCapture = useCallback2(() => {
|
|
2066
2086
|
setInputMode(defaultInputMode);
|
|
2067
2087
|
setCommittedTranscript("");
|
|
@@ -2070,6 +2090,7 @@ function IssueReportModal() {
|
|
|
2070
2090
|
setVoiceAppendRequestId(0);
|
|
2071
2091
|
}, [defaultInputMode]);
|
|
2072
2092
|
useIsomorphicLayoutEffect(() => {
|
|
2093
|
+
screenshotCacheRef.current = null;
|
|
2073
2094
|
if (!isOpen) {
|
|
2074
2095
|
resetVoiceCapture();
|
|
2075
2096
|
attachmentState.reset();
|
|
@@ -2103,9 +2124,16 @@ function IssueReportModal() {
|
|
|
2103
2124
|
setInputMode("voice");
|
|
2104
2125
|
setVoiceStartRequestId((current) => current + 1);
|
|
2105
2126
|
};
|
|
2127
|
+
const handleSelectText = () => {
|
|
2128
|
+
setInputMode("text");
|
|
2129
|
+
setVoiceStartRequestId(0);
|
|
2130
|
+
setVoiceAppendRequestId(0);
|
|
2131
|
+
};
|
|
2106
2132
|
const handleAppendTranscript = (transcript) => {
|
|
2107
2133
|
setNote((current) => appendTranscriptToNote(current, transcript));
|
|
2108
2134
|
setInputMode("text");
|
|
2135
|
+
setVoiceStartRequestId(0);
|
|
2136
|
+
setVoiceAppendRequestId(0);
|
|
2109
2137
|
};
|
|
2110
2138
|
const uploadPendingFiles = async () => {
|
|
2111
2139
|
const upload = client.issueReporting.uploadAttachment;
|
|
@@ -2143,40 +2171,54 @@ function IssueReportModal() {
|
|
|
2143
2171
|
if (!target || !isValid || isSubmitting) {
|
|
2144
2172
|
return;
|
|
2145
2173
|
}
|
|
2174
|
+
if (submitInFlightRef.current) {
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
submitInFlightRef.current = true;
|
|
2178
|
+
setIsSubmitInFlight(true);
|
|
2146
2179
|
setSubmitError(null);
|
|
2147
2180
|
try {
|
|
2148
2181
|
const noteForSubmit = normalizedNote;
|
|
2149
2182
|
let newAttachmentIds = [];
|
|
2150
2183
|
let screenshotCaptureMetadata = null;
|
|
2151
2184
|
if (screenshotCapture?.enabled && mode === "create" && canUseAttachments && client.issueReporting.uploadAttachment) {
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2185
|
+
if (screenshotCacheRef.current) {
|
|
2186
|
+
newAttachmentIds.push(screenshotCacheRef.current.attachmentId);
|
|
2187
|
+
screenshotCaptureMetadata = screenshotCacheRef.current.metadata;
|
|
2188
|
+
} else {
|
|
2189
|
+
try {
|
|
2190
|
+
const { captureScreenshot } = await import("./screenshot-capture.mjs");
|
|
2191
|
+
const captureResult = await captureScreenshot(screenshotCapture, {
|
|
2192
|
+
pageUrl: target.page_url,
|
|
2193
|
+
createMode,
|
|
2194
|
+
inputMode: effectiveInputMode,
|
|
2195
|
+
target: {
|
|
2196
|
+
componentKey: target.component_key,
|
|
2197
|
+
componentLabel: target.component_label,
|
|
2198
|
+
surfaceRef: target.surface_ref,
|
|
2199
|
+
metadata: target.metadata
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
const uploaded = await client.issueReporting.uploadAttachment(
|
|
2203
|
+
captureResult.blob,
|
|
2204
|
+
{ filename: captureResult.filename }
|
|
2205
|
+
);
|
|
2206
|
+
newAttachmentIds.push(uploaded.id);
|
|
2207
|
+
screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
|
|
2208
|
+
screenshotCapture,
|
|
2209
|
+
"attached"
|
|
2210
|
+
);
|
|
2211
|
+
screenshotCacheRef.current = {
|
|
2212
|
+
attachmentId: uploaded.id,
|
|
2213
|
+
metadata: screenshotCaptureMetadata
|
|
2214
|
+
};
|
|
2215
|
+
} catch (captureError) {
|
|
2216
|
+
screenshotCaptureMetadata = buildScreenshotCaptureMetadata(
|
|
2217
|
+
screenshotCapture,
|
|
2218
|
+
"failed"
|
|
2219
|
+
);
|
|
2220
|
+
screenshotCapture.onCaptureError?.(captureError);
|
|
2221
|
+
}
|
|
2180
2222
|
}
|
|
2181
2223
|
}
|
|
2182
2224
|
if (canUseAttachments && attachmentState.pendingFiles.length > 0) {
|
|
@@ -2244,13 +2286,23 @@ function IssueReportModal() {
|
|
|
2244
2286
|
(current) => current.phase === "uploading" ? { ...current, phase: "error", error: message } : current
|
|
2245
2287
|
);
|
|
2246
2288
|
setSubmitError(message);
|
|
2289
|
+
} finally {
|
|
2290
|
+
submitInFlightRef.current = false;
|
|
2291
|
+
setIsSubmitInFlight(false);
|
|
2247
2292
|
}
|
|
2248
2293
|
};
|
|
2249
2294
|
return /* @__PURE__ */ jsx2(Dialog.Root, { open: isOpen, onOpenChange: (open) => !open && handleCloseModal(), children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
2250
|
-
/* @__PURE__ */ jsx2(
|
|
2295
|
+
/* @__PURE__ */ jsx2(
|
|
2296
|
+
Dialog.Overlay,
|
|
2297
|
+
{
|
|
2298
|
+
"data-spaps-screenshot-exclude": "",
|
|
2299
|
+
className: cn("fixed inset-0 bg-slate-950/45 backdrop-blur-sm", Z_MODAL_OVERLAY)
|
|
2300
|
+
}
|
|
2301
|
+
),
|
|
2251
2302
|
/* @__PURE__ */ jsxs(
|
|
2252
2303
|
Dialog.Content,
|
|
2253
2304
|
{
|
|
2305
|
+
"data-spaps-screenshot-exclude": "",
|
|
2254
2306
|
className: cn(
|
|
2255
2307
|
"fixed left-1/2 top-1/2 max-h-[90vh] max-w-xl -translate-x-1/2 -translate-y-1/2 overflow-y-auto border border-slate-200 bg-white p-6 focus:outline-none",
|
|
2256
2308
|
Z_MODAL_CONTENT,
|
|
@@ -2296,7 +2348,7 @@ function IssueReportModal() {
|
|
|
2296
2348
|
attachmentValidationErrors: attachmentState.validationErrors,
|
|
2297
2349
|
onRetryHydration: () => void retryModalHydration(),
|
|
2298
2350
|
onClose: handleCloseModal,
|
|
2299
|
-
onSelectText:
|
|
2351
|
+
onSelectText: handleSelectText,
|
|
2300
2352
|
onSelectVoice: () => setInputMode("voice"),
|
|
2301
2353
|
onRequestStartVoiceInput: handleRequestStartVoiceInput,
|
|
2302
2354
|
onRequestAppendTranscript: handleRequestAppendTranscript,
|
|
@@ -2345,35 +2397,42 @@ function FloatingIssueReportButton({
|
|
|
2345
2397
|
}
|
|
2346
2398
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2347
2399
|
/* @__PURE__ */ jsx2(IssueReportModeBanner, {}),
|
|
2348
|
-
!isReportMode ? /* @__PURE__ */ jsx2(
|
|
2349
|
-
"
|
|
2400
|
+
!isReportMode ? /* @__PURE__ */ jsx2(
|
|
2401
|
+
"div",
|
|
2350
2402
|
{
|
|
2351
|
-
|
|
2352
|
-
"
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2403
|
+
"data-spaps-screenshot-exclude": "",
|
|
2404
|
+
className: cn("fixed bottom-12 right-4", Z_FLOATING_BUTTON, positionClassName),
|
|
2405
|
+
children: /* @__PURE__ */ jsx2(IssueReportPopover, { children: /* @__PURE__ */ jsxs(
|
|
2406
|
+
"button",
|
|
2407
|
+
{
|
|
2408
|
+
type: "button",
|
|
2409
|
+
"aria-label": needsResponse ? `${copy.entryAriaLabel} (${copy.threadNeedsResponseBadge})` : copy.entryAriaLabel,
|
|
2410
|
+
onClick: () => isPopoverOpen ? closePopover() : openPopover(),
|
|
2411
|
+
className: cn(
|
|
2412
|
+
"relative flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-lg transition hover:-translate-y-0.5 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-300",
|
|
2413
|
+
status.isPending && "animate-pulse",
|
|
2414
|
+
className
|
|
2415
|
+
),
|
|
2416
|
+
children: [
|
|
2417
|
+
/* @__PURE__ */ jsx2(
|
|
2418
|
+
BugBeetle,
|
|
2419
|
+
{
|
|
2420
|
+
className: cn("h-6 w-6", getEntryPointClassName(entryPointState)),
|
|
2421
|
+
weight: "fill"
|
|
2422
|
+
}
|
|
2423
|
+
),
|
|
2424
|
+
needsResponse ? /* @__PURE__ */ jsx2(
|
|
2425
|
+
"span",
|
|
2426
|
+
{
|
|
2427
|
+
"data-testid": "issue-report-needs-response-badge",
|
|
2428
|
+
className: "absolute -right-0.5 -top-0.5 h-3 w-3 rounded-full border-2 border-white bg-amber-500"
|
|
2429
|
+
}
|
|
2430
|
+
) : null
|
|
2431
|
+
]
|
|
2432
|
+
}
|
|
2433
|
+
) })
|
|
2375
2434
|
}
|
|
2376
|
-
)
|
|
2435
|
+
) : null,
|
|
2377
2436
|
/* @__PURE__ */ jsx2(IssueReportModal, {})
|
|
2378
2437
|
] });
|
|
2379
2438
|
}
|
|
@@ -46,7 +46,11 @@ var DEFAULT_MASK_SELECTORS = [
|
|
|
46
46
|
"option",
|
|
47
47
|
"select",
|
|
48
48
|
"textarea",
|
|
49
|
-
|
|
49
|
+
// Match every editable region, not just the explicit `contenteditable="true"`
|
|
50
|
+
// form. Bare (`contenteditable`), empty-string (`contenteditable=""`), and
|
|
51
|
+
// `contenteditable="plaintext-only"` editors are all editable and must be
|
|
52
|
+
// masked; only the explicitly disabled `contenteditable="false"` is excluded.
|
|
53
|
+
'[contenteditable]:not([contenteditable="false"])'
|
|
50
54
|
];
|
|
51
55
|
var SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
|
|
52
56
|
var ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -114,7 +118,7 @@ async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
|
|
|
114
118
|
let currentCanvas = canvas;
|
|
115
119
|
let scaled = false;
|
|
116
120
|
for (const scale of [0.85, 0.7, 0.55, 0.4]) {
|
|
117
|
-
currentCanvas = scaleCanvas(
|
|
121
|
+
currentCanvas = scaleCanvas(canvas, scale);
|
|
118
122
|
scaled = true;
|
|
119
123
|
for (const format of fallbackTypes) {
|
|
120
124
|
for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
|
|
@@ -201,9 +205,14 @@ async function captureScreenshot(config, context) {
|
|
|
201
205
|
function resetHtml2CanvasCache() {
|
|
202
206
|
html2canvasPromise = null;
|
|
203
207
|
}
|
|
204
|
-
function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors =
|
|
208
|
+
function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = []) {
|
|
205
209
|
clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
|
|
206
|
-
|
|
210
|
+
for (const selector of [...DEFAULT_MASK_SELECTORS, ...maskSelectors]) {
|
|
211
|
+
try {
|
|
212
|
+
clonedDocument.querySelectorAll(selector).forEach(maskCaptureElement);
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
207
216
|
redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
|
|
208
217
|
}
|
|
209
218
|
function canUseBrowserCapture() {
|
|
@@ -8,7 +8,11 @@ var DEFAULT_MASK_SELECTORS = [
|
|
|
8
8
|
"option",
|
|
9
9
|
"select",
|
|
10
10
|
"textarea",
|
|
11
|
-
|
|
11
|
+
// Match every editable region, not just the explicit `contenteditable="true"`
|
|
12
|
+
// form. Bare (`contenteditable`), empty-string (`contenteditable=""`), and
|
|
13
|
+
// `contenteditable="plaintext-only"` editors are all editable and must be
|
|
14
|
+
// masked; only the explicitly disabled `contenteditable="false"` is excluded.
|
|
15
|
+
'[contenteditable]:not([contenteditable="false"])'
|
|
12
16
|
];
|
|
13
17
|
var SCREENSHOT_CAPTURE_REDACTION_TEXT = "[redacted]";
|
|
14
18
|
var ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -76,7 +80,7 @@ async function canvasToSizedBlob(canvas, imageType, quality, maxBytes) {
|
|
|
76
80
|
let currentCanvas = canvas;
|
|
77
81
|
let scaled = false;
|
|
78
82
|
for (const scale of [0.85, 0.7, 0.55, 0.4]) {
|
|
79
|
-
currentCanvas = scaleCanvas(
|
|
83
|
+
currentCanvas = scaleCanvas(canvas, scale);
|
|
80
84
|
scaled = true;
|
|
81
85
|
for (const format of fallbackTypes) {
|
|
82
86
|
for (const candidateQuality of [0.86, 0.72, 0.58, 0.44, 0.32]) {
|
|
@@ -163,9 +167,14 @@ async function captureScreenshot(config, context) {
|
|
|
163
167
|
function resetHtml2CanvasCache() {
|
|
164
168
|
html2canvasPromise = null;
|
|
165
169
|
}
|
|
166
|
-
function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors =
|
|
170
|
+
function maskScreenshotCaptureDocument(clonedDocument, excludeSelectors = DEFAULT_EXCLUDE_SELECTORS, maskSelectors = []) {
|
|
167
171
|
clonedDocument.querySelectorAll([...excludeSelectors, "script"].join(",")).forEach((element) => element.remove());
|
|
168
|
-
|
|
172
|
+
for (const selector of [...DEFAULT_MASK_SELECTORS, ...maskSelectors]) {
|
|
173
|
+
try {
|
|
174
|
+
clonedDocument.querySelectorAll(selector).forEach(maskCaptureElement);
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
169
178
|
redactSensitiveTextNodes(clonedDocument.body ?? clonedDocument.documentElement);
|
|
170
179
|
}
|
|
171
180
|
function canUseBrowserCapture() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps-issue-reporting-react",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Shared React issue-reporting UI for Sweet Potato platform consumers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
53
53
|
"@radix-ui/react-popover": "^1.1.15",
|
|
54
54
|
"date-fns": "^4.1.0",
|
|
55
|
-
"spaps-types": "^1.5.
|
|
55
|
+
"spaps-types": "^1.5.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@tanstack/react-query": ">=5.0.0",
|