feedtack 0.5.1 → 1.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 +45 -9
- package/dist/chunk-3INDOI4N.js +1235 -0
- package/dist/index.d.ts +65 -3
- package/dist/index.js +86 -1
- package/dist/react/index.d.ts +5 -7
- package/dist/react/index.js +500 -787
- package/dist/{theme-C-uctIoI.d.ts → types-Cu4Oahg4.d.ts} +45 -4
- package/package.json +1 -1
- package/dist/chunk-PPM4AIJU.js +0 -237
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
declare const SCHEMA_VERSION = "
|
|
1
|
+
declare const SCHEMA_VERSION = "2.0.0";
|
|
2
|
+
type FeedtackScope = 'site' | 'page' | 'element';
|
|
2
3
|
interface FeedtackUser {
|
|
3
4
|
/** Unique identifier — used for attribution across pins, replies, resolutions, archives */
|
|
4
5
|
id: string;
|
|
@@ -81,17 +82,19 @@ interface FeedtackDeviceMeta {
|
|
|
81
82
|
platform: string;
|
|
82
83
|
touchEnabled: boolean;
|
|
83
84
|
}
|
|
84
|
-
type FeedtackSentiment = '
|
|
85
|
+
type FeedtackSentiment = 'good' | 'bad' | null;
|
|
85
86
|
interface FeedtackPayload {
|
|
86
87
|
schemaVersion: string;
|
|
87
88
|
/** Unique feedback ID, e.g. ft_01j... */
|
|
88
89
|
id: string;
|
|
89
90
|
/** ISO 8601 UTC */
|
|
90
91
|
timestamp: string;
|
|
92
|
+
/** Feedback scope: site-wide, page-level, or element-specific */
|
|
93
|
+
scope: FeedtackScope;
|
|
91
94
|
submittedBy: FeedtackUser;
|
|
92
95
|
comment: string;
|
|
93
96
|
sentiment: FeedtackSentiment;
|
|
94
|
-
/**
|
|
97
|
+
/** Pins placed on the page. Empty for site/page scope. */
|
|
95
98
|
pins: FeedtackPin[];
|
|
96
99
|
page: FeedtackPageMeta;
|
|
97
100
|
viewport: FeedtackViewportMeta;
|
|
@@ -127,6 +130,7 @@ interface FeedtackFilter {
|
|
|
127
130
|
url?: string;
|
|
128
131
|
pathname?: string;
|
|
129
132
|
userId?: string;
|
|
133
|
+
scope?: FeedtackScope;
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
/** Plugin contract — implement this interface to create a custom feedtack backend */
|
|
@@ -164,4 +168,41 @@ interface FeedtackTheme {
|
|
|
164
168
|
/** Maps FeedtackTheme fields to CSS custom properties on #feedtack-root */
|
|
165
169
|
declare function themeToCSS(theme: FeedtackTheme): Record<string, string>;
|
|
166
170
|
|
|
167
|
-
|
|
171
|
+
declare function generateId(): string;
|
|
172
|
+
interface FeedtackFlushEvent {
|
|
173
|
+
pathname: string;
|
|
174
|
+
items: FeedbackItem[];
|
|
175
|
+
}
|
|
176
|
+
interface FeedtackEngineOpts {
|
|
177
|
+
adapter: FeedtackAdapter;
|
|
178
|
+
currentUser: FeedtackUser;
|
|
179
|
+
hotkey?: string;
|
|
180
|
+
theme?: FeedtackTheme;
|
|
181
|
+
onError?: (err: Error) => void;
|
|
182
|
+
disabled?: boolean;
|
|
183
|
+
onFlush?: (event: FeedtackFlushEvent) => void;
|
|
184
|
+
flushIdleMs?: number;
|
|
185
|
+
rescopeRoles?: string[];
|
|
186
|
+
}
|
|
187
|
+
interface FeedtackEngineState {
|
|
188
|
+
isPinModeActive: boolean;
|
|
189
|
+
pendingPins: Array<Omit<FeedtackPin, 'index'>>;
|
|
190
|
+
selectedColor: string;
|
|
191
|
+
showForm: boolean;
|
|
192
|
+
comment: string;
|
|
193
|
+
sentiment: FeedtackSentiment;
|
|
194
|
+
commentError: boolean;
|
|
195
|
+
submitting: boolean;
|
|
196
|
+
feedbackItems: FeedbackItem[];
|
|
197
|
+
siteFeedback: FeedbackItem[];
|
|
198
|
+
pageFeedback: FeedbackItem[];
|
|
199
|
+
loading: boolean;
|
|
200
|
+
openThreadId: string | null;
|
|
201
|
+
replyBody: string;
|
|
202
|
+
isModalOpen: boolean;
|
|
203
|
+
composeScope: 'site' | 'page';
|
|
204
|
+
pathname: string;
|
|
205
|
+
}
|
|
206
|
+
type FeedtackStateListener = (state: FeedtackEngineState, changedKeys: Array<keyof FeedtackEngineState>) => void;
|
|
207
|
+
|
|
208
|
+
export { type AncestorNode as A, type FeedtackAdapter as F, SCHEMA_VERSION as S, type FeedtackPayload as a, type FeedtackReply as b, type FeedtackResolution as c, type FeedtackFilter as d, type FeedbackItem as e, type FeedtackDeviceMeta as f, type FeedtackPageMeta as g, type FeedtackViewportMeta as h, type FeedtackPinTarget as i, type FeedtackEngineOpts as j, type FeedtackEngineState as k, type FeedtackStateListener as l, type FeedtackArchive as m, type FeedtackBoundingRect as n, type FeedtackFlushEvent as o, type FeedtackPin as p, type FeedtackScope as q, type FeedtackSentiment as r, type FeedtackTheme as s, type FeedtackUser as t, generateId as u, themeToCSS as v };
|
package/package.json
CHANGED
package/dist/chunk-PPM4AIJU.js
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
// src/capture/meta.ts
|
|
2
|
-
function getViewportMeta() {
|
|
3
|
-
return {
|
|
4
|
-
width: window.innerWidth,
|
|
5
|
-
height: window.innerHeight,
|
|
6
|
-
scrollX: window.scrollX,
|
|
7
|
-
scrollY: window.scrollY,
|
|
8
|
-
devicePixelRatio: window.devicePixelRatio
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
function getPageMeta() {
|
|
12
|
-
return {
|
|
13
|
-
url: window.location.href,
|
|
14
|
-
pathname: window.location.pathname,
|
|
15
|
-
title: document.title
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function getDeviceMeta() {
|
|
19
|
-
return {
|
|
20
|
-
userAgent: navigator.userAgent,
|
|
21
|
-
platform: navigator.platform,
|
|
22
|
-
touchEnabled: navigator.maxTouchPoints > 0
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function getPinCoords(event) {
|
|
26
|
-
const x = event.clientX + window.scrollX;
|
|
27
|
-
const y = event.clientY + window.scrollY;
|
|
28
|
-
const docWidth = document.documentElement.scrollWidth;
|
|
29
|
-
const docHeight = document.documentElement.scrollHeight;
|
|
30
|
-
return {
|
|
31
|
-
x,
|
|
32
|
-
y,
|
|
33
|
-
xPct: Number((x / docWidth * 100).toFixed(2)),
|
|
34
|
-
yPct: Number((y / docHeight * 100).toFixed(2))
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// src/capture/fiber.ts
|
|
39
|
-
var fiberKey;
|
|
40
|
-
function getFiberKey(element) {
|
|
41
|
-
if (fiberKey !== void 0) return fiberKey;
|
|
42
|
-
const key = Object.keys(element).find((k) => k.startsWith("__reactFiber$"));
|
|
43
|
-
fiberKey = key ?? null;
|
|
44
|
-
return fiberKey;
|
|
45
|
-
}
|
|
46
|
-
function getComponentName(element) {
|
|
47
|
-
try {
|
|
48
|
-
const key = getFiberKey(element);
|
|
49
|
-
if (!key) return null;
|
|
50
|
-
let fiber = element[key];
|
|
51
|
-
while (fiber) {
|
|
52
|
-
const type = fiber.type;
|
|
53
|
-
if (type && typeof type !== "string") {
|
|
54
|
-
const name = type.displayName ?? type.name;
|
|
55
|
-
if (name && name !== "Anonymous") return name;
|
|
56
|
-
}
|
|
57
|
-
fiber = fiber.return;
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
} catch {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// src/capture/target.ts
|
|
66
|
-
var INTERACTIVE_SELECTOR = "button,a,input,select,textarea,label";
|
|
67
|
-
function resolveTarget(element) {
|
|
68
|
-
const promoted = element.closest(INTERACTIVE_SELECTOR);
|
|
69
|
-
return promoted ?? element;
|
|
70
|
-
}
|
|
71
|
-
function attr(el, name) {
|
|
72
|
-
return el.getAttribute(name);
|
|
73
|
-
}
|
|
74
|
-
function nthChild(el) {
|
|
75
|
-
let n = 1;
|
|
76
|
-
let sib = el.previousElementSibling;
|
|
77
|
-
while (sib) {
|
|
78
|
-
n++;
|
|
79
|
-
sib = sib.previousElementSibling;
|
|
80
|
-
}
|
|
81
|
-
return n;
|
|
82
|
-
}
|
|
83
|
-
function nthOfType(el) {
|
|
84
|
-
const tag = el.tagName;
|
|
85
|
-
let n = 1;
|
|
86
|
-
let sib = el.previousElementSibling;
|
|
87
|
-
while (sib) {
|
|
88
|
-
if (sib.tagName === tag) n++;
|
|
89
|
-
sib = sib.previousElementSibling;
|
|
90
|
-
}
|
|
91
|
-
return n;
|
|
92
|
-
}
|
|
93
|
-
function serializeNode(el) {
|
|
94
|
-
const id = attr(el, "id");
|
|
95
|
-
const dataTestId = attr(el, "data-testid") ?? attr(el, "data-test-id");
|
|
96
|
-
const dataFeedtackComponent = attr(el, "data-feedtack-component");
|
|
97
|
-
const hasStableId = !!(id || dataTestId);
|
|
98
|
-
return {
|
|
99
|
-
tag: el.tagName.toLowerCase(),
|
|
100
|
-
id,
|
|
101
|
-
ariaLabel: attr(el, "aria-label"),
|
|
102
|
-
role: attr(el, "role"),
|
|
103
|
-
type: attr(el, "type"),
|
|
104
|
-
name: attr(el, "name"),
|
|
105
|
-
title: attr(el, "title"),
|
|
106
|
-
alt: attr(el, "alt"),
|
|
107
|
-
dataTestId,
|
|
108
|
-
dataFeedtackComponent,
|
|
109
|
-
nthChild: hasStableId ? null : nthChild(el),
|
|
110
|
-
nthOfType: hasStableId ? null : nthOfType(el),
|
|
111
|
-
componentName: dataFeedtackComponent ?? getComponentName(el)
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function getAncestorChain(element) {
|
|
115
|
-
const chain = [];
|
|
116
|
-
let current = element.parentElement;
|
|
117
|
-
while (current && current !== document.body && chain.length < 5) {
|
|
118
|
-
chain.push(serializeNode(current));
|
|
119
|
-
current = current.parentElement;
|
|
120
|
-
}
|
|
121
|
-
return chain;
|
|
122
|
-
}
|
|
123
|
-
function getCSSSelector(element) {
|
|
124
|
-
const parts = [];
|
|
125
|
-
let current = element;
|
|
126
|
-
while (current && current !== document.body) {
|
|
127
|
-
const id = current.getAttribute("id");
|
|
128
|
-
const testId = current.getAttribute("data-testid") ?? current.getAttribute("data-test-id");
|
|
129
|
-
const feedtackComponent = current.getAttribute("data-feedtack-component");
|
|
130
|
-
if (id) {
|
|
131
|
-
parts.unshift(`#${id}`);
|
|
132
|
-
break;
|
|
133
|
-
} else if (testId) {
|
|
134
|
-
parts.unshift(`[data-testid="${testId}"]`);
|
|
135
|
-
break;
|
|
136
|
-
} else if (feedtackComponent) {
|
|
137
|
-
parts.unshift(`[data-feedtack-component="${feedtackComponent}"]`);
|
|
138
|
-
break;
|
|
139
|
-
} else {
|
|
140
|
-
const tag = current.tagName.toLowerCase();
|
|
141
|
-
const parent = current.parentElement;
|
|
142
|
-
if (parent) {
|
|
143
|
-
const siblings = Array.from(parent.children).filter(
|
|
144
|
-
(c) => c.tagName === current.tagName
|
|
145
|
-
);
|
|
146
|
-
const index = siblings.indexOf(current) + 1;
|
|
147
|
-
parts.unshift(
|
|
148
|
-
siblings.length > 1 ? `${tag}:nth-of-type(${index})` : tag
|
|
149
|
-
);
|
|
150
|
-
} else {
|
|
151
|
-
parts.unshift(tag);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
current = current.parentElement;
|
|
155
|
-
}
|
|
156
|
-
return parts.join(" > ");
|
|
157
|
-
}
|
|
158
|
-
function deriveElementPath(target, ancestors) {
|
|
159
|
-
const dataTestId = target.getAttribute("data-testid") ?? target.getAttribute("data-test-id");
|
|
160
|
-
if (dataTestId) return null;
|
|
161
|
-
const targetPart = (() => {
|
|
162
|
-
const tag = target.tagName.toLowerCase();
|
|
163
|
-
const classes = Array.from(target.classList).join(".");
|
|
164
|
-
return classes ? `${tag}.${classes}` : tag;
|
|
165
|
-
})();
|
|
166
|
-
const ancestorParts = ancestors.map((a) => {
|
|
167
|
-
if (a.dataTestId) return `[data-testid="${a.dataTestId}"]`;
|
|
168
|
-
const classes = "";
|
|
169
|
-
return classes ? `${a.tag}.${classes}` : a.tag;
|
|
170
|
-
});
|
|
171
|
-
return [targetPart, ...ancestorParts].join(" > ");
|
|
172
|
-
}
|
|
173
|
-
function getTargetMeta(element) {
|
|
174
|
-
const resolved = resolveTarget(element);
|
|
175
|
-
const id = resolved.getAttribute("id");
|
|
176
|
-
const dataTestId = resolved.getAttribute("data-testid") ?? resolved.getAttribute("data-test-id");
|
|
177
|
-
const feedtackComponent = resolved.getAttribute("data-feedtack-component");
|
|
178
|
-
let selector;
|
|
179
|
-
let best_effort;
|
|
180
|
-
if (id) {
|
|
181
|
-
selector = `#${id}`;
|
|
182
|
-
best_effort = false;
|
|
183
|
-
} else if (dataTestId) {
|
|
184
|
-
selector = `[data-testid="${dataTestId}"]`;
|
|
185
|
-
best_effort = false;
|
|
186
|
-
} else if (feedtackComponent) {
|
|
187
|
-
selector = `[data-feedtack-component="${feedtackComponent}"]`;
|
|
188
|
-
best_effort = false;
|
|
189
|
-
} else {
|
|
190
|
-
selector = getCSSSelector(resolved);
|
|
191
|
-
best_effort = true;
|
|
192
|
-
}
|
|
193
|
-
const ancestors = getAncestorChain(resolved);
|
|
194
|
-
const rect = resolved.getBoundingClientRect();
|
|
195
|
-
return {
|
|
196
|
-
selector,
|
|
197
|
-
best_effort,
|
|
198
|
-
dataTestId,
|
|
199
|
-
elementPath: deriveElementPath(resolved, ancestors),
|
|
200
|
-
tagName: resolved.tagName,
|
|
201
|
-
ancestors,
|
|
202
|
-
boundingRect: {
|
|
203
|
-
x: rect.x + window.scrollX,
|
|
204
|
-
y: rect.y + window.scrollY,
|
|
205
|
-
width: rect.width,
|
|
206
|
-
height: rect.height
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// src/types/payload.ts
|
|
212
|
-
var SCHEMA_VERSION = "1.0.0";
|
|
213
|
-
|
|
214
|
-
// src/types/theme.ts
|
|
215
|
-
function themeToCSS(theme) {
|
|
216
|
-
const map = {};
|
|
217
|
-
if (theme.primary) map["--ft-primary"] = theme.primary;
|
|
218
|
-
if (theme.background) map["--ft-bg"] = theme.background;
|
|
219
|
-
if (theme.surface) map["--ft-surface"] = theme.surface;
|
|
220
|
-
if (theme.text) map["--ft-text"] = theme.text;
|
|
221
|
-
if (theme.textMuted) map["--ft-text-muted"] = theme.textMuted;
|
|
222
|
-
if (theme.border) map["--ft-border"] = theme.border;
|
|
223
|
-
if (theme.radius) map["--ft-radius"] = theme.radius;
|
|
224
|
-
if (theme.badge) map["--ft-badge"] = theme.badge;
|
|
225
|
-
return map;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export {
|
|
229
|
-
getViewportMeta,
|
|
230
|
-
getPageMeta,
|
|
231
|
-
getDeviceMeta,
|
|
232
|
-
getPinCoords,
|
|
233
|
-
getCSSSelector,
|
|
234
|
-
getTargetMeta,
|
|
235
|
-
SCHEMA_VERSION,
|
|
236
|
-
themeToCSS
|
|
237
|
-
};
|