@inputbuffer/feedback 0.1.1 → 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/README.md +192 -56
- package/dist/api.d.ts +3 -2
- 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 +71 -22
- package/dist/widget.esm.js +9 -27
- package/dist/widget.js +9 -27
- package/package.json +1 -1
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. |
|
|
@@ -236,8 +260,26 @@ Supported attributes:
|
|
|
236
260
|
| `theme-selected` | Background color of the selected thumb. |
|
|
237
261
|
| `theme-selected-color` | Icon color of the selected thumb. |
|
|
238
262
|
| `inject-styles` | Set to `"false"` to skip automatic style injection. |
|
|
263
|
+
| `color-scheme` | `"light"`, `"dark"`, or `"auto"`. |
|
|
264
|
+
| `show-label` | `"true"` to show the label, `"false"` to hide it. |
|
|
265
|
+
| `modal-title` | Title shown above the feedback textarea. |
|
|
266
|
+
| `modal-placeholder` | Placeholder text for the feedback textarea. |
|
|
267
|
+
| `show-title-field` | `"true"` to show an optional title input. |
|
|
268
|
+
| `submitted-by` | Opaque identifier for whoever is submitting, e.g. your own user id. Sent as `submitted_by`. |
|
|
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
|
+
```
|
|
239
281
|
|
|
240
|
-
|
|
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.
|
|
241
283
|
|
|
242
284
|
### `InputBufferIO.createBar(config)`
|
|
243
285
|
|
|
@@ -253,8 +295,8 @@ document.getElementById('my-slot').appendChild(bar.element);
|
|
|
253
295
|
|
|
254
296
|
| Property | Type | Default | Description |
|
|
255
297
|
|---|---|---|---|
|
|
256
|
-
| `apiKey` | string | — | **Required.** Your widget
|
|
257
|
-
| `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. |
|
|
258
300
|
| `label` | string | — | Text shown next to the thumbs. |
|
|
259
301
|
| `showLabel` | boolean | `true` | Set to `false` to show thumbs only. |
|
|
260
302
|
| `placement` | `'inline'` \| `'fixed'` | `'inline'` | `fixed` pins the bar to the bottom of the viewport. |
|
|
@@ -265,16 +307,13 @@ document.getElementById('my-slot').appendChild(bar.element);
|
|
|
265
307
|
| `theme.text` | string | — | Text color. |
|
|
266
308
|
| `theme.selected` | string | — | Background color of the selected thumb. |
|
|
267
309
|
| `theme.selectedColor` | string | — | Icon color of the selected thumb. |
|
|
268
|
-
| `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | — | The kind of thing the user is giving feedback on.
|
|
269
|
-
| `target.
|
|
270
|
-
| `target.displayName` | string | — | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
|
|
271
|
-
| `target.dedupKey` | string | — | Custom deduplication key (max 500 chars). |
|
|
272
|
-
| `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). |
|
|
273
312
|
| `modalTitle` | string | — | Heading for the follow-up popover. |
|
|
274
313
|
| `modalPlaceholder` | string | — | Textarea placeholder for the follow-up popover. |
|
|
275
|
-
| `showEmailField` | boolean | `false` | Show/hide the email field in the follow-up popover. |
|
|
276
314
|
| `showTitleField` | boolean | `false` | Show/hide the title field in the follow-up popover. |
|
|
277
|
-
| `
|
|
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). |
|
|
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. |
|
|
278
317
|
| `injectStyles` | boolean | `true` | Set to `false` to skip automatic style injection. |
|
|
279
318
|
|
|
280
319
|
### `bar.on(event, handler)`
|
|
@@ -291,7 +330,7 @@ bar.on('error', (err) => console.error('Submission failed:', err));
|
|
|
291
330
|
|
|
292
331
|
| Event | Handler signature | When it fires |
|
|
293
332
|
|---|---|---|
|
|
294
|
-
| `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. |
|
|
295
334
|
| `open` | `({ sentiment: 'positive' \| 'negative' }) => void` | The follow-up popover opens. |
|
|
296
335
|
| `submit` | `({ id: string }) => void` | Feedback was submitted successfully. |
|
|
297
336
|
| `close` | `() => void` | The follow-up popover closes. |
|
|
@@ -313,11 +352,16 @@ When `data-api-key` is present on the script tag, the widget initializes automat
|
|
|
313
352
|
|
|
314
353
|
| Attribute | Type | Description |
|
|
315
354
|
|---|---|---|
|
|
316
|
-
| `data-api-key` | string | **Required.** Your widget
|
|
317
|
-
| `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). |
|
|
318
357
|
| `data-attach-to` | string | CSS selector for the element that opens the modal on click. |
|
|
319
358
|
| `data-inject-styles` | boolean | Set to `"false"` to skip automatic style injection. |
|
|
320
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`. |
|
|
321
365
|
| `data-theme-primary` | string | Primary color (buttons, focus rings). Any CSS color value. |
|
|
322
366
|
| `data-theme-background` | string | Modal background color. |
|
|
323
367
|
| `data-theme-text` | string | Modal text color. |
|
|
@@ -338,16 +382,15 @@ const ib = InputBufferIO.createModal({
|
|
|
338
382
|
|
|
339
383
|
| Property | Type | Default | Description |
|
|
340
384
|
|---|---|---|---|
|
|
341
|
-
| `apiKey` | string | — | **Required.** Your widget
|
|
342
|
-
| `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. |
|
|
343
387
|
| `attachTo` | string | — | CSS selector. Clicking the matched element calls `open()`. |
|
|
344
388
|
| `injectStyles` | boolean | `true` | Set to `false` to skip automatic style injection. |
|
|
345
389
|
| `title` | string | — | Modal heading. Omit to render no title. |
|
|
346
390
|
| `placeholder` | string | `"What's on your mind?"` | Textarea placeholder text. |
|
|
347
|
-
| `showEmailField` | boolean | `false` | Set to `true` to show an optional email field. |
|
|
348
391
|
| `showTitleField` | boolean | `false` | Set to `true` to show an optional title field. |
|
|
349
392
|
| `showSentiment` | boolean | `false` | Show thumbs up/down sentiment buttons in the modal. |
|
|
350
|
-
| `
|
|
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). |
|
|
351
394
|
| `colorScheme` | `'light'` \| `'dark'` \| `'auto'` | `'auto'` | Force a color scheme or follow the system setting. |
|
|
352
395
|
| `theme.primary` | string | — | Primary color (buttons, focus rings). |
|
|
353
396
|
| `theme.background` | string | — | Modal background color. |
|
|
@@ -365,8 +408,6 @@ ib.open({
|
|
|
365
408
|
title: 'Was this helpful?',
|
|
366
409
|
target: {
|
|
367
410
|
type: 'documentation',
|
|
368
|
-
targetId: 'auth-overview',
|
|
369
|
-
displayName: 'Authentication overview',
|
|
370
411
|
metadata: {
|
|
371
412
|
page_url: window.location.href,
|
|
372
413
|
section_heading: 'Authentication',
|
|
@@ -381,15 +422,11 @@ ib.open({
|
|
|
381
422
|
| Property | Type | Description |
|
|
382
423
|
|---|---|---|
|
|
383
424
|
| `title` | string | Overrides the modal heading for this open call. |
|
|
384
|
-
| `sentiment` | `'positive'` \| `'negative'` | Pre-selects a sentiment thumb. Only relevant when `showSentiment` is enabled. |
|
|
385
|
-
| `target.type` | `'documentation'` \| `'rest_endpoint'` \| `'cli_command'` | The kind of thing the user is giving feedback on.
|
|
386
|
-
| `target.
|
|
387
|
-
| `target.displayName` | string | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
|
|
388
|
-
| `target.dedupKey` | string | Custom deduplication key (max 500 chars). |
|
|
389
|
-
| `target.metadata` | object | Type-specific fields — see [Target metadata schemas](#target-metadata-schemas). |
|
|
390
|
-
| `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). |
|
|
391
428
|
| `prefill.description` | string | Pre-populates the textarea. |
|
|
392
|
-
| `
|
|
429
|
+
| `submittedBy` | string | Overrides the `submittedBy` set in `createModal(config)` for this open call. |
|
|
393
430
|
|
|
394
431
|
### `instance.close()`
|
|
395
432
|
|
|
@@ -420,19 +457,26 @@ ib.on('error', (err) => console.error('Submission failed:', err));
|
|
|
420
457
|
The currently loaded widget version string.
|
|
421
458
|
|
|
422
459
|
```js
|
|
423
|
-
console.log(InputBufferIO.version); // e.g. "0.
|
|
460
|
+
console.log(InputBufferIO.version); // e.g. "0.3.0"
|
|
424
461
|
```
|
|
425
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
|
+
|
|
426
471
|
### Target metadata schemas
|
|
427
472
|
|
|
428
|
-
|
|
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`.
|
|
429
474
|
|
|
430
475
|
**`documentation`**
|
|
431
476
|
|
|
432
477
|
| Field | Required | Description |
|
|
433
478
|
|---|---|---|
|
|
434
|
-
| `page_url` |
|
|
435
|
-
| `page_slug` | No | Slug or path of the page. |
|
|
479
|
+
| `page_url` | **Yes** | URL of the documentation page. |
|
|
436
480
|
| `section_heading` | No | Heading of the section the user is viewing. |
|
|
437
481
|
| `doc_version` | No | Documentation version string. |
|
|
438
482
|
|
|
@@ -440,8 +484,8 @@ The fields accepted in `target.metadata` depend on `target.type`. The server val
|
|
|
440
484
|
|
|
441
485
|
| Field | Required | Description |
|
|
442
486
|
|---|---|---|
|
|
443
|
-
| `method` | Yes
|
|
444
|
-
| `path` | Yes
|
|
487
|
+
| `method` | **Yes** | HTTP method (`GET`, `POST`, etc.). |
|
|
488
|
+
| `path` | **Yes** | API path (e.g. `/users/{id}`). |
|
|
445
489
|
| `host` | No | Hostname (e.g. `api.example.com`). |
|
|
446
490
|
| `api_version` | No | API version string. |
|
|
447
491
|
|
|
@@ -449,11 +493,83 @@ The fields accepted in `target.metadata` depend on `target.type`. The server val
|
|
|
449
493
|
|
|
450
494
|
| Field | Required | Description |
|
|
451
495
|
|---|---|---|
|
|
452
|
-
| `command` | Yes
|
|
453
|
-
| `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`). |
|
|
454
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
|
|
502
|
+
|
|
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
|
+
```
|
|
455
525
|
|
|
456
|
-
|
|
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.
|
|
457
573
|
|
|
458
574
|
---
|
|
459
575
|
|
|
@@ -471,7 +587,7 @@ Each component uses stable selectors you can target directly in your stylesheet.
|
|
|
471
587
|
| `#ib-modal-body` | — | Body area |
|
|
472
588
|
| `#ib-title` | `.ib-modal-title` | Modal heading |
|
|
473
589
|
| `#ib-textarea` | `.ib-modal-textarea` | Feedback text field |
|
|
474
|
-
| `#ib-
|
|
590
|
+
| `#ib-title-input` | `.ib-modal-title-input` | Optional title input |
|
|
475
591
|
| `#ib-submit` | `.ib-modal-submit` | Submit button |
|
|
476
592
|
| `#ib-close` | `.ib-modal-close` | Close button |
|
|
477
593
|
| `#ib-success` | `.ib-modal-success` | Success message |
|
|
@@ -492,7 +608,7 @@ The IDs are the stable public API and will not change between releases. The `.ib
|
|
|
492
608
|
| `.ib-bar-body` | Popover body |
|
|
493
609
|
| `.ib-bar-title` | Popover heading |
|
|
494
610
|
| `.ib-bar-textarea` | Feedback text field |
|
|
495
|
-
| `.ib-bar-
|
|
611
|
+
| `.ib-bar-title-input` | Optional title input |
|
|
496
612
|
| `.ib-bar-submit` | Submit button |
|
|
497
613
|
| `.ib-bar-success` | Success message |
|
|
498
614
|
| `.ib-bar-error` | Error message |
|
|
@@ -530,8 +646,6 @@ document.getElementById('feedback-btn').addEventListener('click', () => {
|
|
|
530
646
|
title: 'Was this page helpful?',
|
|
531
647
|
target: {
|
|
532
648
|
type: 'documentation',
|
|
533
|
-
targetId: window.location.pathname,
|
|
534
|
-
displayName: document.title,
|
|
535
649
|
metadata: {
|
|
536
650
|
page_url: window.location.href,
|
|
537
651
|
section_heading: document.querySelector('h1')?.textContent ?? '',
|
|
@@ -550,8 +664,6 @@ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
|
|
|
550
664
|
ib.open({
|
|
551
665
|
target: {
|
|
552
666
|
type: 'rest_endpoint',
|
|
553
|
-
targetId: 'POST /v1/uploads',
|
|
554
|
-
displayName: 'Upload a file',
|
|
555
667
|
metadata: { method: 'POST', path: '/v1/uploads' },
|
|
556
668
|
},
|
|
557
669
|
});
|
|
@@ -566,8 +678,6 @@ const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
|
|
|
566
678
|
ib.open({
|
|
567
679
|
target: {
|
|
568
680
|
type: 'cli_command',
|
|
569
|
-
targetId: 'deploy',
|
|
570
|
-
displayName: 'my-cli deploy',
|
|
571
681
|
metadata: { command: 'deploy' },
|
|
572
682
|
},
|
|
573
683
|
});
|
|
@@ -631,6 +741,32 @@ document.getElementById('my-slot').appendChild(bar.element);
|
|
|
631
741
|
</script>
|
|
632
742
|
```
|
|
633
743
|
|
|
744
|
+
### Identify who submitted feedback
|
|
745
|
+
|
|
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.
|
|
747
|
+
|
|
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
|
+
```
|
|
756
|
+
|
|
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
|
|
760
|
+
|
|
761
|
+
```js
|
|
762
|
+
InputBufferIO.createModal({
|
|
763
|
+
apiKey: 'ibw_YOUR_WIDGET_TOKEN',
|
|
764
|
+
apiUrl: 'http://localhost:8080',
|
|
765
|
+
});
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
`apiUrl` is a base origin, not an endpoint. The widget appends `/api/v0/feedback` and `/api/v0/reactions` itself.
|
|
769
|
+
|
|
634
770
|
---
|
|
635
771
|
|
|
636
772
|
## License
|
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { OpenOptions } 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<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 S="0.1.1",U="https://inputbuffer.io/api/v0/inputs";async function M(t,o,b,s,r,h){var p;let a={description:o};if(s&&(a.title=s),b&&(a.contactEmail=b),r!=null&&r.sentiment&&(a.sentiment=r.sentiment),r!=null&&r.source&&(a.source=r.source),r!=null&&r.target){let i=r.target;a.targets=[{target_type:i.type,...i.targetId&&{target_id:i.targetId},...i.displayName&&{display_name:i.displayName},...i.dedupKey&&{dedup_key:i.dedupKey},metadata:i.metadata}]}let l=new AbortController,m=setTimeout(()=>l.abort(),1e4),c;try{c=await fetch(h!=null?h:U,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json","X-IB-Client":`inputbuffer-widget/${S} (javascript)`},body:JSON.stringify(a),credentials:"omit",signal:l.signal})}finally{clearTimeout(m)}if(!c.ok){let i=await c.json().catch(()=>({}));throw new Error(((p=i==null?void 0:i.error)==null?void 0:p.message)||"Submission failed. Please try again.")}return c.json()}function R(t){let o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.setAttribute("viewBox","0 0 24 24"),o.setAttribute("width","18"),o.setAttribute("height","18"),o.setAttribute("fill","currentColor"),o.setAttribute("aria-hidden","true"),o.style.display="block";let b=document.createElementNS("http://www.w3.org/2000/svg","path");return b.setAttribute("d",t),o.appendChild(b),o}var $="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",q="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";function _(t){var O,H;let o,b={vote:[],open:[],submit:[],close:[],error:[]};function s(e,g){var y;(y=b[e])==null||y.forEach(C=>C(g))}let r=document.createElement("div"),h=t.colorScheme==="dark"?" ib-theme-dark":t.colorScheme==="light"?" ib-theme-light":"";r.className="ib-bar-wrapper"+(t.placement==="fixed"?" ib-bar-wrapper--fixed":"")+h;let{theme:a={}}=t;a.primary&&r.style.setProperty("--ib-primary",a.primary),a.background&&r.style.setProperty("--ib-background",a.background),a.surface&&r.style.setProperty("--ib-surface",a.surface),a.text&&r.style.setProperty("--ib-text",a.text),a.selected&&r.style.setProperty("--ib-selected",a.selected),a.selectedColor&&r.style.setProperty("--ib-selected-color",a.selectedColor);let l=document.createElement("div");l.className="ib-bar";let m=document.createElement("div");m.className="ib-bar-label-area";let c=document.createElement("span");c.className="ib-bar-label",c.textContent=(O=t.label)!=null?O:"Was this helpful?",m.appendChild(c);let p=document.createElement("div");p.className="ib-bar-actions"+(t.showLabel===!1?" ib-bar-actions--no-label":"");let i=document.createElement("button");i.type="button",i.className="ib-bar-btn ib-bar-btn--up",i.setAttribute("aria-label","Yes"),i.appendChild(R($));let d=document.createElement("button");d.type="button",d.className="ib-bar-btn ib-bar-btn--down",d.setAttribute("aria-label","No"),d.appendChild(R(q)),p.appendChild(i),p.appendChild(d),t.showLabel!==!1&&l.appendChild(m),l.appendChild(p);let n=document.createElement("div");n.className="ib-bar-popover",n.setAttribute("role","dialog"),n.setAttribute("aria-modal","false");let N=document.createElement("div");if(N.className="ib-bar-header",t.modalTitle){n.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,N.appendChild(e)}else n.setAttribute("aria-label","Feedback");let f=document.createElement("div");f.className="ib-bar-body";let v=document.createElement("textarea");v.className="ib-bar-textarea",v.placeholder=(H=t.modalPlaceholder)!=null?H:"What's on your mind?",v.setAttribute("aria-label","Feedback");let x=document.createElement("p");x.className="ib-bar-error";let B=document.createElement("p");B.className="ib-bar-success";let u=document.createElement("button");if(u.type="button",u.className="ib-bar-submit",u.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"),f.appendChild(e)}if(f.appendChild(v),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"),f.appendChild(e)}f.appendChild(x),f.appendChild(B);let A=document.createElement("div");A.className="ib-bar-footer",A.appendChild(u);let I=document.createElement("div");I.className="ib-branding";let w=document.createElement("a");w.href="https://inputbuffer.io",w.target="_blank",w.rel="noopener noreferrer",w.textContent="Powered by inputbuffer.io",I.appendChild(w),A.appendChild(I),f.appendChild(A),n.appendChild(N),n.appendChild(f),r.appendChild(n),r.appendChild(l);function E(){let e=l.getBoundingClientRect();n.style.top=`${e.top-n.offsetHeight-8}px`,n.style.left=`${e.right-320}px`}function z(e){o=e,n.classList.add("ib-bar-popover--visible"),requestAnimationFrame(E),x.textContent="",B.textContent="",v.focus(),setTimeout(()=>document.addEventListener("click",T),0),document.addEventListener("keydown",P),window.addEventListener("scroll",E,{passive:!0}),window.addEventListener("resize",E,{passive:!0}),s("open",{sentiment:e})}function L(){n.classList.contains("ib-bar-popover--visible")&&(n.classList.remove("ib-bar-popover--visible"),document.removeEventListener("click",T),document.removeEventListener("keydown",P),window.removeEventListener("scroll",E),window.removeEventListener("resize",E),v.value="",s("close"))}function W(){i.classList.remove("ib-bar-btn--active"),d.classList.remove("ib-bar-btn--active"),o=void 0}function T(e){r.contains(e.target)||L()}function P(e){e.key==="Escape"&&L()}async function K(){let e=v.value.trim();if(e.length<10){x.textContent="Please enter at least 10 characters.";return}x.textContent="",u.disabled=!0,u.textContent="Sending\u2026";let g=n.querySelector(".ib-bar-title-input"),y=(g==null?void 0:g.value.trim())||null,C=n.querySelector(".ib-bar-email"),G=(C==null?void 0:C.value.trim())||null;try{let k=await M(t.apiKey,e,G,y,{sentiment:o,target:t.target,source:t.source},t.apiUrl);s("submit",k),B.textContent="Thanks for your feedback!",u.textContent="Send feedback",setTimeout(()=>{L(),W()},2e3)}catch(k){s("error",k instanceof Error?k:new Error("Something went wrong.")),x.textContent=k instanceof Error?k.message:"Something went wrong.",u.disabled=!1,u.textContent="Send feedback"}}function j(e){i.classList.remove("ib-bar-btn--active"),d.classList.remove("ib-bar-btn--active"),e.classList.add("ib-bar-btn--active")}return i.addEventListener("click",()=>{s("vote",{sentiment:"positive"}),j(i),z("positive")}),d.addEventListener("click",()=>{s("vote",{sentiment:"negative"}),j(d),z("negative")}),u.addEventListener("click",K),{element:r,on(e,g){var y;(y=b[e])==null||y.push(g)},destroy(){L(),r.remove()}}}var D=`/* 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.0",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=24*60*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 S="0.1.1",U="https://inputbuffer.io/api/v0/inputs";async function M(t,o,b,s,
|
|
|
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 S="0.1.1",U="https://inputbuffer.io/api/v0/inputs";async function M(t,o,b,s,
|
|
|
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 S="0.1.1",U="https://inputbuffer.io/api/v0/inputs";async function M(t,o,b,s,
|
|
|
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};
|