@checkflow/sdk 1.1.3 → 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.
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,208 +20,1129 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- captureScreenshot: () => captureScreenshot,
24
- destroy: () => destroy,
25
- hideWidget: () => hideWidget,
26
- init: () => init,
27
- sendFeedback: () => sendFeedback,
28
- showWidget: () => showWidget,
29
- startHighlighting: () => startHighlighting
23
+ // src/annotation/types.ts
24
+ var DEFAULT_STYLE, HIGHLIGHT_STYLE, BLUR_STYLE, COLOR_PALETTE, STROKE_WIDTHS;
25
+ var init_types = __esm({
26
+ "src/annotation/types.ts"() {
27
+ "use strict";
28
+ DEFAULT_STYLE = {
29
+ strokeColor: "#FF3B30",
30
+ strokeWidth: 3,
31
+ fillColor: "transparent",
32
+ opacity: 1,
33
+ fontSize: 16,
34
+ fontFamily: "system-ui, -apple-system, sans-serif"
35
+ };
36
+ HIGHLIGHT_STYLE = {
37
+ strokeColor: "transparent",
38
+ fillColor: "#FFEB3B",
39
+ opacity: 0.4
40
+ };
41
+ BLUR_STYLE = {
42
+ strokeColor: "#666666",
43
+ fillColor: "#666666",
44
+ opacity: 0.8
45
+ };
46
+ COLOR_PALETTE = [
47
+ "#FF3B30",
48
+ // Red
49
+ "#FF9500",
50
+ // Orange
51
+ "#FFCC00",
52
+ // Yellow
53
+ "#34C759",
54
+ // Green
55
+ "#007AFF",
56
+ // Blue
57
+ "#5856D6",
58
+ // Purple
59
+ "#AF52DE",
60
+ // Pink
61
+ "#000000",
62
+ // Black
63
+ "#FFFFFF"
64
+ // White
65
+ ];
66
+ STROKE_WIDTHS = [1, 2, 3, 5, 8];
67
+ }
30
68
  });
31
- module.exports = __toCommonJS(index_exports);
32
69
 
