@inputbuffer/feedback 0.2.0 → 0.3.1
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/README.md +178 -70
- package/dist/api.d.ts +3 -3
- package/dist/attrs.d.ts +13 -0
- package/dist/bar-entry.d.ts +2 -0
- package/dist/bar.css +1 -1
- package/dist/bar.esm.js +5 -12
- package/dist/bar.js +5 -12
- package/dist/errors.d.ts +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/modal-entry.d.ts +2 -0
- package/dist/modal.css +1 -1
- package/dist/modal.esm.js +5 -16
- package/dist/modal.js +5 -16
- package/dist/types.d.ts +51 -13
- package/dist/widget.esm.js +9 -27
- package/dist/widget.js +9 -27
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ This package is a lightweight embeddable widget you can drop into your documenta
|
|
|
19
19
|
- [CDN](#cdn-recommended)
|
|
20
20
|
- [npm](#npm)
|
|
21
21
|
- [Browser support](#browser-support)
|
|
22
|
+
- [Authentication](#authentication)
|
|
22
23
|
- [Reference](#reference)
|
|
23
24
|
- [Feedback bar](#feedback-bar)
|
|
24
25
|
- [Web component](#web-component)
|
|
@@ -33,7 +34,10 @@ This package is a lightweight embeddable widget you can drop into your documenta
|
|
|
33
34
|
- [`instance.destroy()`](#instancedestroy)
|
|
34
35
|
- [`instance.on(event, handler)`](#instanceonevent-handler)
|
|
35
36
|
- [`InputBufferIO.version`](#inputbufferioversion)
|
|
36
|
-
- [
|
|
37
|
+
- [Targets](#targets)
|
|
38
|
+
- [Target metadata schemas](#target-metadata-schemas)
|
|
39
|
+
- [Target attributes](#target-attributes)
|
|
40
|
+
- [Errors](#errors)
|
|
37
41
|
- [CSS customization](#css-customization)
|
|
38
42
|
- [Modal selectors](#modal-selectors)
|
|
39
43
|
- [Bar selectors](#bar-selectors)
|
|
@@ -45,15 +49,15 @@ This package is a lightweight embeddable widget you can drop into your documenta
|
|
|
45
49
|
|
|
46
50
|
## Quick start
|
|
47
51
|
|
|
48
|
-
Before you start you will need a widget
|
|
52
|
+
Before you start you will need a **widget token** (`ibw_…`) from your [InputBuffer dashboard](https://inputbuffer.io) — see [Authentication](#authentication). Widget tokens are designed to be embedded in a browser; your full-access `ib_…` token is not and will be rejected.
|
|
49
53
|
|
|
50
54
|
There are three bundles — pick the one that matches your use case:
|
|
51
55
|
|
|
52
56
|
| Bundle | Size | What it includes |
|
|
53
57
|
|---|---|---|
|
|
54
58
|
| `bar.js` | 18 KB | Thumbs up/down bar with optional follow-up form |
|
|
55
|
-
| `modal.js` |
|
|
56
|
-
| `widget.js` |
|
|
59
|
+
| `modal.js` | 16 KB | Full-text feedback modal |
|
|
60
|
+
| `widget.js` | 32 KB | Both bar and modal |
|
|
57
61
|
|
|
58
62
|
### Inline thumbs bar (web component)
|
|
59
63
|
|
|
@@ -119,7 +123,7 @@ If you want a full-text feedback modal triggered by a button, load `modal.js` wi
|
|
|
119
123
|
</script>
|
|
120
124
|
```
|
|
121
125
|
|
|
122
|
-
When the page loads the widget connects to `#feedback-btn`. Clicking it opens a modal with a text field and optional
|
|
126
|
+
When the page loads the widget connects to `#feedback-btn`. Clicking it opens a modal with a text field and an optional title field.
|
|
123
127
|
|
|
124
128
|
### Verify it's working
|
|
125
129
|
|
|
@@ -129,6 +133,7 @@ Submit a test message from your page and open your InputBuffer dashboard. It sho
|
|
|
129
133
|
|
|
130
134
|
- **Nothing happens on click:** check that the `data-attach-to` selector matches your button's `id` exactly, including the `#`.
|
|
131
135
|
- **401 error:** your `data-api-key` is invalid — try again with a fresh copy from the dashboard, and if it still fails [contact us](https://inputbuffer.io/contact).
|
|
136
|
+
- **403 error:** either you used a full-access `ib_…` token instead of a widget `ibw_…` token, or the page's origin is not on your token's allowlist. The widget logs the specific reason to the browser console — see [Errors](#errors).
|
|
132
137
|
|
|
133
138
|
### Where to go from here
|
|
134
139
|
|
|
@@ -145,12 +150,12 @@ Submit a test message from your page and open your InputBuffer dashboard. It sho
|
|
|
145
150
|
Load only the component you need — each is roughly half the full bundle:
|
|
146
151
|
|
|
147
152
|
```html
|
|
148
|
-
<!-- Full bundle (modal + bar) —
|
|
153
|
+
<!-- Full bundle (modal + bar) — 32 KB -->
|
|
149
154
|
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/widget.js"
|
|
150
155
|
data-api-key="YOUR_WIDGET_TOKEN">
|
|
151
156
|
</script>
|
|
152
157
|
|
|
153
|
-
<!-- Modal only —
|
|
158
|
+
<!-- Modal only — 16 KB -->
|
|
154
159
|
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js"
|
|
155
160
|
data-api-key="YOUR_WIDGET_TOKEN">
|
|
156
161
|
</script>
|
|
@@ -161,7 +166,7 @@ Load only the component you need — each is roughly half the full bundle:
|
|
|
161
166
|
|
|
162
167
|
> **SRI note:** For production deployments, add a Subresource Integrity `integrity` attribute to guard against CDN compromise. Generate the hash for each pinned version with:
|
|
163
168
|
> ```bash
|
|
164
|
-
> curl -s https://cdn.jsdelivr.net/npm/@inputbuffer/feedback@0.
|
|
169
|
+
> curl -s https://cdn.jsdelivr.net/npm/@inputbuffer/feedback@0.3.0/dist/widget.js | openssl dgst -sha384 -binary | openssl base64 -A
|
|
165
170
|
> ```
|
|
166
171
|
> Then use `integrity="sha384-<hash>" crossorigin="anonymous"` on the `<script>` tag.
|
|
167
172
|
|
|
@@ -178,10 +183,10 @@ npm install @inputbuffer/feedback
|
|
|
178
183
|
Import only what you use — your bundler will tree-shake the rest:
|
|
179
184
|
|
|
180
185
|
```js
|
|
181
|
-
// Modal only (~
|
|
186
|
+
// Modal only (~16 KB minified)
|
|
182
187
|
import { createModal } from '@inputbuffer/feedback/modal';
|
|
183
188
|
|
|
184
|
-
// Bar only (~18 KB
|
|
189
|
+
// Bar only (~18 KB minified)
|
|
185
190
|
import { createBar } from '@inputbuffer/feedback/bar';
|
|
186
191
|
|
|
187
192
|
// Full bundle
|
|
@@ -196,6 +201,25 @@ Chrome 111+, Firefox 113+, Safari 16.2+. The bundles target ES2019 and rely on C
|
|
|
196
201
|
|
|
197
202
|
---
|
|
198
203
|
|
|
204
|
+
## Authentication
|
|
205
|
+
|
|
206
|
+
InputBuffer issues two kinds of token, and this widget needs the browser-safe one.
|
|
207
|
+
|
|
208
|
+
| Token | Prefix | Where it belongs |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| Widget | `ibw_` | **Use this.** Safe to embed in a public page. Scoped to submitting feedback and reactions, nothing else. |
|
|
211
|
+
| Full access | `ib_` | Server-side only. Rejected with `403 widget-token-restricted` when sent from a browser. |
|
|
212
|
+
|
|
213
|
+
Create a widget token in **Settings → API Tokens** with the Widget scope, then add the origins your widget is embedded on to the token's allowlist. A request from an origin that isn't allowlisted is rejected with `403 forbidden-origin`.
|
|
214
|
+
|
|
215
|
+
Because the token is public, it can only create feedback and reactions — it cannot read your existing feedback, browse buffers, or touch anything else in your organization.
|
|
216
|
+
|
|
217
|
+
> **Privacy:** feedback text is sent to third-party AI services for classification and search, and is not scrubbed first. Don't prompt users for personal information, credentials, or production secrets.
|
|
218
|
+
|
|
219
|
+
The full API this widget speaks to is documented at [inputbuffer.io/docs/api](https://inputbuffer.io/docs/api/getting-started), with the machine-readable spec at [openapi.yaml](https://inputbuffer.io/docs/api/openapi.yaml).
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
199
223
|
## Reference
|
|
200
224
|
|
|
201
225
|
Each CDN bundle sets `window.InputBufferIO` at parse time — no `DOMContentLoaded` needed. What's exposed depends on which bundle you load:
|
|
@@ -226,8 +250,8 @@ Supported attributes:
|
|
|
226
250
|
|
|
227
251
|
| Attribute | Description |
|
|
228
252
|
|---|---|
|
|
229
|
-
| `api-key` | **Required.** Your widget
|
|
230
|
-
| `api-url` | Override the API
|
|
253
|
+
| `api-key` | **Required.** Your widget token (`ibw_…`). |
|
|
254
|
+
| `api-url` | Override the API base origin (e.g. `http://localhost:8080`). Paths are appended by the widget. |
|
|
231
255
|
| `label` | Text shown next to the thumbs. |
|
|
232
256
|
| `placement` | `"inline"` (default) or `"fixed"` (pins to bottom of viewport). |
|
|
233
257
|
| `theme-primary` | Primary color. |
|
|
@@ -241,9 +265,21 @@ Supported attributes:
|
|
|
241
265
|
| `modal-title` | Title shown above the feedback textarea. |
|
|
242
266
|
| `modal-placeholder` | Placeholder text for the feedback textarea. |
|
|
243
267
|
| `show-title-field` | `"true"` to show an optional title input. |
|
|
244
|
-
| `
|
|
245
|
-
| `source` | Identifier for the feedback source. |
|
|
268
|
+
| `submitted-by` | Opaque identifier for whoever is submitting, e.g. your own user id. Sent as `submitted_by`. |
|
|
246
269
|
| `user-id` | Stable user identifier for reaction deduplication. When set, only one reaction per user is recorded per target (all-time). When omitted, deduplication falls back to IP address with a 24-hour window. |
|
|
270
|
+
| `target-*` | Attaches the bar to a specific docs page, endpoint, or command. See [Target attributes](#target-attributes). |
|
|
271
|
+
|
|
272
|
+
Attach a target to record thumb votes against it:
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<inputbuffer-feedback
|
|
276
|
+
api-key="ibw_YOUR_WIDGET_TOKEN"
|
|
277
|
+
label="Was this helpful?"
|
|
278
|
+
target-type="documentation">
|
|
279
|
+
</inputbuffer-feedback>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Without a target, a thumb click still opens the follow-up form, but there is nothing for the reactions API to count the vote against.
|
|
247
283
|
|
|
248
284
|
### `InputBufferIO.createBar(config)`
|
|
249
285
|
|
|
@@ -259,8 +295,8 @@ document.getElementById('my-slot').appendChild(bar.element);
|
|
|
259
295
|
|
|
260
296
|
| Property | Type | Default | Description |
|
|
261
297
|
|---|---|---|---|
|
|
262
|
-
| `apiKey` | string | — | **Required.** Your widget
|
|
263
|
-
| `apiUrl` | string |
|
|
298
|
+
| `apiKey` | string | — | **Required.** Your widget token (`ibw_…`). |
|
|
299
|
+
| `apiUrl` | string | `'https://inputbuffer.io'` | API base origin. Paths are appended by the widget. |
|
|
264
300
|
| `label` | string | — | Text shown next to the thumbs. |
|
|
265
301
|
| `showLabel` | boolean | `true` | Set to `false` to show thumbs only. |
|
|
266
302
|
| `placement` | `'inline'` \| `'fixed'` | `'inline'` | `fixed` pins the bar to the bottom of the viewport. |
|
|
@@ -271,16 +307,12 @@ document.getElementById('my-slot').appendChild(bar.element);
|
|
|
271
307
|
| `theme.text` | string | — | Text color. |
|
|
272
308
|
| `theme.selected` | string | — | Background color of the selected thumb. |
|
|
273
309
|
| `theme.selectedColor` | string | — | Icon color of the selected thumb. |
|
|
274
|
-
| `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | — | The kind of thing the user is giving feedback on.
|
|
275
|
-
| `target.
|
|
276
|
-
| `target.displayName` | string | — | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
|
|
277
|
-
| `target.dedupKey` | string | — | Custom deduplication key (max 500 chars). |
|
|
278
|
-
| `target.metadata` | object | — | Type-specific fields — see [Target metadata schemas](#target-metadata-schemas). |
|
|
310
|
+
| `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | — | The kind of thing the user is giving feedback on. Required to record thumb votes. |
|
|
311
|
+
| `target.metadata` | object | — | **Required** when `target` is set. Type-specific fields — see [Target metadata schemas](#target-metadata-schemas). |
|
|
279
312
|
| `modalTitle` | string | — | Heading for the follow-up popover. |
|
|
280
313
|
| `modalPlaceholder` | string | — | Textarea placeholder for the follow-up popover. |
|
|
281
|
-
| `showEmailField` | boolean | `false` | Show/hide the email field in the follow-up popover. |
|
|
282
314
|
| `showTitleField` | boolean | `false` | Show/hide the title field in the follow-up popover. |
|
|
283
|
-
| `
|
|
315
|
+
| `submittedBy` | string | — | Opaque identifier for whoever is submitting, e.g. your own user id or an anonymous token. Sent as `submitted_by` (max 300 chars). |
|
|
284
316
|
| `userId` | string | — | Stable user identifier for reaction deduplication. When set, only one reaction per user is recorded per target (all-time). When omitted, deduplication falls back to IP address with a 24-hour window. |
|
|
285
317
|
| `injectStyles` | boolean | `true` | Set to `false` to skip automatic style injection. |
|
|
286
318
|
|
|
@@ -298,7 +330,7 @@ bar.on('error', (err) => console.error('Submission failed:', err));
|
|
|
298
330
|
|
|
299
331
|
| Event | Handler signature | When it fires |
|
|
300
332
|
|---|---|---|
|
|
301
|
-
| `vote` | `({ sentiment: 'positive' \| 'negative' }) => void` | User clicks a thumb.
|
|
333
|
+
| `vote` | `({ sentiment: 'positive' \| 'negative' }) => void` | User clicks a thumb. If a `target` is configured, the reaction is recorded immediately via `POST /api/v0/reactions` (fire-and-forget — a failure never blocks the follow-up form). The selection is persisted in `localStorage` for 24 hours so it survives page reloads. |
|
|
302
334
|
| `open` | `({ sentiment: 'positive' \| 'negative' }) => void` | The follow-up popover opens. |
|
|
303
335
|
| `submit` | `({ id: string }) => void` | Feedback was submitted successfully. |
|
|
304
336
|
| `close` | `() => void` | The follow-up popover closes. |
|
|
@@ -320,11 +352,16 @@ When `data-api-key` is present on the script tag, the widget initializes automat
|
|
|
320
352
|
|
|
321
353
|
| Attribute | Type | Description |
|
|
322
354
|
|---|---|---|
|
|
323
|
-
| `data-api-key` | string | **Required.** Your widget
|
|
324
|
-
| `data-api-url` | string | Override the API
|
|
355
|
+
| `data-api-key` | string | **Required.** Your widget token (`ibw_…`). |
|
|
356
|
+
| `data-api-url` | string | Override the API base origin (useful for local dev/testing). |
|
|
325
357
|
| `data-attach-to` | string | CSS selector for the element that opens the modal on click. |
|
|
326
358
|
| `data-inject-styles` | boolean | Set to `"false"` to skip automatic style injection. |
|
|
327
359
|
| `data-color-scheme` | string | `"light"`, `"dark"`, or `"auto"`. |
|
|
360
|
+
| `data-title` | string | Modal heading. |
|
|
361
|
+
| `data-placeholder` | string | Textarea placeholder text. |
|
|
362
|
+
| `data-show-title-field` | boolean | `"true"` to show an optional title field. |
|
|
363
|
+
| `data-show-sentiment` | boolean | `"true"` to show thumbs up/down buttons in the modal. |
|
|
364
|
+
| `data-submitted-by` | string | Opaque identifier for whoever is submitting. Sent as `submitted_by`. |
|
|
328
365
|
| `data-theme-primary` | string | Primary color (buttons, focus rings). Any CSS color value. |
|
|
329
366
|
| `data-theme-background` | string | Modal background color. |
|
|
330
367
|
| `data-theme-text` | string | Modal text color. |
|
|
@@ -345,16 +382,15 @@ const ib = InputBufferIO.createModal({
|
|
|
345
382
|
|
|
346
383
|
| Property | Type | Default | Description |
|
|
347
384
|
|---|---|---|---|
|
|
348
|
-
| `apiKey` | string | — | **Required.** Your widget
|
|
349
|
-
| `apiUrl` | string |
|
|
385
|
+
| `apiKey` | string | — | **Required.** Your widget token (`ibw_…`). |
|
|
386
|
+
| `apiUrl` | string | `'https://inputbuffer.io'` | API base origin (useful for local dev/testing). Paths are appended by the widget. |
|
|
350
387
|
| `attachTo` | string | — | CSS selector. Clicking the matched element calls `open()`. |
|
|
351
388
|
| `injectStyles` | boolean | `true` | Set to `false` to skip automatic style injection. |
|
|
352
389
|
| `title` | string | — | Modal heading. Omit to render no title. |
|
|
353
390
|
| `placeholder` | string | `"What's on your mind?"` | Textarea placeholder text. |
|
|
354
|
-
| `showEmailField` | boolean | `false` | Set to `true` to show an optional email field. |
|
|
355
391
|
| `showTitleField` | boolean | `false` | Set to `true` to show an optional title field. |
|
|
356
392
|
| `showSentiment` | boolean | `false` | Show thumbs up/down sentiment buttons in the modal. |
|
|
357
|
-
| `
|
|
393
|
+
| `submittedBy` | string | — | Opaque identifier for whoever is submitting, e.g. your own user id or an anonymous token. Sent as `submitted_by` (max 300 chars). |
|
|
358
394
|
| `colorScheme` | `'light'` \| `'dark'` \| `'auto'` | `'auto'` | Force a color scheme or follow the system setting. |
|
|
359
395
|
| `theme.primary` | string | — | Primary color (buttons, focus rings). |
|
|
360
396
|
| `theme.background` | string | — | Modal background color. |
|
|
@@ -372,8 +408,6 @@ ib.open({
|
|
|
372
408
|
title: 'Was this helpful?',
|
|
373
409
|
target: {
|
|
374
410
|
type: 'documentation',
|
|
375
|
-
targetId: 'auth-overview',
|
|
376
|
-
displayName: 'Authentication overview',
|
|
377
411
|
metadata: {
|
|
378
412
|
page_url: window.location.href,
|
|
379
413
|
section_heading: 'Authentication',
|
|
@@ -388,15 +422,11 @@ ib.open({
|
|
|
388
422
|
| Property | Type | Description |
|
|
389
423
|
|---|---|---|
|
|
390
424
|
| `title` | string | Overrides the modal heading for this open call. |
|
|
391
|
-
| `sentiment` | `'positive'` \| `'negative'` | Pre-selects a sentiment thumb. Only relevant when `showSentiment` is enabled. |
|
|
392
|
-
| `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | The kind of thing the user is giving feedback on.
|
|
393
|
-
| `target.
|
|
394
|
-
| `target.displayName` | string | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
|
|
395
|
-
| `target.dedupKey` | string | Custom deduplication key (max 500 chars). |
|
|
396
|
-
| `target.metadata` | object | Type-specific fields — see [Target metadata schemas](#target-metadata-schemas). |
|
|
397
|
-
| `prefill.email` | string | Pre-populates the email field. |
|
|
425
|
+
| `sentiment` | `'positive'` \| `'negative'` | Pre-selects a sentiment thumb. Only relevant when `showSentiment` is enabled. Drives the UI only — the feedback API has no sentiment field. |
|
|
426
|
+
| `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | The kind of thing the user is giving feedback on. |
|
|
427
|
+
| `target.metadata` | object | **Required** when `target` is set. Type-specific fields — see [Target metadata schemas](#target-metadata-schemas). |
|
|
398
428
|
| `prefill.description` | string | Pre-populates the textarea. |
|
|
399
|
-
| `
|
|
429
|
+
| `submittedBy` | string | Overrides the `submittedBy` set in `createModal(config)` for this open call. |
|
|
400
430
|
|
|
401
431
|
### `instance.close()`
|
|
402
432
|
|
|
@@ -427,19 +457,26 @@ ib.on('error', (err) => console.error('Submission failed:', err));
|
|
|
427
457
|
The currently loaded widget version string.
|
|
428
458
|
|
|
429
459
|
```js
|
|
430
|
-
console.log(InputBufferIO.version); // e.g. "0.
|
|
460
|
+
console.log(InputBufferIO.version); // e.g. "0.3.0"
|
|
431
461
|
```
|
|
432
462
|
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Targets
|
|
466
|
+
|
|
467
|
+
A target is the thing feedback is *about* — a docs page, an API endpoint, a CLI command. Two targets of the same type with the same metadata are the same target, which is how InputBuffer groups feedback and counts reactions. You don't register targets ahead of time: send the type and metadata, and the API resolves an existing target or creates one.
|
|
468
|
+
|
|
469
|
+
Targets are also what make the thumbs bar's votes countable. Without a target, a thumb click opens the follow-up form but records no reaction.
|
|
470
|
+
|
|
433
471
|
### Target metadata schemas
|
|
434
472
|
|
|
435
|
-
|
|
473
|
+
`metadata` is required whenever you set a target, and the fields it accepts depend on `target.type`. The server rejects a target that is missing a required field with a `422`.
|
|
436
474
|
|
|
437
475
|
**`documentation`**
|
|
438
476
|
|
|
439
477
|
| Field | Required | Description |
|
|
440
478
|
|---|---|---|
|
|
441
|
-
| `page_url` |
|
|
442
|
-
| `page_slug` | No | Slug or path of the page. |
|
|
479
|
+
| `page_url` | **Yes** | URL of the documentation page. |
|
|
443
480
|
| `section_heading` | No | Heading of the section the user is viewing. |
|
|
444
481
|
| `doc_version` | No | Documentation version string. |
|
|
445
482
|
|
|
@@ -447,8 +484,8 @@ The fields accepted in `target.metadata` depend on `target.type`. The server val
|
|
|
447
484
|
|
|
448
485
|
| Field | Required | Description |
|
|
449
486
|
|---|---|---|
|
|
450
|
-
| `method` | Yes
|
|
451
|
-
| `path` | Yes
|
|
487
|
+
| `method` | **Yes** | HTTP method (`GET`, `POST`, etc.). |
|
|
488
|
+
| `path` | **Yes** | API path (e.g. `/users/{id}`). |
|
|
452
489
|
| `host` | No | Hostname (e.g. `api.example.com`). |
|
|
453
490
|
| `api_version` | No | API version string. |
|
|
454
491
|
|
|
@@ -456,11 +493,83 @@ The fields accepted in `target.metadata` depend on `target.type`. The server val
|
|
|
456
493
|
|
|
457
494
|
| Field | Required | Description |
|
|
458
495
|
|---|---|---|
|
|
459
|
-
| `command` | Yes
|
|
460
|
-
| `subcommand` | No | Subcommand (e.g. `
|
|
496
|
+
| `command` | **Yes** | Top-level CLI command (e.g. `deploy`). |
|
|
497
|
+
| `subcommand` | No | Subcommand, which may be multi-word (e.g. `container run`). |
|
|
461
498
|
| `cli_version` | No | CLI version string. |
|
|
499
|
+
| `args` | No | Documented flags and args, e.g. `['--rm', '--network']`. When feedback is submitted with more than one, each flag becomes its own target. |
|
|
500
|
+
|
|
501
|
+
### Target attributes
|
|
462
502
|
|
|
463
|
-
|
|
503
|
+
The `<inputbuffer-feedback>` element builds a target from `target-type` plus the metadata attributes for that type:
|
|
504
|
+
|
|
505
|
+
| Attribute | Applies to | Maps to |
|
|
506
|
+
|---|---|---|
|
|
507
|
+
| `target-type` | all | `type` — `documentation`, `rest_endpoint`, or `cli_command` |
|
|
508
|
+
| `target-page-url` | `documentation` | `page_url` — defaults to the current page URL |
|
|
509
|
+
| `target-section-heading` | `documentation` | `section_heading` |
|
|
510
|
+
| `target-doc-version` | `documentation` | `doc_version` |
|
|
511
|
+
| `target-method` | `rest_endpoint` | `method` |
|
|
512
|
+
| `target-path` | `rest_endpoint` | `path` |
|
|
513
|
+
| `target-host` | `rest_endpoint` | `host` |
|
|
514
|
+
| `target-api-version` | `rest_endpoint` | `api_version` |
|
|
515
|
+
| `target-command` | `cli_command` | `command` |
|
|
516
|
+
| `target-subcommand` | `cli_command` | `subcommand` |
|
|
517
|
+
| `target-cli-version` | `cli_command` | `cli_version` |
|
|
518
|
+
| `target-args` | `cli_command` | `args` — comma-separated |
|
|
519
|
+
|
|
520
|
+
Because `target-page-url` defaults to `window.location.href`, rating the current docs page needs one attribute:
|
|
521
|
+
|
|
522
|
+
```html
|
|
523
|
+
<inputbuffer-feedback api-key="ibw_YOUR_WIDGET_TOKEN" target-type="documentation"></inputbuffer-feedback>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
An endpoint reference needs its identifying fields spelled out:
|
|
527
|
+
|
|
528
|
+
```html
|
|
529
|
+
<inputbuffer-feedback
|
|
530
|
+
api-key="ibw_YOUR_WIDGET_TOKEN"
|
|
531
|
+
target-type="rest_endpoint"
|
|
532
|
+
target-method="POST"
|
|
533
|
+
target-path="/v1/uploads">
|
|
534
|
+
</inputbuffer-feedback>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
If a required attribute is missing the widget logs a warning and drops the target rather than sending a request the server would reject.
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Errors
|
|
542
|
+
|
|
543
|
+
API errors arrive as [RFC 7807 Problem Details](https://inputbuffer.io/docs/api/problems). The `error` event hands you an `ApiError` with `type`, `title`, `status`, `detail`, `category`, and (on validation failures) `field`. Switch on `type` rather than `status` — several types share a status code.
|
|
544
|
+
|
|
545
|
+
`category` decides what the user sees:
|
|
546
|
+
|
|
547
|
+
- **`user`** — they sent something the API rejected. `detail` is written for them and is shown in the form as-is.
|
|
548
|
+
- **`integration`** — your embed is misconfigured. The user sees a generic message, and the widget logs the real reason to the browser console.
|
|
549
|
+
|
|
550
|
+
| Problem type | Status | What went wrong |
|
|
551
|
+
|---|---|---|
|
|
552
|
+
| `unauthorized`, `invalid-token`, `invalid-token-format` | 401 | The token is missing, malformed, or revoked. |
|
|
553
|
+
| `widget-token-restricted` | 403 | A full-access `ib_…` token was used from a browser. Use a widget `ibw_…` token. |
|
|
554
|
+
| `forbidden-origin` | 403 | This page's origin isn't on the token's allowlist. |
|
|
555
|
+
| `usage-limit-reached` | 402 | The organization hit its lifetime feedback limit. |
|
|
556
|
+
| `missing-required-field`, `invalid-field-value` | 422 | A field is missing or invalid; `field` names it. |
|
|
557
|
+
| `rate-limited` | 429 | Too many requests. |
|
|
558
|
+
| `internal-error` | 500 | Something failed on InputBuffer's end. |
|
|
559
|
+
|
|
560
|
+
`ApiError` is exported from every entry point, so you can narrow with `instanceof`:
|
|
561
|
+
|
|
562
|
+
```js
|
|
563
|
+
import { ApiError } from '@inputbuffer/feedback/modal';
|
|
564
|
+
|
|
565
|
+
ib.on('error', (err) => {
|
|
566
|
+
if (err instanceof ApiError && err.category === 'integration') {
|
|
567
|
+
console.error('Fix your embed:', err.type, err.detail);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Network failures and the 10-second request timeout surface as ordinary `Error`s, not `ApiError`s.
|
|
464
573
|
|
|
465
574
|
---
|
|
466
575
|
|
|
@@ -478,7 +587,7 @@ Each component uses stable selectors you can target directly in your stylesheet.
|
|
|
478
587
|
| `#ib-modal-body` | — | Body area |
|
|
479
588
|
| `#ib-title` | `.ib-modal-title` | Modal heading |
|
|
480
589
|
| `#ib-textarea` | `.ib-modal-textarea` | Feedback text field |
|
|
481
|
-
| `#ib-
|
|
590
|
+
| `#ib-title-input` | `.ib-modal-title-input` | Optional title input |
|
|
482
591
|
| `#ib-submit` | `.ib-modal-submit` | Submit button |
|
|
483
592
|
| `#ib-close` | `.ib-modal-close` | Close button |
|
|
484
593
|
| `#ib-success` | `.ib-modal-success` | Success message |
|
|
@@ -499,7 +608,7 @@ The IDs are the stable public API and will not change between releases. The `.ib
|
|
|
499
608
|
| `.ib-bar-body` | Popover body |
|
|
500
609
|
| `.ib-bar-title` | Popover heading |
|
|
501
610
|
| `.ib-bar-textarea` | Feedback text field |
|
|
502
|
-
| `.ib-bar-
|
|
611
|
+
| `.ib-bar-title-input` | Optional title input |
|
|
503
612
|
| `.ib-bar-submit` | Submit button |
|
|
504
613
|
| `.ib-bar-success` | Success message |
|
|
505
614
|
| `.ib-bar-error` | Error message |
|
|
@@ -537,8 +646,6 @@ document.getElementById('feedback-btn').addEventListener('click', () => {
|
|
|
537
646
|
title: 'Was this page helpful?',
|
|
538
647
|
target: {
|
|
539
648
|
type: 'documentation',
|
|
540
|
-
targetId: window.location.pathname,
|
|
541
|
-
displayName: document.title,
|
|
542
649
|
metadata: {
|
|
543
650
|
page_url: window.location.href,
|
|
544
651
|
section_heading: document.querySelector('h1')?.textContent ?? '',
|
|
@@ -557,8 +664,6 @@ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
|
|
|
557
664
|
ib.open({
|
|
558
665
|
target: {
|
|
559
666
|
type: 'rest_endpoint',
|
|
560
|
-
targetId: 'POST /v1/uploads',
|
|
561
|
-
displayName: 'Upload a file',
|
|
562
667
|
metadata: { method: 'POST', path: '/v1/uploads' },
|
|
563
668
|
},
|
|
564
669
|
});
|
|
@@ -573,8 +678,6 @@ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
|
|
|
573
678
|
ib.open({
|
|
574
679
|
target: {
|
|
575
680
|
type: 'cli_command',
|
|
576
|
-
targetId: 'deploy',
|
|
577
|
-
displayName: 'my-cli deploy',
|
|
578
681
|
metadata: { command: 'deploy' },
|
|
579
682
|
},
|
|
580
683
|
});
|
|
@@ -638,26 +741,31 @@ document.getElementById('my-slot').appendChild(bar.element);
|
|
|
638
741
|
</script>
|
|
639
742
|
```
|
|
640
743
|
|
|
641
|
-
|
|
744
|
+
### Identify who submitted feedback
|
|
642
745
|
|
|
643
|
-
|
|
746
|
+
`submittedBy` is an opaque string — your own user id, or an anonymous token. It's stored exactly as sent and never parsed, so prefer an id over an email address.
|
|
644
747
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
748
|
+
```js
|
|
749
|
+
InputBufferIO.createBar({
|
|
750
|
+
apiKey: 'ibw_YOUR_WIDGET_TOKEN',
|
|
751
|
+
target: { type: 'documentation', metadata: { page_url: window.location.href } },
|
|
752
|
+
submittedBy: currentUser.id, // attached to submitted feedback
|
|
753
|
+
userId: currentUser.id, // deduplicates thumb votes
|
|
754
|
+
});
|
|
755
|
+
```
|
|
648
756
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
757
|
+
`submittedBy` and `userId` do different jobs and can hold the same value. `submittedBy` records who wrote a piece of feedback; `userId` holds each person to one reaction per target for all time. Without `userId`, reactions fall back to one per target per IP address every 24 hours, which is best-effort — people behind a shared network can overwrite each other.
|
|
758
|
+
|
|
759
|
+
### Point the widget at a local API
|
|
652
760
|
|
|
653
|
-
You can use both together:
|
|
654
761
|
```js
|
|
655
|
-
InputBufferIO.
|
|
656
|
-
apiKey: '
|
|
657
|
-
|
|
658
|
-
target: { type: 'documentation', metadata: { page_slug: 'getting-started' } },
|
|
762
|
+
InputBufferIO.createModal({
|
|
763
|
+
apiKey: 'ibw_YOUR_WIDGET_TOKEN',
|
|
764
|
+
apiUrl: 'http://localhost:8080',
|
|
659
765
|
});
|
|
766
|
+
```
|
|
660
767
|
|
|
768
|
+
`apiUrl` is a base origin, not an endpoint. The widget appends `/api/v0/feedback` and `/api/v0/reactions` itself.
|
|
661
769
|
|
|
662
770
|
---
|
|
663
771
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { OpenOptions, TargetSpec } from './types.js';
|
|
1
|
+
import type { OpenOptions, ReactionResult, TargetSpec } from './types.js';
|
|
2
2
|
export declare const WIDGET_VERSION: string;
|
|
3
|
-
export declare function submitFeedback(apiKey: string, description: string,
|
|
3
|
+
export declare function submitFeedback(apiKey: string, description: string, title: string | null, options?: OpenOptions, apiUrl?: string): Promise<{
|
|
4
4
|
id: string;
|
|
5
5
|
}>;
|
|
6
|
-
export declare function submitReaction(apiKey: string, reactionValue: 1 | -1, target: TargetSpec, userId?: string | null, apiUrl?: string): Promise<
|
|
6
|
+
export declare function submitReaction(apiKey: string, reactionValue: 1 | -1, target: TargetSpec, userId?: string | null, apiUrl?: string): Promise<ReactionResult>;
|
package/dist/attrs.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FeedbackBarConfig, TargetSpec, WidgetConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a target from `target-type` plus the metadata attributes for that type. The API
|
|
4
|
+
* requires a target's identifying fields, so a target missing one is dropped with a warning
|
|
5
|
+
* rather than sent for the server to reject.
|
|
6
|
+
*/
|
|
7
|
+
export declare function targetFromAttributes(el: Element): TargetSpec | undefined;
|
|
8
|
+
/** Reads a complete bar config off an `<inputbuffer-feedback>` element. */
|
|
9
|
+
export declare function barConfigFromElement(el: Element, apiKey: string): FeedbackBarConfig;
|
|
10
|
+
/** True unless `inject-styles="false"` — absent means inject. */
|
|
11
|
+
export declare function shouldInjectStyles(el: Element): boolean;
|
|
12
|
+
/** Reads a modal config off the `data-*` attributes of the loading `<script>` tag. */
|
|
13
|
+
export declare function modalConfigFromScript(script: HTMLScriptElement, apiKey: string): WidgetConfig;
|
package/dist/bar-entry.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ declare const InputBufferIO: {
|
|
|
5
5
|
version: string;
|
|
6
6
|
};
|
|
7
7
|
export type { FeedbackBarConfig, FeedbackBarInstance };
|
|
8
|
+
export type { TargetSpec, RestEndpointTarget, DocumentationTarget, CliCommandTarget, TargetRef, ReactionResult, ProblemDetails, ProblemType, } from './types.js';
|
|
9
|
+
export { ApiError } from './types.js';
|
|
8
10
|
export { InputBufferIO, createBar };
|
package/dist/bar.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.ib-bar-wrapper{--ib-primary: #6366f1;--ib-primary-hover: #4338ca;--ib-background: #f6f6f8;--ib-surface: #ffffff;--ib-text: #111827;--ib-muted: #8d99ae;--ib-border: var(--ib-primary);--ib-focus-color: var(--ib-primary);--ib-input-hover-border: var(--ib-border);--ib-selected: var(--ib-primary);--ib-selected-color: var(--ib-surface);--ib-radius: 2px;--ib-radius-input: 2px;position:relative;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.ib-bar-wrapper--fixed{position:fixed;bottom:32px;right:32px;z-index:9998}.ib-bar{display:inline-flex;align-items:stretch;height:40px;width:100%;overflow:hidden;background-color:var(--ib-background);border:1px solid var(--ib-border);border-radius:2px;box-shadow:0 1px 4px #00000014;box-sizing:border-box}.ib-bar-popover{display:none;position:fixed;width:320px;border:1px solid var(--ib-border);border-radius:var(--ib-radius);box-shadow:0 1px 3px #0000001f;box-sizing:border-box;z-index:9999;overflow:hidden}.ib-bar-popover--visible{display:block}.ib-bar-header{background:var(--ib-surface);padding:12px 16px;border-bottom:1px solid var(--ib-border)}.ib-bar-body{background:var(--ib-background);padding:16px}.ib-bar-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--ib-text);margin:0}.ib-bar-textarea{width:100%;background:var(--ib-background);border:1px solid var(--ib-border);border-radius:var(--ib-radius-input);padding:10px 12px;font-size:14px;font-family:inherit;color:var(--ib-text);resize:vertical;min-height:80px;box-sizing:border-box;outline:none;display:block}.ib-bar-textarea:hover{border-color:var(--ib-input-hover-border)}.ib-bar-textarea:focus{border-color:var(--ib-focus-color);box-shadow:0 0 0 2px color-mix(in srgb,var(--ib-focus-color) 15%,transparent)}.ib-bar-wrapper .ib-bar-body .ib-bar-title-input
|
|
1
|
+
.ib-bar-wrapper{--ib-primary: #6366f1;--ib-primary-hover: #4338ca;--ib-background: #f6f6f8;--ib-surface: #ffffff;--ib-text: #111827;--ib-muted: #8d99ae;--ib-border: var(--ib-primary);--ib-focus-color: var(--ib-primary);--ib-input-hover-border: var(--ib-border);--ib-selected: var(--ib-primary);--ib-selected-color: var(--ib-surface);--ib-radius: 2px;--ib-radius-input: 2px;position:relative;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.ib-bar-wrapper--fixed{position:fixed;bottom:32px;right:32px;z-index:9998}.ib-bar{display:inline-flex;align-items:stretch;height:40px;width:100%;overflow:hidden;background-color:var(--ib-background);border:1px solid var(--ib-border);border-radius:2px;box-shadow:0 1px 4px #00000014;box-sizing:border-box}.ib-bar-popover{display:none;position:fixed;width:320px;border:1px solid var(--ib-border);border-radius:var(--ib-radius);box-shadow:0 1px 3px #0000001f;box-sizing:border-box;z-index:9999;overflow:hidden}.ib-bar-popover--visible{display:block}.ib-bar-header{background:var(--ib-surface);padding:12px 16px;border-bottom:1px solid var(--ib-border)}.ib-bar-body{background:var(--ib-background);padding:16px}.ib-bar-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--ib-text);margin:0}.ib-bar-textarea{width:100%;background:var(--ib-background);border:1px solid var(--ib-border);border-radius:var(--ib-radius-input);padding:10px 12px;font-size:14px;font-family:inherit;color:var(--ib-text);resize:vertical;min-height:80px;box-sizing:border-box;outline:none;display:block}.ib-bar-textarea:hover{border-color:var(--ib-input-hover-border)}.ib-bar-textarea:focus{border-color:var(--ib-focus-color);box-shadow:0 0 0 2px color-mix(in srgb,var(--ib-focus-color) 15%,transparent)}.ib-bar-wrapper .ib-bar-body .ib-bar-title-input{width:100%;background:var(--ib-background);border:1px solid var(--ib-border);border-radius:var(--ib-radius-input);padding:8px 12px;font-size:14px;font-family:inherit;color:var(--ib-text);box-sizing:border-box;outline:none;display:block}.ib-bar-title-input{margin-bottom:8px}.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:hover{border-color:var(--ib-input-hover-border)}.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:focus{border-color:var(--ib-focus-color);box-shadow:0 0 0 2px color-mix(in srgb,var(--ib-focus-color) 15%,transparent)}.ib-bar-submit{background:var(--ib-primary);color:#fff;border:none;border-radius:var(--ib-radius-input);padding:8px 16px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;font-family:inherit;cursor:pointer;display:inline-block;transition:background .15s}.ib-bar-submit:hover{background:var(--ib-primary-hover)}.ib-bar-submit:disabled{opacity:.6;cursor:not-allowed}.ib-bar-footer{display:flex;align-items:center;justify-content:space-between;margin-top:10px}.ib-branding a{font-size:10px;color:var(--ib-muted);text-decoration:none;letter-spacing:.03em}.ib-branding a:hover{color:var(--ib-primary);text-decoration:underline}.ib-bar-error,.ib-bar-success{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;margin:8px 0 0}.ib-bar-error:empty,.ib-bar-success:empty{display:none}.ib-bar-error{color:#dc2626}.ib-bar-success{color:#16a34a}.ib-bar-label-area{flex:1;display:flex;align-items:center;padding:0 16px}.ib-bar-label{font-size:11px;font-weight:600;color:var(--ib-muted);text-transform:uppercase;letter-spacing:.1em;white-space:nowrap;user-select:none}.ib-bar-actions{display:flex;align-items:stretch;background:var(--ib-background);border-left:1px solid var(--ib-border)}.ib-bar-actions--no-label{border-left:none}.ib-bar-btn{width:40px;display:flex;align-items:center;justify-content:center;background:var(--ib-background);border:none;border-right:1px solid var(--ib-border);border-radius:0;color:var(--ib-primary);cursor:pointer;transition:background .15s,color .15s,transform .1s;padding:0;-webkit-appearance:none;appearance:none;box-sizing:border-box;margin:0}.ib-bar-btn--down{border-right:none}.ib-bar-btn:hover{background:var(--ib-primary);color:var(--ib-surface);transform:scale(1.15)}.ib-bar-btn:active{transform:scale(.95)}.ib-bar-btn:focus{outline:1px solid var(--ib-primary);outline-offset:-1px}.ib-bar-btn--active{background:var(--ib-selected);color:var(--ib-selected-color)}.ib-bar-btn--active:hover{background:var(--ib-primary-hover, var(--ib-primary));color:var(--ib-surface);transform:scale(1.1)}@media(prefers-color-scheme:dark){.ib-bar-wrapper:not(.ib-theme-light),.ib-bar-wrapper.ib-theme-dark{--ib-primary: #3A5244;--ib-primary-hover: #4E6857;--ib-background: #25272B;--ib-surface: #2C3630;--ib-text: #E5E7EB;--ib-muted: #9CA3AF;--ib-border: #363840;--ib-focus-color: #7B9B82;--ib-input-hover-border: rgba(255, 255, 255, .2);--ib-selected: #7B9B82;--ib-selected-color: #1a2420;--ib-radius: 8px;--ib-radius-input: 4px}}.ib-bar-wrapper.ib-theme-dark{--ib-primary: #3A5244;--ib-primary-hover: #4E6857;--ib-background: #25272B;--ib-surface: #2C3630;--ib-text: #E5E7EB;--ib-muted: #9CA3AF;--ib-border: #363840;--ib-focus-color: #7B9B82;--ib-input-hover-border: rgba(255, 255, 255, .2);--ib-selected: #7B9B82;--ib-selected-color: #1a2420;--ib-radius: 8px;--ib-radius-input: 4px}
|
package/dist/bar.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var C=class extends Error{constructor(i){super(i.detail),this.name="ApiError",this.type=i.type,this.status=i.status,this.detail=i.detail,this.category=i.category,this.field=i.field}};var N="0.2.0",ee="https://inputbuffer.io/api/v0/inputs";async function W(t,i,s,g,d,h){var p,u,v,n;let l={description:i};if(g&&(l.title=g),s&&(l.contact_email=s),d!=null&&d.source&&(l.source=d.source),d!=null&&d.target){let o=d.target;l.targets=[{type:o.type,metadata:o.metadata}]}let a=new AbortController,x=setTimeout(()=>a.abort(),1e4),r;try{r=await fetch(h!=null?h:ee,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json","X-IB-Client":`inputbuffer-widget/${N} (javascript)`},body:JSON.stringify(l),credentials:"omit",signal:a.signal})}finally{clearTimeout(x)}if(!r.ok){let o=await r.json().catch(()=>({}));throw new C({type:(p=o.type)!=null?p:"https://inputbuffer.io/problems/internal-error",title:(u=o.title)!=null?u:"Error",detail:(v=o.detail)!=null?v:"Submission failed. Please try again.",status:(n=o.status)!=null?n:r.status,category:o.category,field:o.field})}let{data:c}=await r.json();return{id:c.id}}var te="https://inputbuffer.io/api/v0/reactions";async function U(t,i,s,g,d){var c,p,u,v;let h=d?d.replace(/\/inputs$/,"/reactions"):te,l={reaction_value:i,target:{type:s.type,metadata:s.metadata}};g&&(l.user_id=g);let a=new AbortController,x=setTimeout(()=>a.abort(),1e4),r;try{r=await fetch(h,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json","X-IB-Client":`inputbuffer-widget/${N} (javascript)`},body:JSON.stringify(l),credentials:"omit",signal:a.signal})}finally{clearTimeout(x)}if(!r.ok){let n=await r.json().catch(()=>({}));throw new C({type:(c=n.type)!=null?c:"https://inputbuffer.io/problems/internal-error",title:(p=n.title)!=null?p:"Error",detail:(u=n.detail)!=null?u:"Submission failed. Please try again.",status:(v=n.status)!=null?v:r.status,category:n.category,field:n.field})}}function V(t){let i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("viewBox","0 0 24 24"),i.setAttribute("width","18"),i.setAttribute("height","18"),i.setAttribute("fill","currentColor"),i.setAttribute("aria-hidden","true"),i.style.display="block";let s=document.createElementNS("http://www.w3.org/2000/svg","path");return s.setAttribute("d",t),i.appendChild(s),i}var re="M9 21h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2zM9 9l4.34-4.34L12 10h9v2l-3 7H9V9zM1 9h2v12H1z",ie="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L10.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm0 12l-4.34 4.34L12 14H3v-2l3-7h9v10zm4-12h2v12h-2z",ae=24*60*60*1e3;function O(t){var $,K;let i;function s(){let e=t.target;return e?`ib:reaction:${t.apiKey}:${e.type}:${JSON.stringify(e.metadata)}`:`ib:reaction:${t.apiKey}:${window.location.pathname}`}function g(){try{let e=localStorage.getItem(s());if(!e)return null;let{sentiment:f,ts:m}=JSON.parse(e);return Date.now()-m>ae?(localStorage.removeItem(s()),null):f}catch{return null}}function d(e){try{localStorage.setItem(s(),JSON.stringify({sentiment:e,ts:Date.now()}))}catch{}}let h={vote:[],open:[],submit:[],close:[],error:[]};function l(e,f){var m;(m=h[e])==null||m.forEach(B=>B(f))}let a=document.createElement("div"),x=t.colorScheme==="dark"?" ib-theme-dark":t.colorScheme==="light"?" ib-theme-light":"";a.className="ib-bar-wrapper"+(t.placement==="fixed"?" ib-bar-wrapper--fixed":"")+x;let{theme:r={}}=t;r.primary&&a.style.setProperty("--ib-primary",r.primary),r.background&&a.style.setProperty("--ib-background",r.background),r.surface&&a.style.setProperty("--ib-surface",r.surface),r.text&&a.style.setProperty("--ib-text",r.text),r.selected&&a.style.setProperty("--ib-selected",r.selected),r.selectedColor&&a.style.setProperty("--ib-selected-color",r.selectedColor);let c=document.createElement("div");c.className="ib-bar";let p=document.createElement("div");p.className="ib-bar-label-area";let u=document.createElement("span");u.className="ib-bar-label",u.textContent=($=t.label)!=null?$:"Was this helpful?",p.appendChild(u);let v=document.createElement("div");v.className="ib-bar-actions"+(t.showLabel===!1?" ib-bar-actions--no-label":"");let n=document.createElement("button");n.type="button",n.className="ib-bar-btn ib-bar-btn--up",n.setAttribute("aria-label","Yes"),n.appendChild(V(re));let o=document.createElement("button");o.type="button",o.className="ib-bar-btn ib-bar-btn--down",o.setAttribute("aria-label","No"),o.appendChild(V(ie)),v.appendChild(n),v.appendChild(o),t.showLabel!==!1&&c.appendChild(p),c.appendChild(v);let b=document.createElement("div");b.className="ib-bar-popover",b.setAttribute("role","dialog"),b.setAttribute("aria-modal","false");let F=document.createElement("div");if(F.className="ib-bar-header",t.modalTitle){b.setAttribute("aria-labelledby","ib-bar-title");let e=document.createElement("p");e.id="ib-bar-title",e.className="ib-bar-title",e.textContent=t.modalTitle,F.appendChild(e)}else b.setAttribute("aria-label","Feedback");let w=document.createElement("div");w.className="ib-bar-body";let k=document.createElement("textarea");k.className="ib-bar-textarea",k.placeholder=(K=t.modalPlaceholder)!=null?K:"What's on your mind?",k.setAttribute("aria-label","Feedback");let S=document.createElement("p");S.className="ib-bar-error";let I=document.createElement("p");I.className="ib-bar-success";let y=document.createElement("button");if(y.type="button",y.className="ib-bar-submit",y.textContent="Send feedback",t.showTitleField===!0){let e=document.createElement("input");e.type="text",e.className="ib-bar-title-input",e.placeholder="Title (optional)",e.setAttribute("aria-label","Feedback title"),w.appendChild(e)}if(w.appendChild(k),t.showEmailField===!0){let e=document.createElement("input");e.type="email",e.className="ib-bar-email",e.placeholder="Your email (optional)",e.setAttribute("aria-label","Email address"),w.appendChild(e)}w.appendChild(S),w.appendChild(I);let L=document.createElement("div");L.className="ib-bar-footer",L.appendChild(y);let P=document.createElement("div");P.className="ib-branding";let T=document.createElement("a");T.href="https://inputbuffer.io",T.target="_blank",T.rel="noopener noreferrer",T.textContent="Powered by inputbuffer.io",P.appendChild(T),L.appendChild(P),w.appendChild(L),b.appendChild(F),b.appendChild(w),a.appendChild(b),a.appendChild(c);function A(){let e=c.getBoundingClientRect();b.style.top=`${e.top-b.offsetHeight-8}px`,b.style.left=`${e.right-320}px`}function q(e){i=e,b.classList.add("ib-bar-popover--visible"),requestAnimationFrame(A),S.textContent="",I.textContent="",k.focus(),setTimeout(()=>document.addEventListener("click",j),0),document.addEventListener("keydown",D),window.addEventListener("scroll",A,{passive:!0}),window.addEventListener("resize",A,{passive:!0}),l("open",{sentiment:e})}function _(){b.classList.contains("ib-bar-popover--visible")&&(b.classList.remove("ib-bar-popover--visible"),document.removeEventListener("click",j),document.removeEventListener("keydown",D),window.removeEventListener("scroll",A),window.removeEventListener("resize",A),k.value="",l("close"))}function X(){n.classList.remove("ib-bar-btn--active"),o.classList.remove("ib-bar-btn--active"),i=void 0}function j(e){a.contains(e.target)||_()}function D(e){e.key==="Escape"&&_()}async function Y(){let e=k.value.trim();if(e.length<10){S.textContent="Please enter at least 10 characters.";return}S.textContent="",y.disabled=!0,y.textContent="Sending\u2026";let f=b.querySelector(".ib-bar-title-input"),m=(f==null?void 0:f.value.trim())||null,B=b.querySelector(".ib-bar-email"),Q=(B==null?void 0:B.value.trim())||null;try{let E=await W(t.apiKey,e,Q,m,{sentiment:i,target:t.target,source:t.source},t.apiUrl);l("submit",E),I.textContent="Thanks for your feedback!",y.textContent="Send feedback",setTimeout(()=>{_(),X()},2e3)}catch(E){l("error",E instanceof Error?E:new Error("Something went wrong."));let Z=E instanceof C&&E.category==="user"?E.detail:"Something went wrong. Please try again.";S.textContent=Z,y.disabled=!1,y.textContent="Send feedback"}}function M(e){n.classList.remove("ib-bar-btn--active"),o.classList.remove("ib-bar-btn--active"),e.classList.add("ib-bar-btn--active")}let z=g();z&&(i=z,M(z==="positive"?n:o));function H(e,f){var m;d(e),M(f),q(e),l("vote",{sentiment:e}),t.target&&U(t.apiKey,e==="positive"?1:-1,t.target,(m=t.userId)!=null?m:null,t.apiUrl).catch(()=>{})}return n.addEventListener("click",()=>H("positive",n)),o.addEventListener("click",()=>H("negative",o)),y.addEventListener("click",Y),{element:a,on(e,f){var m;(m=h[e])==null||m.push(f)},destroy(){_(),a.remove()}}}var G=`/* InputBuffer Feedback Bar Styles
|
|
1
|
+
var x=class extends Error{constructor(t){super(t.detail),this.name="ApiError",this.type=t.type,this.title=t.title,this.status=t.status,this.detail=t.detail,this.category=t.category,this.field=t.field}};var K="Something went wrong. Please try again.";function V(e){return e instanceof x?e.category==="user"?e.detail:(e.category==="integration"&&w(`${e.title}: ${e.detail}`,e.type),K):K}function w(e,t){typeof console!="undefined"&&console.warn(`[inputbuffer] ${e}`,t!=null?t:"")}var z="0.3.1",ae="https://inputbuffer.io",se="/api/v0/feedback",de="/api/v0/reactions",ce=1e4,le=5e3,be=500,pe=300;function q(e,t){return`${(e!=null?e:ae).replace(/\/+$/,"")}${t}`}async function G(e,t,i){var b,a,p,d;let n=new AbortController,l=setTimeout(()=>n.abort(),ce),s;try{s=await fetch(e,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json","X-IB-Client":`inputbuffer-widget/${z} (javascript)`},body:JSON.stringify(i),credentials:"omit",signal:n.signal})}finally{clearTimeout(l)}if(!s.ok){let u=await s.json().catch(()=>({}));throw new x({type:(b=u.type)!=null?b:"https://inputbuffer.io/docs/api/problems/internal-error",title:(a=u.title)!=null?a:"Error",detail:(p=u.detail)!=null?p:"Submission failed. Please try again.",status:(d=u.status)!=null?d:s.status,category:u.category,field:u.field})}return s}async function J(e,t,i,n,l){let s={description:t.slice(0,le)};if(i&&(s.title=i.slice(0,be)),n!=null&&n.submittedBy&&(s.submitted_by=n.submittedBy.slice(0,pe)),n!=null&&n.target){let p=n.target;s.targets=[{type:p.type,metadata:p.metadata}]}let b=await G(q(l,se),e,s),{data:a}=await b.json();return{id:a.id}}async function X(e,t,i,n,l){let s={reaction_value:t,target:{type:i.type,metadata:i.metadata}};n&&(s.user_id=n);let b;try{b=await G(q(l,de),e,s)}catch(p){throw p instanceof x&&p.status===422&&p.field==="user_id"&&w("Reaction rejected: no user_id and no client IP. Set the `userId` option (or the `user-id` attribute)."),p}let{data:a}=await b.json();return a}function Y(e){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("width","18"),t.setAttribute("height","18"),t.setAttribute("fill","currentColor"),t.setAttribute("aria-hidden","true"),t.style.display="block";let i=document.createElementNS("http://www.w3.org/2000/svg","path");return i.setAttribute("d",e),t.appendChild(i),t}var ue="M9 21h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2zM9 9l4.34-4.34L12 10h9v2l-3 7H9V9zM1 9h2v12H1z",me="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L10.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm0 12l-4.34 4.34L12 14H3v-2l3-7h9v10zm4-12h2v12h-2z",fe=1440*60*1e3;function O(e){var $,W;let t;function i(){let r=e.target;return r?`ib:reaction:${e.apiKey}:${r.type}:${JSON.stringify(r.metadata)}`:`ib:reaction:${e.apiKey}:${window.location.pathname}`}function n(){try{let r=localStorage.getItem(i());if(!r)return null;let{sentiment:f,ts:m}=JSON.parse(r);return Date.now()-m>fe?(localStorage.removeItem(i()),null):f}catch{return null}}function l(r){try{localStorage.setItem(i(),JSON.stringify({sentiment:r,ts:Date.now()}))}catch{}}let s={vote:[],open:[],submit:[],close:[],error:[]};function b(r,f){var m;(m=s[r])==null||m.forEach(E=>E(f))}let a=document.createElement("div"),p=e.colorScheme==="dark"?" ib-theme-dark":e.colorScheme==="light"?" ib-theme-light":"";a.className="ib-bar-wrapper"+(e.placement==="fixed"?" ib-bar-wrapper--fixed":"")+p;let{theme:d={}}=e;d.primary&&a.style.setProperty("--ib-primary",d.primary),d.background&&a.style.setProperty("--ib-background",d.background),d.surface&&a.style.setProperty("--ib-surface",d.surface),d.text&&a.style.setProperty("--ib-text",d.text),d.selected&&a.style.setProperty("--ib-selected",d.selected),d.selectedColor&&a.style.setProperty("--ib-selected-color",d.selectedColor);let u=document.createElement("div");u.className="ib-bar";let F=document.createElement("div");F.className="ib-bar-label-area";let L=document.createElement("span");L.className="ib-bar-label",L.textContent=($=e.label)!=null?$:"Was this helpful?",F.appendChild(L);let B=document.createElement("div");B.className="ib-bar-actions"+(e.showLabel===!1?" ib-bar-actions--no-label":"");let g=document.createElement("button");g.type="button",g.className="ib-bar-btn ib-bar-btn--up",g.setAttribute("aria-label","Yes"),g.appendChild(Y(ue));let h=document.createElement("button");h.type="button",h.className="ib-bar-btn ib-bar-btn--down",h.setAttribute("aria-label","No"),h.appendChild(Y(me)),B.appendChild(g),B.appendChild(h),e.showLabel!==!1&&u.appendChild(F),u.appendChild(B);let c=document.createElement("div");c.className="ib-bar-popover",c.setAttribute("role","dialog"),c.setAttribute("aria-modal","false");let P=document.createElement("div");if(P.className="ib-bar-header",e.modalTitle){c.setAttribute("aria-labelledby","ib-bar-title");let r=document.createElement("p");r.id="ib-bar-title",r.className="ib-bar-title",r.textContent=e.modalTitle,P.appendChild(r)}else c.setAttribute("aria-label","Feedback");let k=document.createElement("div");k.className="ib-bar-body";let y=document.createElement("textarea");y.className="ib-bar-textarea",y.placeholder=(W=e.modalPlaceholder)!=null?W:"What's on your mind?",y.maxLength=5e3,y.setAttribute("aria-label","Feedback");let C=document.createElement("p");C.className="ib-bar-error";let _=document.createElement("p");_.className="ib-bar-success";let v=document.createElement("button");if(v.type="button",v.className="ib-bar-submit",v.textContent="Send feedback",e.showTitleField===!0){let r=document.createElement("input");r.type="text",r.className="ib-bar-title-input",r.placeholder="Title (optional)",r.maxLength=500,r.setAttribute("aria-label","Feedback title"),k.appendChild(r)}k.appendChild(y),k.appendChild(C),k.appendChild(_);let A=document.createElement("div");A.className="ib-bar-footer",A.appendChild(v);let R=document.createElement("div");R.className="ib-branding";let T=document.createElement("a");T.href="https://inputbuffer.io",T.target="_blank",T.rel="noopener noreferrer",T.textContent="Powered by inputbuffer.io",R.appendChild(T),A.appendChild(R),k.appendChild(A),c.appendChild(P),c.appendChild(k),a.appendChild(c),a.appendChild(u);function S(){let r=u.getBoundingClientRect();c.style.top=`${r.top-c.offsetHeight-8}px`,c.style.left=`${r.right-320}px`}function ie(r){t=r,c.classList.add("ib-bar-popover--visible"),requestAnimationFrame(S),C.textContent="",_.textContent="",y.focus(),setTimeout(()=>document.addEventListener("click",M),0),document.addEventListener("keydown",D),window.addEventListener("scroll",S,{passive:!0}),window.addEventListener("resize",S,{passive:!0}),b("open",{sentiment:r})}function I(){c.classList.contains("ib-bar-popover--visible")&&(c.classList.remove("ib-bar-popover--visible"),document.removeEventListener("click",M),document.removeEventListener("keydown",D),window.removeEventListener("scroll",S),window.removeEventListener("resize",S),y.value="",b("close"))}function ne(){g.classList.remove("ib-bar-btn--active"),h.classList.remove("ib-bar-btn--active"),t=void 0}function M(r){a.contains(r.target)||I()}function D(r){r.key==="Escape"&&I()}async function oe(){let r=y.value.trim();if(r.length<10){C.textContent="Please enter at least 10 characters.";return}C.textContent="",v.disabled=!0,v.textContent="Sending\u2026";let f=c.querySelector(".ib-bar-title-input"),m=(f==null?void 0:f.value.trim())||null;try{let E=await J(e.apiKey,r,m,{sentiment:t,target:e.target,submittedBy:e.submittedBy},e.apiUrl);b("submit",E),_.textContent="Thanks for your feedback!",v.textContent="Send feedback",setTimeout(()=>{I(),ne()},2e3)}catch(E){b("error",E instanceof Error?E:new Error("Something went wrong.")),C.textContent=V(E),v.disabled=!1,v.textContent="Send feedback"}}function H(r){g.classList.remove("ib-bar-btn--active"),h.classList.remove("ib-bar-btn--active"),r.classList.add("ib-bar-btn--active")}let N=n();N&&(t=N,H(N==="positive"?g:h));function U(r,f){var m;l(r),H(f),ie(r),b("vote",{sentiment:r}),e.target&&X(e.apiKey,r==="positive"?1:-1,e.target,(m=e.userId)!=null?m:null,e.apiUrl).catch(()=>{})}return g.addEventListener("click",()=>U("positive",g)),h.addEventListener("click",()=>U("negative",h)),v.addEventListener("click",oe),{element:a,on(r,f){var m;(m=s[r])==null||m.push(f)},destroy(){I(),a.remove()}}}function o(e,t){var i;return(i=e.getAttribute(t))!=null?i:void 0}function Q(e,t){let i=e.getAttribute(t);return i===null?void 0:i==="true"}function ge(e){return{primary:o(e,"theme-primary"),background:o(e,"theme-background"),text:o(e,"theme-text"),selected:o(e,"theme-selected"),selectedColor:o(e,"theme-selected-color")}}function he(e){var i;let t=e.getAttribute("target-type");if(t)switch(t){case"documentation":{let n=(i=o(e,"target-page-url"))!=null?i:typeof window!="undefined"?window.location.href:void 0;if(!n){w('target-type="documentation" needs target-page-url. Ignoring the target.');return}return{type:"documentation",metadata:{page_url:n,section_heading:o(e,"target-section-heading"),doc_version:o(e,"target-doc-version")}}}case"rest_endpoint":{let n=o(e,"target-method"),l=o(e,"target-path");if(!n||!l){w('target-type="rest_endpoint" needs target-method and target-path. Ignoring the target.');return}return{type:"rest_endpoint",metadata:{method:n,path:l,host:o(e,"target-host"),api_version:o(e,"target-api-version")}}}case"cli_command":{let n=o(e,"target-command");if(!n){w('target-type="cli_command" needs target-command. Ignoring the target.');return}let l=o(e,"target-args");return{type:"cli_command",metadata:{command:n,subcommand:o(e,"target-subcommand"),cli_version:o(e,"target-cli-version"),args:l?l.split(",").map(s=>s.trim()).filter(Boolean):void 0}}}default:w(`Unknown target-type "${t}". Expected documentation, rest_endpoint, or cli_command.`);return}}function Z(e,t){return{apiKey:t,apiUrl:o(e,"api-url"),label:o(e,"label"),showLabel:Q(e,"show-label"),placement:e.getAttribute("placement")==="fixed"?"fixed":"inline",colorScheme:o(e,"color-scheme"),modalTitle:o(e,"modal-title"),modalPlaceholder:o(e,"modal-placeholder"),showTitleField:Q(e,"show-title-field"),submittedBy:o(e,"submitted-by"),userId:o(e,"user-id"),target:he(e),theme:ge(e)}}function ee(e){return e.getAttribute("inject-styles")!=="false"}var te=`/* InputBuffer Feedback Bar Styles
|
|
2
2
|
* .ib-bar-* and .ib-bar-popover* classes are stable public API \u2014 do not rename them.
|
|
3
3
|
* Developers may target these classes from their own stylesheets to customize the widget.
|
|
4
4
|
*/
|
|
@@ -106,8 +106,7 @@ var C=class extends Error{constructor(i){super(i.detail),this.name="ApiError",th
|
|
|
106
106
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ib-focus-color) 15%, transparent);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
.ib-bar-wrapper .ib-bar-body .ib-bar-title-input
|
|
110
|
-
.ib-bar-wrapper .ib-bar-body .ib-bar-email {
|
|
109
|
+
.ib-bar-wrapper .ib-bar-body .ib-bar-title-input {
|
|
111
110
|
width: 100%;
|
|
112
111
|
background: var(--ib-background);
|
|
113
112
|
border: 1px solid var(--ib-border);
|
|
@@ -125,17 +124,11 @@ var C=class extends Error{constructor(i){super(i.detail),this.name="ApiError",th
|
|
|
125
124
|
margin-bottom: 8px;
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
.ib-bar-
|
|
129
|
-
margin-top: 8px;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:hover,
|
|
133
|
-
.ib-bar-wrapper .ib-bar-body .ib-bar-email:hover {
|
|
127
|
+
.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:hover {
|
|
134
128
|
border-color: var(--ib-input-hover-border);
|
|
135
129
|
}
|
|
136
130
|
|
|
137
|
-
.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:focus
|
|
138
|
-
.ib-bar-wrapper .ib-bar-body .ib-bar-email:focus {
|
|
131
|
+
.ib-bar-wrapper .ib-bar-body .ib-bar-title-input:focus {
|
|
139
132
|
border-color: var(--ib-focus-color);
|
|
140
133
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ib-focus-color) 15%, transparent);
|
|
141
134
|
}
|
|
@@ -319,4 +312,4 @@ var C=class extends Error{constructor(i){super(i.detail),this.name="ApiError",th
|
|
|
319
312
|
--ib-radius: 8px;
|
|
320
313
|
--ib-radius-input: 4px;
|
|
321
314
|
}
|
|
322
|
-
`;function
|
|
315
|
+
`;function re(){if(typeof document=="undefined"||document.getElementById("ib-bar-styles"))return;let e=document.createElement("style");e.id="ib-bar-styles",e.textContent=te,document.head.appendChild(e)}function ye(e){return e.injectStyles!==!1&&re(),O(e)}var j=class extends HTMLElement{constructor(){super(...arguments);this._bar=null}connectedCallback(){let i=this.getAttribute("api-key");i&&(ee(this)&&re(),this._bar=O(Z(this,i)),this.appendChild(this._bar.element))}disconnectedCallback(){var i;(i=this._bar)==null||i.destroy(),this._bar=null}};typeof customElements!="undefined"&&!customElements.get("inputbuffer-feedback")&&customElements.define("inputbuffer-feedback",j);var xe={createBar:ye,version:z};typeof window!="undefined"&&(window.InputBufferIO=xe);export{x as ApiError,xe as InputBufferIO,ye as createBar};
|