@checkflow/sdk 1.1.4 → 1.1.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.
@@ -0,0 +1,1737 @@
1
+ // src/annotation/types.ts
2
+ var DEFAULT_STYLE = {
3
+ strokeColor: "#FF3B30",
4
+ strokeWidth: 3,
5
+ fillColor: "transparent",
6
+ opacity: 1,
7
+ fontSize: 16,
8
+ fontFamily: "system-ui, -apple-system, sans-serif"
9
+ };
10
+ var HIGHLIGHT_STYLE = {
11
+ strokeColor: "transparent",
12
+ fillColor: "#FFEB3B",
13
+ opacity: 0.4
14
+ };
15
+ var BLUR_STYLE = {
16
+ strokeColor: "#666666",
17
+ fillColor: "#666666",
18
+ opacity: 0.8
19
+ };
20
+ var COLOR_PALETTE = [
21
+ "#FF3B30",
22
+ // Red
23
+ "#FF9500",
24
+ // Orange
25
+ "#FFCC00",
26
+ // Yellow
27
+ "#34C759",
28
+ // Green
29
+ "#007AFF",
30
+ // Blue
31
+ "#5856D6",
32
+ // Purple
33
+ "#AF52DE",
34
+ // Pink
35
+ "#000000",
36
+ // Black
37
+ "#FFFFFF"
38
+ // White
39
+ ];
40
+ var STROKE_WIDTHS = [1, 2, 3, 5, 8];
41
+
42
+ // src/annotation/toolbar.ts
43
+ var TOOL_ICONS = {
44
+ select: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="M13 13l6 6"/></svg>`,
45
+ rectangle: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`,
46
+ ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
47
+ arrow: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>`,
48
+ line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
49
+ highlight: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>`,
50
+ blur: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3 .5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm15 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-11 10c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-17c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 5.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm8 .5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3 8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM14 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-4-12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0 8.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm4-4.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-4c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"/></svg>`,
51
+ text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
52
+ freehand: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>`
53
+ };
54
+ var TOOL_LABELS = {
55
+ select: "S\xE9lection",
56
+ rectangle: "Rectangle",
57
+ ellipse: "Ellipse",
58
+ arrow: "Fl\xE8che",
59
+ line: "Ligne",
60
+ highlight: "Surbrillance",
61
+ blur: "Floutage",
62
+ text: "Texte",
63
+ freehand: "Dessin libre"
64
+ };
65
+ var AnnotationToolbar = class {
66
+ constructor(config) {
67
+ this.colorPicker = null;
68
+ this.strokePicker = null;
69
+ this.config = config;
70
+ this.element = this.createToolbar();
71
+ }
72
+ getElement() {
73
+ return this.element;
74
+ }
75
+ setActiveTool(tool) {
76
+ this.config.activeTool = tool;
77
+ this.updateActiveState();
78
+ }
79
+ setStyle(style) {
80
+ this.config.style = style;
81
+ this.updateStyleDisplay();
82
+ }
83
+ createToolbar() {
84
+ const toolbar = document.createElement("div");
85
+ toolbar.className = "cf-toolbar";
86
+ const toolsSection = document.createElement("div");
87
+ toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
88
+ for (const tool of this.config.tools) {
89
+ const button = this.createToolButton(tool);
90
+ toolsSection.appendChild(button);
91
+ }
92
+ const separator1 = document.createElement("div");
93
+ separator1.className = "cf-toolbar-separator";
94
+ const styleSection = document.createElement("div");
95
+ styleSection.className = "cf-toolbar-section cf-toolbar-style";
96
+ const colorBtn = document.createElement("button");
97
+ colorBtn.className = "cf-toolbar-btn cf-color-btn";
98
+ colorBtn.title = "Couleur";
99
+ colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
100
+ colorBtn.addEventListener("click", () => this.toggleColorPicker());
101
+ styleSection.appendChild(colorBtn);
102
+ const strokeBtn = document.createElement("button");
103
+ strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
104
+ strokeBtn.title = "\xC9paisseur";
105
+ strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
106
+ strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
107
+ styleSection.appendChild(strokeBtn);
108
+ const separator2 = document.createElement("div");
109
+ separator2.className = "cf-toolbar-separator";
110
+ const actionsSection = document.createElement("div");
111
+ actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
112
+ const undoBtn = document.createElement("button");
113
+ undoBtn.className = "cf-toolbar-btn";
114
+ undoBtn.title = "Annuler (Ctrl+Z)";
115
+ undoBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"/></svg>`;
116
+ undoBtn.addEventListener("click", () => this.config.onUndo());
117
+ actionsSection.appendChild(undoBtn);
118
+ const clearBtn = document.createElement("button");
119
+ clearBtn.className = "cf-toolbar-btn";
120
+ clearBtn.title = "Tout effacer";
121
+ clearBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>`;
122
+ clearBtn.addEventListener("click", () => this.config.onClear());
123
+ actionsSection.appendChild(clearBtn);
124
+ toolbar.appendChild(toolsSection);
125
+ toolbar.appendChild(separator1);
126
+ toolbar.appendChild(styleSection);
127
+ toolbar.appendChild(separator2);
128
+ toolbar.appendChild(actionsSection);
129
+ return toolbar;
130
+ }
131
+ createToolButton(tool) {
132
+ const button = document.createElement("button");
133
+ button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
134
+ button.dataset.tool = tool;
135
+ button.title = TOOL_LABELS[tool];
136
+ button.innerHTML = TOOL_ICONS[tool];
137
+ button.addEventListener("click", () => {
138
+ this.config.onToolChange(tool);
139
+ this.setActiveTool(tool);
140
+ });
141
+ return button;
142
+ }
143
+ updateActiveState() {
144
+ const buttons = this.element.querySelectorAll(".cf-tool-btn");
145
+ buttons.forEach((btn) => {
146
+ const button = btn;
147
+ button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
148
+ });
149
+ }
150
+ updateStyleDisplay() {
151
+ const colorPreview = this.element.querySelector(".cf-color-preview");
152
+ if (colorPreview) {
153
+ colorPreview.style.background = this.config.style.strokeColor;
154
+ }
155
+ const strokePreview = this.element.querySelector(".cf-stroke-preview");
156
+ if (strokePreview) {
157
+ strokePreview.textContent = `${this.config.style.strokeWidth}px`;
158
+ }
159
+ }
160
+ toggleColorPicker() {
161
+ if (this.colorPicker) {
162
+ this.colorPicker.remove();
163
+ this.colorPicker = null;
164
+ return;
165
+ }
166
+ if (this.strokePicker) {
167
+ this.strokePicker.remove();
168
+ this.strokePicker = null;
169
+ }
170
+ this.colorPicker = document.createElement("div");
171
+ this.colorPicker.className = "cf-picker cf-color-picker";
172
+ for (const color of COLOR_PALETTE) {
173
+ const swatch = document.createElement("button");
174
+ swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
175
+ swatch.style.background = color;
176
+ swatch.addEventListener("click", () => {
177
+ this.config.onStyleChange({ strokeColor: color });
178
+ this.setStyle({ ...this.config.style, strokeColor: color });
179
+ this.colorPicker?.remove();
180
+ this.colorPicker = null;
181
+ });
182
+ this.colorPicker.appendChild(swatch);
183
+ }
184
+ const colorBtn = this.element.querySelector(".cf-color-btn");
185
+ colorBtn?.appendChild(this.colorPicker);
186
+ }
187
+ toggleStrokePicker() {
188
+ if (this.strokePicker) {
189
+ this.strokePicker.remove();
190
+ this.strokePicker = null;
191
+ return;
192
+ }
193
+ if (this.colorPicker) {
194
+ this.colorPicker.remove();
195
+ this.colorPicker = null;
196
+ }
197
+ this.strokePicker = document.createElement("div");
198
+ this.strokePicker.className = "cf-picker cf-stroke-picker";
199
+ for (const width of STROKE_WIDTHS) {
200
+ const option = document.createElement("button");
201
+ option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
202
+ option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
203
+ option.addEventListener("click", () => {
204
+ this.config.onStyleChange({ strokeWidth: width });
205
+ this.setStyle({ ...this.config.style, strokeWidth: width });
206
+ this.strokePicker?.remove();
207
+ this.strokePicker = null;
208
+ });
209
+ this.strokePicker.appendChild(option);
210
+ }
211
+ const strokeBtn = this.element.querySelector(".cf-stroke-btn");
212
+ strokeBtn?.appendChild(this.strokePicker);
213
+ }
214
+ };
215
+
216
+ // src/annotation/styles.ts
217
+ var STYLES = `
218
+ /* Annotation Overlay */
219
+ .cf-annotation-overlay {
220
+ position: fixed;
221
+ top: 0;
222
+ left: 0;
223
+ right: 0;
224
+ bottom: 0;
225
+ z-index: 999999;
226
+ background: rgba(0, 0, 0, 0.85);
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ animation: cf-fade-in 0.2s ease-out;
231
+ }
232
+
233
+ @keyframes cf-fade-in {
234
+ from { opacity: 0; }
235
+ to { opacity: 1; }
236
+ }
237
+
238
+ /* Editor Wrapper */
239
+ .cf-annotation-wrapper {
240
+ display: flex;
241
+ flex-direction: column;
242
+ max-width: 95vw;
243
+ max-height: 95vh;
244
+ background: #1a1a1a;
245
+ border-radius: 12px;
246
+ overflow: hidden;
247
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
248
+ }
249
+
250
+ /* Canvas Container */
251
+ .cf-annotation-canvas-container {
252
+ flex: 1;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ overflow: auto;
257
+ padding: 16px;
258
+ background: #0d0d0d;
259
+ }
260
+
261
+ .cf-annotation-canvas {
262
+ max-width: 100%;
263
+ max-height: calc(95vh - 140px);
264
+ border-radius: 4px;
265
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
266
+ cursor: crosshair;
267
+ }
268
+
269
+ /* Toolbar */
270
+ .cf-toolbar {
271
+ display: flex;
272
+ align-items: center;
273
+ gap: 8px;
274
+ padding: 12px 16px;
275
+ background: #2a2a2a;
276
+ border-bottom: 1px solid #3a3a3a;
277
+ }
278
+
279
+ .cf-toolbar-section {
280
+ display: flex;
281
+ align-items: center;
282
+ gap: 4px;
283
+ }
284
+
285
+ .cf-toolbar-separator {
286
+ width: 1px;
287
+ height: 24px;
288
+ background: #4a4a4a;
289
+ margin: 0 8px;
290
+ }
291
+
292
+ .cf-toolbar-btn {
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ width: 36px;
297
+ height: 36px;
298
+ padding: 0;
299
+ border: none;
300
+ border-radius: 8px;
301
+ background: transparent;
302
+ color: #999;
303
+ cursor: pointer;
304
+ transition: all 0.15s ease;
305
+ }
306
+
307
+ .cf-toolbar-btn:hover {
308
+ background: #3a3a3a;
309
+ color: #fff;
310
+ }
311
+
312
+ .cf-toolbar-btn.active {
313
+ background: #007AFF;
314
+ color: #fff;
315
+ }
316
+
317
+ .cf-toolbar-btn svg {
318
+ width: 20px;
319
+ height: 20px;
320
+ }
321
+
322
+ /* Color Button */
323
+ .cf-color-btn {
324
+ position: relative;
325
+ }
326
+
327
+ .cf-color-preview {
328
+ width: 20px;
329
+ height: 20px;
330
+ border-radius: 50%;
331
+ border: 2px solid #fff;
332
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
333
+ }
334
+
335
+ /* Stroke Button */
336
+ .cf-stroke-btn {
337
+ width: auto;
338
+ padding: 0 12px;
339
+ }
340
+
341
+ .cf-stroke-preview {
342
+ font-size: 12px;
343
+ font-weight: 500;
344
+ color: inherit;
345
+ }
346
+
347
+ /* Pickers */
348
+ .cf-picker {
349
+ position: absolute;
350
+ top: 100%;
351
+ left: 50%;
352
+ transform: translateX(-50%);
353
+ margin-top: 8px;
354
+ padding: 8px;
355
+ background: #2a2a2a;
356
+ border-radius: 8px;
357
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
358
+ z-index: 10;
359
+ }
360
+
361
+ .cf-color-picker {
362
+ display: grid;
363
+ grid-template-columns: repeat(5, 1fr);
364
+ gap: 4px;
365
+ width: 140px;
366
+ }
367
+
368
+ .cf-color-swatch {
369
+ width: 24px;
370
+ height: 24px;
371
+ border-radius: 50%;
372
+ border: 2px solid transparent;
373
+ cursor: pointer;
374
+ transition: transform 0.15s ease;
375
+ }
376
+
377
+ .cf-color-swatch:hover {
378
+ transform: scale(1.15);
379
+ }
380
+
381
+ .cf-color-swatch.active {
382
+ border-color: #fff;
383
+ }
384
+
385
+ .cf-stroke-picker {
386
+ display: flex;
387
+ flex-direction: column;
388
+ gap: 4px;
389
+ min-width: 80px;
390
+ }
391
+
392
+ .cf-stroke-option {
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 8px;
396
+ padding: 6px 10px;
397
+ border: none;
398
+ border-radius: 4px;
399
+ background: transparent;
400
+ color: #999;
401
+ font-size: 12px;
402
+ cursor: pointer;
403
+ transition: all 0.15s ease;
404
+ }
405
+
406
+ .cf-stroke-option:hover {
407
+ background: #3a3a3a;
408
+ color: #fff;
409
+ }
410
+
411
+ .cf-stroke-option.active {
412
+ background: #007AFF;
413
+ color: #fff;
414
+ }
415
+
416
+ .cf-stroke-option span {
417
+ width: 24px;
418
+ background: currentColor;
419
+ border-radius: 2px;
420
+ }
421
+
422
+ /* Action Buttons */
423
+ .cf-annotation-actions {
424
+ display: flex;
425
+ justify-content: flex-end;
426
+ gap: 12px;
427
+ padding: 12px 16px;
428
+ background: #2a2a2a;
429
+ border-top: 1px solid #3a3a3a;
430
+ }
431
+
432
+ .cf-btn {
433
+ display: flex;
434
+ align-items: center;
435
+ justify-content: center;
436
+ padding: 10px 20px;
437
+ border: none;
438
+ border-radius: 8px;
439
+ font-size: 14px;
440
+ font-weight: 500;
441
+ cursor: pointer;
442
+ transition: all 0.15s ease;
443
+ }
444
+
445
+ .cf-btn-primary {
446
+ background: #007AFF;
447
+ color: #fff;
448
+ }
449
+
450
+ .cf-btn-primary:hover {
451
+ background: #0066DD;
452
+ }
453
+
454
+ .cf-btn-secondary {
455
+ background: #3a3a3a;
456
+ color: #fff;
457
+ }
458
+
459
+ .cf-btn-secondary:hover {
460
+ background: #4a4a4a;
461
+ }
462
+
463
+ /* Text Input */
464
+ .cf-annotation-text-input {
465
+ position: fixed;
466
+ z-index: 1000000;
467
+ padding: 4px 8px;
468
+ border: 2px solid #007AFF;
469
+ border-radius: 4px;
470
+ background: rgba(255, 255, 255, 0.95);
471
+ font-size: 16px;
472
+ font-family: system-ui, -apple-system, sans-serif;
473
+ outline: none;
474
+ min-width: 150px;
475
+ }
476
+
477
+ /* Tool-specific cursors */
478
+ .cf-annotation-canvas[data-tool="select"] { cursor: default; }
479
+ .cf-annotation-canvas[data-tool="rectangle"] { cursor: crosshair; }
480
+ .cf-annotation-canvas[data-tool="ellipse"] { cursor: crosshair; }
481
+ .cf-annotation-canvas[data-tool="arrow"] { cursor: crosshair; }
482
+ .cf-annotation-canvas[data-tool="line"] { cursor: crosshair; }
483
+ .cf-annotation-canvas[data-tool="highlight"] { cursor: crosshair; }
484
+ .cf-annotation-canvas[data-tool="blur"] { cursor: crosshair; }
485
+ .cf-annotation-canvas[data-tool="text"] { cursor: text; }
486
+ .cf-annotation-canvas[data-tool="freehand"] { cursor: crosshair; }
487
+
488
+ /* Responsive */
489
+ @media (max-width: 768px) {
490
+ .cf-toolbar {
491
+ flex-wrap: wrap;
492
+ justify-content: center;
493
+ }
494
+
495
+ .cf-toolbar-separator {
496
+ display: none;
497
+ }
498
+
499
+ .cf-annotation-actions {
500
+ justify-content: stretch;
501
+ }
502
+
503
+ .cf-annotation-actions .cf-btn {
504
+ flex: 1;
505
+ }
506
+ }
507
+ `;
508
+ var stylesInjected = false;
509
+ function injectAnnotationStyles() {
510
+ if (stylesInjected) return;
511
+ const styleElement = document.createElement("style");
512
+ styleElement.id = "cf-annotation-styles";
513
+ styleElement.textContent = STYLES;
514
+ document.head.appendChild(styleElement);
515
+ stylesInjected = true;
516
+ }
517
+
518
+ // src/annotation/editor.ts
519
+ var AnnotationEditor = class {
520
+ constructor(config) {
521
+ this.container = null;
522
+ this.canvas = null;
523
+ this.ctx = null;
524
+ this.toolbar = null;
525
+ this.annotations = [];
526
+ this.backgroundImage = null;
527
+ this.state = {
528
+ activeTool: "rectangle",
529
+ style: { ...DEFAULT_STYLE },
530
+ isDrawing: false,
531
+ currentAnnotation: null
532
+ };
533
+ this.startPoint = null;
534
+ this.freehandPoints = [];
535
+ this.textInput = null;
536
+ this.config = {
537
+ ...config,
538
+ tools: config.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
539
+ defaultStyle: config.defaultStyle || DEFAULT_STYLE
540
+ };
541
+ this.state.style = { ...this.config.defaultStyle };
542
+ }
543
+ /**
544
+ * Open the annotation editor with a screenshot
545
+ */
546
+ async open(screenshotDataUrl2) {
547
+ injectAnnotationStyles();
548
+ this.backgroundImage = await this.loadImage(screenshotDataUrl2);
549
+ this.createEditorUI();
550
+ this.setupEventListeners();
551
+ this.render();
552
+ }
553
+ /**
554
+ * Close the editor
555
+ */
556
+ close() {
557
+ if (this.container) {
558
+ this.container.remove();
559
+ this.container = null;
560
+ }
561
+ this.canvas = null;
562
+ this.ctx = null;
563
+ this.toolbar = null;
564
+ this.annotations = [];
565
+ this.backgroundImage = null;
566
+ }
567
+ /**
568
+ * Get the annotated image as data URL
569
+ */
570
+ getAnnotatedImage() {
571
+ if (!this.canvas || !this.ctx) return "";
572
+ const tempCanvas = document.createElement("canvas");
573
+ tempCanvas.width = this.canvas.width;
574
+ tempCanvas.height = this.canvas.height;
575
+ const tempCtx = tempCanvas.getContext("2d");
576
+ if (this.backgroundImage) {
577
+ tempCtx.drawImage(this.backgroundImage, 0, 0);
578
+ }
579
+ this.drawAnnotations(tempCtx);
580
+ return tempCanvas.toDataURL("image/png");
581
+ }
582
+ /**
583
+ * Get annotations data
584
+ */
585
+ getAnnotations() {
586
+ return [...this.annotations];
587
+ }
588
+ // Private methods
589
+ loadImage(src) {
590
+ return new Promise((resolve, reject) => {
591
+ const img = new Image();
592
+ img.onload = () => resolve(img);
593
+ img.onerror = reject;
594
+ img.src = src;
595
+ });
596
+ }
597
+ createEditorUI() {
598
+ this.container = document.createElement("div");
599
+ this.container.className = "cf-annotation-overlay";
600
+ const wrapper = document.createElement("div");
601
+ wrapper.className = "cf-annotation-wrapper";
602
+ const canvasContainer = document.createElement("div");
603
+ canvasContainer.className = "cf-annotation-canvas-container";
604
+ this.canvas = document.createElement("canvas");
605
+ this.canvas.className = "cf-annotation-canvas";
606
+ this.canvas.width = this.backgroundImage?.width || 1920;
607
+ this.canvas.height = this.backgroundImage?.height || 1080;
608
+ this.ctx = this.canvas.getContext("2d");
609
+ canvasContainer.appendChild(this.canvas);
610
+ this.toolbar = new AnnotationToolbar({
611
+ tools: this.config.tools,
612
+ activeTool: this.state.activeTool,
613
+ style: this.state.style,
614
+ onToolChange: (tool) => this.setActiveTool(tool),
615
+ onStyleChange: (style) => this.setStyle(style),
616
+ onUndo: () => this.undo(),
617
+ onClear: () => this.clearAll(),
618
+ onSave: () => this.save(),
619
+ onCancel: () => this.cancel()
620
+ });
621
+ const actions = document.createElement("div");
622
+ actions.className = "cf-annotation-actions";
623
+ actions.innerHTML = `
624
+ <button class="cf-btn cf-btn-secondary" data-action="cancel">Annuler</button>
625
+ <button class="cf-btn cf-btn-primary" data-action="save">Enregistrer</button>
626
+ `;
627
+ wrapper.appendChild(this.toolbar.getElement());
628
+ wrapper.appendChild(canvasContainer);
629
+ wrapper.appendChild(actions);
630
+ this.container.appendChild(wrapper);
631
+ document.body.appendChild(this.container);
632
+ actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
633
+ actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
634
+ }
635
+ setupEventListeners() {
636
+ if (!this.canvas) return;
637
+ this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
638
+ this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
639
+ this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
640
+ this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
641
+ this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
642
+ this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
643
+ this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
644
+ document.addEventListener("keydown", this.handleKeyDown.bind(this));
645
+ }
646
+ handleMouseDown(e) {
647
+ const point = this.getCanvasPoint(e);
648
+ this.startDrawing(point);
649
+ }
650
+ handleMouseMove(e) {
651
+ const point = this.getCanvasPoint(e);
652
+ this.continueDrawing(point);
653
+ }
654
+ handleMouseUp(_e) {
655
+ this.finishDrawing();
656
+ }
657
+ handleTouchStart(e) {
658
+ e.preventDefault();
659
+ const touch = e.touches[0];
660
+ const point = this.getCanvasPointFromTouch(touch);
661
+ this.startDrawing(point);
662
+ }
663
+ handleTouchMove(e) {
664
+ e.preventDefault();
665
+ const touch = e.touches[0];
666
+ const point = this.getCanvasPointFromTouch(touch);
667
+ this.continueDrawing(point);
668
+ }
669
+ handleTouchEnd(e) {
670
+ e.preventDefault();
671
+ this.finishDrawing();
672
+ }
673
+ handleKeyDown(e) {
674
+ if ((e.ctrlKey || e.metaKey) && e.key === "z") {
675
+ e.preventDefault();
676
+ this.undo();
677
+ }
678
+ if (e.key === "Escape") {
679
+ if (this.state.isDrawing) {
680
+ this.state.isDrawing = false;
681
+ this.state.currentAnnotation = null;
682
+ this.render();
683
+ } else {
684
+ this.cancel();
685
+ }
686
+ }
687
+ if (e.key === "Enter" && !this.state.isDrawing) {
688
+ this.save();
689
+ }
690
+ }
691
+ getCanvasPoint(e) {
692
+ const rect = this.canvas.getBoundingClientRect();
693
+ const scaleX = this.canvas.width / rect.width;
694
+ const scaleY = this.canvas.height / rect.height;
695
+ return {
696
+ x: (e.clientX - rect.left) * scaleX,
697
+ y: (e.clientY - rect.top) * scaleY
698
+ };
699
+ }
700
+ getCanvasPointFromTouch(touch) {
701
+ const rect = this.canvas.getBoundingClientRect();
702
+ const scaleX = this.canvas.width / rect.width;
703
+ const scaleY = this.canvas.height / rect.height;
704
+ return {
705
+ x: (touch.clientX - rect.left) * scaleX,
706
+ y: (touch.clientY - rect.top) * scaleY
707
+ };
708
+ }
709
+ startDrawing(point) {
710
+ if (this.state.activeTool === "select") return;
711
+ this.state.isDrawing = true;
712
+ this.startPoint = point;
713
+ if (this.state.activeTool === "text") {
714
+ this.showTextInput(point);
715
+ return;
716
+ }
717
+ if (this.state.activeTool === "freehand") {
718
+ this.freehandPoints = [point];
719
+ }
720
+ }
721
+ continueDrawing(point) {
722
+ if (!this.state.isDrawing || !this.startPoint) return;
723
+ if (this.state.activeTool === "freehand") {
724
+ this.freehandPoints.push(point);
725
+ }
726
+ this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
727
+ this.render();
728
+ }
729
+ finishDrawing() {
730
+ if (!this.state.isDrawing || !this.state.currentAnnotation) {
731
+ this.state.isDrawing = false;
732
+ return;
733
+ }
734
+ if (this.isValidAnnotation(this.state.currentAnnotation)) {
735
+ this.annotations.push(this.state.currentAnnotation);
736
+ }
737
+ this.state.isDrawing = false;
738
+ this.state.currentAnnotation = null;
739
+ this.startPoint = null;
740
+ this.freehandPoints = [];
741
+ this.render();
742
+ }
743
+ isValidAnnotation(annotation) {
744
+ switch (annotation.type) {
745
+ case "rectangle":
746
+ case "ellipse":
747
+ case "highlight":
748
+ case "blur":
749
+ const bounds = annotation.bounds;
750
+ return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
751
+ case "arrow":
752
+ case "line":
753
+ const start = annotation.start;
754
+ const end = annotation.end;
755
+ const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
756
+ return dist > 10;
757
+ case "freehand":
758
+ return annotation.points.length > 2;
759
+ case "text":
760
+ return !!annotation.text?.trim();
761
+ default:
762
+ return true;
763
+ }
764
+ }
765
+ createAnnotation(start, end) {
766
+ const id = this.generateId();
767
+ const baseStyle = { ...this.state.style };
768
+ switch (this.state.activeTool) {
769
+ case "rectangle":
770
+ return {
771
+ id,
772
+ type: "rectangle",
773
+ bounds: this.createBounds(start, end),
774
+ style: baseStyle,
775
+ timestamp: Date.now()
776
+ };
777
+ case "ellipse":
778
+ return {
779
+ id,
780
+ type: "ellipse",
781
+ bounds: this.createBounds(start, end),
782
+ style: baseStyle,
783
+ timestamp: Date.now()
784
+ };
785
+ case "arrow":
786
+ return {
787
+ id,
788
+ type: "arrow",
789
+ start: { ...start },
790
+ end: { ...end },
791
+ style: baseStyle,
792
+ timestamp: Date.now()
793
+ };
794
+ case "line":
795
+ return {
796
+ id,
797
+ type: "line",
798
+ start: { ...start },
799
+ end: { ...end },
800
+ style: baseStyle,
801
+ timestamp: Date.now()
802
+ };
803
+ case "highlight":
804
+ return {
805
+ id,
806
+ type: "highlight",
807
+ bounds: this.createBounds(start, end),
808
+ style: { ...baseStyle, ...HIGHLIGHT_STYLE },
809
+ timestamp: Date.now()
810
+ };
811
+ case "blur":
812
+ return {
813
+ id,
814
+ type: "blur",
815
+ bounds: this.createBounds(start, end),
816
+ style: { ...baseStyle, ...BLUR_STYLE },
817
+ blurAmount: 10,
818
+ timestamp: Date.now()
819
+ };
820
+ case "freehand":
821
+ return {
822
+ id,
823
+ type: "freehand",
824
+ points: [...this.freehandPoints],
825
+ style: baseStyle,
826
+ timestamp: Date.now()
827
+ };
828
+ default:
829
+ return {
830
+ id,
831
+ type: "rectangle",
832
+ bounds: this.createBounds(start, end),
833
+ style: baseStyle,
834
+ timestamp: Date.now()
835
+ };
836
+ }
837
+ }
838
+ createBounds(start, end) {
839
+ return {
840
+ x: Math.min(start.x, end.x),
841
+ y: Math.min(start.y, end.y),
842
+ width: Math.abs(end.x - start.x),
843
+ height: Math.abs(end.y - start.y)
844
+ };
845
+ }
846
+ showTextInput(point) {
847
+ if (this.textInput) {
848
+ this.textInput.remove();
849
+ }
850
+ const rect = this.canvas.getBoundingClientRect();
851
+ const scaleX = rect.width / this.canvas.width;
852
+ const scaleY = rect.height / this.canvas.height;
853
+ this.textInput = document.createElement("input");
854
+ this.textInput.type = "text";
855
+ this.textInput.className = "cf-annotation-text-input";
856
+ this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
857
+ this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
858
+ this.textInput.style.color = this.state.style.strokeColor;
859
+ this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
860
+ this.textInput.placeholder = "Tapez votre texte...";
861
+ document.body.appendChild(this.textInput);
862
+ this.textInput.focus();
863
+ const handleTextSubmit = () => {
864
+ if (this.textInput && this.textInput.value.trim()) {
865
+ const textAnnotation = {
866
+ id: this.generateId(),
867
+ type: "text",
868
+ position: point,
869
+ text: this.textInput.value.trim(),
870
+ style: { ...this.state.style },
871
+ timestamp: Date.now()
872
+ };
873
+ this.annotations.push(textAnnotation);
874
+ this.render();
875
+ }
876
+ this.textInput?.remove();
877
+ this.textInput = null;
878
+ this.state.isDrawing = false;
879
+ };
880
+ this.textInput.addEventListener("blur", handleTextSubmit);
881
+ this.textInput.addEventListener("keydown", (e) => {
882
+ if (e.key === "Enter") {
883
+ handleTextSubmit();
884
+ } else if (e.key === "Escape") {
885
+ this.textInput?.remove();
886
+ this.textInput = null;
887
+ this.state.isDrawing = false;
888
+ }
889
+ });
890
+ }
891
+ generateId() {
892
+ return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
893
+ }
894
+ setActiveTool(tool) {
895
+ this.state.activeTool = tool;
896
+ this.toolbar?.setActiveTool(tool);
897
+ if (this.canvas) {
898
+ this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
899
+ }
900
+ }
901
+ setStyle(style) {
902
+ this.state.style = { ...this.state.style, ...style };
903
+ this.toolbar?.setStyle(this.state.style);
904
+ }
905
+ undo() {
906
+ if (this.annotations.length > 0) {
907
+ this.annotations.pop();
908
+ this.render();
909
+ }
910
+ }
911
+ clearAll() {
912
+ this.annotations = [];
913
+ this.render();
914
+ }
915
+ save() {
916
+ const imageData = this.getAnnotatedImage();
917
+ this.config.onSave?.(this.annotations, imageData);
918
+ this.close();
919
+ }
920
+ cancel() {
921
+ this.config.onCancel?.();
922
+ this.close();
923
+ }
924
+ render() {
925
+ if (!this.ctx || !this.canvas) return;
926
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
927
+ if (this.backgroundImage) {
928
+ this.ctx.drawImage(this.backgroundImage, 0, 0);
929
+ }
930
+ this.drawAnnotations(this.ctx);
931
+ if (this.state.currentAnnotation) {
932
+ this.drawAnnotation(this.ctx, this.state.currentAnnotation);
933
+ }
934
+ }
935
+ drawAnnotations(ctx) {
936
+ for (const annotation of this.annotations) {
937
+ this.drawAnnotation(ctx, annotation);
938
+ }
939
+ }
940
+ drawAnnotation(ctx, annotation) {
941
+ ctx.save();
942
+ ctx.globalAlpha = annotation.style.opacity;
943
+ ctx.strokeStyle = annotation.style.strokeColor;
944
+ ctx.fillStyle = annotation.style.fillColor;
945
+ ctx.lineWidth = annotation.style.strokeWidth;
946
+ ctx.lineCap = "round";
947
+ ctx.lineJoin = "round";
948
+ switch (annotation.type) {
949
+ case "rectangle":
950
+ this.drawRectangle(ctx, annotation.bounds, annotation.style);
951
+ break;
952
+ case "ellipse":
953
+ this.drawEllipse(ctx, annotation.bounds, annotation.style);
954
+ break;
955
+ case "arrow":
956
+ this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
957
+ break;
958
+ case "line":
959
+ this.drawLine(ctx, annotation.start, annotation.end);
960
+ break;
961
+ case "highlight":
962
+ this.drawHighlight(ctx, annotation.bounds, annotation.style);
963
+ break;
964
+ case "blur":
965
+ this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
966
+ break;
967
+ case "text":
968
+ this.drawText(ctx, annotation.position, annotation.text, annotation.style);
969
+ break;
970
+ case "freehand":
971
+ this.drawFreehand(ctx, annotation.points);
972
+ break;
973
+ }
974
+ ctx.restore();
975
+ }
976
+ drawRectangle(ctx, bounds, style) {
977
+ if (style.fillColor !== "transparent") {
978
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
979
+ }
980
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
981
+ }
982
+ drawEllipse(ctx, bounds, style) {
983
+ const centerX = bounds.x + bounds.width / 2;
984
+ const centerY = bounds.y + bounds.height / 2;
985
+ const radiusX = bounds.width / 2;
986
+ const radiusY = bounds.height / 2;
987
+ ctx.beginPath();
988
+ ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
989
+ if (style.fillColor !== "transparent") {
990
+ ctx.fill();
991
+ }
992
+ ctx.stroke();
993
+ }
994
+ drawArrow(ctx, start, end, style) {
995
+ const headLength = 15 + style.strokeWidth * 2;
996
+ const angle = Math.atan2(end.y - start.y, end.x - start.x);
997
+ ctx.beginPath();
998
+ ctx.moveTo(start.x, start.y);
999
+ ctx.lineTo(end.x, end.y);
1000
+ ctx.stroke();
1001
+ ctx.beginPath();
1002
+ ctx.moveTo(end.x, end.y);
1003
+ ctx.lineTo(
1004
+ end.x - headLength * Math.cos(angle - Math.PI / 6),
1005
+ end.y - headLength * Math.sin(angle - Math.PI / 6)
1006
+ );
1007
+ ctx.lineTo(
1008
+ end.x - headLength * Math.cos(angle + Math.PI / 6),
1009
+ end.y - headLength * Math.sin(angle + Math.PI / 6)
1010
+ );
1011
+ ctx.closePath();
1012
+ ctx.fillStyle = style.strokeColor;
1013
+ ctx.fill();
1014
+ }
1015
+ drawLine(ctx, start, end) {
1016
+ ctx.beginPath();
1017
+ ctx.moveTo(start.x, start.y);
1018
+ ctx.lineTo(end.x, end.y);
1019
+ ctx.stroke();
1020
+ }
1021
+ drawHighlight(ctx, bounds, style) {
1022
+ ctx.fillStyle = style.fillColor;
1023
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1024
+ }
1025
+ drawBlur(ctx, bounds, blurAmount) {
1026
+ const pixelSize = Math.max(blurAmount, 5);
1027
+ if (this.backgroundImage) {
1028
+ const tempCanvas = document.createElement("canvas");
1029
+ tempCanvas.width = bounds.width;
1030
+ tempCanvas.height = bounds.height;
1031
+ const tempCtx = tempCanvas.getContext("2d");
1032
+ tempCtx.drawImage(
1033
+ this.backgroundImage,
1034
+ bounds.x,
1035
+ bounds.y,
1036
+ bounds.width,
1037
+ bounds.height,
1038
+ 0,
1039
+ 0,
1040
+ bounds.width,
1041
+ bounds.height
1042
+ );
1043
+ const w = tempCanvas.width;
1044
+ const h = tempCanvas.height;
1045
+ tempCtx.imageSmoothingEnabled = false;
1046
+ tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
1047
+ tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
1048
+ ctx.drawImage(tempCanvas, bounds.x, bounds.y);
1049
+ }
1050
+ ctx.strokeStyle = "#999";
1051
+ ctx.lineWidth = 1;
1052
+ ctx.setLineDash([5, 5]);
1053
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1054
+ ctx.setLineDash([]);
1055
+ }
1056
+ drawText(ctx, position, text, style) {
1057
+ ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
1058
+ ctx.fillStyle = style.strokeColor;
1059
+ ctx.textBaseline = "top";
1060
+ const metrics = ctx.measureText(text);
1061
+ const padding = 4;
1062
+ ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
1063
+ ctx.fillRect(
1064
+ position.x - padding,
1065
+ position.y - padding,
1066
+ metrics.width + padding * 2,
1067
+ (style.fontSize || 16) + padding * 2
1068
+ );
1069
+ ctx.fillStyle = style.strokeColor;
1070
+ ctx.fillText(text, position.x, position.y);
1071
+ }
1072
+ drawFreehand(ctx, points) {
1073
+ if (points.length < 2) return;
1074
+ ctx.beginPath();
1075
+ ctx.moveTo(points[0].x, points[0].y);
1076
+ for (let i = 1; i < points.length; i++) {
1077
+ ctx.lineTo(points[i].x, points[i].y);
1078
+ }
1079
+ ctx.stroke();
1080
+ }
1081
+ };
1082
+
1083
+ // src/widget.ts
1084
+ var DEFAULT_BUTTON_CONFIG = {
1085
+ text: "Report Bug",
1086
+ backgroundColor: "#1e3a5f",
1087
+ textColor: "#ffffff",
1088
+ borderRadius: "8px",
1089
+ padding: "10px 18px",
1090
+ fontSize: "14px",
1091
+ fontWeight: "500",
1092
+ border: "none",
1093
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
1094
+ className: "",
1095
+ showIcon: true,
1096
+ size: "default",
1097
+ variant: "default"
1098
+ };
1099
+ var SIZE_PRESETS = {
1100
+ xs: { padding: "6px 10px", fontSize: "12px" },
1101
+ sm: { padding: "8px 14px", fontSize: "13px" },
1102
+ default: { padding: "10px 18px", fontSize: "14px" },
1103
+ lg: { padding: "12px 24px", fontSize: "15px" }
1104
+ };
1105
+ var VARIANT_PRESETS = {
1106
+ default: { bg: "#1e3a5f", color: "#ffffff", border: "none" },
1107
+ outline: { bg: "transparent", color: "#1e3a5f", border: "1px solid #1e3a5f" },
1108
+ ghost: { bg: "transparent", color: "#1e3a5f", border: "none" },
1109
+ link: { bg: "transparent", color: "#1e3a5f", border: "none" }
1110
+ };
1111
+ var DEFAULT_CONFIG = {
1112
+ position: "bottom-right",
1113
+ color: "#1e3a5f",
1114
+ text: "Report Bug",
1115
+ showOnInit: true,
1116
+ button: DEFAULT_BUTTON_CONFIG
1117
+ };
1118
+ var container = null;
1119
+ var onSubmitCallback = null;
1120
+ var screenshotDataUrl = null;
1121
+ var annotatedImageUrl = null;
1122
+ var annotationsData = [];
1123
+ var isExpanded = false;
1124
+ var currentCfg = { ...DEFAULT_CONFIG };
1125
+ var currentUser = void 0;
1126
+ function mountWidget(config = {}, onSubmit, opts) {
1127
+ if (container) return;
1128
+ onSubmitCallback = onSubmit;
1129
+ currentUser = opts?.user;
1130
+ const cfg = { ...DEFAULT_CONFIG, ...config };
1131
+ currentCfg = cfg;
1132
+ container = document.createElement("div");
1133
+ container.id = "checkflow-widget";
1134
+ injectStyles(cfg);
1135
+ container.innerHTML = getTriggerHTML(cfg);
1136
+ document.body.appendChild(container);
1137
+ const btn = container.querySelector("#cf-trigger");
1138
+ btn?.addEventListener("click", () => openModal(cfg, opts));
1139
+ }
1140
+ function openFeedbackModal(config = {}, onSubmit, opts) {
1141
+ onSubmitCallback = onSubmit;
1142
+ currentUser = opts?.user;
1143
+ const cfg = { ...DEFAULT_CONFIG, ...config };
1144
+ currentCfg = cfg;
1145
+ injectStyles(cfg);
1146
+ openModal(cfg, opts);
1147
+ }
1148
+ function getBackdropClass(cfg, expanded) {
1149
+ if (expanded) return "cf-backdrop--center";
1150
+ if (cfg.position === "center") return "cf-backdrop--center";
1151
+ return "cf-backdrop--corner cf-pos-" + (cfg.position || "bottom-right");
1152
+ }
1153
+ function openModal(cfg, opts) {
1154
+ const existing = document.getElementById("cf-modal-backdrop");
1155
+ if (existing) existing.remove();
1156
+ isExpanded = false;
1157
+ screenshotDataUrl = null;
1158
+ annotatedImageUrl = null;
1159
+ annotationsData = [];
1160
+ const backdrop = document.createElement("div");
1161
+ backdrop.id = "cf-modal-backdrop";
1162
+ backdrop.className = getBackdropClass(cfg, false);
1163
+ backdrop.innerHTML = getModalHTML(cfg, false);
1164
+ document.body.appendChild(backdrop);
1165
+ backdrop.addEventListener("click", (e) => {
1166
+ if (e.target === backdrop) closeModal();
1167
+ });
1168
+ bindModalEvents(cfg, opts);
1169
+ }
1170
+ function closeModal() {
1171
+ const backdrop = document.getElementById("cf-modal-backdrop");
1172
+ if (backdrop) backdrop.remove();
1173
+ isExpanded = false;
1174
+ screenshotDataUrl = null;
1175
+ annotatedImageUrl = null;
1176
+ annotationsData = [];
1177
+ }
1178
+ function expandModal(cfg, opts) {
1179
+ const backdrop = document.getElementById("cf-modal-backdrop");
1180
+ if (!backdrop) return;
1181
+ const name = backdrop.querySelector("#cf-name")?.value || "";
1182
+ const email = backdrop.querySelector("#cf-email")?.value || "";
1183
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
1184
+ isExpanded = true;
1185
+ backdrop.className = getBackdropClass(cfg, true);
1186
+ backdrop.innerHTML = getModalHTML(cfg, true);
1187
+ bindModalEvents(cfg, opts);
1188
+ const nameEl = backdrop.querySelector("#cf-name");
1189
+ const emailEl = backdrop.querySelector("#cf-email");
1190
+ const descEl = backdrop.querySelector("#cf-desc");
1191
+ if (nameEl) nameEl.value = name;
1192
+ if (emailEl) emailEl.value = email;
1193
+ if (descEl) descEl.value = desc;
1194
+ }
1195
+ function collapseModal(cfg, opts) {
1196
+ const backdrop = document.getElementById("cf-modal-backdrop");
1197
+ if (!backdrop) return;
1198
+ const name = backdrop.querySelector("#cf-name")?.value || "";
1199
+ const email = backdrop.querySelector("#cf-email")?.value || "";
1200
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
1201
+ isExpanded = false;
1202
+ screenshotDataUrl = null;
1203
+ annotatedImageUrl = null;
1204
+ annotationsData = [];
1205
+ backdrop.className = getBackdropClass(cfg, false);
1206
+ backdrop.innerHTML = getModalHTML(cfg, false);
1207
+ bindModalEvents(cfg, opts);
1208
+ const nameEl = backdrop.querySelector("#cf-name");
1209
+ const emailEl = backdrop.querySelector("#cf-email");
1210
+ const descEl = backdrop.querySelector("#cf-desc");
1211
+ if (nameEl) nameEl.value = name;
1212
+ if (emailEl) emailEl.value = email;
1213
+ if (descEl) descEl.value = desc;
1214
+ }
1215
+ function openAnnotationEditor(cfg, opts) {
1216
+ if (!screenshotDataUrl) return;
1217
+ const backdrop = document.getElementById("cf-modal-backdrop");
1218
+ if (backdrop) backdrop.style.display = "none";
1219
+ const editor = new AnnotationEditor({
1220
+ tools: ["rectangle", "ellipse", "arrow", "highlight", "blur", "text", "freehand"],
1221
+ defaultStyle: { strokeColor: "#FF3B30", strokeWidth: 3, fillColor: "transparent", opacity: 1, fontSize: 16, fontFamily: "system-ui, -apple-system, sans-serif" },
1222
+ onSave: (annotations, imageData) => {
1223
+ annotationsData = annotations;
1224
+ annotatedImageUrl = imageData;
1225
+ if (backdrop) {
1226
+ backdrop.style.display = "flex";
1227
+ const img = backdrop.querySelector("#cf-screenshot-img");
1228
+ if (img) img.src = annotatedImageUrl;
1229
+ const annotBtn = backdrop.querySelector("#cf-annotate-btn");
1230
+ if (annotBtn) annotBtn.textContent = `Annoter (${annotationsData.length})`;
1231
+ }
1232
+ },
1233
+ onCancel: () => {
1234
+ if (backdrop) backdrop.style.display = "flex";
1235
+ }
1236
+ });
1237
+ editor.open(screenshotDataUrl);
1238
+ }
1239
+ function bindModalEvents(cfg, opts) {
1240
+ const backdrop = document.getElementById("cf-modal-backdrop");
1241
+ if (!backdrop) return;
1242
+ backdrop.querySelector("#cf-cancel")?.addEventListener("click", closeModal);
1243
+ backdrop.querySelector("#cf-screenshot-btn")?.addEventListener("click", async () => {
1244
+ if (!opts?.onScreenshot) return;
1245
+ backdrop.style.display = "none";
1246
+ try {
1247
+ const data = await opts.onScreenshot();
1248
+ if (data) {
1249
+ screenshotDataUrl = data;
1250
+ backdrop.style.display = "flex";
1251
+ expandModal(cfg, opts);
1252
+ return;
1253
+ }
1254
+ } catch {
1255
+ }
1256
+ backdrop.style.display = "flex";
1257
+ });
1258
+ backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
1259
+ collapseModal(cfg, opts);
1260
+ });
1261
+ backdrop.querySelector("#cf-annotate-btn")?.addEventListener("click", () => {
1262
+ openAnnotationEditor(cfg, opts);
1263
+ });
1264
+ backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
1265
+ const desc = backdrop.querySelector("#cf-desc")?.value;
1266
+ const nameEl = backdrop.querySelector("#cf-name");
1267
+ const emailEl = backdrop.querySelector("#cf-email");
1268
+ const name = nameEl?.value || currentUser?.name;
1269
+ const email = emailEl?.value || currentUser?.email;
1270
+ if (!desc?.trim()) {
1271
+ const descEl = backdrop.querySelector("#cf-desc");
1272
+ if (descEl) {
1273
+ descEl.style.borderColor = "#e74c3c";
1274
+ descEl.focus();
1275
+ }
1276
+ return;
1277
+ }
1278
+ onSubmitCallback?.({
1279
+ title: desc.trim().slice(0, 100),
1280
+ description: desc.trim(),
1281
+ type: "BUG",
1282
+ priority: "MEDIUM",
1283
+ screenshot: annotatedImageUrl || screenshotDataUrl || void 0,
1284
+ annotations: annotationsData.length > 0 ? annotationsData : void 0,
1285
+ reporter_name: name?.trim() || void 0,
1286
+ reporter_email: email?.trim() || void 0
1287
+ });
1288
+ closeModal();
1289
+ showToast("Rapport envoy\xE9 avec succ\xE8s !");
1290
+ });
1291
+ }
1292
+ function unmountWidget() {
1293
+ closeModal();
1294
+ if (container) {
1295
+ container.remove();
1296
+ container = null;
1297
+ }
1298
+ }
1299
+ function showToast(msg) {
1300
+ const toast = document.createElement("div");
1301
+ toast.textContent = msg;
1302
+ toast.style.cssText = 'position:fixed;bottom:80px;right:20px;background:#10b981;color:#fff;padding:10px 20px;border-radius:10px;font-size:14px;z-index:100010;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;box-shadow:0 4px 16px rgba(0,0,0,.18);animation:cf-toast-in .3s ease;';
1303
+ document.body.appendChild(toast);
1304
+ setTimeout(() => {
1305
+ toast.style.opacity = "0";
1306
+ toast.style.transition = "opacity .3s";
1307
+ }, 2500);
1308
+ setTimeout(() => toast.remove(), 3e3);
1309
+ }
1310
+ function injectStyles(cfg) {
1311
+ const existingStyle = document.getElementById("cf-widget-styles");
1312
+ if (existingStyle) existingStyle.remove();
1313
+ const btn = cfg.button || {};
1314
+ const sizePreset = SIZE_PRESETS[btn.size || "default"] || SIZE_PRESETS.default;
1315
+ const variantPreset = VARIANT_PRESETS[btn.variant || "default"] || VARIANT_PRESETS.default;
1316
+ const bgColor = btn.backgroundColor || cfg.color || variantPreset.bg;
1317
+ const textColor = btn.textColor || variantPreset.color;
1318
+ const borderRadius = btn.borderRadius || "8px";
1319
+ const padding = btn.padding || sizePreset.padding;
1320
+ const fontSize = btn.fontSize || sizePreset.fontSize;
1321
+ const fontWeight = btn.fontWeight || "500";
1322
+ const border = btn.border || variantPreset.border;
1323
+ const boxShadow = btn.boxShadow || "0 2px 8px rgba(0,0,0,0.15)";
1324
+ const primaryColor = cfg.color || "#1e3a5f";
1325
+ const style = document.createElement("style");
1326
+ style.id = "cf-widget-styles";
1327
+ style.textContent = `
1328
+ @keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
1329
+ @keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
1330
+
1331
+ /* User card styles */
1332
+ .cf-user-card {
1333
+ display:flex;
1334
+ align-items:center;
1335
+ gap:12px;
1336
+ padding:12px;
1337
+ background:#f8f9fa;
1338
+ border-radius:10px;
1339
+ margin-bottom:4px;
1340
+ }
1341
+ .cf-user-avatar {
1342
+ width:40px;
1343
+ height:40px;
1344
+ border-radius:50%;
1345
+ object-fit:cover;
1346
+ }
1347
+ .cf-user-avatar-placeholder {
1348
+ width:40px;
1349
+ height:40px;
1350
+ border-radius:50%;
1351
+ background:${primaryColor};
1352
+ color:#fff;
1353
+ display:flex;
1354
+ align-items:center;
1355
+ justify-content:center;
1356
+ font-size:16px;
1357
+ font-weight:600;
1358
+ }
1359
+ .cf-user-info {
1360
+ flex:1;
1361
+ min-width:0;
1362
+ }
1363
+ .cf-user-name {
1364
+ font-size:14px;
1365
+ font-weight:600;
1366
+ color:#1a1a2e;
1367
+ }
1368
+ .cf-user-email {
1369
+ font-size:12px;
1370
+ color:#666;
1371
+ overflow:hidden;
1372
+ text-overflow:ellipsis;
1373
+ white-space:nowrap;
1374
+ }
1375
+
1376
+ /* Trigger button - now inline, not fixed position */
1377
+ #cf-trigger {
1378
+ position:fixed;
1379
+ ${getPositionCSS(cfg.position || "bottom-right")}
1380
+ z-index:100000;
1381
+ background:${bgColor};
1382
+ color:${textColor};
1383
+ border:${border};
1384
+ border-radius:${borderRadius};
1385
+ padding:${padding};
1386
+ font-size:${fontSize};
1387
+ font-weight:${fontWeight};
1388
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1389
+ cursor:pointer;
1390
+ box-shadow:${boxShadow};
1391
+ display:inline-flex;
1392
+ align-items:center;
1393
+ gap:8px;
1394
+ transition:transform .15s,box-shadow .15s,opacity .15s;
1395
+ }
1396
+ #cf-trigger:hover { opacity:0.9; }
1397
+ #cf-trigger svg { width:16px; height:16px; flex-shrink:0; }
1398
+
1399
+ #cf-modal-backdrop {
1400
+ position:fixed;
1401
+ top:0;left:0;right:0;bottom:0;
1402
+ background:rgba(0,0,0,.45);
1403
+ z-index:100001;
1404
+ display:flex;
1405
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1406
+ }
1407
+ #cf-modal-backdrop.cf-backdrop--center {
1408
+ align-items:center;
1409
+ justify-content:center;
1410
+ }
1411
+ #cf-modal-backdrop.cf-backdrop--corner {
1412
+ padding:20px;
1413
+ }
1414
+ #cf-modal-backdrop.cf-pos-bottom-right {
1415
+ align-items:flex-end;
1416
+ justify-content:flex-end;
1417
+ }
1418
+ #cf-modal-backdrop.cf-pos-bottom-left {
1419
+ align-items:flex-end;
1420
+ justify-content:flex-start;
1421
+ }
1422
+ #cf-modal-backdrop.cf-pos-top-right {
1423
+ align-items:flex-start;
1424
+ justify-content:flex-end;
1425
+ }
1426
+ #cf-modal-backdrop.cf-pos-top-left {
1427
+ align-items:flex-start;
1428
+ justify-content:flex-start;
1429
+ }
1430
+
1431
+ .cf-modal {
1432
+ background:#fff;
1433
+ border-radius:16px;
1434
+ box-shadow:0 24px 80px rgba(0,0,0,.25);
1435
+ overflow:hidden;
1436
+ animation:cf-modal-in .25s ease;
1437
+ display:flex;
1438
+ flex-direction:column;
1439
+ max-height:90vh;
1440
+ }
1441
+ .cf-modal--compact { width:420px; }
1442
+ .cf-modal--expanded { width:min(1100px,92vw); flex-direction:column; }
1443
+
1444
+ .cf-modal-header {
1445
+ display:flex;
1446
+ align-items:center;
1447
+ justify-content:space-between;
1448
+ padding:20px 24px 16px;
1449
+ border-bottom:1px solid #f0f0f0;
1450
+ }
1451
+ .cf-modal-header h2 {
1452
+ margin:0;
1453
+ font-size:20px;
1454
+ font-weight:700;
1455
+ color:#1a1a2e;
1456
+ }
1457
+ .cf-modal-header .cf-logo {
1458
+ width:28px;
1459
+ height:28px;
1460
+ color:${cfg.color};
1461
+ }
1462
+
1463
+ .cf-modal-body { display:flex; flex:1; overflow:hidden; }
1464
+
1465
+ .cf-screenshot-panel {
1466
+ flex:1;
1467
+ min-width:0;
1468
+ background:#1a1a2e;
1469
+ display:flex;
1470
+ flex-direction:column;
1471
+ position:relative;
1472
+ }
1473
+ .cf-screenshot-panel img {
1474
+ width:100%;
1475
+ height:100%;
1476
+ object-fit:contain;
1477
+ max-height:60vh;
1478
+ }
1479
+ .cf-screenshot-tools {
1480
+ position:absolute;
1481
+ bottom:16px;
1482
+ left:50%;
1483
+ transform:translateX(-50%);
1484
+ display:flex;
1485
+ gap:8px;
1486
+ }
1487
+ .cf-screenshot-tools button {
1488
+ padding:8px 18px;
1489
+ border-radius:8px;
1490
+ font-size:13px;
1491
+ font-weight:500;
1492
+ cursor:pointer;
1493
+ font-family:inherit;
1494
+ border:none;
1495
+ transition:all .15s;
1496
+ }
1497
+ .cf-tool-highlight {
1498
+ background:${primaryColor};
1499
+ color:#fff;
1500
+ }
1501
+ .cf-tool-highlight:hover { opacity:.9; }
1502
+ .cf-tool-mask {
1503
+ background:#fff;
1504
+ color:#1a1a2e;
1505
+ border:1px solid #e0e0e0 !important;
1506
+ }
1507
+ .cf-tool-mask:hover { background:#f5f5f5; }
1508
+
1509
+ .cf-form-panel {
1510
+ width:100%;
1511
+ padding:20px 24px;
1512
+ display:flex;
1513
+ flex-direction:column;
1514
+ gap:14px;
1515
+ overflow-y:auto;
1516
+ }
1517
+ .cf-modal--expanded .cf-form-panel {
1518
+ width:340px;
1519
+ flex-shrink:0;
1520
+ border-left:1px solid #f0f0f0;
1521
+ }
1522
+
1523
+ .cf-field label {
1524
+ display:block;
1525
+ font-size:13px;
1526
+ font-weight:500;
1527
+ color:#1a1a2e;
1528
+ margin-bottom:6px;
1529
+ }
1530
+ .cf-field label span { font-weight:400; color:#999; }
1531
+ .cf-field input, .cf-field textarea {
1532
+ width:100%;
1533
+ padding:10px 14px;
1534
+ border:1px solid #e0e0e0;
1535
+ border-radius:10px;
1536
+ font-size:14px;
1537
+ font-family:inherit;
1538
+ box-sizing:border-box;
1539
+ outline:none;
1540
+ transition:border-color .15s,box-shadow .15s;
1541
+ color:#1a1a2e;
1542
+ background:#fff;
1543
+ }
1544
+ .cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
1545
+ .cf-field input:focus, .cf-field textarea:focus {
1546
+ border-color:${primaryColor};
1547
+ box-shadow:0 0 0 3px ${primaryColor}18;
1548
+ }
1549
+ .cf-field textarea { resize:vertical; min-height:100px; }
1550
+
1551
+ .cf-screenshot-action {
1552
+ width:100%;
1553
+ padding:12px;
1554
+ border:1px solid #e0e0e0;
1555
+ border-radius:10px;
1556
+ background:#fff;
1557
+ font-size:14px;
1558
+ color:#1a1a2e;
1559
+ cursor:pointer;
1560
+ font-family:inherit;
1561
+ transition:all .15s;
1562
+ text-align:center;
1563
+ }
1564
+ .cf-screenshot-action:hover { background:#f8f8f8; border-color:#ccc; }
1565
+
1566
+ .cf-remove-screenshot {
1567
+ width:100%;
1568
+ padding:10px;
1569
+ border:1px solid #e0e0e0;
1570
+ border-radius:10px;
1571
+ background:#fff;
1572
+ font-size:13px;
1573
+ color:#666;
1574
+ cursor:pointer;
1575
+ font-family:inherit;
1576
+ transition:all .15s;
1577
+ text-align:center;
1578
+ }
1579
+ .cf-remove-screenshot:hover { background:#fff0f0; color:#e74c3c; border-color:#e74c3c; }
1580
+
1581
+ .cf-submit-btn {
1582
+ width:100%;
1583
+ padding:13px;
1584
+ border:none;
1585
+ border-radius:10px;
1586
+ background:${primaryColor};
1587
+ color:#fff;
1588
+ font-size:15px;
1589
+ font-weight:600;
1590
+ cursor:pointer;
1591
+ font-family:inherit;
1592
+ transition:all .15s;
1593
+ }
1594
+ .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${primaryColor}40; }
1595
+ .cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
1596
+
1597
+ .cf-cancel-btn {
1598
+ width:100%;
1599
+ padding:11px;
1600
+ border:1px solid #e0e0e0;
1601
+ border-radius:10px;
1602
+ background:#fff;
1603
+ color:#1a1a2e;
1604
+ font-size:14px;
1605
+ font-weight:500;
1606
+ cursor:pointer;
1607
+ font-family:inherit;
1608
+ transition:all .15s;
1609
+ text-align:center;
1610
+ }
1611
+ .cf-cancel-btn:hover { background:#f8f8f8; }
1612
+
1613
+ .cf-footer {
1614
+ text-align:center;
1615
+ padding:12px;
1616
+ border-top:1px solid #f0f0f0;
1617
+ font-size:11px;
1618
+ color:#bbb;
1619
+ }
1620
+ .cf-footer a {
1621
+ color:#999;
1622
+ text-decoration:none;
1623
+ font-weight:500;
1624
+ }
1625
+ .cf-footer a:hover { color:${primaryColor}; }
1626
+ `;
1627
+ document.head.appendChild(style);
1628
+ }
1629
+ function getPositionCSS(pos) {
1630
+ switch (pos) {
1631
+ case "bottom-left":
1632
+ return "bottom:20px;left:20px;";
1633
+ case "top-right":
1634
+ return "top:20px;right:20px;";
1635
+ case "top-left":
1636
+ return "top:20px;left:20px;";
1637
+ case "center":
1638
+ return "bottom:20px;left:50%;transform:translateX(-50%);";
1639
+ default:
1640
+ return "bottom:20px;right:20px;";
1641
+ }
1642
+ }
1643
+ function getTriggerHTML(cfg) {
1644
+ const btn = cfg.button || {};
1645
+ const text = btn.text || cfg.text || "Report Bug";
1646
+ const showIcon = btn.showIcon !== false;
1647
+ const customClass = btn.className || "";
1648
+ const icon = showIcon ? `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>` : "";
1649
+ return `
1650
+ <button id="cf-trigger" class="${customClass}">
1651
+ ${icon}
1652
+ ${text}
1653
+ </button>
1654
+ `;
1655
+ }
1656
+ function getCheckflowLogo() {
1657
+ return `<svg class="cf-logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4"/><path d="M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2"/><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02"/><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"/><path d="M8.65 22c.21-.66.45-1.32.57-2"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/><path d="M2 16h.01"/><path d="M21.8 16c.2-2 .131-5.354 0-6"/><path d="M9 6.8a6 6 0 0 1 9 5.2v2"/></svg>`;
1658
+ }
1659
+ function getModalHTML(cfg, expanded) {
1660
+ const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
1661
+ const hasUser = currentUser && (currentUser.name || currentUser.email);
1662
+ const userInfoCard = hasUser ? `
1663
+ <div class="cf-user-card">
1664
+ ${currentUser?.avatar ? `<img class="cf-user-avatar" src="${currentUser.avatar}" alt="${currentUser.name || "User"}" />` : `<div class="cf-user-avatar-placeholder">${(currentUser?.name || currentUser?.email || "U").charAt(0).toUpperCase()}</div>`}
1665
+ <div class="cf-user-info">
1666
+ ${currentUser?.name ? `<div class="cf-user-name">${currentUser.name}</div>` : ""}
1667
+ ${currentUser?.email ? `<div class="cf-user-email">${currentUser.email}</div>` : ""}
1668
+ </div>
1669
+ </div>
1670
+ ` : "";
1671
+ const nameEmailFields = hasUser ? "" : `
1672
+ <div class="cf-field">
1673
+ <label>Nom <span>(optionnel)</span></label>
1674
+ <input id="cf-name" type="text" placeholder="Votre nom" />
1675
+ </div>
1676
+ <div class="cf-field">
1677
+ <label>Email <span>(optionnel)</span></label>
1678
+ <input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
1679
+ </div>
1680
+ `;
1681
+ const formFields = `
1682
+ ${userInfoCard}
1683
+ ${nameEmailFields}
1684
+ <div class="cf-field">
1685
+ <label>Description <span>(obligatoire)</span></label>
1686
+ <textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
1687
+ </div>
1688
+ `;
1689
+ if (expanded && screenshotDataUrl) {
1690
+ const displayImg = annotatedImageUrl || screenshotDataUrl;
1691
+ const annotLabel = annotationsData.length > 0 ? `Annoter (${annotationsData.length})` : "Annoter";
1692
+ return `
1693
+ <div class="${modalClass}">
1694
+ <div class="cf-modal-header">
1695
+ <h2>Envoyer le rapport</h2>
1696
+ ${getCheckflowLogo()}
1697
+ </div>
1698
+ <div class="cf-modal-body">
1699
+ <div class="cf-screenshot-panel">
1700
+ <img id="cf-screenshot-img" src="${displayImg}" alt="Capture d'\xE9cran" />
1701
+ <div class="cf-screenshot-tools">
1702
+ <button class="cf-tool-highlight" id="cf-annotate-btn">${annotLabel}</button>
1703
+ </div>
1704
+ </div>
1705
+ <div class="cf-form-panel">
1706
+ ${formFields}
1707
+ <button class="cf-remove-screenshot" id="cf-remove-screenshot">Supprimer la capture d'\xE9cran</button>
1708
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
1709
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
1710
+ </div>
1711
+ </div>
1712
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
1713
+ </div>
1714
+ `;
1715
+ }
1716
+ return `
1717
+ <div class="${modalClass}">
1718
+ <div class="cf-modal-header">
1719
+ <h2>Envoyer le rapport</h2>
1720
+ ${getCheckflowLogo()}
1721
+ </div>
1722
+ <div class="cf-form-panel">
1723
+ ${formFields}
1724
+ <button class="cf-screenshot-action" id="cf-screenshot-btn">Ajouter une capture d'\xE9cran</button>
1725
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
1726
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
1727
+ </div>
1728
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
1729
+ </div>
1730
+ `;
1731
+ }
1732
+
1733
+ export {
1734
+ mountWidget,
1735
+ openFeedbackModal,
1736
+ unmountWidget
1737
+ };