blocfeed 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -2
- package/README.md +219 -6
- package/dist/chunk-FMFONS5S.js +2 -0
- package/dist/chunk-JLPJP7DD.cjs +2 -0
- package/dist/{controller-DmWFVHoj.d.cts → controller-olqOZLhp.d.cts} +54 -4
- package/dist/{controller-DmWFVHoj.d.ts → controller-olqOZLhp.d.ts} +54 -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 +87 -2
- package/dist/main.d.cts +2 -2
- package/dist/main.d.ts +2 -2
- package/dist/main.js +87 -2
- package/package.json +11 -1
- package/dist/chunk-37P3GL6V.js +0 -2
- package/dist/chunk-PEPIK3FN.cjs +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.3.0 — 2026-02-20
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### New features
|
|
6
|
+
|
|
7
|
+
- **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`.
|
|
8
|
+
- **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`.
|
|
9
|
+
- **Widget positioning** — new `config.ui.position` option places the trigger button in any corner: `"bottom-right"` (default), `"bottom-left"`, `"top-right"`, `"top-left"`.
|
|
10
|
+
- **Theme customization** — new `config.ui.theme` option overrides visual appearance via CSS variables: `accentColor`, `panelBackground`, `panelForeground`, `fontFamily`.
|
|
11
|
+
- **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.
|
|
12
|
+
- **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"`.
|
|
13
|
+
- **Client-side rate limiting** — minimum 2-second interval enforced between submissions to prevent accidental spam.
|
|
14
|
+
- **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.
|
|
15
|
+
- **Capture progress indicator** — pulsing spinner animation shown during screenshot capture and submission phases.
|
|
16
|
+
|
|
17
|
+
### Improvements
|
|
18
|
+
|
|
19
|
+
- **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.
|
|
20
|
+
- **Full-page screenshot storage** — full-page screenshots are now processed and stored on the platform (previously captured but discarded server-side).
|
|
21
|
+
- **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.
|
|
22
|
+
- **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.
|
|
23
|
+
- **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.
|
|
24
|
+
|
|
25
|
+
### Platform changes (blocfeed-frontend)
|
|
26
|
+
|
|
27
|
+
- `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.
|
|
28
|
+
- New `POST /api/feedback/:id/screenshots` fallback endpoint for uploading screenshots as base64 when object storage is unavailable.
|
|
29
|
+
- New SQL migration `scripts/026_fullpage_screenshots.sql` adds `screenshot_fullpage_*` and `meta_user_*` columns.
|
|
6
30
|
|
|
7
31
|
## 0.2.2 — 2026-02-17
|
|
8
32
|
|
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,140 @@ 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
|
|
106
|
+
ui: {
|
|
107
|
+
position: "bottom-right", // "bottom-left" | "top-right" | "top-left"
|
|
108
|
+
zIndex: 99999,
|
|
109
|
+
theme: {
|
|
110
|
+
accentColor: "#12D393",
|
|
111
|
+
panelBackground: "rgba(0, 0, 0, 0.95)",
|
|
112
|
+
panelForeground: "#ffffff",
|
|
113
|
+
fontFamily: "Inter, sans-serif",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Screenshot defaults
|
|
118
|
+
capture: {
|
|
119
|
+
element: true,
|
|
120
|
+
fullPage: false,
|
|
121
|
+
mime: "image/png", // or "image/jpeg"
|
|
122
|
+
quality: 0.92, // JPEG quality (0–1)
|
|
123
|
+
timeoutMs: 12000,
|
|
124
|
+
maxDimension: 2048,
|
|
125
|
+
pixelRatio: 2,
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Retry & transport
|
|
129
|
+
transport: {
|
|
130
|
+
timeoutMs: 12000, // per-request timeout
|
|
131
|
+
maxAttempts: 2, // retry count
|
|
132
|
+
backoffMs: 500, // base backoff delay
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Metadata enrichment
|
|
136
|
+
metadata: {
|
|
137
|
+
enabled: true,
|
|
138
|
+
enrich: async (context) => ({
|
|
139
|
+
orgId: "org_abc",
|
|
140
|
+
plan: "pro",
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// Picker rules
|
|
145
|
+
picker: {
|
|
146
|
+
ignoreSelectors: ["[data-private]"],
|
|
147
|
+
isSelectable: (el) => el.tagName !== "HTML",
|
|
148
|
+
},
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## User Identity
|
|
154
|
+
|
|
155
|
+
Attach user information to every feedback submission:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
<BlocFeedWidget
|
|
159
|
+
blocfeed_id="bf_..."
|
|
160
|
+
config={{
|
|
161
|
+
user: {
|
|
162
|
+
id: currentUser.id,
|
|
163
|
+
email: currentUser.email,
|
|
164
|
+
name: currentUser.name,
|
|
165
|
+
},
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
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`.
|
|
171
|
+
|
|
172
|
+
## Widget Position
|
|
173
|
+
|
|
174
|
+
Place the trigger button in any corner:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
config={{
|
|
178
|
+
ui: { position: "bottom-left" } // default: "bottom-right"
|
|
179
|
+
}}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Options: `"bottom-right"` | `"bottom-left"` | `"top-right"` | `"top-left"`
|
|
183
|
+
|
|
184
|
+
## Theme Customization
|
|
185
|
+
|
|
186
|
+
Override the widget's visual appearance via CSS variables:
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
config={{
|
|
190
|
+
ui: {
|
|
191
|
+
theme: {
|
|
192
|
+
accentColor: "#12D393", // buttons, highlights, focus rings
|
|
193
|
+
panelBackground: "rgba(0,0,0,0.95)", // panel & trigger background
|
|
194
|
+
panelForeground: "#ffffff", // text color
|
|
195
|
+
fontFamily: "Inter, sans-serif", // font stack
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
}}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Retry & Transport
|
|
202
|
+
|
|
203
|
+
Configure retry behavior for unreliable networks:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
config={{
|
|
207
|
+
transport: {
|
|
208
|
+
timeoutMs: 15000, // 15s timeout per attempt
|
|
209
|
+
maxAttempts: 3, // retry up to 3 times
|
|
210
|
+
backoffMs: 1000, // 1s base backoff (exponential with jitter)
|
|
211
|
+
},
|
|
212
|
+
}}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Offline Queue
|
|
216
|
+
|
|
217
|
+
When a submission fails due to a network error, the payload is automatically saved to `localStorage` and retried:
|
|
218
|
+
|
|
219
|
+
- On the next page load (1s after controller initialization)
|
|
220
|
+
- When the browser comes back online (`online` event)
|
|
221
|
+
|
|
222
|
+
Screenshots are stripped from queued payloads to stay within `localStorage` limits. The queue holds up to 50 items (FIFO eviction).
|
|
223
|
+
|
|
86
224
|
## Picking rules (ignore / filter)
|
|
87
225
|
|
|
88
226
|
### Ignore a subtree (recommended)
|
|
@@ -116,7 +254,7 @@ export function CheckoutButton() {
|
|
|
116
254
|
}
|
|
117
255
|
```
|
|
118
256
|
|
|
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.
|
|
257
|
+
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
258
|
|
|
121
259
|
### `selection.testId`
|
|
122
260
|
|
|
@@ -124,8 +262,44 @@ BlocFeed extracts a best-effort `testId` from common attributes like `data-testi
|
|
|
124
262
|
|
|
125
263
|
## Screenshots
|
|
126
264
|
|
|
127
|
-
|
|
128
|
-
|
|
265
|
+
Screenshots are uploaded efficiently using a two-phase flow:
|
|
266
|
+
|
|
267
|
+
1. The text payload (metadata, selection, message) is sent first without screenshot data
|
|
268
|
+
2. If the platform returns presigned upload URLs (Wasabi/S3), screenshots are uploaded directly to object storage
|
|
269
|
+
3. If presigned URLs are unavailable, screenshots fall back to a secondary POST endpoint
|
|
270
|
+
|
|
271
|
+
Both **element** and **full-page** screenshots are supported and stored on the platform.
|
|
272
|
+
|
|
273
|
+
### Known limitations
|
|
274
|
+
|
|
275
|
+
The default screenshot engine (`html-to-image`) has known issues with:
|
|
276
|
+
- Cross-origin images without permissive CORS headers
|
|
277
|
+
- CSS `clip-path` and `backdrop-filter`
|
|
278
|
+
- Some web fonts
|
|
279
|
+
|
|
280
|
+
### Alternative screenshot adapter
|
|
281
|
+
|
|
282
|
+
For better compatibility, you can use `modern-screenshot` as a drop-in replacement:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npm install modern-screenshot
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
import { createModernScreenshotAdapter } from "blocfeed/engine";
|
|
290
|
+
import * as modernScreenshot from "modern-screenshot";
|
|
291
|
+
|
|
292
|
+
<BlocFeedWidget
|
|
293
|
+
blocfeed_id="bf_..."
|
|
294
|
+
config={{
|
|
295
|
+
capture: {
|
|
296
|
+
adapter: createModernScreenshotAdapter(modernScreenshot),
|
|
297
|
+
},
|
|
298
|
+
}}
|
|
299
|
+
/>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Convert to Blob
|
|
129
303
|
|
|
130
304
|
```ts
|
|
131
305
|
import { dataUrlToBlob } from "blocfeed/engine";
|
|
@@ -133,11 +307,25 @@ import { dataUrlToBlob } from "blocfeed/engine";
|
|
|
133
307
|
const blob = dataUrlToBlob(payload.screenshots?.element?.dataUrl ?? "");
|
|
134
308
|
```
|
|
135
309
|
|
|
136
|
-
|
|
310
|
+
## Keyboard Accessibility
|
|
311
|
+
|
|
312
|
+
The widget supports full keyboard navigation:
|
|
313
|
+
|
|
314
|
+
- **Tab** cycles through interactive elements in the feedback panel
|
|
315
|
+
- **Shift+Tab** cycles backward
|
|
316
|
+
- **Escape** cancels picking or closes the panel
|
|
317
|
+
- **Ctrl/Cmd+Enter** submits feedback from the textarea
|
|
318
|
+
- Focus is trapped within the panel when open
|
|
319
|
+
- All interactive elements have `aria-label` attributes
|
|
320
|
+
- Status messages use `aria-live="polite"` for screen reader announcements
|
|
321
|
+
|
|
322
|
+
## Client-Side Rate Limiting
|
|
323
|
+
|
|
324
|
+
A minimum 2-second interval is enforced between submissions to prevent accidental spam. Rapid submissions return a descriptive error.
|
|
137
325
|
|
|
138
326
|
## Custom UI
|
|
139
327
|
|
|
140
|
-
If you don
|
|
328
|
+
If you don't want the default widget UI:
|
|
141
329
|
|
|
142
330
|
```tsx
|
|
143
331
|
import { BlocFeedProvider, useBlocFeed } from "blocfeed";
|
|
@@ -165,6 +353,31 @@ import { createBlocFeedController } from "blocfeed/engine";
|
|
|
165
353
|
const controller = createBlocFeedController({ blocfeed_id: "bf_your_project_blocfeed_id" });
|
|
166
354
|
```
|
|
167
355
|
|
|
356
|
+
### Offline queue utilities (headless)
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
import { enqueue, dequeueAll, clearQueue, getQueueSize } from "blocfeed/engine";
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## TypeScript
|
|
363
|
+
|
|
364
|
+
All types are exported from both entry points:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
import type {
|
|
368
|
+
BlocFeedConfig,
|
|
369
|
+
BlocFeedUser,
|
|
370
|
+
TransportConfig,
|
|
371
|
+
WidgetPosition,
|
|
372
|
+
ThemeConfig,
|
|
373
|
+
FeedbackPayload,
|
|
374
|
+
FeedbackApiResponse,
|
|
375
|
+
BlocFeedState,
|
|
376
|
+
SessionPhase,
|
|
377
|
+
// ... and more
|
|
378
|
+
} from "blocfeed";
|
|
379
|
+
```
|
|
380
|
+
|
|
168
381
|
## Local development
|
|
169
382
|
|
|
170
383
|
```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,26 @@ 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
|
+
interface ThemeConfig {
|
|
28
|
+
accentColor?: string;
|
|
29
|
+
panelBackground?: string;
|
|
30
|
+
panelForeground?: string;
|
|
31
|
+
fontFamily?: string;
|
|
32
|
+
}
|
|
13
33
|
interface Rect {
|
|
14
34
|
x: number;
|
|
15
35
|
y: number;
|
|
@@ -121,11 +141,29 @@ interface BlocFeedConfig {
|
|
|
121
141
|
capture?: CaptureConfig;
|
|
122
142
|
picker?: PickerConfig;
|
|
123
143
|
metadata?: MetadataConfig;
|
|
144
|
+
/** First-class user identity attached to every submission. */
|
|
145
|
+
user?: BlocFeedUser;
|
|
146
|
+
/** Transport / retry configuration. */
|
|
147
|
+
transport?: TransportConfig;
|
|
124
148
|
ui?: {
|
|
125
|
-
/**
|
|
126
|
-
* z-index for the widget overlay/panel.
|
|
127
|
-
*/
|
|
149
|
+
/** z-index for the widget overlay/panel. */
|
|
128
150
|
zIndex?: number;
|
|
151
|
+
/** Position of the trigger button. Default: "bottom-right" */
|
|
152
|
+
position?: WidgetPosition;
|
|
153
|
+
/** Theme overrides. */
|
|
154
|
+
theme?: ThemeConfig;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
interface ScreenshotIntent {
|
|
158
|
+
element?: {
|
|
159
|
+
mime: string;
|
|
160
|
+
width: number;
|
|
161
|
+
height: number;
|
|
162
|
+
};
|
|
163
|
+
fullPage?: {
|
|
164
|
+
mime: string;
|
|
165
|
+
width: number;
|
|
166
|
+
height: number;
|
|
129
167
|
};
|
|
130
168
|
}
|
|
131
169
|
interface FeedbackPayload {
|
|
@@ -135,8 +173,20 @@ interface FeedbackPayload {
|
|
|
135
173
|
message: string;
|
|
136
174
|
selection?: ElementDescriptor;
|
|
137
175
|
screenshots?: CaptureResult;
|
|
176
|
+
/** Lightweight screenshot metadata sent instead of base64 dataUrls. */
|
|
177
|
+
screenshot_intent?: ScreenshotIntent;
|
|
178
|
+
/** First-class user identity. */
|
|
179
|
+
user?: BlocFeedUser;
|
|
138
180
|
metadata: Record<string, unknown>;
|
|
139
181
|
}
|
|
182
|
+
interface FeedbackApiResponse {
|
|
183
|
+
success: boolean;
|
|
184
|
+
feedback_id?: string;
|
|
185
|
+
upload_urls?: {
|
|
186
|
+
element?: string;
|
|
187
|
+
fullPage?: string;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
140
190
|
interface TransportResult {
|
|
141
191
|
ok: boolean;
|
|
142
192
|
error?: BlocFeedError;
|
|
@@ -179,4 +229,4 @@ interface BlocFeedController {
|
|
|
179
229
|
}
|
|
180
230
|
declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
|
|
181
231
|
|
|
182
|
-
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type
|
|
232
|
+
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 StateListener as q, createBlocFeedController as r };
|
|
@@ -10,6 +10,26 @@ 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
|
+
interface ThemeConfig {
|
|
28
|
+
accentColor?: string;
|
|
29
|
+
panelBackground?: string;
|
|
30
|
+
panelForeground?: string;
|
|
31
|
+
fontFamily?: string;
|
|
32
|
+
}
|
|
13
33
|
interface Rect {
|
|
14
34
|
x: number;
|
|
15
35
|
y: number;
|
|
@@ -121,11 +141,29 @@ interface BlocFeedConfig {
|
|
|
121
141
|
capture?: CaptureConfig;
|
|
122
142
|
picker?: PickerConfig;
|
|
123
143
|
metadata?: MetadataConfig;
|
|
144
|
+
/** First-class user identity attached to every submission. */
|
|
145
|
+
user?: BlocFeedUser;
|
|
146
|
+
/** Transport / retry configuration. */
|
|
147
|
+
transport?: TransportConfig;
|
|
124
148
|
ui?: {
|
|
125
|
-
/**
|
|
126
|
-
* z-index for the widget overlay/panel.
|
|
127
|
-
*/
|
|
149
|
+
/** z-index for the widget overlay/panel. */
|
|
128
150
|
zIndex?: number;
|
|
151
|
+
/** Position of the trigger button. Default: "bottom-right" */
|
|
152
|
+
position?: WidgetPosition;
|
|
153
|
+
/** Theme overrides. */
|
|
154
|
+
theme?: ThemeConfig;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
interface ScreenshotIntent {
|
|
158
|
+
element?: {
|
|
159
|
+
mime: string;
|
|
160
|
+
width: number;
|
|
161
|
+
height: number;
|
|
162
|
+
};
|
|
163
|
+
fullPage?: {
|
|
164
|
+
mime: string;
|
|
165
|
+
width: number;
|
|
166
|
+
height: number;
|
|
129
167
|
};
|
|
130
168
|
}
|
|
131
169
|
interface FeedbackPayload {
|
|
@@ -135,8 +173,20 @@ interface FeedbackPayload {
|
|
|
135
173
|
message: string;
|
|
136
174
|
selection?: ElementDescriptor;
|
|
137
175
|
screenshots?: CaptureResult;
|
|
176
|
+
/** Lightweight screenshot metadata sent instead of base64 dataUrls. */
|
|
177
|
+
screenshot_intent?: ScreenshotIntent;
|
|
178
|
+
/** First-class user identity. */
|
|
179
|
+
user?: BlocFeedUser;
|
|
138
180
|
metadata: Record<string, unknown>;
|
|
139
181
|
}
|
|
182
|
+
interface FeedbackApiResponse {
|
|
183
|
+
success: boolean;
|
|
184
|
+
feedback_id?: string;
|
|
185
|
+
upload_urls?: {
|
|
186
|
+
element?: string;
|
|
187
|
+
fullPage?: string;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
140
190
|
interface TransportResult {
|
|
141
191
|
ok: boolean;
|
|
142
192
|
error?: BlocFeedError;
|
|
@@ -179,4 +229,4 @@ interface BlocFeedController {
|
|
|
179
229
|
}
|
|
180
230
|
declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
|
|
181
231
|
|
|
182
|
-
export { type BlocFeedConfig as B, type CaptureConfig as C, type ElementDescriptor as E, type
|
|
232
|
+
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 StateListener as q, createBlocFeedController as r };
|
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 S(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 S(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 y(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=y;
|
package/dist/engine.d.cts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as
|
|
1
|
+
import { j as ScreenshotAdapter, C as CaptureConfig, f as CaptureResult, h as MetadataConfig, i as MetadataContext, d as BlocFeedUser, g as FeedbackPayload } from './controller-olqOZLhp.cjs';
|
|
2
|
+
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as FeedbackApiResponse, H as HoverListener, I as ImageAsset, R as Rect, k as ScreenshotAdapterOptions, l as ScreenshotIntent, m as ScreenshotMime, n as SessionPhase, q as StateListener, S as SubmitResult, T as ThemeConfig, o as TransportConfig, W as WidgetPosition, r as createBlocFeedController } from './controller-olqOZLhp.cjs';
|
|
3
3
|
|
|
4
4
|
declare function createHtmlToImageAdapter(): ScreenshotAdapter;
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Minimal interface that `modern-screenshot` satisfies.
|
|
8
|
+
* The consumer must install `modern-screenshot` themselves:
|
|
9
|
+
*
|
|
10
|
+
* npm install modern-screenshot
|
|
11
|
+
*
|
|
12
|
+
* Then pass the module into this factory:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createModernScreenshotAdapter } from "blocfeed/engine";
|
|
16
|
+
* import * as ms from "modern-screenshot";
|
|
17
|
+
*
|
|
18
|
+
* <BlocFeedWidget
|
|
19
|
+
* blocfeed_id="bf_..."
|
|
20
|
+
* config={{ capture: { adapter: createModernScreenshotAdapter(ms) } }}
|
|
21
|
+
* />
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Benefits over `html-to-image`:
|
|
25
|
+
* - Better cross-origin image handling
|
|
26
|
+
* - Supports CSS `backdrop-filter`, `clip-path`
|
|
27
|
+
* - Better web font rendering
|
|
28
|
+
*/
|
|
29
|
+
interface ModernScreenshotModule {
|
|
30
|
+
domToPng: (node: Node, options?: Record<string, unknown>) => Promise<string>;
|
|
31
|
+
domToJpeg: (node: Node, options?: Record<string, unknown>) => Promise<string>;
|
|
32
|
+
}
|
|
33
|
+
declare function createModernScreenshotAdapter(mod: ModernScreenshotModule): ScreenshotAdapter;
|
|
34
|
+
|
|
6
35
|
declare function runCapture(params: {
|
|
7
36
|
selectionElement: Element | null;
|
|
8
37
|
capture: CaptureConfig | undefined;
|
|
@@ -12,8 +41,27 @@ declare function runCapture(params: {
|
|
|
12
41
|
declare function collectMetadata(params: {
|
|
13
42
|
config: MetadataConfig | undefined;
|
|
14
43
|
context: MetadataContext;
|
|
44
|
+
user?: BlocFeedUser;
|
|
15
45
|
}): Promise<Record<string, unknown>>;
|
|
16
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Add a failed payload to the offline queue.
|
|
49
|
+
* Screenshots are stripped to stay within localStorage limits.
|
|
50
|
+
*/
|
|
51
|
+
declare function enqueue(payload: FeedbackPayload): void;
|
|
52
|
+
/**
|
|
53
|
+
* Remove and return all queued payloads.
|
|
54
|
+
*/
|
|
55
|
+
declare function dequeueAll(): FeedbackPayload[];
|
|
56
|
+
/**
|
|
57
|
+
* Clear the entire queue.
|
|
58
|
+
*/
|
|
59
|
+
declare function clearQueue(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Get the number of items in the queue.
|
|
62
|
+
*/
|
|
63
|
+
declare function getQueueSize(): number;
|
|
64
|
+
|
|
17
65
|
declare function dataUrlToBlob(dataUrl: string): Blob;
|
|
18
66
|
|
|
19
|
-
export { CaptureResult, ScreenshotAdapter, collectMetadata, createHtmlToImageAdapter, dataUrlToBlob, runCapture };
|
|
67
|
+
export { BlocFeedUser, CaptureResult, FeedbackPayload, type ModernScreenshotModule, ScreenshotAdapter, clearQueue, collectMetadata, createHtmlToImageAdapter, createModernScreenshotAdapter, dataUrlToBlob, dequeueAll, enqueue, getQueueSize, runCapture };
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as
|
|
1
|
+
import { j as ScreenshotAdapter, C as CaptureConfig, f as CaptureResult, h as MetadataConfig, i as MetadataContext, d as BlocFeedUser, g as FeedbackPayload } from './controller-olqOZLhp.js';
|
|
2
|
+
export { B as BlocFeedConfig, b as BlocFeedController, c as BlocFeedError, a as BlocFeedState, E as ElementDescriptor, F as FeedbackApiResponse, H as HoverListener, I as ImageAsset, R as Rect, k as ScreenshotAdapterOptions, l as ScreenshotIntent, m as ScreenshotMime, n as SessionPhase, q as StateListener, S as SubmitResult, T as ThemeConfig, o as TransportConfig, W as WidgetPosition, r as createBlocFeedController } from './controller-olqOZLhp.js';
|
|
3
3
|
|
|
4
4
|
declare function createHtmlToImageAdapter(): ScreenshotAdapter;
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Minimal interface that `modern-screenshot` satisfies.
|
|
8
|
+
* The consumer must install `modern-screenshot` themselves:
|
|
9
|
+
*
|
|
10
|
+
* npm install modern-screenshot
|
|
11
|
+
*
|
|
12
|
+
* Then pass the module into this factory:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createModernScreenshotAdapter } from "blocfeed/engine";
|
|
16
|
+
* import * as ms from "modern-screenshot";
|
|
17
|
+
*
|
|
18
|
+
* <BlocFeedWidget
|
|
19
|
+
* blocfeed_id="bf_..."
|
|
20
|
+
* config={{ capture: { adapter: createModernScreenshotAdapter(ms) } }}
|
|
21
|
+
* />
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Benefits over `html-to-image`:
|
|
25
|
+
* - Better cross-origin image handling
|
|
26
|
+
* - Supports CSS `backdrop-filter`, `clip-path`
|
|
27
|
+
* - Better web font rendering
|
|
28
|
+
*/
|
|
29
|
+
interface ModernScreenshotModule {
|
|
30
|
+
domToPng: (node: Node, options?: Record<string, unknown>) => Promise<string>;
|
|
31
|
+
domToJpeg: (node: Node, options?: Record<string, unknown>) => Promise<string>;
|
|
32
|
+
}
|
|
33
|
+
declare function createModernScreenshotAdapter(mod: ModernScreenshotModule): ScreenshotAdapter;
|
|
34
|
+
|
|
6
35
|
declare function runCapture(params: {
|
|
7
36
|
selectionElement: Element | null;
|
|
8
37
|
capture: CaptureConfig | undefined;
|
|
@@ -12,8 +41,27 @@ declare function runCapture(params: {
|
|
|
12
41
|
declare function collectMetadata(params: {
|
|
13
42
|
config: MetadataConfig | undefined;
|
|
14
43
|
context: MetadataContext;
|
|
44
|
+
user?: BlocFeedUser;
|
|
15
45
|
}): Promise<Record<string, unknown>>;
|
|
16
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Add a failed payload to the offline queue.
|
|
49
|
+
* Screenshots are stripped to stay within localStorage limits.
|
|
50
|
+
*/
|
|
51
|
+
declare function enqueue(payload: FeedbackPayload): void;
|
|
52
|
+
/**
|
|
53
|
+
* Remove and return all queued payloads.
|
|
54
|
+
*/
|
|
55
|
+
declare function dequeueAll(): FeedbackPayload[];
|
|
56
|
+
/**
|
|
57
|
+
* Clear the entire queue.
|
|
58
|
+
*/
|
|
59
|
+
declare function clearQueue(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Get the number of items in the queue.
|
|
62
|
+
*/
|
|
63
|
+
declare function getQueueSize(): number;
|
|
64
|
+
|
|
17
65
|
declare function dataUrlToBlob(dataUrl: string): Blob;
|
|
18
66
|
|
|
19
|
-
export { CaptureResult, ScreenshotAdapter, collectMetadata, createHtmlToImageAdapter, dataUrlToBlob, runCapture };
|
|
67
|
+
export { BlocFeedUser, CaptureResult, FeedbackPayload, type ModernScreenshotModule, ScreenshotAdapter, clearQueue, collectMetadata, createHtmlToImageAdapter, createModernScreenshotAdapter, dataUrlToBlob, dequeueAll, enqueue, getQueueSize, runCapture };
|
package/dist/engine.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{e as collectMetadata,
|
|
1
|
+
import {a}from'./chunk-FMFONS5S.js';export{h as clearQueue,e as collectMetadata,j as createBlocFeedController,c as createHtmlToImageAdapter,g as dequeueAll,f as enqueue,i as getQueueSize,d as runCapture}from'./chunk-FMFONS5S.js';function c(o){if(o?.aborted)throw new Error("Aborted")}async function S(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 S(o);return {dataUrl:o,mime:t,width:e,height:r}}function x(o){return {async captureElement(t,e){if(!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(!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$1=Math.min(1,t.maxDimension/Math.max(r,n)),s={width:Math.max(1,Math.round(r*a$1)),height:Math.max(1,Math.round(n*a$1)),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 y(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})}export{x as createModernScreenshotAdapter,y as dataUrlToBlob};
|
package/dist/main.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
'use strict';var
|
|
2
|
+
'use strict';var chunkJLPJP7DD_cjs=require('./chunk-JLPJP7DD.cjs'),react=require('react'),jsxRuntime=require('react/jsx-runtime'),reactDom=require('react-dom');var h=react.createContext(null);function k(t){let e=react.useMemo(()=>chunkJLPJP7DD_cjs.j({...t.config??{},blocfeed_id:t.blocfeed_id}),[]),[i,c]=react.useState(()=>e.getState());return react.useEffect(()=>e.subscribe(c),[e]),react.useEffect(()=>e.setConfig({...t.config??{},blocfeed_id:t.blocfeed_id}),[e,t.config,t.blocfeed_id]),react.useEffect(()=>()=>e.destroy(),[e]),jsxRuntime.jsx(h.Provider,{value:{controller:e,state:i},children:t.children})}var L="blocfeed-styles-v1",G=`
|
|
3
3
|
:where([data-blocfeed-ui-root]),
|
|
4
4
|
:where([data-blocfeed-ui-root]) * {
|
|
5
5
|
box-sizing: border-box;
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
color: var(--bf-panel-fg);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/* ------------------------------------------------------------------ */
|
|
24
|
+
/* Trigger button \u2014 default bottom-right */
|
|
25
|
+
/* ------------------------------------------------------------------ */
|
|
26
|
+
|
|
23
27
|
:where([data-blocfeed-ui-root]) .bf-trigger {
|
|
24
28
|
position: fixed;
|
|
25
29
|
right: 18px;
|
|
@@ -40,11 +44,26 @@
|
|
|
40
44
|
-webkit-tap-highlight-color: transparent;
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
:where([data-blocfeed-ui-root]) .bf-trigger:hover {
|
|
48
|
+
border-color: var(--bf-accent);
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
:where([data-blocfeed-ui-root]) .bf-trigger:focus-visible {
|
|
44
52
|
outline: 2px solid var(--bf-accent);
|
|
45
53
|
outline-offset: 2px;
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
/* Position variants (item 9) */
|
|
57
|
+
:where([data-blocfeed-ui-root]) .bf-trigger-bl {
|
|
58
|
+
left: 18px; bottom: 18px; right: auto; top: auto;
|
|
59
|
+
}
|
|
60
|
+
:where([data-blocfeed-ui-root]) .bf-trigger-tr {
|
|
61
|
+
right: 18px; top: 18px; bottom: auto; left: auto;
|
|
62
|
+
}
|
|
63
|
+
:where([data-blocfeed-ui-root]) .bf-trigger-tl {
|
|
64
|
+
left: 18px; top: 18px; right: auto; bottom: auto;
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
:where([data-blocfeed-ui-root]) .bf-dot {
|
|
49
68
|
width: 10px;
|
|
50
69
|
height: 10px;
|
|
@@ -53,6 +72,10 @@
|
|
|
53
72
|
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.18);
|
|
54
73
|
}
|
|
55
74
|
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
/* Overlay, blocker, highlight */
|
|
77
|
+
/* ------------------------------------------------------------------ */
|
|
78
|
+
|
|
56
79
|
:where([data-blocfeed-ui-root]) .bf-overlay {
|
|
57
80
|
position: fixed;
|
|
58
81
|
inset: 0;
|
|
@@ -75,6 +98,10 @@
|
|
|
75
98
|
pointer-events: none;
|
|
76
99
|
}
|
|
77
100
|
|
|
101
|
+
/* ------------------------------------------------------------------ */
|
|
102
|
+
/* Hint bar */
|
|
103
|
+
/* ------------------------------------------------------------------ */
|
|
104
|
+
|
|
78
105
|
:where([data-blocfeed-ui-root]) .bf-hint {
|
|
79
106
|
position: fixed;
|
|
80
107
|
top: 16px;
|
|
@@ -99,6 +126,10 @@
|
|
|
99
126
|
color: var(--bf-muted);
|
|
100
127
|
}
|
|
101
128
|
|
|
129
|
+
/* ------------------------------------------------------------------ */
|
|
130
|
+
/* Buttons */
|
|
131
|
+
/* ------------------------------------------------------------------ */
|
|
132
|
+
|
|
102
133
|
:where([data-blocfeed-ui-root]) .bf-btn {
|
|
103
134
|
border: 1px solid var(--bf-border);
|
|
104
135
|
background: transparent;
|
|
@@ -109,6 +140,11 @@
|
|
|
109
140
|
user-select: none;
|
|
110
141
|
}
|
|
111
142
|
|
|
143
|
+
:where([data-blocfeed-ui-root]) .bf-btn:focus-visible {
|
|
144
|
+
outline: 2px solid var(--bf-accent);
|
|
145
|
+
outline-offset: 2px;
|
|
146
|
+
}
|
|
147
|
+
|
|
112
148
|
:where([data-blocfeed-ui-root]) .bf-btn[disabled] {
|
|
113
149
|
opacity: 0.6;
|
|
114
150
|
cursor: not-allowed;
|
|
@@ -120,6 +156,10 @@
|
|
|
120
156
|
color: white;
|
|
121
157
|
}
|
|
122
158
|
|
|
159
|
+
/* ------------------------------------------------------------------ */
|
|
160
|
+
/* Panel */
|
|
161
|
+
/* ------------------------------------------------------------------ */
|
|
162
|
+
|
|
123
163
|
:where([data-blocfeed-ui-root]) .bf-panel {
|
|
124
164
|
position: fixed;
|
|
125
165
|
width: 360px;
|
|
@@ -154,6 +194,10 @@
|
|
|
154
194
|
gap: 10px;
|
|
155
195
|
}
|
|
156
196
|
|
|
197
|
+
/* ------------------------------------------------------------------ */
|
|
198
|
+
/* Form elements */
|
|
199
|
+
/* ------------------------------------------------------------------ */
|
|
200
|
+
|
|
157
201
|
:where([data-blocfeed-ui-root]) .bf-textarea {
|
|
158
202
|
width: 100%;
|
|
159
203
|
min-height: 96px;
|
|
@@ -195,9 +239,16 @@
|
|
|
195
239
|
padding-top: 4px;
|
|
196
240
|
}
|
|
197
241
|
|
|
242
|
+
/* ------------------------------------------------------------------ */
|
|
243
|
+
/* Status, errors, toast */
|
|
244
|
+
/* ------------------------------------------------------------------ */
|
|
245
|
+
|
|
198
246
|
:where([data-blocfeed-ui-root]) .bf-status {
|
|
199
247
|
font-size: 12px;
|
|
200
248
|
color: var(--bf-muted);
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
gap: 8px;
|
|
201
252
|
}
|
|
202
253
|
|
|
203
254
|
:where([data-blocfeed-ui-root]) .bf-error {
|
|
@@ -220,4 +271,38 @@
|
|
|
220
271
|
font-size: 13px;
|
|
221
272
|
color: rgba(243, 244, 246, 0.9);
|
|
222
273
|
}
|
|
223
|
-
|
|
274
|
+
|
|
275
|
+
/* ------------------------------------------------------------------ */
|
|
276
|
+
/* Capture spinner (item 12) */
|
|
277
|
+
/* ------------------------------------------------------------------ */
|
|
278
|
+
|
|
279
|
+
:where([data-blocfeed-ui-root]) .bf-spinner {
|
|
280
|
+
display: inline-block;
|
|
281
|
+
width: 10px;
|
|
282
|
+
height: 10px;
|
|
283
|
+
border-radius: 50%;
|
|
284
|
+
background: var(--bf-accent);
|
|
285
|
+
animation: bf-pulse 1s ease-in-out infinite;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@keyframes bf-pulse {
|
|
289
|
+
0%, 100% { opacity: 0.4; transform: scale(0.8); }
|
|
290
|
+
50% { opacity: 1; transform: scale(1.2); }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* ------------------------------------------------------------------ */
|
|
294
|
+
/* Screen reader only */
|
|
295
|
+
/* ------------------------------------------------------------------ */
|
|
296
|
+
|
|
297
|
+
:where([data-blocfeed-ui-root]) .bf-sr-only {
|
|
298
|
+
position: absolute;
|
|
299
|
+
width: 1px;
|
|
300
|
+
height: 1px;
|
|
301
|
+
padding: 0;
|
|
302
|
+
margin: -1px;
|
|
303
|
+
overflow: hidden;
|
|
304
|
+
clip: rect(0, 0, 0, 0);
|
|
305
|
+
white-space: nowrap;
|
|
306
|
+
border: 0;
|
|
307
|
+
}
|
|
308
|
+
`;function W(){if(!chunkJLPJP7DD_cjs.a()||document.getElementById(L))return;let t=document.createElement("style");t.id=L,t.textContent=G,document.head.appendChild(t);}function B(){let t=react.useContext(h);if(!t)throw new Error("useBlocFeed must be used within a <BlocFeedProvider />");return {state:t.state,controller:t.controller,start:t.controller.start,stop:t.controller.stop,clearSelection:t.controller.clearSelection,submit:t.controller.submit}}function I(t,e,i){return Math.max(e,Math.min(i,t))}function Z(t,e,i="bottom-right"){let o=window.innerWidth,s=window.innerHeight,a;a=I(t.x,12,Math.max(12,o-e-12));let d=t.y+t.height+12,x=Math.max(12,t.y-240);return {top:d+240<=s?d:x,left:a}}function ee(t){switch(t){case "bottom-left":return "bf-trigger bf-trigger-bl";case "top-right":return "bf-trigger bf-trigger-tr";case "top-left":return "bf-trigger bf-trigger-tl";default:return "bf-trigger"}}function te(t){let{rect:e}=t,i={left:`${e.x}px`,top:`${e.y}px`,width:`${Math.max(0,e.width)}px`,height:`${Math.max(0,e.height)}px`};return jsxRuntime.jsx("div",{className:"bf-highlight",style:i,"aria-hidden":"true"})}function oe(t){let e=react.useRef(null);return react.useEffect(()=>{if(!t||!e.current)return;e.current.querySelector(".bf-textarea")?.focus();let c=o=>{if(o.key!=="Tab"||!e.current)return;let s=e.current.querySelectorAll('button:not([disabled]), textarea:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])');if(s.length===0)return;let a=s[0],d=s[s.length-1];o.shiftKey&&document.activeElement===a?(o.preventDefault(),d.focus()):!o.shiftKey&&document.activeElement===d&&(o.preventDefault(),a.focus());};return document.addEventListener("keydown",c,{capture:true}),()=>document.removeEventListener("keydown",c,{capture:true})},[t]),e}function re(t){let{state:e,controller:i,start:c,stop:o,clearSelection:s,submit:a}=B(),d=t.config.ui?.position,[x,S]=react.useState(null),[p,P]=react.useState(""),[v,E]=react.useState(t.config.capture?.element??true),[y,R]=react.useState(t.config.capture?.fullPage??false),[K,N]=react.useState(null),z=e.phase==="review"||e.phase==="capturing"||e.phase==="submitting"||e.phase==="error"||e.phase==="success",j=oe(z);react.useEffect(()=>i.subscribeHover(S),[i]),react.useEffect(()=>{let n=i.__unsafeGetSelectedElement();if(!n||e.phase==="idle"||e.phase==="picking"){N(null);return}let M=()=>{N(chunkJLPJP7DD_cjs.b(n.getBoundingClientRect()));};M();let m=()=>M();return window.addEventListener("scroll",m,{capture:true,passive:true}),window.addEventListener("resize",m,{passive:true}),()=>{window.removeEventListener("scroll",m,{capture:true}),window.removeEventListener("resize",m);}},[i,e.phase,e.selection?.selector]),react.useEffect(()=>{e.phase==="review"&&(P(""),E(t.config.capture?.element??true),R(t.config.capture?.fullPage??false));},[e.phase,e.selection?.selector,t.config.capture?.element,t.config.capture?.fullPage]),react.useEffect(()=>{if(e.phase!=="success")return;let n=window.setTimeout(()=>o(),1200);return ()=>window.clearTimeout(n)},[e.phase,o]);let f=e.phase==="capturing"||e.phase==="submitting",b=e.phase==="picking"?x?.rect??null:K??e.selection?.rect??null,w=react.useMemo(()=>b?Z(b,360,d):null,[b?.x,b?.y,b?.width,b?.height,d]),T=e.lastError?.message,C=react.useCallback(()=>{a(p,{capture:{element:v,fullPage:y}});},[a,p,v,y]),O=react.useCallback(n=>{(n.metaKey||n.ctrlKey)&&n.key==="Enter"&&p.trim().length>0&&!f&&(n.preventDefault(),C());},[C,p,f]);return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[e.phase==="idle"&&jsxRuntime.jsxs("button",{className:ee(d),type:"button",onClick:()=>c(),"aria-label":"Give feedback",children:[jsxRuntime.jsx("span",{className:"bf-dot","aria-hidden":"true"}),"Feedback"]}),e.phase!=="idle"&&jsxRuntime.jsxs("div",{className:"bf-overlay",role:"presentation",children:[e.phase!=="picking"&&jsxRuntime.jsx("div",{className:"bf-blocker",role:"presentation",onClick:()=>o()}),b&&jsxRuntime.jsx(te,{rect:b}),e.phase==="picking"&&jsxRuntime.jsxs("div",{className:"bf-hint",role:"status","aria-live":"polite",children:[jsxRuntime.jsxs("p",{id:"bf-hint-text",children:["Click an element to attach your feedback. Press ",jsxRuntime.jsx("strong",{children:"Esc"})," to cancel."]}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>o(),"aria-label":"Cancel element selection",children:"Cancel"})]}),z&&w&&jsxRuntime.jsxs("div",{ref:j,className:"bf-panel",style:{left:w.left,top:w.top},role:"dialog","aria-modal":"true","aria-label":"Feedback form",children:[jsxRuntime.jsxs("div",{className:"bf-panelHeader",children:[jsxRuntime.jsx("div",{className:"bf-title",id:"bf-panel-title",children:"Feedback"}),jsxRuntime.jsxs("div",{className:"bf-row",style:{justifyContent:"flex-end"},children:[jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>s(),disabled:f,"aria-label":"Re-pick element",children:"Re-pick"}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>o(),disabled:f,"aria-label":"Close feedback form",children:"Close"})]})]}),jsxRuntime.jsxs("div",{className:"bf-panelBody",children:[jsxRuntime.jsx("textarea",{className:"bf-textarea",placeholder:"What's happening? What did you expect?",value:p,onChange:n=>P(n.target.value),onKeyDown:O,disabled:f,"aria-label":"Feedback message"}),jsxRuntime.jsxs("div",{className:"bf-row",role:"group","aria-label":"Screenshot options",children:[jsxRuntime.jsxs("label",{children:[jsxRuntime.jsx("input",{type:"checkbox",checked:v,onChange:n=>E(n.target.checked),disabled:f}),"Screenshot element"]}),jsxRuntime.jsxs("label",{children:[jsxRuntime.jsx("input",{type:"checkbox",checked:y,onChange:n=>R(n.target.checked),disabled:f}),"Full page"]})]}),e.phase==="capturing"&&jsxRuntime.jsxs("div",{className:"bf-status",role:"status","aria-live":"polite",children:[jsxRuntime.jsx("span",{className:"bf-spinner","aria-hidden":"true"}),"Capturing screenshots\u2026"]}),e.phase==="submitting"&&jsxRuntime.jsxs("div",{className:"bf-status",role:"status","aria-live":"polite",children:[jsxRuntime.jsx("span",{className:"bf-spinner","aria-hidden":"true"}),"Submitting\u2026"]}),e.phase==="success"&&jsxRuntime.jsx("div",{className:"bf-status",role:"status","aria-live":"polite",children:"Sent. Thank you!"}),e.phase==="error"&&T&&jsxRuntime.jsx("div",{className:"bf-error",role:"alert",children:T}),jsxRuntime.jsxs("div",{className:"bf-actions",children:[jsxRuntime.jsx("button",{type:"button",className:"bf-btn",onClick:()=>o(),disabled:f,"aria-label":"Cancel feedback",children:"Cancel"}),jsxRuntime.jsx("button",{type:"button",className:"bf-btn bf-btnPrimary",onClick:C,disabled:f||p.trim().length===0,"aria-label":"Send feedback",children:"Send"})]})]})]})]}),e.phase==="success"&&jsxRuntime.jsx("div",{className:"bf-toast",role:"status","aria-live":"polite",children:"Feedback sent"})]})}function ae(t){let e={...t.config??{},blocfeed_id:t.blocfeed_id},[i,c]=react.useState(null);return react.useEffect(()=>{W();let o=document.createElement("div");o.setAttribute("data-blocfeed-ui-root","true"),o.setAttribute("data-blocfeed-ui","true");let s=e.ui?.zIndex;typeof s=="number"&&o.style.setProperty("--bf-z",String(s));let a=e.ui?.theme;return a&&(a.accentColor&&o.style.setProperty("--bf-accent",a.accentColor),a.panelBackground&&o.style.setProperty("--bf-panel-bg",a.panelBackground),a.panelForeground&&o.style.setProperty("--bf-panel-fg",a.panelForeground),a.fontFamily&&o.style.setProperty("--bf-font",a.fontFamily)),document.body.appendChild(o),c(o),()=>{o.remove(),c(null);}},[e.ui?.zIndex,e.ui?.theme?.accentColor,e.ui?.theme?.panelBackground,e.ui?.theme?.panelForeground,e.ui?.theme?.fontFamily]),i?reactDom.createPortal(jsxRuntime.jsx(k,{blocfeed_id:e.blocfeed_id,...t.config?{config:t.config}:{},children:jsxRuntime.jsx(re,{config:e})}),i):null}exports.BlocFeedProvider=k;exports.BlocFeedWidget=ae;exports.useBlocFeed=B;
|
package/dist/main.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-
|
|
2
|
-
export { c as BlocFeedError, d as
|
|
1
|
+
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-olqOZLhp.cjs';
|
|
2
|
+
export { c as BlocFeedError, d as BlocFeedUser, e as CaptureDiagnostics, f as CaptureResult, E as ElementDescriptor, F as FeedbackApiResponse, g as FeedbackPayload, I as ImageAsset, M as MaybePromise, h as MetadataConfig, i as MetadataContext, P as PickerConfig, R as Rect, j as ScreenshotAdapter, k as ScreenshotAdapterOptions, l as ScreenshotIntent, m as ScreenshotMime, n as SessionPhase, T as ThemeConfig, o as TransportConfig, p as TransportResult, W as WidgetPosition } from './controller-olqOZLhp.cjs';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { ReactNode } from 'react';
|
package/dist/main.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-
|
|
2
|
-
export { c as BlocFeedError, d as
|
|
1
|
+
import { B as BlocFeedConfig, a as BlocFeedState, b as BlocFeedController, C as CaptureConfig, S as SubmitResult } from './controller-olqOZLhp.js';
|
|
2
|
+
export { c as BlocFeedError, d as BlocFeedUser, e as CaptureDiagnostics, f as CaptureResult, E as ElementDescriptor, F as FeedbackApiResponse, g as FeedbackPayload, I as ImageAsset, M as MaybePromise, h as MetadataConfig, i as MetadataContext, P as PickerConfig, R as Rect, j as ScreenshotAdapter, k as ScreenshotAdapterOptions, l as ScreenshotIntent, m as ScreenshotMime, n as SessionPhase, T as ThemeConfig, o as TransportConfig, p as TransportResult, W as WidgetPosition } from './controller-olqOZLhp.js';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { ReactNode } from 'react';
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import {j,a,b}from'./chunk-FMFONS5S.js';import {createContext,useMemo,useState,useEffect,useContext,useCallback,useRef}from'react';import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {createPortal}from'react-dom';var h=createContext(null);function k(t){let e=useMemo(()=>j({...t.config??{},blocfeed_id:t.blocfeed_id}),[]),[i,c]=useState(()=>e.getState());return useEffect(()=>e.subscribe(c),[e]),useEffect(()=>e.setConfig({...t.config??{},blocfeed_id:t.blocfeed_id}),[e,t.config,t.blocfeed_id]),useEffect(()=>()=>e.destroy(),[e]),jsx(h.Provider,{value:{controller:e,state:i},children:t.children})}var L="blocfeed-styles-v1",G=`
|
|
3
3
|
:where([data-blocfeed-ui-root]),
|
|
4
4
|
:where([data-blocfeed-ui-root]) * {
|
|
5
5
|
box-sizing: border-box;
|
|
@@ -20,6 +20,10 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
20
20
|
color: var(--bf-panel-fg);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/* ------------------------------------------------------------------ */
|
|
24
|
+
/* Trigger button \u2014 default bottom-right */
|
|
25
|
+
/* ------------------------------------------------------------------ */
|
|
26
|
+
|
|
23
27
|
:where([data-blocfeed-ui-root]) .bf-trigger {
|
|
24
28
|
position: fixed;
|
|
25
29
|
right: 18px;
|
|
@@ -40,11 +44,26 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
40
44
|
-webkit-tap-highlight-color: transparent;
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
:where([data-blocfeed-ui-root]) .bf-trigger:hover {
|
|
48
|
+
border-color: var(--bf-accent);
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
:where([data-blocfeed-ui-root]) .bf-trigger:focus-visible {
|
|
44
52
|
outline: 2px solid var(--bf-accent);
|
|
45
53
|
outline-offset: 2px;
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
/* Position variants (item 9) */
|
|
57
|
+
:where([data-blocfeed-ui-root]) .bf-trigger-bl {
|
|
58
|
+
left: 18px; bottom: 18px; right: auto; top: auto;
|
|
59
|
+
}
|
|
60
|
+
:where([data-blocfeed-ui-root]) .bf-trigger-tr {
|
|
61
|
+
right: 18px; top: 18px; bottom: auto; left: auto;
|
|
62
|
+
}
|
|
63
|
+
:where([data-blocfeed-ui-root]) .bf-trigger-tl {
|
|
64
|
+
left: 18px; top: 18px; right: auto; bottom: auto;
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
:where([data-blocfeed-ui-root]) .bf-dot {
|
|
49
68
|
width: 10px;
|
|
50
69
|
height: 10px;
|
|
@@ -53,6 +72,10 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
53
72
|
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.18);
|
|
54
73
|
}
|
|
55
74
|
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
/* Overlay, blocker, highlight */
|
|
77
|
+
/* ------------------------------------------------------------------ */
|
|
78
|
+
|
|
56
79
|
:where([data-blocfeed-ui-root]) .bf-overlay {
|
|
57
80
|
position: fixed;
|
|
58
81
|
inset: 0;
|
|
@@ -75,6 +98,10 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
75
98
|
pointer-events: none;
|
|
76
99
|
}
|
|
77
100
|
|
|
101
|
+
/* ------------------------------------------------------------------ */
|
|
102
|
+
/* Hint bar */
|
|
103
|
+
/* ------------------------------------------------------------------ */
|
|
104
|
+
|
|
78
105
|
:where([data-blocfeed-ui-root]) .bf-hint {
|
|
79
106
|
position: fixed;
|
|
80
107
|
top: 16px;
|
|
@@ -99,6 +126,10 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
99
126
|
color: var(--bf-muted);
|
|
100
127
|
}
|
|
101
128
|
|
|
129
|
+
/* ------------------------------------------------------------------ */
|
|
130
|
+
/* Buttons */
|
|
131
|
+
/* ------------------------------------------------------------------ */
|
|
132
|
+
|
|
102
133
|
:where([data-blocfeed-ui-root]) .bf-btn {
|
|
103
134
|
border: 1px solid var(--bf-border);
|
|
104
135
|
background: transparent;
|
|
@@ -109,6 +140,11 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
109
140
|
user-select: none;
|
|
110
141
|
}
|
|
111
142
|
|
|
143
|
+
:where([data-blocfeed-ui-root]) .bf-btn:focus-visible {
|
|
144
|
+
outline: 2px solid var(--bf-accent);
|
|
145
|
+
outline-offset: 2px;
|
|
146
|
+
}
|
|
147
|
+
|
|
112
148
|
:where([data-blocfeed-ui-root]) .bf-btn[disabled] {
|
|
113
149
|
opacity: 0.6;
|
|
114
150
|
cursor: not-allowed;
|
|
@@ -120,6 +156,10 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
120
156
|
color: white;
|
|
121
157
|
}
|
|
122
158
|
|
|
159
|
+
/* ------------------------------------------------------------------ */
|
|
160
|
+
/* Panel */
|
|
161
|
+
/* ------------------------------------------------------------------ */
|
|
162
|
+
|
|
123
163
|
:where([data-blocfeed-ui-root]) .bf-panel {
|
|
124
164
|
position: fixed;
|
|
125
165
|
width: 360px;
|
|
@@ -154,6 +194,10 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
154
194
|
gap: 10px;
|
|
155
195
|
}
|
|
156
196
|
|
|
197
|
+
/* ------------------------------------------------------------------ */
|
|
198
|
+
/* Form elements */
|
|
199
|
+
/* ------------------------------------------------------------------ */
|
|
200
|
+
|
|
157
201
|
:where([data-blocfeed-ui-root]) .bf-textarea {
|
|
158
202
|
width: 100%;
|
|
159
203
|
min-height: 96px;
|
|
@@ -195,9 +239,16 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
195
239
|
padding-top: 4px;
|
|
196
240
|
}
|
|
197
241
|
|
|
242
|
+
/* ------------------------------------------------------------------ */
|
|
243
|
+
/* Status, errors, toast */
|
|
244
|
+
/* ------------------------------------------------------------------ */
|
|
245
|
+
|
|
198
246
|
:where([data-blocfeed-ui-root]) .bf-status {
|
|
199
247
|
font-size: 12px;
|
|
200
248
|
color: var(--bf-muted);
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
gap: 8px;
|
|
201
252
|
}
|
|
202
253
|
|
|
203
254
|
:where([data-blocfeed-ui-root]) .bf-error {
|
|
@@ -220,4 +271,38 @@ import {f,a,b}from'./chunk-37P3GL6V.js';import {createContext,useMemo,useState,u
|
|
|
220
271
|
font-size: 13px;
|
|
221
272
|
color: rgba(243, 244, 246, 0.9);
|
|
222
273
|
}
|
|
223
|
-
|
|
274
|
+
|
|
275
|
+
/* ------------------------------------------------------------------ */
|
|
276
|
+
/* Capture spinner (item 12) */
|
|
277
|
+
/* ------------------------------------------------------------------ */
|
|
278
|
+
|
|
279
|
+
:where([data-blocfeed-ui-root]) .bf-spinner {
|
|
280
|
+
display: inline-block;
|
|
281
|
+
width: 10px;
|
|
282
|
+
height: 10px;
|
|
283
|
+
border-radius: 50%;
|
|
284
|
+
background: var(--bf-accent);
|
|
285
|
+
animation: bf-pulse 1s ease-in-out infinite;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@keyframes bf-pulse {
|
|
289
|
+
0%, 100% { opacity: 0.4; transform: scale(0.8); }
|
|
290
|
+
50% { opacity: 1; transform: scale(1.2); }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* ------------------------------------------------------------------ */
|
|
294
|
+
/* Screen reader only */
|
|
295
|
+
/* ------------------------------------------------------------------ */
|
|
296
|
+
|
|
297
|
+
:where([data-blocfeed-ui-root]) .bf-sr-only {
|
|
298
|
+
position: absolute;
|
|
299
|
+
width: 1px;
|
|
300
|
+
height: 1px;
|
|
301
|
+
padding: 0;
|
|
302
|
+
margin: -1px;
|
|
303
|
+
overflow: hidden;
|
|
304
|
+
clip: rect(0, 0, 0, 0);
|
|
305
|
+
white-space: nowrap;
|
|
306
|
+
border: 0;
|
|
307
|
+
}
|
|
308
|
+
`;function W(){if(!a()||document.getElementById(L))return;let t=document.createElement("style");t.id=L,t.textContent=G,document.head.appendChild(t);}function B(){let t=useContext(h);if(!t)throw new Error("useBlocFeed must be used within a <BlocFeedProvider />");return {state:t.state,controller:t.controller,start:t.controller.start,stop:t.controller.stop,clearSelection:t.controller.clearSelection,submit:t.controller.submit}}function I(t,e,i){return Math.max(e,Math.min(i,t))}function Z(t,e,i="bottom-right"){let o=window.innerWidth,s=window.innerHeight,a;a=I(t.x,12,Math.max(12,o-e-12));let d=t.y+t.height+12,x=Math.max(12,t.y-240);return {top:d+240<=s?d:x,left:a}}function ee(t){switch(t){case "bottom-left":return "bf-trigger bf-trigger-bl";case "top-right":return "bf-trigger bf-trigger-tr";case "top-left":return "bf-trigger bf-trigger-tl";default:return "bf-trigger"}}function te(t){let{rect:e}=t,i={left:`${e.x}px`,top:`${e.y}px`,width:`${Math.max(0,e.width)}px`,height:`${Math.max(0,e.height)}px`};return jsx("div",{className:"bf-highlight",style:i,"aria-hidden":"true"})}function oe(t){let e=useRef(null);return useEffect(()=>{if(!t||!e.current)return;e.current.querySelector(".bf-textarea")?.focus();let c=o=>{if(o.key!=="Tab"||!e.current)return;let s=e.current.querySelectorAll('button:not([disabled]), textarea:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])');if(s.length===0)return;let a=s[0],d=s[s.length-1];o.shiftKey&&document.activeElement===a?(o.preventDefault(),d.focus()):!o.shiftKey&&document.activeElement===d&&(o.preventDefault(),a.focus());};return document.addEventListener("keydown",c,{capture:true}),()=>document.removeEventListener("keydown",c,{capture:true})},[t]),e}function re(t){let{state:e,controller:i,start:c,stop:o,clearSelection:s,submit:a}=B(),d=t.config.ui?.position,[x,S]=useState(null),[p,P]=useState(""),[v,E]=useState(t.config.capture?.element??true),[y,R]=useState(t.config.capture?.fullPage??false),[K,N]=useState(null),z=e.phase==="review"||e.phase==="capturing"||e.phase==="submitting"||e.phase==="error"||e.phase==="success",j=oe(z);useEffect(()=>i.subscribeHover(S),[i]),useEffect(()=>{let n=i.__unsafeGetSelectedElement();if(!n||e.phase==="idle"||e.phase==="picking"){N(null);return}let M=()=>{N(b(n.getBoundingClientRect()));};M();let m=()=>M();return window.addEventListener("scroll",m,{capture:true,passive:true}),window.addEventListener("resize",m,{passive:true}),()=>{window.removeEventListener("scroll",m,{capture:true}),window.removeEventListener("resize",m);}},[i,e.phase,e.selection?.selector]),useEffect(()=>{e.phase==="review"&&(P(""),E(t.config.capture?.element??true),R(t.config.capture?.fullPage??false));},[e.phase,e.selection?.selector,t.config.capture?.element,t.config.capture?.fullPage]),useEffect(()=>{if(e.phase!=="success")return;let n=window.setTimeout(()=>o(),1200);return ()=>window.clearTimeout(n)},[e.phase,o]);let f=e.phase==="capturing"||e.phase==="submitting",b$1=e.phase==="picking"?x?.rect??null:K??e.selection?.rect??null,w=useMemo(()=>b$1?Z(b$1,360,d):null,[b$1?.x,b$1?.y,b$1?.width,b$1?.height,d]),T=e.lastError?.message,C=useCallback(()=>{a(p,{capture:{element:v,fullPage:y}});},[a,p,v,y]),O=useCallback(n=>{(n.metaKey||n.ctrlKey)&&n.key==="Enter"&&p.trim().length>0&&!f&&(n.preventDefault(),C());},[C,p,f]);return jsxs(Fragment,{children:[e.phase==="idle"&&jsxs("button",{className:ee(d),type:"button",onClick:()=>c(),"aria-label":"Give feedback",children:[jsx("span",{className:"bf-dot","aria-hidden":"true"}),"Feedback"]}),e.phase!=="idle"&&jsxs("div",{className:"bf-overlay",role:"presentation",children:[e.phase!=="picking"&&jsx("div",{className:"bf-blocker",role:"presentation",onClick:()=>o()}),b$1&&jsx(te,{rect:b$1}),e.phase==="picking"&&jsxs("div",{className:"bf-hint",role:"status","aria-live":"polite",children:[jsxs("p",{id:"bf-hint-text",children:["Click an element to attach your feedback. Press ",jsx("strong",{children:"Esc"})," to cancel."]}),jsx("button",{type:"button",className:"bf-btn",onClick:()=>o(),"aria-label":"Cancel element selection",children:"Cancel"})]}),z&&w&&jsxs("div",{ref:j,className:"bf-panel",style:{left:w.left,top:w.top},role:"dialog","aria-modal":"true","aria-label":"Feedback form",children:[jsxs("div",{className:"bf-panelHeader",children:[jsx("div",{className:"bf-title",id:"bf-panel-title",children:"Feedback"}),jsxs("div",{className:"bf-row",style:{justifyContent:"flex-end"},children:[jsx("button",{type:"button",className:"bf-btn",onClick:()=>s(),disabled:f,"aria-label":"Re-pick element",children:"Re-pick"}),jsx("button",{type:"button",className:"bf-btn",onClick:()=>o(),disabled:f,"aria-label":"Close feedback form",children:"Close"})]})]}),jsxs("div",{className:"bf-panelBody",children:[jsx("textarea",{className:"bf-textarea",placeholder:"What's happening? What did you expect?",value:p,onChange:n=>P(n.target.value),onKeyDown:O,disabled:f,"aria-label":"Feedback message"}),jsxs("div",{className:"bf-row",role:"group","aria-label":"Screenshot options",children:[jsxs("label",{children:[jsx("input",{type:"checkbox",checked:v,onChange:n=>E(n.target.checked),disabled:f}),"Screenshot element"]}),jsxs("label",{children:[jsx("input",{type:"checkbox",checked:y,onChange:n=>R(n.target.checked),disabled:f}),"Full page"]})]}),e.phase==="capturing"&&jsxs("div",{className:"bf-status",role:"status","aria-live":"polite",children:[jsx("span",{className:"bf-spinner","aria-hidden":"true"}),"Capturing screenshots\u2026"]}),e.phase==="submitting"&&jsxs("div",{className:"bf-status",role:"status","aria-live":"polite",children:[jsx("span",{className:"bf-spinner","aria-hidden":"true"}),"Submitting\u2026"]}),e.phase==="success"&&jsx("div",{className:"bf-status",role:"status","aria-live":"polite",children:"Sent. Thank you!"}),e.phase==="error"&&T&&jsx("div",{className:"bf-error",role:"alert",children:T}),jsxs("div",{className:"bf-actions",children:[jsx("button",{type:"button",className:"bf-btn",onClick:()=>o(),disabled:f,"aria-label":"Cancel feedback",children:"Cancel"}),jsx("button",{type:"button",className:"bf-btn bf-btnPrimary",onClick:C,disabled:f||p.trim().length===0,"aria-label":"Send feedback",children:"Send"})]})]})]})]}),e.phase==="success"&&jsx("div",{className:"bf-toast",role:"status","aria-live":"polite",children:"Feedback sent"})]})}function ae(t){let e={...t.config??{},blocfeed_id:t.blocfeed_id},[i,c]=useState(null);return useEffect(()=>{W();let o=document.createElement("div");o.setAttribute("data-blocfeed-ui-root","true"),o.setAttribute("data-blocfeed-ui","true");let s=e.ui?.zIndex;typeof s=="number"&&o.style.setProperty("--bf-z",String(s));let a=e.ui?.theme;return a&&(a.accentColor&&o.style.setProperty("--bf-accent",a.accentColor),a.panelBackground&&o.style.setProperty("--bf-panel-bg",a.panelBackground),a.panelForeground&&o.style.setProperty("--bf-panel-fg",a.panelForeground),a.fontFamily&&o.style.setProperty("--bf-font",a.fontFamily)),document.body.appendChild(o),c(o),()=>{o.remove(),c(null);}},[e.ui?.zIndex,e.ui?.theme?.accentColor,e.ui?.theme?.panelBackground,e.ui?.theme?.panelForeground,e.ui?.theme?.fontFamily]),i?createPortal(jsx(k,{blocfeed_id:e.blocfeed_id,...t.config?{config:t.config}:{},children:jsx(re,{config:e})}),i):null}export{k as BlocFeedProvider,ae as BlocFeedWidget,B as useBlocFeed};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blocfeed",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Drop-in feedback + screenshot widget for React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,6 +42,16 @@
|
|
|
42
42
|
"require": "./dist/engine.cjs"
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
+
"typesVersions": {
|
|
46
|
+
"*": {
|
|
47
|
+
".": [
|
|
48
|
+
"./dist/main.d.ts"
|
|
49
|
+
],
|
|
50
|
+
"engine": [
|
|
51
|
+
"./dist/engine.d.ts"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
45
55
|
"scripts": {
|
|
46
56
|
"build": "tsup",
|
|
47
57
|
"build:debug": "tsup --sourcemap",
|
package/dist/chunk-37P3GL6V.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function b(){return typeof window<"u"&&typeof document<"u"}function W(e){let n=globalThis.CSS;return typeof n?.escape=="function"?n.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,t=>{let r=t.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function H(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function de(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function fe(e){return e.replace(/\s+/g," ").trim()}function me(e,n=140){let t=e.textContent;if(!t)return;let r=fe(t);if(r)return r.length<=n?r:`${r.slice(0,n-1)}\u2026`}function pe(e){let n=1;for(let t=e.previousElementSibling;t;t=t.previousElementSibling)t.tagName===e.tagName&&(n+=1);return n}var K=["data-testid","data-test-id","data-test","data-qa","data-cy"],z="data-blocfeed-component";function ge(e){let n=e.closest(`[${z}]`);if(!n)return;let r=n.getAttribute(z)?.trim();return r||void 0}function we(e){for(let n of K){let t=e.closest(`[${n}]`);if(!t)continue;let o=t.getAttribute(n)?.trim();if(o)return o}}function he(e){try{let n=e,t=Object.getOwnPropertyNames(n);for(let r of t)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=n[r];if(o&&typeof o=="object")return o}}catch{}return null}function C(e){if(e&&typeof e!="string"){if(typeof e=="function"){let n=e;return typeof n.displayName=="string"&&n.displayName?n.displayName:typeof n.name=="string"&&n.name?n.name:void 0}if(typeof e=="object"){let n=e,t=n.displayName;if(typeof t=="string"&&t)return t;let r=n.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=n.type;return C(o)}}}function be(e){let n=he(e);if(!n)return;let t=n._debugOwner;for(let o=0;t&&o<50;o+=1){let i=C(t.type)??C(t.elementType);if(i)return i;t=t._debugOwner;}let r=n;for(let o=0;r&&o<80;o+=1){let i=C(r.type)??C(r.elementType);if(i)return i;r=r.return;}}function ye(e){let n=e.tagName.toLowerCase(),t=e.getAttribute("id");if(t)return `#${W(t)}`;for(let r of K){let o=e.getAttribute(r);if(o)return `${n}[${r}="${W(o)}"]`}return `${n}:nth-of-type(${pe(e)})`}function Ee(e,n=10){let t=[],r=e;for(;r&&t.length<n;){let o=ye(r);if(t.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return t.join(" > ")}function Y(e,n){if(!n||n.length===0)return false;for(let t of n)if(e.closest(t))return true;return false}function N(e,n){if(!e||Y(e,n.ignoreSelectors))return null;let t=e;for(;t;){if(Y(t,n.ignoreSelectors))return null;if(n.isSelectable?.(t)??ve(t))return t;t=t.parentElement;}return null}function ve(e){let n=e.tagName;return !(n==="HTML"||n==="BODY")}function Q(e){let n=e.getBoundingClientRect(),t={selector:Ee(e),tagName:e.tagName.toLowerCase(),rect:H(n),pageRect:de(n)},r=e.getAttribute("id");r&&(t.id=r);let o=e.className;typeof o=="string"&&o.trim()&&(t.className=o);let i=me(e);i&&(t.textSnippet=i);let l=ge(e)??be(e);l&&(t.componentName=l);let s=we(e);return s&&(t.testId=s),t}var O=null;async function X(){return O||(O=import('html-to-image')),O}async function Se(e){return await new Promise((n,t)=>{let r=new Image;r.onload=()=>n({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>t(new Error("Failed to load generated screenshot")),r.src=e;})}async function J(e,n){let{width:t,height:r}=await Se(e);return {dataUrl:e,mime:n,width:t,height:r}}function T(e){if(e?.aborted)throw new Error("Aborted")}function G(){return {async captureElement(e,n){if(!b())throw new Error("captureElement can only run in the browser");T(n.signal);let t=await X();T(n.signal);let r=n.mime==="image/jpeg"?await t.toJpeg(e,{quality:n.quality??.92,pixelRatio:n.pixelRatio}):await t.toPng(e,{pixelRatio:n.pixelRatio});return T(n.signal),await J(r,n.mime)},async captureFullPage(e){if(!b())throw new Error("captureFullPage can only run in the browser");T(e.signal);let n=document.documentElement,t=Math.max(n.scrollWidth,n.clientWidth),r=Math.max(n.scrollHeight,n.clientHeight),o=Math.min(1,e.maxDimension/Math.max(t,r)),i=Math.max(1,Math.round(t*o)),l=Math.max(1,Math.round(r*o)),s=await X();T(e.signal);let u=e.mime==="image/jpeg"?await s.toJpeg(n,{width:i,height:l,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await s.toPng(n,{width:i,height:l,pixelRatio:e.pixelRatio});return T(e.signal),await J(u,e.mime)}}}function ke(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function m(e,n,t){let r={kind:e,message:ke(n)};return t&&(r.detail=t),r}var Fe=12e3,xe=2048,Pe=.92;function Z(){return Date.now()}function Te(e){return new Promise((n,t)=>{let r=()=>t(new Error("Aborted"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:true});})}async function V(e,n,t){let r=new Promise((i,l)=>{let s=setTimeout(()=>l(new Error("Timeout")),n);typeof s.unref=="function"&&s.unref();}),o=[e,r];return t&&o.push(Te(t)),await Promise.race(o)}function Ae(e){if(!b())return 1;let n=window.devicePixelRatio||1,t=e?.pixelRatio??Math.min(n,2);return Math.max(.1,t)}function Ce(e){return !!(e?.element||e?.fullPage)}function ee(e){let n={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(n.quality=e.quality),e.signal&&(n.signal=e.signal),n}async function te(e){let{selectionElement:n,capture:t,signal:r}=e;if(!b()||!Ce(t))return;let o=Z(),i=[],l=t?.timeoutMs??Fe,s=t?.maxDimension??xe,u=t?.mime??"image/png",d=t?.quality??Pe,v=t?.adapter??G(),y={},p=Ae(t);if(t?.element&&n)try{let f=n.getBoundingClientRect(),g=Math.min(1,s/Math.max(f.width,f.height)),c=Math.min(p,p*g),E=await V(Promise.resolve(v.captureElement(n,{...ee({mime:u,quality:d,pixelRatio:c,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}})})),l,r);y.element=E;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"element"}));}if(t?.fullPage)try{let f=await V(Promise.resolve(v.captureFullPage(ee({mime:u,quality:d,pixelRatio:p,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}}))),l,r);y.fullPage=f;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"fullPage"}));}let S=Z(),a={startedAt:o,finishedAt:S,durationMs:Math.max(0,S-o)};return i.length>0&&(a.errors=i),{...y,diagnostics:a}}function Re(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Me(){return b()?{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:Re()}:{}}async function ne(e){let{config:n,context:t}=e;if(n?.enabled===false)return {};let r=Me(),o=n?.enrich;if(!o)return r;try{let i=await o(t);return {...r,...i}}catch(i){let l=m("unknown",i);return {...r,blocfeedMetadataError:l.message}}}function U(e){let n=null,t=null,r=(...o)=>{t=o,n===null&&(n=requestAnimationFrame(()=>{if(n=null,!t)return;let i=t;t=null,e(...i);}));};return r.cancel=()=>{n!==null&&cancelAnimationFrame(n),n=null,t=null;},r}function M(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function B(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function re(e,n){if(!b())throw new Error("BlocFeed picker can only run in a browser environment.");let t=e.ignoreSelectors,r=e.isSelectable,o={};t&&t.length>0&&(o.ignoreSelectors=t),r&&(o.isSelectable=r);let i=null,l=null,s=(a,f=false)=>{if(!a){i=null,l=null,n.onHover(null);return}let g=H(a.getBoundingClientRect()),c=`${Math.round(g.x)}:${Math.round(g.y)}:${Math.round(g.width)}:${Math.round(g.height)}`;!f&&a===i&&c===l||(i=a,l=c,n.onHover({element:a,rect:g}));},u=U(a=>{if(M(a.target))return;let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);s(g);}),d=U(()=>{i&&s(i,true);}),v=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},y=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},p=a=>{if(M(a.target))return;B(a),a.preventDefault();let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);g&&n.onSelect({element:g,descriptor:Q(g)});},S=a=>{a.key==="Escape"&&(B(a),a.preventDefault(),n.onCancel());};return window.addEventListener("pointermove",u,{capture:true,passive:true}),window.addEventListener("pointerdown",v,{capture:true}),window.addEventListener("pointerup",y,{capture:true}),window.addEventListener("click",p,{capture:true}),window.addEventListener("keydown",S,{capture:true}),window.addEventListener("scroll",d,{capture:true,passive:true}),window.addEventListener("resize",d,{passive:true}),{stop(){window.removeEventListener("pointermove",u,{capture:true}),window.removeEventListener("pointerdown",v,{capture:true}),window.removeEventListener("pointerup",y,{capture:true}),window.removeEventListener("click",p,{capture:true}),window.removeEventListener("keydown",S,{capture:true}),window.removeEventListener("scroll",d,{capture:true}),window.removeEventListener("resize",d),u.cancel(),d.cancel(),n.onHover(null);}}}var Be=12e3,$=2,oe=500,_e="https://blocfeed.com/api/feedback";function ie(e,n){return new Promise((t,r)=>{if(n?.aborted){r(new Error("Aborted"));return}let o=setTimeout(t,e),i=()=>{clearTimeout(o),r(new Error("Aborted"));};n?.addEventListener("abort",i,{once:true});})}function Le(e){return e>=500&&e<=599}async function ae(e){let{payload:n,signal:t}=e;for(let r=1;r<=$;r+=1){let o=new AbortController,i=setTimeout(()=>o.abort(),Be),l=()=>o.abort();t&&t.addEventListener("abort",l,{once:true});try{let s=await fetch(_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n),signal:o.signal});if(s.ok)return {ok:!0,status:s.status};if(r<$&&Le(s.status)){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:!1,status:s.status,error:m("api_failed",new Error(`HTTP ${s.status}`))}}catch(s){if(o.signal.aborted||t?.aborted)return {ok:false,error:m("aborted",s)};if(r<$){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:false,error:m("api_failed",s)}}finally{clearTimeout(i),t&&t.removeEventListener("abort",l);}}return {ok:false,error:m("api_failed",new Error("Failed"))}}async function se(e){let{signal:n}=e,t={ok:false};try{let r=n?{payload:e.payload,signal:n}:{payload:e.payload};t.api=await ae(r),t.ok=!!t.api?.ok;}catch(r){t.api={ok:false,error:m("api_failed",r)},t.ok=false;}return {payload:e.payload,result:t}}var Ie=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function De(e){let n=[...Ie,...e?.ignoreSelectors??[]],t=Array.from(new Set(n));return {...e,ignoreSelectors:t}}function le(){return {phase:"idle"}}function ut(e){let n=e,t=le(),r=new Set,o=new Set,i=null,l=null,s=null,u=null,d=0,v=()=>{for(let c of r)c(t);},y=c=>{for(let E of o)E(c);},p=c=>{t=c,v();},S=()=>{d+=1,u?.abort(),u=null;},a=()=>{i?.stop(),i=null,y(null),s!==null&&b()&&(document.documentElement.style.cursor=s,s=null);},f=()=>{S(),a(),l=null,p(le());},g=()=>{if(!b())return;a(),l=null;let c=De(n.picker);s=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",p({phase:"picking"}),i=re(c,{onHover:y,onSelect:({element:E,descriptor:R})=>{l=E,a(),p({phase:"review",selection:R});},onCancel:()=>{f();}});};return {getState:()=>t,subscribe(c){return r.add(c),()=>r.delete(c)},subscribeHover(c){return o.add(c),()=>o.delete(c)},start(){t.phase==="capturing"||t.phase==="submitting"||t.phase!=="picking"&&g();},stop(){f();},clearSelection(){t.phase==="capturing"||t.phase==="submitting"||g();},setConfig(c){n=c;},async submit(c,E){if(!b()){let w=m("configuration",new Error("BlocFeed submit can only run in the browser"));return p({phase:"error",lastError:w}),{ok:false}}let R=n.blocfeed_id?.trim?.()??"";if(!R){let k={phase:"error",lastError:m("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return t.selection&&(k.selection=t.selection),p(k),{ok:false}}if(t.phase==="capturing"||t.phase==="submitting")return {ok:false};let A=d+1;d=A,u?.abort(),u=new AbortController;let x=u.signal,h=t.selection,_=E?.capture?{...n.capture,...E.capture}:n.capture,q=!!(_?.element||_?.fullPage),j={phase:q?"capturing":"submitting"};h&&(j.selection=h),p(j);try{let w=q?await te({selectionElement:l,capture:_,signal:x}):void 0;if(x.aborted||d!==A)return {ok:!1};let k={phase:"submitting"};h&&(k.selection=h),w&&(k.capture=w),p(k);let P={};h&&(P.selection=h),w&&(P.capture=w);let ce=await ne({config:n.metadata,context:P}),L={version:1,createdAt:new Date().toISOString(),blocfeed_id:R,message:c,metadata:ce};h&&(L.selection=h),w&&(L.screenshots=w);let{result:F}=await se({payload:L,signal:x});if(x.aborted||d!==A)return F;if(F.ok){let D={phase:"success",lastSubmit:F};return h&&(D.selection=h),w&&(D.capture=w),p(D),F}let ue=F.api?.error??m("unknown",new Error("Submission failed")),I={phase:"error",lastSubmit:F,lastError:ue};return h&&(I.selection=h),w&&(I.capture=w),p(I),F}catch(w){if(x.aborted||d!==A)return {ok:false};let P={phase:"error",lastError:x.aborted?m("aborted",w):m("unknown",w)};return h&&(P.selection=h),p(P),{ok:false}}finally{d===A&&(u=null);}},__unsafeGetSelectedElement(){return l},destroy(){f(),r.clear(),o.clear();}}}
|
|
2
|
-
export{b as a,H as b,G as c,te as d,ne as e,ut as f};
|
package/dist/chunk-PEPIK3FN.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
'use strict';function b(){return typeof window<"u"&&typeof document<"u"}function W(e){let n=globalThis.CSS;return typeof n?.escape=="function"?n.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,t=>{let r=t.codePointAt(0);return r===void 0?"":`\\${r.toString(16)} `})}function H(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function de(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function fe(e){return e.replace(/\s+/g," ").trim()}function me(e,n=140){let t=e.textContent;if(!t)return;let r=fe(t);if(r)return r.length<=n?r:`${r.slice(0,n-1)}\u2026`}function pe(e){let n=1;for(let t=e.previousElementSibling;t;t=t.previousElementSibling)t.tagName===e.tagName&&(n+=1);return n}var K=["data-testid","data-test-id","data-test","data-qa","data-cy"],z="data-blocfeed-component";function ge(e){let n=e.closest(`[${z}]`);if(!n)return;let r=n.getAttribute(z)?.trim();return r||void 0}function we(e){for(let n of K){let t=e.closest(`[${n}]`);if(!t)continue;let o=t.getAttribute(n)?.trim();if(o)return o}}function he(e){try{let n=e,t=Object.getOwnPropertyNames(n);for(let r of t)if(r.startsWith("__reactFiber$")||r.startsWith("__reactInternalInstance$")){let o=n[r];if(o&&typeof o=="object")return o}}catch{}return null}function C(e){if(e&&typeof e!="string"){if(typeof e=="function"){let n=e;return typeof n.displayName=="string"&&n.displayName?n.displayName:typeof n.name=="string"&&n.name?n.name:void 0}if(typeof e=="object"){let n=e,t=n.displayName;if(typeof t=="string"&&t)return t;let r=n.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=n.type;return C(o)}}}function be(e){let n=he(e);if(!n)return;let t=n._debugOwner;for(let o=0;t&&o<50;o+=1){let i=C(t.type)??C(t.elementType);if(i)return i;t=t._debugOwner;}let r=n;for(let o=0;r&&o<80;o+=1){let i=C(r.type)??C(r.elementType);if(i)return i;r=r.return;}}function ye(e){let n=e.tagName.toLowerCase(),t=e.getAttribute("id");if(t)return `#${W(t)}`;for(let r of K){let o=e.getAttribute(r);if(o)return `${n}[${r}="${W(o)}"]`}return `${n}:nth-of-type(${pe(e)})`}function Ee(e,n=10){let t=[],r=e;for(;r&&t.length<n;){let o=ye(r);if(t.unshift(o),o.startsWith("#"))break;r=r.parentElement;}return t.join(" > ")}function Y(e,n){if(!n||n.length===0)return false;for(let t of n)if(e.closest(t))return true;return false}function N(e,n){if(!e||Y(e,n.ignoreSelectors))return null;let t=e;for(;t;){if(Y(t,n.ignoreSelectors))return null;if(n.isSelectable?.(t)??ve(t))return t;t=t.parentElement;}return null}function ve(e){let n=e.tagName;return !(n==="HTML"||n==="BODY")}function Q(e){let n=e.getBoundingClientRect(),t={selector:Ee(e),tagName:e.tagName.toLowerCase(),rect:H(n),pageRect:de(n)},r=e.getAttribute("id");r&&(t.id=r);let o=e.className;typeof o=="string"&&o.trim()&&(t.className=o);let i=me(e);i&&(t.textSnippet=i);let l=ge(e)??be(e);l&&(t.componentName=l);let s=we(e);return s&&(t.testId=s),t}var O=null;async function X(){return O||(O=import('html-to-image')),O}async function Se(e){return await new Promise((n,t)=>{let r=new Image;r.onload=()=>n({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>t(new Error("Failed to load generated screenshot")),r.src=e;})}async function J(e,n){let{width:t,height:r}=await Se(e);return {dataUrl:e,mime:n,width:t,height:r}}function T(e){if(e?.aborted)throw new Error("Aborted")}function G(){return {async captureElement(e,n){if(!b())throw new Error("captureElement can only run in the browser");T(n.signal);let t=await X();T(n.signal);let r=n.mime==="image/jpeg"?await t.toJpeg(e,{quality:n.quality??.92,pixelRatio:n.pixelRatio}):await t.toPng(e,{pixelRatio:n.pixelRatio});return T(n.signal),await J(r,n.mime)},async captureFullPage(e){if(!b())throw new Error("captureFullPage can only run in the browser");T(e.signal);let n=document.documentElement,t=Math.max(n.scrollWidth,n.clientWidth),r=Math.max(n.scrollHeight,n.clientHeight),o=Math.min(1,e.maxDimension/Math.max(t,r)),i=Math.max(1,Math.round(t*o)),l=Math.max(1,Math.round(r*o)),s=await X();T(e.signal);let u=e.mime==="image/jpeg"?await s.toJpeg(n,{width:i,height:l,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await s.toPng(n,{width:i,height:l,pixelRatio:e.pixelRatio});return T(e.signal),await J(u,e.mime)}}}function ke(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function m(e,n,t){let r={kind:e,message:ke(n)};return t&&(r.detail=t),r}var Fe=12e3,xe=2048,Pe=.92;function Z(){return Date.now()}function Te(e){return new Promise((n,t)=>{let r=()=>t(new Error("Aborted"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:true});})}async function V(e,n,t){let r=new Promise((i,l)=>{let s=setTimeout(()=>l(new Error("Timeout")),n);typeof s.unref=="function"&&s.unref();}),o=[e,r];return t&&o.push(Te(t)),await Promise.race(o)}function Ae(e){if(!b())return 1;let n=window.devicePixelRatio||1,t=e?.pixelRatio??Math.min(n,2);return Math.max(.1,t)}function Ce(e){return !!(e?.element||e?.fullPage)}function ee(e){let n={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(n.quality=e.quality),e.signal&&(n.signal=e.signal),n}async function te(e){let{selectionElement:n,capture:t,signal:r}=e;if(!b()||!Ce(t))return;let o=Z(),i=[],l=t?.timeoutMs??Fe,s=t?.maxDimension??xe,u=t?.mime??"image/png",d=t?.quality??Pe,v=t?.adapter??G(),y={},p=Ae(t);if(t?.element&&n)try{let f=n.getBoundingClientRect(),g=Math.min(1,s/Math.max(f.width,f.height)),c=Math.min(p,p*g),E=await V(Promise.resolve(v.captureElement(n,{...ee({mime:u,quality:d,pixelRatio:c,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}})})),l,r);y.element=E;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"element"}));}if(t?.fullPage)try{let f=await V(Promise.resolve(v.captureFullPage(ee({mime:u,quality:d,pixelRatio:p,maxDimension:s,includeQuality:u==="image/jpeg",...r?{signal:r}:{}}))),l,r);y.fullPage=f;}catch(f){if(r?.aborted)throw f;i.push(m("capture_failed",f,{target:"fullPage"}));}let S=Z(),a={startedAt:o,finishedAt:S,durationMs:Math.max(0,S-o)};return i.length>0&&(a.errors=i),{...y,diagnostics:a}}function Re(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function Me(){return b()?{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:Re()}:{}}async function ne(e){let{config:n,context:t}=e;if(n?.enabled===false)return {};let r=Me(),o=n?.enrich;if(!o)return r;try{let i=await o(t);return {...r,...i}}catch(i){let l=m("unknown",i);return {...r,blocfeedMetadataError:l.message}}}function U(e){let n=null,t=null,r=(...o)=>{t=o,n===null&&(n=requestAnimationFrame(()=>{if(n=null,!t)return;let i=t;t=null,e(...i);}));};return r.cancel=()=>{n!==null&&cancelAnimationFrame(n),n=null,t=null;},r}function M(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function B(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function re(e,n){if(!b())throw new Error("BlocFeed picker can only run in a browser environment.");let t=e.ignoreSelectors,r=e.isSelectable,o={};t&&t.length>0&&(o.ignoreSelectors=t),r&&(o.isSelectable=r);let i=null,l=null,s=(a,f=false)=>{if(!a){i=null,l=null,n.onHover(null);return}let g=H(a.getBoundingClientRect()),c=`${Math.round(g.x)}:${Math.round(g.y)}:${Math.round(g.width)}:${Math.round(g.height)}`;!f&&a===i&&c===l||(i=a,l=c,n.onHover({element:a,rect:g}));},u=U(a=>{if(M(a.target))return;let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);s(g);}),d=U(()=>{i&&s(i,true);}),v=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},y=a=>{M(a.target)||(B(a),a.pointerType==="mouse"&&a.preventDefault());},p=a=>{if(M(a.target))return;B(a),a.preventDefault();let f=document.elementFromPoint(a.clientX,a.clientY),g=N(f,o);g&&n.onSelect({element:g,descriptor:Q(g)});},S=a=>{a.key==="Escape"&&(B(a),a.preventDefault(),n.onCancel());};return window.addEventListener("pointermove",u,{capture:true,passive:true}),window.addEventListener("pointerdown",v,{capture:true}),window.addEventListener("pointerup",y,{capture:true}),window.addEventListener("click",p,{capture:true}),window.addEventListener("keydown",S,{capture:true}),window.addEventListener("scroll",d,{capture:true,passive:true}),window.addEventListener("resize",d,{passive:true}),{stop(){window.removeEventListener("pointermove",u,{capture:true}),window.removeEventListener("pointerdown",v,{capture:true}),window.removeEventListener("pointerup",y,{capture:true}),window.removeEventListener("click",p,{capture:true}),window.removeEventListener("keydown",S,{capture:true}),window.removeEventListener("scroll",d,{capture:true}),window.removeEventListener("resize",d),u.cancel(),d.cancel(),n.onHover(null);}}}var Be=12e3,$=2,oe=500,_e="https://blocfeed.com/api/feedback";function ie(e,n){return new Promise((t,r)=>{if(n?.aborted){r(new Error("Aborted"));return}let o=setTimeout(t,e),i=()=>{clearTimeout(o),r(new Error("Aborted"));};n?.addEventListener("abort",i,{once:true});})}function Le(e){return e>=500&&e<=599}async function ae(e){let{payload:n,signal:t}=e;for(let r=1;r<=$;r+=1){let o=new AbortController,i=setTimeout(()=>o.abort(),Be),l=()=>o.abort();t&&t.addEventListener("abort",l,{once:true});try{let s=await fetch(_e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n),signal:o.signal});if(s.ok)return {ok:!0,status:s.status};if(r<$&&Le(s.status)){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:!1,status:s.status,error:m("api_failed",new Error(`HTTP ${s.status}`))}}catch(s){if(o.signal.aborted||t?.aborted)return {ok:false,error:m("aborted",s)};if(r<$){let u=.85+Math.random()*.3,d=Math.round(oe*2**(r-1)*u);await ie(d,t);continue}return {ok:false,error:m("api_failed",s)}}finally{clearTimeout(i),t&&t.removeEventListener("abort",l);}}return {ok:false,error:m("api_failed",new Error("Failed"))}}async function se(e){let{signal:n}=e,t={ok:false};try{let r=n?{payload:e.payload,signal:n}:{payload:e.payload};t.api=await ae(r),t.ok=!!t.api?.ok;}catch(r){t.api={ok:false,error:m("api_failed",r)},t.ok=false;}return {payload:e.payload,result:t}}var Ie=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function De(e){let n=[...Ie,...e?.ignoreSelectors??[]],t=Array.from(new Set(n));return {...e,ignoreSelectors:t}}function le(){return {phase:"idle"}}function ut(e){let n=e,t=le(),r=new Set,o=new Set,i=null,l=null,s=null,u=null,d=0,v=()=>{for(let c of r)c(t);},y=c=>{for(let E of o)E(c);},p=c=>{t=c,v();},S=()=>{d+=1,u?.abort(),u=null;},a=()=>{i?.stop(),i=null,y(null),s!==null&&b()&&(document.documentElement.style.cursor=s,s=null);},f=()=>{S(),a(),l=null,p(le());},g=()=>{if(!b())return;a(),l=null;let c=De(n.picker);s=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",p({phase:"picking"}),i=re(c,{onHover:y,onSelect:({element:E,descriptor:R})=>{l=E,a(),p({phase:"review",selection:R});},onCancel:()=>{f();}});};return {getState:()=>t,subscribe(c){return r.add(c),()=>r.delete(c)},subscribeHover(c){return o.add(c),()=>o.delete(c)},start(){t.phase==="capturing"||t.phase==="submitting"||t.phase!=="picking"&&g();},stop(){f();},clearSelection(){t.phase==="capturing"||t.phase==="submitting"||g();},setConfig(c){n=c;},async submit(c,E){if(!b()){let w=m("configuration",new Error("BlocFeed submit can only run in the browser"));return p({phase:"error",lastError:w}),{ok:false}}let R=n.blocfeed_id?.trim?.()??"";if(!R){let k={phase:"error",lastError:m("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return t.selection&&(k.selection=t.selection),p(k),{ok:false}}if(t.phase==="capturing"||t.phase==="submitting")return {ok:false};let A=d+1;d=A,u?.abort(),u=new AbortController;let x=u.signal,h=t.selection,_=E?.capture?{...n.capture,...E.capture}:n.capture,q=!!(_?.element||_?.fullPage),j={phase:q?"capturing":"submitting"};h&&(j.selection=h),p(j);try{let w=q?await te({selectionElement:l,capture:_,signal:x}):void 0;if(x.aborted||d!==A)return {ok:!1};let k={phase:"submitting"};h&&(k.selection=h),w&&(k.capture=w),p(k);let P={};h&&(P.selection=h),w&&(P.capture=w);let ce=await ne({config:n.metadata,context:P}),L={version:1,createdAt:new Date().toISOString(),blocfeed_id:R,message:c,metadata:ce};h&&(L.selection=h),w&&(L.screenshots=w);let{result:F}=await se({payload:L,signal:x});if(x.aborted||d!==A)return F;if(F.ok){let D={phase:"success",lastSubmit:F};return h&&(D.selection=h),w&&(D.capture=w),p(D),F}let ue=F.api?.error??m("unknown",new Error("Submission failed")),I={phase:"error",lastSubmit:F,lastError:ue};return h&&(I.selection=h),w&&(I.capture=w),p(I),F}catch(w){if(x.aborted||d!==A)return {ok:false};let P={phase:"error",lastError:x.aborted?m("aborted",w):m("unknown",w)};return h&&(P.selection=h),p(P),{ok:false}}finally{d===A&&(u=null);}},__unsafeGetSelectedElement(){return l},destroy(){f(),r.clear(),o.clear();}}}
|
|
2
|
-
exports.a=b;exports.b=H;exports.c=G;exports.d=te;exports.e=ne;exports.f=ut;
|