blocfeed 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -2
- package/README.md +251 -6
- package/dist/chunk-FMFONS5S.js +2 -0
- package/dist/chunk-JLPJP7DD.cjs +2 -0
- package/dist/{controller-DmWFVHoj.d.cts → controller-D7kOehzb.d.cts} +57 -4
- package/dist/{controller-DmWFVHoj.d.ts → controller-D7kOehzb.d.ts} +57 -4
- package/dist/engine.cjs +1 -1
- package/dist/engine.d.cts +51 -3
- package/dist/engine.d.ts +51 -3
- package/dist/engine.js +1 -1
- package/dist/main.cjs +169 -2
- package/dist/main.d.cts +2 -2
- package/dist/main.d.ts +2 -2
- package/dist/main.js +169 -2
- package/package.json +18 -1
- package/dist/chunk-37P3GL6V.js +0 -2
- package/dist/chunk-PEPIK3FN.cjs +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.4.0 — 2026-02-20
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### New features
|
|
6
|
+
|
|
7
|
+
- **Animated trigger styles** — new `config.ui.triggerStyle` option with 7 selectable styles:
|
|
8
|
+
- `"classic"` (default) — standard pill button, no framer-motion required
|
|
9
|
+
- `"dot"` — breathing dot with glow that springs into a pill on hover
|
|
10
|
+
- `"bubble"` — floating chat-bubble icon with tooltip on hover
|
|
11
|
+
- `"edge-tab"` — thin vertical tab flush to screen edge that slides out on hover
|
|
12
|
+
- `"pulse-ring"` — sonar-style pulsing rings around a dot that contracts to a pill on hover
|
|
13
|
+
- `"minimal"` — text-only "Feedback" with animated underline on hover
|
|
14
|
+
- `"icon-pop"` — wobbling message icon that pops and reveals text on hover
|
|
15
|
+
- All animated styles include smooth spring-based enter/exit transitions via framer-motion
|
|
16
|
+
- `framer-motion` added as an optional peer dependency (only required for non-classic styles)
|
|
17
|
+
- New `TriggerStyle` type exported from both `blocfeed` and `blocfeed/engine`
|
|
18
|
+
|
|
19
|
+
### Architecture
|
|
20
|
+
|
|
21
|
+
- Trigger button extracted from `BlocFeedWidget` into modular components under `src/react/triggers/`
|
|
22
|
+
- Each trigger variant is a self-contained component with its own animations
|
|
23
|
+
- `ClassicTrigger` has zero framer-motion imports for consumers who don't need animations
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 0.3.0 — 2026-02-20
|
|
28
|
+
|
|
29
|
+
### New features
|
|
30
|
+
|
|
31
|
+
- **User identity** — new `config.user` prop attaches `id`, `email`, and `name` to every submission. Values are included as a top-level `user` field in the payload and merged into `metadata` as `userId`, `userEmail`, `userName`.
|
|
32
|
+
- **Offline queue** — failed submissions (network errors) are automatically saved to `localStorage` and retried on the next page load or when the browser comes back online. Queue holds up to 50 items (FIFO eviction); screenshots are stripped to stay within storage limits. Headless utilities: `enqueue`, `dequeueAll`, `clearQueue`, `getQueueSize` exported from `blocfeed/engine`.
|
|
33
|
+
- **Widget positioning** — new `config.ui.position` option places the trigger button in any corner: `"bottom-right"` (default), `"bottom-left"`, `"top-right"`, `"top-left"`.
|
|
34
|
+
- **Theme customization** — new `config.ui.theme` option overrides visual appearance via CSS variables: `accentColor`, `panelBackground`, `panelForeground`, `fontFamily`.
|
|
35
|
+
- **Configurable retry & transport** — new `config.transport` option with `timeoutMs`, `maxAttempts`, and `backoffMs` (exponential backoff with jitter). Defaults unchanged: 12s timeout, 2 attempts, 500ms base backoff.
|
|
36
|
+
- **Keyboard accessibility** — full keyboard navigation with focus trapping, `Tab`/`Shift+Tab` cycling, `Escape` to cancel, `Ctrl/Cmd+Enter` to submit. All interactive elements have `aria-label` attributes; status messages use `aria-live="polite"`.
|
|
37
|
+
- **Client-side rate limiting** — minimum 2-second interval enforced between submissions to prevent accidental spam.
|
|
38
|
+
- **Alternative screenshot adapter** — `createModernScreenshotAdapter()` exported from `blocfeed/engine` for using `modern-screenshot` as a drop-in replacement with better cross-origin, `clip-path`, and `backdrop-filter` support.
|
|
39
|
+
- **Capture progress indicator** — pulsing spinner animation shown during screenshot capture and submission phases.
|
|
40
|
+
|
|
41
|
+
### Improvements
|
|
42
|
+
|
|
43
|
+
- **Two-phase screenshot upload** — the text payload (metadata, selection, message) is sent first without base64 data. If the platform returns presigned upload URLs, screenshots are uploaded directly to object storage. Falls back to a secondary POST endpoint when presigned URLs are unavailable.
|
|
44
|
+
- **Full-page screenshot storage** — full-page screenshots are now processed and stored on the platform (previously captured but discarded server-side).
|
|
45
|
+
- **Production fiber optimization** — React fiber introspection for `componentName` now checks for `_debugOwner` before traversing. In production builds (where debug info is stripped), the expensive 80-node traversal is skipped entirely; only a 10-node parent chain is checked.
|
|
46
|
+
- **TypeScript strict exports** — all new types (`BlocFeedUser`, `TransportConfig`, `WidgetPosition`, `ThemeConfig`, `FeedbackApiResponse`, `ScreenshotIntent`) exported from both `blocfeed` and `blocfeed/engine`. Added `typesVersions` in `package.json` for older bundler compatibility.
|
|
47
|
+
- **Test coverage** — expanded from 6 tests (2 files) to 49 tests (7 files) covering the controller state machine, API client (retries, rate limiting, abort), offline queue, metadata collection, error normalization, and picker behavior.
|
|
48
|
+
|
|
49
|
+
### Platform changes (blocfeed-frontend)
|
|
50
|
+
|
|
51
|
+
- `POST /api/feedback` now stores full-page screenshots, reads `payload.user` for dedicated identity columns, and returns `feedback_id` + presigned `upload_urls` in the response.
|
|
52
|
+
- New `POST /api/feedback/:id/screenshots` fallback endpoint for uploading screenshots as base64 when object storage is unavailable.
|
|
53
|
+
- New SQL migration `scripts/026_fullpage_screenshots.sql` adds `screenshot_fullpage_*` and `meta_user_*` columns.
|
|
6
54
|
|
|
7
55
|
## 0.2.2 — 2026-02-17
|
|
8
56
|
|
package/README.md
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Drop-in in-app feedback widget for **Next.js** and **React**:
|
|
4
4
|
|
|
5
|
-
- Safe
|
|
5
|
+
- Safe "feedback mode" with **DOM element picking** (blocks host app clicks while active)
|
|
6
6
|
- Optional **element** + **full page** screenshots (capture code is lazy-loaded)
|
|
7
7
|
- Typed, JSON-serializable `FeedbackPayload` with contextual metadata
|
|
8
8
|
- Submits directly to the BlocFeed platform via `blocfeed_id`
|
|
9
9
|
- Optional `selection.componentName` + `selection.testId` for faster triage
|
|
10
|
+
- **First-class user identity** — attach `user.id`, `email`, `name` to every submission
|
|
11
|
+
- **Offline queue** — failed submissions are stored and retried automatically
|
|
12
|
+
- **Customizable position, theme, and retry behavior**
|
|
13
|
+
- **Accessible** — keyboard navigation, focus trapping, ARIA attributes
|
|
10
14
|
|
|
11
15
|
Docs live in `docs/` (start at `docs/index.md`). Architecture pointers are in `ARCHITECTURE.md`.
|
|
12
16
|
|
|
@@ -83,6 +87,171 @@ export function App() {
|
|
|
83
87
|
}
|
|
84
88
|
```
|
|
85
89
|
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
All configuration is passed via the `config` prop on `<BlocFeedWidget>` or `<BlocFeedProvider>`:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<BlocFeedWidget
|
|
96
|
+
blocfeed_id="bf_..."
|
|
97
|
+
config={{
|
|
98
|
+
// User identity (attached to every submission)
|
|
99
|
+
user: {
|
|
100
|
+
id: "user_123",
|
|
101
|
+
email: "jane@example.com",
|
|
102
|
+
name: "Jane Doe",
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Widget position, theme & trigger style
|
|
106
|
+
ui: {
|
|
107
|
+
position: "bottom-right", // "bottom-left" | "top-right" | "top-left"
|
|
108
|
+
triggerStyle: "dot", // "classic" | "bubble" | "edge-tab" | "pulse-ring" | "minimal" | "icon-pop"
|
|
109
|
+
zIndex: 99999,
|
|
110
|
+
theme: {
|
|
111
|
+
accentColor: "#12D393",
|
|
112
|
+
panelBackground: "rgba(0, 0, 0, 0.95)",
|
|
113
|
+
panelForeground: "#ffffff",
|
|
114
|
+
fontFamily: "Inter, sans-serif",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Screenshot defaults
|
|
119
|
+
capture: {
|
|
120
|
+
element: true,
|
|
121
|
+
fullPage: false,
|
|
122
|
+
mime: "image/png", // or "image/jpeg"
|
|
123
|
+
quality: 0.92, // JPEG quality (0–1)
|
|
124
|
+
timeoutMs: 12000,
|
|
125
|
+
maxDimension: 2048,
|
|
126
|
+
pixelRatio: 2,
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Retry & transport
|
|
130
|
+
transport: {
|
|
131
|
+
timeoutMs: 12000, // per-request timeout
|
|
132
|
+
maxAttempts: 2, // retry count
|
|
133
|
+
backoffMs: 500, // base backoff delay
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// Metadata enrichment
|
|
137
|
+
metadata: {
|
|
138
|
+
enabled: true,
|
|
139
|
+
enrich: async (context) => ({
|
|
140
|
+
orgId: "org_abc",
|
|
141
|
+
plan: "pro",
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Picker rules
|
|
146
|
+
picker: {
|
|
147
|
+
ignoreSelectors: ["[data-private]"],
|
|
148
|
+
isSelectable: (el) => el.tagName !== "HTML",
|
|
149
|
+
},
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## User Identity
|
|
155
|
+
|
|
156
|
+
Attach user information to every feedback submission:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<BlocFeedWidget
|
|
160
|
+
blocfeed_id="bf_..."
|
|
161
|
+
config={{
|
|
162
|
+
user: {
|
|
163
|
+
id: currentUser.id,
|
|
164
|
+
email: currentUser.email,
|
|
165
|
+
name: currentUser.name,
|
|
166
|
+
},
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The `user` object is included in the payload as a top-level field and its values are also merged into `metadata` as `userId`, `userEmail`, `userName`.
|
|
172
|
+
|
|
173
|
+
## Widget Position
|
|
174
|
+
|
|
175
|
+
Place the trigger button in any corner:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
config={{
|
|
179
|
+
ui: { position: "bottom-left" } // default: "bottom-right"
|
|
180
|
+
}}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Options: `"bottom-right"` | `"bottom-left"` | `"top-right"` | `"top-left"`
|
|
184
|
+
|
|
185
|
+
## Trigger Styles
|
|
186
|
+
|
|
187
|
+
Customize the trigger button's appearance and animation:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
config={{
|
|
191
|
+
ui: { triggerStyle: "dot" }
|
|
192
|
+
}}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
| Style | Description | Requires framer-motion |
|
|
196
|
+
|-------|-------------|----------------------|
|
|
197
|
+
| `"classic"` | Default pill button with colored dot (no animation) | No |
|
|
198
|
+
| `"dot"` | Breathing dot that expands to pill on hover | Yes |
|
|
199
|
+
| `"bubble"` | Floating chat-bubble icon with tooltip on hover | Yes |
|
|
200
|
+
| `"edge-tab"` | Thin tab anchored to screen edge, slides out on hover | Yes |
|
|
201
|
+
| `"pulse-ring"` | Sonar-style pulsing rings around a dot, expands on hover | Yes |
|
|
202
|
+
| `"minimal"` | Text-only "Feedback" with animated underline on hover | Yes |
|
|
203
|
+
| `"icon-pop"` | Wobbling icon that pops and reveals text on hover | Yes |
|
|
204
|
+
|
|
205
|
+
All animated styles include smooth enter/exit transitions powered by framer-motion's spring physics.
|
|
206
|
+
|
|
207
|
+
### Installing framer-motion
|
|
208
|
+
|
|
209
|
+
All styles except `"classic"` require `framer-motion` as a peer dependency:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npm install framer-motion
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Theme Customization
|
|
216
|
+
|
|
217
|
+
Override the widget's visual appearance via CSS variables:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
config={{
|
|
221
|
+
ui: {
|
|
222
|
+
theme: {
|
|
223
|
+
accentColor: "#12D393", // buttons, highlights, focus rings
|
|
224
|
+
panelBackground: "rgba(0,0,0,0.95)", // panel & trigger background
|
|
225
|
+
panelForeground: "#ffffff", // text color
|
|
226
|
+
fontFamily: "Inter, sans-serif", // font stack
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
}}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Retry & Transport
|
|
233
|
+
|
|
234
|
+
Configure retry behavior for unreliable networks:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
config={{
|
|
238
|
+
transport: {
|
|
239
|
+
timeoutMs: 15000, // 15s timeout per attempt
|
|
240
|
+
maxAttempts: 3, // retry up to 3 times
|
|
241
|
+
backoffMs: 1000, // 1s base backoff (exponential with jitter)
|
|
242
|
+
},
|
|
243
|
+
}}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Offline Queue
|
|
247
|
+
|
|
248
|
+
When a submission fails due to a network error, the payload is automatically saved to `localStorage` and retried:
|
|
249
|
+
|
|
250
|
+
- On the next page load (1s after controller initialization)
|
|
251
|
+
- When the browser comes back online (`online` event)
|
|
252
|
+
|
|
253
|
+
Screenshots are stripped from queued payloads to stay within `localStorage` limits. The queue holds up to 50 items (FIFO eviction).
|
|
254
|
+
|
|
86
255
|
## Picking rules (ignore / filter)
|
|
87
256
|
|
|
88
257
|
### Ignore a subtree (recommended)
|
|
@@ -116,7 +285,7 @@ export function CheckoutButton() {
|
|
|
116
285
|
}
|
|
117
286
|
```
|
|
118
287
|
|
|
119
|
-
BlocFeed will also attempt a **best-effort** component name in React/Next **dev builds** (no setup), but it may be missing/minified in production.
|
|
288
|
+
BlocFeed will also attempt a **best-effort** component name in React/Next **dev builds** (no setup), but it may be missing/minified in production. In production, the fiber traversal is limited to 10 nodes (vs 80 in dev) to avoid wasting cycles on minified names.
|
|
120
289
|
|
|
121
290
|
### `selection.testId`
|
|
122
291
|
|
|
@@ -124,8 +293,44 @@ BlocFeed extracts a best-effort `testId` from common attributes like `data-testi
|
|
|
124
293
|
|
|
125
294
|
## Screenshots
|
|
126
295
|
|
|
127
|
-
|
|
128
|
-
|
|
296
|
+
Screenshots are uploaded efficiently using a two-phase flow:
|
|
297
|
+
|
|
298
|
+
1. The text payload (metadata, selection, message) is sent first without screenshot data
|
|
299
|
+
2. If the platform returns presigned upload URLs (Wasabi/S3), screenshots are uploaded directly to object storage
|
|
300
|
+
3. If presigned URLs are unavailable, screenshots fall back to a secondary POST endpoint
|
|
301
|
+
|
|
302
|
+
Both **element** and **full-page** screenshots are supported and stored on the platform.
|
|
303
|
+
|
|
304
|
+
### Known limitations
|
|
305
|
+
|
|
306
|
+
The default screenshot engine (`html-to-image`) has known issues with:
|
|
307
|
+
- Cross-origin images without permissive CORS headers
|
|
308
|
+
- CSS `clip-path` and `backdrop-filter`
|
|
309
|
+
- Some web fonts
|
|
310
|
+
|
|
311
|
+
### Alternative screenshot adapter
|
|
312
|
+
|
|
313
|
+
For better compatibility, you can use `modern-screenshot` as a drop-in replacement:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
npm install modern-screenshot
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
import { createModernScreenshotAdapter } from "blocfeed/engine";
|
|
321
|
+
import * as modernScreenshot from "modern-screenshot";
|
|
322
|
+
|
|
323
|
+
<BlocFeedWidget
|
|
324
|
+
blocfeed_id="bf_..."
|
|
325
|
+
config={{
|
|
326
|
+
capture: {
|
|
327
|
+
adapter: createModernScreenshotAdapter(modernScreenshot),
|
|
328
|
+
},
|
|
329
|
+
}}
|
|
330
|
+
/>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Convert to Blob
|
|
129
334
|
|
|
130
335
|
```ts
|
|
131
336
|
import { dataUrlToBlob } from "blocfeed/engine";
|
|
@@ -133,11 +338,25 @@ import { dataUrlToBlob } from "blocfeed/engine";
|
|
|
133
338
|
const blob = dataUrlToBlob(payload.screenshots?.element?.dataUrl ?? "");
|
|
134
339
|
```
|
|
135
340
|
|
|
136
|
-
|
|
341
|
+
## Keyboard Accessibility
|
|
342
|
+
|
|
343
|
+
The widget supports full keyboard navigation:
|
|
344
|
+
|
|
345
|
+
- **Tab** cycles through interactive elements in the feedback panel
|
|
346
|
+
- **Shift+Tab** cycles backward
|
|
347
|
+
- **Escape** cancels picking or closes the panel
|
|
348
|
+
- **Ctrl/Cmd+Enter** submits feedback from the textarea
|
|
349
|
+
- Focus is trapped within the panel when open
|
|
350
|
+
- All interactive elements have `aria-label` attributes
|
|
351
|
+
- Status messages use `aria-live="polite"` for screen reader announcements
|
|
352
|
+
|
|
353
|
+
## Client-Side Rate Limiting
|
|
354
|
+
|
|
355
|
+
A minimum 2-second interval is enforced between submissions to prevent accidental spam. Rapid submissions return a descriptive error.
|
|
137
356
|
|
|
138
357
|
## Custom UI
|
|
139
358
|
|
|
140
|
-
If you don
|
|
359
|
+
If you don't want the default widget UI:
|
|
141
360
|
|
|
142
361
|
```tsx
|
|
143
362
|
import { BlocFeedProvider, useBlocFeed } from "blocfeed";
|
|
@@ -165,6 +384,32 @@ import { createBlocFeedController } from "blocfeed/engine";
|
|
|
165
384
|
const controller = createBlocFeedController({ blocfeed_id: "bf_your_project_blocfeed_id" });
|
|
166
385
|
```
|
|
167
386
|
|
|
387
|
+
### Offline queue utilities (headless)
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
import { enqueue, dequeueAll, clearQueue, getQueueSize } from "blocfeed/engine";
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## TypeScript
|
|
394
|
+
|
|
395
|
+
All types are exported from both entry points:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
import type {
|
|
399
|
+
BlocFeedConfig,
|
|
400
|
+
BlocFeedUser,
|
|
401
|
+
TransportConfig,
|
|
402
|
+
TriggerStyle,
|
|
403
|
+
WidgetPosition,
|
|
404
|
+
ThemeConfig,
|
|
405
|
+
FeedbackPayload,
|
|
406
|
+
FeedbackApiResponse,
|
|
407
|
+
BlocFeedState,
|
|
408
|
+
SessionPhase,
|
|
409
|
+
// ... and more
|
|
410
|
+
} from "blocfeed";
|
|
411
|
+
```
|
|
412
|
+
|
|
168
413
|
## Local development
|
|
169
414
|
|
|
170
415
|
```bash
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function h(){return typeof window<"u"&&typeof document<"u"}function X(t){let e=globalThis.CSS;return typeof e?.escape=="function"?e.escape(t):t.replace(/[^a-zA-Z0-9_-]/g,n=>{let r=n.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function O(t){return {x:t.x,y:t.y,width:t.width,height:t.height}}function ye(t){return {x:t.x+window.scrollX,y:t.y+window.scrollY,width:t.width,height:t.height}}function Ee(t){return t.replace(/\s+/g," ").trim()}function Se(t,e=140){let n=t.textContent;if(!n)return;let r=Ee(n);if(r)return r.length<=e?r:`${r.slice(0,e-1)}\u2026`}function ke(t){let e=1;for(let n=t.previousElementSibling;n;n=n.previousElementSibling)n.tagName===t.tagName&&(e+=1);return e}var V=["data-testid","data-test-id","data-test","data-qa","data-cy"],G="data-blocfeed-component";function ve(t){let e=t.closest(`[${G}]`);if(!e)return;let r=e.getAttribute(G)?.trim();return r||void 0}function Pe(t){for(let e of V){let n=t.closest(`[${e}]`);if(!n)continue;let o=n.getAttribute(e)?.trim();if(o)return o}}function xe(t){try{let e=t,n=Object.getOwnPropertyNames(e);for(let r of n)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=e[r];if(o&&typeof o=="object")return o}}catch{}return null}function B(t){if(t&&typeof t!="string"){if(typeof t=="function"){let e=t;return typeof e.displayName=="string"&&e.displayName?e.displayName:typeof e.name=="string"&&e.name?e.name:void 0}if(typeof t=="object"){let e=t,n=e.displayName;if(typeof n=="string"&&n)return n;let r=e.render;if(typeof r=="function"){let i=r;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let o=e.type;return B(o)}}}function Fe(t){let e=xe(t);if(!e)return;let n=e._debugOwner!==void 0;if(n){let i=e._debugOwner;for(let s=0;i&&s<50;s+=1){let l=B(i.type)??B(i.elementType);if(l)return l;i=i._debugOwner;}}let r=e,o=n?80:10;for(let i=0;r&&i<o;i+=1){let s=B(r.type)??B(r.elementType);if(s)return s;r=r.return;}}function Ae(t){let e=t.tagName.toLowerCase(),n=t.getAttribute("id");if(n)return `#${X(n)}`;for(let r of V){let o=t.getAttribute(r);if(o)return `${e}[${r}="${X(o)}"]`}return `${e}:nth-of-type(${ke(t)})`}function Te(t,e=10){let n=[],r=t;for(;r&&n.length<e;){let o=Ae(r);if(n.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return n.join(" > ")}function Z(t,e){if(!e||e.length===0)return false;for(let n of e)if(t.closest(n))return true;return false}function H(t,e){if(!t||Z(t,e.ignoreSelectors))return null;let n=t;for(;n;){if(Z(n,e.ignoreSelectors))return null;if(e.isSelectable?.(n)??Re(n))return n;n=n.parentElement;}return null}function Re(t){let e=t.tagName;return !(e==="HTML"||e==="BODY")}function ee(t){let e=t.getBoundingClientRect(),n={selector:Te(t),tagName:t.tagName.toLowerCase(),rect:O(e),pageRect:ye(e)},r=t.getAttribute("id");r&&(n.id=r);let o=t.className;typeof o=="string"&&o.trim()&&(n.className=o);let i=Se(t);i&&(n.textSnippet=i);let s=ve(t)??Fe(t);s&&(n.componentName=s);let l=Pe(t);return l&&(n.testId=l),n}var q=null;async function te(){return q||(q=import('html-to-image')),q}async function Ce(t){return await new Promise((e,n)=>{let r=new Image;r.onload=()=>e({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>n(new Error("Failed to load generated screenshot")),r.src=t;})}async function ne(t,e){let{width:n,height:r}=await Ce(t);return {dataUrl:t,mime:e,width:n,height:r}}function R(t){if(t?.aborted)throw new Error("Aborted")}function re(){return {async captureElement(t,e){if(!h())throw new Error("captureElement can only run in the browser");R(e.signal);let n=await te();R(e.signal);let r=e.mime==="image/jpeg"?await n.toJpeg(t,{quality:e.quality??.92,pixelRatio:e.pixelRatio}):await n.toPng(t,{pixelRatio:e.pixelRatio});return R(e.signal),await ne(r,e.mime)},async captureFullPage(t){if(!h())throw new Error("captureFullPage can only run in the browser");R(t.signal);let e=document.documentElement,n=Math.max(e.scrollWidth,e.clientWidth),r=Math.max(e.scrollHeight,e.clientHeight),o=Math.min(1,t.maxDimension/Math.max(n,r)),i=Math.max(1,Math.round(n*o)),s=Math.max(1,Math.round(r*o)),l=await te();R(t.signal);let f=t.mime==="image/jpeg"?await l.toJpeg(e,{width:i,height:s,quality:t.quality??.92,pixelRatio:t.pixelRatio}):await l.toPng(e,{width:i,height:s,pixelRatio:t.pixelRatio});return R(t.signal),await ne(f,t.mime)}}}function Me(t){if(t instanceof Error)return t.message;if(typeof t=="string")return t;try{return JSON.stringify(t)}catch{return "Unknown error"}}function m(t,e,n){let r={kind:t,message:Me(e)};return n&&(r.detail=n),r}var Be=12e3,_e=2048,Ie=.92;function oe(){return Date.now()}function Le(t){return new Promise((e,n)=>{let r=()=>n(new Error("Aborted"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:true});})}async function ie(t,e,n){let r=new Promise((i,s)=>{let l=setTimeout(()=>s(new Error("Timeout")),e);typeof l.unref=="function"&&l.unref();}),o=[t,r];return n&&o.push(Le(n)),await Promise.race(o)}function De(t){if(!h())return 1;let e=window.devicePixelRatio||1,n=t?.pixelRatio??Math.min(e,2);return Math.max(.1,n)}function Ne(t){return !!(t?.element||t?.fullPage)}function ae(t){let e={mime:t.mime,pixelRatio:t.pixelRatio,maxDimension:t.maxDimension};return t.includeQuality&&(e.quality=t.quality),t.signal&&(e.signal=t.signal),e}async function se(t){let{selectionElement:e,capture:n,signal:r}=t;if(!h()||!Ne(n))return;let o=oe(),i=[],s=n?.timeoutMs??Be,l=n?.maxDimension??_e,f=n?.mime??"image/png",p=n?.quality??Ie,w=n?.adapter??re(),y={},E=De(n);if(n?.element&&e)try{let c=e.getBoundingClientRect(),u=Math.min(1,l/Math.max(c.width,c.height)),v=Math.min(E,E*u),x=await ie(Promise.resolve(w.captureElement(e,{...ae({mime:f,quality:p,pixelRatio:v,maxDimension:l,includeQuality:f==="image/jpeg",...r?{signal:r}:{}})})),s,r);y.element=x;}catch(c){if(r?.aborted)throw c;i.push(m("capture_failed",c,{target:"element"}));}if(n?.fullPage)try{let c=await ie(Promise.resolve(w.captureFullPage(ae({mime:f,quality:p,pixelRatio:E,maxDimension:l,includeQuality:f==="image/jpeg",...r?{signal:r}:{}}))),s,r);y.fullPage=c;}catch(c){if(r?.aborted)throw c;i.push(m("capture_failed",c,{target:"fullPage"}));}let g=oe(),a={startedAt:o,finishedAt:g,durationMs:Math.max(0,g-o)};return i.length>0&&(a.errors=i),{...y,diagnostics:a}}function Ue(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Oe(){return h()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Ue()}:{}}function He(t){if(!t)return {};let e={};return t.id&&(e.userId=t.id),t.email&&(e.userEmail=t.email),t.name&&(e.userName=t.name),e}async function le(t){let{config:e,context:n,user:r}=t;if(e?.enabled===false)return {};let o={...Oe(),...He(r)},i=e?.enrich;if(!i)return o;try{let s=await i(n);return {...o,...s}}catch(s){let l=m("unknown",s);return {...o,blocfeedMetadataError:l.message}}}var $="blocfeed-queue",qe=50;function j(){if(!h())return [];try{let t=localStorage.getItem($);if(!t)return [];let e=JSON.parse(t);return Array.isArray(e)?e:[]}catch{return []}}function Q(t){if(h())try{t.length===0?localStorage.removeItem($):localStorage.setItem($,JSON.stringify(t));}catch{}}function $e(t){let e={...t};if(e.screenshots){let n={...e.screenshots};n.element&&(n.element={...n.element,dataUrl:""}),n.fullPage&&(n.fullPage={...n.fullPage,dataUrl:""}),e.screenshots=n;}return e}function W(t){let e=j(),n=$e(t);for(n.metadata={...n.metadata,_queued:true},e.push({payload:n,timestamp:Date.now()});e.length>qe;)e.shift();Q(e);}function ce(){let t=j();return t.length===0?[]:(Q([]),t.map(e=>e.payload))}function mt(){Q([]);}function pt(){return j().length}function z(t){let e=null,n=null,r=(...o)=>{n=o,e===null&&(e=requestAnimationFrame(()=>{if(e=null,!n)return;let i=n;n=null,t(...i);}));};return r.cancel=()=>{e!==null&&cancelAnimationFrame(e),e=null,n=null;},r}function I(t){return t instanceof Element?!!t.closest("[data-blocfeed-ui]"):false}function L(t){t.stopPropagation(),t.stopImmediatePropagation?.();}function ue(t,e){if(!h())throw new Error("BlocFeed picker can only run in a browser environment.");let n=t.ignoreSelectors,r=t.isSelectable,o={};n&&n.length>0&&(o.ignoreSelectors=n),r&&(o.isSelectable=r);let i=null,s=null,l=(a,c=false)=>{if(!a){i=null,s=null,e.onHover(null);return}let u=O(a.getBoundingClientRect()),v=`${Math.round(u.x)}:${Math.round(u.y)}:${Math.round(u.width)}:${Math.round(u.height)}`;!c&&a===i&&v===s||(i=a,s=v,e.onHover({element:a,rect:u}));},f=z(a=>{if(I(a.target))return;let c=document.elementFromPoint(a.clientX,a.clientY),u=H(c,o);l(u);}),p=z(()=>{i&&l(i,true);}),w=a=>{I(a.target)||(L(a),a.pointerType==="mouse"&&a.preventDefault());},y=a=>{I(a.target)||(L(a),a.pointerType==="mouse"&&a.preventDefault());},E=a=>{if(I(a.target))return;L(a),a.preventDefault();let c=document.elementFromPoint(a.clientX,a.clientY),u=H(c,o);u&&e.onSelect({element:u,descriptor:ee(u)});},g=a=>{a.key==="Escape"&&(L(a),a.preventDefault(),e.onCancel());};return window.addEventListener("pointermove",f,{capture:true,passive:true}),window.addEventListener("pointerdown",w,{capture:true}),window.addEventListener("pointerup",y,{capture:true}),window.addEventListener("click",E,{capture:true}),window.addEventListener("keydown",g,{capture:true}),window.addEventListener("scroll",p,{capture:true,passive:true}),window.addEventListener("resize",p,{passive:true}),{stop(){window.removeEventListener("pointermove",f,{capture:true}),window.removeEventListener("pointerdown",w,{capture:true}),window.removeEventListener("pointerup",y,{capture:true}),window.removeEventListener("click",E,{capture:true}),window.removeEventListener("keydown",g,{capture:true}),window.removeEventListener("scroll",p,{capture:true}),window.removeEventListener("resize",p),f.cancel(),p.cancel(),e.onHover(null);}}}var je=12e3,Qe=2,We=500,ze=2e3,pe="https://blocfeed.com/api/feedback",de=0;function fe(t,e){return new Promise((n,r)=>{if(e?.aborted){r(new Error("Aborted"));return}let o=setTimeout(n,t),i=()=>{clearTimeout(o),r(new Error("Aborted"));};e?.addEventListener("abort",i,{once:true});})}function Je(t){return t>=500&&t<=599}function Ye(t){let[e,n]=t.split(",",2);if(!e||!n)throw new Error("Invalid data URL");let o=/data:(.*?);base64/.exec(e)?.[1]||"application/octet-stream",i=atob(n),s=new Uint8Array(i.length);for(let l=0;l<i.length;l+=1)s[l]=i.charCodeAt(l);return new Blob([s],{type:o})}function Ke(t){let e={},n={...t};if(n.screenshots){let r={},o={...n.screenshots};o.element&&(e.element=o.element.dataUrl,r.element={mime:o.element.mime,width:o.element.width,height:o.element.height},o.element={...o.element,dataUrl:""}),o.fullPage&&(e.fullPage=o.fullPage.dataUrl,r.fullPage={mime:o.fullPage.mime,width:o.fullPage.width,height:o.fullPage.height},o.fullPage={...o.fullPage,dataUrl:""}),n.screenshots=o,(r.element||r.fullPage)&&(n.screenshot_intent=r);}return {lean:n,extracted:e}}async function me(t,e,n){let r=Ye(e);await fetch(t,{method:"PUT",body:r,headers:{"content-type":r.type},...n?{signal:n}:{}});}async function Xe(t){let{feedbackId:e,extracted:n,screenshots:r,signal:o}=t,i={};n.element&&r?.element&&(i.element={dataUrl:n.element,mime:r.element.mime,width:r.element.width,height:r.element.height}),n.fullPage&&r?.fullPage&&(i.fullPage={dataUrl:n.fullPage,mime:r.fullPage.mime,width:r.fullPage.width,height:r.fullPage.height}),Object.keys(i).length!==0&&await fetch(`${pe}/${e}/screenshots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(i),...o?{signal:o}:{}});}async function ge(t){let{signal:e,transport:n}=t;if(Date.now()-de<ze)return {ok:false,error:m("configuration",new Error("Please wait before submitting again"))};let o=n?.timeoutMs??je,i=n?.maxAttempts??Qe,s=n?.backoffMs??We,l=!!(t.payload.screenshots?.element?.dataUrl||t.payload.screenshots?.fullPage?.dataUrl),{lean:f,extracted:p}=l?Ke(t.payload):{lean:t.payload,extracted:{}},w={...p,...t.screenshotDataUrls};for(let y=1;y<=i;y+=1){let E=new AbortController,g=setTimeout(()=>E.abort(),o),a=()=>E.abort();e&&e.addEventListener("abort",a,{once:true});try{let c=await fetch(pe,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f),signal:E.signal});if(c.ok){de=Date.now();let u;try{u=await c.json();}catch{}if((w.element||w.fullPage)&&u){let d=u.upload_urls;if(d){let k=[];w.element&&d.element&&k.push(me(d.element,w.element,e)),w.fullPage&&d.fullPage&&k.push(me(d.fullPage,w.fullPage,e));try{await Promise.all(k);}catch{}}else if(u.feedback_id)try{await Xe({feedbackId:u.feedback_id,extracted:w,screenshots:t.payload.screenshots,...e?{signal:e}:{}});}catch{}}let x={ok:!0,status:c.status};return u&&(x.apiResponse=u),x}if(y<i&&Je(c.status)){let u=.85+Math.random()*.3,v=Math.round(s*2**(y-1)*u);await fe(v,e);continue}return {ok:!1,status:c.status,error:m("api_failed",new Error(`HTTP ${c.status}`))}}catch(c){if(E.signal.aborted||e?.aborted)return {ok:false,error:m("aborted",c)};if(y<i){let u=.85+Math.random()*.3,v=Math.round(s*2**(y-1)*u);await fe(v,e);continue}return {ok:false,error:m("api_failed",c)}}finally{clearTimeout(g),e&&e.removeEventListener("abort",a);}}return {ok:false,error:m("api_failed",new Error("Failed"))}}async function J(t){let{signal:e,transport:n}=t,r={ok:false};try{let o={payload:t.payload,...e?{signal:e}:{},...n?{transport:n}:{}};r.api=await ge(o),r.ok=!!r.api?.ok;}catch(o){r.api={ok:false,error:m("api_failed",o)},r.ok=false;}return {payload:t.payload,result:r}}var Ge=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function Ze(t){let e=[...Ge,...t?.ignoreSelectors??[]],n=Array.from(new Set(e));return {...t,ignoreSelectors:n}}function he(){return {phase:"idle"}}function Ve(t){if(t.ok)return false;let e=t.api;return e?e.status&&e.status>=400&&e.status<500||e.error?.kind==="aborted"||e.error?.kind==="configuration"?false:!e.ok:true}function Bt(t){let e=t,n=he(),r=new Set,o=new Set,i=null,s=null,l=null,f=null,p=0,w=null,y=()=>{for(let d of r)d(n);},E=d=>{for(let k of o)k(d);},g=d=>{n=d,y();},a=()=>{p+=1,f?.abort(),f=null;},c=()=>{i?.stop(),i=null,E(null),l!==null&&h()&&(document.documentElement.style.cursor=l,l=null);},u=()=>{a(),c(),s=null,g(he());},v=()=>{if(!h())return;c(),s=null;let d=Ze(e.picker);l=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",g({phase:"picking"}),i=ue(d,{onHover:E,onSelect:({element:k,descriptor:_})=>{s=k,c(),g({phase:"review",selection:_});},onCancel:()=>{u();}});},x=()=>{let d=ce();if(d.length!==0)for(let k of d)J({payload:k,...e.transport?{transport:e.transport}:{}}).catch(()=>{W(k);});};if(h()){setTimeout(x,1e3);let d=()=>x();window.addEventListener("online",d),w=()=>window.removeEventListener("online",d);}return {getState:()=>n,subscribe(d){return r.add(d),()=>r.delete(d)},subscribeHover(d){return o.add(d),()=>o.delete(d)},start(){n.phase==="capturing"||n.phase==="submitting"||n.phase!=="picking"&&v();},stop(){u();},clearSelection(){n.phase==="capturing"||n.phase==="submitting"||v();},setConfig(d){e=d;},async submit(d,k){if(!h()){let b=m("configuration",new Error("BlocFeed submit can only run in the browser"));return g({phase:"error",lastError:b}),{ok:false}}let _=e.blocfeed_id?.trim?.()??"";if(!_){let F={phase:"error",lastError:m("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return n.selection&&(F.selection=n.selection),g(F),{ok:false}}if(n.phase==="capturing"||n.phase==="submitting")return {ok:false};let C=p+1;p=C,f?.abort(),f=new AbortController;let A=f.signal,S=n.selection,D=k?.capture?{...e.capture,...k.capture}:e.capture,Y=!!(D?.element||D?.fullPage),K={phase:Y?"capturing":"submitting"};S&&(K.selection=S),g(K);try{let b=Y?await se({selectionElement:s,capture:D,signal:A}):void 0;if(A.aborted||p!==C)return {ok:!1};let F={phase:"submitting"};S&&(F.selection=S),b&&(F.capture=b),g(F);let T={};S&&(T.selection=S),b&&(T.capture=b);let we=await le({config:e.metadata,context:T,...e.user?{user:e.user}:{}}),M={version:1,createdAt:new Date().toISOString(),blocfeed_id:_,message:d,metadata:we};e.user&&(M.user=e.user),S&&(M.selection=S),b&&(M.screenshots=b);let{result:P}=await J({payload:M,signal:A,...e.transport?{transport:e.transport}:{}});if(A.aborted||p!==C)return P;if(P.ok){let U={phase:"success",lastSubmit:P};return S&&(U.selection=S),b&&(U.capture=b),g(U),P}Ve(P)&&W(M);let be=P.api?.error??m("unknown",new Error("Submission failed")),N={phase:"error",lastSubmit:P,lastError:be};return S&&(N.selection=S),b&&(N.capture=b),g(N),P}catch(b){if(A.aborted||p!==C)return {ok:false};let T={phase:"error",lastError:A.aborted?m("aborted",b):m("unknown",b)};return S&&(T.selection=S),g(T),{ok:false}}finally{p===C&&(f=null);}},__unsafeGetSelectedElement(){return s},destroy(){u(),r.clear(),o.clear(),w?.(),w=null;}}}
|
|
2
|
+
export{h as a,O as b,re as c,se as d,le as e,W as f,ce as g,mt as h,pt as i,Bt as j};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';function h(){return typeof window<"u"&&typeof document<"u"}function X(t){let e=globalThis.CSS;return typeof e?.escape=="function"?e.escape(t):t.replace(/[^a-zA-Z0-9_-]/g,n=>{let r=n.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function O(t){return {x:t.x,y:t.y,width:t.width,height:t.height}}function ye(t){return {x:t.x+window.scrollX,y:t.y+window.scrollY,width:t.width,height:t.height}}function Ee(t){return t.replace(/\s+/g," ").trim()}function Se(t,e=140){let n=t.textContent;if(!n)return;let r=Ee(n);if(r)return r.length<=e?r:`${r.slice(0,e-1)}\u2026`}function ke(t){let e=1;for(let n=t.previousElementSibling;n;n=n.previousElementSibling)n.tagName===t.tagName&&(e+=1);return e}var V=["data-testid","data-test-id","data-test","data-qa","data-cy"],G="data-blocfeed-component";function ve(t){let e=t.closest(`[${G}]`);if(!e)return;let r=e.getAttribute(G)?.trim();return r||void 0}function Pe(t){for(let e of V){let n=t.closest(`[${e}]`);if(!n)continue;let o=n.getAttribute(e)?.trim();if(o)return o}}function xe(t){try{let e=t,n=Object.getOwnPropertyNames(e);for(let r of n)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=e[r];if(o&&typeof o=="object")return o}}catch{}return null}function B(t){if(t&&typeof t!="string"){if(typeof t=="function"){let e=t;return typeof e.displayName=="string"&&e.displayName?e.displayName:typeof e.name=="string"&&e.name?e.name:void 0}if(typeof t=="object"){let e=t,n=e.displayName;if(typeof n=="string"&&n)return n;let r=e.render;if(typeof r=="function"){let i=r;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let o=e.type;return B(o)}}}function Fe(t){let e=xe(t);if(!e)return;let n=e._debugOwner!==void 0;if(n){let i=e._debugOwner;for(let s=0;i&&s<50;s+=1){let l=B(i.type)??B(i.elementType);if(l)return l;i=i._debugOwner;}}let r=e,o=n?80:10;for(let i=0;r&&i<o;i+=1){let s=B(r.type)??B(r.elementType);if(s)return s;r=r.return;}}function Ae(t){let e=t.tagName.toLowerCase(),n=t.getAttribute("id");if(n)return `#${X(n)}`;for(let r of V){let o=t.getAttribute(r);if(o)return `${e}[${r}="${X(o)}"]`}return `${e}:nth-of-type(${ke(t)})`}function Te(t,e=10){let n=[],r=t;for(;r&&n.length<e;){let o=Ae(r);if(n.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return n.join(" > ")}function Z(t,e){if(!e||e.length===0)return false;for(let n of e)if(t.closest(n))return true;return false}function H(t,e){if(!t||Z(t,e.ignoreSelectors))return null;let n=t;for(;n;){if(Z(n,e.ignoreSelectors))return null;if(e.isSelectable?.(n)??Re(n))return n;n=n.parentElement;}return null}function Re(t){let e=t.tagName;return !(e==="HTML"||e==="BODY")}function ee(t){let e=t.getBoundingClientRect(),n={selector:Te(t),tagName:t.tagName.toLowerCase(),rect:O(e),pageRect:ye(e)},r=t.getAttribute("id");r&&(n.id=r);let o=t.className;typeof o=="string"&&o.trim()&&(n.className=o);let i=Se(t);i&&(n.textSnippet=i);let s=ve(t)??Fe(t);s&&(n.componentName=s);let l=Pe(t);return l&&(n.testId=l),n}var q=null;async function te(){return q||(q=import('html-to-image')),q}async function Ce(t){return await new Promise((e,n)=>{let r=new Image;r.onload=()=>e({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>n(new Error("Failed to load generated screenshot")),r.src=t;})}async function ne(t,e){let{width:n,height:r}=await Ce(t);return {dataUrl:t,mime:e,width:n,height:r}}function R(t){if(t?.aborted)throw new Error("Aborted")}function re(){return {async captureElement(t,e){if(!h())throw new Error("captureElement can only run in the browser");R(e.signal);let n=await te();R(e.signal);let r=e.mime==="image/jpeg"?await n.toJpeg(t,{quality:e.quality??.92,pixelRatio:e.pixelRatio}):await n.toPng(t,{pixelRatio:e.pixelRatio});return R(e.signal),await ne(r,e.mime)},async captureFullPage(t){if(!h())throw new Error("captureFullPage can only run in the browser");R(t.signal);let e=document.documentElement,n=Math.max(e.scrollWidth,e.clientWidth),r=Math.max(e.scrollHeight,e.clientHeight),o=Math.min(1,t.maxDimension/Math.max(n,r)),i=Math.max(1,Math.round(n*o)),s=Math.max(1,Math.round(r*o)),l=await te();R(t.signal);let f=t.mime==="image/jpeg"?await l.toJpeg(e,{width:i,height:s,quality:t.quality??.92,pixelRatio:t.pixelRatio}):await l.toPng(e,{width:i,height:s,pixelRatio:t.pixelRatio});return R(t.signal),await ne(f,t.mime)}}}function Me(t){if(t instanceof Error)return t.message;if(typeof t=="string")return t;try{return JSON.stringify(t)}catch{return "Unknown error"}}function m(t,e,n){let r={kind:t,message:Me(e)};return n&&(r.detail=n),r}var Be=12e3,_e=2048,Ie=.92;function oe(){return Date.now()}function Le(t){return new Promise((e,n)=>{let r=()=>n(new Error("Aborted"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:true});})}async function ie(t,e,n){let r=new Promise((i,s)=>{let l=setTimeout(()=>s(new Error("Timeout")),e);typeof l.unref=="function"&&l.unref();}),o=[t,r];return n&&o.push(Le(n)),await Promise.race(o)}function De(t){if(!h())return 1;let e=window.devicePixelRatio||1,n=t?.pixelRatio??Math.min(e,2);return Math.max(.1,n)}function Ne(t){return !!(t?.element||t?.fullPage)}function ae(t){let e={mime:t.mime,pixelRatio:t.pixelRatio,maxDimension:t.maxDimension};return t.includeQuality&&(e.quality=t.quality),t.signal&&(e.signal=t.signal),e}async function se(t){let{selectionElement:e,capture:n,signal:r}=t;if(!h()||!Ne(n))return;let o=oe(),i=[],s=n?.timeoutMs??Be,l=n?.maxDimension??_e,f=n?.mime??"image/png",p=n?.quality??Ie,w=n?.adapter??re(),y={},E=De(n);if(n?.element&&e)try{let c=e.getBoundingClientRect(),u=Math.min(1,l/Math.max(c.width,c.height)),v=Math.min(E,E*u),x=await ie(Promise.resolve(w.captureElement(e,{...ae({mime:f,quality:p,pixelRatio:v,maxDimension:l,includeQuality:f==="image/jpeg",...r?{signal:r}:{}})})),s,r);y.element=x;}catch(c){if(r?.aborted)throw c;i.push(m("capture_failed",c,{target:"element"}));}if(n?.fullPage)try{let c=await ie(Promise.resolve(w.captureFullPage(ae({mime:f,quality:p,pixelRatio:E,maxDimension:l,includeQuality:f==="image/jpeg",...r?{signal:r}:{}}))),s,r);y.fullPage=c;}catch(c){if(r?.aborted)throw c;i.push(m("capture_failed",c,{target:"fullPage"}));}let g=oe(),a={startedAt:o,finishedAt:g,durationMs:Math.max(0,g-o)};return i.length>0&&(a.errors=i),{...y,diagnostics:a}}function Ue(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Oe(){return h()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Ue()}:{}}function He(t){if(!t)return {};let e={};return t.id&&(e.userId=t.id),t.email&&(e.userEmail=t.email),t.name&&(e.userName=t.name),e}async function le(t){let{config:e,context:n,user:r}=t;if(e?.enabled===false)return {};let o={...Oe(),...He(r)},i=e?.enrich;if(!i)return o;try{let s=await i(n);return {...o,...s}}catch(s){let l=m("unknown",s);return {...o,blocfeedMetadataError:l.message}}}var $="blocfeed-queue",qe=50;function j(){if(!h())return [];try{let t=localStorage.getItem($);if(!t)return [];let e=JSON.parse(t);return Array.isArray(e)?e:[]}catch{return []}}function Q(t){if(h())try{t.length===0?localStorage.removeItem($):localStorage.setItem($,JSON.stringify(t));}catch{}}function $e(t){let e={...t};if(e.screenshots){let n={...e.screenshots};n.element&&(n.element={...n.element,dataUrl:""}),n.fullPage&&(n.fullPage={...n.fullPage,dataUrl:""}),e.screenshots=n;}return e}function W(t){let e=j(),n=$e(t);for(n.metadata={...n.metadata,_queued:true},e.push({payload:n,timestamp:Date.now()});e.length>qe;)e.shift();Q(e);}function ce(){let t=j();return t.length===0?[]:(Q([]),t.map(e=>e.payload))}function mt(){Q([]);}function pt(){return j().length}function z(t){let e=null,n=null,r=(...o)=>{n=o,e===null&&(e=requestAnimationFrame(()=>{if(e=null,!n)return;let i=n;n=null,t(...i);}));};return r.cancel=()=>{e!==null&&cancelAnimationFrame(e),e=null,n=null;},r}function I(t){return t instanceof Element?!!t.closest("[data-blocfeed-ui]"):false}function L(t){t.stopPropagation(),t.stopImmediatePropagation?.();}function ue(t,e){if(!h())throw new Error("BlocFeed picker can only run in a browser environment.");let n=t.ignoreSelectors,r=t.isSelectable,o={};n&&n.length>0&&(o.ignoreSelectors=n),r&&(o.isSelectable=r);let i=null,s=null,l=(a,c=false)=>{if(!a){i=null,s=null,e.onHover(null);return}let u=O(a.getBoundingClientRect()),v=`${Math.round(u.x)}:${Math.round(u.y)}:${Math.round(u.width)}:${Math.round(u.height)}`;!c&&a===i&&v===s||(i=a,s=v,e.onHover({element:a,rect:u}));},f=z(a=>{if(I(a.target))return;let c=document.elementFromPoint(a.clientX,a.clientY),u=H(c,o);l(u);}),p=z(()=>{i&&l(i,true);}),w=a=>{I(a.target)||(L(a),a.pointerType==="mouse"&&a.preventDefault());},y=a=>{I(a.target)||(L(a),a.pointerType==="mouse"&&a.preventDefault());},E=a=>{if(I(a.target))return;L(a),a.preventDefault();let c=document.elementFromPoint(a.clientX,a.clientY),u=H(c,o);u&&e.onSelect({element:u,descriptor:ee(u)});},g=a=>{a.key==="Escape"&&(L(a),a.preventDefault(),e.onCancel());};return window.addEventListener("pointermove",f,{capture:true,passive:true}),window.addEventListener("pointerdown",w,{capture:true}),window.addEventListener("pointerup",y,{capture:true}),window.addEventListener("click",E,{capture:true}),window.addEventListener("keydown",g,{capture:true}),window.addEventListener("scroll",p,{capture:true,passive:true}),window.addEventListener("resize",p,{passive:true}),{stop(){window.removeEventListener("pointermove",f,{capture:true}),window.removeEventListener("pointerdown",w,{capture:true}),window.removeEventListener("pointerup",y,{capture:true}),window.removeEventListener("click",E,{capture:true}),window.removeEventListener("keydown",g,{capture:true}),window.removeEventListener("scroll",p,{capture:true}),window.removeEventListener("resize",p),f.cancel(),p.cancel(),e.onHover(null);}}}var je=12e3,Qe=2,We=500,ze=2e3,pe="https://blocfeed.com/api/feedback",de=0;function fe(t,e){return new Promise((n,r)=>{if(e?.aborted){r(new Error("Aborted"));return}let o=setTimeout(n,t),i=()=>{clearTimeout(o),r(new Error("Aborted"));};e?.addEventListener("abort",i,{once:true});})}function Je(t){return t>=500&&t<=599}function Ye(t){let[e,n]=t.split(",",2);if(!e||!n)throw new Error("Invalid data URL");let o=/data:(.*?);base64/.exec(e)?.[1]||"application/octet-stream",i=atob(n),s=new Uint8Array(i.length);for(let l=0;l<i.length;l+=1)s[l]=i.charCodeAt(l);return new Blob([s],{type:o})}function Ke(t){let e={},n={...t};if(n.screenshots){let r={},o={...n.screenshots};o.element&&(e.element=o.element.dataUrl,r.element={mime:o.element.mime,width:o.element.width,height:o.element.height},o.element={...o.element,dataUrl:""}),o.fullPage&&(e.fullPage=o.fullPage.dataUrl,r.fullPage={mime:o.fullPage.mime,width:o.fullPage.width,height:o.fullPage.height},o.fullPage={...o.fullPage,dataUrl:""}),n.screenshots=o,(r.element||r.fullPage)&&(n.screenshot_intent=r);}return {lean:n,extracted:e}}async function me(t,e,n){let r=Ye(e);await fetch(t,{method:"PUT",body:r,headers:{"content-type":r.type},...n?{signal:n}:{}});}async function Xe(t){let{feedbackId:e,extracted:n,screenshots:r,signal:o}=t,i={};n.element&&r?.element&&(i.element={dataUrl:n.element,mime:r.element.mime,width:r.element.width,height:r.element.height}),n.fullPage&&r?.fullPage&&(i.fullPage={dataUrl:n.fullPage,mime:r.fullPage.mime,width:r.fullPage.width,height:r.fullPage.height}),Object.keys(i).length!==0&&await fetch(`${pe}/${e}/screenshots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(i),...o?{signal:o}:{}});}async function ge(t){let{signal:e,transport:n}=t;if(Date.now()-de<ze)return {ok:false,error:m("configuration",new Error("Please wait before submitting again"))};let o=n?.timeoutMs??je,i=n?.maxAttempts??Qe,s=n?.backoffMs??We,l=!!(t.payload.screenshots?.element?.dataUrl||t.payload.screenshots?.fullPage?.dataUrl),{lean:f,extracted:p}=l?Ke(t.payload):{lean:t.payload,extracted:{}},w={...p,...t.screenshotDataUrls};for(let y=1;y<=i;y+=1){let E=new AbortController,g=setTimeout(()=>E.abort(),o),a=()=>E.abort();e&&e.addEventListener("abort",a,{once:true});try{let c=await fetch(pe,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f),signal:E.signal});if(c.ok){de=Date.now();let u;try{u=await c.json();}catch{}if((w.element||w.fullPage)&&u){let d=u.upload_urls;if(d){let k=[];w.element&&d.element&&k.push(me(d.element,w.element,e)),w.fullPage&&d.fullPage&&k.push(me(d.fullPage,w.fullPage,e));try{await Promise.all(k);}catch{}}else if(u.feedback_id)try{await Xe({feedbackId:u.feedback_id,extracted:w,screenshots:t.payload.screenshots,...e?{signal:e}:{}});}catch{}}let x={ok:!0,status:c.status};return u&&(x.apiResponse=u),x}if(y<i&&Je(c.status)){let u=.85+Math.random()*.3,v=Math.round(s*2**(y-1)*u);await fe(v,e);continue}return {ok:!1,status:c.status,error:m("api_failed",new Error(`HTTP ${c.status}`))}}catch(c){if(E.signal.aborted||e?.aborted)return {ok:false,error:m("aborted",c)};if(y<i){let u=.85+Math.random()*.3,v=Math.round(s*2**(y-1)*u);await fe(v,e);continue}return {ok:false,error:m("api_failed",c)}}finally{clearTimeout(g),e&&e.removeEventListener("abort",a);}}return {ok:false,error:m("api_failed",new Error("Failed"))}}async function J(t){let{signal:e,transport:n}=t,r={ok:false};try{let o={payload:t.payload,...e?{signal:e}:{},...n?{transport:n}:{}};r.api=await ge(o),r.ok=!!r.api?.ok;}catch(o){r.api={ok:false,error:m("api_failed",o)},r.ok=false;}return {payload:t.payload,result:r}}var Ge=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function Ze(t){let e=[...Ge,...t?.ignoreSelectors??[]],n=Array.from(new Set(e));return {...t,ignoreSelectors:n}}function he(){return {phase:"idle"}}function Ve(t){if(t.ok)return false;let e=t.api;return e?e.status&&e.status>=400&&e.status<500||e.error?.kind==="aborted"||e.error?.kind==="configuration"?false:!e.ok:true}function Bt(t){let e=t,n=he(),r=new Set,o=new Set,i=null,s=null,l=null,f=null,p=0,w=null,y=()=>{for(let d of r)d(n);},E=d=>{for(let k of o)k(d);},g=d=>{n=d,y();},a=()=>{p+=1,f?.abort(),f=null;},c=()=>{i?.stop(),i=null,E(null),l!==null&&h()&&(document.documentElement.style.cursor=l,l=null);},u=()=>{a(),c(),s=null,g(he());},v=()=>{if(!h())return;c(),s=null;let d=Ze(e.picker);l=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",g({phase:"picking"}),i=ue(d,{onHover:E,onSelect:({element:k,descriptor:_})=>{s=k,c(),g({phase:"review",selection:_});},onCancel:()=>{u();}});},x=()=>{let d=ce();if(d.length!==0)for(let k of d)J({payload:k,...e.transport?{transport:e.transport}:{}}).catch(()=>{W(k);});};if(h()){setTimeout(x,1e3);let d=()=>x();window.addEventListener("online",d),w=()=>window.removeEventListener("online",d);}return {getState:()=>n,subscribe(d){return r.add(d),()=>r.delete(d)},subscribeHover(d){return o.add(d),()=>o.delete(d)},start(){n.phase==="capturing"||n.phase==="submitting"||n.phase!=="picking"&&v();},stop(){u();},clearSelection(){n.phase==="capturing"||n.phase==="submitting"||v();},setConfig(d){e=d;},async submit(d,k){if(!h()){let b=m("configuration",new Error("BlocFeed submit can only run in the browser"));return g({phase:"error",lastError:b}),{ok:false}}let _=e.blocfeed_id?.trim?.()??"";if(!_){let F={phase:"error",lastError:m("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return n.selection&&(F.selection=n.selection),g(F),{ok:false}}if(n.phase==="capturing"||n.phase==="submitting")return {ok:false};let C=p+1;p=C,f?.abort(),f=new AbortController;let A=f.signal,S=n.selection,D=k?.capture?{...e.capture,...k.capture}:e.capture,Y=!!(D?.element||D?.fullPage),K={phase:Y?"capturing":"submitting"};S&&(K.selection=S),g(K);try{let b=Y?await se({selectionElement:s,capture:D,signal:A}):void 0;if(A.aborted||p!==C)return {ok:!1};let F={phase:"submitting"};S&&(F.selection=S),b&&(F.capture=b),g(F);let T={};S&&(T.selection=S),b&&(T.capture=b);let we=await le({config:e.metadata,context:T,...e.user?{user:e.user}:{}}),M={version:1,createdAt:new Date().toISOString(),blocfeed_id:_,message:d,metadata:we};e.user&&(M.user=e.user),S&&(M.selection=S),b&&(M.screenshots=b);let{result:P}=await J({payload:M,signal:A,...e.transport?{transport:e.transport}:{}});if(A.aborted||p!==C)return P;if(P.ok){let U={phase:"success",lastSubmit:P};return S&&(U.selection=S),b&&(U.capture=b),g(U),P}Ve(P)&&W(M);let be=P.api?.error??m("unknown",new Error("Submission failed")),N={phase:"error",lastSubmit:P,lastError:be};return S&&(N.selection=S),b&&(N.capture=b),g(N),P}catch(b){if(A.aborted||p!==C)return {ok:false};let T={phase:"error",lastError:A.aborted?m("aborted",b):m("unknown",b)};return S&&(T.selection=S),g(T),{ok:false}}finally{p===C&&(f=null);}},__unsafeGetSelectedElement(){return s},destroy(){u(),r.clear(),o.clear(),w?.(),w=null;}}}
|
|
2
|
+
exports.a=h;exports.b=O;exports.c=re;exports.d=se;exports.e=le;exports.f=W;exports.g=ce;exports.h=mt;exports.i=pt;exports.j=Bt;
|
|
@@ -10,6 +10,27 @@ interface ImageAsset {
|
|
|
10
10
|
width: number;
|
|
11
11
|
height: number;
|
|
12
12
|
}
|
|
13
|
+
interface BlocFeedUser {
|
|
14
|
+
id?: string;
|
|
15
|
+
email?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
}
|
|
18
|
+
interface TransportConfig {
|
|
19
|
+
/** Timeout per request attempt in milliseconds. Default: 12 000 */
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
/** Maximum number of retry attempts. Default: 2 */
|
|
22
|
+
maxAttempts?: number;
|
|
23
|
+
/** Base backoff delay in milliseconds. Default: 500 */
|
|
24
|
+
backoffMs?: number;
|
|
25
|
+
}
|
|
26
|
+
type WidgetPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
27
|
+
type TriggerStyle = "classic" | "dot" | "bubble" | "edge-tab" | "pulse-ring" | "minimal" | "icon-pop";
|
|
28
|
+
interface ThemeConfig {
|
|
29
|
+
accentColor?: string;
|
|
30
|
+
panelBackground?: string;
|
|
31
|
+
panelForeground?: string;
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
}
|
|
13
34
|
interface Rect {
|
|
14
35
|
x: number;
|
|
15
36
|
y: number;
|
|
@@ -121,11 +142,31 @@ interface BlocFeedConfig {
|
|
|
121
142
|
capture?: CaptureConfig;
|
|
122
143
|
picker?: PickerConfig;
|
|
123
144
|
metadata?: MetadataConfig;
|
|
145
|
+
/** First-class user identity attached to every submission. */
|
|
146
|
+
user?: BlocFeedUser;
|
|
147
|
+
/** Transport / retry configuration. */
|
|
148
|
+
transport?: TransportConfig;
|
|
124
149
|
ui?: {
|
|
125
|
-
/**
|
|
126
|
-
* z-index for the widget overlay/panel.
|
|
127
|
-
*/
|
|
150
|
+
/** z-index for the widget overlay/panel. */
|
|
128
151
|
zIndex?: number;
|
|
152
|
+
/** Position of the trigger button. Default: "bottom-right" */
|
|
153
|
+
position?: WidgetPosition;
|
|
154
|
+
/** Theme overrides. */
|
|
155
|
+
theme?: ThemeConfig;
|
|
156
|
+
/** Trigger button animation style. Default: "classic" */
|
|
157
|
+
triggerStyle?: TriggerStyle;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
interface ScreenshotIntent {
|
|
161
|
+
element?: {
|
|
162
|
+
mime: string;
|
|
163
|
+
width: number;
|
|
164
|
+
height: number;
|
|
165
|
+
};
|
|
166
|
+
fullPage?: {
|
|
167
|
+
mime: string;
|
|
168
|
+
width: number;
|
|
169
|
+
height: number;
|
|
129
170
|
};
|
|
130
171
|
}
|
|
131
172
|
interface FeedbackPayload {
|
|
@@ -135,8 +176,20 @@ interface FeedbackPayload {
|
|
|
135
176
|
message: string;
|
|
136
177
|
selection?: ElementDescriptor;
|
|
137
178
|
screenshots?: CaptureResult;
|
|
179
|
+
/** Lightweight screenshot metadata sent instead of base64 dataUrls. */
|
|
180
|
+
screenshot_intent?: ScreenshotIntent;
|
|
181
|
+
/** First-class user identity. */
|
|
182
|
+
user?: BlocFeedUser;
|
|
138
183
|
metadata: Record<string, unknown>;
|
|
139
184
|
}
|
|
185
|
+
interface FeedbackApiResponse {
|
|
186
|
+
success: boolean;
|
|
187
|
+
feedback_id?: string;
|
|
188
|
+
upload_urls?: {
|
|
189
|
+
element?: string;
|
|
190
|
+
fullPage?: string;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
140
193
|
interface TransportResult {
|
|
141
194
|
ok: boolean;
|
|
142
195
|
error?: BlocFeedError;
|
|
@@ -179,4 +232,4 @@ interface BlocFeedController {
|
|
|
179
232
|
}
|
|
180
233
|
declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
|
|
181
234
|
|
|
182
|
-
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type
|
|
235
|
+
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type FeedbackApiResponse as F, type HoverListener as H, type ImageAsset as I, type MaybePromise as M, type PickerConfig as P, type Rect as R, type SubmitResult as S, type ThemeConfig as T, type WidgetPosition as W, type BlocFeedState as a, type BlocFeedController as b, type BlocFeedError as c, type BlocFeedUser as d, type CaptureDiagnostics as e, type CaptureResult as f, type FeedbackPayload as g, type MetadataConfig as h, type MetadataContext as i, type ScreenshotAdapter as j, type ScreenshotAdapterOptions as k, type ScreenshotIntent as l, type ScreenshotMime as m, type SessionPhase as n, type TransportConfig as o, type TransportResult as p, type TriggerStyle as q, type StateListener as r, createBlocFeedController as s };
|
|
@@ -10,6 +10,27 @@ interface ImageAsset {
|
|
|
10
10
|
width: number;
|
|
11
11
|
height: number;
|
|
12
12
|
}
|
|
13
|
+
interface BlocFeedUser {
|
|
14
|
+
id?: string;
|
|
15
|
+
email?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
}
|
|
18
|
+
interface TransportConfig {
|
|
19
|
+
/** Timeout per request attempt in milliseconds. Default: 12 000 */
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
/** Maximum number of retry attempts. Default: 2 */
|
|
22
|
+
maxAttempts?: number;
|
|
23
|
+
/** Base backoff delay in milliseconds. Default: 500 */
|
|
24
|
+
backoffMs?: number;
|
|
25
|
+
}
|
|
26
|
+
type WidgetPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
27
|
+
type TriggerStyle = "classic" | "dot" | "bubble" | "edge-tab" | "pulse-ring" | "minimal" | "icon-pop";
|
|
28
|
+
interface ThemeConfig {
|
|
29
|
+
accentColor?: string;
|
|
30
|
+
panelBackground?: string;
|
|
31
|
+
panelForeground?: string;
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
}
|
|
13
34
|
interface Rect {
|
|
14
35
|
x: number;
|
|
15
36
|
y: number;
|
|
@@ -121,11 +142,31 @@ interface BlocFeedConfig {
|
|
|
121
142
|
capture?: CaptureConfig;
|
|
122
143
|
picker?: PickerConfig;
|
|
123
144
|
metadata?: MetadataConfig;
|
|
145
|
+
/** First-class user identity attached to every submission. */
|
|
146
|
+
user?: BlocFeedUser;
|
|
147
|
+
/** Transport / retry configuration. */
|
|
148
|
+
transport?: TransportConfig;
|
|
124
149
|
ui?: {
|
|
125
|
-
/**
|
|
126
|
-
* z-index for the widget overlay/panel.
|
|
127
|
-
*/
|
|
150
|
+
/** z-index for the widget overlay/panel. */
|
|
128
151
|
zIndex?: number;
|
|
152
|
+
/** Position of the trigger button. Default: "bottom-right" */
|
|
153
|
+
position?: WidgetPosition;
|
|
154
|
+
/** Theme overrides. */
|
|
155
|
+
theme?: ThemeConfig;
|
|
156
|
+
/** Trigger button animation style. Default: "classic" */
|
|
157
|
+
triggerStyle?: TriggerStyle;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
interface ScreenshotIntent {
|
|
161
|
+
element?: {
|
|
162
|
+
mime: string;
|
|
163
|
+
width: number;
|
|
164
|
+
height: number;
|
|
165
|
+
};
|
|
166
|
+
fullPage?: {
|
|
167
|
+
mime: string;
|
|
168
|
+
width: number;
|
|
169
|
+
height: number;
|
|
129
170
|
};
|
|
130
171
|
}
|
|
131
172
|
interface FeedbackPayload {
|
|
@@ -135,8 +176,20 @@ interface FeedbackPayload {
|
|
|
135
176
|
message: string;
|
|
136
177
|
selection?: ElementDescriptor;
|
|
137
178
|
screenshots?: CaptureResult;
|
|
179
|
+
/** Lightweight screenshot metadata sent instead of base64 dataUrls. */
|
|
180
|
+
screenshot_intent?: ScreenshotIntent;
|
|
181
|
+
/** First-class user identity. */
|
|
182
|
+
user?: BlocFeedUser;
|
|
138
183
|
metadata: Record<string, unknown>;
|
|
139
184
|
}
|
|
185
|
+
interface FeedbackApiResponse {
|
|
186
|
+
success: boolean;
|
|
187
|
+
feedback_id?: string;
|
|
188
|
+
upload_urls?: {
|
|
189
|
+
element?: string;
|
|
190
|
+
fullPage?: string;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
140
193
|
interface TransportResult {
|
|
141
194
|
ok: boolean;
|
|
142
195
|
error?: BlocFeedError;
|
|
@@ -179,4 +232,4 @@ interface BlocFeedController {
|
|
|
179
232
|
}
|
|
180
233
|
declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
|
|
181
234
|
|
|
182
|
-
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type
|
|
235
|
+
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type FeedbackApiResponse as F, type HoverListener as H, type ImageAsset as I, type MaybePromise as M, type PickerConfig as P, type Rect as R, type SubmitResult as S, type ThemeConfig as T, type WidgetPosition as W, type BlocFeedState as a, type BlocFeedController as b, type BlocFeedError as c, type BlocFeedUser as d, type CaptureDiagnostics as e, type CaptureResult as f, type FeedbackPayload as g, type MetadataConfig as h, type MetadataContext as i, type ScreenshotAdapter as j, type ScreenshotAdapterOptions as k, type ScreenshotIntent as l, type ScreenshotMime as m, type SessionPhase as n, type TransportConfig as o, type TransportResult as p, type TriggerStyle as q, type StateListener as r, createBlocFeedController as s };
|
package/dist/engine.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkJLPJP7DD_cjs=require('./chunk-JLPJP7DD.cjs');function c(o){if(o?.aborted)throw new Error("Aborted")}async function A(o){return await new Promise((t,e)=>{let r=new Image;r.onload=()=>t({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>e(new Error("Failed to load generated screenshot")),r.src=o;})}async function l(o,t){let{width:e,height:r}=await A(o);return {dataUrl:o,mime:t,width:e,height:r}}function x(o){return {async captureElement(t,e){if(!chunkJLPJP7DD_cjs.a())throw new Error("captureElement can only run in the browser");c(e.signal);let r={scale:e.pixelRatio},n=e.mime==="image/jpeg"?await o.domToJpeg(t,{...r,quality:e.quality??.92}):await o.domToPng(t,r);return c(e.signal),await l(n,e.mime)},async captureFullPage(t){if(!chunkJLPJP7DD_cjs.a())throw new Error("captureFullPage can only run in the browser");c(t.signal);let e=document.documentElement,r=Math.max(e.scrollWidth,e.clientWidth),n=Math.max(e.scrollHeight,e.clientHeight),a=Math.min(1,t.maxDimension/Math.max(r,n)),s={width:Math.max(1,Math.round(r*a)),height:Math.max(1,Math.round(n*a)),scale:t.pixelRatio},i=t.mime==="image/jpeg"?await o.domToJpeg(e,{...s,quality:t.quality??.92}):await o.domToPng(e,s);return c(t.signal),await l(i,t.mime)}}}function b(o){let[t,e]=o.split(",",2);if(!t||!e)throw new Error("Invalid data URL");let n=/data:(.*?);base64/.exec(t)?.[1]||"application/octet-stream",a=atob(e),s=new Uint8Array(a.length);for(let i=0;i<a.length;i+=1)s[i]=a.charCodeAt(i);return new Blob([s],{type:n})}Object.defineProperty(exports,"clearQueue",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.h}});Object.defineProperty(exports,"collectMetadata",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.e}});Object.defineProperty(exports,"createBlocFeedController",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.j}});Object.defineProperty(exports,"createHtmlToImageAdapter",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.c}});Object.defineProperty(exports,"dequeueAll",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.g}});Object.defineProperty(exports,"enqueue",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.f}});Object.defineProperty(exports,"getQueueSize",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.i}});Object.defineProperty(exports,"runCapture",{enumerable:true,get:function(){return chunkJLPJP7DD_cjs.d}});exports.createModernScreenshotAdapter=x;exports.dataUrlToBlob=b;
|