@triagly/sdk 0.1.2 → 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/README.md +1 -24
- package/dist/api.d.ts +2 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +95 -131
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +95 -131
- 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 +1 -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/package.json +1 -2
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
|
```
|
|
@@ -419,25 +415,6 @@ try {
|
|
|
419
415
|
|
|
420
416
|
See [Styling Guide](./docs/guides/STYLING.md) for all available variables and more examples.
|
|
421
417
|
|
|
422
|
-
## Screenshot Support
|
|
423
|
-
|
|
424
|
-
To enable screenshot capture, include html2canvas:
|
|
425
|
-
|
|
426
|
-
```html
|
|
427
|
-
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
Or install via NPM and import in your bundle:
|
|
431
|
-
|
|
432
|
-
```bash
|
|
433
|
-
npm install html2canvas
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
```typescript
|
|
437
|
-
import html2canvas from 'html2canvas';
|
|
438
|
-
(window as any).html2canvas = html2canvas;
|
|
439
|
-
```
|
|
440
|
-
|
|
441
418
|
## Console Log Capture
|
|
442
419
|
|
|
443
420
|
The SDK automatically captures browser console messages (errors and warnings by default) to help developers debug issues.
|
package/dist/api.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { FeedbackData, FeedbackMetadata, FeedbackResponse } from './types';
|
|
2
|
+
export type Environment = 'production' | 'staging';
|
|
2
3
|
export declare class TriaglyAPI {
|
|
3
4
|
private apiUrl;
|
|
4
5
|
private publishableKey;
|
|
5
6
|
private getToken?;
|
|
6
7
|
private turnstileSiteKey;
|
|
7
8
|
private static readonly DEFAULT_TURNSTILE_SITE_KEY;
|
|
8
|
-
constructor(publishableKey: string, apiUrl?: string, getToken?: () => Promise<string>, turnstileSiteKey?: string);
|
|
9
|
+
constructor(publishableKey: string, environment?: Environment, apiUrl?: string, getToken?: () => Promise<string>, turnstileSiteKey?: string);
|
|
9
10
|
/**
|
|
10
11
|
* Get the Turnstile site key
|
|
11
12
|
*/
|
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;
|
|
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;AAOjB,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAEnD,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,WAAW,GAAE,WAA0B,EACvC,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,MAAM;IAU3B;;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,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;
|
|
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;IAyDjC;;OAEG;IACH,OAAO,CAAC,IAAI;IAkBZ;;OAEG;YACW,YAAY;IAuD1B;;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) {
|
|
@@ -297,17 +294,16 @@ class FeedbackWidget {
|
|
|
297
294
|
turnstileContainer.setAttribute('data-widget-id', widgetId);
|
|
298
295
|
}
|
|
299
296
|
catch (error) {
|
|
300
|
-
console.error('Triagly: Failed to render Turnstile widget:', error);
|
|
297
|
+
console.error('Triagly: Failed to render Turnstile widget:', error instanceof Error ? error.message : 'Unknown error');
|
|
301
298
|
}
|
|
302
299
|
}
|
|
303
300
|
/**
|
|
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,10 +814,14 @@ class FeedbackWidget {
|
|
|
818
814
|
}
|
|
819
815
|
|
|
820
816
|
// API Client
|
|
821
|
-
const
|
|
817
|
+
const API_URLS = {
|
|
818
|
+
production: 'https://iipkklhhafrjesryscjh.supabase.co/functions/v1',
|
|
819
|
+
staging: 'https://bssghvinezdawvupcyci.supabase.co/functions/v1',
|
|
820
|
+
};
|
|
822
821
|
class TriaglyAPI {
|
|
823
|
-
constructor(publishableKey, apiUrl, getToken, turnstileSiteKey) {
|
|
824
|
-
|
|
822
|
+
constructor(publishableKey, environment = 'production', apiUrl, getToken, turnstileSiteKey) {
|
|
823
|
+
// apiUrl override takes precedence, then environment-based URL
|
|
824
|
+
this.apiUrl = (apiUrl || API_URLS[environment]).replace(/\/$/, ''); // Remove trailing slash
|
|
825
825
|
this.publishableKey = publishableKey;
|
|
826
826
|
this.getToken = getToken;
|
|
827
827
|
// Always use Triagly's Turnstile site key (can be overridden for testing)
|
|
@@ -859,7 +859,7 @@ class TriaglyAPI {
|
|
|
859
859
|
}
|
|
860
860
|
}
|
|
861
861
|
catch (error) {
|
|
862
|
-
console.warn('Failed to get Turnstile token:', error);
|
|
862
|
+
console.warn('Failed to get Turnstile token:', error instanceof Error ? error.message : 'Unknown error');
|
|
863
863
|
}
|
|
864
864
|
}
|
|
865
865
|
return null;
|
|
@@ -883,7 +883,7 @@ class TriaglyAPI {
|
|
|
883
883
|
hardenedToken = await this.getToken();
|
|
884
884
|
}
|
|
885
885
|
catch (error) {
|
|
886
|
-
console.error('Failed to get hardened token:', error);
|
|
886
|
+
console.error('Failed to get hardened token:', error instanceof Error ? error.message : 'Unknown error');
|
|
887
887
|
throw new Error('Failed to authenticate. Please try again.');
|
|
888
888
|
}
|
|
889
889
|
}
|
|
@@ -896,7 +896,6 @@ class TriaglyAPI {
|
|
|
896
896
|
consoleLogs: data.consoleLogs,
|
|
897
897
|
},
|
|
898
898
|
tags: data.tags,
|
|
899
|
-
screenshot: data.screenshot,
|
|
900
899
|
reporterEmail: data.reporterEmail,
|
|
901
900
|
reporterName: data.reporterName,
|
|
902
901
|
turnstileToken,
|
|
@@ -983,34 +982,6 @@ function detectBrowser() {
|
|
|
983
982
|
}
|
|
984
983
|
return browser;
|
|
985
984
|
}
|
|
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
985
|
/**
|
|
1015
986
|
* Simple rate limiter using localStorage
|
|
1016
987
|
*/
|
|
@@ -1063,7 +1034,7 @@ class RateLimiter {
|
|
|
1063
1034
|
localStorage.setItem(this.key, JSON.stringify(data));
|
|
1064
1035
|
}
|
|
1065
1036
|
catch (error) {
|
|
1066
|
-
console.error('Failed to store rate limit data:', error);
|
|
1037
|
+
console.error('Failed to store rate limit data:', error instanceof Error ? error.message : 'Unknown error');
|
|
1067
1038
|
}
|
|
1068
1039
|
}
|
|
1069
1040
|
}
|
|
@@ -1168,7 +1139,7 @@ class ConsoleLogger {
|
|
|
1168
1139
|
}
|
|
1169
1140
|
catch (error) {
|
|
1170
1141
|
// Don't let logging break the app
|
|
1171
|
-
this.originalConsole.error('Failed to capture log:', error);
|
|
1142
|
+
this.originalConsole.error('Failed to capture log:', error instanceof Error ? error.message : 'Unknown error');
|
|
1172
1143
|
}
|
|
1173
1144
|
}
|
|
1174
1145
|
/**
|
|
@@ -1231,7 +1202,7 @@ class Triagly {
|
|
|
1231
1202
|
theme: 'auto',
|
|
1232
1203
|
position: 'bottom-right',
|
|
1233
1204
|
buttonShape: 'rounded',
|
|
1234
|
-
buttonText: '
|
|
1205
|
+
buttonText: 'Feedback',
|
|
1235
1206
|
placeholderText: 'Describe what happened...',
|
|
1236
1207
|
successMessage: 'Feedback sent successfully!',
|
|
1237
1208
|
errorMessage: 'Failed to send feedback. Please try again.',
|
|
@@ -1242,7 +1213,7 @@ class Triagly {
|
|
|
1242
1213
|
apiKey,
|
|
1243
1214
|
publishableKey: apiKey, // Keep for backward compatibility
|
|
1244
1215
|
};
|
|
1245
|
-
this.api = new TriaglyAPI(apiKey, this.config.apiUrl, this.config.getToken, this.config.turnstileSiteKey);
|
|
1216
|
+
this.api = new TriaglyAPI(apiKey, this.config.environment || 'production', this.config.apiUrl, this.config.getToken, this.config.turnstileSiteKey);
|
|
1246
1217
|
// Always pass Turnstile site key to widget (from API which has default)
|
|
1247
1218
|
this.config.turnstileSiteKey = this.api.getTurnstileSiteKey();
|
|
1248
1219
|
this.widget = new FeedbackWidget(this.config);
|
|
@@ -1286,17 +1257,11 @@ class Triagly {
|
|
|
1286
1257
|
}
|
|
1287
1258
|
// Collect metadata
|
|
1288
1259
|
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
1260
|
// Prepare feedback data
|
|
1295
1261
|
const feedbackData = {
|
|
1296
1262
|
title: data.title,
|
|
1297
1263
|
description: data.description,
|
|
1298
1264
|
reporterEmail: data.reporterEmail,
|
|
1299
|
-
screenshot: screenshot || undefined,
|
|
1300
1265
|
consoleLogs: this.consoleLogger?.getLogs(),
|
|
1301
1266
|
};
|
|
1302
1267
|
// Submit to API with Turnstile token if provided
|
|
@@ -1311,10 +1276,9 @@ class Triagly {
|
|
|
1311
1276
|
if (this.config.onSuccess) {
|
|
1312
1277
|
this.config.onSuccess(response.id);
|
|
1313
1278
|
}
|
|
1314
|
-
console.log('Feedback submitted successfully:', response.id);
|
|
1315
1279
|
}
|
|
1316
1280
|
catch (error) {
|
|
1317
|
-
console.error('Failed to submit feedback:', error);
|
|
1281
|
+
console.error('Failed to submit feedback:', error instanceof Error ? error.message : 'Unknown error');
|
|
1318
1282
|
// Dispatch error event for UI layer
|
|
1319
1283
|
document.dispatchEvent(new CustomEvent('triagly:error', {
|
|
1320
1284
|
detail: error
|