glitchgrab 1.30.0 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -2
- package/dist/index.d.mts +266 -3
- package/dist/index.d.ts +266 -3
- package/dist/index.js +1474 -233
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1413 -180
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -7,24 +7,83 @@ import {
|
|
|
7
7
|
useRef as useRef3,
|
|
8
8
|
useEffect as useEffect3,
|
|
9
9
|
useCallback,
|
|
10
|
+
useMemo,
|
|
10
11
|
useState as useState3
|
|
11
12
|
} from "react";
|
|
12
13
|
|
|
13
14
|
// src/error-boundary.tsx
|
|
14
15
|
import * as React from "react";
|
|
15
16
|
|
|
17
|
+
// src/redact.ts
|
|
18
|
+
var SENSITIVE_KEY_RE = /(pass|pwd|secret|token|auth|session|cookie|credential|apikey|api_key|access_key|private|signature|otp|pin|cvv|card|ssn|aadhaar|pan)/i;
|
|
19
|
+
var EMAIL_RE = /[\w.+-]+@[\w-]+\.[\w.-]+/g;
|
|
20
|
+
var JWT_RE = /\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{8,}\b/g;
|
|
21
|
+
var BEARER_RE = /\b(bearer\s+)[\w-.~+/]{12,}=*/gi;
|
|
22
|
+
var MAX_BODY_LENGTH = 500;
|
|
23
|
+
function redactString(value) {
|
|
24
|
+
return value.replace(JWT_RE, "[REDACTED]").replace(BEARER_RE, "$1[REDACTED]").replace(EMAIL_RE, "[REDACTED_EMAIL]");
|
|
25
|
+
}
|
|
26
|
+
function redactValue(value, depth) {
|
|
27
|
+
if (depth > 4) return "[DEPTH]";
|
|
28
|
+
if (value === null || value === void 0) return value;
|
|
29
|
+
if (typeof value === "string") return redactString(value);
|
|
30
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
31
|
+
if (Array.isArray(value)) return value.slice(0, 20).map((v) => redactValue(v, depth + 1));
|
|
32
|
+
if (typeof value === "object") {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
35
|
+
out[key] = SENSITIVE_KEY_RE.test(key) ? "[REDACTED]" : redactValue(inner, depth + 1);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
return String(value);
|
|
40
|
+
}
|
|
41
|
+
function redactBody(body, maxLength = MAX_BODY_LENGTH) {
|
|
42
|
+
try {
|
|
43
|
+
const trimmed = body.trim();
|
|
44
|
+
if (!trimmed) return "";
|
|
45
|
+
let result;
|
|
46
|
+
try {
|
|
47
|
+
result = JSON.stringify(redactValue(JSON.parse(trimmed), 0));
|
|
48
|
+
} catch (e) {
|
|
49
|
+
result = redactString(trimmed);
|
|
50
|
+
}
|
|
51
|
+
return result.length > maxLength ? `${result.slice(0, maxLength)}\u2026[truncated]` : result;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
16
57
|
// src/breadcrumbs.ts
|
|
17
58
|
var MAX_DEFAULT = 50;
|
|
18
59
|
var breadcrumbs = [];
|
|
19
60
|
var maxBreadcrumbs = MAX_DEFAULT;
|
|
20
61
|
var initialized = false;
|
|
21
|
-
|
|
62
|
+
var responseBodyOrigins = [];
|
|
63
|
+
function mayCaptureBody(url) {
|
|
64
|
+
try {
|
|
65
|
+
if (typeof window === "undefined") return false;
|
|
66
|
+
const origin = new URL(url, window.location.href).origin;
|
|
67
|
+
if (origin === window.location.origin) return true;
|
|
68
|
+
return responseBodyOrigins.includes(origin);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function initBreadcrumbs(max, options) {
|
|
74
|
+
var _a;
|
|
75
|
+
if ((_a = options == null ? void 0 : options.responseBodyOrigins) == null ? void 0 : _a.length) {
|
|
76
|
+
responseBodyOrigins = Array.from(
|
|
77
|
+
/* @__PURE__ */ new Set([...responseBodyOrigins, ...options.responseBodyOrigins])
|
|
78
|
+
);
|
|
79
|
+
}
|
|
22
80
|
if (initialized) return;
|
|
23
81
|
maxBreadcrumbs = max != null ? max : MAX_DEFAULT;
|
|
24
82
|
try {
|
|
25
83
|
if (typeof window === "undefined") return;
|
|
26
84
|
interceptConsole();
|
|
27
85
|
interceptFetch();
|
|
86
|
+
interceptXhr();
|
|
28
87
|
interceptNavigation();
|
|
29
88
|
interceptClicks();
|
|
30
89
|
initialized = true;
|
|
@@ -83,6 +142,16 @@ function argsToString(args) {
|
|
|
83
142
|
return "[unknown]";
|
|
84
143
|
}
|
|
85
144
|
}
|
|
145
|
+
async function readErrorBody(response, url) {
|
|
146
|
+
try {
|
|
147
|
+
if (response.ok) return "";
|
|
148
|
+
if (!mayCaptureBody(url)) return "";
|
|
149
|
+
const text = await response.clone().text();
|
|
150
|
+
return redactBody(text);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
86
155
|
function interceptFetch() {
|
|
87
156
|
const origFetch = window.fetch;
|
|
88
157
|
window.fetch = async function(input, init) {
|
|
@@ -95,10 +164,12 @@ function interceptFetch() {
|
|
|
95
164
|
input,
|
|
96
165
|
init
|
|
97
166
|
);
|
|
167
|
+
const body = await readErrorBody(response, url);
|
|
98
168
|
addBreadcrumb("api", `${method} ${url.slice(0, 100)} \u2192 ${response.status}`, {
|
|
99
169
|
method,
|
|
100
170
|
status: String(response.status),
|
|
101
|
-
duration: `${Date.now() - start}ms
|
|
171
|
+
duration: `${Date.now() - start}ms`,
|
|
172
|
+
...body ? { responseBody: body } : {}
|
|
102
173
|
});
|
|
103
174
|
return response;
|
|
104
175
|
} catch (err) {
|
|
@@ -111,6 +182,67 @@ function interceptFetch() {
|
|
|
111
182
|
}
|
|
112
183
|
};
|
|
113
184
|
}
|
|
185
|
+
function interceptXhr() {
|
|
186
|
+
if (typeof XMLHttpRequest === "undefined") return;
|
|
187
|
+
const origOpen = XMLHttpRequest.prototype.open;
|
|
188
|
+
const origSend = XMLHttpRequest.prototype.send;
|
|
189
|
+
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
190
|
+
try {
|
|
191
|
+
this.__ggMethod = method;
|
|
192
|
+
this.__ggUrl = typeof url === "string" ? url : url.toString();
|
|
193
|
+
const record = () => {
|
|
194
|
+
var _a, _b, _c, _d;
|
|
195
|
+
try {
|
|
196
|
+
const recordedMethod = (_a = this.__ggMethod) != null ? _a : "GET";
|
|
197
|
+
const recordedUrl = ((_b = this.__ggUrl) != null ? _b : "").slice(0, 100);
|
|
198
|
+
const duration = `${Date.now() - ((_c = this.__ggStart) != null ? _c : Date.now())}ms`;
|
|
199
|
+
if (!this.status) {
|
|
200
|
+
addBreadcrumb("api", `${recordedMethod} ${recordedUrl} \u2192 FAILED`, {
|
|
201
|
+
method: recordedMethod,
|
|
202
|
+
duration
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const failed = this.status < 200 || this.status >= 300;
|
|
207
|
+
const body = failed && mayCaptureBody((_d = this.__ggUrl) != null ? _d : "") ? readXhrBody(this) : "";
|
|
208
|
+
addBreadcrumb("api", `${recordedMethod} ${recordedUrl} \u2192 ${this.status}`, {
|
|
209
|
+
method: recordedMethod,
|
|
210
|
+
status: String(this.status),
|
|
211
|
+
duration,
|
|
212
|
+
...body ? { responseBody: body } : {}
|
|
213
|
+
});
|
|
214
|
+
} catch (e) {
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
if (!this.__ggListening) {
|
|
218
|
+
this.__ggListening = true;
|
|
219
|
+
this.addEventListener("loadend", record);
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
}
|
|
223
|
+
return origOpen.apply(this, [
|
|
224
|
+
method,
|
|
225
|
+
url,
|
|
226
|
+
...rest
|
|
227
|
+
]);
|
|
228
|
+
};
|
|
229
|
+
XMLHttpRequest.prototype.send = function(...args) {
|
|
230
|
+
try {
|
|
231
|
+
this.__ggStart = Date.now();
|
|
232
|
+
} catch (e) {
|
|
233
|
+
}
|
|
234
|
+
return origSend.apply(this, args);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function readXhrBody(xhr) {
|
|
238
|
+
try {
|
|
239
|
+
if (xhr.responseType && xhr.responseType !== "text" && xhr.responseType !== "json") return "";
|
|
240
|
+
const raw = xhr.responseType === "json" ? JSON.stringify(xhr.response) : xhr.responseText;
|
|
241
|
+
return raw ? redactBody(raw) : "";
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
114
246
|
function interceptNavigation() {
|
|
115
247
|
const origPush = history.pushState.bind(history);
|
|
116
248
|
const origReplace = history.replaceState.bind(history);
|
|
@@ -145,6 +277,90 @@ function interceptClicks() {
|
|
|
145
277
|
);
|
|
146
278
|
}
|
|
147
279
|
|
|
280
|
+
// src/runtime.ts
|
|
281
|
+
var pageLoadedAt = Date.now();
|
|
282
|
+
var errorCount = 0;
|
|
283
|
+
function incrementErrorCount() {
|
|
284
|
+
errorCount += 1;
|
|
285
|
+
return errorCount;
|
|
286
|
+
}
|
|
287
|
+
var toMb = (bytes) => typeof bytes === "number" ? Math.round(bytes / 1048576 * 10) / 10 : void 0;
|
|
288
|
+
function captureRuntimeInfo() {
|
|
289
|
+
try {
|
|
290
|
+
if (typeof window === "undefined") return null;
|
|
291
|
+
const memory = performance == null ? void 0 : performance.memory;
|
|
292
|
+
const connection = navigator == null ? void 0 : navigator.connection;
|
|
293
|
+
return {
|
|
294
|
+
timeOnPageMs: Date.now() - pageLoadedAt,
|
|
295
|
+
errorCount,
|
|
296
|
+
visibility: typeof document !== "undefined" ? document.visibilityState : "unknown",
|
|
297
|
+
...memory ? {
|
|
298
|
+
jsHeapUsedMb: toMb(memory.usedJSHeapSize),
|
|
299
|
+
jsHeapLimitMb: toMb(memory.jsHeapSizeLimit)
|
|
300
|
+
} : {},
|
|
301
|
+
...connection ? {
|
|
302
|
+
connectionType: connection.effectiveType,
|
|
303
|
+
downlinkMbps: connection.downlink,
|
|
304
|
+
rttMs: connection.rtt,
|
|
305
|
+
saveData: connection.saveData
|
|
306
|
+
} : {}
|
|
307
|
+
};
|
|
308
|
+
} catch (e) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/app-context.ts
|
|
314
|
+
var MAX_KEYS = 30;
|
|
315
|
+
var MAX_VALUE_LENGTH = 200;
|
|
316
|
+
var appContext = {};
|
|
317
|
+
var release;
|
|
318
|
+
function coerce(value) {
|
|
319
|
+
if (typeof value === "string") return value.slice(0, MAX_VALUE_LENGTH);
|
|
320
|
+
if (value === null || value === void 0) return "";
|
|
321
|
+
try {
|
|
322
|
+
return JSON.stringify(value).slice(0, MAX_VALUE_LENGTH);
|
|
323
|
+
} catch (e) {
|
|
324
|
+
return String(value).slice(0, MAX_VALUE_LENGTH);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function setContext(key, value) {
|
|
328
|
+
try {
|
|
329
|
+
if (!key) return;
|
|
330
|
+
if (value === null || value === void 0) {
|
|
331
|
+
delete appContext[key];
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (!(key in appContext) && Object.keys(appContext).length >= MAX_KEYS) return;
|
|
335
|
+
appContext[key] = coerce(value);
|
|
336
|
+
} catch (e) {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function setContexts(values) {
|
|
340
|
+
try {
|
|
341
|
+
for (const [key, value] of Object.entries(values != null ? values : {})) setContext(key, value);
|
|
342
|
+
} catch (e) {
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function getAppContext() {
|
|
346
|
+
return { ...appContext };
|
|
347
|
+
}
|
|
348
|
+
function clearAppContext() {
|
|
349
|
+
appContext = {};
|
|
350
|
+
}
|
|
351
|
+
function setRelease(value) {
|
|
352
|
+
if (value) release = value;
|
|
353
|
+
}
|
|
354
|
+
function getRelease() {
|
|
355
|
+
if (release) return release;
|
|
356
|
+
try {
|
|
357
|
+
const fromEnv = process.env.NEXT_PUBLIC_APP_VERSION || process.env.NEXT_PUBLIC_RELEASE || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA;
|
|
358
|
+
return fromEnv || void 0;
|
|
359
|
+
} catch (e) {
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
148
364
|
// src/utils.ts
|
|
149
365
|
var SENSITIVE_PARAMS = [
|
|
150
366
|
"token",
|
|
@@ -213,7 +429,10 @@ function captureContext(visitedPages) {
|
|
|
213
429
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
214
430
|
visitedPages: visitedPages.map(sanitizeUrl),
|
|
215
431
|
breadcrumbs: getBreadcrumbs(),
|
|
216
|
-
deviceInfo: captureDeviceInfo()
|
|
432
|
+
deviceInfo: captureDeviceInfo(),
|
|
433
|
+
runtime: captureRuntimeInfo(),
|
|
434
|
+
appContext: getAppContext(),
|
|
435
|
+
release: getRelease()
|
|
217
436
|
};
|
|
218
437
|
} catch (e) {
|
|
219
438
|
return {
|
|
@@ -222,10 +441,42 @@ function captureContext(visitedPages) {
|
|
|
222
441
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
223
442
|
visitedPages: [],
|
|
224
443
|
breadcrumbs: [],
|
|
225
|
-
deviceInfo: null
|
|
444
|
+
deviceInfo: null,
|
|
445
|
+
runtime: null,
|
|
446
|
+
appContext: {}
|
|
226
447
|
};
|
|
227
448
|
}
|
|
228
449
|
}
|
|
450
|
+
function contextMetadata(context) {
|
|
451
|
+
var _a;
|
|
452
|
+
try {
|
|
453
|
+
const runtime = context.runtime;
|
|
454
|
+
const appContext2 = Object.entries((_a = context.appContext) != null ? _a : {}).reduce(
|
|
455
|
+
(acc, [key, value]) => {
|
|
456
|
+
acc[`ctx_${key}`] = value;
|
|
457
|
+
return acc;
|
|
458
|
+
},
|
|
459
|
+
{}
|
|
460
|
+
);
|
|
461
|
+
return {
|
|
462
|
+
...context.release ? { release: context.release } : {},
|
|
463
|
+
...runtime ? {
|
|
464
|
+
timeOnPageMs: String(runtime.timeOnPageMs),
|
|
465
|
+
errorCount: String(runtime.errorCount),
|
|
466
|
+
visibility: runtime.visibility,
|
|
467
|
+
...runtime.jsHeapUsedMb !== void 0 ? { jsHeapUsedMb: String(runtime.jsHeapUsedMb) } : {},
|
|
468
|
+
...runtime.jsHeapLimitMb !== void 0 ? { jsHeapLimitMb: String(runtime.jsHeapLimitMb) } : {},
|
|
469
|
+
...runtime.connectionType ? { connectionType: runtime.connectionType } : {},
|
|
470
|
+
...runtime.downlinkMbps !== void 0 ? { downlinkMbps: String(runtime.downlinkMbps) } : {},
|
|
471
|
+
...runtime.rttMs !== void 0 ? { rttMs: String(runtime.rttMs) } : {},
|
|
472
|
+
...runtime.saveData !== void 0 ? { saveData: String(runtime.saveData) } : {}
|
|
473
|
+
} : {},
|
|
474
|
+
...appContext2
|
|
475
|
+
};
|
|
476
|
+
} catch (e) {
|
|
477
|
+
return {};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
229
480
|
var DEFAULT_BASE_URL = "https://glitchgrab.dev";
|
|
230
481
|
var MAX_RETRIES = 3;
|
|
231
482
|
var RETRY_BASE_MS = 500;
|
|
@@ -273,6 +524,41 @@ async function sendReport(payload, baseUrl) {
|
|
|
273
524
|
return null;
|
|
274
525
|
}
|
|
275
526
|
}
|
|
527
|
+
async function sendFeedback(payload, baseUrl) {
|
|
528
|
+
var _a;
|
|
529
|
+
try {
|
|
530
|
+
const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/sdk/feedback`;
|
|
531
|
+
const body = JSON.stringify(payload);
|
|
532
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
533
|
+
try {
|
|
534
|
+
const response = await fetch(url, {
|
|
535
|
+
method: "POST",
|
|
536
|
+
headers: {
|
|
537
|
+
"Content-Type": "application/json",
|
|
538
|
+
Authorization: `Bearer ${payload.token}`
|
|
539
|
+
},
|
|
540
|
+
body
|
|
541
|
+
});
|
|
542
|
+
if (response.ok) {
|
|
543
|
+
const envelope = await response.json();
|
|
544
|
+
return { success: envelope.success, ...(_a = envelope.data) != null ? _a : {} };
|
|
545
|
+
}
|
|
546
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
} catch (e) {
|
|
550
|
+
}
|
|
551
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
552
|
+
await new Promise(
|
|
553
|
+
(resolve) => setTimeout(resolve, RETRY_BASE_MS * Math.pow(2, attempt))
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
} catch (e) {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
276
562
|
async function enhanceText(text, token, baseUrl, screenshot, context) {
|
|
277
563
|
var _a;
|
|
278
564
|
try {
|
|
@@ -362,11 +648,12 @@ function stripUrlQuery(url) {
|
|
|
362
648
|
}
|
|
363
649
|
}
|
|
364
650
|
function computeSignature(params) {
|
|
365
|
-
var _a, _b;
|
|
651
|
+
var _a, _b, _c, _d;
|
|
366
652
|
const msg = (_b = (_a = params.errorMessage) == null ? void 0 : _a.trim()) != null ? _b : "";
|
|
367
653
|
const page = stripUrlQuery(params.pageUrl);
|
|
368
654
|
const frame = topStackFrame(params.errorStack);
|
|
369
|
-
|
|
655
|
+
const digest = (_d = (_c = params.digest) == null ? void 0 : _c.trim()) != null ? _d : "";
|
|
656
|
+
return hashString(digest ? `${msg}|${page}|${frame}|${digest}` : `${msg}|${page}|${frame}`);
|
|
370
657
|
}
|
|
371
658
|
function shouldSkipDuplicate(signature, windowMs = DEFAULT_WINDOW_MS, now = Date.now()) {
|
|
372
659
|
const last = seen.get(signature);
|
|
@@ -391,6 +678,78 @@ function clearDedupCache() {
|
|
|
391
678
|
seen.clear();
|
|
392
679
|
}
|
|
393
680
|
|
|
681
|
+
// src/capture.ts
|
|
682
|
+
var config = null;
|
|
683
|
+
function registerCaptureConfig(next) {
|
|
684
|
+
config = next;
|
|
685
|
+
}
|
|
686
|
+
function matchesIgnorePatterns(message, patterns) {
|
|
687
|
+
if (!patterns || patterns.length === 0) return false;
|
|
688
|
+
return patterns.some(
|
|
689
|
+
(pattern) => pattern instanceof RegExp ? pattern.test(message) : message.includes(pattern)
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
function sessionMetadata(session) {
|
|
693
|
+
return {
|
|
694
|
+
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
695
|
+
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
696
|
+
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
697
|
+
...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function buildCapturedErrorPayload(error, options, cfg, capturedBy = "captureError") {
|
|
701
|
+
var _a, _b, _c;
|
|
702
|
+
const err = error instanceof Error ? error : void 0;
|
|
703
|
+
const errorMessage = err ? err.message : String(error);
|
|
704
|
+
if (!errorMessage) return null;
|
|
705
|
+
if (matchesIgnorePatterns(errorMessage, cfg.ignoreErrors)) return null;
|
|
706
|
+
incrementErrorCount();
|
|
707
|
+
const context = captureContext(cfg.getVisitedPages());
|
|
708
|
+
const digest = (_a = options == null ? void 0 : options.digest) != null ? _a : typeof (error == null ? void 0 : error.digest) === "string" ? error.digest : void 0;
|
|
709
|
+
const sig = computeSignature({
|
|
710
|
+
errorMessage,
|
|
711
|
+
pageUrl: context.url,
|
|
712
|
+
errorStack: err == null ? void 0 : err.stack,
|
|
713
|
+
digest
|
|
714
|
+
});
|
|
715
|
+
if (shouldSkipDuplicate(sig)) return null;
|
|
716
|
+
return {
|
|
717
|
+
token: cfg.token,
|
|
718
|
+
source: "SDK_AUTO",
|
|
719
|
+
type: "BUG",
|
|
720
|
+
errorMessage,
|
|
721
|
+
errorStack: err == null ? void 0 : err.stack,
|
|
722
|
+
componentStack: (_b = options == null ? void 0 : options.componentStack) != null ? _b : void 0,
|
|
723
|
+
pageUrl: context.url,
|
|
724
|
+
userAgent: context.userAgent,
|
|
725
|
+
breadcrumbs: context.breadcrumbs,
|
|
726
|
+
deviceInfo: (_c = context.deviceInfo) != null ? _c : void 0,
|
|
727
|
+
metadata: {
|
|
728
|
+
timestamp: context.timestamp,
|
|
729
|
+
visitedPages: JSON.stringify(context.visitedPages),
|
|
730
|
+
capturedBy,
|
|
731
|
+
...contextMetadata(context),
|
|
732
|
+
...(options == null ? void 0 : options.boundary) ? { boundary: options.boundary } : {},
|
|
733
|
+
...digest ? { digest } : {},
|
|
734
|
+
...sessionMetadata(cfg.session),
|
|
735
|
+
...options == null ? void 0 : options.metadata
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function captureError(error, options) {
|
|
740
|
+
try {
|
|
741
|
+
const cfg = config;
|
|
742
|
+
if (!(cfg == null ? void 0 : cfg.token)) return;
|
|
743
|
+
const payload = buildCapturedErrorPayload(error, options, cfg);
|
|
744
|
+
if (!payload) return;
|
|
745
|
+
void sendReport(payload, cfg.baseUrl).then((result) => {
|
|
746
|
+
if (result && cfg.onReportSent) cfg.onReportSent(result);
|
|
747
|
+
});
|
|
748
|
+
if (cfg.onError && error instanceof Error) cfg.onError(error);
|
|
749
|
+
} catch (e) {
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
394
753
|
// src/error-boundary.tsx
|
|
395
754
|
var GlitchgrabErrorBoundary = class extends React.Component {
|
|
396
755
|
constructor(props) {
|
|
@@ -401,40 +760,22 @@ var GlitchgrabErrorBoundary = class extends React.Component {
|
|
|
401
760
|
return { hasError: true };
|
|
402
761
|
}
|
|
403
762
|
componentDidCatch(error, errorInfo) {
|
|
404
|
-
var _a
|
|
763
|
+
var _a;
|
|
405
764
|
try {
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
765
|
+
const payload = buildCapturedErrorPayload(
|
|
766
|
+
error,
|
|
767
|
+
{ componentStack: (_a = errorInfo.componentStack) != null ? _a : void 0 },
|
|
768
|
+
{
|
|
769
|
+
token: this.props.token,
|
|
770
|
+
baseUrl: this.props.baseUrl,
|
|
771
|
+
session: this.props.session,
|
|
772
|
+
getVisitedPages: () => this.props.visitedPages
|
|
773
|
+
},
|
|
774
|
+
"GlitchgrabErrorBoundary"
|
|
775
|
+
);
|
|
776
|
+
if (payload) {
|
|
777
|
+
sendReport(payload, this.props.baseUrl);
|
|
415
778
|
}
|
|
416
|
-
const session = this.props.session;
|
|
417
|
-
const payload = {
|
|
418
|
-
token: this.props.token,
|
|
419
|
-
source: "SDK_AUTO",
|
|
420
|
-
type: "BUG",
|
|
421
|
-
errorMessage: error.message,
|
|
422
|
-
errorStack: error.stack,
|
|
423
|
-
componentStack: (_a = errorInfo.componentStack) != null ? _a : void 0,
|
|
424
|
-
pageUrl: context.url,
|
|
425
|
-
userAgent: context.userAgent,
|
|
426
|
-
breadcrumbs: context.breadcrumbs,
|
|
427
|
-
deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
|
|
428
|
-
metadata: {
|
|
429
|
-
timestamp: context.timestamp,
|
|
430
|
-
visitedPages: JSON.stringify(context.visitedPages),
|
|
431
|
-
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
432
|
-
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
433
|
-
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
434
|
-
...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {}
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
sendReport(payload, this.props.baseUrl);
|
|
438
779
|
if (this.props.onError) {
|
|
439
780
|
this.props.onError(error);
|
|
440
781
|
}
|
|
@@ -452,17 +793,45 @@ var GlitchgrabErrorBoundary = class extends React.Component {
|
|
|
452
793
|
}
|
|
453
794
|
};
|
|
454
795
|
|
|
455
|
-
//
|
|
796
|
+
// ../report-ui/dist/index.mjs
|
|
456
797
|
import {
|
|
457
798
|
useState as useState2,
|
|
458
799
|
useRef as useRef2,
|
|
459
800
|
useEffect as useEffect2
|
|
460
801
|
} from "react";
|
|
461
802
|
import { createPortal } from "react-dom";
|
|
462
|
-
|
|
463
|
-
// src/annotation-canvas.tsx
|
|
464
803
|
import { useEffect, useRef, useState } from "react";
|
|
465
804
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
805
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
806
|
+
var MAX_SCREENSHOT_DATA_URL_LENGTH = 18e5;
|
|
807
|
+
var QUALITY_LADDER = [0.92, 0.85, 0.75];
|
|
808
|
+
var DOWNSCALE_FACTOR = 0.7;
|
|
809
|
+
function downscale(canvas, factor) {
|
|
810
|
+
const next = document.createElement("canvas");
|
|
811
|
+
next.width = Math.max(1, Math.round(canvas.width * factor));
|
|
812
|
+
next.height = Math.max(1, Math.round(canvas.height * factor));
|
|
813
|
+
const ctx = next.getContext("2d");
|
|
814
|
+
if (!ctx) return null;
|
|
815
|
+
ctx.imageSmoothingEnabled = true;
|
|
816
|
+
ctx.imageSmoothingQuality = "high";
|
|
817
|
+
ctx.drawImage(canvas, 0, 0, next.width, next.height);
|
|
818
|
+
return next;
|
|
819
|
+
}
|
|
820
|
+
function encodeScreenshot(canvas, maxLength = MAX_SCREENSHOT_DATA_URL_LENGTH) {
|
|
821
|
+
let current = canvas;
|
|
822
|
+
for (let round = 0; round < 2; round++) {
|
|
823
|
+
let encoded = "";
|
|
824
|
+
for (const quality of QUALITY_LADDER) {
|
|
825
|
+
encoded = current.toDataURL("image/jpeg", quality);
|
|
826
|
+
if (encoded.length <= maxLength) return encoded;
|
|
827
|
+
}
|
|
828
|
+
if (round === 1) return encoded;
|
|
829
|
+
const smaller = downscale(current, DOWNSCALE_FACTOR);
|
|
830
|
+
if (!smaller) return encoded;
|
|
831
|
+
current = smaller;
|
|
832
|
+
}
|
|
833
|
+
return current.toDataURL("image/jpeg", QUALITY_LADDER[0]);
|
|
834
|
+
}
|
|
466
835
|
var PALETTE = ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#a855f7", "#ffffff"];
|
|
467
836
|
function drawStroke(ctx, s) {
|
|
468
837
|
ctx.strokeStyle = s.color;
|
|
@@ -548,7 +917,7 @@ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
|
|
|
548
917
|
const handleSave = () => {
|
|
549
918
|
const canvas = canvasRef.current;
|
|
550
919
|
if (!canvas) return;
|
|
551
|
-
onSave(canvas
|
|
920
|
+
onSave(encodeScreenshot(canvas));
|
|
552
921
|
};
|
|
553
922
|
const iconBtn = (active = false) => ({
|
|
554
923
|
width: "30px",
|
|
@@ -738,8 +1107,6 @@ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
|
|
|
738
1107
|
}
|
|
739
1108
|
);
|
|
740
1109
|
}
|
|
741
|
-
|
|
742
|
-
// src/shortcut.ts
|
|
743
1110
|
var GLITCHGRAB_SHORTCUT = "Ctrl+Shift+G";
|
|
744
1111
|
var GLITCHGRAB_SHORTCUT_MAC = "\u2318\u21E7G";
|
|
745
1112
|
function isMac() {
|
|
@@ -753,12 +1120,38 @@ function isMac() {
|
|
|
753
1120
|
function getShortcutLabel() {
|
|
754
1121
|
return isMac() ? GLITCHGRAB_SHORTCUT_MAC : GLITCHGRAB_SHORTCUT;
|
|
755
1122
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1123
|
+
var ATTACHMENT_ACCEPT = [
|
|
1124
|
+
"image/*",
|
|
1125
|
+
// text — inlined into the issue body
|
|
1126
|
+
".html",
|
|
1127
|
+
".htm",
|
|
1128
|
+
".txt",
|
|
1129
|
+
".md",
|
|
1130
|
+
".json",
|
|
1131
|
+
".xml",
|
|
1132
|
+
".log",
|
|
1133
|
+
".yml",
|
|
1134
|
+
".yaml",
|
|
1135
|
+
".csv",
|
|
1136
|
+
".har",
|
|
1137
|
+
".js",
|
|
1138
|
+
".mjs",
|
|
1139
|
+
".cjs",
|
|
1140
|
+
".ts",
|
|
1141
|
+
".tsx",
|
|
1142
|
+
".jsx",
|
|
1143
|
+
".css",
|
|
1144
|
+
".scss",
|
|
1145
|
+
".py",
|
|
1146
|
+
".sh",
|
|
1147
|
+
// binary documents
|
|
1148
|
+
".pdf",
|
|
1149
|
+
".doc",
|
|
1150
|
+
".docx",
|
|
1151
|
+
".xls",
|
|
1152
|
+
".xlsx",
|
|
1153
|
+
".zip"
|
|
1154
|
+
].join(",");
|
|
762
1155
|
function useIsDark() {
|
|
763
1156
|
if (typeof window === "undefined") return true;
|
|
764
1157
|
try {
|
|
@@ -871,6 +1264,98 @@ function detectHostAccent() {
|
|
|
871
1264
|
return null;
|
|
872
1265
|
}
|
|
873
1266
|
}
|
|
1267
|
+
function initialsOf(name) {
|
|
1268
|
+
return name.trim().split(/\s+/).slice(0, 2).map((part) => {
|
|
1269
|
+
var _a, _b;
|
|
1270
|
+
return (_b = (_a = part[0]) == null ? void 0 : _a.toUpperCase()) != null ? _b : "";
|
|
1271
|
+
}).join("");
|
|
1272
|
+
}
|
|
1273
|
+
function ReporterChip({
|
|
1274
|
+
reporter,
|
|
1275
|
+
t
|
|
1276
|
+
}) {
|
|
1277
|
+
var _a;
|
|
1278
|
+
const [avatarFailed, setAvatarFailed] = useState2(false);
|
|
1279
|
+
const avatarUrl = (reporter == null ? void 0 : reporter.avatarUrl) || "";
|
|
1280
|
+
useEffect2(() => {
|
|
1281
|
+
setAvatarFailed(false);
|
|
1282
|
+
}, [avatarUrl]);
|
|
1283
|
+
const name = ((_a = reporter == null ? void 0 : reporter.name) == null ? void 0 : _a.trim()) || "";
|
|
1284
|
+
const isAnonymous = !name;
|
|
1285
|
+
const initials = isAnonymous ? "" : initialsOf(name);
|
|
1286
|
+
const label = isAnonymous ? "Anonymous" : name;
|
|
1287
|
+
const title = isAnonymous ? "No reporter identity was passed to Glitchgrab \u2014 this report won't be attributed to anyone" : [name, reporter == null ? void 0 : reporter.email, reporter == null ? void 0 : reporter.role].filter(Boolean).join(" \xB7 ");
|
|
1288
|
+
const showAvatarImage = !!avatarUrl && !avatarFailed;
|
|
1289
|
+
return /* @__PURE__ */ jsxs2(
|
|
1290
|
+
"span",
|
|
1291
|
+
{
|
|
1292
|
+
title,
|
|
1293
|
+
style: { display: "flex", alignItems: "center", gap: "6px", minWidth: 0 },
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ jsx2(
|
|
1296
|
+
"span",
|
|
1297
|
+
{
|
|
1298
|
+
"aria-hidden": "true",
|
|
1299
|
+
style: {
|
|
1300
|
+
width: "18px",
|
|
1301
|
+
height: "18px",
|
|
1302
|
+
flexShrink: 0,
|
|
1303
|
+
borderRadius: "50%",
|
|
1304
|
+
overflow: "hidden",
|
|
1305
|
+
display: "flex",
|
|
1306
|
+
alignItems: "center",
|
|
1307
|
+
justifyContent: "center",
|
|
1308
|
+
fontSize: "8px",
|
|
1309
|
+
fontWeight: 700,
|
|
1310
|
+
letterSpacing: "0.02em",
|
|
1311
|
+
background: isAnonymous ? t.inputBg : t.accent,
|
|
1312
|
+
color: isAnonymous ? t.textMuted : t.accentText,
|
|
1313
|
+
border: `1px solid ${isAnonymous ? t.inputBorder : "transparent"}`
|
|
1314
|
+
},
|
|
1315
|
+
children: showAvatarImage ? /* @__PURE__ */ jsx2(
|
|
1316
|
+
"img",
|
|
1317
|
+
{
|
|
1318
|
+
src: avatarUrl,
|
|
1319
|
+
alt: "",
|
|
1320
|
+
width: 18,
|
|
1321
|
+
height: 18,
|
|
1322
|
+
onError: () => setAvatarFailed(true),
|
|
1323
|
+
style: { width: "100%", height: "100%", objectFit: "cover" }
|
|
1324
|
+
}
|
|
1325
|
+
) : initials ? initials : /* @__PURE__ */ jsxs2("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", children: [
|
|
1326
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "5.5", r: "2.75", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
1327
|
+
/* @__PURE__ */ jsx2(
|
|
1328
|
+
"path",
|
|
1329
|
+
{
|
|
1330
|
+
d: "M2.75 13.5c0-2.35 2.35-3.75 5.25-3.75s5.25 1.4 5.25 3.75",
|
|
1331
|
+
stroke: "currentColor",
|
|
1332
|
+
strokeWidth: "1.4",
|
|
1333
|
+
strokeLinecap: "round"
|
|
1334
|
+
}
|
|
1335
|
+
)
|
|
1336
|
+
] })
|
|
1337
|
+
}
|
|
1338
|
+
),
|
|
1339
|
+
/* @__PURE__ */ jsxs2(
|
|
1340
|
+
"span",
|
|
1341
|
+
{
|
|
1342
|
+
style: {
|
|
1343
|
+
fontSize: "11px",
|
|
1344
|
+
color: t.textMuted,
|
|
1345
|
+
overflow: "hidden",
|
|
1346
|
+
textOverflow: "ellipsis",
|
|
1347
|
+
whiteSpace: "nowrap"
|
|
1348
|
+
},
|
|
1349
|
+
children: [
|
|
1350
|
+
label,
|
|
1351
|
+
(reporter == null ? void 0 : reporter.role) ? ` \xB7 ${reporter.role}` : ""
|
|
1352
|
+
]
|
|
1353
|
+
}
|
|
1354
|
+
)
|
|
1355
|
+
]
|
|
1356
|
+
}
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
874
1359
|
function getTheme(dark) {
|
|
875
1360
|
const hostAccent = detectHostAccent();
|
|
876
1361
|
const defaults = dark ? {
|
|
@@ -940,12 +1425,36 @@ function isLowQualityText(text) {
|
|
|
940
1425
|
}
|
|
941
1426
|
return null;
|
|
942
1427
|
}
|
|
1428
|
+
async function captureViaHtml2Canvas() {
|
|
1429
|
+
try {
|
|
1430
|
+
const { default: html2canvas } = await import("html2canvas-pro");
|
|
1431
|
+
const scale = Math.min(Math.max(window.devicePixelRatio || 1, 1), 2);
|
|
1432
|
+
const canvas = await html2canvas(document.body, {
|
|
1433
|
+
scale,
|
|
1434
|
+
logging: false,
|
|
1435
|
+
useCORS: true,
|
|
1436
|
+
allowTaint: true,
|
|
1437
|
+
x: window.scrollX,
|
|
1438
|
+
y: window.scrollY,
|
|
1439
|
+
width: window.innerWidth,
|
|
1440
|
+
height: window.innerHeight,
|
|
1441
|
+
windowWidth: window.innerWidth,
|
|
1442
|
+
windowHeight: window.innerHeight
|
|
1443
|
+
});
|
|
1444
|
+
return encodeScreenshot(canvas);
|
|
1445
|
+
} catch (e) {
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
943
1449
|
function ReportDialog({
|
|
944
1450
|
report,
|
|
1451
|
+
sendFeedback: sendFeedback2,
|
|
945
1452
|
enhanceText: enhanceText2,
|
|
946
1453
|
transcribeAudio: transcribeAudio2,
|
|
947
1454
|
types,
|
|
948
|
-
showSeverity = true
|
|
1455
|
+
showSeverity = true,
|
|
1456
|
+
captureScreenshot = captureViaHtml2Canvas,
|
|
1457
|
+
reporter
|
|
949
1458
|
}) {
|
|
950
1459
|
const [isEnhancing, setIsEnhancing] = useState2(false);
|
|
951
1460
|
const [isEnhanced, setIsEnhanced] = useState2(false);
|
|
@@ -968,6 +1477,25 @@ function ReportDialog({
|
|
|
968
1477
|
const [attachments, setAttachments] = useState2([]);
|
|
969
1478
|
const [previewIndex, setPreviewIndex] = useState2(null);
|
|
970
1479
|
const [annotatingIndex, setAnnotatingIndex] = useState2(null);
|
|
1480
|
+
const [previewZoomed, setPreviewZoomed] = useState2(false);
|
|
1481
|
+
const [previewNatural, setPreviewNatural] = useState2(null);
|
|
1482
|
+
const previewScrollRef = useRef2(null);
|
|
1483
|
+
const previewImgElRef = useRef2(null);
|
|
1484
|
+
const panRef = useRef2(null);
|
|
1485
|
+
const zoomAnchorRef = useRef2(null);
|
|
1486
|
+
const resetPreviewZoom = () => {
|
|
1487
|
+
setPreviewZoomed(false);
|
|
1488
|
+
setPreviewNatural(null);
|
|
1489
|
+
zoomAnchorRef.current = null;
|
|
1490
|
+
};
|
|
1491
|
+
const openPreview = (i) => {
|
|
1492
|
+
resetPreviewZoom();
|
|
1493
|
+
setPreviewIndex(i);
|
|
1494
|
+
};
|
|
1495
|
+
const closePreview = () => {
|
|
1496
|
+
resetPreviewZoom();
|
|
1497
|
+
setPreviewIndex(null);
|
|
1498
|
+
};
|
|
971
1499
|
const fileInputRef = useRef2(null);
|
|
972
1500
|
const modalRef = useRef2(null);
|
|
973
1501
|
const previewImgRef = useRef2(null);
|
|
@@ -996,17 +1524,64 @@ function ReportDialog({
|
|
|
996
1524
|
const [severity, setSeverity] = useState2("medium");
|
|
997
1525
|
const [validationError, setValidationError] = useState2(null);
|
|
998
1526
|
const [placeholderIdx, setPlaceholderIdx] = useState2(0);
|
|
1527
|
+
const [rating, setRating] = useState2(0);
|
|
1528
|
+
const [hoveredStar, setHoveredStar] = useState2(0);
|
|
1529
|
+
const isRating = reportType === "RATING";
|
|
1530
|
+
const submitDisabled = isSubmitting || (isRating ? rating < 1 : !description.trim());
|
|
999
1531
|
const availableTypes = types != null ? types : [
|
|
1000
1532
|
"BUG",
|
|
1001
1533
|
"FEATURE_REQUEST",
|
|
1002
1534
|
"UI_IMPROVEMENT",
|
|
1003
1535
|
"PERFORMANCE",
|
|
1004
1536
|
"SECURITY",
|
|
1005
|
-
"QUESTION"
|
|
1006
|
-
"OTHER"
|
|
1537
|
+
"QUESTION"
|
|
1007
1538
|
];
|
|
1539
|
+
const tileGridRef = useRef2(null);
|
|
1540
|
+
useEffect2(() => {
|
|
1541
|
+
if (!isOpen || step !== 1) return;
|
|
1542
|
+
const onKey = (e) => {
|
|
1543
|
+
const el = e.target;
|
|
1544
|
+
const tag = el == null ? void 0 : el.tagName;
|
|
1545
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || (el == null ? void 0 : el.isContentEditable)) return;
|
|
1546
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
1547
|
+
if (sendFeedback2 && /^[1-5]$/.test(e.key)) {
|
|
1548
|
+
e.preventDefault();
|
|
1549
|
+
setRating(Number(e.key));
|
|
1550
|
+
setReportType("RATING");
|
|
1551
|
+
setStep(2);
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
const letter = e.key.toLowerCase();
|
|
1555
|
+
if (!/^[a-z]$/.test(letter)) return;
|
|
1556
|
+
const match = availableTypes.find(
|
|
1557
|
+
(type) => getTypeLabel(type).toLowerCase().startsWith(letter)
|
|
1558
|
+
);
|
|
1559
|
+
if (match) {
|
|
1560
|
+
e.preventDefault();
|
|
1561
|
+
setReportType(match);
|
|
1562
|
+
setStep(2);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
window.addEventListener("keydown", onKey);
|
|
1566
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1567
|
+
}, [isOpen, step, sendFeedback2, availableTypes.join(",")]);
|
|
1568
|
+
const handleTileGridKeyDown = (e) => {
|
|
1569
|
+
var _a, _b, _c;
|
|
1570
|
+
const keys = ["ArrowRight", "ArrowLeft", "ArrowDown", "ArrowUp"];
|
|
1571
|
+
if (!keys.includes(e.key)) return;
|
|
1572
|
+
const tiles = Array.from(
|
|
1573
|
+
(_b = (_a = tileGridRef.current) == null ? void 0 : _a.querySelectorAll("button[data-gg-type]")) != null ? _b : []
|
|
1574
|
+
);
|
|
1575
|
+
if (tiles.length === 0) return;
|
|
1576
|
+
e.preventDefault();
|
|
1577
|
+
const current = tiles.indexOf(document.activeElement);
|
|
1578
|
+
const delta = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : e.key === "ArrowDown" ? 2 : -2;
|
|
1579
|
+
const next = current === -1 ? 0 : Math.min(Math.max(current + delta, 0), tiles.length - 1);
|
|
1580
|
+
(_c = tiles[next]) == null ? void 0 : _c.focus();
|
|
1581
|
+
};
|
|
1008
1582
|
const isDark = useIsDark();
|
|
1009
1583
|
const t = getTheme(isDark);
|
|
1584
|
+
const canZoomPreview = previewNatural !== null && typeof window !== "undefined" && (previewNatural.w > window.innerWidth - 32 || previewNatural.h > window.innerHeight - 80);
|
|
1010
1585
|
const handleFileUpload = (e) => {
|
|
1011
1586
|
const files = e.target.files;
|
|
1012
1587
|
if (!files || files.length === 0) return;
|
|
@@ -1031,53 +1606,14 @@ function ReportDialog({
|
|
|
1031
1606
|
reader.readAsDataURL(file);
|
|
1032
1607
|
});
|
|
1033
1608
|
};
|
|
1034
|
-
const retakeScreenshot = async () => {
|
|
1035
|
-
setPreviewIndex(null);
|
|
1036
|
-
setIsOpen(false);
|
|
1037
|
-
setScreenshots([]);
|
|
1038
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
1039
|
-
try {
|
|
1040
|
-
const { default: html2canvas } = await import("html2canvas-pro");
|
|
1041
|
-
const canvas = await html2canvas(document.body, {
|
|
1042
|
-
scale: 0.5,
|
|
1043
|
-
logging: false,
|
|
1044
|
-
useCORS: true,
|
|
1045
|
-
allowTaint: true,
|
|
1046
|
-
x: window.scrollX,
|
|
1047
|
-
y: window.scrollY,
|
|
1048
|
-
width: window.innerWidth,
|
|
1049
|
-
height: window.innerHeight,
|
|
1050
|
-
windowWidth: window.innerWidth,
|
|
1051
|
-
windowHeight: window.innerHeight
|
|
1052
|
-
});
|
|
1053
|
-
setScreenshots([canvas.toDataURL("image/jpeg", 0.6)]);
|
|
1054
|
-
} catch (e) {
|
|
1055
|
-
}
|
|
1056
|
-
setIsOpen(true);
|
|
1057
|
-
};
|
|
1058
1609
|
const handleOpen = async () => {
|
|
1059
1610
|
setSubmitted(false);
|
|
1060
1611
|
if (availableTypes.length === 1) {
|
|
1061
1612
|
setReportType(availableTypes[0]);
|
|
1062
1613
|
setStep(2);
|
|
1063
1614
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const canvas = await html2canvas(document.body, {
|
|
1067
|
-
scale: 0.5,
|
|
1068
|
-
logging: false,
|
|
1069
|
-
useCORS: true,
|
|
1070
|
-
allowTaint: true,
|
|
1071
|
-
x: window.scrollX,
|
|
1072
|
-
y: window.scrollY,
|
|
1073
|
-
width: window.innerWidth,
|
|
1074
|
-
height: window.innerHeight,
|
|
1075
|
-
windowWidth: window.innerWidth,
|
|
1076
|
-
windowHeight: window.innerHeight
|
|
1077
|
-
});
|
|
1078
|
-
setScreenshots([canvas.toDataURL("image/jpeg", 0.6)]);
|
|
1079
|
-
} catch (e) {
|
|
1080
|
-
}
|
|
1615
|
+
const shot = await captureScreenshot();
|
|
1616
|
+
if (shot) setScreenshots([shot]);
|
|
1081
1617
|
setIsOpen(true);
|
|
1082
1618
|
};
|
|
1083
1619
|
useEffect2(() => {
|
|
@@ -1098,24 +1634,87 @@ function ReportDialog({
|
|
|
1098
1634
|
if (!isOpen) return;
|
|
1099
1635
|
const handleEsc = (e) => {
|
|
1100
1636
|
if (e.key === "Escape") {
|
|
1101
|
-
if (
|
|
1637
|
+
if (previewZoomed) setPreviewZoomed(false);
|
|
1638
|
+
else if (previewIndex !== null) closePreview();
|
|
1102
1639
|
else handleClose();
|
|
1103
1640
|
}
|
|
1104
1641
|
};
|
|
1105
1642
|
document.addEventListener("keydown", handleEsc);
|
|
1106
1643
|
return () => document.removeEventListener("keydown", handleEsc);
|
|
1107
|
-
}, [isOpen, previewIndex]);
|
|
1644
|
+
}, [isOpen, previewIndex, previewZoomed]);
|
|
1108
1645
|
useEffect2(() => {
|
|
1109
1646
|
if (previewIndex === null || annotatingIndex !== null) return;
|
|
1110
1647
|
const handlePointerDown = (e) => {
|
|
1111
1648
|
var _a;
|
|
1112
1649
|
if (!((_a = previewImgRef.current) == null ? void 0 : _a.contains(e.target))) {
|
|
1113
|
-
|
|
1650
|
+
closePreview();
|
|
1114
1651
|
}
|
|
1115
1652
|
};
|
|
1116
1653
|
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
1117
1654
|
return () => document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
1118
1655
|
}, [previewIndex, annotatingIndex]);
|
|
1656
|
+
useEffect2(() => {
|
|
1657
|
+
var _a;
|
|
1658
|
+
const el = previewScrollRef.current;
|
|
1659
|
+
if (!previewZoomed || !el) return;
|
|
1660
|
+
const anchor = (_a = zoomAnchorRef.current) != null ? _a : { fx: 0.5, fy: 0.5 };
|
|
1661
|
+
el.scrollLeft = anchor.fx * el.scrollWidth - el.clientWidth / 2;
|
|
1662
|
+
el.scrollTop = anchor.fy * el.scrollHeight - el.clientHeight / 2;
|
|
1663
|
+
}, [previewZoomed]);
|
|
1664
|
+
const togglePreviewZoom = (e) => {
|
|
1665
|
+
var _a;
|
|
1666
|
+
e.stopPropagation();
|
|
1667
|
+
if ((_a = panRef.current) == null ? void 0 : _a.moved) return;
|
|
1668
|
+
if (!previewZoomed && !canZoomPreview) return;
|
|
1669
|
+
const img = previewImgElRef.current;
|
|
1670
|
+
if (!previewZoomed && img) {
|
|
1671
|
+
const rect = img.getBoundingClientRect();
|
|
1672
|
+
zoomAnchorRef.current = {
|
|
1673
|
+
fx: (e.clientX - rect.left) / rect.width,
|
|
1674
|
+
fy: (e.clientY - rect.top) / rect.height
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
setPreviewZoomed((z) => !z);
|
|
1678
|
+
};
|
|
1679
|
+
const handlePanStart = (e) => {
|
|
1680
|
+
const el = previewScrollRef.current;
|
|
1681
|
+
if (!previewZoomed || !el) return;
|
|
1682
|
+
panRef.current = {
|
|
1683
|
+
x: e.clientX,
|
|
1684
|
+
y: e.clientY,
|
|
1685
|
+
left: el.scrollLeft,
|
|
1686
|
+
top: el.scrollTop,
|
|
1687
|
+
moved: false
|
|
1688
|
+
};
|
|
1689
|
+
el.setPointerCapture(e.pointerId);
|
|
1690
|
+
};
|
|
1691
|
+
const handlePanMove = (e) => {
|
|
1692
|
+
const el = previewScrollRef.current;
|
|
1693
|
+
const start = panRef.current;
|
|
1694
|
+
if (!el || !start) return;
|
|
1695
|
+
const dx = e.clientX - start.x;
|
|
1696
|
+
const dy = e.clientY - start.y;
|
|
1697
|
+
if (Math.abs(dx) > 4 || Math.abs(dy) > 4) start.moved = true;
|
|
1698
|
+
el.scrollLeft = start.left - dx;
|
|
1699
|
+
el.scrollTop = start.top - dy;
|
|
1700
|
+
};
|
|
1701
|
+
const handlePanEnd = (e) => {
|
|
1702
|
+
var _a, _b;
|
|
1703
|
+
const el = previewScrollRef.current;
|
|
1704
|
+
const moved = (_b = (_a = panRef.current) == null ? void 0 : _a.moved) != null ? _b : false;
|
|
1705
|
+
try {
|
|
1706
|
+
el == null ? void 0 : el.releasePointerCapture(e.pointerId);
|
|
1707
|
+
} catch (e2) {
|
|
1708
|
+
}
|
|
1709
|
+
if (moved) {
|
|
1710
|
+
panRef.current = { x: 0, y: 0, left: 0, top: 0, moved: true };
|
|
1711
|
+
setTimeout(() => {
|
|
1712
|
+
panRef.current = null;
|
|
1713
|
+
}, 0);
|
|
1714
|
+
} else {
|
|
1715
|
+
panRef.current = null;
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1119
1718
|
useEffect2(() => {
|
|
1120
1719
|
if (!isOpen) return;
|
|
1121
1720
|
const handlePaste = (e) => {
|
|
@@ -1123,18 +1722,16 @@ function ReportDialog({
|
|
|
1123
1722
|
try {
|
|
1124
1723
|
const items = (_a = e.clipboardData) == null ? void 0 : _a.items;
|
|
1125
1724
|
if (!items) return;
|
|
1126
|
-
|
|
1725
|
+
const pasted = [];
|
|
1127
1726
|
for (let i = 0; i < items.length; i++) {
|
|
1128
|
-
if (items[i].
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1727
|
+
if (items[i].kind !== "file") continue;
|
|
1728
|
+
const file = items[i].getAsFile();
|
|
1729
|
+
if (file) pasted.push(file);
|
|
1730
|
+
}
|
|
1731
|
+
if (pasted.length > 0) {
|
|
1732
|
+
e.preventDefault();
|
|
1733
|
+
addFiles(pasted);
|
|
1136
1734
|
}
|
|
1137
|
-
if (matched) e.preventDefault();
|
|
1138
1735
|
} catch (e2) {
|
|
1139
1736
|
}
|
|
1140
1737
|
};
|
|
@@ -1180,6 +1777,8 @@ function ReportDialog({
|
|
|
1180
1777
|
stopVoice();
|
|
1181
1778
|
setIsOpen(false);
|
|
1182
1779
|
setStep(1);
|
|
1780
|
+
setRating(0);
|
|
1781
|
+
setHoveredStar(0);
|
|
1183
1782
|
setReportType("BUG");
|
|
1184
1783
|
setSeverity("medium");
|
|
1185
1784
|
setValidationError(null);
|
|
@@ -1325,7 +1924,30 @@ function ReportDialog({
|
|
|
1325
1924
|
};
|
|
1326
1925
|
const handleSubmit = async () => {
|
|
1327
1926
|
try {
|
|
1328
|
-
if (
|
|
1927
|
+
if (isSubmitting) return;
|
|
1928
|
+
if (isRating) {
|
|
1929
|
+
if (rating < 1) {
|
|
1930
|
+
setValidationError("Pick a star rating first.");
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
if (!sendFeedback2) return;
|
|
1934
|
+
setIsSubmitting(true);
|
|
1935
|
+
const result2 = await sendFeedback2(rating, description.trim() || void 0);
|
|
1936
|
+
if (result2 == null ? void 0 : result2.success) {
|
|
1937
|
+
setSubmitted(true);
|
|
1938
|
+
setDescription("");
|
|
1939
|
+
setRating(0);
|
|
1940
|
+
setTimeout(() => {
|
|
1941
|
+
setSubmitted(false);
|
|
1942
|
+
handleClose();
|
|
1943
|
+
}, 2e3);
|
|
1944
|
+
} else {
|
|
1945
|
+
setValidationError("Could not send \u2014 please try again.");
|
|
1946
|
+
}
|
|
1947
|
+
setIsSubmitting(false);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
if (!description.trim()) return;
|
|
1329
1951
|
const qualityError = isLowQualityText(description);
|
|
1330
1952
|
if (qualityError) {
|
|
1331
1953
|
setValidationError(qualityError);
|
|
@@ -1340,6 +1962,8 @@ function ReportDialog({
|
|
|
1340
1962
|
metadata.severity = severity;
|
|
1341
1963
|
}
|
|
1342
1964
|
const result = await report(
|
|
1965
|
+
// The RATING branch returned above, so anything reaching here is a real
|
|
1966
|
+
// ReportType — the widened tile union can't leak into the report API.
|
|
1343
1967
|
reportType,
|
|
1344
1968
|
description.trim(),
|
|
1345
1969
|
Object.keys(metadata).length > 0 ? metadata : void 0
|
|
@@ -1383,6 +2007,18 @@ function ReportDialog({
|
|
|
1383
2007
|
button[data-gg-type="SECURITY"] > svg{animation:gg-shield-pulse 2.2s ease-in-out infinite;transform-origin:top}
|
|
1384
2008
|
button[data-gg-type="QUESTION"] > svg{animation:gg-question-shake 2s ease-in-out infinite}
|
|
1385
2009
|
button[data-gg-type="OTHER"] > svg{animation:gg-chat-tilt 2.2s ease-in-out infinite}
|
|
2010
|
+
/* Lift on hover, settle on press. Cheaper and steadier than doing it
|
|
2011
|
+
through React state, and :focus-visible keeps the ring off mouse users
|
|
2012
|
+
while leaving it for the arrow-key path. */
|
|
2013
|
+
button[data-gg-type]{transition:transform .15s ease,border-color .15s ease,box-shadow .15s ease,background-color .15s ease}
|
|
2014
|
+
button[data-gg-type]:hover{transform:translateY(-2px);box-shadow:0 6px 16px rgba(0,0,0,.18)}
|
|
2015
|
+
button[data-gg-type]:active{transform:translateY(0) scale(.985);box-shadow:none}
|
|
2016
|
+
button[data-gg-type]:focus-visible{outline:2px solid ${t.accent};outline-offset:2px}
|
|
2017
|
+
@keyframes gg-star-pop{0%{transform:scale(.6);opacity:0}60%{transform:scale(1.12)}100%{transform:scale(1);opacity:1}}
|
|
2018
|
+
button[data-gg-star]{transition:transform .12s ease}
|
|
2019
|
+
button[data-gg-star]:hover{transform:scale(1.18)}
|
|
2020
|
+
button[data-gg-star]:active{transform:scale(.94)}
|
|
2021
|
+
button[data-gg-star]:focus-visible{outline:2px solid #f59e0b;outline-offset:3px;border-radius:4px}
|
|
1386
2022
|
` }),
|
|
1387
2023
|
document.body
|
|
1388
2024
|
),
|
|
@@ -1414,7 +2050,7 @@ function ReportDialog({
|
|
|
1414
2050
|
style: {
|
|
1415
2051
|
position: "relative",
|
|
1416
2052
|
zIndex: 2147483647,
|
|
1417
|
-
width: "
|
|
2053
|
+
width: "420px",
|
|
1418
2054
|
maxWidth: "calc(100% - 32px)",
|
|
1419
2055
|
maxHeight: "calc(100dvh - 32px)",
|
|
1420
2056
|
display: "flex",
|
|
@@ -1624,7 +2260,7 @@ function ReportDialog({
|
|
|
1624
2260
|
"div",
|
|
1625
2261
|
{
|
|
1626
2262
|
style: {
|
|
1627
|
-
padding: "
|
|
2263
|
+
padding: "18px",
|
|
1628
2264
|
overflowY: "auto",
|
|
1629
2265
|
flex: "1 1 auto",
|
|
1630
2266
|
minHeight: 0
|
|
@@ -1645,13 +2281,145 @@ function ReportDialog({
|
|
|
1645
2281
|
]
|
|
1646
2282
|
}
|
|
1647
2283
|
) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2284
|
+
step === 1 && sendFeedback2 && /* @__PURE__ */ jsxs2(
|
|
2285
|
+
"div",
|
|
2286
|
+
{
|
|
2287
|
+
style: {
|
|
2288
|
+
border: "1px solid rgba(245,158,11,0.35)",
|
|
2289
|
+
background: "rgba(245,158,11,0.07)",
|
|
2290
|
+
borderRadius: "10px",
|
|
2291
|
+
padding: "14px 16px",
|
|
2292
|
+
marginBottom: "16px",
|
|
2293
|
+
display: "flex",
|
|
2294
|
+
alignItems: "center",
|
|
2295
|
+
justifyContent: "space-between",
|
|
2296
|
+
gap: "12px",
|
|
2297
|
+
flexWrap: "wrap"
|
|
2298
|
+
},
|
|
2299
|
+
onMouseLeave: () => setHoveredStar(0),
|
|
2300
|
+
children: [
|
|
2301
|
+
/* @__PURE__ */ jsx2(
|
|
2302
|
+
"span",
|
|
2303
|
+
{
|
|
2304
|
+
style: { fontSize: "13px", fontWeight: 600, color: t.text },
|
|
2305
|
+
children: "How are we doing?"
|
|
2306
|
+
}
|
|
2307
|
+
),
|
|
2308
|
+
/* @__PURE__ */ jsx2("span", { style: { display: "flex", gap: "4px" }, children: [1, 2, 3, 4, 5].map((star) => {
|
|
2309
|
+
const filled = star <= (hoveredStar || rating);
|
|
2310
|
+
return /* @__PURE__ */ jsx2(
|
|
2311
|
+
"button",
|
|
2312
|
+
{
|
|
2313
|
+
type: "button",
|
|
2314
|
+
"data-gg-star": star,
|
|
2315
|
+
"aria-label": `${star} star${star > 1 ? "s" : ""}`,
|
|
2316
|
+
onMouseEnter: () => setHoveredStar(star),
|
|
2317
|
+
onFocus: () => setHoveredStar(star),
|
|
2318
|
+
onClick: () => {
|
|
2319
|
+
setRating(star);
|
|
2320
|
+
setReportType("RATING");
|
|
2321
|
+
setStep(2);
|
|
2322
|
+
},
|
|
2323
|
+
style: {
|
|
2324
|
+
border: "none",
|
|
2325
|
+
background: "transparent",
|
|
2326
|
+
padding: 0,
|
|
2327
|
+
cursor: "pointer",
|
|
2328
|
+
lineHeight: 0
|
|
2329
|
+
},
|
|
2330
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "26", height: "26", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
|
|
2331
|
+
"path",
|
|
2332
|
+
{
|
|
2333
|
+
d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
|
|
2334
|
+
fill: filled ? "#f59e0b" : "transparent",
|
|
2335
|
+
stroke: "#f59e0b",
|
|
2336
|
+
strokeWidth: "1.5",
|
|
2337
|
+
strokeLinejoin: "round"
|
|
2338
|
+
}
|
|
2339
|
+
) })
|
|
2340
|
+
},
|
|
2341
|
+
star
|
|
2342
|
+
);
|
|
2343
|
+
}) })
|
|
2344
|
+
]
|
|
2345
|
+
}
|
|
2346
|
+
),
|
|
2347
|
+
step === 1 && sendFeedback2 && /* @__PURE__ */ jsxs2(
|
|
2348
|
+
"div",
|
|
2349
|
+
{
|
|
2350
|
+
style: {
|
|
2351
|
+
display: "flex",
|
|
2352
|
+
alignItems: "center",
|
|
2353
|
+
gap: "10px",
|
|
2354
|
+
marginBottom: "16px"
|
|
2355
|
+
},
|
|
2356
|
+
children: [
|
|
2357
|
+
/* @__PURE__ */ jsx2("span", { style: { flex: 1, height: "1px", background: t.border } }),
|
|
2358
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: "11px", color: t.textMuted }, children: "or report something" }),
|
|
2359
|
+
/* @__PURE__ */ jsx2("span", { style: { flex: 1, height: "1px", background: t.border } })
|
|
2360
|
+
]
|
|
2361
|
+
}
|
|
2362
|
+
),
|
|
2363
|
+
step === 1 && screenshots.length > 0 && /* @__PURE__ */ jsxs2(
|
|
2364
|
+
"div",
|
|
2365
|
+
{
|
|
2366
|
+
style: {
|
|
2367
|
+
display: "flex",
|
|
2368
|
+
alignItems: "center",
|
|
2369
|
+
gap: "10px",
|
|
2370
|
+
marginBottom: "14px",
|
|
2371
|
+
padding: "8px 10px",
|
|
2372
|
+
border: `1px solid ${t.inputBorder}`,
|
|
2373
|
+
borderRadius: "8px",
|
|
2374
|
+
background: t.inputBg
|
|
2375
|
+
},
|
|
2376
|
+
children: [
|
|
2377
|
+
/* @__PURE__ */ jsx2(
|
|
2378
|
+
"img",
|
|
2379
|
+
{
|
|
2380
|
+
src: screenshots[0],
|
|
2381
|
+
alt: "",
|
|
2382
|
+
style: {
|
|
2383
|
+
width: "34px",
|
|
2384
|
+
height: "24px",
|
|
2385
|
+
objectFit: "cover",
|
|
2386
|
+
borderRadius: "4px",
|
|
2387
|
+
border: `1px solid ${t.border}`,
|
|
2388
|
+
flexShrink: 0
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
),
|
|
2392
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: "11px", color: t.textMuted, flex: 1 }, children: "Screenshot attached" }),
|
|
2393
|
+
/* @__PURE__ */ jsx2(
|
|
2394
|
+
"button",
|
|
2395
|
+
{
|
|
2396
|
+
type: "button",
|
|
2397
|
+
onClick: () => setScreenshots([]),
|
|
2398
|
+
"aria-label": "Remove screenshot",
|
|
2399
|
+
style: {
|
|
2400
|
+
border: "none",
|
|
2401
|
+
background: "transparent",
|
|
2402
|
+
color: t.textMuted,
|
|
2403
|
+
cursor: "pointer",
|
|
2404
|
+
fontSize: "15px",
|
|
2405
|
+
lineHeight: 1,
|
|
2406
|
+
padding: "2px 4px"
|
|
2407
|
+
},
|
|
2408
|
+
children: "\xD7"
|
|
2409
|
+
}
|
|
2410
|
+
)
|
|
2411
|
+
]
|
|
2412
|
+
}
|
|
2413
|
+
),
|
|
1648
2414
|
step === 1 && /* @__PURE__ */ jsx2(
|
|
1649
2415
|
"div",
|
|
1650
2416
|
{
|
|
2417
|
+
ref: tileGridRef,
|
|
2418
|
+
onKeyDown: handleTileGridKeyDown,
|
|
1651
2419
|
style: {
|
|
1652
2420
|
display: "grid",
|
|
1653
|
-
gridTemplateColumns: "1fr 1fr",
|
|
1654
|
-
gap: "
|
|
2421
|
+
gridTemplateColumns: "1fr 1fr 1fr",
|
|
2422
|
+
gap: "10px"
|
|
1655
2423
|
},
|
|
1656
2424
|
children: availableTypes.map((type) => /* @__PURE__ */ jsxs2(
|
|
1657
2425
|
"button",
|
|
@@ -1666,15 +2434,14 @@ function ReportDialog({
|
|
|
1666
2434
|
display: "flex",
|
|
1667
2435
|
flexDirection: "column",
|
|
1668
2436
|
alignItems: "center",
|
|
1669
|
-
gap: "
|
|
1670
|
-
padding: "
|
|
2437
|
+
gap: "9px",
|
|
2438
|
+
padding: "18px 10px",
|
|
1671
2439
|
borderRadius: "8px",
|
|
1672
2440
|
border: `1px solid ${t.inputBorder}`,
|
|
1673
2441
|
background: t.inputBg,
|
|
1674
2442
|
cursor: "pointer",
|
|
1675
2443
|
color: t.text,
|
|
1676
|
-
fontFamily: "inherit"
|
|
1677
|
-
transition: "border-color 0.15s ease"
|
|
2444
|
+
fontFamily: "inherit"
|
|
1678
2445
|
},
|
|
1679
2446
|
onMouseEnter: (e) => {
|
|
1680
2447
|
e.currentTarget.style.borderColor = t.accent;
|
|
@@ -1683,17 +2450,17 @@ function ReportDialog({
|
|
|
1683
2450
|
e.currentTarget.style.borderColor = t.inputBorder;
|
|
1684
2451
|
},
|
|
1685
2452
|
children: [
|
|
1686
|
-
getTypeIcon(type, t.accent),
|
|
1687
|
-
/* @__PURE__ */ jsx2("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
|
|
2453
|
+
getTypeIcon(type, t.accent, 22),
|
|
1688
2454
|
/* @__PURE__ */ jsx2(
|
|
1689
2455
|
"span",
|
|
1690
2456
|
{
|
|
1691
2457
|
style: {
|
|
1692
|
-
fontSize: "
|
|
1693
|
-
|
|
1694
|
-
|
|
2458
|
+
fontSize: "12px",
|
|
2459
|
+
fontWeight: 600,
|
|
2460
|
+
textAlign: "center",
|
|
2461
|
+
lineHeight: "1.2"
|
|
1695
2462
|
},
|
|
1696
|
-
children:
|
|
2463
|
+
children: getTypeLabel(type)
|
|
1697
2464
|
}
|
|
1698
2465
|
)
|
|
1699
2466
|
]
|
|
@@ -1703,6 +2470,77 @@ function ReportDialog({
|
|
|
1703
2470
|
}
|
|
1704
2471
|
),
|
|
1705
2472
|
step === 2 && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2473
|
+
/* @__PURE__ */ jsxs2(
|
|
2474
|
+
"button",
|
|
2475
|
+
{
|
|
2476
|
+
type: "button",
|
|
2477
|
+
onClick: () => setStep(1),
|
|
2478
|
+
style: {
|
|
2479
|
+
display: "inline-flex",
|
|
2480
|
+
alignItems: "center",
|
|
2481
|
+
gap: "6px",
|
|
2482
|
+
marginBottom: "10px",
|
|
2483
|
+
padding: "4px 10px 4px 8px",
|
|
2484
|
+
borderRadius: "999px",
|
|
2485
|
+
border: `1px solid ${isRating ? "rgba(245,158,11,0.4)" : t.inputBorder}`,
|
|
2486
|
+
background: isRating ? "rgba(245,158,11,0.1)" : t.inputBg,
|
|
2487
|
+
color: t.text,
|
|
2488
|
+
fontSize: "11px",
|
|
2489
|
+
fontWeight: 600,
|
|
2490
|
+
fontFamily: "inherit",
|
|
2491
|
+
cursor: "pointer"
|
|
2492
|
+
},
|
|
2493
|
+
children: [
|
|
2494
|
+
getTypeIcon(reportType, isRating ? "#f59e0b" : t.accent, 13),
|
|
2495
|
+
getTypeLabel(reportType),
|
|
2496
|
+
/* @__PURE__ */ jsx2("span", { style: { color: t.textMuted, fontWeight: 400 }, children: "\xB7 change" })
|
|
2497
|
+
]
|
|
2498
|
+
}
|
|
2499
|
+
),
|
|
2500
|
+
isRating && /* @__PURE__ */ jsx2(
|
|
2501
|
+
"div",
|
|
2502
|
+
{
|
|
2503
|
+
style: {
|
|
2504
|
+
display: "flex",
|
|
2505
|
+
gap: "6px",
|
|
2506
|
+
marginBottom: "12px"
|
|
2507
|
+
},
|
|
2508
|
+
onMouseLeave: () => setHoveredStar(0),
|
|
2509
|
+
children: [1, 2, 3, 4, 5].map((star) => {
|
|
2510
|
+
const filled = star <= (hoveredStar || rating);
|
|
2511
|
+
return /* @__PURE__ */ jsx2(
|
|
2512
|
+
"button",
|
|
2513
|
+
{
|
|
2514
|
+
type: "button",
|
|
2515
|
+
"aria-label": `${star} star${star > 1 ? "s" : ""}`,
|
|
2516
|
+
onClick: () => {
|
|
2517
|
+
setRating(star);
|
|
2518
|
+
if (validationError) setValidationError(null);
|
|
2519
|
+
},
|
|
2520
|
+
onMouseEnter: () => setHoveredStar(star),
|
|
2521
|
+
style: {
|
|
2522
|
+
border: "none",
|
|
2523
|
+
background: "transparent",
|
|
2524
|
+
padding: 0,
|
|
2525
|
+
cursor: "pointer",
|
|
2526
|
+
lineHeight: 0
|
|
2527
|
+
},
|
|
2528
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "30", height: "30", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
|
|
2529
|
+
"path",
|
|
2530
|
+
{
|
|
2531
|
+
d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
|
|
2532
|
+
fill: filled ? "#f59e0b" : "transparent",
|
|
2533
|
+
stroke: filled ? "#f59e0b" : t.inputBorder,
|
|
2534
|
+
strokeWidth: "1.5",
|
|
2535
|
+
strokeLinejoin: "round"
|
|
2536
|
+
}
|
|
2537
|
+
) })
|
|
2538
|
+
},
|
|
2539
|
+
star
|
|
2540
|
+
);
|
|
2541
|
+
})
|
|
2542
|
+
}
|
|
2543
|
+
),
|
|
1706
2544
|
/* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
|
|
1707
2545
|
/* @__PURE__ */ jsx2(
|
|
1708
2546
|
"textarea",
|
|
@@ -2101,7 +2939,7 @@ function ReportDialog({
|
|
|
2101
2939
|
children: voiceError
|
|
2102
2940
|
}
|
|
2103
2941
|
),
|
|
2104
|
-
/* @__PURE__ */ jsxs2("div", { style: { marginTop: "10px" }, children: [
|
|
2942
|
+
!isRating && /* @__PURE__ */ jsxs2("div", { style: { marginTop: "10px" }, children: [
|
|
2105
2943
|
/* @__PURE__ */ jsxs2(
|
|
2106
2944
|
"span",
|
|
2107
2945
|
{
|
|
@@ -2141,7 +2979,7 @@ function ReportDialog({
|
|
|
2141
2979
|
{
|
|
2142
2980
|
src,
|
|
2143
2981
|
alt: `Screenshot ${i + 1}`,
|
|
2144
|
-
onClick: () =>
|
|
2982
|
+
onClick: () => openPreview(i),
|
|
2145
2983
|
style: {
|
|
2146
2984
|
width: "100%",
|
|
2147
2985
|
height: "100%",
|
|
@@ -2545,37 +3383,54 @@ function ReportDialog({
|
|
|
2545
3383
|
{
|
|
2546
3384
|
type: "button",
|
|
2547
3385
|
onClick: handleSubmit,
|
|
2548
|
-
disabled:
|
|
3386
|
+
disabled: submitDisabled,
|
|
2549
3387
|
style: {
|
|
2550
3388
|
marginTop: "12px",
|
|
2551
3389
|
width: "100%",
|
|
2552
3390
|
padding: "10px",
|
|
2553
3391
|
borderRadius: "8px",
|
|
2554
3392
|
border: "none",
|
|
2555
|
-
backgroundColor:
|
|
2556
|
-
color:
|
|
3393
|
+
backgroundColor: submitDisabled ? t.bgSecondary : t.accent,
|
|
3394
|
+
color: submitDisabled ? t.textMuted : t.accentText,
|
|
2557
3395
|
fontSize: "14px",
|
|
2558
3396
|
fontWeight: 600,
|
|
2559
|
-
cursor:
|
|
3397
|
+
cursor: submitDisabled ? "not-allowed" : "pointer",
|
|
2560
3398
|
fontFamily: "inherit",
|
|
2561
3399
|
transition: "background-color 0.15s ease"
|
|
2562
3400
|
},
|
|
2563
|
-
children: isSubmitting ? "Sending..." : "Send Report"
|
|
3401
|
+
children: isSubmitting ? "Sending..." : isRating ? "Send Rating" : "Send Report"
|
|
2564
3402
|
}
|
|
2565
3403
|
)
|
|
2566
3404
|
] })
|
|
2567
3405
|
] })
|
|
2568
3406
|
}
|
|
2569
3407
|
),
|
|
2570
|
-
/* @__PURE__ */
|
|
3408
|
+
/* @__PURE__ */ jsxs2(
|
|
2571
3409
|
"div",
|
|
2572
3410
|
{
|
|
2573
3411
|
style: {
|
|
2574
3412
|
padding: "8px 16px 10px",
|
|
2575
3413
|
borderTop: `1px solid ${t.border}`,
|
|
2576
|
-
|
|
3414
|
+
display: "flex",
|
|
3415
|
+
alignItems: "center",
|
|
3416
|
+
justifyContent: "space-between",
|
|
3417
|
+
gap: "12px"
|
|
2577
3418
|
},
|
|
2578
|
-
children:
|
|
3419
|
+
children: [
|
|
3420
|
+
/* @__PURE__ */ jsx2(ReporterChip, { reporter, t }),
|
|
3421
|
+
/* @__PURE__ */ jsx2(
|
|
3422
|
+
"span",
|
|
3423
|
+
{
|
|
3424
|
+
style: {
|
|
3425
|
+
fontSize: "11px",
|
|
3426
|
+
color: t.textMuted,
|
|
3427
|
+
whiteSpace: "nowrap",
|
|
3428
|
+
flexShrink: 0
|
|
3429
|
+
},
|
|
3430
|
+
children: "Powered by Glitchgrab"
|
|
3431
|
+
}
|
|
3432
|
+
)
|
|
3433
|
+
]
|
|
2579
3434
|
}
|
|
2580
3435
|
)
|
|
2581
3436
|
]
|
|
@@ -2590,7 +3445,7 @@ function ReportDialog({
|
|
|
2590
3445
|
{
|
|
2591
3446
|
ref: fileInputRef,
|
|
2592
3447
|
type: "file",
|
|
2593
|
-
accept:
|
|
3448
|
+
accept: ATTACHMENT_ACCEPT,
|
|
2594
3449
|
multiple: true,
|
|
2595
3450
|
onChange: handleFileUpload,
|
|
2596
3451
|
style: { display: "none" }
|
|
@@ -2614,7 +3469,7 @@ function ReportDialog({
|
|
|
2614
3469
|
},
|
|
2615
3470
|
onClick: (e) => {
|
|
2616
3471
|
e.stopPropagation();
|
|
2617
|
-
|
|
3472
|
+
closePreview();
|
|
2618
3473
|
},
|
|
2619
3474
|
onMouseDown: (e) => e.stopPropagation(),
|
|
2620
3475
|
children: [
|
|
@@ -2622,22 +3477,59 @@ function ReportDialog({
|
|
|
2622
3477
|
"div",
|
|
2623
3478
|
{
|
|
2624
3479
|
ref: previewImgRef,
|
|
2625
|
-
style: {
|
|
3480
|
+
style: {
|
|
3481
|
+
position: "relative",
|
|
3482
|
+
display: "flex",
|
|
3483
|
+
maxWidth: "100%",
|
|
3484
|
+
maxHeight: "calc(100vh - 80px)"
|
|
3485
|
+
},
|
|
2626
3486
|
onClick: (e) => e.stopPropagation(),
|
|
2627
3487
|
children: [
|
|
2628
3488
|
/* @__PURE__ */ jsx2(
|
|
2629
|
-
"
|
|
3489
|
+
"div",
|
|
2630
3490
|
{
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
3491
|
+
ref: previewScrollRef,
|
|
3492
|
+
onPointerDown: handlePanStart,
|
|
3493
|
+
onPointerMove: handlePanMove,
|
|
3494
|
+
onPointerUp: handlePanEnd,
|
|
3495
|
+
onPointerCancel: handlePanEnd,
|
|
3496
|
+
onClick: togglePreviewZoom,
|
|
3497
|
+
style: previewZoomed ? {
|
|
3498
|
+
// Fill the overlay so there is room to pan around a
|
|
3499
|
+
// full-resolution screenshot.
|
|
3500
|
+
width: "calc(100vw - 32px)",
|
|
3501
|
+
height: "calc(100vh - 80px)",
|
|
3502
|
+
overflow: "auto",
|
|
2636
3503
|
borderRadius: "8px",
|
|
2637
|
-
|
|
2638
|
-
cursor: "
|
|
2639
|
-
|
|
2640
|
-
|
|
3504
|
+
touchAction: "none",
|
|
3505
|
+
cursor: "grab"
|
|
3506
|
+
} : { maxWidth: "100%", lineHeight: 0 },
|
|
3507
|
+
children: /* @__PURE__ */ jsx2(
|
|
3508
|
+
"img",
|
|
3509
|
+
{
|
|
3510
|
+
ref: previewImgElRef,
|
|
3511
|
+
src: screenshots[previewIndex],
|
|
3512
|
+
alt: "Screenshot preview",
|
|
3513
|
+
onLoad: (e) => setPreviewNatural({
|
|
3514
|
+
w: e.currentTarget.naturalWidth,
|
|
3515
|
+
h: e.currentTarget.naturalHeight
|
|
3516
|
+
}),
|
|
3517
|
+
style: previewZoomed ? {
|
|
3518
|
+
// Natural size — one image pixel per CSS pixel.
|
|
3519
|
+
maxWidth: "none",
|
|
3520
|
+
maxHeight: "none",
|
|
3521
|
+
display: "block",
|
|
3522
|
+
cursor: "zoom-out"
|
|
3523
|
+
} : {
|
|
3524
|
+
maxWidth: "100%",
|
|
3525
|
+
maxHeight: "calc(100vh - 80px)",
|
|
3526
|
+
borderRadius: "8px",
|
|
3527
|
+
objectFit: "contain",
|
|
3528
|
+
cursor: canZoomPreview ? "zoom-in" : "default",
|
|
3529
|
+
display: "block"
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
)
|
|
2641
3533
|
}
|
|
2642
3534
|
),
|
|
2643
3535
|
/* @__PURE__ */ jsxs2(
|
|
@@ -2701,7 +3593,11 @@ function ReportDialog({
|
|
|
2701
3593
|
color: t.textMuted,
|
|
2702
3594
|
fontSize: "12px"
|
|
2703
3595
|
},
|
|
2704
|
-
children:
|
|
3596
|
+
children: [
|
|
3597
|
+
screenshots.length > 1 ? `${previewIndex + 1} / ${screenshots.length}` : null,
|
|
3598
|
+
canZoomPreview ? previewZoomed ? "Drag to pan \xB7 click image to fit" : "Click image to zoom to full size" : null,
|
|
3599
|
+
previewZoomed ? "Esc to fit" : "Click outside to close"
|
|
3600
|
+
].filter(Boolean).join(" \xB7 ")
|
|
2705
3601
|
}
|
|
2706
3602
|
)
|
|
2707
3603
|
}
|
|
@@ -2731,6 +3627,8 @@ function ReportDialog({
|
|
|
2731
3627
|
}
|
|
2732
3628
|
function getTypeLabel(type) {
|
|
2733
3629
|
switch (type) {
|
|
3630
|
+
case "RATING":
|
|
3631
|
+
return "Rating";
|
|
2734
3632
|
case "BUG":
|
|
2735
3633
|
return "Bug Report";
|
|
2736
3634
|
case "FEATURE_REQUEST":
|
|
@@ -2747,25 +3645,8 @@ function getTypeLabel(type) {
|
|
|
2747
3645
|
return "Other";
|
|
2748
3646
|
}
|
|
2749
3647
|
}
|
|
2750
|
-
function getTypeSubtitle(type) {
|
|
2751
|
-
switch (type) {
|
|
2752
|
-
case "BUG":
|
|
2753
|
-
return "Something isn't working";
|
|
2754
|
-
case "FEATURE_REQUEST":
|
|
2755
|
-
return "Suggest an improvement";
|
|
2756
|
-
case "UI_IMPROVEMENT":
|
|
2757
|
-
return "Something looks off";
|
|
2758
|
-
case "PERFORMANCE":
|
|
2759
|
-
return "Something is slow";
|
|
2760
|
-
case "SECURITY":
|
|
2761
|
-
return "Report a security concern";
|
|
2762
|
-
case "QUESTION":
|
|
2763
|
-
return "Ask a question";
|
|
2764
|
-
case "OTHER":
|
|
2765
|
-
return "General feedback";
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
3648
|
function getPlaceholder(type, idx = 0, hasVoice = false) {
|
|
3649
|
+
if (type === "RATING") return "What made it good \u2014 or what let you down? (optional)";
|
|
2769
3650
|
if (hasVoice && idx === 1) return "Hold Space to speak \u2014 we'll transcribe it";
|
|
2770
3651
|
switch (type) {
|
|
2771
3652
|
case "BUG":
|
|
@@ -2793,6 +3674,16 @@ function getTypeIcon(type, color, size = 24) {
|
|
|
2793
3674
|
style: { flexShrink: 0 }
|
|
2794
3675
|
};
|
|
2795
3676
|
switch (type) {
|
|
3677
|
+
case "RATING":
|
|
3678
|
+
return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2(
|
|
3679
|
+
"path",
|
|
3680
|
+
{
|
|
3681
|
+
d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
|
|
3682
|
+
stroke: color,
|
|
3683
|
+
strokeWidth: "1.5",
|
|
3684
|
+
strokeLinejoin: "round"
|
|
3685
|
+
}
|
|
3686
|
+
) });
|
|
2796
3687
|
case "BUG":
|
|
2797
3688
|
return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2(
|
|
2798
3689
|
"path",
|
|
@@ -2885,6 +3776,112 @@ function getTypeIcon(type, color, size = 24) {
|
|
|
2885
3776
|
}
|
|
2886
3777
|
}
|
|
2887
3778
|
|
|
3779
|
+
// src/rejection.ts
|
|
3780
|
+
var DETAILS_MAX_LENGTH = 1e3;
|
|
3781
|
+
var MESSAGE_MAX_LENGTH = 200;
|
|
3782
|
+
function isUnactionableRejection(description) {
|
|
3783
|
+
if (description.stack) return false;
|
|
3784
|
+
const message = description.message.trim();
|
|
3785
|
+
return message === "" || message === "[object Object]";
|
|
3786
|
+
}
|
|
3787
|
+
function readString(value) {
|
|
3788
|
+
if (typeof value !== "string") return void 0;
|
|
3789
|
+
const trimmed = value.trim();
|
|
3790
|
+
if (trimmed === "" || trimmed === "[object Object]") return void 0;
|
|
3791
|
+
return trimmed.slice(0, MESSAGE_MAX_LENGTH);
|
|
3792
|
+
}
|
|
3793
|
+
function readScalar(value) {
|
|
3794
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
3795
|
+
return readString(value);
|
|
3796
|
+
}
|
|
3797
|
+
function readStack(reason) {
|
|
3798
|
+
try {
|
|
3799
|
+
const stack = reason == null ? void 0 : reason.stack;
|
|
3800
|
+
return typeof stack === "string" && stack.trim() !== "" ? stack : void 0;
|
|
3801
|
+
} catch (e) {
|
|
3802
|
+
return void 0;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
function serialize(reason) {
|
|
3806
|
+
try {
|
|
3807
|
+
const seen2 = /* @__PURE__ */ new WeakSet();
|
|
3808
|
+
const json = JSON.stringify(reason, (_key, value) => {
|
|
3809
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
3810
|
+
if (typeof value === "object" && value !== null) {
|
|
3811
|
+
if (seen2.has(value)) return "[Circular]";
|
|
3812
|
+
seen2.add(value);
|
|
3813
|
+
}
|
|
3814
|
+
return value;
|
|
3815
|
+
});
|
|
3816
|
+
if (!json || json === "{}" || json === "null") return void 0;
|
|
3817
|
+
return json.slice(0, DETAILS_MAX_LENGTH);
|
|
3818
|
+
} catch (e) {
|
|
3819
|
+
return void 0;
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
var SHAPE_MAX_KEYS = 5;
|
|
3823
|
+
function describeShape(reason) {
|
|
3824
|
+
try {
|
|
3825
|
+
const keys = Object.keys(reason);
|
|
3826
|
+
if (keys.length === 0) return "";
|
|
3827
|
+
const listed = keys.slice(0, SHAPE_MAX_KEYS).join(", ");
|
|
3828
|
+
const suffix = keys.length > SHAPE_MAX_KEYS ? ", \u2026" : "";
|
|
3829
|
+
return `Rejected object: ${listed}${suffix}`.slice(0, MESSAGE_MAX_LENGTH);
|
|
3830
|
+
} catch (e) {
|
|
3831
|
+
return "";
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
function describeRejection(reason) {
|
|
3835
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3836
|
+
try {
|
|
3837
|
+
if (reason instanceof Error) {
|
|
3838
|
+
return {
|
|
3839
|
+
message: reason.message || reason.name || "",
|
|
3840
|
+
stack: reason.stack
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
const stack = readStack(reason);
|
|
3844
|
+
if (typeof reason === "string") {
|
|
3845
|
+
return { message: (_a = readString(reason)) != null ? _a : "", stack };
|
|
3846
|
+
}
|
|
3847
|
+
if (typeof reason === "number" || typeof reason === "boolean") {
|
|
3848
|
+
return { message: String(reason), stack };
|
|
3849
|
+
}
|
|
3850
|
+
if (reason === null || reason === void 0) {
|
|
3851
|
+
return { message: "" };
|
|
3852
|
+
}
|
|
3853
|
+
const source = reason;
|
|
3854
|
+
const nestedError = source.error;
|
|
3855
|
+
const message = (_h = (_g = (_f = (_e = (_d = (_c = (_b = readString(source.message)) != null ? _b : readString(nestedError)) != null ? _c : readString(nestedError == null ? void 0 : nestedError.message)) != null ? _d : readScalar(source.code)) != null ? _e : readString(source.name)) != null ? _f : readScalar(source.status)) != null ? _g : readString(source.statusText)) != null ? _h : "";
|
|
3856
|
+
const details = serialize(reason);
|
|
3857
|
+
return {
|
|
3858
|
+
message: message || describeShape(reason),
|
|
3859
|
+
stack,
|
|
3860
|
+
details
|
|
3861
|
+
};
|
|
3862
|
+
} catch (e) {
|
|
3863
|
+
return { message: "" };
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
// src/shortcut.ts
|
|
3868
|
+
var GLITCHGRAB_SHORTCUT2 = "Ctrl+Shift+G";
|
|
3869
|
+
var GLITCHGRAB_SHORTCUT_MAC2 = "\u2318\u21E7G";
|
|
3870
|
+
function isMac2() {
|
|
3871
|
+
try {
|
|
3872
|
+
if (typeof navigator === "undefined") return false;
|
|
3873
|
+
return /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent);
|
|
3874
|
+
} catch (e) {
|
|
3875
|
+
return false;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
function getShortcutLabel2() {
|
|
3879
|
+
return isMac2() ? GLITCHGRAB_SHORTCUT_MAC2 : GLITCHGRAB_SHORTCUT2;
|
|
3880
|
+
}
|
|
3881
|
+
function matchesShortcut(e) {
|
|
3882
|
+
return (e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "g";
|
|
3883
|
+
}
|
|
3884
|
+
|
|
2888
3885
|
// src/provider.tsx
|
|
2889
3886
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2890
3887
|
var DEFAULT_BASE_URL2 = "https://glitchgrab.dev";
|
|
@@ -2908,14 +3905,28 @@ function GlitchgrabProviderInner({
|
|
|
2908
3905
|
fallback,
|
|
2909
3906
|
types,
|
|
2910
3907
|
showSeverity,
|
|
2911
|
-
ignoreErrors
|
|
3908
|
+
ignoreErrors,
|
|
3909
|
+
release: release2,
|
|
3910
|
+
context: appContext2,
|
|
3911
|
+
responseBodyOrigins: responseBodyOrigins2
|
|
2912
3912
|
}) {
|
|
2913
3913
|
const visitedPagesRef = useRef3([]);
|
|
3914
|
+
setRelease(release2);
|
|
3915
|
+
if (appContext2) setContexts(appContext2);
|
|
3916
|
+
registerCaptureConfig({
|
|
3917
|
+
token,
|
|
3918
|
+
baseUrl,
|
|
3919
|
+
session,
|
|
3920
|
+
ignoreErrors,
|
|
3921
|
+
getVisitedPages: () => visitedPagesRef.current,
|
|
3922
|
+
onError,
|
|
3923
|
+
onReportSent
|
|
3924
|
+
});
|
|
2914
3925
|
useEffect3(() => {
|
|
2915
3926
|
if (enableBreadcrumbs) {
|
|
2916
|
-
initBreadcrumbs(maxBreadcrumbs2);
|
|
3927
|
+
initBreadcrumbs(maxBreadcrumbs2, { responseBodyOrigins: responseBodyOrigins2 });
|
|
2917
3928
|
}
|
|
2918
|
-
}, [enableBreadcrumbs, maxBreadcrumbs2]);
|
|
3929
|
+
}, [enableBreadcrumbs, maxBreadcrumbs2, responseBodyOrigins2 == null ? void 0 : responseBodyOrigins2.join(",")]);
|
|
2919
3930
|
useEffect3(() => {
|
|
2920
3931
|
try {
|
|
2921
3932
|
if (typeof window === "undefined") return;
|
|
@@ -2963,10 +3974,7 @@ function GlitchgrabProviderInner({
|
|
|
2963
3974
|
];
|
|
2964
3975
|
const matchesIgnore = (message) => {
|
|
2965
3976
|
if (KNOWN_NOISE_RE.some((pattern) => pattern.test(message))) return true;
|
|
2966
|
-
|
|
2967
|
-
return ignoreErrors.some(
|
|
2968
|
-
(pattern) => pattern instanceof RegExp ? pattern.test(message) : message.includes(pattern)
|
|
2969
|
-
);
|
|
3977
|
+
return matchesIgnorePatterns(message, ignoreErrors);
|
|
2970
3978
|
};
|
|
2971
3979
|
const handleError = (event) => {
|
|
2972
3980
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -2984,6 +3992,7 @@ function GlitchgrabProviderInner({
|
|
|
2984
3992
|
if (event.filename && EXTENSION_ORIGIN_RE.test(event.filename) || ((_a = event.error) == null ? void 0 : _a.stack) && EXTENSION_ORIGIN_RE.test(event.error.stack)) {
|
|
2985
3993
|
return;
|
|
2986
3994
|
}
|
|
3995
|
+
incrementErrorCount();
|
|
2987
3996
|
const context = captureContext(visitedPagesRef.current);
|
|
2988
3997
|
const sig = computeSignature({
|
|
2989
3998
|
errorMessage: event.message,
|
|
@@ -3004,6 +4013,7 @@ function GlitchgrabProviderInner({
|
|
|
3004
4013
|
metadata: {
|
|
3005
4014
|
timestamp: context.timestamp,
|
|
3006
4015
|
visitedPages: JSON.stringify(context.visitedPages),
|
|
4016
|
+
...contextMetadata(context),
|
|
3007
4017
|
filename: (_e = event.filename) != null ? _e : "",
|
|
3008
4018
|
lineno: String((_f = event.lineno) != null ? _f : ""),
|
|
3009
4019
|
colno: String((_g = event.colno) != null ? _g : ""),
|
|
@@ -3023,17 +4033,20 @@ function GlitchgrabProviderInner({
|
|
|
3023
4033
|
const handleRejection = (event) => {
|
|
3024
4034
|
var _a;
|
|
3025
4035
|
try {
|
|
3026
|
-
const context = captureContext(visitedPagesRef.current);
|
|
3027
4036
|
const reason = event.reason;
|
|
3028
|
-
const
|
|
4037
|
+
const description = describeRejection(reason);
|
|
4038
|
+
const errMsg = description.message;
|
|
4039
|
+
const errStack = description.stack;
|
|
3029
4040
|
if (errMsg === "Script error." || errMsg === "Script error") {
|
|
3030
4041
|
return;
|
|
3031
4042
|
}
|
|
3032
4043
|
if (matchesIgnore(errMsg)) {
|
|
3033
4044
|
return;
|
|
3034
4045
|
}
|
|
3035
|
-
const errStack = reason instanceof Error ? reason.stack : void 0;
|
|
3036
4046
|
if (errStack && EXTENSION_ORIGIN_RE.test(errStack)) return;
|
|
4047
|
+
if (isUnactionableRejection(description)) return;
|
|
4048
|
+
incrementErrorCount();
|
|
4049
|
+
const context = captureContext(visitedPagesRef.current);
|
|
3037
4050
|
const sig = computeSignature({
|
|
3038
4051
|
errorMessage: errMsg,
|
|
3039
4052
|
pageUrl: context.url,
|
|
@@ -3053,7 +4066,9 @@ function GlitchgrabProviderInner({
|
|
|
3053
4066
|
metadata: {
|
|
3054
4067
|
timestamp: context.timestamp,
|
|
3055
4068
|
visitedPages: JSON.stringify(context.visitedPages),
|
|
4069
|
+
...contextMetadata(context),
|
|
3056
4070
|
type: "unhandledrejection",
|
|
4071
|
+
...description.details ? { rejectionReason: description.details } : {},
|
|
3057
4072
|
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
3058
4073
|
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
3059
4074
|
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
@@ -3093,6 +4108,7 @@ function GlitchgrabProviderInner({
|
|
|
3093
4108
|
metadata: {
|
|
3094
4109
|
timestamp: context.timestamp,
|
|
3095
4110
|
visitedPages: JSON.stringify(context.visitedPages),
|
|
4111
|
+
...contextMetadata(context),
|
|
3096
4112
|
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
3097
4113
|
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
3098
4114
|
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
@@ -3113,6 +4129,53 @@ function GlitchgrabProviderInner({
|
|
|
3113
4129
|
(description, metadata) => report("BUG", description, metadata),
|
|
3114
4130
|
[report]
|
|
3115
4131
|
);
|
|
4132
|
+
const feedback = useCallback(
|
|
4133
|
+
async (rating, message, metadata) => {
|
|
4134
|
+
try {
|
|
4135
|
+
const context = captureContext(visitedPagesRef.current);
|
|
4136
|
+
return await sendFeedback(
|
|
4137
|
+
{
|
|
4138
|
+
token,
|
|
4139
|
+
rating,
|
|
4140
|
+
message,
|
|
4141
|
+
pageUrl: context.url,
|
|
4142
|
+
userAgent: context.userAgent,
|
|
4143
|
+
metadata: {
|
|
4144
|
+
timestamp: context.timestamp,
|
|
4145
|
+
...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
|
|
4146
|
+
...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
|
|
4147
|
+
...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
|
|
4148
|
+
...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {},
|
|
4149
|
+
...metadata
|
|
4150
|
+
}
|
|
4151
|
+
},
|
|
4152
|
+
baseUrl
|
|
4153
|
+
);
|
|
4154
|
+
} catch (e) {
|
|
4155
|
+
return null;
|
|
4156
|
+
}
|
|
4157
|
+
},
|
|
4158
|
+
[token, baseUrl, session]
|
|
4159
|
+
);
|
|
4160
|
+
const openFeedbackDialog = useCallback(() => {
|
|
4161
|
+
if (typeof window !== "undefined") {
|
|
4162
|
+
window.dispatchEvent(
|
|
4163
|
+
new CustomEvent("glitchgrab:open-report", { detail: { type: "RATING" } })
|
|
4164
|
+
);
|
|
4165
|
+
}
|
|
4166
|
+
}, []);
|
|
4167
|
+
const captureError2 = useCallback(
|
|
4168
|
+
(error, options) => {
|
|
4169
|
+
captureError(error, options);
|
|
4170
|
+
},
|
|
4171
|
+
[]
|
|
4172
|
+
);
|
|
4173
|
+
const setContext2 = useCallback((key, value) => {
|
|
4174
|
+
setContext(key, value);
|
|
4175
|
+
}, []);
|
|
4176
|
+
const setContexts2 = useCallback((values) => {
|
|
4177
|
+
setContexts(values);
|
|
4178
|
+
}, []);
|
|
3116
4179
|
const addBreadcrumb2 = useCallback(
|
|
3117
4180
|
(message, data) => {
|
|
3118
4181
|
addBreadcrumb("custom", message, data);
|
|
@@ -3164,9 +4227,19 @@ function GlitchgrabProviderInner({
|
|
|
3164
4227
|
} catch (e) {
|
|
3165
4228
|
}
|
|
3166
4229
|
}, [openReportDialog]);
|
|
3167
|
-
const
|
|
4230
|
+
const reporter = useMemo(() => {
|
|
4231
|
+
var _a;
|
|
4232
|
+
if (!session) return null;
|
|
4233
|
+
const avatar = (_a = session.avatarUrl) != null ? _a : session.image;
|
|
4234
|
+
return {
|
|
4235
|
+
name: session.name,
|
|
4236
|
+
email: session.email,
|
|
4237
|
+
avatarUrl: typeof avatar === "string" ? avatar : null
|
|
4238
|
+
};
|
|
4239
|
+
}, [session]);
|
|
4240
|
+
const [shortcutLabel, setShortcutLabel] = useState3(GLITCHGRAB_SHORTCUT2);
|
|
3168
4241
|
useEffect3(() => {
|
|
3169
|
-
setShortcutLabel(
|
|
4242
|
+
setShortcutLabel(getShortcutLabel2());
|
|
3170
4243
|
}, []);
|
|
3171
4244
|
return /* @__PURE__ */ jsxs3(
|
|
3172
4245
|
GlitchgrabContext.Provider,
|
|
@@ -3176,6 +4249,11 @@ function GlitchgrabProviderInner({
|
|
|
3176
4249
|
baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
|
|
3177
4250
|
reportBug,
|
|
3178
4251
|
report,
|
|
4252
|
+
captureError: captureError2,
|
|
4253
|
+
setContext: setContext2,
|
|
4254
|
+
setContexts: setContexts2,
|
|
4255
|
+
sendFeedback: feedback,
|
|
4256
|
+
openFeedbackDialog,
|
|
3179
4257
|
addBreadcrumb: addBreadcrumb2,
|
|
3180
4258
|
openReportDialog,
|
|
3181
4259
|
enhanceText: enhance,
|
|
@@ -3194,7 +4272,7 @@ function GlitchgrabProviderInner({
|
|
|
3194
4272
|
children
|
|
3195
4273
|
}
|
|
3196
4274
|
),
|
|
3197
|
-
/* @__PURE__ */ jsx3(ReportDialog, { report, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity })
|
|
4275
|
+
/* @__PURE__ */ jsx3(ReportDialog, { report, sendFeedback: feedback, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity, reporter })
|
|
3198
4276
|
]
|
|
3199
4277
|
}
|
|
3200
4278
|
);
|
|
@@ -3304,6 +4382,91 @@ function ReportButton({
|
|
|
3304
4382
|
);
|
|
3305
4383
|
}
|
|
3306
4384
|
|
|
4385
|
+
// src/feedback-button.tsx
|
|
4386
|
+
import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
4387
|
+
function useGlitchgrabSafe2() {
|
|
4388
|
+
try {
|
|
4389
|
+
return useGlitchgrab();
|
|
4390
|
+
} catch (e) {
|
|
4391
|
+
return null;
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
function FeedbackButton({
|
|
4395
|
+
position = "bottom-left",
|
|
4396
|
+
label = "Feedback",
|
|
4397
|
+
className,
|
|
4398
|
+
children
|
|
4399
|
+
}) {
|
|
4400
|
+
var _a, _b;
|
|
4401
|
+
const glitchgrab = useGlitchgrabSafe2();
|
|
4402
|
+
if (!glitchgrab) return null;
|
|
4403
|
+
const handleClick = () => glitchgrab.openFeedbackDialog();
|
|
4404
|
+
if (children) {
|
|
4405
|
+
return /* @__PURE__ */ jsx5(Fragment4, { children: children({ onClick: handleClick }) });
|
|
4406
|
+
}
|
|
4407
|
+
const isTop = (_a = position == null ? void 0 : position.startsWith("top")) != null ? _a : false;
|
|
4408
|
+
const isLeft = (_b = position == null ? void 0 : position.endsWith("left")) != null ? _b : false;
|
|
4409
|
+
const positionStyles = {
|
|
4410
|
+
...isLeft ? { left: "16px" } : { right: "16px" },
|
|
4411
|
+
...isTop ? { top: "16px" } : { bottom: "16px" }
|
|
4412
|
+
};
|
|
4413
|
+
return /* @__PURE__ */ jsxs5(
|
|
4414
|
+
"button",
|
|
4415
|
+
{
|
|
4416
|
+
type: "button",
|
|
4417
|
+
onClick: handleClick,
|
|
4418
|
+
className,
|
|
4419
|
+
style: {
|
|
4420
|
+
position: "fixed",
|
|
4421
|
+
...positionStyles,
|
|
4422
|
+
zIndex: 99999,
|
|
4423
|
+
padding: "10px 18px",
|
|
4424
|
+
borderRadius: "24px",
|
|
4425
|
+
border: "none",
|
|
4426
|
+
backgroundColor: "#18181b",
|
|
4427
|
+
color: "#fafafa",
|
|
4428
|
+
fontSize: "14px",
|
|
4429
|
+
fontWeight: 500,
|
|
4430
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
4431
|
+
cursor: "pointer",
|
|
4432
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.1)",
|
|
4433
|
+
transition: "transform 0.15s ease, box-shadow 0.15s ease",
|
|
4434
|
+
display: "flex",
|
|
4435
|
+
alignItems: "center",
|
|
4436
|
+
gap: "6px"
|
|
4437
|
+
},
|
|
4438
|
+
onMouseEnter: (e) => {
|
|
4439
|
+
e.target.style.transform = "scale(1.05)";
|
|
4440
|
+
},
|
|
4441
|
+
onMouseLeave: (e) => {
|
|
4442
|
+
e.target.style.transform = "scale(1)";
|
|
4443
|
+
},
|
|
4444
|
+
"aria-label": label,
|
|
4445
|
+
children: [
|
|
4446
|
+
/* @__PURE__ */ jsx5(
|
|
4447
|
+
"svg",
|
|
4448
|
+
{
|
|
4449
|
+
width: "16",
|
|
4450
|
+
height: "16",
|
|
4451
|
+
viewBox: "0 0 24 24",
|
|
4452
|
+
fill: "none",
|
|
4453
|
+
style: { flexShrink: 0 },
|
|
4454
|
+
"aria-hidden": "true",
|
|
4455
|
+
children: /* @__PURE__ */ jsx5(
|
|
4456
|
+
"path",
|
|
4457
|
+
{
|
|
4458
|
+
d: "M12 2.5l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3 1.1-6.5L2.6 9.3l6.5-.9L12 2.5z",
|
|
4459
|
+
fill: "currentColor"
|
|
4460
|
+
}
|
|
4461
|
+
)
|
|
4462
|
+
}
|
|
4463
|
+
),
|
|
4464
|
+
label
|
|
4465
|
+
]
|
|
4466
|
+
}
|
|
4467
|
+
);
|
|
4468
|
+
}
|
|
4469
|
+
|
|
3307
4470
|
// src/use-reports.ts
|
|
3308
4471
|
import { useState as useState5, useEffect as useEffect4, useCallback as useCallback2 } from "react";
|
|
3309
4472
|
var BASE_URL = "https://glitchgrab.dev";
|
|
@@ -3409,29 +4572,99 @@ function useGlitchgrabActions({
|
|
|
3409
4572
|
error
|
|
3410
4573
|
};
|
|
3411
4574
|
}
|
|
4575
|
+
|
|
4576
|
+
// src/use-feedback.ts
|
|
4577
|
+
import { useState as useState6, useEffect as useEffect5, useCallback as useCallback3 } from "react";
|
|
4578
|
+
var DEFAULT_BASE_URL3 = "https://glitchgrab.dev";
|
|
4579
|
+
function buildUrl({
|
|
4580
|
+
approvedOnly,
|
|
4581
|
+
userId,
|
|
4582
|
+
minRating,
|
|
4583
|
+
limit = 50,
|
|
4584
|
+
baseUrl
|
|
4585
|
+
}) {
|
|
4586
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
4587
|
+
if (approvedOnly) params.set("approved", "true");
|
|
4588
|
+
if (userId) params.set("reporterPrimaryKey", userId);
|
|
4589
|
+
if (minRating) params.set("minRating", String(minRating));
|
|
4590
|
+
return `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL3}/api/v1/sdk/feedback?${params.toString()}`;
|
|
4591
|
+
}
|
|
4592
|
+
async function fetchGlitchgrabFeedback(query) {
|
|
4593
|
+
var _a, _b;
|
|
4594
|
+
const res = await fetch(buildUrl(query), {
|
|
4595
|
+
headers: { Authorization: `Bearer ${query.token}` }
|
|
4596
|
+
});
|
|
4597
|
+
const data = await res.json();
|
|
4598
|
+
if (!data.success) throw new Error((_a = data.error) != null ? _a : "Failed to fetch feedback");
|
|
4599
|
+
return (_b = data.data) != null ? _b : [];
|
|
4600
|
+
}
|
|
4601
|
+
function useGlitchgrabFeedback(query) {
|
|
4602
|
+
const { token, approvedOnly, userId, minRating, limit, baseUrl } = query;
|
|
4603
|
+
const [feedback, setFeedback] = useState6([]);
|
|
4604
|
+
const [isLoading, setIsLoading] = useState6(true);
|
|
4605
|
+
const [isFetching, setIsFetching] = useState6(false);
|
|
4606
|
+
const [error, setError] = useState6(null);
|
|
4607
|
+
const load = useCallback3(async () => {
|
|
4608
|
+
if (!token) {
|
|
4609
|
+
setIsLoading(false);
|
|
4610
|
+
return;
|
|
4611
|
+
}
|
|
4612
|
+
setIsFetching(true);
|
|
4613
|
+
setError(null);
|
|
4614
|
+
try {
|
|
4615
|
+
const data = await fetchGlitchgrabFeedback({
|
|
4616
|
+
token,
|
|
4617
|
+
approvedOnly,
|
|
4618
|
+
userId,
|
|
4619
|
+
minRating,
|
|
4620
|
+
limit,
|
|
4621
|
+
baseUrl
|
|
4622
|
+
});
|
|
4623
|
+
setFeedback(data);
|
|
4624
|
+
} catch (e) {
|
|
4625
|
+
setError(e instanceof Error ? e.message : "Network error");
|
|
4626
|
+
} finally {
|
|
4627
|
+
setIsLoading(false);
|
|
4628
|
+
setIsFetching(false);
|
|
4629
|
+
}
|
|
4630
|
+
}, [token, approvedOnly, userId, minRating, limit, baseUrl]);
|
|
4631
|
+
useEffect5(() => {
|
|
4632
|
+
load();
|
|
4633
|
+
}, [load]);
|
|
4634
|
+
return { feedback, isLoading, isFetching, error, refetch: load };
|
|
4635
|
+
}
|
|
3412
4636
|
export {
|
|
3413
|
-
|
|
3414
|
-
|
|
4637
|
+
FeedbackButton,
|
|
4638
|
+
GLITCHGRAB_SHORTCUT2 as GLITCHGRAB_SHORTCUT,
|
|
4639
|
+
GLITCHGRAB_SHORTCUT_MAC2 as GLITCHGRAB_SHORTCUT_MAC,
|
|
3415
4640
|
GlitchgrabErrorBoundary,
|
|
3416
4641
|
GlitchgrabProvider,
|
|
3417
4642
|
ReportButton,
|
|
3418
4643
|
addBreadcrumb,
|
|
3419
4644
|
captureContext,
|
|
3420
4645
|
captureDeviceInfo,
|
|
4646
|
+
captureError,
|
|
4647
|
+
clearAppContext,
|
|
3421
4648
|
clearBreadcrumbs,
|
|
3422
4649
|
clearDedupCache,
|
|
3423
4650
|
computeSignature,
|
|
3424
4651
|
enhanceText,
|
|
4652
|
+
fetchGlitchgrabFeedback,
|
|
3425
4653
|
fetchGlitchgrabReports,
|
|
4654
|
+
getAppContext,
|
|
3426
4655
|
getBreadcrumbs,
|
|
3427
|
-
getShortcutLabel,
|
|
4656
|
+
getShortcutLabel2 as getShortcutLabel,
|
|
3428
4657
|
initBreadcrumbs,
|
|
3429
4658
|
matchesShortcut,
|
|
3430
4659
|
sanitizeUrl,
|
|
4660
|
+
sendFeedback,
|
|
3431
4661
|
sendReport,
|
|
4662
|
+
setContext,
|
|
4663
|
+
setContexts,
|
|
3432
4664
|
shouldSkipDuplicate,
|
|
3433
4665
|
useGlitchgrab,
|
|
3434
4666
|
useGlitchgrabActions,
|
|
4667
|
+
useGlitchgrabFeedback,
|
|
3435
4668
|
useGlitchgrabReports
|
|
3436
4669
|
};
|
|
3437
4670
|
//# sourceMappingURL=index.mjs.map
|