@mushi-mushi/web 1.2.0 → 1.3.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/CONTRIBUTING.md +11 -0
- package/dist/index.cjs +566 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +566 -84
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -45,10 +45,20 @@ var en = {
|
|
|
45
45
|
heading: "Tell us more",
|
|
46
46
|
descriptionPlaceholder: "Describe what happened\u2026",
|
|
47
47
|
screenshotButton: "Attach Screenshot",
|
|
48
|
-
screenshotAttached: "Screenshot attached",
|
|
48
|
+
screenshotAttached: "Screenshot attached \u2713",
|
|
49
|
+
screenshotCapturing: "Taking screenshot\u2026",
|
|
50
|
+
screenshotFailed: "Couldn't capture \u2014 describe it instead",
|
|
49
51
|
elementButton: "Select Element",
|
|
50
|
-
elementSelected: "Element selected",
|
|
51
|
-
|
|
52
|
+
elementSelected: "Element selected \u2713",
|
|
53
|
+
elementCapturing: "Click anything on the page\u2026",
|
|
54
|
+
elementSelectorHint: "Click any element \xB7 Esc to cancel",
|
|
55
|
+
optional: "(optional)",
|
|
56
|
+
tooShort: "A bit more detail helps us fix it faster",
|
|
57
|
+
examplePrompts: [
|
|
58
|
+
"The save button does nothing",
|
|
59
|
+
"Page froze for 10 seconds",
|
|
60
|
+
"Layout looks broken here"
|
|
61
|
+
]
|
|
52
62
|
}
|
|
53
63
|
};
|
|
54
64
|
|
|
@@ -95,10 +105,20 @@ var ja = {
|
|
|
95
105
|
heading: "\u8A73\u7D30\u3092\u6559\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
96
106
|
descriptionPlaceholder: "\u4F55\u304C\u8D77\u304D\u305F\u304B\u8AAC\u660E\u3057\u3066\u304F\u3060\u3055\u3044\u2026",
|
|
97
107
|
screenshotButton: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8",
|
|
98
|
-
screenshotAttached: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8\u6E08\u307F",
|
|
108
|
+
screenshotAttached: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8\u6E08\u307F \u2713",
|
|
109
|
+
screenshotCapturing: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u64AE\u5F71\u4E2D\u2026",
|
|
110
|
+
screenshotFailed: "\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F \u2014 \u6587\u5B57\u3067\u6559\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
99
111
|
elementButton: "\u8981\u7D20\u3092\u9078\u629E",
|
|
100
|
-
elementSelected: "\u8981\u7D20\u9078\u629E\u6E08\u307F",
|
|
101
|
-
|
|
112
|
+
elementSelected: "\u8981\u7D20\u9078\u629E\u6E08\u307F \u2713",
|
|
113
|
+
elementCapturing: "\u30DA\u30FC\u30B8\u4E0A\u306E\u8981\u7D20\u3092\u30AF\u30EA\u30C3\u30AF\u2026",
|
|
114
|
+
elementSelectorHint: "\u8981\u7D20\u3092\u30AF\u30EA\u30C3\u30AF \xB7 Esc \u3067\u30AD\u30E3\u30F3\u30BB\u30EB",
|
|
115
|
+
optional: "\uFF08\u4EFB\u610F\uFF09",
|
|
116
|
+
tooShort: "\u3082\u3046\u5C11\u3057\u8A73\u3057\u304F\u6559\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
117
|
+
examplePrompts: [
|
|
118
|
+
"\u4FDD\u5B58\u30DC\u30BF\u30F3\u304C\u53CD\u5FDC\u3057\u306A\u3044",
|
|
119
|
+
"\u30DA\u30FC\u30B8\u304C10\u79D2\u56FA\u307E\u3063\u305F",
|
|
120
|
+
"\u30EC\u30A4\u30A2\u30A6\u30C8\u304C\u5D29\u308C\u3066\u3044\u308B"
|
|
121
|
+
]
|
|
102
122
|
}
|
|
103
123
|
};
|
|
104
124
|
|
|
@@ -145,10 +165,20 @@ var th = {
|
|
|
145
165
|
heading: "\u0E1A\u0E2D\u0E01\u0E40\u0E23\u0E32\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21",
|
|
146
166
|
descriptionPlaceholder: "\u0E2D\u0E18\u0E34\u0E1A\u0E32\u0E22\u0E2A\u0E34\u0E48\u0E07\u0E17\u0E35\u0E48\u0E40\u0E01\u0E34\u0E14\u0E02\u0E36\u0E49\u0E19\u2026",
|
|
147
167
|
screenshotButton: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15",
|
|
148
|
-
screenshotAttached: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15\u0E41\u0E25\u0E49\u0E27",
|
|
168
|
+
screenshotAttached: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15\u0E41\u0E25\u0E49\u0E27 \u2713",
|
|
169
|
+
screenshotCapturing: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E16\u0E48\u0E32\u0E22\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15\u2026",
|
|
170
|
+
screenshotFailed: "\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E16\u0E48\u0E32\u0E22\u0E20\u0E32\u0E1E\u0E44\u0E14\u0E49 \u2014 \u0E42\u0E1B\u0E23\u0E14\u0E2D\u0E18\u0E34\u0E1A\u0E32\u0E22\u0E41\u0E17\u0E19",
|
|
149
171
|
elementButton: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A",
|
|
150
|
-
elementSelected: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E41\u0E25\u0E49\u0E27",
|
|
151
|
-
|
|
172
|
+
elementSelected: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E41\u0E25\u0E49\u0E27 \u2713",
|
|
173
|
+
elementCapturing: "\u0E04\u0E25\u0E34\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E1A\u0E19\u0E2B\u0E19\u0E49\u0E32\u2026",
|
|
174
|
+
elementSelectorHint: "\u0E04\u0E25\u0E34\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E43\u0E14\u0E01\u0E47\u0E44\u0E14\u0E49 \xB7 Esc \u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E22\u0E01\u0E40\u0E25\u0E34\u0E01",
|
|
175
|
+
optional: "(\u0E44\u0E21\u0E48\u0E1A\u0E31\u0E07\u0E04\u0E31\u0E1A)",
|
|
176
|
+
tooShort: "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E43\u0E2B\u0E49\u0E23\u0E32\u0E22\u0E25\u0E30\u0E40\u0E2D\u0E35\u0E22\u0E14\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21",
|
|
177
|
+
examplePrompts: [
|
|
178
|
+
"\u0E1B\u0E38\u0E48\u0E21\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E44\u0E21\u0E48\u0E17\u0E33\u0E07\u0E32\u0E19",
|
|
179
|
+
"\u0E2B\u0E19\u0E49\u0E32\u0E04\u0E49\u0E32\u0E07\u0E19\u0E32\u0E19 10 \u0E27\u0E34\u0E19\u0E32\u0E17\u0E35",
|
|
180
|
+
"\u0E40\u0E25\u0E22\u0E4C\u0E40\u0E2D\u0E32\u0E15\u0E4C\u0E1E\u0E31\u0E07"
|
|
181
|
+
]
|
|
152
182
|
}
|
|
153
183
|
};
|
|
154
184
|
|
|
@@ -195,18 +225,29 @@ var es = {
|
|
|
195
225
|
heading: "Cu\xE9ntanos m\xE1s",
|
|
196
226
|
descriptionPlaceholder: "Describe lo que pas\xF3\u2026",
|
|
197
227
|
screenshotButton: "Adjuntar captura",
|
|
198
|
-
screenshotAttached: "Captura adjunta",
|
|
228
|
+
screenshotAttached: "Captura adjunta \u2713",
|
|
229
|
+
screenshotCapturing: "Tomando captura\u2026",
|
|
230
|
+
screenshotFailed: "No se pudo capturar \u2014 descr\xEDbelo en su lugar",
|
|
199
231
|
elementButton: "Seleccionar elemento",
|
|
200
|
-
elementSelected: "Elemento seleccionado",
|
|
201
|
-
|
|
232
|
+
elementSelected: "Elemento seleccionado \u2713",
|
|
233
|
+
elementCapturing: "Haz clic en cualquier elemento\u2026",
|
|
234
|
+
elementSelectorHint: "Clic en cualquier elemento \xB7 Esc para cancelar",
|
|
235
|
+
optional: "(opcional)",
|
|
236
|
+
tooShort: "Un poco m\xE1s de detalle nos ayuda a resolverlo",
|
|
237
|
+
examplePrompts: [
|
|
238
|
+
"El bot\xF3n guardar no responde",
|
|
239
|
+
"La p\xE1gina se congel\xF3 10 segundos",
|
|
240
|
+
"El dise\xF1o se ve roto aqu\xED"
|
|
241
|
+
]
|
|
202
242
|
}
|
|
203
243
|
};
|
|
204
244
|
|
|
205
245
|
// src/i18n/index.ts
|
|
206
246
|
var locales = { en, ja, th, es };
|
|
207
247
|
function getLocale(code) {
|
|
208
|
-
|
|
209
|
-
|
|
248
|
+
const resolved = code && code !== "auto" ? code : typeof navigator !== "undefined" ? navigator.language ?? navigator.languages?.[0] : void 0;
|
|
249
|
+
if (!resolved) return en;
|
|
250
|
+
const base = resolved.split("-")[0].toLowerCase();
|
|
210
251
|
return locales[base] ?? en;
|
|
211
252
|
}
|
|
212
253
|
function getAvailableLocales() {
|
|
@@ -239,6 +280,7 @@ function getWidgetStyles(theme) {
|
|
|
239
280
|
-webkit-font-smoothing: antialiased;
|
|
240
281
|
-moz-osx-font-smoothing: grayscale;
|
|
241
282
|
font-feature-settings: 'ss01', 'cv11'; /* nicer system-ui glyphs where supported */
|
|
283
|
+
--mushi-ok: ${isDark ? "#4ade80" : "#16a34a"};
|
|
242
284
|
}
|
|
243
285
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
244
286
|
button { font-family: inherit; }
|
|
@@ -691,6 +733,51 @@ function getWidgetStyles(theme) {
|
|
|
691
733
|
box-shadow: inset 2px 0 0 ${vermillion};
|
|
692
734
|
}
|
|
693
735
|
|
|
736
|
+
/* Example starter chips \u2014 reduce first-report activation energy */
|
|
737
|
+
.mushi-example-chips {
|
|
738
|
+
display: flex;
|
|
739
|
+
flex-wrap: wrap;
|
|
740
|
+
gap: 6px;
|
|
741
|
+
margin-bottom: 10px;
|
|
742
|
+
}
|
|
743
|
+
.mushi-example-chip {
|
|
744
|
+
padding: 4px 10px;
|
|
745
|
+
border: 1px solid ${rule};
|
|
746
|
+
border-radius: 12px;
|
|
747
|
+
background: transparent;
|
|
748
|
+
color: ${inkMuted};
|
|
749
|
+
font-family: ${fontBody};
|
|
750
|
+
font-size: 11px;
|
|
751
|
+
cursor: pointer;
|
|
752
|
+
transition: color 150ms ${easeStamp}, border-color 150ms ${easeStamp}, background 150ms ${easeStamp};
|
|
753
|
+
white-space: nowrap;
|
|
754
|
+
}
|
|
755
|
+
.mushi-example-chip:hover {
|
|
756
|
+
color: ${ink};
|
|
757
|
+
border-color: ${inkMuted};
|
|
758
|
+
background: ${isDark ? "rgba(242,235,221,0.06)" : "rgba(14,13,11,0.04)"};
|
|
759
|
+
}
|
|
760
|
+
.mushi-example-chip:focus-visible {
|
|
761
|
+
outline: 2px solid ${vermillion};
|
|
762
|
+
outline-offset: 2px;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/* Textarea wrapper to position char counter */
|
|
766
|
+
.mushi-textarea-wrap {
|
|
767
|
+
position: relative;
|
|
768
|
+
}
|
|
769
|
+
.mushi-char-counter {
|
|
770
|
+
position: absolute;
|
|
771
|
+
bottom: 4px;
|
|
772
|
+
right: 0;
|
|
773
|
+
font-family: ${fontMono};
|
|
774
|
+
font-size: 10px;
|
|
775
|
+
letter-spacing: 0.04em;
|
|
776
|
+
color: ${inkFaint};
|
|
777
|
+
pointer-events: none;
|
|
778
|
+
transition: color 200ms ${easeStamp};
|
|
779
|
+
}
|
|
780
|
+
|
|
694
781
|
.mushi-textarea {
|
|
695
782
|
width: 100%;
|
|
696
783
|
min-height: 96px;
|
|
@@ -753,10 +840,34 @@ function getWidgetStyles(theme) {
|
|
|
753
840
|
border-color: ${vermillion};
|
|
754
841
|
background: ${vermillionWash};
|
|
755
842
|
}
|
|
843
|
+
.mushi-attach-btn.loading {
|
|
844
|
+
opacity: 0.7;
|
|
845
|
+
cursor: wait;
|
|
846
|
+
}
|
|
847
|
+
.mushi-attach-btn.error {
|
|
848
|
+
color: ${vermillion};
|
|
849
|
+
border-color: ${vermillionWash};
|
|
850
|
+
}
|
|
756
851
|
.mushi-attach-btn:focus-visible {
|
|
757
852
|
outline: 2px solid ${vermillion};
|
|
758
853
|
outline-offset: 2px;
|
|
759
854
|
}
|
|
855
|
+
@keyframes mushi-spin {
|
|
856
|
+
to { transform: rotate(360deg); }
|
|
857
|
+
}
|
|
858
|
+
@keyframes mushi-fade-in {
|
|
859
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
860
|
+
to { opacity: 1; transform: translateY(0); }
|
|
861
|
+
}
|
|
862
|
+
.mushi-spinner {
|
|
863
|
+
display: inline-block;
|
|
864
|
+
width: 10px;
|
|
865
|
+
height: 10px;
|
|
866
|
+
border: 1.5px solid currentColor;
|
|
867
|
+
border-top-color: transparent;
|
|
868
|
+
border-radius: 50%;
|
|
869
|
+
animation: mushi-spin 0.7s linear infinite;
|
|
870
|
+
}
|
|
760
871
|
|
|
761
872
|
.mushi-footer {
|
|
762
873
|
padding: 14px 22px 16px;
|
|
@@ -1230,7 +1341,8 @@ var MushiWidget = class {
|
|
|
1230
1341
|
draggable: config.draggable ?? false,
|
|
1231
1342
|
brandFooter: config.brandFooter ?? true,
|
|
1232
1343
|
outdatedBanner: config.outdatedBanner ?? "auto",
|
|
1233
|
-
betaMode: config.betaMode ?? {}
|
|
1344
|
+
betaMode: config.betaMode ?? {},
|
|
1345
|
+
minDescriptionLength: config.minDescriptionLength ?? 20
|
|
1234
1346
|
};
|
|
1235
1347
|
this.callbacks = callbacks;
|
|
1236
1348
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
@@ -1249,12 +1361,22 @@ var MushiWidget = class {
|
|
|
1249
1361
|
selectedCategory = null;
|
|
1250
1362
|
selectedIntent = null;
|
|
1251
1363
|
screenshotAttached = false;
|
|
1364
|
+
screenshotCapturing = false;
|
|
1365
|
+
screenshotError = false;
|
|
1252
1366
|
allowScreenshotRemove = true;
|
|
1253
1367
|
elementSelected = false;
|
|
1368
|
+
elementCapturing = false;
|
|
1254
1369
|
submitting = false;
|
|
1370
|
+
/** Hint element injected outside the shadow DOM during element selection. */
|
|
1371
|
+
selectorHint = null;
|
|
1255
1372
|
triggerVisible = true;
|
|
1256
1373
|
triggerShrunk = false;
|
|
1257
1374
|
triggerHiddenByScroll = false;
|
|
1375
|
+
/** Milliseconds since mount — used for the 30s first-time nudge gate. */
|
|
1376
|
+
mountedAt = null;
|
|
1377
|
+
nudgeShown = false;
|
|
1378
|
+
nudgeEl = null;
|
|
1379
|
+
nudgeTimer = null;
|
|
1258
1380
|
sdkFreshness = null;
|
|
1259
1381
|
reporterReports = [];
|
|
1260
1382
|
reporterComments = [];
|
|
@@ -1281,6 +1403,7 @@ var MushiWidget = class {
|
|
|
1281
1403
|
this.syncAttachedLaunchers();
|
|
1282
1404
|
this.syncSmartHide();
|
|
1283
1405
|
this.render();
|
|
1406
|
+
this.mountedAt = Date.now();
|
|
1284
1407
|
}
|
|
1285
1408
|
getIsMounted() {
|
|
1286
1409
|
return this.host.isConnected;
|
|
@@ -1307,7 +1430,8 @@ var MushiWidget = class {
|
|
|
1307
1430
|
...config.draggable !== void 0 ? { draggable: config.draggable } : {},
|
|
1308
1431
|
...config.brandFooter !== void 0 ? { brandFooter: config.brandFooter } : {},
|
|
1309
1432
|
...config.outdatedBanner !== void 0 ? { outdatedBanner: config.outdatedBanner } : {},
|
|
1310
|
-
...config.betaMode !== void 0 ? { betaMode: config.betaMode } : {}
|
|
1433
|
+
...config.betaMode !== void 0 ? { betaMode: config.betaMode } : {},
|
|
1434
|
+
...config.minDescriptionLength !== void 0 ? { minDescriptionLength: config.minDescriptionLength } : {}
|
|
1311
1435
|
};
|
|
1312
1436
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
1313
1437
|
this.syncAttachedLaunchers();
|
|
@@ -1318,9 +1442,13 @@ var MushiWidget = class {
|
|
|
1318
1442
|
if (this.isOpen) return;
|
|
1319
1443
|
this.isOpen = true;
|
|
1320
1444
|
this.screenshotAttached = false;
|
|
1445
|
+
this.screenshotCapturing = false;
|
|
1446
|
+
this.screenshotError = false;
|
|
1321
1447
|
this.elementSelected = false;
|
|
1448
|
+
this.elementCapturing = false;
|
|
1322
1449
|
this.submitting = false;
|
|
1323
1450
|
this.submittedAt = null;
|
|
1451
|
+
this.removeSelectorHint();
|
|
1324
1452
|
if (options?.category) {
|
|
1325
1453
|
this.selectedCategory = options.category;
|
|
1326
1454
|
this.selectedIntent = null;
|
|
@@ -1376,8 +1504,114 @@ var MushiWidget = class {
|
|
|
1376
1504
|
}
|
|
1377
1505
|
setElementSelected(selected) {
|
|
1378
1506
|
this.elementSelected = selected;
|
|
1507
|
+
this.elementCapturing = false;
|
|
1508
|
+
this.removeSelectorHint();
|
|
1509
|
+
if (this.isOpen) this.render();
|
|
1510
|
+
}
|
|
1511
|
+
setScreenshotCapturing(capturing) {
|
|
1512
|
+
this.screenshotCapturing = capturing;
|
|
1513
|
+
this.screenshotError = false;
|
|
1514
|
+
if (this.isOpen) this.render();
|
|
1515
|
+
}
|
|
1516
|
+
setScreenshotError(failed) {
|
|
1517
|
+
this.screenshotError = failed;
|
|
1518
|
+
this.screenshotCapturing = false;
|
|
1519
|
+
if (this.isOpen) this.render();
|
|
1520
|
+
}
|
|
1521
|
+
setElementCapturing(capturing) {
|
|
1522
|
+
this.elementCapturing = capturing;
|
|
1523
|
+
if (capturing) {
|
|
1524
|
+
this.showSelectorHint();
|
|
1525
|
+
} else {
|
|
1526
|
+
this.removeSelectorHint();
|
|
1527
|
+
}
|
|
1379
1528
|
if (this.isOpen) this.render();
|
|
1380
1529
|
}
|
|
1530
|
+
/** Hide the widget panel (but keep the host element) during element selection
|
|
1531
|
+
* so the user can click any element on the page without the panel
|
|
1532
|
+
* intercepting the event. */
|
|
1533
|
+
hidePanel() {
|
|
1534
|
+
const panel = this.shadow.querySelector(".mushi-panel");
|
|
1535
|
+
if (panel) panel.style.display = "none";
|
|
1536
|
+
}
|
|
1537
|
+
showPanel() {
|
|
1538
|
+
const panel = this.shadow.querySelector(".mushi-panel");
|
|
1539
|
+
if (panel) panel.style.display = "";
|
|
1540
|
+
}
|
|
1541
|
+
showSelectorHint() {
|
|
1542
|
+
this.removeSelectorHint();
|
|
1543
|
+
const hint = document.createElement("div");
|
|
1544
|
+
hint.id = "mushi-selector-hint";
|
|
1545
|
+
hint.setAttribute("role", "status");
|
|
1546
|
+
hint.setAttribute("aria-live", "polite");
|
|
1547
|
+
hint.style.cssText = `
|
|
1548
|
+
position: fixed;
|
|
1549
|
+
bottom: 24px;
|
|
1550
|
+
left: 50%;
|
|
1551
|
+
transform: translateX(-50%);
|
|
1552
|
+
z-index: 2147483646;
|
|
1553
|
+
background: rgba(17,17,17,0.92);
|
|
1554
|
+
color: #fff;
|
|
1555
|
+
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
1556
|
+
font-size: 12px;
|
|
1557
|
+
letter-spacing: 0.04em;
|
|
1558
|
+
padding: 8px 16px;
|
|
1559
|
+
border-radius: 20px;
|
|
1560
|
+
pointer-events: none;
|
|
1561
|
+
white-space: nowrap;
|
|
1562
|
+
backdrop-filter: blur(4px);
|
|
1563
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.35);
|
|
1564
|
+
`;
|
|
1565
|
+
hint.textContent = this.locale.step3.elementSelectorHint;
|
|
1566
|
+
document.body.appendChild(hint);
|
|
1567
|
+
this.selectorHint = hint;
|
|
1568
|
+
}
|
|
1569
|
+
removeSelectorHint() {
|
|
1570
|
+
this.selectorHint?.remove();
|
|
1571
|
+
this.selectorHint = null;
|
|
1572
|
+
document.getElementById("mushi-selector-hint")?.remove();
|
|
1573
|
+
}
|
|
1574
|
+
showNudge() {
|
|
1575
|
+
if (this.nudgeShown || this.nudgeEl) return;
|
|
1576
|
+
this.nudgeShown = true;
|
|
1577
|
+
const trigger = this.shadow.querySelector(".mushi-trigger");
|
|
1578
|
+
const rect = trigger?.getBoundingClientRect();
|
|
1579
|
+
const nudge = document.createElement("div");
|
|
1580
|
+
nudge.id = "mushi-nudge-bubble";
|
|
1581
|
+
nudge.setAttribute("role", "tooltip");
|
|
1582
|
+
const isRight = this.config.position.includes("right");
|
|
1583
|
+
nudge.style.cssText = `
|
|
1584
|
+
position: fixed;
|
|
1585
|
+
z-index: 2147483645;
|
|
1586
|
+
${rect ? `bottom: ${window.innerHeight - rect.top + 8}px; ${isRight ? `right: ${window.innerWidth - rect.right}px;` : `left: ${rect.left}px;`}` : "bottom: 80px; right: 24px;"}
|
|
1587
|
+
background: rgba(17,17,17,0.92);
|
|
1588
|
+
color: #fff;
|
|
1589
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
1590
|
+
font-size: 12px;
|
|
1591
|
+
line-height: 1.4;
|
|
1592
|
+
padding: 8px 12px;
|
|
1593
|
+
border-radius: 8px;
|
|
1594
|
+
max-width: 200px;
|
|
1595
|
+
pointer-events: none;
|
|
1596
|
+
backdrop-filter: blur(4px);
|
|
1597
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.35);
|
|
1598
|
+
animation: mushi-fade-in 0.15s ease forwards;
|
|
1599
|
+
`;
|
|
1600
|
+
nudge.textContent = this.locale.step3.tooShort.startsWith("A bit") ? "Found a bug? One sentence is enough \u{1F41B}" : "\u30D0\u30B0\u3092\u898B\u3064\u3051\u305F\uFF1F\u4E00\u884C\u3067\u5927\u4E08\u592B\u3067\u3059 \u{1F41B}";
|
|
1601
|
+
document.body.appendChild(nudge);
|
|
1602
|
+
this.nudgeEl = nudge;
|
|
1603
|
+
if (this.nudgeTimer !== null) clearTimeout(this.nudgeTimer);
|
|
1604
|
+
this.nudgeTimer = setTimeout(() => this.removeNudge(), 5e3);
|
|
1605
|
+
}
|
|
1606
|
+
removeNudge() {
|
|
1607
|
+
if (this.nudgeTimer !== null) {
|
|
1608
|
+
clearTimeout(this.nudgeTimer);
|
|
1609
|
+
this.nudgeTimer = null;
|
|
1610
|
+
}
|
|
1611
|
+
this.nudgeEl?.remove();
|
|
1612
|
+
this.nudgeEl = null;
|
|
1613
|
+
document.getElementById("mushi-nudge-bubble")?.remove();
|
|
1614
|
+
}
|
|
1381
1615
|
setSdkFreshness(info) {
|
|
1382
1616
|
this.sdkFreshness = info;
|
|
1383
1617
|
if (this.isOpen) this.render();
|
|
@@ -1403,6 +1637,8 @@ var MushiWidget = class {
|
|
|
1403
1637
|
this.smartHideCleanup = null;
|
|
1404
1638
|
this.attachedLaunchers.forEach((cleanup) => cleanup());
|
|
1405
1639
|
this.attachedLaunchers = [];
|
|
1640
|
+
this.removeSelectorHint();
|
|
1641
|
+
this.removeNudge();
|
|
1406
1642
|
this.host.remove();
|
|
1407
1643
|
}
|
|
1408
1644
|
syncAttachedLaunchers() {
|
|
@@ -1498,9 +1734,22 @@ var MushiWidget = class {
|
|
|
1498
1734
|
trigger.style.zIndex = String(this.config.zIndex);
|
|
1499
1735
|
this.applyInsetVars(trigger);
|
|
1500
1736
|
trigger.addEventListener("click", () => {
|
|
1737
|
+
this.removeNudge();
|
|
1501
1738
|
if (this.isOpen) this.close();
|
|
1502
1739
|
else this.open();
|
|
1503
1740
|
});
|
|
1741
|
+
trigger.addEventListener("mouseenter", () => {
|
|
1742
|
+
const onPageMs = this.mountedAt ? Date.now() - this.mountedAt : 0;
|
|
1743
|
+
if (!this.nudgeShown && !this.isOpen && onPageMs >= 3e4) {
|
|
1744
|
+
this.showNudge();
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
trigger.addEventListener("mouseleave", () => {
|
|
1748
|
+
if (this.nudgeEl) {
|
|
1749
|
+
if (this.nudgeTimer !== null) clearTimeout(this.nudgeTimer);
|
|
1750
|
+
this.nudgeTimer = setTimeout(() => this.removeNudge(), 2e3);
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1504
1753
|
this.shadow.appendChild(trigger);
|
|
1505
1754
|
}
|
|
1506
1755
|
const panel = document.createElement("div");
|
|
@@ -1752,25 +2001,62 @@ var MushiWidget = class {
|
|
|
1752
2001
|
${this.renderStepIndicator(STEP_NUMBER.intent)}
|
|
1753
2002
|
`;
|
|
1754
2003
|
}
|
|
2004
|
+
effectiveMinLength() {
|
|
2005
|
+
const base = this.config.minDescriptionLength ?? 20;
|
|
2006
|
+
const lang = this.config.locale === "auto" ? typeof navigator !== "undefined" ? navigator.language ?? "" : "" : this.config.locale ?? "";
|
|
2007
|
+
const isCjk = /^(ja|zh|ko)/i.test(lang);
|
|
2008
|
+
return isCjk ? Math.max(4, Math.floor(base / 2)) : base;
|
|
2009
|
+
}
|
|
1755
2010
|
renderDetailsStep() {
|
|
1756
2011
|
const t = this.locale;
|
|
2012
|
+
const minLen = this.effectiveMinLength();
|
|
2013
|
+
const screenshotLabel = this.screenshotCapturing ? t.step3.screenshotCapturing : this.screenshotError ? t.step3.screenshotFailed : this.screenshotAttached ? t.step3.screenshotAttached : t.step3.screenshotButton;
|
|
2014
|
+
const screenshotClass = [
|
|
2015
|
+
"mushi-attach-btn",
|
|
2016
|
+
this.screenshotAttached ? "active" : "",
|
|
2017
|
+
this.screenshotError ? "error" : "",
|
|
2018
|
+
this.screenshotCapturing ? "loading" : ""
|
|
2019
|
+
].filter(Boolean).join(" ");
|
|
2020
|
+
const elementLabel = this.elementCapturing ? t.step3.elementCapturing : this.elementSelected ? t.step3.elementSelected : t.step3.elementButton;
|
|
2021
|
+
const elementClass = [
|
|
2022
|
+
"mushi-attach-btn",
|
|
2023
|
+
this.elementSelected ? "active" : "",
|
|
2024
|
+
this.elementCapturing ? "loading" : ""
|
|
2025
|
+
].filter(Boolean).join(" ");
|
|
2026
|
+
const exampleChips = t.step3.examplePrompts.map((p) => `<button type="button" class="mushi-example-chip" data-example="${escapeHtml(p)}">${escapeHtml(p)}</button>`).join("");
|
|
1757
2027
|
return `
|
|
1758
2028
|
${this.renderHeader({ title: t.step3.heading, showBack: true, step: STEP_NUMBER.details })}
|
|
1759
2029
|
<div class="mushi-body">
|
|
1760
|
-
<
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
2030
|
+
<div class="mushi-example-chips" aria-label="Example prompts">${exampleChips}</div>
|
|
2031
|
+
<div class="mushi-textarea-wrap">
|
|
2032
|
+
<textarea
|
|
2033
|
+
class="mushi-textarea"
|
|
2034
|
+
placeholder="${t.step3.descriptionPlaceholder}"
|
|
2035
|
+
rows="4"
|
|
2036
|
+
aria-label="${t.step3.heading}"
|
|
2037
|
+
autofocus
|
|
2038
|
+
></textarea>
|
|
2039
|
+
<div class="mushi-char-counter" data-role="char-counter" aria-hidden="true">
|
|
2040
|
+
<span data-role="char-current">0</span>/<span data-role="char-min">${minLen}</span>
|
|
2041
|
+
</div>
|
|
2042
|
+
</div>
|
|
1767
2043
|
<div class="mushi-attachments">
|
|
1768
|
-
<button type="button" class="
|
|
1769
|
-
|
|
2044
|
+
<button type="button" class="${screenshotClass}"
|
|
2045
|
+
data-action="screenshot"
|
|
2046
|
+
${this.screenshotCapturing ? "disabled" : ""}
|
|
2047
|
+
aria-label="${escapeHtml(screenshotLabel)}"
|
|
2048
|
+
>
|
|
2049
|
+
${this.screenshotCapturing ? '<span class="mushi-spinner" aria-hidden="true"></span>' : "\u{1F4F8}"}
|
|
2050
|
+
${escapeHtml(screenshotLabel)}
|
|
1770
2051
|
</button>
|
|
1771
|
-
${this.screenshotAttached && this.allowScreenshotRemove ? '<button type="button" class="mushi-attach-btn danger" data-action="remove-screenshot">\u2715 Remove
|
|
1772
|
-
<button type="button" class="
|
|
1773
|
-
|
|
2052
|
+
${this.screenshotAttached && this.allowScreenshotRemove ? '<button type="button" class="mushi-attach-btn danger" data-action="remove-screenshot" aria-label="Remove screenshot">\u2715 Remove</button>' : ""}
|
|
2053
|
+
<button type="button" class="${elementClass}"
|
|
2054
|
+
data-action="element"
|
|
2055
|
+
${this.elementCapturing ? "disabled" : ""}
|
|
2056
|
+
aria-label="${escapeHtml(elementLabel)}"
|
|
2057
|
+
>
|
|
2058
|
+
${this.elementCapturing ? '<span class="mushi-spinner" aria-hidden="true"></span>' : "\u{1F3AF}"}
|
|
2059
|
+
${escapeHtml(elementLabel)}
|
|
1774
2060
|
</button>
|
|
1775
2061
|
</div>
|
|
1776
2062
|
<div class="mushi-error" style="display:none" role="alert"></div>
|
|
@@ -1940,6 +2226,30 @@ var MushiWidget = class {
|
|
|
1940
2226
|
this.render();
|
|
1941
2227
|
});
|
|
1942
2228
|
});
|
|
2229
|
+
const textarea = panel.querySelector(".mushi-textarea");
|
|
2230
|
+
const charCurrentEl = panel.querySelector('[data-role="char-current"]');
|
|
2231
|
+
if (textarea && charCurrentEl) {
|
|
2232
|
+
const minLen = this.effectiveMinLength();
|
|
2233
|
+
const updateCounter = () => {
|
|
2234
|
+
const len = textarea.value.trim().length;
|
|
2235
|
+
charCurrentEl.textContent = String(len);
|
|
2236
|
+
const counterEl = panel.querySelector('[data-role="char-counter"]');
|
|
2237
|
+
if (counterEl) {
|
|
2238
|
+
counterEl.style.color = len >= minLen ? "var(--mushi-ok, #22c55e)" : "";
|
|
2239
|
+
}
|
|
2240
|
+
};
|
|
2241
|
+
textarea.addEventListener("input", updateCounter);
|
|
2242
|
+
}
|
|
2243
|
+
panel.querySelectorAll("[data-example]").forEach((chip) => {
|
|
2244
|
+
chip.addEventListener("click", () => {
|
|
2245
|
+
const example = chip.dataset.example ?? "";
|
|
2246
|
+
if (textarea) {
|
|
2247
|
+
textarea.value = example;
|
|
2248
|
+
textarea.focus();
|
|
2249
|
+
textarea.dispatchEvent(new Event("input"));
|
|
2250
|
+
}
|
|
2251
|
+
});
|
|
2252
|
+
});
|
|
1943
2253
|
panel.querySelector('[data-action="screenshot"]')?.addEventListener("click", () => {
|
|
1944
2254
|
this.callbacks.onScreenshotRequest();
|
|
1945
2255
|
});
|
|
@@ -1950,14 +2260,16 @@ var MushiWidget = class {
|
|
|
1950
2260
|
this.callbacks.onElementSelectorRequest?.();
|
|
1951
2261
|
});
|
|
1952
2262
|
const submitReport = () => {
|
|
1953
|
-
const
|
|
1954
|
-
const description =
|
|
2263
|
+
const textarea2 = panel.querySelector(".mushi-textarea");
|
|
2264
|
+
const description = textarea2?.value?.trim() ?? "";
|
|
1955
2265
|
const errorEl = panel.querySelector(".mushi-error");
|
|
1956
|
-
const
|
|
1957
|
-
if (description.length <
|
|
2266
|
+
const minLen = this.effectiveMinLength();
|
|
2267
|
+
if (description.length < minLen) {
|
|
1958
2268
|
if (errorEl) {
|
|
1959
|
-
|
|
2269
|
+
const msg = `${t.step3.tooShort} (${description.length}/${minLen})`;
|
|
2270
|
+
errorEl.textContent = msg;
|
|
1960
2271
|
errorEl.style.display = "block";
|
|
2272
|
+
textarea2?.focus();
|
|
1961
2273
|
}
|
|
1962
2274
|
return;
|
|
1963
2275
|
}
|
|
@@ -2054,8 +2366,81 @@ var MushiWidget = class {
|
|
|
2054
2366
|
this.render();
|
|
2055
2367
|
}
|
|
2056
2368
|
}
|
|
2369
|
+
/* ── Marketing / Playwright recorder (debug GIF capture) ─────────── */
|
|
2370
|
+
getRecorderStep() {
|
|
2371
|
+
return this.step;
|
|
2372
|
+
}
|
|
2373
|
+
getRecorderTrigger() {
|
|
2374
|
+
return this.shadow.querySelector(".mushi-trigger");
|
|
2375
|
+
}
|
|
2376
|
+
getRecorderCategoryButton(category) {
|
|
2377
|
+
return this.shadow.querySelector(`[data-category="${category}"]`);
|
|
2378
|
+
}
|
|
2379
|
+
getRecorderIntentButton(label) {
|
|
2380
|
+
return Array.from(this.shadow.querySelectorAll("[data-intent]")).find(
|
|
2381
|
+
(el) => el.dataset.intent === label
|
|
2382
|
+
) ?? null;
|
|
2383
|
+
}
|
|
2384
|
+
getRecorderSubmitButton() {
|
|
2385
|
+
return this.shadow.querySelector('[data-action="submit"]');
|
|
2386
|
+
}
|
|
2387
|
+
recorderClickTrigger() {
|
|
2388
|
+
if (this.isOpen) this.close();
|
|
2389
|
+
this.open();
|
|
2390
|
+
}
|
|
2391
|
+
recorderSelectCategory(category) {
|
|
2392
|
+
if (!this.isOpen) this.open();
|
|
2393
|
+
if (this.step !== "category") {
|
|
2394
|
+
this.selectedCategory = null;
|
|
2395
|
+
this.selectedIntent = null;
|
|
2396
|
+
this.step = "category";
|
|
2397
|
+
this.render();
|
|
2398
|
+
}
|
|
2399
|
+
this.selectedCategory = category;
|
|
2400
|
+
this.step = "intent";
|
|
2401
|
+
this.render();
|
|
2402
|
+
}
|
|
2403
|
+
recorderSelectIntent(label) {
|
|
2404
|
+
if (!this.isOpen || this.step !== "intent") return;
|
|
2405
|
+
this.selectedIntent = label;
|
|
2406
|
+
this.step = "details";
|
|
2407
|
+
this.render();
|
|
2408
|
+
}
|
|
2409
|
+
recorderFocusDescription() {
|
|
2410
|
+
const textarea = this.shadow.querySelector(".mushi-textarea");
|
|
2411
|
+
textarea?.focus();
|
|
2412
|
+
}
|
|
2413
|
+
recorderSubmit() {
|
|
2414
|
+
const submit = this.shadow.querySelector('[data-action="submit"]');
|
|
2415
|
+
submit?.click();
|
|
2416
|
+
}
|
|
2057
2417
|
};
|
|
2058
2418
|
|
|
2419
|
+
// src/marketing-recorder.ts
|
|
2420
|
+
function centerOf(el) {
|
|
2421
|
+
if (!el) return null;
|
|
2422
|
+
const r = el.getBoundingClientRect();
|
|
2423
|
+
if (r.width === 0 && r.height === 0) return null;
|
|
2424
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
2425
|
+
}
|
|
2426
|
+
function exposeMarketingRecorder(widget) {
|
|
2427
|
+
if (typeof globalThis === "undefined") return;
|
|
2428
|
+
const api = {
|
|
2429
|
+
ready: () => widget.getIsMounted(),
|
|
2430
|
+
getStep: () => widget.getRecorderStep(),
|
|
2431
|
+
getTriggerCenter: () => centerOf(widget.getRecorderTrigger()),
|
|
2432
|
+
getCategoryCenter: (category) => centerOf(widget.getRecorderCategoryButton(category)),
|
|
2433
|
+
getIntentCenter: (label) => centerOf(widget.getRecorderIntentButton(label)),
|
|
2434
|
+
getSubmitCenter: () => centerOf(widget.getRecorderSubmitButton()),
|
|
2435
|
+
clickTrigger: () => widget.recorderClickTrigger(),
|
|
2436
|
+
selectCategory: (category) => widget.recorderSelectCategory(category),
|
|
2437
|
+
selectIntent: (label) => widget.recorderSelectIntent(label),
|
|
2438
|
+
focusDescription: () => widget.recorderFocusDescription(),
|
|
2439
|
+
submit: () => widget.recorderSubmit()
|
|
2440
|
+
};
|
|
2441
|
+
globalThis.__mushiRecorder = api;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2059
2444
|
// src/rewards.ts
|
|
2060
2445
|
var MIN_FLUSH_INTERVAL = 3e4;
|
|
2061
2446
|
var DEFAULT_FLUSH_INTERVAL = 3e5;
|
|
@@ -2547,50 +2932,16 @@ function truncateUrl(url) {
|
|
|
2547
2932
|
function createScreenshotCapture(options = {}) {
|
|
2548
2933
|
let activeOptions = options;
|
|
2549
2934
|
async function take() {
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
const
|
|
2557
|
-
|
|
2558
|
-
canvas.width = width * dpr;
|
|
2559
|
-
canvas.height = height * dpr;
|
|
2560
|
-
ctx.scale(dpr, dpr);
|
|
2561
|
-
const safeDocument = buildPrivacySafeDocument(activeOptions.privacy);
|
|
2562
|
-
const svgData = `
|
|
2563
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
2564
|
-
<foreignObject width="100%" height="100%">
|
|
2565
|
-
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
2566
|
-
${new XMLSerializer().serializeToString(safeDocument)}
|
|
2567
|
-
</div>
|
|
2568
|
-
</foreignObject>
|
|
2569
|
-
</svg>
|
|
2570
|
-
`;
|
|
2571
|
-
const img = new Image();
|
|
2572
|
-
const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
|
2573
|
-
const url = URL.createObjectURL(blob);
|
|
2574
|
-
return new Promise((resolve) => {
|
|
2575
|
-
img.onload = () => {
|
|
2576
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
2577
|
-
URL.revokeObjectURL(url);
|
|
2578
|
-
try {
|
|
2579
|
-
const dataUrl = canvas.toDataURL("image/jpeg", 0.7);
|
|
2580
|
-
resolve(dataUrl);
|
|
2581
|
-
} catch {
|
|
2582
|
-
resolve(null);
|
|
2583
|
-
}
|
|
2584
|
-
};
|
|
2585
|
-
img.onerror = () => {
|
|
2586
|
-
URL.revokeObjectURL(url);
|
|
2587
|
-
resolve(null);
|
|
2588
|
-
};
|
|
2589
|
-
img.src = url;
|
|
2590
|
-
});
|
|
2591
|
-
} catch {
|
|
2592
|
-
return null;
|
|
2935
|
+
if (typeof document === "undefined") {
|
|
2936
|
+
return { ok: false, reason: "unsupported", message: "Not in a browser context" };
|
|
2937
|
+
}
|
|
2938
|
+
const svgResult = await trySvgCapture(activeOptions.privacy);
|
|
2939
|
+
if (svgResult.ok) return svgResult;
|
|
2940
|
+
if (svgResult.reason !== "unsupported") {
|
|
2941
|
+
const mediaResult = await tryDisplayMediaCapture();
|
|
2942
|
+
if (mediaResult.ok) return mediaResult;
|
|
2593
2943
|
}
|
|
2944
|
+
return svgResult;
|
|
2594
2945
|
}
|
|
2595
2946
|
return {
|
|
2596
2947
|
take,
|
|
@@ -2599,8 +2950,110 @@ function createScreenshotCapture(options = {}) {
|
|
|
2599
2950
|
}
|
|
2600
2951
|
};
|
|
2601
2952
|
}
|
|
2953
|
+
async function trySvgCapture(privacy) {
|
|
2954
|
+
try {
|
|
2955
|
+
const canvas = document.createElement("canvas");
|
|
2956
|
+
const ctx = canvas.getContext("2d");
|
|
2957
|
+
if (!ctx) return { ok: false, reason: "unsupported", message: "Canvas 2d context unavailable" };
|
|
2958
|
+
const width = window.innerWidth;
|
|
2959
|
+
const height = window.innerHeight;
|
|
2960
|
+
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
2961
|
+
canvas.width = width * dpr;
|
|
2962
|
+
canvas.height = height * dpr;
|
|
2963
|
+
ctx.scale(dpr, dpr);
|
|
2964
|
+
const safeDocument = buildPrivacySafeDocument(privacy);
|
|
2965
|
+
const svgData = `
|
|
2966
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
2967
|
+
<foreignObject width="100%" height="100%">
|
|
2968
|
+
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
2969
|
+
${new XMLSerializer().serializeToString(safeDocument)}
|
|
2970
|
+
</div>
|
|
2971
|
+
</foreignObject>
|
|
2972
|
+
</svg>
|
|
2973
|
+
`;
|
|
2974
|
+
const img = new Image();
|
|
2975
|
+
const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
|
2976
|
+
const url = URL.createObjectURL(blob);
|
|
2977
|
+
const loadResult = await new Promise((resolve) => {
|
|
2978
|
+
img.onload = () => resolve("loaded");
|
|
2979
|
+
img.onerror = () => resolve("error");
|
|
2980
|
+
const timeout = setTimeout(() => resolve("error"), 5e3);
|
|
2981
|
+
img.onload = () => {
|
|
2982
|
+
clearTimeout(timeout);
|
|
2983
|
+
resolve("loaded");
|
|
2984
|
+
};
|
|
2985
|
+
});
|
|
2986
|
+
URL.revokeObjectURL(url);
|
|
2987
|
+
if (loadResult === "error") {
|
|
2988
|
+
return { ok: false, reason: "load-error", message: "SVG image load failed" };
|
|
2989
|
+
}
|
|
2990
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
2991
|
+
try {
|
|
2992
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.75);
|
|
2993
|
+
return { ok: true, dataUrl };
|
|
2994
|
+
} catch (err) {
|
|
2995
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2996
|
+
return { ok: false, reason: "tainted", message };
|
|
2997
|
+
}
|
|
2998
|
+
} catch (err) {
|
|
2999
|
+
return { ok: false, reason: "error", message: err instanceof Error ? err.message : String(err) };
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
async function tryDisplayMediaCapture() {
|
|
3003
|
+
if (typeof navigator === "undefined" || !("mediaDevices" in navigator)) {
|
|
3004
|
+
return { ok: false, reason: "unsupported", message: "mediaDevices not available" };
|
|
3005
|
+
}
|
|
3006
|
+
const mediaDevices = navigator.mediaDevices;
|
|
3007
|
+
if (typeof mediaDevices.getDisplayMedia !== "function") {
|
|
3008
|
+
return { ok: false, reason: "unsupported", message: "getDisplayMedia not available" };
|
|
3009
|
+
}
|
|
3010
|
+
let stream = null;
|
|
3011
|
+
try {
|
|
3012
|
+
stream = await mediaDevices.getDisplayMedia({
|
|
3013
|
+
video: { displaySurface: "browser" },
|
|
3014
|
+
audio: false
|
|
3015
|
+
});
|
|
3016
|
+
const track = stream.getVideoTracks()[0];
|
|
3017
|
+
if (!track) return { ok: false, reason: "error", message: "No video track" };
|
|
3018
|
+
const imageCapture = new window.ImageCapture(track);
|
|
3019
|
+
const bitmap = await imageCapture.grabFrame();
|
|
3020
|
+
const canvas = document.createElement("canvas");
|
|
3021
|
+
canvas.width = bitmap.width;
|
|
3022
|
+
canvas.height = bitmap.height;
|
|
3023
|
+
const ctx = canvas.getContext("2d");
|
|
3024
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
3025
|
+
bitmap.close();
|
|
3026
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
|
|
3027
|
+
return { ok: true, dataUrl };
|
|
3028
|
+
} catch (err) {
|
|
3029
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3030
|
+
if (err instanceof Error && err.name === "NotAllowedError") {
|
|
3031
|
+
return { ok: false, reason: "cancelled", message };
|
|
3032
|
+
}
|
|
3033
|
+
return { ok: false, reason: "error", message };
|
|
3034
|
+
} finally {
|
|
3035
|
+
stream?.getTracks().forEach((t) => t.stop());
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
2602
3038
|
function buildPrivacySafeDocument(privacy) {
|
|
2603
3039
|
const clone = document.documentElement.cloneNode(true);
|
|
3040
|
+
for (const img of Array.from(clone.querySelectorAll("img[src]"))) {
|
|
3041
|
+
const src = img.getAttribute("src") ?? "";
|
|
3042
|
+
try {
|
|
3043
|
+
const url = new URL(src, window.location.href);
|
|
3044
|
+
if (url.origin !== window.location.origin) {
|
|
3045
|
+
img.removeAttribute("src");
|
|
3046
|
+
img.removeAttribute("srcset");
|
|
3047
|
+
}
|
|
3048
|
+
} catch {
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
for (const el of Array.from(clone.querySelectorAll("[style]"))) {
|
|
3052
|
+
const style = el.getAttribute("style") ?? "";
|
|
3053
|
+
if (/url\(["']?https?:\/\/(?!localhost)/.test(style)) {
|
|
3054
|
+
el.setAttribute("style", style.replace(/url\([^)]*\)/g, "none"));
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
2604
3057
|
for (const selector of privacy?.blockSelectors ?? []) {
|
|
2605
3058
|
for (const el of safeQueryAll(clone, selector)) {
|
|
2606
3059
|
el.remove();
|
|
@@ -2797,6 +3250,11 @@ function createElementSelector() {
|
|
|
2797
3250
|
e.stopPropagation();
|
|
2798
3251
|
const target = e.target;
|
|
2799
3252
|
if (target === overlay) return;
|
|
3253
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
3254
|
+
const hitsMushiHost = path.some(
|
|
3255
|
+
(node) => node instanceof Element && node.id === "mushi-mushi-widget"
|
|
3256
|
+
);
|
|
3257
|
+
if (hitsMushiHost) return;
|
|
2800
3258
|
const captured = captureElement(target);
|
|
2801
3259
|
finish(captured);
|
|
2802
3260
|
}
|
|
@@ -3511,7 +3969,7 @@ function createProactiveManager(config = {}) {
|
|
|
3511
3969
|
|
|
3512
3970
|
// src/version.ts
|
|
3513
3971
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
|
|
3514
|
-
var MUSHI_SDK_VERSION = "1.
|
|
3972
|
+
var MUSHI_SDK_VERSION = "1.3.0" ;
|
|
3515
3973
|
|
|
3516
3974
|
// src/mushi.ts
|
|
3517
3975
|
var instance = null;
|
|
@@ -3716,8 +4174,21 @@ function createInstance(config) {
|
|
|
3716
4174
|
onScreenshotRequest: async () => {
|
|
3717
4175
|
if (!screenshotCap || activeConfig.capture?.screenshot === "off") return;
|
|
3718
4176
|
log.debug("Taking screenshot");
|
|
3719
|
-
|
|
3720
|
-
|
|
4177
|
+
widget.setScreenshotCapturing(true);
|
|
4178
|
+
const result = await screenshotCap.take();
|
|
4179
|
+
if (result.ok) {
|
|
4180
|
+
pendingScreenshot = result.dataUrl;
|
|
4181
|
+
widget.setScreenshotAttached(true);
|
|
4182
|
+
log.debug("Screenshot captured");
|
|
4183
|
+
} else {
|
|
4184
|
+
pendingScreenshot = null;
|
|
4185
|
+
if (result.reason !== "cancelled") {
|
|
4186
|
+
widget.setScreenshotError(true);
|
|
4187
|
+
log.debug("Screenshot failed", { reason: result.reason, message: result.message });
|
|
4188
|
+
} else {
|
|
4189
|
+
widget.setScreenshotCapturing(false);
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
3721
4192
|
},
|
|
3722
4193
|
onScreenshotRemove: () => {
|
|
3723
4194
|
log.debug("Screenshot attachment removed");
|
|
@@ -3727,11 +4198,19 @@ function createInstance(config) {
|
|
|
3727
4198
|
onElementSelectorRequest: async () => {
|
|
3728
4199
|
if (!elementSelector || activeConfig.capture?.elementSelector === false) return;
|
|
3729
4200
|
log.debug("Element selector activated");
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
4201
|
+
widget.setElementCapturing(true);
|
|
4202
|
+
widget.hidePanel();
|
|
4203
|
+
try {
|
|
4204
|
+
const el = await elementSelector.activate();
|
|
4205
|
+
if (el) {
|
|
4206
|
+
pendingElement = el;
|
|
4207
|
+
widget.setElementSelected(true);
|
|
4208
|
+
log.debug("Element selected", { tagName: el.tagName, xpath: el.xpath });
|
|
4209
|
+
} else {
|
|
4210
|
+
widget.setElementCapturing(false);
|
|
4211
|
+
}
|
|
4212
|
+
} finally {
|
|
4213
|
+
widget.showPanel();
|
|
3735
4214
|
}
|
|
3736
4215
|
},
|
|
3737
4216
|
async onReporterReportsRequest() {
|
|
@@ -4259,6 +4738,9 @@ function createInstance(config) {
|
|
|
4259
4738
|
enqueue({ action, metadata });
|
|
4260
4739
|
}
|
|
4261
4740
|
};
|
|
4741
|
+
if (typeof globalThis !== "undefined" && (bootstrapConfig.debug ?? false)) {
|
|
4742
|
+
exposeMarketingRecorder(widget);
|
|
4743
|
+
}
|
|
4262
4744
|
return sdk;
|
|
4263
4745
|
}
|
|
4264
4746
|
function mergeRuntimeConfig(config, runtime) {
|