@triagly/sdk 0.1.1-beta.4 → 0.1.1-beta.6
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 +130 -24
- package/dist/api.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +82 -121
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +82 -121
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/utils.d.ts +0 -4
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/package.json +15 -9
package/README.md
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> JavaScript SDK for Triagly - Turn user feedback into GitHub issues instantly.
|
|
4
4
|
|
|
5
|
-
Lightweight (<10KB gzipped) browser widget that captures user feedback
|
|
5
|
+
Lightweight (<10KB gzipped) browser widget that captures user feedback and automatically creates issues in your tracker.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- 🎨 **Beautiful UI Widget** - Clean, customizable feedback form
|
|
10
|
-
- 📸 **Screenshot Capture** - Automatic page screenshots (with html2canvas)
|
|
11
10
|
- 🐛 **Console Log Capture** - Automatically captures browser console errors and warnings for debugging
|
|
12
11
|
- 🤖 **Bot Protection** - Cloudflare Turnstile integration to prevent spam
|
|
13
12
|
- 🔒 **Secure Authentication** - Uses publishable keys with optional hardened mode
|
|
@@ -34,9 +33,6 @@ yarn add @triagly/sdk
|
|
|
34
33
|
<!-- Include Cloudflare Turnstile for bot protection -->
|
|
35
34
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
|
36
35
|
|
|
37
|
-
<!-- Include html2canvas for screenshot support (optional) -->
|
|
38
|
-
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
39
|
-
|
|
40
36
|
<!-- Include Triagly SDK -->
|
|
41
37
|
<script src="https://unpkg.com/@triagly/sdk/dist/index.min.js"></script>
|
|
42
38
|
```
|
|
@@ -204,6 +200,135 @@ Remove the widget and clean up event listeners.
|
|
|
204
200
|
triagly.destroy();
|
|
205
201
|
```
|
|
206
202
|
|
|
203
|
+
#### `submitNPS(submission)`
|
|
204
|
+
Submit an NPS (Net Promoter Score) metric. Score must be between 0-10.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const response = await triagly.submitNPS({
|
|
208
|
+
score: 9,
|
|
209
|
+
question: 'How likely are you to recommend us?',
|
|
210
|
+
userIdentifier: 'user@example.com',
|
|
211
|
+
metadata: { source: 'email-campaign' }
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### `submitCSAT(submission)`
|
|
216
|
+
Submit a CSAT (Customer Satisfaction) metric. Score must be between 1-5.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const response = await triagly.submitCSAT({
|
|
220
|
+
score: 4,
|
|
221
|
+
question: 'How satisfied are you with our service?',
|
|
222
|
+
userIdentifier: 'user@example.com'
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### `submitCES(submission)`
|
|
227
|
+
Submit a CES (Customer Effort Score) metric. Score must be between 1-7.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const response = await triagly.submitCES({
|
|
231
|
+
score: 2,
|
|
232
|
+
question: 'How easy was it to resolve your issue?',
|
|
233
|
+
metadata: { support_ticket_id: 'TKT-123' }
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### `getMetricStats(params)`
|
|
238
|
+
Retrieve statistics for a specific metric type with optional date range.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const stats = await triagly.getMetricStats({
|
|
242
|
+
metricType: 'NPS',
|
|
243
|
+
startDate: '2025-01-01',
|
|
244
|
+
endDate: '2025-01-31'
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Metrics Support
|
|
249
|
+
|
|
250
|
+
The SDK now supports submitting standard metrics like NPS (Net Promoter Score), CSAT (Customer Satisfaction), and CES (Customer Effort Score).
|
|
251
|
+
|
|
252
|
+
### Submit NPS (Net Promoter Score)
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import Triagly from '@triagly/sdk';
|
|
256
|
+
|
|
257
|
+
const triagly = new Triagly({
|
|
258
|
+
publishableKey: 'pub_live_abc123'
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Submit NPS (score: 0-10)
|
|
262
|
+
const npsResponse = await triagly.submitNPS({
|
|
263
|
+
score: 9,
|
|
264
|
+
question: 'How likely are you to recommend us?',
|
|
265
|
+
userIdentifier: 'user@example.com',
|
|
266
|
+
metadata: { source: 'email-campaign' }
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
console.log('NPS submitted:', npsResponse.id);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Submit CSAT (Customer Satisfaction)
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Submit CSAT (score: 1-5)
|
|
276
|
+
const csatResponse = await triagly.submitCSAT({
|
|
277
|
+
score: 4,
|
|
278
|
+
question: 'How satisfied are you with our service?',
|
|
279
|
+
userIdentifier: 'user@example.com'
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
console.log('CSAT submitted:', csatResponse.id);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Submit CES (Customer Effort Score)
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// Submit CES (score: 1-7, lower is better)
|
|
289
|
+
const cesResponse = await triagly.submitCES({
|
|
290
|
+
score: 2,
|
|
291
|
+
question: 'How easy was it to resolve your issue?',
|
|
292
|
+
userIdentifier: 'user@example.com',
|
|
293
|
+
metadata: { support_ticket_id: 'TKT-123' }
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
console.log('CES submitted:', cesResponse.id);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Get Metric Statistics
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Get NPS statistics for a date range
|
|
303
|
+
const stats = await triagly.getMetricStats({
|
|
304
|
+
metricType: 'NPS',
|
|
305
|
+
startDate: '2025-01-01',
|
|
306
|
+
endDate: '2025-01-31'
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Type-safe access based on metric type
|
|
310
|
+
if (stats.metric_type === 'NPS') {
|
|
311
|
+
console.log(`NPS Score: ${stats.score}`);
|
|
312
|
+
console.log(`Promoters: ${stats.promoters_percent}%`);
|
|
313
|
+
console.log(`Passives: ${stats.passives_percent}%`);
|
|
314
|
+
console.log(`Detractors: ${stats.detractors_percent}%`);
|
|
315
|
+
} else if (stats.metric_type === 'CSAT') {
|
|
316
|
+
console.log(`Average Score: ${stats.average_score}`);
|
|
317
|
+
console.log(`Satisfied: ${stats.percent_satisfied}%`);
|
|
318
|
+
} else if (stats.metric_type === 'CES') {
|
|
319
|
+
console.log(`Average Effort Score: ${stats.average_effort_score}`);
|
|
320
|
+
}
|
|
321
|
+
console.log(`Total Responses: ${stats.total_responses}`);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Metric Score Ranges
|
|
325
|
+
|
|
326
|
+
- **NPS**: 0-10 (0 = Not at all likely, 10 = Extremely likely)
|
|
327
|
+
- **CSAT**: 1-5 (1 = Very dissatisfied, 5 = Very satisfied)
|
|
328
|
+
- **CES**: 1-7 (1 = Very easy, 7 = Very difficult)
|
|
329
|
+
|
|
330
|
+
The SDK automatically validates score ranges and throws descriptive errors for invalid inputs.
|
|
331
|
+
|
|
207
332
|
## Examples
|
|
208
333
|
|
|
209
334
|
### Custom Button Trigger
|
|
@@ -290,25 +415,6 @@ try {
|
|
|
290
415
|
|
|
291
416
|
See [Styling Guide](./docs/guides/STYLING.md) for all available variables and more examples.
|
|
292
417
|
|
|
293
|
-
## Screenshot Support
|
|
294
|
-
|
|
295
|
-
To enable screenshot capture, include html2canvas:
|
|
296
|
-
|
|
297
|
-
```html
|
|
298
|
-
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Or install via NPM and import in your bundle:
|
|
302
|
-
|
|
303
|
-
```bash
|
|
304
|
-
npm install html2canvas
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
import html2canvas from 'html2canvas';
|
|
309
|
-
(window as any).html2canvas = html2canvas;
|
|
310
|
-
```
|
|
311
|
-
|
|
312
418
|
## Console Log Capture
|
|
313
419
|
|
|
314
420
|
The SDK automatically captures browser console messages (errors and warnings by default) to help developers debug issues.
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAIjB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAwB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IAGjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAA8B;gBAG9E,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,MAAM;IAS3B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;YACW,iBAAiB;IA4B/B;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAIjB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAwB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IAGjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAA8B;gBAG9E,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,MAAM;IAS3B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;YACW,iBAAiB;IA4B/B;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC;CA0E7B"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,YAAY,EACb,MAAM,SAAS,CAAC;AAKjB,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAA8B;gBAEvC,MAAM,EAAE,aAAa;IAwDjC;;OAEG;IACH,OAAO,CAAC,IAAI;IAkBZ;;OAEG;YACW,YAAY;IAyD1B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxE;;OAEG;IACH,OAAO,IAAI,IAAI;CAKhB;AAGD,cAAc,SAAS,CAAC;AAexB,eAAe,OAAO,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -45,26 +45,23 @@ class FeedbackWidget {
|
|
|
45
45
|
// Button orientation
|
|
46
46
|
const orientation = this.config.orientation || 'horizontal';
|
|
47
47
|
button.classList.add(`triagly-orientation-${orientation}`);
|
|
48
|
+
// Speech bubble SVG icon
|
|
49
|
+
const speechBubbleIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
|
|
50
|
+
const largeIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
|
|
48
51
|
// Handle button text based on shape
|
|
49
|
-
const fullText = this.config.buttonText || '
|
|
52
|
+
const fullText = this.config.buttonText || 'Feedback';
|
|
50
53
|
if (shape === 'circular') {
|
|
51
|
-
button.innerHTML =
|
|
54
|
+
button.innerHTML = largeIcon;
|
|
52
55
|
button.setAttribute('aria-label', fullText);
|
|
53
56
|
}
|
|
54
57
|
else if (shape === 'expandable') {
|
|
55
|
-
// Expandable starts with
|
|
56
|
-
button.innerHTML =
|
|
58
|
+
// Expandable starts with icon, expands to full text on hover
|
|
59
|
+
button.innerHTML = `<span class="triagly-btn-icon">${largeIcon}</span><span class="triagly-btn-text"> ${this.config.buttonText || 'Feedback'}</span>`;
|
|
57
60
|
button.setAttribute('aria-label', fullText);
|
|
58
|
-
// Store custom text if provided
|
|
59
|
-
if (this.config.buttonText) {
|
|
60
|
-
const textSpan = button.querySelector('.triagly-btn-text');
|
|
61
|
-
if (textSpan) {
|
|
62
|
-
textSpan.textContent = ' ' + this.config.buttonText.replace('🐛', '').trim();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
61
|
}
|
|
66
62
|
else {
|
|
67
|
-
|
|
63
|
+
// Default: icon + text
|
|
64
|
+
button.innerHTML = `${speechBubbleIcon}<span class="triagly-btn-label">${fullText}</span>`;
|
|
68
65
|
}
|
|
69
66
|
button.onclick = () => this.toggle();
|
|
70
67
|
// Position button
|
|
@@ -130,9 +127,9 @@ class FeedbackWidget {
|
|
|
130
127
|
this.setupKeyboardEvents();
|
|
131
128
|
// Set up focus trap
|
|
132
129
|
this.setupFocusTrap();
|
|
133
|
-
// Focus on
|
|
134
|
-
const
|
|
135
|
-
|
|
130
|
+
// Focus on description field
|
|
131
|
+
const descInput = this.container?.querySelector('#triagly-description');
|
|
132
|
+
descInput?.focus();
|
|
136
133
|
}, 0);
|
|
137
134
|
}
|
|
138
135
|
/**
|
|
@@ -177,7 +174,7 @@ class FeedbackWidget {
|
|
|
177
174
|
overlay.className = 'triagly-overlay';
|
|
178
175
|
overlay.setAttribute('role', 'dialog');
|
|
179
176
|
overlay.setAttribute('aria-modal', 'true');
|
|
180
|
-
overlay.setAttribute('aria-
|
|
177
|
+
overlay.setAttribute('aria-label', 'Send feedback');
|
|
181
178
|
overlay.onclick = (e) => {
|
|
182
179
|
if (e.target === overlay)
|
|
183
180
|
this.close('overlay');
|
|
@@ -188,7 +185,6 @@ class FeedbackWidget {
|
|
|
188
185
|
const header = document.createElement('div');
|
|
189
186
|
header.className = 'triagly-header';
|
|
190
187
|
header.innerHTML = `
|
|
191
|
-
<h3 id="triagly-modal-title">Send Feedback</h3>
|
|
192
188
|
<button type="button" class="triagly-close" aria-label="Close feedback form">×</button>
|
|
193
189
|
`;
|
|
194
190
|
const closeBtn = header.querySelector('.triagly-close');
|
|
@@ -197,16 +193,7 @@ class FeedbackWidget {
|
|
|
197
193
|
form.className = 'triagly-form';
|
|
198
194
|
form.innerHTML = `
|
|
199
195
|
<div class="triagly-field">
|
|
200
|
-
<label for="triagly-
|
|
201
|
-
<input
|
|
202
|
-
type="text"
|
|
203
|
-
id="triagly-title"
|
|
204
|
-
placeholder="Brief summary of your feedback"
|
|
205
|
-
/>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<div class="triagly-field">
|
|
209
|
-
<label for="triagly-description">Description *</label>
|
|
196
|
+
<label for="triagly-description">What's on your mind?</label>
|
|
210
197
|
<textarea
|
|
211
198
|
id="triagly-description"
|
|
212
199
|
required
|
|
@@ -215,6 +202,15 @@ class FeedbackWidget {
|
|
|
215
202
|
></textarea>
|
|
216
203
|
</div>
|
|
217
204
|
|
|
205
|
+
<div class="triagly-field">
|
|
206
|
+
<label for="triagly-name">Name (optional)</label>
|
|
207
|
+
<input
|
|
208
|
+
type="text"
|
|
209
|
+
id="triagly-name"
|
|
210
|
+
placeholder="Your name"
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
218
214
|
<div class="triagly-field">
|
|
219
215
|
<label for="triagly-email">Email (optional)</label>
|
|
220
216
|
<input
|
|
@@ -224,13 +220,6 @@ class FeedbackWidget {
|
|
|
224
220
|
/>
|
|
225
221
|
</div>
|
|
226
222
|
|
|
227
|
-
<div class="triagly-field triagly-checkbox">
|
|
228
|
-
<label>
|
|
229
|
-
<input type="checkbox" id="triagly-screenshot" checked />
|
|
230
|
-
<span>Include screenshot</span>
|
|
231
|
-
</label>
|
|
232
|
-
</div>
|
|
233
|
-
|
|
234
223
|
${this.config.turnstileSiteKey ? `
|
|
235
224
|
<div class="triagly-field triagly-turnstile">
|
|
236
225
|
<div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>
|
|
@@ -254,8 +243,16 @@ class FeedbackWidget {
|
|
|
254
243
|
e.preventDefault();
|
|
255
244
|
this.handleSubmit(form);
|
|
256
245
|
};
|
|
246
|
+
const footer = document.createElement('div');
|
|
247
|
+
footer.className = 'triagly-footer';
|
|
248
|
+
footer.innerHTML = `
|
|
249
|
+
<a href="https://triagly.com" target="_blank" rel="noopener noreferrer" class="triagly-branding">
|
|
250
|
+
Powered by <strong>Triagly</strong>
|
|
251
|
+
</a>
|
|
252
|
+
`;
|
|
257
253
|
modal.appendChild(header);
|
|
258
254
|
modal.appendChild(form);
|
|
255
|
+
modal.appendChild(footer);
|
|
259
256
|
overlay.appendChild(modal);
|
|
260
257
|
// Render Turnstile widget if available
|
|
261
258
|
if (this.config.turnstileSiteKey) {
|
|
@@ -304,10 +301,9 @@ class FeedbackWidget {
|
|
|
304
301
|
* Handle form submission
|
|
305
302
|
*/
|
|
306
303
|
async handleSubmit(form) {
|
|
307
|
-
const titleInput = form.querySelector('#triagly-title');
|
|
308
304
|
const descInput = form.querySelector('#triagly-description');
|
|
305
|
+
const nameInput = form.querySelector('#triagly-name');
|
|
309
306
|
const emailInput = form.querySelector('#triagly-email');
|
|
310
|
-
const screenshotCheckbox = form.querySelector('#triagly-screenshot');
|
|
311
307
|
const statusDiv = form.querySelector('#triagly-status');
|
|
312
308
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
313
309
|
const turnstileContainer = form.querySelector('.cf-turnstile');
|
|
@@ -321,10 +317,9 @@ class FeedbackWidget {
|
|
|
321
317
|
turnstileToken = turnstileContainer.getAttribute('data-turnstile-response') || undefined;
|
|
322
318
|
}
|
|
323
319
|
const data = {
|
|
324
|
-
title: titleInput.value.trim() || undefined,
|
|
325
320
|
description: descInput.value.trim(),
|
|
321
|
+
reporterName: nameInput.value.trim() || undefined,
|
|
326
322
|
reporterEmail: emailInput.value.trim() || undefined,
|
|
327
|
-
includeScreenshot: screenshotCheckbox.checked,
|
|
328
323
|
turnstileToken,
|
|
329
324
|
};
|
|
330
325
|
// Create a promise that waits for actual submission result
|
|
@@ -388,21 +383,32 @@ class FeedbackWidget {
|
|
|
388
383
|
position: fixed;
|
|
389
384
|
z-index: 999999;
|
|
390
385
|
padding: 12px 20px;
|
|
391
|
-
background: var(--triagly-button-bg, #
|
|
386
|
+
background: var(--triagly-button-bg, #18181b);
|
|
392
387
|
color: var(--triagly-button-text, #ffffff);
|
|
393
388
|
border: none;
|
|
394
389
|
border-radius: var(--triagly-button-radius, 8px);
|
|
395
390
|
font-size: 14px;
|
|
396
391
|
font-weight: 500;
|
|
397
392
|
cursor: pointer;
|
|
398
|
-
box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(
|
|
393
|
+
box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(0, 0, 0, 0.15));
|
|
399
394
|
transition: all 0.2s;
|
|
395
|
+
display: inline-flex;
|
|
396
|
+
align-items: center;
|
|
397
|
+
gap: 8px;
|
|
400
398
|
}
|
|
401
399
|
|
|
402
400
|
.triagly-button:hover {
|
|
403
|
-
background: var(--triagly-button-bg-hover, #
|
|
401
|
+
background: var(--triagly-button-bg-hover, #27272a);
|
|
404
402
|
transform: translateY(-2px);
|
|
405
|
-
box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(
|
|
403
|
+
box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.triagly-button .triagly-icon {
|
|
407
|
+
flex-shrink: 0;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.triagly-button .triagly-btn-label {
|
|
411
|
+
line-height: 1;
|
|
406
412
|
}
|
|
407
413
|
|
|
408
414
|
/* Prevent expandable buttons from shifting on hover */
|
|
@@ -496,8 +502,8 @@ class FeedbackWidget {
|
|
|
496
502
|
min-width: auto;
|
|
497
503
|
padding: 12px 20px;
|
|
498
504
|
border-radius: 30px;
|
|
499
|
-
background: var(--triagly-button-bg-hover, #
|
|
500
|
-
box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(
|
|
505
|
+
background: var(--triagly-button-bg-hover, #27272a);
|
|
506
|
+
box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));
|
|
501
507
|
}
|
|
502
508
|
.triagly-shape-expandable:hover .triagly-btn-text {
|
|
503
509
|
width: auto;
|
|
@@ -556,18 +562,10 @@ class FeedbackWidget {
|
|
|
556
562
|
|
|
557
563
|
.triagly-header {
|
|
558
564
|
display: flex;
|
|
559
|
-
justify-content:
|
|
565
|
+
justify-content: flex-end;
|
|
560
566
|
align-items: center;
|
|
561
|
-
padding:
|
|
567
|
+
padding: 8px 12px 0;
|
|
562
568
|
background: var(--triagly-header-bg, #ffffff);
|
|
563
|
-
border-bottom: 1px solid var(--triagly-header-border, #e5e7eb);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
.triagly-header h3 {
|
|
567
|
-
margin: 0;
|
|
568
|
-
font-size: 18px;
|
|
569
|
-
font-weight: 600;
|
|
570
|
-
color: var(--triagly-header-text, #111827);
|
|
571
569
|
}
|
|
572
570
|
|
|
573
571
|
.triagly-close {
|
|
@@ -625,46 +623,21 @@ class FeedbackWidget {
|
|
|
625
623
|
.triagly-field input:focus,
|
|
626
624
|
.triagly-field textarea:focus {
|
|
627
625
|
outline: none;
|
|
628
|
-
border-color: var(--triagly-input-border-focus, #
|
|
629
|
-
box-shadow: 0 0 0
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
.triagly-checkbox label {
|
|
633
|
-
display: flex;
|
|
634
|
-
align-items: center;
|
|
635
|
-
gap: 8px;
|
|
636
|
-
cursor: pointer;
|
|
637
|
-
font-weight: 400;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
.triagly-checkbox label span {
|
|
641
|
-
user-select: none;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
.triagly-checkbox input {
|
|
645
|
-
width: 16px;
|
|
646
|
-
height: 16px;
|
|
647
|
-
margin: 0;
|
|
648
|
-
cursor: pointer;
|
|
626
|
+
border-color: var(--triagly-input-border-focus, #a1a1aa);
|
|
627
|
+
box-shadow: 0 0 0 2px rgba(161, 161, 170, 0.15);
|
|
649
628
|
}
|
|
650
629
|
|
|
651
630
|
/* Focus visible styles for accessibility */
|
|
652
631
|
.triagly-button:focus-visible,
|
|
653
632
|
.triagly-field input:focus-visible,
|
|
654
633
|
.triagly-field textarea:focus-visible,
|
|
655
|
-
.triagly-checkbox input:focus-visible,
|
|
656
634
|
.triagly-btn-primary:focus-visible,
|
|
657
635
|
.triagly-btn-secondary:focus-visible,
|
|
658
636
|
.triagly-close:focus-visible {
|
|
659
|
-
outline: 2px solid #
|
|
637
|
+
outline: 2px solid #a1a1aa;
|
|
660
638
|
outline-offset: 2px;
|
|
661
639
|
}
|
|
662
640
|
|
|
663
|
-
/* Checkbox label gets visual indicator when checkbox is focused */
|
|
664
|
-
.triagly-checkbox input:focus-visible + span {
|
|
665
|
-
text-decoration: underline;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
641
|
.triagly-turnstile {
|
|
669
642
|
display: flex;
|
|
670
643
|
justify-content: center;
|
|
@@ -690,12 +663,12 @@ class FeedbackWidget {
|
|
|
690
663
|
}
|
|
691
664
|
|
|
692
665
|
.triagly-btn-primary {
|
|
693
|
-
background: var(--triagly-btn-primary-bg, #
|
|
666
|
+
background: var(--triagly-btn-primary-bg, #18181b);
|
|
694
667
|
color: var(--triagly-btn-primary-text, #ffffff);
|
|
695
668
|
}
|
|
696
669
|
|
|
697
670
|
.triagly-btn-primary:hover:not(:disabled) {
|
|
698
|
-
background: var(--triagly-btn-primary-bg-hover, #
|
|
671
|
+
background: var(--triagly-btn-primary-bg-hover, #27272a);
|
|
699
672
|
}
|
|
700
673
|
|
|
701
674
|
.triagly-btn-primary:disabled {
|
|
@@ -731,6 +704,29 @@ class FeedbackWidget {
|
|
|
731
704
|
background: var(--triagly-error-bg, #fee2e2);
|
|
732
705
|
color: var(--triagly-error-text, #991b1b);
|
|
733
706
|
}
|
|
707
|
+
|
|
708
|
+
.triagly-footer {
|
|
709
|
+
padding: 12px 24px 16px;
|
|
710
|
+
text-align: right;
|
|
711
|
+
border-top: 1px solid var(--triagly-footer-border, #e5e7eb);
|
|
712
|
+
background: var(--triagly-footer-bg, #f9fafb);
|
|
713
|
+
border-radius: 0 0 var(--triagly-modal-radius, 12px) var(--triagly-modal-radius, 12px);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.triagly-branding {
|
|
717
|
+
font-size: 12px;
|
|
718
|
+
color: var(--triagly-footer-text, #6b7280);
|
|
719
|
+
text-decoration: none;
|
|
720
|
+
transition: color 0.2s;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.triagly-branding:hover {
|
|
724
|
+
color: var(--triagly-footer-text-hover, #18181b);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.triagly-branding strong {
|
|
728
|
+
font-weight: 600;
|
|
729
|
+
}
|
|
734
730
|
`;
|
|
735
731
|
document.head.appendChild(style);
|
|
736
732
|
}
|
|
@@ -818,7 +814,7 @@ class FeedbackWidget {
|
|
|
818
814
|
}
|
|
819
815
|
|
|
820
816
|
// API Client
|
|
821
|
-
const DEFAULT_API_URL = 'https://
|
|
817
|
+
const DEFAULT_API_URL = 'https://bssghvinezdawvupcyci.supabase.co/functions/v1';
|
|
822
818
|
class TriaglyAPI {
|
|
823
819
|
constructor(publishableKey, apiUrl, getToken, turnstileSiteKey) {
|
|
824
820
|
this.apiUrl = (apiUrl || DEFAULT_API_URL).replace(/\/$/, ''); // Remove trailing slash
|
|
@@ -896,7 +892,6 @@ class TriaglyAPI {
|
|
|
896
892
|
consoleLogs: data.consoleLogs,
|
|
897
893
|
},
|
|
898
894
|
tags: data.tags,
|
|
899
|
-
screenshot: data.screenshot,
|
|
900
895
|
reporterEmail: data.reporterEmail,
|
|
901
896
|
reporterName: data.reporterName,
|
|
902
897
|
turnstileToken,
|
|
@@ -983,34 +978,6 @@ function detectBrowser() {
|
|
|
983
978
|
}
|
|
984
979
|
return browser;
|
|
985
980
|
}
|
|
986
|
-
/**
|
|
987
|
-
* Capture screenshot of current page
|
|
988
|
-
*/
|
|
989
|
-
async function captureScreenshot() {
|
|
990
|
-
try {
|
|
991
|
-
// Use html2canvas library if available
|
|
992
|
-
if (typeof window.html2canvas !== 'undefined') {
|
|
993
|
-
const canvas = await window.html2canvas(document.body, {
|
|
994
|
-
logging: false,
|
|
995
|
-
useCORS: true,
|
|
996
|
-
allowTaint: true,
|
|
997
|
-
});
|
|
998
|
-
return canvas.toDataURL('image/png');
|
|
999
|
-
}
|
|
1000
|
-
// Fallback to native screenshot API if supported (limited browser support)
|
|
1001
|
-
if ('mediaDevices' in navigator && 'getDisplayMedia' in navigator.mediaDevices) {
|
|
1002
|
-
// This requires user interaction and shows a permission dialog
|
|
1003
|
-
// Not ideal for automatic screenshots
|
|
1004
|
-
console.warn('Screenshot capture requires html2canvas library');
|
|
1005
|
-
return null;
|
|
1006
|
-
}
|
|
1007
|
-
return null;
|
|
1008
|
-
}
|
|
1009
|
-
catch (error) {
|
|
1010
|
-
console.error('Screenshot capture failed:', error);
|
|
1011
|
-
return null;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
981
|
/**
|
|
1015
982
|
* Simple rate limiter using localStorage
|
|
1016
983
|
*/
|
|
@@ -1231,7 +1198,7 @@ class Triagly {
|
|
|
1231
1198
|
theme: 'auto',
|
|
1232
1199
|
position: 'bottom-right',
|
|
1233
1200
|
buttonShape: 'rounded',
|
|
1234
|
-
buttonText: '
|
|
1201
|
+
buttonText: 'Feedback',
|
|
1235
1202
|
placeholderText: 'Describe what happened...',
|
|
1236
1203
|
successMessage: 'Feedback sent successfully!',
|
|
1237
1204
|
errorMessage: 'Failed to send feedback. Please try again.',
|
|
@@ -1286,17 +1253,11 @@ class Triagly {
|
|
|
1286
1253
|
}
|
|
1287
1254
|
// Collect metadata
|
|
1288
1255
|
const metadata = collectMetadata(this.config.metadata);
|
|
1289
|
-
// Capture screenshot if requested
|
|
1290
|
-
let screenshot = null;
|
|
1291
|
-
if (data.includeScreenshot) {
|
|
1292
|
-
screenshot = await captureScreenshot();
|
|
1293
|
-
}
|
|
1294
1256
|
// Prepare feedback data
|
|
1295
1257
|
const feedbackData = {
|
|
1296
1258
|
title: data.title,
|
|
1297
1259
|
description: data.description,
|
|
1298
1260
|
reporterEmail: data.reporterEmail,
|
|
1299
|
-
screenshot: screenshot || undefined,
|
|
1300
1261
|
consoleLogs: this.consoleLogger?.getLogs(),
|
|
1301
1262
|
};
|
|
1302
1263
|
// Submit to API with Turnstile token if provided
|