@triagly/sdk 0.1.1-beta.3 → 0.1.1-beta.5
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 +99 -120
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +99 -120
- 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 +4 -0
- 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
|
@@ -13,6 +13,24 @@ class FeedbackWidget {
|
|
|
13
13
|
init() {
|
|
14
14
|
this.createButton();
|
|
15
15
|
this.injectStyles();
|
|
16
|
+
// Load Turnstile script if configured
|
|
17
|
+
if (this.config.turnstileSiteKey) {
|
|
18
|
+
this.loadTurnstileScript();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load Cloudflare Turnstile script dynamically
|
|
23
|
+
*/
|
|
24
|
+
loadTurnstileScript() {
|
|
25
|
+
// Check if already loaded
|
|
26
|
+
if (window.turnstile || document.querySelector('script[src*="turnstile"]')) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const script = document.createElement('script');
|
|
30
|
+
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
|
|
31
|
+
script.async = true;
|
|
32
|
+
script.defer = true;
|
|
33
|
+
document.head.appendChild(script);
|
|
16
34
|
}
|
|
17
35
|
/**
|
|
18
36
|
* Create the feedback button
|
|
@@ -27,26 +45,23 @@ class FeedbackWidget {
|
|
|
27
45
|
// Button orientation
|
|
28
46
|
const orientation = this.config.orientation || 'horizontal';
|
|
29
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>`;
|
|
30
51
|
// Handle button text based on shape
|
|
31
|
-
const fullText = this.config.buttonText || '
|
|
52
|
+
const fullText = this.config.buttonText || 'Feedback';
|
|
32
53
|
if (shape === 'circular') {
|
|
33
|
-
button.innerHTML =
|
|
54
|
+
button.innerHTML = largeIcon;
|
|
34
55
|
button.setAttribute('aria-label', fullText);
|
|
35
56
|
}
|
|
36
57
|
else if (shape === 'expandable') {
|
|
37
|
-
// Expandable starts with
|
|
38
|
-
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>`;
|
|
39
60
|
button.setAttribute('aria-label', fullText);
|
|
40
|
-
// Store custom text if provided
|
|
41
|
-
if (this.config.buttonText) {
|
|
42
|
-
const textSpan = button.querySelector('.triagly-btn-text');
|
|
43
|
-
if (textSpan) {
|
|
44
|
-
textSpan.textContent = ' ' + this.config.buttonText.replace('🐛', '').trim();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
61
|
}
|
|
48
62
|
else {
|
|
49
|
-
|
|
63
|
+
// Default: icon + text
|
|
64
|
+
button.innerHTML = `${speechBubbleIcon}<span class="triagly-btn-label">${fullText}</span>`;
|
|
50
65
|
}
|
|
51
66
|
button.onclick = () => this.toggle();
|
|
52
67
|
// Position button
|
|
@@ -112,9 +127,9 @@ class FeedbackWidget {
|
|
|
112
127
|
this.setupKeyboardEvents();
|
|
113
128
|
// Set up focus trap
|
|
114
129
|
this.setupFocusTrap();
|
|
115
|
-
// Focus on
|
|
116
|
-
const
|
|
117
|
-
|
|
130
|
+
// Focus on description field
|
|
131
|
+
const descInput = this.container?.querySelector('#triagly-description');
|
|
132
|
+
descInput?.focus();
|
|
118
133
|
}, 0);
|
|
119
134
|
}
|
|
120
135
|
/**
|
|
@@ -159,7 +174,7 @@ class FeedbackWidget {
|
|
|
159
174
|
overlay.className = 'triagly-overlay';
|
|
160
175
|
overlay.setAttribute('role', 'dialog');
|
|
161
176
|
overlay.setAttribute('aria-modal', 'true');
|
|
162
|
-
overlay.setAttribute('aria-
|
|
177
|
+
overlay.setAttribute('aria-label', 'Send feedback');
|
|
163
178
|
overlay.onclick = (e) => {
|
|
164
179
|
if (e.target === overlay)
|
|
165
180
|
this.close('overlay');
|
|
@@ -170,7 +185,6 @@ class FeedbackWidget {
|
|
|
170
185
|
const header = document.createElement('div');
|
|
171
186
|
header.className = 'triagly-header';
|
|
172
187
|
header.innerHTML = `
|
|
173
|
-
<h3 id="triagly-modal-title">Send Feedback</h3>
|
|
174
188
|
<button type="button" class="triagly-close" aria-label="Close feedback form">×</button>
|
|
175
189
|
`;
|
|
176
190
|
const closeBtn = header.querySelector('.triagly-close');
|
|
@@ -179,16 +193,7 @@ class FeedbackWidget {
|
|
|
179
193
|
form.className = 'triagly-form';
|
|
180
194
|
form.innerHTML = `
|
|
181
195
|
<div class="triagly-field">
|
|
182
|
-
<label for="triagly-
|
|
183
|
-
<input
|
|
184
|
-
type="text"
|
|
185
|
-
id="triagly-title"
|
|
186
|
-
placeholder="Brief summary of your feedback"
|
|
187
|
-
/>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
<div class="triagly-field">
|
|
191
|
-
<label for="triagly-description">Description *</label>
|
|
196
|
+
<label for="triagly-description">What's on your mind?</label>
|
|
192
197
|
<textarea
|
|
193
198
|
id="triagly-description"
|
|
194
199
|
required
|
|
@@ -197,6 +202,15 @@ class FeedbackWidget {
|
|
|
197
202
|
></textarea>
|
|
198
203
|
</div>
|
|
199
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
|
+
|
|
200
214
|
<div class="triagly-field">
|
|
201
215
|
<label for="triagly-email">Email (optional)</label>
|
|
202
216
|
<input
|
|
@@ -206,13 +220,6 @@ class FeedbackWidget {
|
|
|
206
220
|
/>
|
|
207
221
|
</div>
|
|
208
222
|
|
|
209
|
-
<div class="triagly-field triagly-checkbox">
|
|
210
|
-
<label>
|
|
211
|
-
<input type="checkbox" id="triagly-screenshot" checked />
|
|
212
|
-
<span>Include screenshot</span>
|
|
213
|
-
</label>
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
223
|
${this.config.turnstileSiteKey ? `
|
|
217
224
|
<div class="triagly-field triagly-turnstile">
|
|
218
225
|
<div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>
|
|
@@ -236,8 +243,16 @@ class FeedbackWidget {
|
|
|
236
243
|
e.preventDefault();
|
|
237
244
|
this.handleSubmit(form);
|
|
238
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
|
+
`;
|
|
239
253
|
modal.appendChild(header);
|
|
240
254
|
modal.appendChild(form);
|
|
255
|
+
modal.appendChild(footer);
|
|
241
256
|
overlay.appendChild(modal);
|
|
242
257
|
// Render Turnstile widget if available
|
|
243
258
|
if (this.config.turnstileSiteKey) {
|
|
@@ -286,10 +301,9 @@ class FeedbackWidget {
|
|
|
286
301
|
* Handle form submission
|
|
287
302
|
*/
|
|
288
303
|
async handleSubmit(form) {
|
|
289
|
-
const titleInput = form.querySelector('#triagly-title');
|
|
290
304
|
const descInput = form.querySelector('#triagly-description');
|
|
305
|
+
const nameInput = form.querySelector('#triagly-name');
|
|
291
306
|
const emailInput = form.querySelector('#triagly-email');
|
|
292
|
-
const screenshotCheckbox = form.querySelector('#triagly-screenshot');
|
|
293
307
|
const statusDiv = form.querySelector('#triagly-status');
|
|
294
308
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
295
309
|
const turnstileContainer = form.querySelector('.cf-turnstile');
|
|
@@ -303,10 +317,9 @@ class FeedbackWidget {
|
|
|
303
317
|
turnstileToken = turnstileContainer.getAttribute('data-turnstile-response') || undefined;
|
|
304
318
|
}
|
|
305
319
|
const data = {
|
|
306
|
-
title: titleInput.value.trim() || undefined,
|
|
307
320
|
description: descInput.value.trim(),
|
|
321
|
+
reporterName: nameInput.value.trim() || undefined,
|
|
308
322
|
reporterEmail: emailInput.value.trim() || undefined,
|
|
309
|
-
includeScreenshot: screenshotCheckbox.checked,
|
|
310
323
|
turnstileToken,
|
|
311
324
|
};
|
|
312
325
|
// Create a promise that waits for actual submission result
|
|
@@ -370,21 +383,32 @@ class FeedbackWidget {
|
|
|
370
383
|
position: fixed;
|
|
371
384
|
z-index: 999999;
|
|
372
385
|
padding: 12px 20px;
|
|
373
|
-
background: var(--triagly-button-bg, #
|
|
386
|
+
background: var(--triagly-button-bg, #18181b);
|
|
374
387
|
color: var(--triagly-button-text, #ffffff);
|
|
375
388
|
border: none;
|
|
376
389
|
border-radius: var(--triagly-button-radius, 8px);
|
|
377
390
|
font-size: 14px;
|
|
378
391
|
font-weight: 500;
|
|
379
392
|
cursor: pointer;
|
|
380
|
-
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));
|
|
381
394
|
transition: all 0.2s;
|
|
395
|
+
display: inline-flex;
|
|
396
|
+
align-items: center;
|
|
397
|
+
gap: 8px;
|
|
382
398
|
}
|
|
383
399
|
|
|
384
400
|
.triagly-button:hover {
|
|
385
|
-
background: var(--triagly-button-bg-hover, #
|
|
401
|
+
background: var(--triagly-button-bg-hover, #27272a);
|
|
386
402
|
transform: translateY(-2px);
|
|
387
|
-
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;
|
|
388
412
|
}
|
|
389
413
|
|
|
390
414
|
/* Prevent expandable buttons from shifting on hover */
|
|
@@ -478,8 +502,8 @@ class FeedbackWidget {
|
|
|
478
502
|
min-width: auto;
|
|
479
503
|
padding: 12px 20px;
|
|
480
504
|
border-radius: 30px;
|
|
481
|
-
background: var(--triagly-button-bg-hover, #
|
|
482
|
-
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));
|
|
483
507
|
}
|
|
484
508
|
.triagly-shape-expandable:hover .triagly-btn-text {
|
|
485
509
|
width: auto;
|
|
@@ -538,18 +562,10 @@ class FeedbackWidget {
|
|
|
538
562
|
|
|
539
563
|
.triagly-header {
|
|
540
564
|
display: flex;
|
|
541
|
-
justify-content:
|
|
565
|
+
justify-content: flex-end;
|
|
542
566
|
align-items: center;
|
|
543
|
-
padding:
|
|
567
|
+
padding: 8px 12px 0;
|
|
544
568
|
background: var(--triagly-header-bg, #ffffff);
|
|
545
|
-
border-bottom: 1px solid var(--triagly-header-border, #e5e7eb);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
.triagly-header h3 {
|
|
549
|
-
margin: 0;
|
|
550
|
-
font-size: 18px;
|
|
551
|
-
font-weight: 600;
|
|
552
|
-
color: var(--triagly-header-text, #111827);
|
|
553
569
|
}
|
|
554
570
|
|
|
555
571
|
.triagly-close {
|
|
@@ -607,46 +623,21 @@ class FeedbackWidget {
|
|
|
607
623
|
.triagly-field input:focus,
|
|
608
624
|
.triagly-field textarea:focus {
|
|
609
625
|
outline: none;
|
|
610
|
-
border-color: var(--triagly-input-border-focus, #
|
|
611
|
-
box-shadow: 0 0 0
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
.triagly-checkbox label {
|
|
615
|
-
display: flex;
|
|
616
|
-
align-items: center;
|
|
617
|
-
gap: 8px;
|
|
618
|
-
cursor: pointer;
|
|
619
|
-
font-weight: 400;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
.triagly-checkbox label span {
|
|
623
|
-
user-select: none;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
.triagly-checkbox input {
|
|
627
|
-
width: 16px;
|
|
628
|
-
height: 16px;
|
|
629
|
-
margin: 0;
|
|
630
|
-
cursor: pointer;
|
|
626
|
+
border-color: var(--triagly-input-border-focus, #a1a1aa);
|
|
627
|
+
box-shadow: 0 0 0 2px rgba(161, 161, 170, 0.15);
|
|
631
628
|
}
|
|
632
629
|
|
|
633
630
|
/* Focus visible styles for accessibility */
|
|
634
631
|
.triagly-button:focus-visible,
|
|
635
632
|
.triagly-field input:focus-visible,
|
|
636
633
|
.triagly-field textarea:focus-visible,
|
|
637
|
-
.triagly-checkbox input:focus-visible,
|
|
638
634
|
.triagly-btn-primary:focus-visible,
|
|
639
635
|
.triagly-btn-secondary:focus-visible,
|
|
640
636
|
.triagly-close:focus-visible {
|
|
641
|
-
outline: 2px solid #
|
|
637
|
+
outline: 2px solid #a1a1aa;
|
|
642
638
|
outline-offset: 2px;
|
|
643
639
|
}
|
|
644
640
|
|
|
645
|
-
/* Checkbox label gets visual indicator when checkbox is focused */
|
|
646
|
-
.triagly-checkbox input:focus-visible + span {
|
|
647
|
-
text-decoration: underline;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
641
|
.triagly-turnstile {
|
|
651
642
|
display: flex;
|
|
652
643
|
justify-content: center;
|
|
@@ -672,12 +663,12 @@ class FeedbackWidget {
|
|
|
672
663
|
}
|
|
673
664
|
|
|
674
665
|
.triagly-btn-primary {
|
|
675
|
-
background: var(--triagly-btn-primary-bg, #
|
|
666
|
+
background: var(--triagly-btn-primary-bg, #18181b);
|
|
676
667
|
color: var(--triagly-btn-primary-text, #ffffff);
|
|
677
668
|
}
|
|
678
669
|
|
|
679
670
|
.triagly-btn-primary:hover:not(:disabled) {
|
|
680
|
-
background: var(--triagly-btn-primary-bg-hover, #
|
|
671
|
+
background: var(--triagly-btn-primary-bg-hover, #27272a);
|
|
681
672
|
}
|
|
682
673
|
|
|
683
674
|
.triagly-btn-primary:disabled {
|
|
@@ -713,6 +704,29 @@ class FeedbackWidget {
|
|
|
713
704
|
background: var(--triagly-error-bg, #fee2e2);
|
|
714
705
|
color: var(--triagly-error-text, #991b1b);
|
|
715
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
|
+
}
|
|
716
730
|
`;
|
|
717
731
|
document.head.appendChild(style);
|
|
718
732
|
}
|
|
@@ -878,7 +892,6 @@ class TriaglyAPI {
|
|
|
878
892
|
consoleLogs: data.consoleLogs,
|
|
879
893
|
},
|
|
880
894
|
tags: data.tags,
|
|
881
|
-
screenshot: data.screenshot,
|
|
882
895
|
reporterEmail: data.reporterEmail,
|
|
883
896
|
reporterName: data.reporterName,
|
|
884
897
|
turnstileToken,
|
|
@@ -965,34 +978,6 @@ function detectBrowser() {
|
|
|
965
978
|
}
|
|
966
979
|
return browser;
|
|
967
980
|
}
|
|
968
|
-
/**
|
|
969
|
-
* Capture screenshot of current page
|
|
970
|
-
*/
|
|
971
|
-
async function captureScreenshot() {
|
|
972
|
-
try {
|
|
973
|
-
// Use html2canvas library if available
|
|
974
|
-
if (typeof window.html2canvas !== 'undefined') {
|
|
975
|
-
const canvas = await window.html2canvas(document.body, {
|
|
976
|
-
logging: false,
|
|
977
|
-
useCORS: true,
|
|
978
|
-
allowTaint: true,
|
|
979
|
-
});
|
|
980
|
-
return canvas.toDataURL('image/png');
|
|
981
|
-
}
|
|
982
|
-
// Fallback to native screenshot API if supported (limited browser support)
|
|
983
|
-
if ('mediaDevices' in navigator && 'getDisplayMedia' in navigator.mediaDevices) {
|
|
984
|
-
// This requires user interaction and shows a permission dialog
|
|
985
|
-
// Not ideal for automatic screenshots
|
|
986
|
-
console.warn('Screenshot capture requires html2canvas library');
|
|
987
|
-
return null;
|
|
988
|
-
}
|
|
989
|
-
return null;
|
|
990
|
-
}
|
|
991
|
-
catch (error) {
|
|
992
|
-
console.error('Screenshot capture failed:', error);
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
981
|
/**
|
|
997
982
|
* Simple rate limiter using localStorage
|
|
998
983
|
*/
|
|
@@ -1213,7 +1198,7 @@ class Triagly {
|
|
|
1213
1198
|
theme: 'auto',
|
|
1214
1199
|
position: 'bottom-right',
|
|
1215
1200
|
buttonShape: 'rounded',
|
|
1216
|
-
buttonText: '
|
|
1201
|
+
buttonText: 'Feedback',
|
|
1217
1202
|
placeholderText: 'Describe what happened...',
|
|
1218
1203
|
successMessage: 'Feedback sent successfully!',
|
|
1219
1204
|
errorMessage: 'Failed to send feedback. Please try again.',
|
|
@@ -1268,17 +1253,11 @@ class Triagly {
|
|
|
1268
1253
|
}
|
|
1269
1254
|
// Collect metadata
|
|
1270
1255
|
const metadata = collectMetadata(this.config.metadata);
|
|
1271
|
-
// Capture screenshot if requested
|
|
1272
|
-
let screenshot = null;
|
|
1273
|
-
if (data.includeScreenshot) {
|
|
1274
|
-
screenshot = await captureScreenshot();
|
|
1275
|
-
}
|
|
1276
1256
|
// Prepare feedback data
|
|
1277
1257
|
const feedbackData = {
|
|
1278
1258
|
title: data.title,
|
|
1279
1259
|
description: data.description,
|
|
1280
1260
|
reporterEmail: data.reporterEmail,
|
|
1281
|
-
screenshot: screenshot || undefined,
|
|
1282
1261
|
consoleLogs: this.consoleLogger?.getLogs(),
|
|
1283
1262
|
};
|
|
1284
1263
|
// Submit to API with Turnstile token if provided
|