33
- // src/collector.ts
34
- function collectContext() {
35
- const ua = navigator.userAgent;
36
- return {
37
- url: window.location.href,
38
- viewport: {
39
- width: window.innerWidth,
40
- height: window.innerHeight,
41
- device: window.innerWidth < 768 ? "mobile" : window.innerWidth < 1024 ? "tablet" : "desktop"
42
- },
43
- user_agent: ua,
44
- browser: detectBrowser(ua),
45
- os: detectOS(ua),
46
- locale: navigator.language,
47
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
48
- };
49
- }
50
- function collectPerformance() {
51
- try {
52
- const entries = performance.getEntriesByType("paint");
53
- const metrics = {};
54
- entries.forEach((e) => {
55
- if (e.name === "first-contentful-paint") metrics.fcp = Math.round(e.startTime);
56
- });
57
- const nav = performance.getEntriesByType("navigation")[0];
58
- if (nav) {
59
- metrics.dom_load = Math.round(nav.domContentLoadedEventEnd - nav.startTime);
60
- metrics.load = Math.round(nav.loadEventEnd - nav.startTime);
61
- }
62
- return Object.keys(metrics).length > 0 ? metrics : void 0;
63
- } catch {
64
- return void 0;
70
+ // src/annotation/toolbar.ts
71
+ var TOOL_ICONS, TOOL_LABELS, AnnotationToolbar;
72
+ var init_toolbar = __esm({
73
+ "src/annotation/toolbar.ts"() {
74
+ "use strict";
75
+ init_types();
76
+ TOOL_ICONS = {
77
+ 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>`,
78
+ 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>`,
79
+ ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
80
+ 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>`,
81
+ line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
82
+ 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>`,
83
+ 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>`,
84
+ text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
85
+ 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>`
86
+ };
87
+ TOOL_LABELS = {
88
+ select: "S\xE9lection",
89
+ rectangle: "Rectangle",
90
+ ellipse: "Ellipse",
91
+ arrow: "Fl\xE8che",
92
+ line: "Ligne",
93
+ highlight: "Surbrillance",
94
+ blur: "Floutage",
95
+ text: "Texte",
96
+ freehand: "Dessin libre"
97
+ };
98
+ AnnotationToolbar = class {
99
+ constructor(config2) {
100
+ this.colorPicker = null;
101
+ this.strokePicker = null;
102
+ this.config = config2;
103
+ this.element = this.createToolbar();
104
+ }
105
+ getElement() {
106
+ return this.element;
107
+ }
108
+ setActiveTool(tool) {
109
+ this.config.activeTool = tool;
110
+ this.updateActiveState();
111
+ }
112
+ setStyle(style) {
113
+ this.config.style = style;
114
+ this.updateStyleDisplay();
115
+ }
116
+ createToolbar() {
117
+ const toolbar = document.createElement("div");
118
+ toolbar.className = "cf-toolbar";
119
+ const toolsSection = document.createElement("div");
120
+ toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
121
+ for (const tool of this.config.tools) {
122
+ const button = this.createToolButton(tool);
123
+ toolsSection.appendChild(button);
124
+ }
125
+ const separator1 = document.createElement("div");
126
+ separator1.className = "cf-toolbar-separator";
127
+ const styleSection = document.createElement("div");
128
+ styleSection.className = "cf-toolbar-section cf-toolbar-style";
129
+ const colorBtn = document.createElement("button");
130
+ colorBtn.className = "cf-toolbar-btn cf-color-btn";
131
+ colorBtn.title = "Couleur";
132
+ colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
133
+ colorBtn.addEventListener("click", () => this.toggleColorPicker());
134
+ styleSection.appendChild(colorBtn);
135
+ const strokeBtn = document.createElement("button");
136
+ strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
137
+ strokeBtn.title = "\xC9paisseur";
138
+ strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
139
+ strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
140
+ styleSection.appendChild(strokeBtn);
141
+ const separator2 = document.createElement("div");
142
+ separator2.className = "cf-toolbar-separator";
143
+ const actionsSection = document.createElement("div");
144
+ actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
145
+ const undoBtn = document.createElement("button");
146
+ undoBtn.className = "cf-toolbar-btn";
147
+ undoBtn.title = "Annuler (Ctrl+Z)";
148
+ 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>`;
149
+ undoBtn.addEventListener("click", () => this.config.onUndo());
150
+ actionsSection.appendChild(undoBtn);
151
+ const clearBtn = document.createElement("button");
152
+ clearBtn.className = "cf-toolbar-btn";
153
+ clearBtn.title = "Tout effacer";
154
+ 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>`;
155
+ clearBtn.addEventListener("click", () => this.config.onClear());
156
+ actionsSection.appendChild(clearBtn);
157
+ toolbar.appendChild(toolsSection);
158
+ toolbar.appendChild(separator1);
159
+ toolbar.appendChild(styleSection);
160
+ toolbar.appendChild(separator2);
161
+ toolbar.appendChild(actionsSection);
162
+ return toolbar;
163
+ }
164
+ createToolButton(tool) {
165
+ const button = document.createElement("button");
166
+ button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
167
+ button.dataset.tool = tool;
168
+ button.title = TOOL_LABELS[tool];
169
+ button.innerHTML = TOOL_ICONS[tool];
170
+ button.addEventListener("click", () => {
171
+ this.config.onToolChange(tool);
172
+ this.setActiveTool(tool);
173
+ });
174
+ return button;
175
+ }
176
+ updateActiveState() {
177
+ const buttons = this.element.querySelectorAll(".cf-tool-btn");
178
+ buttons.forEach((btn) => {
179
+ const button = btn;
180
+ button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
181
+ });
182
+ }
183
+ updateStyleDisplay() {
184
+ const colorPreview = this.element.querySelector(".cf-color-preview");
185
+ if (colorPreview) {
186
+ colorPreview.style.background = this.config.style.strokeColor;
187
+ }
188
+ const strokePreview = this.element.querySelector(".cf-stroke-preview");
189
+ if (strokePreview) {
190
+ strokePreview.textContent = `${this.config.style.strokeWidth}px`;
191
+ }
192
+ }
193
+ toggleColorPicker() {
194
+ if (this.colorPicker) {
195
+ this.colorPicker.remove();
196
+ this.colorPicker = null;
197
+ return;
198
+ }
199
+ if (this.strokePicker) {
200
+ this.strokePicker.remove();
201
+ this.strokePicker = null;
202
+ }
203
+ this.colorPicker = document.createElement("div");
204
+ this.colorPicker.className = "cf-picker cf-color-picker";
205
+ for (const color of COLOR_PALETTE) {
206
+ const swatch = document.createElement("button");
207
+ swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
208
+ swatch.style.background = color;
209
+ swatch.addEventListener("click", () => {
210
+ this.config.onStyleChange({ strokeColor: color });
211
+ this.setStyle({ ...this.config.style, strokeColor: color });
212
+ this.colorPicker?.remove();
213
+ this.colorPicker = null;
214
+ });
215
+ this.colorPicker.appendChild(swatch);
216
+ }
217
+ const colorBtn = this.element.querySelector(".cf-color-btn");
218
+ colorBtn?.appendChild(this.colorPicker);
219
+ }
220
+ toggleStrokePicker() {
221
+ if (this.strokePicker) {
222
+ this.strokePicker.remove();
223
+ this.strokePicker = null;
224
+ return;
225
+ }
226
+ if (this.colorPicker) {
227
+ this.colorPicker.remove();
228
+ this.colorPicker = null;
229
+ }
230
+ this.strokePicker = document.createElement("div");
231
+ this.strokePicker.className = "cf-picker cf-stroke-picker";
232
+ for (const width of STROKE_WIDTHS) {
233
+ const option = document.createElement("button");
234
+ option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
235
+ option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
236
+ option.addEventListener("click", () => {
237
+ this.config.onStyleChange({ strokeWidth: width });
238
+ this.setStyle({ ...this.config.style, strokeWidth: width });
239
+ this.strokePicker?.remove();
240
+ this.strokePicker = null;
241
+ });
242
+ this.strokePicker.appendChild(option);
243
+ }
244
+ const strokeBtn = this.element.querySelector(".cf-stroke-btn");
245
+ strokeBtn?.appendChild(this.strokePicker);
246
+ }
247
+ };
65
248
  }
249
+ });
250
+
251
+ // src/annotation/styles.ts
252
+ function injectAnnotationStyles() {
253
+ if (stylesInjected) return;
254
+ const styleElement = document.createElement("style");
255
+ styleElement.id = "cf-annotation-styles";
256
+ styleElement.textContent = STYLES;
257
+ document.head.appendChild(styleElement);
258
+ stylesInjected = true;
66
259
  }
67
- function detectBrowser(ua) {
68
- if (ua.includes("Firefox/")) return "Firefox";
69
- if (ua.includes("Edg/")) return "Edge";
70
- if (ua.includes("Chrome/")) return "Chrome";
71
- if (ua.includes("Safari/")) return "Safari";
72
- return "Unknown";
260
+ var STYLES, stylesInjected;
261
+ var init_styles = __esm({
262
+ "src/annotation/styles.ts"() {
263
+ "use strict";
264
+ STYLES = `
265
+ /* Annotation Overlay */
266
+ .cf-annotation-overlay {
267
+ position: fixed;
268
+ top: 0;
269
+ left: 0;
270
+ right: 0;
271
+ bottom: 0;
272
+ z-index: 999999;
273
+ background: rgba(0, 0, 0, 0.85);
274
+ display: flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ animation: cf-fade-in 0.2s ease-out;
73
278
  }
74
- function detectOS(ua) {
75
- if (ua.includes("Windows")) return "Windows";
76
- if (ua.includes("Mac OS")) return "macOS";
77
- if (ua.includes("Linux")) return "Linux";
78
- if (ua.includes("Android")) return "Android";
79
- if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
80
- return "Unknown";
279
+
280
+ @keyframes cf-fade-in {
281
+ from { opacity: 0; }
282
+ to { opacity: 1; }
81
283
  }
82
284
 
83
- // src/console-interceptor.ts
84
- var MAX_LOGS = 50;
85
- var logs = [];
86
- var errorsCapture = [];
87
- var installed = false;
88
- function installInterceptors() {
89
- if (installed) return;
90
- installed = true;
91
- const origConsole = {
92
- log: console.log,
93
- warn: console.warn,
94
- error: console.error
95
- };
96
- ["log", "warn", "error"].forEach((level) => {
97
- console[level] = (...args) => {
98
- logs.push({
99
- level,
100
- message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" "),
101
- timestamp: Date.now()
102
- });
103
- if (logs.length > MAX_LOGS) logs.shift();
104
- origConsole[level](...args);
105
- };
106
- });
107
- window.addEventListener("error", (event) => {
108
- errorsCapture.push({
109
- message: event.message,
110
- filename: event.filename,
111
- lineno: event.lineno,
112
- colno: event.colno,
113
- timestamp: Date.now()
114
- });
115
- if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
116
- });
117
- window.addEventListener("unhandledrejection", (event) => {
118
- errorsCapture.push({
119
- message: String(event.reason),
120
- type: "unhandledrejection",
121
- timestamp: Date.now()
122
- });
123
- if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
124
- });
285
+ /* Editor Wrapper */
286
+ .cf-annotation-wrapper {
287
+ display: flex;
288
+ flex-direction: column;
289
+ max-width: 95vw;
290
+ max-height: 95vh;
291
+ background: #1a1a1a;
292
+ border-radius: 12px;
293
+ overflow: hidden;
294
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
125
295
  }
126
- function getConsoleLogs() {
127
- return [...logs];
296
+
297
+ /* Canvas Container */
298
+ .cf-annotation-canvas-container {
299
+ flex: 1;
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: center;
303
+ overflow: auto;
304
+ padding: 16px;
305
+ background: #0d0d0d;
128
306
  }
129
- function getJavascriptErrors() {
130
- return [...errorsCapture];
307
+
308
+ .cf-annotation-canvas {
309
+ max-width: 100%;
310
+ max-height: calc(95vh - 140px);
311
+ border-radius: 4px;
312
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
313
+ cursor: crosshair;
131
314
  }
132
- function clearLogs() {
133
- logs = [];
134
- errorsCapture = [];
315
+
316
+ /* Toolbar */
317
+ .cf-toolbar {
318
+ display: flex;
319
+ align-items: center;
320
+ gap: 8px;
321
+ padding: 12px 16px;
322
+ background: #2a2a2a;
323
+ border-bottom: 1px solid #3a3a3a;
135
324
  }
136
325
 
137
- // src/network-interceptor.ts
138
- var MAX_LOGS2 = 50;
139
- var logs2 = [];
140
- var installed2 = false;
141
- function installNetworkInterceptor() {
142
- if (installed2 || typeof window === "undefined") return;
143
- installed2 = true;
144
- const origFetch = window.fetch;
145
- window.fetch = async function(...args) {
146
- const start = Date.now();
147
- const req = new Request(...args);
148
- const entry = {
149
- method: req.method,
150
- url: req.url,
151
- type: "fetch",
152
- timestamp: start
153
- };
154
- try {
155
- const res = await origFetch.apply(this, args);
156
- entry.status = res.status;
157
- entry.duration = Date.now() - start;
158
- entry.response_type = res.headers.get("content-type") || void 0;
159
- pushLog(entry);
160
- return res;
161
- } catch (err) {
162
- entry.duration = Date.now() - start;
163
- entry.error = err.message || "Network error";
164
- pushLog(entry);
165
- throw err;
166
- }
167
- };
168
- const origOpen = XMLHttpRequest.prototype.open;
169
- const origSend = XMLHttpRequest.prototype.send;
170
- XMLHttpRequest.prototype.open = function(method, url, ...rest) {
171
- this.__cf_method = method;
172
- this.__cf_url = String(url);
173
- return origOpen.apply(this, [method, url, ...rest]);
174
- };
175
- XMLHttpRequest.prototype.send = function(...args) {
176
- const start = Date.now();
177
- const xhr = this;
178
- xhr.addEventListener("loadend", () => {
179
- const entry = {
180
- method: xhr.__cf_method || "GET",
181
- url: xhr.__cf_url || "",
182
- status: xhr.status,
183
- duration: Date.now() - start,
184
- type: "xhr",
185
- timestamp: start,
186
- response_type: xhr.getResponseHeader("content-type") || void 0
187
- };
188
- if (xhr.status === 0) entry.error = "Request failed";
189
- pushLog(entry);
190
- });
191
- return origSend.apply(this, args);
192
- };
326
+ .cf-toolbar-section {
327
+ display: flex;
328
+ align-items: center;
329
+ gap: 4px;
193
330
  }
194
- function pushLog(entry) {
195
- if (entry.url.includes("/sdk/feedback")) return;
196
- logs2.push(entry);
197
- if (logs2.length > MAX_LOGS2) logs2.shift();
331
+
332
+ .cf-toolbar-separator {
333
+ width: 1px;
334
+ height: 24px;
335
+ background: #4a4a4a;
336
+ margin: 0 8px;
198
337
  }
199
- function getNetworkLogs() {
200
- return [...logs2];
338
+
339
+ .cf-toolbar-btn {
340
+ display: flex;
341
+ align-items: center;
342
+ justify-content: center;
343
+ width: 36px;
344
+ height: 36px;
345
+ padding: 0;
346
+ border: none;
347
+ border-radius: 8px;
348
+ background: transparent;
349
+ color: #999;
350
+ cursor: pointer;
351
+ transition: all 0.15s ease;
201
352
  }
202
- function clearNetworkLogs() {
203
- logs2 = [];
353
+
354
+ .cf-toolbar-btn:hover {
355
+ background: #3a3a3a;
356
+ color: #fff;
357
+ }
358
+
359
+ .cf-toolbar-btn.active {
360
+ background: #007AFF;
361
+ color: #fff;
362
+ }
363
+
364
+ .cf-toolbar-btn svg {
365
+ width: 20px;
366
+ height: 20px;
367
+ }
368
+
369
+ /* Color Button */
370
+ .cf-color-btn {
371
+ position: relative;
372
+ }
373
+
374
+ .cf-color-preview {
375
+ width: 20px;
376
+ height: 20px;
377
+ border-radius: 50%;
378
+ border: 2px solid #fff;
379
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
380
+ }
381
+
382
+ /* Stroke Button */
383
+ .cf-stroke-btn {
384
+ width: auto;
385
+ padding: 0 12px;
386
+ }
387
+
388
+ .cf-stroke-preview {
389
+ font-size: 12px;
390
+ font-weight: 500;
391
+ color: inherit;
392
+ }
393
+
394
+ /* Pickers */
395
+ .cf-picker {
396
+ position: absolute;
397
+ top: 100%;
398
+ left: 50%;
399
+ transform: translateX(-50%);
400
+ margin-top: 8px;
401
+ padding: 8px;
402
+ background: #2a2a2a;
403
+ border-radius: 8px;
404
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
405
+ z-index: 10;
406
+ }
407
+
408
+ .cf-color-picker {
409
+ display: grid;
410
+ grid-template-columns: repeat(5, 1fr);
411
+ gap: 4px;
412
+ width: 140px;
413
+ }
414
+
415
+ .cf-color-swatch {
416
+ width: 24px;
417
+ height: 24px;
418
+ border-radius: 50%;
419
+ border: 2px solid transparent;
420
+ cursor: pointer;
421
+ transition: transform 0.15s ease;
422
+ }
423
+
424
+ .cf-color-swatch:hover {
425
+ transform: scale(1.15);
426
+ }
427
+
428
+ .cf-color-swatch.active {
429
+ border-color: #fff;
430
+ }
431
+
432
+ .cf-stroke-picker {
433
+ display: flex;
434
+ flex-direction: column;
435
+ gap: 4px;
436
+ min-width: 80px;
204
437
  }
205
438
 
439
+ .cf-stroke-option {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 8px;
443
+ padding: 6px 10px;
444
+ border: none;
445
+ border-radius: 4px;
446
+ background: transparent;
447
+ color: #999;
448
+ font-size: 12px;
449
+ cursor: pointer;
450
+ transition: all 0.15s ease;
451
+ }
452
+
453
+ .cf-stroke-option:hover {
454
+ background: #3a3a3a;
455
+ color: #fff;
456
+ }
457
+
458
+ .cf-stroke-option.active {
459
+ background: #007AFF;
460
+ color: #fff;
461
+ }
462
+
463
+ .cf-stroke-option span {
464
+ width: 24px;
465
+ background: currentColor;
466
+ border-radius: 2px;
467
+ }
468
+
469
+ /* Action Buttons */
470
+ .cf-annotation-actions {
471
+ display: flex;
472
+ justify-content: flex-end;
473
+ gap: 12px;
474
+ padding: 12px 16px;
475
+ background: #2a2a2a;
476
+ border-top: 1px solid #3a3a3a;
477
+ }
478
+
479
+ .cf-btn {
480
+ display: flex;
481
+ align-items: center;
482
+ justify-content: center;
483
+ padding: 10px 20px;
484
+ border: none;
485
+ border-radius: 8px;
486
+ font-size: 14px;
487
+ font-weight: 500;
488
+ cursor: pointer;
489
+ transition: all 0.15s ease;
490
+ }
491
+
492
+ .cf-btn-primary {
493
+ background: #007AFF;
494
+ color: #fff;
495
+ }
496
+
497
+ .cf-btn-primary:hover {
498
+ background: #0066DD;
499
+ }
500
+
501
+ .cf-btn-secondary {
502
+ background: #3a3a3a;
503
+ color: #fff;
504
+ }
505
+
506
+ .cf-btn-secondary:hover {
507
+ background: #4a4a4a;
508
+ }
509
+
510
+ /* Text Input */
511
+ .cf-annotation-text-input {
512
+ position: fixed;
513
+ z-index: 1000000;
514
+ padding: 4px 8px;
515
+ border: 2px solid #007AFF;
516
+ border-radius: 4px;
517
+ background: rgba(255, 255, 255, 0.95);
518
+ font-size: 16px;
519
+ font-family: system-ui, -apple-system, sans-serif;
520
+ outline: none;
521
+ min-width: 150px;
522
+ }
523
+
524
+ /* Tool-specific cursors */
525
+ .cf-annotation-canvas[data-tool="select"] { cursor: default; }
526
+ .cf-annotation-canvas[data-tool="rectangle"] { cursor: crosshair; }
527
+ .cf-annotation-canvas[data-tool="ellipse"] { cursor: crosshair; }
528
+ .cf-annotation-canvas[data-tool="arrow"] { cursor: crosshair; }
529
+ .cf-annotation-canvas[data-tool="line"] { cursor: crosshair; }
530
+ .cf-annotation-canvas[data-tool="highlight"] { cursor: crosshair; }
531
+ .cf-annotation-canvas[data-tool="blur"] { cursor: crosshair; }
532
+ .cf-annotation-canvas[data-tool="text"] { cursor: text; }
533
+ .cf-annotation-canvas[data-tool="freehand"] { cursor: crosshair; }
534
+
535
+ /* Responsive */
536
+ @media (max-width: 768px) {
537
+ .cf-toolbar {
538
+ flex-wrap: wrap;
539
+ justify-content: center;
540
+ }
541
+
542
+ .cf-toolbar-separator {
543
+ display: none;
544
+ }
545
+
546
+ .cf-annotation-actions {
547
+ justify-content: stretch;
548
+ }
549
+
550
+ .cf-annotation-actions .cf-btn {
551
+ flex: 1;
552
+ }
553
+ }
554
+ `;
555
+ stylesInjected = false;
556
+ }
557
+ });
558
+
559
+ // src/annotation/editor.ts
560
+ var AnnotationEditor;
561
+ var init_editor = __esm({
562
+ "src/annotation/editor.ts"() {
563
+ "use strict";
564
+ init_types();
565
+ init_toolbar();
566
+ init_styles();
567
+ AnnotationEditor = class {
568
+ constructor(config2) {
569
+ this.container = null;
570
+ this.canvas = null;
571
+ this.ctx = null;
572
+ this.toolbar = null;
573
+ this.annotations = [];
574
+ this.backgroundImage = null;
575
+ this.state = {
576
+ activeTool: "rectangle",
577
+ style: { ...DEFAULT_STYLE },
578
+ isDrawing: false,
579
+ currentAnnotation: null
580
+ };
581
+ this.startPoint = null;
582
+ this.freehandPoints = [];
583
+ this.textInput = null;
584
+ this.config = {
585
+ ...config2,
586
+ tools: config2.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
587
+ defaultStyle: config2.defaultStyle || DEFAULT_STYLE
588
+ };
589
+ this.state.style = { ...this.config.defaultStyle };
590
+ }
591
+ /**
592
+ * Open the annotation editor with a screenshot
593
+ */
594
+ async open(screenshotDataUrl2) {
595
+ injectAnnotationStyles();
596
+ this.backgroundImage = await this.loadImage(screenshotDataUrl2);
597
+ this.createEditorUI();
598
+ this.setupEventListeners();
599
+ this.render();
600
+ }
601
+ /**
602
+ * Close the editor
603
+ */
604
+ close() {
605
+ if (this.container) {
606
+ this.container.remove();
607
+ this.container = null;
608
+ }
609
+ this.canvas = null;
610
+ this.ctx = null;
611
+ this.toolbar = null;
612
+ this.annotations = [];
613
+ this.backgroundImage = null;
614
+ }
615
+ /**
616
+ * Get the annotated image as data URL
617
+ */
618
+ getAnnotatedImage() {
619
+ if (!this.canvas || !this.ctx) return "";
620
+ const tempCanvas = document.createElement("canvas");
621
+ tempCanvas.width = this.canvas.width;
622
+ tempCanvas.height = this.canvas.height;
623
+ const tempCtx = tempCanvas.getContext("2d");
624
+ if (this.backgroundImage) {
625
+ tempCtx.drawImage(this.backgroundImage, 0, 0);
626
+ }
627
+ this.drawAnnotations(tempCtx);
628
+ return tempCanvas.toDataURL("image/png");
629
+ }
630
+ /**
631
+ * Get annotations data
632
+ */
633
+ getAnnotations() {
634
+ return [...this.annotations];
635
+ }
636
+ // Private methods
637
+ loadImage(src) {
638
+ return new Promise((resolve, reject) => {
639
+ const img = new Image();
640
+ img.onload = () => resolve(img);
641
+ img.onerror = reject;
642
+ img.src = src;
643
+ });
644
+ }
645
+ createEditorUI() {
646
+ this.container = document.createElement("div");
647
+ this.container.className = "cf-annotation-overlay";
648
+ const wrapper = document.createElement("div");
649
+ wrapper.className = "cf-annotation-wrapper";
650
+ const canvasContainer = document.createElement("div");
651
+ canvasContainer.className = "cf-annotation-canvas-container";
652
+ this.canvas = document.createElement("canvas");
653
+ this.canvas.className = "cf-annotation-canvas";
654
+ this.canvas.width = this.backgroundImage?.width || 1920;
655
+ this.canvas.height = this.backgroundImage?.height || 1080;
656
+ this.ctx = this.canvas.getContext("2d");
657
+ canvasContainer.appendChild(this.canvas);
658
+ this.toolbar = new AnnotationToolbar({
659
+ tools: this.config.tools,
660
+ activeTool: this.state.activeTool,
661
+ style: this.state.style,
662
+ onToolChange: (tool) => this.setActiveTool(tool),
663
+ onStyleChange: (style) => this.setStyle(style),
664
+ onUndo: () => this.undo(),
665
+ onClear: () => this.clearAll(),
666
+ onSave: () => this.save(),
667
+ onCancel: () => this.cancel()
668
+ });
669
+ const actions = document.createElement("div");
670
+ actions.className = "cf-annotation-actions";
671
+ actions.innerHTML = `
672
+ <button class="cf-btn cf-btn-secondary" data-action="cancel">Annuler</button>
673
+ <button class="cf-btn cf-btn-primary" data-action="save">Enregistrer</button>
674
+ `;
675
+ wrapper.appendChild(this.toolbar.getElement());
676
+ wrapper.appendChild(canvasContainer);
677
+ wrapper.appendChild(actions);
678
+ this.container.appendChild(wrapper);
679
+ document.body.appendChild(this.container);
680
+ actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
681
+ actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
682
+ }
683
+ setupEventListeners() {
684
+ if (!this.canvas) return;
685
+ this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
686
+ this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
687
+ this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
688
+ this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
689
+ this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
690
+ this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
691
+ this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
692
+ document.addEventListener("keydown", this.handleKeyDown.bind(this));
693
+ }
694
+ handleMouseDown(e) {
695
+ const point = this.getCanvasPoint(e);
696
+ this.startDrawing(point);
697
+ }
698
+ handleMouseMove(e) {
699
+ const point = this.getCanvasPoint(e);
700
+ this.continueDrawing(point);
701
+ }
702
+ handleMouseUp(_e) {
703
+ this.finishDrawing();
704
+ }
705
+ handleTouchStart(e) {
706
+ e.preventDefault();
707
+ const touch = e.touches[0];
708
+ const point = this.getCanvasPointFromTouch(touch);
709
+ this.startDrawing(point);
710
+ }
711
+ handleTouchMove(e) {
712
+ e.preventDefault();
713
+ const touch = e.touches[0];
714
+ const point = this.getCanvasPointFromTouch(touch);
715
+ this.continueDrawing(point);
716
+ }
717
+ handleTouchEnd(e) {
718
+ e.preventDefault();
719
+ this.finishDrawing();
720
+ }
721
+ handleKeyDown(e) {
722
+ if ((e.ctrlKey || e.metaKey) && e.key === "z") {
723
+ e.preventDefault();
724
+ this.undo();
725
+ }
726
+ if (e.key === "Escape") {
727
+ if (this.state.isDrawing) {
728
+ this.state.isDrawing = false;
729
+ this.state.currentAnnotation = null;
730
+ this.render();
731
+ } else {
732
+ this.cancel();
733
+ }
734
+ }
735
+ if (e.key === "Enter" && !this.state.isDrawing) {
736
+ this.save();
737
+ }
738
+ }
739
+ getCanvasPoint(e) {
740
+ const rect = this.canvas.getBoundingClientRect();
741
+ const scaleX = this.canvas.width / rect.width;
742
+ const scaleY = this.canvas.height / rect.height;
743
+ return {
744
+ x: (e.clientX - rect.left) * scaleX,
745
+ y: (e.clientY - rect.top) * scaleY
746
+ };
747
+ }
748
+ getCanvasPointFromTouch(touch) {
749
+ const rect = this.canvas.getBoundingClientRect();
750
+ const scaleX = this.canvas.width / rect.width;
751
+ const scaleY = this.canvas.height / rect.height;
752
+ return {
753
+ x: (touch.clientX - rect.left) * scaleX,
754
+ y: (touch.clientY - rect.top) * scaleY
755
+ };
756
+ }
757
+ startDrawing(point) {
758
+ if (this.state.activeTool === "select") return;
759
+ this.state.isDrawing = true;
760
+ this.startPoint = point;
761
+ if (this.state.activeTool === "text") {
762
+ this.showTextInput(point);
763
+ return;
764
+ }
765
+ if (this.state.activeTool === "freehand") {
766
+ this.freehandPoints = [point];
767
+ }
768
+ }
769
+ continueDrawing(point) {
770
+ if (!this.state.isDrawing || !this.startPoint) return;
771
+ if (this.state.activeTool === "freehand") {
772
+ this.freehandPoints.push(point);
773
+ }
774
+ this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
775
+ this.render();
776
+ }
777
+ finishDrawing() {
778
+ if (!this.state.isDrawing || !this.state.currentAnnotation) {
779
+ this.state.isDrawing = false;
780
+ return;
781
+ }
782
+ if (this.isValidAnnotation(this.state.currentAnnotation)) {
783
+ this.annotations.push(this.state.currentAnnotation);
784
+ }
785
+ this.state.isDrawing = false;
786
+ this.state.currentAnnotation = null;
787
+ this.startPoint = null;
788
+ this.freehandPoints = [];
789
+ this.render();
790
+ }
791
+ isValidAnnotation(annotation) {
792
+ switch (annotation.type) {
793
+ case "rectangle":
794
+ case "ellipse":
795
+ case "highlight":
796
+ case "blur":
797
+ const bounds = annotation.bounds;
798
+ return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
799
+ case "arrow":
800
+ case "line":
801
+ const start = annotation.start;
802
+ const end = annotation.end;
803
+ const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
804
+ return dist > 10;
805
+ case "freehand":
806
+ return annotation.points.length > 2;
807
+ case "text":
808
+ return !!annotation.text?.trim();
809
+ default:
810
+ return true;
811
+ }
812
+ }
813
+ createAnnotation(start, end) {
814
+ const id = this.generateId();
815
+ const baseStyle = { ...this.state.style };
816
+ switch (this.state.activeTool) {
817
+ case "rectangle":
818
+ return {
819
+ id,
820
+ type: "rectangle",
821
+ bounds: this.createBounds(start, end),
822
+ style: baseStyle,
823
+ timestamp: Date.now()
824
+ };
825
+ case "ellipse":
826
+ return {
827
+ id,
828
+ type: "ellipse",
829
+ bounds: this.createBounds(start, end),
830
+ style: baseStyle,
831
+ timestamp: Date.now()
832
+ };
833
+ case "arrow":
834
+ return {
835
+ id,
836
+ type: "arrow",
837
+ start: { ...start },
838
+ end: { ...end },
839
+ style: baseStyle,
840
+ timestamp: Date.now()
841
+ };
842
+ case "line":
843
+ return {
844
+ id,
845
+ type: "line",
846
+ start: { ...start },
847
+ end: { ...end },
848
+ style: baseStyle,
849
+ timestamp: Date.now()
850
+ };
851
+ case "highlight":
852
+ return {
853
+ id,
854
+ type: "highlight",
855
+ bounds: this.createBounds(start, end),
856
+ style: { ...baseStyle, ...HIGHLIGHT_STYLE },
857
+ timestamp: Date.now()
858
+ };
859
+ case "blur":
860
+ return {
861
+ id,
862
+ type: "blur",
863
+ bounds: this.createBounds(start, end),
864
+ style: { ...baseStyle, ...BLUR_STYLE },
865
+ blurAmount: 10,
866
+ timestamp: Date.now()
867
+ };
868
+ case "freehand":
869
+ return {
870
+ id,
871
+ type: "freehand",
872
+ points: [...this.freehandPoints],
873
+ style: baseStyle,
874
+ timestamp: Date.now()
875
+ };
876
+ default:
877
+ return {
878
+ id,
879
+ type: "rectangle",
880
+ bounds: this.createBounds(start, end),
881
+ style: baseStyle,
882
+ timestamp: Date.now()
883
+ };
884
+ }
885
+ }
886
+ createBounds(start, end) {
887
+ return {
888
+ x: Math.min(start.x, end.x),
889
+ y: Math.min(start.y, end.y),
890
+ width: Math.abs(end.x - start.x),
891
+ height: Math.abs(end.y - start.y)
892
+ };
893
+ }
894
+ showTextInput(point) {
895
+ if (this.textInput) {
896
+ this.textInput.remove();
897
+ }
898
+ const rect = this.canvas.getBoundingClientRect();
899
+ const scaleX = rect.width / this.canvas.width;
900
+ const scaleY = rect.height / this.canvas.height;
901
+ this.textInput = document.createElement("input");
902
+ this.textInput.type = "text";
903
+ this.textInput.className = "cf-annotation-text-input";
904
+ this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
905
+ this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
906
+ this.textInput.style.color = this.state.style.strokeColor;
907
+ this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
908
+ this.textInput.placeholder = "Tapez votre texte...";
909
+ document.body.appendChild(this.textInput);
910
+ this.textInput.focus();
911
+ const handleTextSubmit = () => {
912
+ if (this.textInput && this.textInput.value.trim()) {
913
+ const textAnnotation = {
914
+ id: this.generateId(),
915
+ type: "text",
916
+ position: point,
917
+ text: this.textInput.value.trim(),
918
+ style: { ...this.state.style },
919
+ timestamp: Date.now()
920
+ };
921
+ this.annotations.push(textAnnotation);
922
+ this.render();
923
+ }
924
+ this.textInput?.remove();
925
+ this.textInput = null;
926
+ this.state.isDrawing = false;
927
+ };
928
+ this.textInput.addEventListener("blur", handleTextSubmit);
929
+ this.textInput.addEventListener("keydown", (e) => {
930
+ if (e.key === "Enter") {
931
+ handleTextSubmit();
932
+ } else if (e.key === "Escape") {
933
+ this.textInput?.remove();
934
+ this.textInput = null;
935
+ this.state.isDrawing = false;
936
+ }
937
+ });
938
+ }
939
+ generateId() {
940
+ return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
941
+ }
942
+ setActiveTool(tool) {
943
+ this.state.activeTool = tool;
944
+ this.toolbar?.setActiveTool(tool);
945
+ if (this.canvas) {
946
+ this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
947
+ }
948
+ }
949
+ setStyle(style) {
950
+ this.state.style = { ...this.state.style, ...style };
951
+ this.toolbar?.setStyle(this.state.style);
952
+ }
953
+ undo() {
954
+ if (this.annotations.length > 0) {
955
+ this.annotations.pop();
956
+ this.render();
957
+ }
958
+ }
959
+ clearAll() {
960
+ this.annotations = [];
961
+ this.render();
962
+ }
963
+ save() {
964
+ const imageData = this.getAnnotatedImage();
965
+ this.config.onSave?.(this.annotations, imageData);
966
+ this.close();
967
+ }
968
+ cancel() {
969
+ this.config.onCancel?.();
970
+ this.close();
971
+ }
972
+ render() {
973
+ if (!this.ctx || !this.canvas) return;
974
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
975
+ if (this.backgroundImage) {
976
+ this.ctx.drawImage(this.backgroundImage, 0, 0);
977
+ }
978
+ this.drawAnnotations(this.ctx);
979
+ if (this.state.currentAnnotation) {
980
+ this.drawAnnotation(this.ctx, this.state.currentAnnotation);
981
+ }
982
+ }
983
+ drawAnnotations(ctx) {
984
+ for (const annotation of this.annotations) {
985
+ this.drawAnnotation(ctx, annotation);
986
+ }
987
+ }
988
+ drawAnnotation(ctx, annotation) {
989
+ ctx.save();
990
+ ctx.globalAlpha = annotation.style.opacity;
991
+ ctx.strokeStyle = annotation.style.strokeColor;
992
+ ctx.fillStyle = annotation.style.fillColor;
993
+ ctx.lineWidth = annotation.style.strokeWidth;
994
+ ctx.lineCap = "round";
995
+ ctx.lineJoin = "round";
996
+ switch (annotation.type) {
997
+ case "rectangle":
998
+ this.drawRectangle(ctx, annotation.bounds, annotation.style);
999
+ break;
1000
+ case "ellipse":
1001
+ this.drawEllipse(ctx, annotation.bounds, annotation.style);
1002
+ break;
1003
+ case "arrow":
1004
+ this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
1005
+ break;
1006
+ case "line":
1007
+ this.drawLine(ctx, annotation.start, annotation.end);
1008
+ break;
1009
+ case "highlight":
1010
+ this.drawHighlight(ctx, annotation.bounds, annotation.style);
1011
+ break;
1012
+ case "blur":
1013
+ this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
1014
+ break;
1015
+ case "text":
1016
+ this.drawText(ctx, annotation.position, annotation.text, annotation.style);
1017
+ break;
1018
+ case "freehand":
1019
+ this.drawFreehand(ctx, annotation.points);
1020
+ break;
1021
+ }
1022
+ ctx.restore();
1023
+ }
1024
+ drawRectangle(ctx, bounds, style) {
1025
+ if (style.fillColor !== "transparent") {
1026
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1027
+ }
1028
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1029
+ }
1030
+ drawEllipse(ctx, bounds, style) {
1031
+ const centerX = bounds.x + bounds.width / 2;
1032
+ const centerY = bounds.y + bounds.height / 2;
1033
+ const radiusX = bounds.width / 2;
1034
+ const radiusY = bounds.height / 2;
1035
+ ctx.beginPath();
1036
+ ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
1037
+ if (style.fillColor !== "transparent") {
1038
+ ctx.fill();
1039
+ }
1040
+ ctx.stroke();
1041
+ }
1042
+ drawArrow(ctx, start, end, style) {
1043
+ const headLength = 15 + style.strokeWidth * 2;
1044
+ const angle = Math.atan2(end.y - start.y, end.x - start.x);
1045
+ ctx.beginPath();
1046
+ ctx.moveTo(start.x, start.y);
1047
+ ctx.lineTo(end.x, end.y);
1048
+ ctx.stroke();
1049
+ ctx.beginPath();
1050
+ ctx.moveTo(end.x, end.y);
1051
+ ctx.lineTo(
1052
+ end.x - headLength * Math.cos(angle - Math.PI / 6),
1053
+ end.y - headLength * Math.sin(angle - Math.PI / 6)
1054
+ );
1055
+ ctx.lineTo(
1056
+ end.x - headLength * Math.cos(angle + Math.PI / 6),
1057
+ end.y - headLength * Math.sin(angle + Math.PI / 6)
1058
+ );
1059
+ ctx.closePath();
1060
+ ctx.fillStyle = style.strokeColor;
1061
+ ctx.fill();
1062
+ }
1063
+ drawLine(ctx, start, end) {
1064
+ ctx.beginPath();
1065
+ ctx.moveTo(start.x, start.y);
1066
+ ctx.lineTo(end.x, end.y);
1067
+ ctx.stroke();
1068
+ }
1069
+ drawHighlight(ctx, bounds, style) {
1070
+ ctx.fillStyle = style.fillColor;
1071
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1072
+ }
1073
+ drawBlur(ctx, bounds, blurAmount) {
1074
+ const pixelSize = Math.max(blurAmount, 5);
1075
+ if (this.backgroundImage) {
1076
+ const tempCanvas = document.createElement("canvas");
1077
+ tempCanvas.width = bounds.width;
1078
+ tempCanvas.height = bounds.height;
1079
+ const tempCtx = tempCanvas.getContext("2d");
1080
+ tempCtx.drawImage(
1081
+ this.backgroundImage,
1082
+ bounds.x,
1083
+ bounds.y,
1084
+ bounds.width,
1085
+ bounds.height,
1086
+ 0,
1087
+ 0,
1088
+ bounds.width,
1089
+ bounds.height
1090
+ );
1091
+ const w = tempCanvas.width;
1092
+ const h = tempCanvas.height;
1093
+ tempCtx.imageSmoothingEnabled = false;
1094
+ tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
1095
+ tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
1096
+ ctx.drawImage(tempCanvas, bounds.x, bounds.y);
1097
+ }
1098
+ ctx.strokeStyle = "#999";
1099
+ ctx.lineWidth = 1;
1100
+ ctx.setLineDash([5, 5]);
1101
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1102
+ ctx.setLineDash([]);
1103
+ }
1104
+ drawText(ctx, position, text, style) {
1105
+ ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
1106
+ ctx.fillStyle = style.strokeColor;
1107
+ ctx.textBaseline = "top";
1108
+ const metrics = ctx.measureText(text);
1109
+ const padding = 4;
1110
+ ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
1111
+ ctx.fillRect(
1112
+ position.x - padding,
1113
+ position.y - padding,
1114
+ metrics.width + padding * 2,
1115
+ (style.fontSize || 16) + padding * 2
1116
+ );
1117
+ ctx.fillStyle = style.strokeColor;
1118
+ ctx.fillText(text, position.x, position.y);
1119
+ }
1120
+ drawFreehand(ctx, points) {
1121
+ if (points.length < 2) return;
1122
+ ctx.beginPath();
1123
+ ctx.moveTo(points[0].x, points[0].y);
1124
+ for (let i = 1; i < points.length; i++) {
1125
+ ctx.lineTo(points[i].x, points[i].y);
1126
+ }
1127
+ ctx.stroke();
1128
+ }
1129
+ };
1130
+ }
1131
+ });
1132
+
206
1133
  // src/widget.ts
207
- var DEFAULT_CONFIG = {
208
- position: "bottom-right",
209
- color: "#1e3a5f",
210
- text: "Report Bug",
211
- showOnInit: true
212
- };
213
- var container = null;
214
- var onSubmitCallback = null;
215
- var screenshotDataUrl = null;
216
- var annotationsData = [];
217
- var isExpanded = false;
1134
+ var widget_exports = {};
1135
+ __export(widget_exports, {
1136
+ mountWidget: () => mountWidget,
1137
+ openFeedbackModal: () => openFeedbackModal,
1138
+ unmountWidget: () => unmountWidget
1139
+ });
218
1140
  function mountWidget(config2 = {}, onSubmit, opts) {
219
1141
  if (container) return;
220
1142
  onSubmitCallback = onSubmit;
1143
+ currentUser = opts?.user;
221
1144
  const cfg = { ...DEFAULT_CONFIG, ...config2 };
1145
+ currentCfg = cfg;
222
1146
  container = document.createElement("div");
223
1147
  container.id = "checkflow-widget";
224
1148
  injectStyles(cfg);
@@ -227,15 +1151,29 @@ function mountWidget(config2 = {}, onSubmit, opts) {
227
1151
  const btn = container.querySelector("#cf-trigger");
228
1152
  btn?.addEventListener("click", () => openModal(cfg, opts));
229
1153
  }
1154
+ function openFeedbackModal(config2 = {}, onSubmit, opts) {
1155
+ onSubmitCallback = onSubmit;
1156
+ currentUser = opts?.user;
1157
+ const cfg = { ...DEFAULT_CONFIG, ...config2 };
1158
+ currentCfg = cfg;
1159
+ injectStyles(cfg);
1160
+ openModal(cfg, opts);
1161
+ }
1162
+ function getBackdropClass(cfg, expanded) {
1163
+ if (expanded) return "cf-backdrop--center";
1164
+ if (cfg.position === "center") return "cf-backdrop--center";
1165
+ return "cf-backdrop--corner cf-pos-" + (cfg.position || "bottom-right");
1166
+ }
230
1167
  function openModal(cfg, opts) {
231
- if (!container) return;
232
1168
  const existing = document.getElementById("cf-modal-backdrop");
233
1169
  if (existing) existing.remove();
234
1170
  isExpanded = false;
235
1171
  screenshotDataUrl = null;
1172
+ annotatedImageUrl = null;
236
1173
  annotationsData = [];
237
1174
  const backdrop = document.createElement("div");
238
1175
  backdrop.id = "cf-modal-backdrop";
1176
+ backdrop.className = getBackdropClass(cfg, false);
239
1177
  backdrop.innerHTML = getModalHTML(cfg, false);
240
1178
  document.body.appendChild(backdrop);
241
1179
  backdrop.addEventListener("click", (e) => {
@@ -248,6 +1186,7 @@ function closeModal() {
248
1186
  if (backdrop) backdrop.remove();
249
1187
  isExpanded = false;
250
1188
  screenshotDataUrl = null;
1189
+ annotatedImageUrl = null;
251
1190
  annotationsData = [];
252
1191
  }
253
1192
  function expandModal(cfg, opts) {
@@ -257,6 +1196,7 @@ function expandModal(cfg, opts) {
257
1196
  const email = backdrop.querySelector("#cf-email")?.value || "";
258
1197
  const desc = backdrop.querySelector("#cf-desc")?.value || "";
259
1198
  isExpanded = true;
1199
+ backdrop.className = getBackdropClass(cfg, true);
260
1200
  backdrop.innerHTML = getModalHTML(cfg, true);
261
1201
  bindModalEvents(cfg, opts);
262
1202
  const nameEl = backdrop.querySelector("#cf-name");
@@ -274,7 +1214,9 @@ function collapseModal(cfg, opts) {
274
1214
  const desc = backdrop.querySelector("#cf-desc")?.value || "";
275
1215
  isExpanded = false;
276
1216
  screenshotDataUrl = null;
1217
+ annotatedImageUrl = null;
277
1218
  annotationsData = [];
1219
+ backdrop.className = getBackdropClass(cfg, false);
278
1220
  backdrop.innerHTML = getModalHTML(cfg, false);
279
1221
  bindModalEvents(cfg, opts);
280
1222
  const nameEl = backdrop.querySelector("#cf-name");
@@ -284,6 +1226,30 @@ function collapseModal(cfg, opts) {
284
1226
  if (emailEl) emailEl.value = email;
285
1227
  if (descEl) descEl.value = desc;
286
1228
  }
1229
+ function openAnnotationEditor(cfg, opts) {
1230
+ if (!screenshotDataUrl) return;
1231
+ const backdrop = document.getElementById("cf-modal-backdrop");
1232
+ if (backdrop) backdrop.style.display = "none";
1233
+ const editor = new AnnotationEditor({
1234
+ tools: ["rectangle", "ellipse", "arrow", "highlight", "blur", "text", "freehand"],
1235
+ defaultStyle: { strokeColor: "#FF3B30", strokeWidth: 3, fillColor: "transparent", opacity: 1, fontSize: 16, fontFamily: "system-ui, -apple-system, sans-serif" },
1236
+ onSave: (annotations, imageData) => {
1237
+ annotationsData = annotations;
1238
+ annotatedImageUrl = imageData;
1239
+ if (backdrop) {
1240
+ backdrop.style.display = "flex";
1241
+ const img = backdrop.querySelector("#cf-screenshot-img");
1242
+ if (img) img.src = annotatedImageUrl;
1243
+ const annotBtn = backdrop.querySelector("#cf-annotate-btn");
1244
+ if (annotBtn) annotBtn.textContent = `Annoter (${annotationsData.length})`;
1245
+ }
1246
+ },
1247
+ onCancel: () => {
1248
+ if (backdrop) backdrop.style.display = "flex";
1249
+ }
1250
+ });
1251
+ editor.open(screenshotDataUrl);
1252
+ }
287
1253
  function bindModalEvents(cfg, opts) {
288
1254
  const backdrop = document.getElementById("cf-modal-backdrop");
289
1255
  if (!backdrop) return;
@@ -306,38 +1272,15 @@ function bindModalEvents(cfg, opts) {
306
1272
  backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
307
1273
  collapseModal(cfg, opts);
308
1274
  });
309
- backdrop.querySelector("#cf-highlight-btn")?.addEventListener("click", async () => {
310
- if (!opts?.onHighlight) return;
311
- backdrop.style.display = "none";
312
- try {
313
- const annotations = await opts.onHighlight();
314
- if (annotations && annotations.length > 0) {
315
- annotationsData = annotations;
316
- }
317
- } catch {
318
- }
319
- backdrop.style.display = "flex";
320
- const hlBtn = backdrop.querySelector("#cf-highlight-btn");
321
- if (hlBtn && annotationsData.length > 0) {
322
- hlBtn.textContent = `Surligner (${annotationsData.length})`;
323
- }
324
- });
325
- backdrop.querySelector("#cf-mask-btn")?.addEventListener("click", async () => {
326
- if (!opts?.onHighlight) return;
327
- backdrop.style.display = "none";
328
- try {
329
- const annotations = await opts.onHighlight();
330
- if (annotations && annotations.length > 0) {
331
- annotationsData = [...annotationsData, ...annotations];
332
- }
333
- } catch {
334
- }
335
- backdrop.style.display = "flex";
1275
+ backdrop.querySelector("#cf-annotate-btn")?.addEventListener("click", () => {
1276
+ openAnnotationEditor(cfg, opts);
336
1277
  });
337
1278
  backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
338
1279
  const desc = backdrop.querySelector("#cf-desc")?.value;
339
- const name = backdrop.querySelector("#cf-name")?.value;
340
- const email = backdrop.querySelector("#cf-email")?.value;
1280
+ const nameEl = backdrop.querySelector("#cf-name");
1281
+ const emailEl = backdrop.querySelector("#cf-email");
1282
+ const name = nameEl?.value || currentUser?.name;
1283
+ const email = emailEl?.value || currentUser?.email;
341
1284
  if (!desc?.trim()) {
342
1285
  const descEl = backdrop.querySelector("#cf-desc");
343
1286
  if (descEl) {
@@ -351,7 +1294,7 @@ function bindModalEvents(cfg, opts) {
351
1294
  description: desc.trim(),
352
1295
  type: "BUG",
353
1296
  priority: "MEDIUM",
354
- screenshot: screenshotDataUrl || void 0,
1297
+ screenshot: annotatedImageUrl || screenshotDataUrl || void 0,
355
1298
  annotations: annotationsData.length > 0 ? annotationsData : void 0,
356
1299
  reporter_name: name?.trim() || void 0,
357
1300
  reporter_email: email?.trim() || void 0
@@ -379,33 +1322,93 @@ function showToast(msg) {
379
1322
  setTimeout(() => toast.remove(), 3e3);
380
1323
  }
381
1324
  function injectStyles(cfg) {
1325
+ const existingStyle = document.getElementById("cf-widget-styles");
1326
+ if (existingStyle) existingStyle.remove();
1327
+ const btn = cfg.button || {};
1328
+ const sizePreset = SIZE_PRESETS[btn.size || "default"] || SIZE_PRESETS.default;
1329
+ const variantPreset = VARIANT_PRESETS[btn.variant || "default"] || VARIANT_PRESETS.default;
1330
+ const bgColor = btn.backgroundColor || cfg.color || variantPreset.bg;
1331
+ const textColor = btn.textColor || variantPreset.color;
1332
+ const borderRadius = btn.borderRadius || "8px";
1333
+ const padding = btn.padding || sizePreset.padding;
1334
+ const fontSize = btn.fontSize || sizePreset.fontSize;
1335
+ const fontWeight = btn.fontWeight || "500";
1336
+ const border = btn.border || variantPreset.border;
1337
+ const boxShadow = btn.boxShadow || "0 2px 8px rgba(0,0,0,0.15)";
1338
+ const primaryColor = cfg.color || "#1e3a5f";
382
1339
  const style = document.createElement("style");
383
1340
  style.id = "cf-widget-styles";
384
1341
  style.textContent = `
385
1342
  @keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
386
1343
  @keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
387
1344
 
1345
+ /* User card styles */
1346
+ .cf-user-card {
1347
+ display:flex;
1348
+ align-items:center;
1349
+ gap:12px;
1350
+ padding:12px;
1351
+ background:#f8f9fa;
1352
+ border-radius:10px;
1353
+ margin-bottom:4px;
1354
+ }
1355
+ .cf-user-avatar {
1356
+ width:40px;
1357
+ height:40px;
1358
+ border-radius:50%;
1359
+ object-fit:cover;
1360
+ }
1361
+ .cf-user-avatar-placeholder {
1362
+ width:40px;
1363
+ height:40px;
1364
+ border-radius:50%;
1365
+ background:${primaryColor};
1366
+ color:#fff;
1367
+ display:flex;
1368
+ align-items:center;
1369
+ justify-content:center;
1370
+ font-size:16px;
1371
+ font-weight:600;
1372
+ }
1373
+ .cf-user-info {
1374
+ flex:1;
1375
+ min-width:0;
1376
+ }
1377
+ .cf-user-name {
1378
+ font-size:14px;
1379
+ font-weight:600;
1380
+ color:#1a1a2e;
1381
+ }
1382
+ .cf-user-email {
1383
+ font-size:12px;
1384
+ color:#666;
1385
+ overflow:hidden;
1386
+ text-overflow:ellipsis;
1387
+ white-space:nowrap;
1388
+ }
1389
+
1390
+ /* Trigger button - now inline, not fixed position */
388
1391
  #cf-trigger {
389
1392
  position:fixed;
390
- ${getPositionCSS(cfg.position)}
1393
+ ${getPositionCSS(cfg.position || "bottom-right")}
391
1394
  z-index:100000;
392
- background:${cfg.color};
393
- color:#fff;
394
- border:none;
395
- border-radius:50px;
396
- padding:11px 20px;
397
- font-size:14px;
398
- font-weight:500;
1395
+ background:${bgColor};
1396
+ color:${textColor};
1397
+ border:${border};
1398
+ border-radius:${borderRadius};
1399
+ padding:${padding};
1400
+ font-size:${fontSize};
1401
+ font-weight:${fontWeight};
399
1402
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
400
1403
  cursor:pointer;
401
- box-shadow:0 4px 16px rgba(0,0,0,.18);
402
- display:flex;
1404
+ box-shadow:${boxShadow};
1405
+ display:inline-flex;
403
1406
  align-items:center;
404
1407
  gap:8px;
405
- transition:transform .15s,box-shadow .15s;
1408
+ transition:transform .15s,box-shadow .15s,opacity .15s;
406
1409
  }
407
- #cf-trigger:hover { transform:scale(1.04); box-shadow:0 6px 24px rgba(0,0,0,.22); }
408
- #cf-trigger svg { width:18px; height:18px; }
1410
+ #cf-trigger:hover { opacity:0.9; }
1411
+ #cf-trigger svg { width:16px; height:16px; flex-shrink:0; }
409
1412
 
410
1413
  #cf-modal-backdrop {
411
1414
  position:fixed;
@@ -413,9 +1416,30 @@ function injectStyles(cfg) {
413
1416
  background:rgba(0,0,0,.45);
414
1417
  z-index:100001;
415
1418
  display:flex;
1419
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1420
+ }
1421
+ #cf-modal-backdrop.cf-backdrop--center {
416
1422
  align-items:center;
417
1423
  justify-content:center;
418
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1424
+ }
1425
+ #cf-modal-backdrop.cf-backdrop--corner {
1426
+ padding:20px;
1427
+ }
1428
+ #cf-modal-backdrop.cf-pos-bottom-right {
1429
+ align-items:flex-end;
1430
+ justify-content:flex-end;
1431
+ }
1432
+ #cf-modal-backdrop.cf-pos-bottom-left {
1433
+ align-items:flex-end;
1434
+ justify-content:flex-start;
1435
+ }
1436
+ #cf-modal-backdrop.cf-pos-top-right {
1437
+ align-items:flex-start;
1438
+ justify-content:flex-end;
1439
+ }
1440
+ #cf-modal-backdrop.cf-pos-top-left {
1441
+ align-items:flex-start;
1442
+ justify-content:flex-start;
419
1443
  }
420
1444
 
421
1445
  .cf-modal {
@@ -485,7 +1509,7 @@ function injectStyles(cfg) {
485
1509
  transition:all .15s;
486
1510
  }
487
1511
  .cf-tool-highlight {
488
- background:${cfg.color};
1512
+ background:${primaryColor};
489
1513
  color:#fff;
490
1514
  }
491
1515
  .cf-tool-highlight:hover { opacity:.9; }
@@ -533,8 +1557,8 @@ function injectStyles(cfg) {
533
1557
  }
534
1558
  .cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
535
1559
  .cf-field input:focus, .cf-field textarea:focus {
536
- border-color:${cfg.color};
537
- box-shadow:0 0 0 3px ${cfg.color}18;
1560
+ border-color:${primaryColor};
1561
+ box-shadow:0 0 0 3px ${primaryColor}18;
538
1562
  }
539
1563
  .cf-field textarea { resize:vertical; min-height:100px; }
540
1564
 
@@ -573,7 +1597,7 @@ function injectStyles(cfg) {
573
1597
  padding:13px;
574
1598
  border:none;
575
1599
  border-radius:10px;
576
- background:${cfg.color};
1600
+ background:${primaryColor};
577
1601
  color:#fff;
578
1602
  font-size:15px;
579
1603
  font-weight:600;
@@ -581,7 +1605,7 @@ function injectStyles(cfg) {
581
1605
  font-family:inherit;
582
1606
  transition:all .15s;
583
1607
  }
584
- .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${cfg.color}40; }
1608
+ .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${primaryColor}40; }
585
1609
  .cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
586
1610
 
587
1611
  .cf-cancel-btn {
@@ -612,7 +1636,7 @@ function injectStyles(cfg) {
612
1636
  text-decoration:none;
613
1637
  font-weight:500;
614
1638
  }
615
- .cf-footer a:hover { color:${cfg.color}; }
1639
+ .cf-footer a:hover { color:${primaryColor}; }
616
1640
  `;
617
1641
  document.head.appendChild(style);
618
1642
  }
@@ -624,15 +1648,22 @@ function getPositionCSS(pos) {
624
1648
  return "top:20px;right:20px;";
625
1649
  case "top-left":
626
1650
  return "top:20px;left:20px;";
1651
+ case "center":
1652
+ return "bottom:20px;left:50%;transform:translateX(-50%);";
627
1653
  default:
628
1654
  return "bottom:20px;right:20px;";
629
1655
  }
630
1656
  }
631
1657
  function getTriggerHTML(cfg) {
1658
+ const btn = cfg.button || {};
1659
+ const text = btn.text || cfg.text || "Report Bug";
1660
+ const showIcon = btn.showIcon !== false;
1661
+ const customClass = btn.className || "";
1662
+ 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>` : "";
632
1663
  return `
633
- <button id="cf-trigger">
634
- <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>
635
- ${cfg.text}
1664
+ <button id="cf-trigger" class="${customClass}">
1665
+ ${icon}
1666
+ ${text}
636
1667
  </button>
637
1668
  `;
638
1669
  }
@@ -641,21 +1672,37 @@ function getCheckflowLogo() {
641
1672
  }
642
1673
  function getModalHTML(cfg, expanded) {
643
1674
  const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
644
- const formFields = `
1675
+ const hasUser = currentUser && (currentUser.name || currentUser.email);
1676
+ const userInfoCard = hasUser ? `
1677
+ <div class="cf-user-card">
1678
+ ${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>`}
1679
+ <div class="cf-user-info">
1680
+ ${currentUser?.name ? `<div class="cf-user-name">${currentUser.name}</div>` : ""}
1681
+ ${currentUser?.email ? `<div class="cf-user-email">${currentUser.email}</div>` : ""}
1682
+ </div>
1683
+ </div>
1684
+ ` : "";
1685
+ const nameEmailFields = hasUser ? "" : `
645
1686
  <div class="cf-field">
646
- <label>Nom <span>(obligatoire)</span></label>
1687
+ <label>Nom <span>(optionnel)</span></label>
647
1688
  <input id="cf-name" type="text" placeholder="Votre nom" />
648
1689
  </div>
649
1690
  <div class="cf-field">
650
- <label>Email <span>(obligatoire)</span></label>
1691
+ <label>Email <span>(optionnel)</span></label>
651
1692
  <input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
652
1693
  </div>
1694
+ `;
1695
+ const formFields = `
1696
+ ${userInfoCard}
1697
+ ${nameEmailFields}
653
1698
  <div class="cf-field">
654
1699
  <label>Description <span>(obligatoire)</span></label>
655
1700
  <textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
656
1701
  </div>
657
1702
  `;
658
1703
  if (expanded && screenshotDataUrl) {
1704
+ const displayImg = annotatedImageUrl || screenshotDataUrl;
1705
+ const annotLabel = annotationsData.length > 0 ? `Annoter (${annotationsData.length})` : "Annoter";
659
1706
  return `
660
1707
  <div class="${modalClass}">
661
1708
  <div class="cf-modal-header">
@@ -664,10 +1711,9 @@ function getModalHTML(cfg, expanded) {
664
1711
  </div>
665
1712
  <div class="cf-modal-body">
666
1713
  <div class="cf-screenshot-panel">
667
- <img src="${screenshotDataUrl}" alt="Capture d'\xE9cran" />
1714
+ <img id="cf-screenshot-img" src="${displayImg}" alt="Capture d'\xE9cran" />
668
1715
  <div class="cf-screenshot-tools">
669
- <button class="cf-tool-highlight" id="cf-highlight-btn">Surligner</button>
670
- <button class="cf-tool-mask" id="cf-mask-btn">Masquer</button>
1716
+ <button class="cf-tool-highlight" id="cf-annotate-btn">${annotLabel}</button>
671
1717
  </div>
672
1718
  </div>
673
1719
  <div class="cf-form-panel">
@@ -697,6 +1743,246 @@ function getModalHTML(cfg, expanded) {
697
1743
  </div>
698
1744
  `;
699
1745
  }
1746
+ var DEFAULT_BUTTON_CONFIG, SIZE_PRESETS, VARIANT_PRESETS, DEFAULT_CONFIG, container, onSubmitCallback, screenshotDataUrl, annotatedImageUrl, annotationsData, isExpanded, currentCfg, currentUser;
1747
+ var init_widget = __esm({
1748
+ "src/widget.ts"() {
1749
+ "use strict";
1750
+ init_editor();
1751
+ DEFAULT_BUTTON_CONFIG = {
1752
+ text: "Report Bug",
1753
+ backgroundColor: "#1e3a5f",
1754
+ textColor: "#ffffff",
1755
+ borderRadius: "8px",
1756
+ padding: "10px 18px",
1757
+ fontSize: "14px",
1758
+ fontWeight: "500",
1759
+ border: "none",
1760
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
1761
+ className: "",
1762
+ showIcon: true,
1763
+ size: "default",
1764
+ variant: "default"
1765
+ };
1766
+ SIZE_PRESETS = {
1767
+ xs: { padding: "6px 10px", fontSize: "12px" },
1768
+ sm: { padding: "8px 14px", fontSize: "13px" },
1769
+ default: { padding: "10px 18px", fontSize: "14px" },
1770
+ lg: { padding: "12px 24px", fontSize: "15px" }
1771
+ };
1772
+ VARIANT_PRESETS = {
1773
+ default: { bg: "#1e3a5f", color: "#ffffff", border: "none" },
1774
+ outline: { bg: "transparent", color: "#1e3a5f", border: "1px solid #1e3a5f" },
1775
+ ghost: { bg: "transparent", color: "#1e3a5f", border: "none" },
1776
+ link: { bg: "transparent", color: "#1e3a5f", border: "none" }
1777
+ };
1778
+ DEFAULT_CONFIG = {
1779
+ position: "bottom-right",
1780
+ color: "#1e3a5f",
1781
+ text: "Report Bug",
1782
+ showOnInit: true,
1783
+ button: DEFAULT_BUTTON_CONFIG
1784
+ };
1785
+ container = null;
1786
+ onSubmitCallback = null;
1787
+ screenshotDataUrl = null;
1788
+ annotatedImageUrl = null;
1789
+ annotationsData = [];
1790
+ isExpanded = false;
1791
+ currentCfg = { ...DEFAULT_CONFIG };
1792
+ currentUser = void 0;
1793
+ }
1794
+ });
1795
+
1796
+ // src/index.ts
1797
+ var index_exports = {};
1798
+ __export(index_exports, {
1799
+ captureScreenshot: () => captureScreenshot,
1800
+ clearUser: () => clearUser,
1801
+ destroy: () => destroy,
1802
+ hideWidget: () => hideWidget,
1803
+ init: () => init,
1804
+ openFeedbackModal: () => openFeedbackModal2,
1805
+ sendFeedback: () => sendFeedback,
1806
+ setUser: () => setUser,
1807
+ showWidget: () => showWidget
1808
+ });
1809
+ module.exports = __toCommonJS(index_exports);
1810
+
1811
+ // src/collector.ts
1812
+ function collectContext() {
1813
+ const ua = navigator.userAgent;
1814
+ return {
1815
+ url: window.location.href,
1816
+ viewport: {
1817
+ width: window.innerWidth,
1818
+ height: window.innerHeight,
1819
+ device: window.innerWidth < 768 ? "mobile" : window.innerWidth < 1024 ? "tablet" : "desktop"
1820
+ },
1821
+ user_agent: ua,
1822
+ browser: detectBrowser(ua),
1823
+ os: detectOS(ua),
1824
+ locale: navigator.language,
1825
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
1826
+ };
1827
+ }
1828
+ function collectPerformance() {
1829
+ try {
1830
+ const entries = performance.getEntriesByType("paint");
1831
+ const metrics = {};
1832
+ entries.forEach((e) => {
1833
+ if (e.name === "first-contentful-paint") metrics.fcp = Math.round(e.startTime);
1834
+ });
1835
+ const nav = performance.getEntriesByType("navigation")[0];
1836
+ if (nav) {
1837
+ metrics.dom_load = Math.round(nav.domContentLoadedEventEnd - nav.startTime);
1838
+ metrics.load = Math.round(nav.loadEventEnd - nav.startTime);
1839
+ }
1840
+ return Object.keys(metrics).length > 0 ? metrics : void 0;
1841
+ } catch {
1842
+ return void 0;
1843
+ }
1844
+ }
1845
+ function detectBrowser(ua) {
1846
+ if (ua.includes("Firefox/")) return "Firefox";
1847
+ if (ua.includes("Edg/")) return "Edge";
1848
+ if (ua.includes("Chrome/")) return "Chrome";
1849
+ if (ua.includes("Safari/")) return "Safari";
1850
+ return "Unknown";
1851
+ }
1852
+ function detectOS(ua) {
1853
+ if (ua.includes("Windows")) return "Windows";
1854
+ if (ua.includes("Mac OS")) return "macOS";
1855
+ if (ua.includes("Linux")) return "Linux";
1856
+ if (ua.includes("Android")) return "Android";
1857
+ if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
1858
+ return "Unknown";
1859
+ }
1860
+
1861
+ // src/console-interceptor.ts
1862
+ var MAX_LOGS = 50;
1863
+ var logs = [];
1864
+ var errorsCapture = [];
1865
+ var installed = false;
1866
+ function installInterceptors() {
1867
+ if (installed) return;
1868
+ installed = true;
1869
+ const origConsole = {
1870
+ log: console.log,
1871
+ warn: console.warn,
1872
+ error: console.error
1873
+ };
1874
+ ["log", "warn", "error"].forEach((level) => {
1875
+ console[level] = (...args) => {
1876
+ logs.push({
1877
+ level,
1878
+ message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" "),
1879
+ timestamp: Date.now()
1880
+ });
1881
+ if (logs.length > MAX_LOGS) logs.shift();
1882
+ origConsole[level](...args);
1883
+ };
1884
+ });
1885
+ window.addEventListener("error", (event) => {
1886
+ errorsCapture.push({
1887
+ message: event.message,
1888
+ filename: event.filename,
1889
+ lineno: event.lineno,
1890
+ colno: event.colno,
1891
+ timestamp: Date.now()
1892
+ });
1893
+ if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
1894
+ });
1895
+ window.addEventListener("unhandledrejection", (event) => {
1896
+ errorsCapture.push({
1897
+ message: String(event.reason),
1898
+ type: "unhandledrejection",
1899
+ timestamp: Date.now()
1900
+ });
1901
+ if (errorsCapture.length > MAX_LOGS) errorsCapture.shift();
1902
+ });
1903
+ }
1904
+ function getConsoleLogs() {
1905
+ return [...logs];
1906
+ }
1907
+ function getJavascriptErrors() {
1908
+ return [...errorsCapture];
1909
+ }
1910
+ function clearLogs() {
1911
+ logs = [];
1912
+ errorsCapture = [];
1913
+ }
1914
+
1915
+ // src/network-interceptor.ts
1916
+ var MAX_LOGS2 = 50;
1917
+ var logs2 = [];
1918
+ var installed2 = false;
1919
+ function installNetworkInterceptor() {
1920
+ if (installed2 || typeof window === "undefined") return;
1921
+ installed2 = true;
1922
+ const origFetch = window.fetch;
1923
+ window.fetch = async function(...args) {
1924
+ const start = Date.now();
1925
+ const req = new Request(...args);
1926
+ const entry = {
1927
+ method: req.method,
1928
+ url: req.url,
1929
+ type: "fetch",
1930
+ timestamp: start
1931
+ };
1932
+ try {
1933
+ const res = await origFetch.apply(this, args);
1934
+ entry.status = res.status;
1935
+ entry.duration = Date.now() - start;
1936
+ entry.response_type = res.headers.get("content-type") || void 0;
1937
+ pushLog(entry);
1938
+ return res;
1939
+ } catch (err) {
1940
+ entry.duration = Date.now() - start;
1941
+ entry.error = err.message || "Network error";
1942
+ pushLog(entry);
1943
+ throw err;
1944
+ }
1945
+ };
1946
+ const origOpen = XMLHttpRequest.prototype.open;
1947
+ const origSend = XMLHttpRequest.prototype.send;
1948
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
1949
+ this.__cf_method = method;
1950
+ this.__cf_url = String(url);
1951
+ return origOpen.apply(this, [method, url, ...rest]);
1952
+ };
1953
+ XMLHttpRequest.prototype.send = function(...args) {
1954
+ const start = Date.now();
1955
+ const xhr = this;
1956
+ xhr.addEventListener("loadend", () => {
1957
+ const entry = {
1958
+ method: xhr.__cf_method || "GET",
1959
+ url: xhr.__cf_url || "",
1960
+ status: xhr.status,
1961
+ duration: Date.now() - start,
1962
+ type: "xhr",
1963
+ timestamp: start,
1964
+ response_type: xhr.getResponseHeader("content-type") || void 0
1965
+ };
1966
+ if (xhr.status === 0) entry.error = "Request failed";
1967
+ pushLog(entry);
1968
+ });
1969
+ return origSend.apply(this, args);
1970
+ };
1971
+ }
1972
+ function pushLog(entry) {
1973
+ if (entry.url.includes("/sdk/feedback")) return;
1974
+ logs2.push(entry);
1975
+ if (logs2.length > MAX_LOGS2) logs2.shift();
1976
+ }
1977
+ function getNetworkLogs() {
1978
+ return [...logs2];
1979
+ }
1980
+ function clearNetworkLogs() {
1981
+ logs2 = [];
1982
+ }
1983
+
1984
+ // src/index.ts
1985
+ init_widget();
700
1986
 
701
1987
  // src/screenshot.ts
702
1988
  var screenshotData = null;
@@ -749,130 +2035,6 @@ function clearScreenshot() {
749
2035
  screenshotData = null;
750
2036
  }
751
2037
 
752
- // src/highlighter.ts
753
- var overlay = null;
754
- var highlights = [];
755
- var isActive = false;
756
- var onDoneCallback = null;
757
- function getSelector(el) {
758
- if (el.id) return `#${el.id}`;
759
- const parts = [];
760
- let current = el;
761
- while (current && current !== document.body) {
762
- let sel = current.tagName.toLowerCase();
763
- if (current.id) {
764
- parts.unshift(`#${current.id}`);
765
- break;
766
- }
767
- if (current.className && typeof current.className === "string") {
768
- const cls = current.className.trim().split(/\s+/).slice(0, 2).join(".");
769
- if (cls) sel += `.${cls}`;
770
- }
771
- const parent = current.parentElement;
772
- if (parent) {
773
- const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
774
- if (siblings.length > 1) sel += `:nth-child(${Array.from(parent.children).indexOf(current) + 1})`;
775
- }
776
- parts.unshift(sel);
777
- current = current.parentElement;
778
- }
779
- return parts.join(" > ");
780
- }
781
- function createOverlay() {
782
- overlay = document.createElement("div");
783
- overlay.id = "cf-highlight-overlay";
784
- overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;z-index:99999;cursor:crosshair;";
785
- const toolbar = document.createElement("div");
786
- toolbar.style.cssText = "position:fixed;top:12px;left:50%;transform:translateX(-50%);z-index:100002;background:#1e3a5f;color:#fff;padding:8px 16px;border-radius:10px;font-family:system-ui;font-size:13px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 20px rgba(0,0,0,.25);";
787
- toolbar.innerHTML = `
788
- <span>Click elements to highlight them</span>
789
- <button id="cf-hl-done" style="background:#10b981;color:#fff;border:none;border-radius:6px;padding:6px 14px;font-size:12px;cursor:pointer;font-weight:500;">Done (${highlights.length})</button>
790
- <button id="cf-hl-cancel" style="background:transparent;color:#fff;border:1px solid rgba(255,255,255,.3);border-radius:6px;padding:6px 14px;font-size:12px;cursor:pointer;">Cancel</button>
791
- `;
792
- overlay.appendChild(toolbar);
793
- document.body.appendChild(overlay);
794
- overlay.addEventListener("click", handleClick);
795
- overlay.addEventListener("mousemove", handleHover);
796
- document.getElementById("cf-hl-done")?.addEventListener("click", finishHighlighting);
797
- document.getElementById("cf-hl-cancel")?.addEventListener("click", cancelHighlighting);
798
- }
799
- var hoverBox = null;
800
- function handleHover(e) {
801
- if (!hoverBox) {
802
- hoverBox = document.createElement("div");
803
- hoverBox.style.cssText = "position:fixed;border:2px solid #0c66e4;background:rgba(12,102,228,0.08);pointer-events:none;z-index:100001;border-radius:3px;transition:all 0.05s;";
804
- document.body.appendChild(hoverBox);
805
- }
806
- const target = document.elementFromPoint(e.clientX, e.clientY);
807
- if (target && target !== overlay && !overlay?.contains(target)) {
808
- const rect = target.getBoundingClientRect();
809
- hoverBox.style.left = rect.left + "px";
810
- hoverBox.style.top = rect.top + "px";
811
- hoverBox.style.width = rect.width + "px";
812
- hoverBox.style.height = rect.height + "px";
813
- hoverBox.style.display = "block";
814
- } else {
815
- hoverBox.style.display = "none";
816
- }
817
- }
818
- function handleClick(e) {
819
- e.preventDefault();
820
- e.stopPropagation();
821
- const target = document.elementFromPoint(e.clientX, e.clientY);
822
- if (!target || target === overlay || overlay?.contains(target)) return;
823
- const rect = target.getBoundingClientRect();
824
- highlights.push({ el: target, rect });
825
- const marker = document.createElement("div");
826
- marker.className = "cf-hl-marker";
827
- marker.style.cssText = `position:fixed;left:${rect.left}px;top:${rect.top}px;width:${rect.width}px;height:${rect.height}px;border:2px solid #ae2a19;background:rgba(174,42,25,0.12);z-index:100001;pointer-events:none;border-radius:3px;`;
828
- const badge = document.createElement("div");
829
- badge.style.cssText = "position:absolute;top:-10px;right:-10px;background:#ae2a19;color:#fff;width:20px;height:20px;border-radius:50%;font-size:11px;display:flex;align-items:center;justify-content:center;font-family:system-ui;font-weight:600;";
830
- badge.textContent = String(highlights.length);
831
- marker.appendChild(badge);
832
- overlay?.appendChild(marker);
833
- const doneBtn = document.getElementById("cf-hl-done");
834
- if (doneBtn) doneBtn.textContent = `Done (${highlights.length})`;
835
- }
836
- function finishHighlighting() {
837
- const annotations = highlights.map((h) => ({
838
- selector: getSelector(h.el),
839
- tagName: h.el.tagName.toLowerCase(),
840
- text: h.el.textContent?.slice(0, 100) || void 0,
841
- rect: { x: Math.round(h.rect.x), y: Math.round(h.rect.y), width: Math.round(h.rect.width), height: Math.round(h.rect.height) },
842
- note: h.note
843
- }));
844
- cleanup();
845
- onDoneCallback?.(annotations);
846
- }
847
- function cancelHighlighting() {
848
- cleanup();
849
- onDoneCallback?.([]);
850
- }
851
- function cleanup() {
852
- if (hoverBox) {
853
- hoverBox.remove();
854
- hoverBox = null;
855
- }
856
- if (overlay) {
857
- overlay.remove();
858
- overlay = null;
859
- }
860
- highlights = [];
861
- isActive = false;
862
- }
863
- function startHighlighting() {
864
- return new Promise((resolve) => {
865
- if (isActive) {
866
- resolve([]);
867
- return;
868
- }
869
- isActive = true;
870
- highlights = [];
871
- onDoneCallback = resolve;
872
- createOverlay();
873
- });
874
- }
875
-
876
2038
  // src/index.ts
877
2039
  var SDK_VERSION = "1.1.0";
878
2040
  var DEFAULT_ENDPOINT = "https://api.checkflow.space/api/v1";
@@ -904,7 +2066,7 @@ function init(cfg) {
904
2066
  },
905
2067
  {
906
2068
  onScreenshot: () => captureScreenshot(),
907
- onHighlight: () => startHighlighting()
2069
+ user: config.user
908
2070
  }
909
2071
  );
910
2072
  }
@@ -934,12 +2096,22 @@ async function sendFeedback(data) {
934
2096
  if (data.annotations && data.annotations.length > 0) {
935
2097
  payload.annotations = data.annotations;
936
2098
  }
937
- if (data.reporter_name) {
2099
+ if (config.user?.name) {
2100
+ payload.reporter_name = config.user.name;
2101
+ } else if (data.reporter_name) {
938
2102
  payload.reporter_name = data.reporter_name;
939
2103
  }
940
- if (data.reporter_email) {
2104
+ if (config.user?.email) {
2105
+ payload.reporter_email = config.user.email;
2106
+ } else if (data.reporter_email) {
941
2107
  payload.reporter_email = data.reporter_email;
942
2108
  }
2109
+ if (config.user?.id) {
2110
+ payload.user_id = config.user.id;
2111
+ }
2112
+ if (config.user?.avatar) {
2113
+ payload.user_avatar = config.user.avatar;
2114
+ }
943
2115
  try {
944
2116
  const res = await fetch(`${config.endpoint}/sdk/feedback`, {
945
2117
  method: "POST",
@@ -976,10 +2148,44 @@ function showWidget() {
976
2148
  },
977
2149
  {
978
2150
  onScreenshot: () => captureScreenshot(),
979
- onHighlight: () => startHighlighting()
2151
+ user: config.user
980
2152
  }
981
2153
  );
982
2154
  }
2155
+ function setUser(user) {
2156
+ if (config) {
2157
+ config.user = user;
2158
+ }
2159
+ }
2160
+ function clearUser() {
2161
+ if (config) {
2162
+ config.user = void 0;
2163
+ }
2164
+ }
2165
+ function openFeedbackModal2() {
2166
+ if (!config) return;
2167
+ Promise.resolve().then(() => (init_widget(), widget_exports)).then(({ openFeedbackModal: openModal2 }) => {
2168
+ openModal2(
2169
+ config.widget,
2170
+ (data) => {
2171
+ sendFeedback({
2172
+ title: data.title,
2173
+ description: data.description,
2174
+ type: data.type,
2175
+ priority: data.priority,
2176
+ screenshot_data: data.screenshot,
2177
+ annotations: data.annotations,
2178
+ reporter_name: data.reporter_name,
2179
+ reporter_email: data.reporter_email
2180
+ });
2181
+ },
2182
+ {
2183
+ onScreenshot: () => captureScreenshot(),
2184
+ user: config.user
2185
+ }
2186
+ );
2187
+ });
2188
+ }
983
2189
  function hideWidget() {
984
2190
  unmountWidget();
985
2191
  }
@@ -993,10 +2199,12 @@ function destroy() {
993
2199
  // Annotate the CommonJS export names for ESM import in node:
994
2200
  0 && (module.exports = {
995
2201
  captureScreenshot,
2202
+ clearUser,
996
2203
  destroy,
997
2204
  hideWidget,
998
2205
  init,
2206
+ openFeedbackModal,
999
2207
  sendFeedback,
1000
- showWidget,
1001
- startHighlighting
2208
+ setUser,
2209
+ showWidget
1002
2210
  });