swarm-tickets 1.0.0 → 2.0.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/LICENSE.txt +0 -0
- package/README.md +307 -61
- package/SKILL.md +222 -46
- package/backup-tickets.sh +0 -0
- package/bug-report-widget.js +536 -0
- package/lib/storage/base-adapter.js +200 -0
- package/lib/storage/index.js +87 -0
- package/lib/storage/json-adapter.js +293 -0
- package/lib/storage/sqlite-adapter.js +552 -0
- package/lib/storage/supabase-adapter.js +614 -0
- package/package.json +21 -12
- package/setup.js +9 -11
- package/ticket-cli.js +0 -0
- package/ticket-server.js +425 -269
- package/ticket-tracker.html +459 -132
- package/tickets.example.json +0 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Tickets Bug Report Widget
|
|
3
|
+
* Lightweight embeddable widget for end-user bug reporting
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* <script src="https://your-server/bug-report-widget.js"
|
|
7
|
+
* data-endpoint="https://your-server/api/bug-report"
|
|
8
|
+
* data-api-key="stk_your_api_key"
|
|
9
|
+
* data-position="bottom-right"
|
|
10
|
+
* data-theme="dark">
|
|
11
|
+
* </script>
|
|
12
|
+
*
|
|
13
|
+
* Or programmatically:
|
|
14
|
+
* SwarmBugReport.init({
|
|
15
|
+
* endpoint: 'https://your-server/api/bug-report',
|
|
16
|
+
* apiKey: 'stk_your_api_key',
|
|
17
|
+
* position: 'bottom-right',
|
|
18
|
+
* theme: 'dark'
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
(function() {
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
// Prevent double initialization
|
|
26
|
+
if (window.SwarmBugReport) return;
|
|
27
|
+
|
|
28
|
+
const DEFAULT_CONFIG = {
|
|
29
|
+
endpoint: '/api/bug-report',
|
|
30
|
+
apiKey: null,
|
|
31
|
+
position: 'bottom-right', // bottom-right, bottom-left, top-right, top-left
|
|
32
|
+
theme: 'dark', // dark, light
|
|
33
|
+
buttonText: 'Report Bug',
|
|
34
|
+
buttonIcon: '🐛',
|
|
35
|
+
successMessage: 'Thank you! Your bug report has been submitted.',
|
|
36
|
+
errorMessage: 'Failed to submit bug report. Please try again.',
|
|
37
|
+
rateLimitMessage: 'Too many reports. Please wait a moment.',
|
|
38
|
+
collectErrors: true, // Automatically capture console errors
|
|
39
|
+
collectScreenshot: false, // Experimental: capture screenshot
|
|
40
|
+
maxErrors: 10 // Max errors to collect
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
let config = { ...DEFAULT_CONFIG };
|
|
44
|
+
let collectedErrors = [];
|
|
45
|
+
let isOpen = false;
|
|
46
|
+
|
|
47
|
+
// Styles
|
|
48
|
+
const STYLES = `
|
|
49
|
+
.swarm-bug-widget {
|
|
50
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
51
|
+
position: fixed;
|
|
52
|
+
z-index: 999999;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.swarm-bug-widget.bottom-right { bottom: 20px; right: 20px; }
|
|
56
|
+
.swarm-bug-widget.bottom-left { bottom: 20px; left: 20px; }
|
|
57
|
+
.swarm-bug-widget.top-right { top: 20px; right: 20px; }
|
|
58
|
+
.swarm-bug-widget.top-left { top: 20px; left: 20px; }
|
|
59
|
+
|
|
60
|
+
.swarm-bug-button {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 8px;
|
|
64
|
+
padding: 12px 20px;
|
|
65
|
+
border: none;
|
|
66
|
+
border-radius: 25px;
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
font-size: 14px;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
71
|
+
transition: all 0.3s ease;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.swarm-bug-widget.dark .swarm-bug-button {
|
|
75
|
+
background: #2a2a2a;
|
|
76
|
+
color: #fff;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.swarm-bug-widget.light .swarm-bug-button {
|
|
80
|
+
background: #fff;
|
|
81
|
+
color: #333;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.swarm-bug-button:hover {
|
|
85
|
+
transform: translateY(-2px);
|
|
86
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.swarm-bug-modal {
|
|
90
|
+
position: fixed;
|
|
91
|
+
inset: 0;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
z-index: 1000000;
|
|
96
|
+
opacity: 0;
|
|
97
|
+
visibility: hidden;
|
|
98
|
+
transition: all 0.3s ease;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.swarm-bug-modal.open {
|
|
102
|
+
opacity: 1;
|
|
103
|
+
visibility: visible;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.swarm-bug-overlay {
|
|
107
|
+
position: absolute;
|
|
108
|
+
inset: 0;
|
|
109
|
+
background: rgba(0, 0, 0, 0.5);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.swarm-bug-form-container {
|
|
113
|
+
position: relative;
|
|
114
|
+
width: 90%;
|
|
115
|
+
max-width: 500px;
|
|
116
|
+
max-height: 90vh;
|
|
117
|
+
overflow-y: auto;
|
|
118
|
+
border-radius: 12px;
|
|
119
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
|
|
120
|
+
transform: translateY(20px);
|
|
121
|
+
transition: transform 0.3s ease;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.swarm-bug-modal.open .swarm-bug-form-container {
|
|
125
|
+
transform: translateY(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.swarm-bug-widget.dark .swarm-bug-form-container {
|
|
129
|
+
background: #1a1a1a;
|
|
130
|
+
color: #e0e0e0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.swarm-bug-widget.light .swarm-bug-form-container {
|
|
134
|
+
background: #fff;
|
|
135
|
+
color: #333;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.swarm-bug-header {
|
|
139
|
+
padding: 20px;
|
|
140
|
+
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
|
|
141
|
+
display: flex;
|
|
142
|
+
justify-content: space-between;
|
|
143
|
+
align-items: center;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.swarm-bug-header h3 {
|
|
147
|
+
margin: 0;
|
|
148
|
+
font-size: 18px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.swarm-bug-close {
|
|
152
|
+
background: none;
|
|
153
|
+
border: none;
|
|
154
|
+
font-size: 24px;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
padding: 0;
|
|
157
|
+
line-height: 1;
|
|
158
|
+
opacity: 0.7;
|
|
159
|
+
transition: opacity 0.2s;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.swarm-bug-widget.dark .swarm-bug-close { color: #fff; }
|
|
163
|
+
.swarm-bug-widget.light .swarm-bug-close { color: #333; }
|
|
164
|
+
|
|
165
|
+
.swarm-bug-close:hover { opacity: 1; }
|
|
166
|
+
|
|
167
|
+
.swarm-bug-form {
|
|
168
|
+
padding: 20px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.swarm-bug-field {
|
|
172
|
+
margin-bottom: 16px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.swarm-bug-field label {
|
|
176
|
+
display: block;
|
|
177
|
+
margin-bottom: 6px;
|
|
178
|
+
font-weight: 500;
|
|
179
|
+
font-size: 14px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.swarm-bug-field input,
|
|
183
|
+
.swarm-bug-field textarea {
|
|
184
|
+
width: 100%;
|
|
185
|
+
padding: 10px 12px;
|
|
186
|
+
border-radius: 6px;
|
|
187
|
+
font-size: 14px;
|
|
188
|
+
font-family: inherit;
|
|
189
|
+
transition: border-color 0.2s;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.swarm-bug-widget.dark .swarm-bug-field input,
|
|
193
|
+
.swarm-bug-widget.dark .swarm-bug-field textarea {
|
|
194
|
+
background: #2a2a2a;
|
|
195
|
+
border: 1px solid #444;
|
|
196
|
+
color: #e0e0e0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.swarm-bug-widget.light .swarm-bug-field input,
|
|
200
|
+
.swarm-bug-widget.light .swarm-bug-field textarea {
|
|
201
|
+
background: #f5f5f5;
|
|
202
|
+
border: 1px solid #ddd;
|
|
203
|
+
color: #333;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.swarm-bug-field input:focus,
|
|
207
|
+
.swarm-bug-field textarea:focus {
|
|
208
|
+
outline: none;
|
|
209
|
+
border-color: #00d4aa;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.swarm-bug-field textarea {
|
|
213
|
+
min-height: 100px;
|
|
214
|
+
resize: vertical;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.swarm-bug-errors-info {
|
|
218
|
+
padding: 10px 12px;
|
|
219
|
+
border-radius: 6px;
|
|
220
|
+
font-size: 12px;
|
|
221
|
+
margin-bottom: 16px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.swarm-bug-widget.dark .swarm-bug-errors-info {
|
|
225
|
+
background: #3a2a2a;
|
|
226
|
+
border: 1px solid #5a3a3a;
|
|
227
|
+
color: #ff9999;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.swarm-bug-widget.light .swarm-bug-errors-info {
|
|
231
|
+
background: #fff5f5;
|
|
232
|
+
border: 1px solid #fcc;
|
|
233
|
+
color: #c00;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.swarm-bug-submit {
|
|
237
|
+
width: 100%;
|
|
238
|
+
padding: 12px;
|
|
239
|
+
border: none;
|
|
240
|
+
border-radius: 6px;
|
|
241
|
+
background: #00d4aa;
|
|
242
|
+
color: #1a1a1a;
|
|
243
|
+
font-size: 14px;
|
|
244
|
+
font-weight: 600;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
transition: all 0.2s;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.swarm-bug-submit:hover {
|
|
250
|
+
background: #00ffcc;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.swarm-bug-submit:disabled {
|
|
254
|
+
opacity: 0.5;
|
|
255
|
+
cursor: not-allowed;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.swarm-bug-status {
|
|
259
|
+
padding: 12px;
|
|
260
|
+
border-radius: 6px;
|
|
261
|
+
margin-top: 16px;
|
|
262
|
+
text-align: center;
|
|
263
|
+
font-size: 14px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.swarm-bug-status.success {
|
|
267
|
+
background: #2d4a2d;
|
|
268
|
+
color: #6bcf7f;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.swarm-bug-status.error {
|
|
272
|
+
background: #4a2d2d;
|
|
273
|
+
color: #ff6b6b;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.swarm-bug-footer {
|
|
277
|
+
padding: 12px 20px;
|
|
278
|
+
border-top: 1px solid rgba(128, 128, 128, 0.2);
|
|
279
|
+
text-align: center;
|
|
280
|
+
font-size: 11px;
|
|
281
|
+
opacity: 0.6;
|
|
282
|
+
}
|
|
283
|
+
`;
|
|
284
|
+
|
|
285
|
+
// Inject styles
|
|
286
|
+
function injectStyles() {
|
|
287
|
+
const style = document.createElement('style');
|
|
288
|
+
style.textContent = STYLES;
|
|
289
|
+
document.head.appendChild(style);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Create widget HTML
|
|
293
|
+
function createWidget() {
|
|
294
|
+
const widget = document.createElement('div');
|
|
295
|
+
widget.className = `swarm-bug-widget ${config.position} ${config.theme}`;
|
|
296
|
+
widget.innerHTML = `
|
|
297
|
+
<button class="swarm-bug-button" aria-label="Report a bug">
|
|
298
|
+
<span>${config.buttonIcon}</span>
|
|
299
|
+
<span>${config.buttonText}</span>
|
|
300
|
+
</button>
|
|
301
|
+
|
|
302
|
+
<div class="swarm-bug-modal">
|
|
303
|
+
<div class="swarm-bug-overlay"></div>
|
|
304
|
+
<div class="swarm-bug-form-container">
|
|
305
|
+
<div class="swarm-bug-header">
|
|
306
|
+
<h3>${config.buttonIcon} Report a Bug</h3>
|
|
307
|
+
<button class="swarm-bug-close" aria-label="Close">×</button>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<form class="swarm-bug-form">
|
|
311
|
+
<div class="swarm-bug-field">
|
|
312
|
+
<label for="swarm-bug-description">What happened?</label>
|
|
313
|
+
<textarea id="swarm-bug-description" placeholder="Please describe what went wrong..." required></textarea>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<div class="swarm-bug-field">
|
|
317
|
+
<label for="swarm-bug-steps">Steps to reproduce (optional)</label>
|
|
318
|
+
<textarea id="swarm-bug-steps" placeholder="1. Go to...\n2. Click on...\n3. See error"></textarea>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div class="swarm-bug-errors-info" style="display: none;"></div>
|
|
322
|
+
|
|
323
|
+
<button type="submit" class="swarm-bug-submit">Submit Report</button>
|
|
324
|
+
|
|
325
|
+
<div class="swarm-bug-status" style="display: none;"></div>
|
|
326
|
+
</form>
|
|
327
|
+
|
|
328
|
+
<div class="swarm-bug-footer">
|
|
329
|
+
Powered by Swarm Tickets
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
`;
|
|
334
|
+
|
|
335
|
+
document.body.appendChild(widget);
|
|
336
|
+
return widget;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Set up error collection
|
|
340
|
+
function setupErrorCollection() {
|
|
341
|
+
if (!config.collectErrors) return;
|
|
342
|
+
|
|
343
|
+
const originalConsoleError = console.error;
|
|
344
|
+
console.error = function(...args) {
|
|
345
|
+
if (collectedErrors.length < config.maxErrors) {
|
|
346
|
+
collectedErrors.push({
|
|
347
|
+
type: 'console.error',
|
|
348
|
+
message: args.map(a => String(a)).join(' '),
|
|
349
|
+
timestamp: new Date().toISOString()
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
originalConsoleError.apply(console, args);
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
window.addEventListener('error', function(event) {
|
|
356
|
+
if (collectedErrors.length < config.maxErrors) {
|
|
357
|
+
collectedErrors.push({
|
|
358
|
+
type: 'window.error',
|
|
359
|
+
message: event.message,
|
|
360
|
+
filename: event.filename,
|
|
361
|
+
lineno: event.lineno,
|
|
362
|
+
colno: event.colno,
|
|
363
|
+
timestamp: new Date().toISOString()
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
window.addEventListener('unhandledrejection', function(event) {
|
|
369
|
+
if (collectedErrors.length < config.maxErrors) {
|
|
370
|
+
collectedErrors.push({
|
|
371
|
+
type: 'unhandledrejection',
|
|
372
|
+
message: String(event.reason),
|
|
373
|
+
timestamp: new Date().toISOString()
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Initialize widget
|
|
380
|
+
function init(userConfig = {}) {
|
|
381
|
+
config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
382
|
+
|
|
383
|
+
injectStyles();
|
|
384
|
+
const widget = createWidget();
|
|
385
|
+
setupErrorCollection();
|
|
386
|
+
|
|
387
|
+
const button = widget.querySelector('.swarm-bug-button');
|
|
388
|
+
const modal = widget.querySelector('.swarm-bug-modal');
|
|
389
|
+
const overlay = widget.querySelector('.swarm-bug-overlay');
|
|
390
|
+
const closeBtn = widget.querySelector('.swarm-bug-close');
|
|
391
|
+
const form = widget.querySelector('.swarm-bug-form');
|
|
392
|
+
const errorsInfo = widget.querySelector('.swarm-bug-errors-info');
|
|
393
|
+
const status = widget.querySelector('.swarm-bug-status');
|
|
394
|
+
|
|
395
|
+
function openModal() {
|
|
396
|
+
isOpen = true;
|
|
397
|
+
modal.classList.add('open');
|
|
398
|
+
|
|
399
|
+
// Show error count
|
|
400
|
+
if (collectedErrors.length > 0) {
|
|
401
|
+
errorsInfo.style.display = 'block';
|
|
402
|
+
errorsInfo.textContent = `📋 ${collectedErrors.length} error(s) captured from this session will be included.`;
|
|
403
|
+
} else {
|
|
404
|
+
errorsInfo.style.display = 'none';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
status.style.display = 'none';
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function closeModal() {
|
|
411
|
+
isOpen = false;
|
|
412
|
+
modal.classList.remove('open');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
button.addEventListener('click', openModal);
|
|
416
|
+
overlay.addEventListener('click', closeModal);
|
|
417
|
+
closeBtn.addEventListener('click', closeModal);
|
|
418
|
+
|
|
419
|
+
document.addEventListener('keydown', (e) => {
|
|
420
|
+
if (e.key === 'Escape' && isOpen) closeModal();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
form.addEventListener('submit', async (e) => {
|
|
424
|
+
e.preventDefault();
|
|
425
|
+
|
|
426
|
+
const submitBtn = form.querySelector('.swarm-bug-submit');
|
|
427
|
+
submitBtn.disabled = true;
|
|
428
|
+
submitBtn.textContent = 'Submitting...';
|
|
429
|
+
status.style.display = 'none';
|
|
430
|
+
|
|
431
|
+
const description = form.querySelector('#swarm-bug-description').value;
|
|
432
|
+
const steps = form.querySelector('#swarm-bug-steps').value;
|
|
433
|
+
|
|
434
|
+
const report = {
|
|
435
|
+
description: description + (steps ? '\n\nSteps to reproduce:\n' + steps : ''),
|
|
436
|
+
location: window.location.href,
|
|
437
|
+
clientError: collectedErrors.length > 0
|
|
438
|
+
? collectedErrors.map(e => `[${e.type}] ${e.message}`).join('\n')
|
|
439
|
+
: '',
|
|
440
|
+
userAgent: navigator.userAgent,
|
|
441
|
+
timestamp: new Date().toISOString(),
|
|
442
|
+
viewport: `${window.innerWidth}x${window.innerHeight}`
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const headers = {
|
|
447
|
+
'Content-Type': 'application/json'
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
if (config.apiKey) {
|
|
451
|
+
headers['X-API-Key'] = config.apiKey;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const response = await fetch(config.endpoint, {
|
|
455
|
+
method: 'POST',
|
|
456
|
+
headers,
|
|
457
|
+
body: JSON.stringify(report)
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const data = await response.json();
|
|
461
|
+
|
|
462
|
+
if (response.ok) {
|
|
463
|
+
status.className = 'swarm-bug-status success';
|
|
464
|
+
status.textContent = config.successMessage;
|
|
465
|
+
status.style.display = 'block';
|
|
466
|
+
form.reset();
|
|
467
|
+
collectedErrors = []; // Clear errors after successful submit
|
|
468
|
+
|
|
469
|
+
setTimeout(() => closeModal(), 2000);
|
|
470
|
+
} else if (response.status === 429) {
|
|
471
|
+
status.className = 'swarm-bug-status error';
|
|
472
|
+
status.textContent = config.rateLimitMessage;
|
|
473
|
+
status.style.display = 'block';
|
|
474
|
+
} else {
|
|
475
|
+
throw new Error(data.error || 'Unknown error');
|
|
476
|
+
}
|
|
477
|
+
} catch (error) {
|
|
478
|
+
status.className = 'swarm-bug-status error';
|
|
479
|
+
status.textContent = config.errorMessage;
|
|
480
|
+
status.style.display = 'block';
|
|
481
|
+
console.error('Bug report submission failed:', error);
|
|
482
|
+
} finally {
|
|
483
|
+
submitBtn.disabled = false;
|
|
484
|
+
submitBtn.textContent = 'Submit Report';
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Auto-initialize from script tag attributes
|
|
490
|
+
function autoInit() {
|
|
491
|
+
const script = document.currentScript || document.querySelector('script[data-endpoint]');
|
|
492
|
+
if (!script) return;
|
|
493
|
+
|
|
494
|
+
const attrs = {
|
|
495
|
+
endpoint: script.getAttribute('data-endpoint'),
|
|
496
|
+
apiKey: script.getAttribute('data-api-key'),
|
|
497
|
+
position: script.getAttribute('data-position'),
|
|
498
|
+
theme: script.getAttribute('data-theme'),
|
|
499
|
+
buttonText: script.getAttribute('data-button-text'),
|
|
500
|
+
buttonIcon: script.getAttribute('data-button-icon')
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// Remove null/undefined values
|
|
504
|
+
Object.keys(attrs).forEach(key => {
|
|
505
|
+
if (attrs[key] === null || attrs[key] === undefined) {
|
|
506
|
+
delete attrs[key];
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
if (Object.keys(attrs).length > 0) {
|
|
511
|
+
if (document.readyState === 'loading') {
|
|
512
|
+
document.addEventListener('DOMContentLoaded', () => init(attrs));
|
|
513
|
+
} else {
|
|
514
|
+
init(attrs);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Export API
|
|
520
|
+
window.SwarmBugReport = {
|
|
521
|
+
init,
|
|
522
|
+
open: () => {
|
|
523
|
+
const modal = document.querySelector('.swarm-bug-modal');
|
|
524
|
+
if (modal) modal.classList.add('open');
|
|
525
|
+
},
|
|
526
|
+
close: () => {
|
|
527
|
+
const modal = document.querySelector('.swarm-bug-modal');
|
|
528
|
+
if (modal) modal.classList.remove('open');
|
|
529
|
+
},
|
|
530
|
+
getErrors: () => [...collectedErrors],
|
|
531
|
+
clearErrors: () => { collectedErrors = []; }
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Auto-initialize
|
|
535
|
+
autoInit();
|
|
536
|
+
})();
|