@feedhog/js 0.1.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 +215 -0
- package/dist/index.cjs +426 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +338 -0
- package/dist/index.d.ts +338 -0
- package/dist/index.js +398 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.js +2 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# @feedhog/js
|
|
2
|
+
|
|
3
|
+
JavaScript SDK for [Feedhog](https://feedhog.com) - collect user feedback from any website or application.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @feedhog/js
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @feedhog/js
|
|
11
|
+
# or
|
|
12
|
+
yarn add @feedhog/js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### CDN Usage
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script src="https://feedhog.com/sdk.js"></script>
|
|
19
|
+
<script>
|
|
20
|
+
const feedhog = new Feedhog({ apiKey: 'fhpk_xxx' });
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import Feedhog from '@feedhog/js';
|
|
28
|
+
|
|
29
|
+
// Initialize with your public API key
|
|
30
|
+
const feedhog = new Feedhog({
|
|
31
|
+
apiKey: 'fhpk_xxx',
|
|
32
|
+
baseUrl: 'https://feedhog.com' // Optional, defaults to production
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Identify the current user (optional but recommended)
|
|
36
|
+
await feedhog.identify({
|
|
37
|
+
externalId: 'user-123',
|
|
38
|
+
email: 'user@example.com',
|
|
39
|
+
name: 'John Doe',
|
|
40
|
+
metadata: { plan: 'pro' }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Submit feedback
|
|
44
|
+
const feedback = await feedhog.submit({
|
|
45
|
+
title: 'Add dark mode',
|
|
46
|
+
description: 'Would love a dark theme option',
|
|
47
|
+
type: 'idea' // 'bug' | 'idea' | 'question' | 'other'
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### `new Feedhog(config)`
|
|
54
|
+
|
|
55
|
+
Create a new Feedhog instance.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const feedhog = new Feedhog({
|
|
59
|
+
apiKey: 'fhpk_xxx', // Required: Your public API key
|
|
60
|
+
baseUrl: 'https://...' // Optional: Custom API URL
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `feedhog.identify(user)`
|
|
65
|
+
|
|
66
|
+
Identify the current user. This associates all subsequent feedback and votes with this user.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
await feedhog.identify({
|
|
70
|
+
externalId: 'user-123', // Required: Your system's user ID
|
|
71
|
+
email: 'user@example.com', // Optional
|
|
72
|
+
name: 'John Doe', // Optional
|
|
73
|
+
avatarUrl: 'https://...', // Optional
|
|
74
|
+
metadata: { plan: 'pro' } // Optional: Custom data
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
User identity is persisted in localStorage, so returning users are automatically recognized.
|
|
79
|
+
|
|
80
|
+
### `feedhog.submit(input)`
|
|
81
|
+
|
|
82
|
+
Submit new feedback.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const feedback = await feedhog.submit({
|
|
86
|
+
title: 'Feature request', // Required
|
|
87
|
+
description: 'Detailed description', // Optional
|
|
88
|
+
type: 'idea', // Optional: 'bug' | 'idea' | 'question' | 'other'
|
|
89
|
+
metadata: { page: '/settings' } // Optional: Custom data
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `feedhog.list(options)`
|
|
94
|
+
|
|
95
|
+
List feedback items with optional filters.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const { items, total, page, totalPages } = await feedhog.list({
|
|
99
|
+
status: ['new', 'planned'], // Filter by status(es)
|
|
100
|
+
type: 'idea', // Filter by type
|
|
101
|
+
search: 'dark mode', // Search query
|
|
102
|
+
sortBy: 'votes', // 'newest' | 'oldest' | 'votes' | 'comments'
|
|
103
|
+
page: 1, // Page number
|
|
104
|
+
limit: 20 // Items per page (max 100)
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `feedhog.get(feedbackId)`
|
|
109
|
+
|
|
110
|
+
Get a single feedback item with full details including comments.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const feedback = await feedhog.get('abc123');
|
|
114
|
+
console.log(feedback.comments);
|
|
115
|
+
console.log(feedback.userHasVoted);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `feedhog.vote(feedbackId)`
|
|
119
|
+
|
|
120
|
+
Toggle vote on a feedback item.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const { voted, voteCount } = await feedhog.vote('abc123');
|
|
124
|
+
// voted: true if vote was added, false if removed
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `feedhog.hasVoted(feedbackId)`
|
|
128
|
+
|
|
129
|
+
Check if the current user has voted on a feedback item.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const { voted, voteCount } = await feedhog.hasVoted('abc123');
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `feedhog.reset()`
|
|
136
|
+
|
|
137
|
+
Clear the current user and stored data. Use when a user logs out.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
feedhog.reset();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `feedhog.user`
|
|
144
|
+
|
|
145
|
+
Get the currently identified user.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
if (feedhog.user) {
|
|
149
|
+
console.log('Logged in as:', feedhog.user.name);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Events
|
|
154
|
+
|
|
155
|
+
Subscribe to SDK events:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// User identified
|
|
159
|
+
feedhog.on('identify', (user) => {
|
|
160
|
+
console.log('User identified:', user);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Feedback submitted
|
|
164
|
+
feedhog.on('submit', (feedback) => {
|
|
165
|
+
console.log('Feedback submitted:', feedback.id);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Vote toggled
|
|
169
|
+
feedhog.on('vote', ({ feedbackId, result }) => {
|
|
170
|
+
console.log('Vote toggled:', result.voted);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Error occurred
|
|
174
|
+
feedhog.on('error', (error) => {
|
|
175
|
+
console.error('SDK error:', error);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// User reset
|
|
179
|
+
feedhog.on('reset', () => {
|
|
180
|
+
console.log('User logged out');
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## TypeScript
|
|
185
|
+
|
|
186
|
+
The SDK is fully typed. Import types as needed:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import Feedhog, {
|
|
190
|
+
type FeedbackType,
|
|
191
|
+
type FeedbackStatus,
|
|
192
|
+
type FeedbackListItem,
|
|
193
|
+
type UserIdentity
|
|
194
|
+
} from '@feedhog/js';
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Error Handling
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import Feedhog, { FeedhogApiError } from '@feedhog/js';
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await feedhog.submit({ title: '' });
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error instanceof FeedhogApiError) {
|
|
206
|
+
console.log('Status:', error.status);
|
|
207
|
+
console.log('Message:', error.message);
|
|
208
|
+
console.log('Field errors:', error.details?.fieldErrors);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Feedhog: () => Feedhog,
|
|
24
|
+
FeedhogApiError: () => FeedhogApiError,
|
|
25
|
+
default: () => index_default
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/utils.ts
|
|
30
|
+
var STORAGE_KEY = "feedhog_user";
|
|
31
|
+
function storeUser(user) {
|
|
32
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
33
|
+
try {
|
|
34
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(user));
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function getStoredUser() {
|
|
39
|
+
if (typeof window === "undefined" || !window.localStorage) return null;
|
|
40
|
+
try {
|
|
41
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
42
|
+
if (!stored) return null;
|
|
43
|
+
return JSON.parse(stored);
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function clearStoredUser() {
|
|
49
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
50
|
+
try {
|
|
51
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function buildUrl(baseUrl, path, params) {
|
|
56
|
+
const url = new URL(path, baseUrl);
|
|
57
|
+
if (params) {
|
|
58
|
+
for (const [key, value] of Object.entries(params)) {
|
|
59
|
+
if (value === void 0) continue;
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
url.searchParams.set(key, value.join(","));
|
|
62
|
+
} else {
|
|
63
|
+
url.searchParams.set(key, String(value));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return url.toString();
|
|
68
|
+
}
|
|
69
|
+
function isBrowser() {
|
|
70
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
71
|
+
}
|
|
72
|
+
var EventEmitter = class {
|
|
73
|
+
constructor() {
|
|
74
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
75
|
+
}
|
|
76
|
+
on(event, callback) {
|
|
77
|
+
if (!this.listeners.has(event)) {
|
|
78
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
79
|
+
}
|
|
80
|
+
this.listeners.get(event).add(callback);
|
|
81
|
+
return () => {
|
|
82
|
+
this.listeners.get(event)?.delete(callback);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
emit(event, data) {
|
|
86
|
+
this.listeners.get(event)?.forEach((callback) => callback(data));
|
|
87
|
+
}
|
|
88
|
+
off(event, callback) {
|
|
89
|
+
this.listeners.get(event)?.delete(callback);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/client.ts
|
|
94
|
+
var DEFAULT_BASE_URL = "https://feedhog.com";
|
|
95
|
+
var FeedhogClient = class {
|
|
96
|
+
constructor(config) {
|
|
97
|
+
if (!config.apiKey) {
|
|
98
|
+
throw new Error("Feedhog: apiKey is required");
|
|
99
|
+
}
|
|
100
|
+
this.apiKey = config.apiKey;
|
|
101
|
+
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Make an authenticated API request
|
|
105
|
+
*/
|
|
106
|
+
async request(method, path, options) {
|
|
107
|
+
const url = options?.params ? buildUrl(this.baseUrl, path, options.params) : `${this.baseUrl}${path}`;
|
|
108
|
+
const response = await fetch(url, {
|
|
109
|
+
method,
|
|
110
|
+
headers: {
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
"x-api-key": this.apiKey
|
|
113
|
+
},
|
|
114
|
+
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
115
|
+
});
|
|
116
|
+
const data = await response.json();
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const error = data;
|
|
119
|
+
throw new FeedhogApiError(
|
|
120
|
+
error.error || "Unknown error",
|
|
121
|
+
response.status,
|
|
122
|
+
error.details
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return data;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Identify or create an end user
|
|
129
|
+
*/
|
|
130
|
+
async identify(user) {
|
|
131
|
+
const response = await this.request(
|
|
132
|
+
"POST",
|
|
133
|
+
"/api/v1/identify",
|
|
134
|
+
{ body: user }
|
|
135
|
+
);
|
|
136
|
+
return response.user;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Submit new feedback
|
|
140
|
+
*/
|
|
141
|
+
async submit(input, user) {
|
|
142
|
+
const body = {
|
|
143
|
+
...input
|
|
144
|
+
};
|
|
145
|
+
if (user) {
|
|
146
|
+
body.endUser = user;
|
|
147
|
+
}
|
|
148
|
+
const response = await this.request(
|
|
149
|
+
"POST",
|
|
150
|
+
"/api/v1/feedback",
|
|
151
|
+
{ body }
|
|
152
|
+
);
|
|
153
|
+
return response.feedback;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* List feedback items (paginated)
|
|
157
|
+
*/
|
|
158
|
+
async list(options) {
|
|
159
|
+
return this.request(
|
|
160
|
+
"GET",
|
|
161
|
+
"/api/v1/feedback",
|
|
162
|
+
{
|
|
163
|
+
params: {
|
|
164
|
+
status: options?.status ? Array.isArray(options.status) ? options.status : [options.status] : void 0,
|
|
165
|
+
type: options?.type ? Array.isArray(options.type) ? options.type : [options.type] : void 0,
|
|
166
|
+
search: options?.search,
|
|
167
|
+
sortBy: options?.sortBy,
|
|
168
|
+
page: options?.page,
|
|
169
|
+
limit: options?.limit
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get a single feedback item with details
|
|
176
|
+
*/
|
|
177
|
+
async get(feedbackId, endUserId) {
|
|
178
|
+
const response = await this.request(
|
|
179
|
+
"GET",
|
|
180
|
+
`/api/v1/feedback/${feedbackId}`,
|
|
181
|
+
{
|
|
182
|
+
params: endUserId ? { endUserId } : void 0
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
return response.feedback;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Toggle vote on feedback
|
|
189
|
+
*/
|
|
190
|
+
async vote(feedbackId, user) {
|
|
191
|
+
return this.request("POST", `/api/v1/feedback/${feedbackId}/vote`, {
|
|
192
|
+
body: user ? { endUser: user } : {}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Check if user has voted on feedback
|
|
197
|
+
*/
|
|
198
|
+
async hasVoted(feedbackId, endUserId) {
|
|
199
|
+
return this.request("GET", `/api/v1/feedback/${feedbackId}/vote`, {
|
|
200
|
+
params: endUserId ? { endUserId } : void 0
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var FeedhogApiError = class extends Error {
|
|
205
|
+
constructor(message, status, details) {
|
|
206
|
+
super(message);
|
|
207
|
+
this.status = status;
|
|
208
|
+
this.details = details;
|
|
209
|
+
this.name = "FeedhogApiError";
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/index.ts
|
|
214
|
+
var Feedhog = class extends EventEmitter {
|
|
215
|
+
constructor(config) {
|
|
216
|
+
super();
|
|
217
|
+
this.currentUser = null;
|
|
218
|
+
this.client = new FeedhogClient(config);
|
|
219
|
+
if (isBrowser()) {
|
|
220
|
+
const stored = getStoredUser();
|
|
221
|
+
if (stored) {
|
|
222
|
+
this.currentUser = { ...stored, identified: false };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get the currently identified user
|
|
228
|
+
*/
|
|
229
|
+
get user() {
|
|
230
|
+
return this.currentUser;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Identify the current user
|
|
234
|
+
*
|
|
235
|
+
* This associates feedback and votes with a specific user in your system.
|
|
236
|
+
* User data is persisted in localStorage for subsequent sessions.
|
|
237
|
+
*
|
|
238
|
+
* @param user - User identity data
|
|
239
|
+
* @returns Promise resolving to the identified user
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* await feedhog.identify({
|
|
244
|
+
* externalId: 'user-123',
|
|
245
|
+
* email: 'user@example.com',
|
|
246
|
+
* name: 'John Doe',
|
|
247
|
+
* metadata: { plan: 'pro' }
|
|
248
|
+
* });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
async identify(user) {
|
|
252
|
+
try {
|
|
253
|
+
const identified = await this.client.identify(user);
|
|
254
|
+
this.currentUser = {
|
|
255
|
+
...user,
|
|
256
|
+
identified: true
|
|
257
|
+
};
|
|
258
|
+
if (isBrowser()) {
|
|
259
|
+
storeUser(user);
|
|
260
|
+
}
|
|
261
|
+
this.emit("identify", identified);
|
|
262
|
+
return identified;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
this.emit("error", error);
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Submit new feedback
|
|
270
|
+
*
|
|
271
|
+
* If a user has been identified, the feedback will be associated with them.
|
|
272
|
+
*
|
|
273
|
+
* @param input - Feedback data
|
|
274
|
+
* @returns Promise resolving to the created feedback item
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* const feedback = await feedhog.submit({
|
|
279
|
+
* title: 'Add dark mode',
|
|
280
|
+
* description: 'Would love a dark theme option',
|
|
281
|
+
* type: 'idea'
|
|
282
|
+
* });
|
|
283
|
+
* console.log('Feedback submitted:', feedback.id);
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
async submit(input) {
|
|
287
|
+
try {
|
|
288
|
+
const feedback = await this.client.submit(
|
|
289
|
+
input,
|
|
290
|
+
this.currentUser || void 0
|
|
291
|
+
);
|
|
292
|
+
this.emit("submit", feedback);
|
|
293
|
+
return feedback;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
this.emit("error", error);
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* List feedback items
|
|
301
|
+
*
|
|
302
|
+
* Returns a paginated list of feedback for the project.
|
|
303
|
+
*
|
|
304
|
+
* @param options - Filter and pagination options
|
|
305
|
+
* @returns Promise resolving to paginated feedback list
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* // Get all ideas in "planned" status
|
|
310
|
+
* const { items, total } = await feedhog.list({
|
|
311
|
+
* status: ['planned', 'in-progress'],
|
|
312
|
+
* type: 'idea',
|
|
313
|
+
* sortBy: 'votes',
|
|
314
|
+
* limit: 10
|
|
315
|
+
* });
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
async list(options) {
|
|
319
|
+
try {
|
|
320
|
+
return await this.client.list(options);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
this.emit("error", error);
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get a single feedback item with full details
|
|
328
|
+
*
|
|
329
|
+
* @param feedbackId - ID of the feedback item
|
|
330
|
+
* @returns Promise resolving to feedback details including comments
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const feedback = await feedhog.get('abc123');
|
|
335
|
+
* console.log('Comments:', feedback.comments.length);
|
|
336
|
+
* console.log('Has voted:', feedback.userHasVoted);
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
async get(feedbackId) {
|
|
340
|
+
try {
|
|
341
|
+
return await this.client.get(
|
|
342
|
+
feedbackId,
|
|
343
|
+
this.currentUser?.externalId
|
|
344
|
+
);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
this.emit("error", error);
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Toggle vote on a feedback item
|
|
352
|
+
*
|
|
353
|
+
* If the user has already voted, this will remove their vote.
|
|
354
|
+
* If they haven't voted, it will add their vote.
|
|
355
|
+
*
|
|
356
|
+
* @param feedbackId - ID of the feedback item to vote on
|
|
357
|
+
* @returns Promise resolving to vote result
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```typescript
|
|
361
|
+
* const { voted, voteCount } = await feedhog.vote('abc123');
|
|
362
|
+
* console.log(voted ? 'Vote added' : 'Vote removed');
|
|
363
|
+
* console.log('Total votes:', voteCount);
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
async vote(feedbackId) {
|
|
367
|
+
try {
|
|
368
|
+
const result = await this.client.vote(
|
|
369
|
+
feedbackId,
|
|
370
|
+
this.currentUser || void 0
|
|
371
|
+
);
|
|
372
|
+
this.emit("vote", { feedbackId, result });
|
|
373
|
+
return result;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
this.emit("error", error);
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Check if the current user has voted on a feedback item
|
|
381
|
+
*
|
|
382
|
+
* @param feedbackId - ID of the feedback item
|
|
383
|
+
* @returns Promise resolving to vote status
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* const { voted, voteCount } = await feedhog.hasVoted('abc123');
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
async hasVoted(feedbackId) {
|
|
391
|
+
try {
|
|
392
|
+
return await this.client.hasVoted(
|
|
393
|
+
feedbackId,
|
|
394
|
+
this.currentUser?.externalId
|
|
395
|
+
);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
this.emit("error", error);
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Reset the SDK state
|
|
403
|
+
*
|
|
404
|
+
* Clears the current user and removes stored data.
|
|
405
|
+
* Use this when a user logs out.
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```typescript
|
|
409
|
+
* feedhog.reset();
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
reset() {
|
|
413
|
+
this.currentUser = null;
|
|
414
|
+
if (isBrowser()) {
|
|
415
|
+
clearStoredUser();
|
|
416
|
+
}
|
|
417
|
+
this.emit("reset", void 0);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
var index_default = Feedhog;
|
|
421
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
422
|
+
0 && (module.exports = {
|
|
423
|
+
Feedhog,
|
|
424
|
+
FeedhogApiError
|
|
425
|
+
});
|
|
426
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/client.ts"],"sourcesContent":["/**\n * Feedhog JavaScript SDK\n *\n * Collect user feedback from any website or application.\n *\n * @example\n * ```typescript\n * import Feedhog from '@feedhog/js';\n *\n * const feedhog = new Feedhog({ apiKey: 'fhpk_xxx' });\n *\n * // Identify the current user (optional but recommended)\n * feedhog.identify({\n * externalId: 'user-123',\n * email: 'user@example.com',\n * name: 'John Doe'\n * });\n *\n * // Submit feedback\n * await feedhog.submit({\n * title: 'Add dark mode',\n * description: 'Would love a dark theme option',\n * type: 'idea'\n * });\n * ```\n */\n\nimport { FeedhogClient, FeedhogApiError } from \"./client\";\nimport {\n storeUser,\n getStoredUser,\n clearStoredUser,\n EventEmitter,\n isBrowser,\n} from \"./utils\";\nimport type {\n FeedhogConfig,\n UserIdentity,\n SubmitFeedbackInput,\n ListFeedbackOptions,\n FeedbackListItem,\n FeedbackDetail,\n PaginatedResponse,\n VoteResult,\n IdentifiedUser,\n CurrentUser,\n FeedbackType,\n FeedbackStatus,\n SortBy,\n} from \"./types\";\n\n// SDK Events\ninterface FeedhogEvents {\n [key: string]: unknown;\n identify: IdentifiedUser;\n submit: FeedbackListItem;\n vote: { feedbackId: string; result: VoteResult };\n error: Error;\n reset: void;\n}\n\n/**\n * Feedhog SDK main class\n */\nexport class Feedhog extends EventEmitter<FeedhogEvents> {\n private readonly client: FeedhogClient;\n private currentUser: CurrentUser | null = null;\n\n constructor(config: FeedhogConfig) {\n super();\n this.client = new FeedhogClient(config);\n\n // Try to restore user from storage\n if (isBrowser()) {\n const stored = getStoredUser();\n if (stored) {\n this.currentUser = { ...stored, identified: false };\n }\n }\n }\n\n /**\n * Get the currently identified user\n */\n get user(): CurrentUser | null {\n return this.currentUser;\n }\n\n /**\n * Identify the current user\n *\n * This associates feedback and votes with a specific user in your system.\n * User data is persisted in localStorage for subsequent sessions.\n *\n * @param user - User identity data\n * @returns Promise resolving to the identified user\n *\n * @example\n * ```typescript\n * await feedhog.identify({\n * externalId: 'user-123',\n * email: 'user@example.com',\n * name: 'John Doe',\n * metadata: { plan: 'pro' }\n * });\n * ```\n */\n async identify(user: UserIdentity): Promise<IdentifiedUser> {\n try {\n const identified = await this.client.identify(user);\n\n this.currentUser = {\n ...user,\n identified: true,\n };\n\n // Persist to storage\n if (isBrowser()) {\n storeUser(user);\n }\n\n this.emit(\"identify\", identified);\n return identified;\n } catch (error) {\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * Submit new feedback\n *\n * If a user has been identified, the feedback will be associated with them.\n *\n * @param input - Feedback data\n * @returns Promise resolving to the created feedback item\n *\n * @example\n * ```typescript\n * const feedback = await feedhog.submit({\n * title: 'Add dark mode',\n * description: 'Would love a dark theme option',\n * type: 'idea'\n * });\n * console.log('Feedback submitted:', feedback.id);\n * ```\n */\n async submit(input: SubmitFeedbackInput): Promise<FeedbackListItem> {\n try {\n const feedback = await this.client.submit(\n input,\n this.currentUser || undefined\n );\n this.emit(\"submit\", feedback);\n return feedback;\n } catch (error) {\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * List feedback items\n *\n * Returns a paginated list of feedback for the project.\n *\n * @param options - Filter and pagination options\n * @returns Promise resolving to paginated feedback list\n *\n * @example\n * ```typescript\n * // Get all ideas in \"planned\" status\n * const { items, total } = await feedhog.list({\n * status: ['planned', 'in-progress'],\n * type: 'idea',\n * sortBy: 'votes',\n * limit: 10\n * });\n * ```\n */\n async list(\n options?: ListFeedbackOptions\n ): Promise<PaginatedResponse<FeedbackListItem>> {\n try {\n return await this.client.list(options);\n } catch (error) {\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * Get a single feedback item with full details\n *\n * @param feedbackId - ID of the feedback item\n * @returns Promise resolving to feedback details including comments\n *\n * @example\n * ```typescript\n * const feedback = await feedhog.get('abc123');\n * console.log('Comments:', feedback.comments.length);\n * console.log('Has voted:', feedback.userHasVoted);\n * ```\n */\n async get(feedbackId: string): Promise<FeedbackDetail> {\n try {\n return await this.client.get(\n feedbackId,\n this.currentUser?.externalId\n );\n } catch (error) {\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * Toggle vote on a feedback item\n *\n * If the user has already voted, this will remove their vote.\n * If they haven't voted, it will add their vote.\n *\n * @param feedbackId - ID of the feedback item to vote on\n * @returns Promise resolving to vote result\n *\n * @example\n * ```typescript\n * const { voted, voteCount } = await feedhog.vote('abc123');\n * console.log(voted ? 'Vote added' : 'Vote removed');\n * console.log('Total votes:', voteCount);\n * ```\n */\n async vote(feedbackId: string): Promise<VoteResult> {\n try {\n const result = await this.client.vote(\n feedbackId,\n this.currentUser || undefined\n );\n this.emit(\"vote\", { feedbackId, result });\n return result;\n } catch (error) {\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * Check if the current user has voted on a feedback item\n *\n * @param feedbackId - ID of the feedback item\n * @returns Promise resolving to vote status\n *\n * @example\n * ```typescript\n * const { voted, voteCount } = await feedhog.hasVoted('abc123');\n * ```\n */\n async hasVoted(feedbackId: string): Promise<VoteResult> {\n try {\n return await this.client.hasVoted(\n feedbackId,\n this.currentUser?.externalId\n );\n } catch (error) {\n this.emit(\"error\", error as Error);\n throw error;\n }\n }\n\n /**\n * Reset the SDK state\n *\n * Clears the current user and removes stored data.\n * Use this when a user logs out.\n *\n * @example\n * ```typescript\n * feedhog.reset();\n * ```\n */\n reset(): void {\n this.currentUser = null;\n if (isBrowser()) {\n clearStoredUser();\n }\n this.emit(\"reset\", undefined);\n }\n}\n\n// Export as default for UMD builds\nexport default Feedhog;\n\n// Export types\nexport type {\n FeedhogConfig,\n UserIdentity,\n SubmitFeedbackInput,\n ListFeedbackOptions,\n FeedbackListItem,\n FeedbackDetail,\n PaginatedResponse,\n VoteResult,\n IdentifiedUser,\n CurrentUser,\n FeedbackType,\n FeedbackStatus,\n SortBy,\n};\n\n// Export error class\nexport { FeedhogApiError };\n","/**\n * Feedhog SDK Utilities\n */\n\nconst STORAGE_KEY = \"feedhog_user\";\n\n/**\n * Store user identity in localStorage (if available)\n */\nexport function storeUser(user: {\n externalId: string;\n email?: string;\n name?: string;\n avatarUrl?: string;\n}): void {\n if (typeof window === \"undefined\" || !window.localStorage) return;\n\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(user));\n } catch {\n // Ignore storage errors (quota exceeded, private mode, etc.)\n }\n}\n\n/**\n * Retrieve stored user identity from localStorage\n */\nexport function getStoredUser(): {\n externalId: string;\n email?: string;\n name?: string;\n avatarUrl?: string;\n} | null {\n if (typeof window === \"undefined\" || !window.localStorage) return null;\n\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (!stored) return null;\n return JSON.parse(stored);\n } catch {\n return null;\n }\n}\n\n/**\n * Clear stored user identity\n */\nexport function clearStoredUser(): void {\n if (typeof window === \"undefined\" || !window.localStorage) return;\n\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Build URL with query parameters\n */\nexport function buildUrl(\n baseUrl: string,\n path: string,\n params?: Record<string, string | string[] | number | undefined>\n): string {\n const url = new URL(path, baseUrl);\n\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n\n if (Array.isArray(value)) {\n url.searchParams.set(key, value.join(\",\"));\n } else {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n return url.toString();\n}\n\n/**\n * Check if we're running in a browser environment\n */\nexport function isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n\n/**\n * Simple event emitter for SDK events\n */\nexport class EventEmitter<Events extends { [key: string]: unknown }> {\n private listeners: Map<keyof Events, Set<(data: unknown) => void>> =\n new Map();\n\n on<K extends keyof Events>(\n event: K,\n callback: (data: Events[K]) => void\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback as (data: unknown) => void);\n\n // Return unsubscribe function\n return () => {\n this.listeners.get(event)?.delete(callback as (data: unknown) => void);\n };\n }\n\n emit<K extends keyof Events>(event: K, data: Events[K]): void {\n this.listeners.get(event)?.forEach((callback) => callback(data));\n }\n\n off<K extends keyof Events>(\n event: K,\n callback: (data: Events[K]) => void\n ): void {\n this.listeners.get(event)?.delete(callback as (data: unknown) => void);\n }\n}\n","/**\n * Feedhog API Client\n * Handles all HTTP communication with the Feedhog API.\n */\n\nimport type {\n FeedhogConfig,\n UserIdentity,\n SubmitFeedbackInput,\n ListFeedbackOptions,\n FeedbackListItem,\n FeedbackDetail,\n PaginatedResponse,\n VoteResult,\n IdentifiedUser,\n ApiError,\n} from \"./types\";\nimport { buildUrl } from \"./utils\";\n\nconst DEFAULT_BASE_URL = \"https://feedhog.com\";\n\nexport class FeedhogClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(config: FeedhogConfig) {\n if (!config.apiKey) {\n throw new Error(\"Feedhog: apiKey is required\");\n }\n\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n }\n\n /**\n * Make an authenticated API request\n */\n private async request<T>(\n method: string,\n path: string,\n options?: {\n body?: unknown;\n params?: Record<string, string | string[] | number | undefined>;\n }\n ): Promise<T> {\n const url = options?.params\n ? buildUrl(this.baseUrl, path, options.params)\n : `${this.baseUrl}${path}`;\n\n const response = await fetch(url, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: options?.body ? JSON.stringify(options.body) : undefined,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n const error = data as ApiError;\n throw new FeedhogApiError(\n error.error || \"Unknown error\",\n response.status,\n error.details\n );\n }\n\n return data as T;\n }\n\n /**\n * Identify or create an end user\n */\n async identify(user: UserIdentity): Promise<IdentifiedUser> {\n const response = await this.request<{ user: IdentifiedUser }>(\n \"POST\",\n \"/api/v1/identify\",\n { body: user }\n );\n return response.user;\n }\n\n /**\n * Submit new feedback\n */\n async submit(\n input: SubmitFeedbackInput,\n user?: UserIdentity\n ): Promise<FeedbackListItem> {\n const body: SubmitFeedbackInput & { endUser?: UserIdentity } = {\n ...input,\n };\n if (user) {\n body.endUser = user;\n }\n\n const response = await this.request<{ feedback: FeedbackListItem }>(\n \"POST\",\n \"/api/v1/feedback\",\n { body }\n );\n return response.feedback;\n }\n\n /**\n * List feedback items (paginated)\n */\n async list(\n options?: ListFeedbackOptions\n ): Promise<PaginatedResponse<FeedbackListItem>> {\n return this.request<PaginatedResponse<FeedbackListItem>>(\n \"GET\",\n \"/api/v1/feedback\",\n {\n params: {\n status: options?.status\n ? Array.isArray(options.status)\n ? options.status\n : [options.status]\n : undefined,\n type: options?.type\n ? Array.isArray(options.type)\n ? options.type\n : [options.type]\n : undefined,\n search: options?.search,\n sortBy: options?.sortBy,\n page: options?.page,\n limit: options?.limit,\n },\n }\n );\n }\n\n /**\n * Get a single feedback item with details\n */\n async get(feedbackId: string, endUserId?: string): Promise<FeedbackDetail> {\n const response = await this.request<{ feedback: FeedbackDetail }>(\n \"GET\",\n `/api/v1/feedback/${feedbackId}`,\n {\n params: endUserId ? { endUserId } : undefined,\n }\n );\n return response.feedback;\n }\n\n /**\n * Toggle vote on feedback\n */\n async vote(feedbackId: string, user?: UserIdentity): Promise<VoteResult> {\n return this.request<VoteResult>(\"POST\", `/api/v1/feedback/${feedbackId}/vote`, {\n body: user ? { endUser: user } : {},\n });\n }\n\n /**\n * Check if user has voted on feedback\n */\n async hasVoted(feedbackId: string, endUserId?: string): Promise<VoteResult> {\n return this.request<VoteResult>(\"GET\", `/api/v1/feedback/${feedbackId}/vote`, {\n params: endUserId ? { endUserId } : undefined,\n });\n }\n}\n\n/**\n * API Error with status code and optional validation details\n */\nexport class FeedhogApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly details?: {\n formErrors: string[];\n fieldErrors: Record<string, string[]>;\n }\n ) {\n super(message);\n this.name = \"FeedhogApiError\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIA,IAAM,cAAc;AAKb,SAAS,UAAU,MAKjB;AACP,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,aAAc;AAE3D,MAAI;AACF,iBAAa,QAAQ,aAAa,KAAK,UAAU,IAAI,CAAC;AAAA,EACxD,QAAQ;AAAA,EAER;AACF;AAKO,SAAS,gBAKP;AACP,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,aAAc,QAAO;AAElE,MAAI;AACF,UAAM,SAAS,aAAa,QAAQ,WAAW;AAC/C,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,KAAK,MAAM,MAAM;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,kBAAwB;AACtC,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,aAAc;AAE3D,MAAI;AACF,iBAAa,WAAW,WAAW;AAAA,EACrC,QAAQ;AAAA,EAER;AACF;AAKO,SAAS,SACd,SACA,MACA,QACQ;AACR,QAAM,MAAM,IAAI,IAAI,MAAM,OAAO;AAEjC,MAAI,QAAQ;AACV,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAI,UAAU,OAAW;AAEzB,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,YAAI,aAAa,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC;AAAA,MAC3C,OAAO;AACL,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAEA,SAAO,IAAI,SAAS;AACtB;AAKO,SAAS,YAAqB;AACnC,SAAO,OAAO,WAAW,eAAe,OAAO,aAAa;AAC9D;AAKO,IAAM,eAAN,MAA8D;AAAA,EAA9D;AACL,SAAQ,YACN,oBAAI,IAAI;AAAA;AAAA,EAEV,GACE,OACA,UACY;AACZ,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAmC;AAGlE,WAAO,MAAM;AACX,WAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAmC;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,KAA6B,OAAU,MAAuB;AAC5D,SAAK,UAAU,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,SAAS,IAAI,CAAC;AAAA,EACjE;AAAA,EAEA,IACE,OACA,UACM;AACN,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAmC;AAAA,EACvE;AACF;;;ACtGA,IAAM,mBAAmB;AAElB,IAAM,gBAAN,MAAoB;AAAA,EAIzB,YAAY,QAAuB;AACjC,QAAI,CAAC,OAAO,QAAQ;AAClB,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AAEA,SAAK,SAAS,OAAO;AACrB,SAAK,WAAW,OAAO,WAAW,kBAAkB,QAAQ,OAAO,EAAE;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,QACZ,QACA,MACA,SAIY;AACZ,UAAM,MAAM,SAAS,SACjB,SAAS,KAAK,SAAS,MAAM,QAAQ,MAAM,IAC3C,GAAG,KAAK,OAAO,GAAG,IAAI;AAE1B,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC;AAAA,MACA,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa,KAAK;AAAA,MACpB;AAAA,MACA,MAAM,SAAS,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,IACvD,CAAC;AAED,UAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ;AACd,YAAM,IAAI;AAAA,QACR,MAAM,SAAS;AAAA,QACf,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAA6C;AAC1D,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,EAAE,MAAM,KAAK;AAAA,IACf;AACA,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OACJ,OACA,MAC2B;AAC3B,UAAM,OAAyD;AAAA,MAC7D,GAAG;AAAA,IACL;AACA,QAAI,MAAM;AACR,WAAK,UAAU;AAAA,IACjB;AAEA,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,EAAE,KAAK;AAAA,IACT;AACA,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KACJ,SAC8C;AAC9C,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,QACE,QAAQ;AAAA,UACN,QAAQ,SAAS,SACb,MAAM,QAAQ,QAAQ,MAAM,IAC1B,QAAQ,SACR,CAAC,QAAQ,MAAM,IACjB;AAAA,UACJ,MAAM,SAAS,OACX,MAAM,QAAQ,QAAQ,IAAI,IACxB,QAAQ,OACR,CAAC,QAAQ,IAAI,IACf;AAAA,UACJ,QAAQ,SAAS;AAAA,UACjB,QAAQ,SAAS;AAAA,UACjB,MAAM,SAAS;AAAA,UACf,OAAO,SAAS;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,YAAoB,WAA6C;AACzE,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B;AAAA,MACA,oBAAoB,UAAU;AAAA,MAC9B;AAAA,QACE,QAAQ,YAAY,EAAE,UAAU,IAAI;AAAA,MACtC;AAAA,IACF;AACA,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,YAAoB,MAA0C;AACvE,WAAO,KAAK,QAAoB,QAAQ,oBAAoB,UAAU,SAAS;AAAA,MAC7E,MAAM,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,IACpC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,YAAoB,WAAyC;AAC1E,WAAO,KAAK,QAAoB,OAAO,oBAAoB,UAAU,SAAS;AAAA,MAC5E,QAAQ,YAAY,EAAE,UAAU,IAAI;AAAA,IACtC,CAAC;AAAA,EACH;AACF;AAKO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACE,SACgB,QACA,SAIhB;AACA,UAAM,OAAO;AANG;AACA;AAMhB,SAAK,OAAO;AAAA,EACd;AACF;;;AFxHO,IAAM,UAAN,cAAsB,aAA4B;AAAA,EAIvD,YAAY,QAAuB;AACjC,UAAM;AAHR,SAAQ,cAAkC;AAIxC,SAAK,SAAS,IAAI,cAAc,MAAM;AAGtC,QAAI,UAAU,GAAG;AACf,YAAM,SAAS,cAAc;AAC7B,UAAI,QAAQ;AACV,aAAK,cAAc,EAAE,GAAG,QAAQ,YAAY,MAAM;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,SAAS,MAA6C;AAC1D,QAAI;AACF,YAAM,aAAa,MAAM,KAAK,OAAO,SAAS,IAAI;AAElD,WAAK,cAAc;AAAA,QACjB,GAAG;AAAA,QACH,YAAY;AAAA,MACd;AAGA,UAAI,UAAU,GAAG;AACf,kBAAU,IAAI;AAAA,MAChB;AAEA,WAAK,KAAK,YAAY,UAAU;AAChC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,OAAO,OAAuD;AAClE,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,OAAO;AAAA,QACjC;AAAA,QACA,KAAK,eAAe;AAAA,MACtB;AACA,WAAK,KAAK,UAAU,QAAQ;AAC5B,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,KACJ,SAC8C;AAC9C,QAAI;AACF,aAAO,MAAM,KAAK,OAAO,KAAK,OAAO;AAAA,IACvC,SAAS,OAAO;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,IAAI,YAA6C;AACrD,QAAI;AACF,aAAO,MAAM,KAAK,OAAO;AAAA,QACvB;AAAA,QACA,KAAK,aAAa;AAAA,MACpB;AAAA,IACF,SAAS,OAAO;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,KAAK,YAAyC;AAClD,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO;AAAA,QAC/B;AAAA,QACA,KAAK,eAAe;AAAA,MACtB;AACA,WAAK,KAAK,QAAQ,EAAE,YAAY,OAAO,CAAC;AACxC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,SAAS,YAAyC;AACtD,QAAI;AACF,aAAO,MAAM,KAAK,OAAO;AAAA,QACvB;AAAA,QACA,KAAK,aAAa;AAAA,MACpB;AAAA,IACF,SAAS,OAAO;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,QAAc;AACZ,SAAK,cAAc;AACnB,QAAI,UAAU,GAAG;AACf,sBAAgB;AAAA,IAClB;AACA,SAAK,KAAK,SAAS,MAAS;AAAA,EAC9B;AACF;AAGA,IAAO,gBAAQ;","names":[]}
|