feedtack 1.1.0 → 1.2.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 +49 -40
- package/dist/{types-Cu4Oahg4.d.ts → adapter-Cn59URIG.d.ts} +68 -59
- package/dist/{chunk-3INDOI4N.js → chunk-GD2SY64K.js} +74 -1
- package/dist/index.d.ts +30 -21
- package/dist/index.js +46 -83
- package/dist/node/index.d.ts +27 -0
- package/dist/node/index.js +169 -0
- package/dist/react/index.d.ts +61 -2
- package/dist/react/index.js +830 -232
- package/dist/types-CHrWe7xT.d.ts +61 -0
- package/package.json +5 -1
package/dist/react/index.js
CHANGED
|
@@ -1,7 +1,265 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FeedtackEngine,
|
|
3
|
-
PIN_PALETTE
|
|
4
|
-
|
|
3
|
+
PIN_PALETTE,
|
|
4
|
+
hashField,
|
|
5
|
+
isContentAdapter,
|
|
6
|
+
isContentEditAdapter,
|
|
7
|
+
scanFields,
|
|
8
|
+
warnIfNotContentAdapter,
|
|
9
|
+
warnIfNotContentEditAdapter
|
|
10
|
+
} from "../chunk-GD2SY64K.js";
|
|
11
|
+
|
|
12
|
+
// src/react/ContentEditToolbar.tsx
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
|
|
15
|
+
// src/react/toolbarStyles.ts
|
|
16
|
+
var styles = {
|
|
17
|
+
toolbar: {
|
|
18
|
+
position: "fixed",
|
|
19
|
+
bottom: 16,
|
|
20
|
+
right: 16,
|
|
21
|
+
zIndex: 9999,
|
|
22
|
+
background: "#1a1614",
|
|
23
|
+
border: "1px solid #3a3330",
|
|
24
|
+
borderRadius: 8,
|
|
25
|
+
padding: "8px 12px",
|
|
26
|
+
display: "flex",
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
gap: 8,
|
|
29
|
+
fontFamily: "monospace",
|
|
30
|
+
fontSize: 13,
|
|
31
|
+
color: "#e5e0d8",
|
|
32
|
+
boxShadow: "0 4px 24px rgba(0,0,0,0.5)",
|
|
33
|
+
flexWrap: "wrap",
|
|
34
|
+
maxWidth: 480
|
|
35
|
+
},
|
|
36
|
+
fieldSection: {
|
|
37
|
+
display: "flex",
|
|
38
|
+
alignItems: "center",
|
|
39
|
+
gap: 6,
|
|
40
|
+
borderRight: "1px solid #3a3330",
|
|
41
|
+
paddingRight: 8
|
|
42
|
+
},
|
|
43
|
+
fieldPath: {
|
|
44
|
+
color: "#888",
|
|
45
|
+
fontSize: 11,
|
|
46
|
+
maxWidth: 160,
|
|
47
|
+
overflow: "hidden",
|
|
48
|
+
textOverflow: "ellipsis",
|
|
49
|
+
whiteSpace: "nowrap"
|
|
50
|
+
},
|
|
51
|
+
savingBadge: {
|
|
52
|
+
color: "#f59e0b",
|
|
53
|
+
fontSize: 11
|
|
54
|
+
},
|
|
55
|
+
btnPrimary: {
|
|
56
|
+
background: "#10b981",
|
|
57
|
+
color: "#fff",
|
|
58
|
+
border: "none",
|
|
59
|
+
borderRadius: 4,
|
|
60
|
+
padding: "3px 10px",
|
|
61
|
+
fontSize: 12,
|
|
62
|
+
cursor: "pointer"
|
|
63
|
+
},
|
|
64
|
+
btnSecondary: {
|
|
65
|
+
background: "transparent",
|
|
66
|
+
color: "#888",
|
|
67
|
+
border: "1px solid #3a3330",
|
|
68
|
+
borderRadius: 4,
|
|
69
|
+
padding: "3px 10px",
|
|
70
|
+
fontSize: 12,
|
|
71
|
+
cursor: "pointer"
|
|
72
|
+
},
|
|
73
|
+
btnGhost: {
|
|
74
|
+
background: "transparent",
|
|
75
|
+
color: "#e5e0d8",
|
|
76
|
+
border: "1px solid #3a3330",
|
|
77
|
+
borderRadius: 4,
|
|
78
|
+
padding: "3px 10px",
|
|
79
|
+
fontSize: 12,
|
|
80
|
+
cursor: "pointer"
|
|
81
|
+
},
|
|
82
|
+
btnSuccess: {
|
|
83
|
+
background: "#10b981",
|
|
84
|
+
color: "#fff",
|
|
85
|
+
border: "none",
|
|
86
|
+
borderRadius: 4,
|
|
87
|
+
padding: "3px 10px",
|
|
88
|
+
fontSize: 12,
|
|
89
|
+
cursor: "pointer"
|
|
90
|
+
},
|
|
91
|
+
btnDanger: {
|
|
92
|
+
background: "transparent",
|
|
93
|
+
color: "#ef4444",
|
|
94
|
+
border: "1px solid #ef4444",
|
|
95
|
+
borderRadius: 4,
|
|
96
|
+
padding: "2px 8px",
|
|
97
|
+
fontSize: 11,
|
|
98
|
+
cursor: "pointer"
|
|
99
|
+
},
|
|
100
|
+
panel: {
|
|
101
|
+
position: "absolute",
|
|
102
|
+
bottom: "100%",
|
|
103
|
+
right: 0,
|
|
104
|
+
marginBottom: 8,
|
|
105
|
+
background: "#1a1614",
|
|
106
|
+
border: "1px solid #3a3330",
|
|
107
|
+
borderRadius: 8,
|
|
108
|
+
padding: "10px 14px",
|
|
109
|
+
minWidth: 280,
|
|
110
|
+
maxWidth: 420,
|
|
111
|
+
boxShadow: "0 4px 24px rgba(0,0,0,0.5)"
|
|
112
|
+
},
|
|
113
|
+
panelTitle: {
|
|
114
|
+
fontSize: 11,
|
|
115
|
+
color: "#888",
|
|
116
|
+
marginBottom: 8,
|
|
117
|
+
textTransform: "uppercase",
|
|
118
|
+
letterSpacing: "0.05em"
|
|
119
|
+
},
|
|
120
|
+
changeRow: {
|
|
121
|
+
display: "flex",
|
|
122
|
+
alignItems: "center",
|
|
123
|
+
gap: 8,
|
|
124
|
+
padding: "4px 0",
|
|
125
|
+
borderBottom: "1px solid #2a2421"
|
|
126
|
+
},
|
|
127
|
+
changePath: {
|
|
128
|
+
fontSize: 11,
|
|
129
|
+
color: "#888",
|
|
130
|
+
minWidth: 100,
|
|
131
|
+
flexShrink: 0
|
|
132
|
+
},
|
|
133
|
+
changeValue: {
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
color: "#e5e0d8",
|
|
136
|
+
flex: 1,
|
|
137
|
+
overflow: "hidden",
|
|
138
|
+
textOverflow: "ellipsis",
|
|
139
|
+
whiteSpace: "nowrap"
|
|
140
|
+
},
|
|
141
|
+
deployOk: {
|
|
142
|
+
color: "#10b981",
|
|
143
|
+
fontSize: 13
|
|
144
|
+
},
|
|
145
|
+
deployPendingTitle: {
|
|
146
|
+
color: "#f59e0b",
|
|
147
|
+
fontSize: 13,
|
|
148
|
+
marginBottom: 6
|
|
149
|
+
},
|
|
150
|
+
deployPendingItem: {
|
|
151
|
+
fontSize: 12,
|
|
152
|
+
color: "#888",
|
|
153
|
+
padding: "2px 0"
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/react/ContentEditToolbar.tsx
|
|
158
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
159
|
+
function ContentEditToolbar({
|
|
160
|
+
focusedField,
|
|
161
|
+
approvalState,
|
|
162
|
+
changes,
|
|
163
|
+
saving,
|
|
164
|
+
onApprove,
|
|
165
|
+
onRevoke,
|
|
166
|
+
onRevert,
|
|
167
|
+
onCheckDeploy
|
|
168
|
+
}) {
|
|
169
|
+
const [showChanges, setShowChanges] = useState(false);
|
|
170
|
+
const [deployResult, setDeployResult] = useState(
|
|
171
|
+
null
|
|
172
|
+
);
|
|
173
|
+
const [checkingDeploy, setCheckingDeploy] = useState(false);
|
|
174
|
+
const handleCheckDeploy = async () => {
|
|
175
|
+
setCheckingDeploy(true);
|
|
176
|
+
try {
|
|
177
|
+
const result = await onCheckDeploy();
|
|
178
|
+
setDeployResult(result);
|
|
179
|
+
} finally {
|
|
180
|
+
setCheckingDeploy(false);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
return /* @__PURE__ */ jsxs("div", { "data-feedtack-edit-ui": true, style: styles.toolbar, children: [
|
|
184
|
+
focusedField && /* @__PURE__ */ jsxs("div", { style: styles.fieldSection, children: [
|
|
185
|
+
/* @__PURE__ */ jsx("span", { style: styles.fieldPath, children: focusedField.fieldPath }),
|
|
186
|
+
saving === focusedField.fieldPath && /* @__PURE__ */ jsx("span", { style: styles.savingBadge, children: "saving\u2026" }),
|
|
187
|
+
approvalState?.stale === false ? /* @__PURE__ */ jsx(
|
|
188
|
+
"button",
|
|
189
|
+
{
|
|
190
|
+
type: "button",
|
|
191
|
+
style: styles.btnSecondary,
|
|
192
|
+
onClick: () => onRevoke(focusedField.fieldPath),
|
|
193
|
+
children: "Unaccept"
|
|
194
|
+
}
|
|
195
|
+
) : /* @__PURE__ */ jsx(
|
|
196
|
+
"button",
|
|
197
|
+
{
|
|
198
|
+
type: "button",
|
|
199
|
+
style: styles.btnPrimary,
|
|
200
|
+
onClick: () => onApprove(focusedField.fieldPath),
|
|
201
|
+
disabled: saving === focusedField.fieldPath,
|
|
202
|
+
children: "Approve"
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
] }),
|
|
206
|
+
/* @__PURE__ */ jsxs(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
type: "button",
|
|
210
|
+
style: styles.btnGhost,
|
|
211
|
+
onClick: () => {
|
|
212
|
+
setShowChanges((v) => !v);
|
|
213
|
+
setDeployResult(null);
|
|
214
|
+
},
|
|
215
|
+
children: [
|
|
216
|
+
"Changes (",
|
|
217
|
+
changes.length,
|
|
218
|
+
")"
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
/* @__PURE__ */ jsx(
|
|
223
|
+
"button",
|
|
224
|
+
{
|
|
225
|
+
type: "button",
|
|
226
|
+
style: deployResult?.approved ? styles.btnSuccess : styles.btnGhost,
|
|
227
|
+
onClick: handleCheckDeploy,
|
|
228
|
+
disabled: checkingDeploy,
|
|
229
|
+
children: checkingDeploy ? "Checking\u2026" : "Check deploy"
|
|
230
|
+
}
|
|
231
|
+
),
|
|
232
|
+
showChanges && changes.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles.panel, children: [
|
|
233
|
+
/* @__PURE__ */ jsx("div", { style: styles.panelTitle, children: "Session changes" }),
|
|
234
|
+
changes.map((c) => /* @__PURE__ */ jsxs("div", { style: styles.changeRow, children: [
|
|
235
|
+
/* @__PURE__ */ jsx("div", { style: styles.changePath, children: c.fieldPath }),
|
|
236
|
+
/* @__PURE__ */ jsxs("div", { style: styles.changeValue, title: c.to, children: [
|
|
237
|
+
c.to.slice(0, 60),
|
|
238
|
+
c.to.length > 60 ? "\u2026" : ""
|
|
239
|
+
] }),
|
|
240
|
+
/* @__PURE__ */ jsx(
|
|
241
|
+
"button",
|
|
242
|
+
{
|
|
243
|
+
type: "button",
|
|
244
|
+
style: styles.btnDanger,
|
|
245
|
+
onClick: () => onRevert(c.fieldPath),
|
|
246
|
+
disabled: saving === c.fieldPath,
|
|
247
|
+
children: "Revert"
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
] }, c.fieldPath))
|
|
251
|
+
] }),
|
|
252
|
+
deployResult && /* @__PURE__ */ jsx("div", { style: styles.panel, children: deployResult.approved ? /* @__PURE__ */ jsx("div", { style: styles.deployOk, children: "All fields approved \u2014 ready to deploy" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
253
|
+
/* @__PURE__ */ jsxs("div", { style: styles.deployPendingTitle, children: [
|
|
254
|
+
deployResult.pending.length,
|
|
255
|
+
" field",
|
|
256
|
+
deployResult.pending.length !== 1 ? "s" : "",
|
|
257
|
+
" need approval:"
|
|
258
|
+
] }),
|
|
259
|
+
deployResult.pending.map((p) => /* @__PURE__ */ jsx("div", { style: styles.deployPendingItem, children: p }, p))
|
|
260
|
+
] }) })
|
|
261
|
+
] });
|
|
262
|
+
}
|
|
5
263
|
|
|
6
264
|
// src/react/utils.ts
|
|
7
265
|
function getAnchoredPosition(x, y) {
|
|
@@ -22,7 +280,7 @@ function cx(...parts) {
|
|
|
22
280
|
}
|
|
23
281
|
|
|
24
282
|
// src/react/CommentForm.tsx
|
|
25
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
283
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
26
284
|
function CommentForm({
|
|
27
285
|
comment,
|
|
28
286
|
commentError,
|
|
@@ -36,14 +294,14 @@ function CommentForm({
|
|
|
36
294
|
onSubmit,
|
|
37
295
|
onCancel
|
|
38
296
|
}) {
|
|
39
|
-
return /* @__PURE__ */
|
|
297
|
+
return /* @__PURE__ */ jsxs2(
|
|
40
298
|
"div",
|
|
41
299
|
{
|
|
42
300
|
className: cx("feedtack-form", classes.form),
|
|
43
301
|
style: { position: "fixed", ...formPos },
|
|
44
302
|
children: [
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
/* @__PURE__ */
|
|
303
|
+
/* @__PURE__ */ jsx2("label", { htmlFor: "feedtack-comment", className: "feedtack-sr-only", children: "Feedback comment" }),
|
|
304
|
+
/* @__PURE__ */ jsx2(
|
|
47
305
|
"textarea",
|
|
48
306
|
{
|
|
49
307
|
id: "feedtack-comment",
|
|
@@ -62,9 +320,9 @@ function CommentForm({
|
|
|
62
320
|
"aria-invalid": commentError || void 0
|
|
63
321
|
}
|
|
64
322
|
),
|
|
65
|
-
commentError && /* @__PURE__ */
|
|
66
|
-
/* @__PURE__ */
|
|
67
|
-
/* @__PURE__ */
|
|
323
|
+
commentError && /* @__PURE__ */ jsx2("span", { id: "feedtack-comment-error", className: "feedtack-error-msg", children: "Comment is required" }),
|
|
324
|
+
/* @__PURE__ */ jsxs2("div", { className: "feedtack-sentiment", children: [
|
|
325
|
+
/* @__PURE__ */ jsx2(
|
|
68
326
|
"button",
|
|
69
327
|
{
|
|
70
328
|
type: "button",
|
|
@@ -73,7 +331,7 @@ function CommentForm({
|
|
|
73
331
|
children: sentimentLabels.satisfied ?? "Good"
|
|
74
332
|
}
|
|
75
333
|
),
|
|
76
|
-
/* @__PURE__ */
|
|
334
|
+
/* @__PURE__ */ jsx2(
|
|
77
335
|
"button",
|
|
78
336
|
{
|
|
79
337
|
type: "button",
|
|
@@ -83,8 +341,8 @@ function CommentForm({
|
|
|
83
341
|
}
|
|
84
342
|
)
|
|
85
343
|
] }),
|
|
86
|
-
/* @__PURE__ */
|
|
87
|
-
/* @__PURE__ */
|
|
344
|
+
/* @__PURE__ */ jsxs2("div", { className: "feedtack-form-actions", children: [
|
|
345
|
+
/* @__PURE__ */ jsx2(
|
|
88
346
|
"button",
|
|
89
347
|
{
|
|
90
348
|
type: "button",
|
|
@@ -93,7 +351,7 @@ function CommentForm({
|
|
|
93
351
|
children: "Cancel"
|
|
94
352
|
}
|
|
95
353
|
),
|
|
96
|
-
/* @__PURE__ */
|
|
354
|
+
/* @__PURE__ */ jsx2(
|
|
97
355
|
"button",
|
|
98
356
|
{
|
|
99
357
|
type: "button",
|
|
@@ -120,10 +378,10 @@ function useFeedtackContext() {
|
|
|
120
378
|
}
|
|
121
379
|
|
|
122
380
|
// src/react/FeedbackModal.tsx
|
|
123
|
-
import { useEffect, useRef } from "react";
|
|
381
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
124
382
|
|
|
125
383
|
// src/react/ThreadView.tsx
|
|
126
|
-
import { jsx as
|
|
384
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
127
385
|
function ThreadView({
|
|
128
386
|
item,
|
|
129
387
|
replyBody,
|
|
@@ -133,17 +391,17 @@ function ThreadView({
|
|
|
133
391
|
onArchive,
|
|
134
392
|
onBack
|
|
135
393
|
}) {
|
|
136
|
-
return /* @__PURE__ */
|
|
137
|
-
/* @__PURE__ */
|
|
138
|
-
/* @__PURE__ */
|
|
139
|
-
/* @__PURE__ */
|
|
140
|
-
/* @__PURE__ */
|
|
394
|
+
return /* @__PURE__ */ jsxs3("div", { className: "feedtack-modal-thread-view", children: [
|
|
395
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "feedtack-modal-back", onClick: onBack, children: "\u2190 Back" }),
|
|
396
|
+
/* @__PURE__ */ jsxs3("div", { className: "feedtack-modal-thread-content", children: [
|
|
397
|
+
/* @__PURE__ */ jsx3("strong", { children: item.payload.submittedBy.name }),
|
|
398
|
+
/* @__PURE__ */ jsx3("p", { children: item.payload.comment })
|
|
141
399
|
] }),
|
|
142
|
-
item.replies.map((r) => /* @__PURE__ */
|
|
143
|
-
/* @__PURE__ */
|
|
144
|
-
/* @__PURE__ */
|
|
400
|
+
item.replies.map((r) => /* @__PURE__ */ jsxs3("div", { className: "feedtack-modal-reply", children: [
|
|
401
|
+
/* @__PURE__ */ jsx3("span", { className: "feedtack-reply-author", children: r.author.name }),
|
|
402
|
+
/* @__PURE__ */ jsx3("p", { children: r.body })
|
|
145
403
|
] }, r.id)),
|
|
146
|
-
/* @__PURE__ */
|
|
404
|
+
/* @__PURE__ */ jsx3(
|
|
147
405
|
"textarea",
|
|
148
406
|
{
|
|
149
407
|
className: "feedtack-modal-textarea",
|
|
@@ -152,9 +410,9 @@ function ThreadView({
|
|
|
152
410
|
onChange: (e) => onReplyBodyChange(e.target.value)
|
|
153
411
|
}
|
|
154
412
|
),
|
|
155
|
-
/* @__PURE__ */
|
|
156
|
-
/* @__PURE__ */
|
|
157
|
-
/* @__PURE__ */
|
|
413
|
+
/* @__PURE__ */ jsxs3("div", { className: "feedtack-modal-actions", children: [
|
|
414
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "feedtack-btn-submit", onClick: onReply, children: "Reply" }),
|
|
415
|
+
/* @__PURE__ */ jsx3(
|
|
158
416
|
"button",
|
|
159
417
|
{
|
|
160
418
|
type: "button",
|
|
@@ -163,7 +421,7 @@ function ThreadView({
|
|
|
163
421
|
children: "Resolve"
|
|
164
422
|
}
|
|
165
423
|
),
|
|
166
|
-
/* @__PURE__ */
|
|
424
|
+
/* @__PURE__ */ jsx3(
|
|
167
425
|
"button",
|
|
168
426
|
{
|
|
169
427
|
type: "button",
|
|
@@ -177,7 +435,7 @@ function ThreadView({
|
|
|
177
435
|
}
|
|
178
436
|
|
|
179
437
|
// src/react/FeedbackModal.tsx
|
|
180
|
-
import { Fragment, jsx as
|
|
438
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
181
439
|
function FeedbackModal({
|
|
182
440
|
isOpen,
|
|
183
441
|
onClose,
|
|
@@ -201,175 +459,183 @@ function FeedbackModal({
|
|
|
201
459
|
openThreadId,
|
|
202
460
|
onOpenThread
|
|
203
461
|
}) {
|
|
204
|
-
const
|
|
462
|
+
const dialogRef = useRef(null);
|
|
205
463
|
useEffect(() => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
464
|
+
const dialog = dialogRef.current;
|
|
465
|
+
if (!dialog) return;
|
|
466
|
+
if (isOpen && !dialog.open) {
|
|
467
|
+
dialog.showModal();
|
|
468
|
+
} else if (!isOpen && dialog.open) {
|
|
469
|
+
dialog.close();
|
|
470
|
+
}
|
|
471
|
+
}, [isOpen]);
|
|
472
|
+
const handleCancel = useCallback(
|
|
473
|
+
(e) => {
|
|
474
|
+
e.preventDefault();
|
|
475
|
+
onClose();
|
|
476
|
+
},
|
|
477
|
+
[onClose]
|
|
478
|
+
);
|
|
479
|
+
const handleBackdropClick = useCallback(
|
|
480
|
+
(e) => {
|
|
481
|
+
if (e.target === dialogRef.current) {
|
|
214
482
|
onClose();
|
|
215
483
|
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return () => {
|
|
220
|
-
window.removeEventListener("keydown", onKey);
|
|
221
|
-
document.removeEventListener("mousedown", onDown);
|
|
222
|
-
};
|
|
223
|
-
}, [isOpen, onClose]);
|
|
484
|
+
},
|
|
485
|
+
[onClose]
|
|
486
|
+
);
|
|
224
487
|
if (!isOpen) return null;
|
|
225
488
|
const threads = activeTab === "site" ? siteFeedback : pageFeedback;
|
|
226
489
|
const openItem = openThreadId ? threads.find((i) => i.payload.id === openThreadId) : null;
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
/* @__PURE__ */
|
|
239
|
-
"
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
className: "feedtack-modal-close",
|
|
243
|
-
onClick: onClose,
|
|
244
|
-
"aria-label": "Close",
|
|
245
|
-
children: "\xD7"
|
|
246
|
-
}
|
|
247
|
-
)
|
|
248
|
-
] }),
|
|
249
|
-
/* @__PURE__ */ jsxs3("div", { className: "feedtack-modal-tabs", children: [
|
|
250
|
-
/* @__PURE__ */ jsxs3(
|
|
251
|
-
"button",
|
|
252
|
-
{
|
|
253
|
-
type: "button",
|
|
254
|
-
className: cx("feedtack-modal-tab", activeTab === "site" && "active"),
|
|
255
|
-
onClick: () => onTabChange("site"),
|
|
256
|
-
children: [
|
|
257
|
-
"Site",
|
|
258
|
-
siteFeedback.length > 0 && /* @__PURE__ */ jsx3("span", { className: "feedtack-tab-count", children: siteFeedback.length })
|
|
259
|
-
]
|
|
260
|
-
}
|
|
261
|
-
),
|
|
262
|
-
/* @__PURE__ */ jsxs3(
|
|
263
|
-
"button",
|
|
264
|
-
{
|
|
265
|
-
type: "button",
|
|
266
|
-
className: cx("feedtack-modal-tab", activeTab === "page" && "active"),
|
|
267
|
-
onClick: () => onTabChange("page"),
|
|
268
|
-
children: [
|
|
269
|
-
"Page",
|
|
270
|
-
pageFeedback.length > 0 && /* @__PURE__ */ jsx3("span", { className: "feedtack-tab-count", children: pageFeedback.length })
|
|
271
|
-
]
|
|
272
|
-
}
|
|
273
|
-
)
|
|
274
|
-
] }),
|
|
275
|
-
/* @__PURE__ */ jsx3("div", { className: "feedtack-modal-body", children: openItem ? /* @__PURE__ */ jsx3(
|
|
276
|
-
ThreadView,
|
|
277
|
-
{
|
|
278
|
-
item: openItem,
|
|
279
|
-
replyBody,
|
|
280
|
-
onReplyBodyChange,
|
|
281
|
-
onReply: () => onReply(openItem.payload.id),
|
|
282
|
-
onResolve: () => onResolve(openItem.payload.id),
|
|
283
|
-
onArchive: () => onArchive(openItem.payload.id),
|
|
284
|
-
onBack: () => onOpenThread(null)
|
|
285
|
-
}
|
|
286
|
-
) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
287
|
-
threads.length > 0 && /* @__PURE__ */ jsx3("div", { className: "feedtack-modal-threads", children: threads.map((item) => /* @__PURE__ */ jsxs3(
|
|
288
|
-
"button",
|
|
289
|
-
{
|
|
290
|
-
type: "button",
|
|
291
|
-
className: "feedtack-modal-thread-item",
|
|
292
|
-
onClick: () => onOpenThread(item.payload.id),
|
|
293
|
-
children: [
|
|
294
|
-
/* @__PURE__ */ jsx3("span", { className: "feedtack-thread-author", children: item.payload.submittedBy.name }),
|
|
295
|
-
/* @__PURE__ */ jsx3("span", { className: "feedtack-thread-comment", children: item.payload.comment }),
|
|
296
|
-
/* @__PURE__ */ jsxs3("span", { className: "feedtack-thread-meta", children: [
|
|
297
|
-
item.replies.length > 0 && `${item.replies.length} ${item.replies.length === 1 ? "reply" : "replies"}`,
|
|
298
|
-
item.resolutions.length > 0 && " \xB7 resolved"
|
|
299
|
-
] })
|
|
300
|
-
]
|
|
301
|
-
},
|
|
302
|
-
item.payload.id
|
|
303
|
-
)) }),
|
|
304
|
-
/* @__PURE__ */ jsxs3("div", { className: "feedtack-modal-compose", children: [
|
|
305
|
-
/* @__PURE__ */ jsx3(
|
|
306
|
-
"textarea",
|
|
490
|
+
return (
|
|
491
|
+
// biome-ignore lint/a11y/useKeyWithClickEvents: native <dialog> handles keyboard via onCancel (Escape)
|
|
492
|
+
/* @__PURE__ */ jsxs4(
|
|
493
|
+
"dialog",
|
|
494
|
+
{
|
|
495
|
+
ref: dialogRef,
|
|
496
|
+
className: "feedtack-modal",
|
|
497
|
+
"aria-label": "Feedback",
|
|
498
|
+
onCancel: handleCancel,
|
|
499
|
+
onClick: handleBackdropClick,
|
|
500
|
+
children: [
|
|
501
|
+
/* @__PURE__ */ jsxs4("div", { className: "feedtack-modal-header", children: [
|
|
502
|
+
/* @__PURE__ */ jsx4("span", { className: "feedtack-modal-title", children: "Feedback" }),
|
|
503
|
+
/* @__PURE__ */ jsx4(
|
|
504
|
+
"button",
|
|
307
505
|
{
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"
|
|
506
|
+
type: "button",
|
|
507
|
+
className: "feedtack-modal-close",
|
|
508
|
+
onClick: onClose,
|
|
509
|
+
"aria-label": "Close",
|
|
510
|
+
children: "\xD7"
|
|
511
|
+
}
|
|
512
|
+
)
|
|
513
|
+
] }),
|
|
514
|
+
/* @__PURE__ */ jsxs4("div", { className: "feedtack-modal-tabs", children: [
|
|
515
|
+
/* @__PURE__ */ jsxs4(
|
|
516
|
+
"button",
|
|
517
|
+
{
|
|
518
|
+
type: "button",
|
|
519
|
+
className: cx("feedtack-modal-tab", activeTab === "site" && "active"),
|
|
520
|
+
onClick: () => onTabChange("site"),
|
|
521
|
+
children: [
|
|
522
|
+
"Site",
|
|
523
|
+
siteFeedback.length > 0 && /* @__PURE__ */ jsx4("span", { className: "feedtack-tab-count", children: siteFeedback.length })
|
|
524
|
+
]
|
|
322
525
|
}
|
|
323
526
|
),
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
"button",
|
|
527
|
+
/* @__PURE__ */ jsxs4(
|
|
528
|
+
"button",
|
|
529
|
+
{
|
|
530
|
+
type: "button",
|
|
531
|
+
className: cx("feedtack-modal-tab", activeTab === "page" && "active"),
|
|
532
|
+
onClick: () => onTabChange("page"),
|
|
533
|
+
children: [
|
|
534
|
+
"Page",
|
|
535
|
+
pageFeedback.length > 0 && /* @__PURE__ */ jsx4("span", { className: "feedtack-tab-count", children: pageFeedback.length })
|
|
536
|
+
]
|
|
537
|
+
}
|
|
538
|
+
)
|
|
539
|
+
] }),
|
|
540
|
+
/* @__PURE__ */ jsx4("div", { className: "feedtack-modal-body", children: openItem ? /* @__PURE__ */ jsx4(
|
|
541
|
+
ThreadView,
|
|
542
|
+
{
|
|
543
|
+
item: openItem,
|
|
544
|
+
replyBody,
|
|
545
|
+
onReplyBodyChange,
|
|
546
|
+
onReply: () => onReply(openItem.payload.id),
|
|
547
|
+
onResolve: () => onResolve(openItem.payload.id),
|
|
548
|
+
onArchive: () => onArchive(openItem.payload.id),
|
|
549
|
+
onBack: () => onOpenThread(null)
|
|
550
|
+
}
|
|
551
|
+
) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
552
|
+
threads.length > 0 && /* @__PURE__ */ jsx4("div", { className: "feedtack-modal-threads", children: threads.map((item) => /* @__PURE__ */ jsxs4(
|
|
553
|
+
"button",
|
|
554
|
+
{
|
|
555
|
+
type: "button",
|
|
556
|
+
className: "feedtack-modal-thread-item",
|
|
557
|
+
onClick: () => onOpenThread(item.payload.id),
|
|
558
|
+
children: [
|
|
559
|
+
/* @__PURE__ */ jsx4("span", { className: "feedtack-thread-author", children: item.payload.submittedBy.name }),
|
|
560
|
+
/* @__PURE__ */ jsx4("span", { className: "feedtack-thread-comment", children: item.payload.comment }),
|
|
561
|
+
/* @__PURE__ */ jsxs4("span", { className: "feedtack-thread-meta", children: [
|
|
562
|
+
item.replies.length > 0 && `${item.replies.length} ${item.replies.length === 1 ? "reply" : "replies"}`,
|
|
563
|
+
item.resolutions.length > 0 && " \xB7 resolved"
|
|
564
|
+
] })
|
|
565
|
+
]
|
|
566
|
+
},
|
|
567
|
+
item.payload.id
|
|
568
|
+
)) }),
|
|
569
|
+
/* @__PURE__ */ jsxs4("div", { className: "feedtack-modal-compose", children: [
|
|
570
|
+
/* @__PURE__ */ jsx4(
|
|
571
|
+
"textarea",
|
|
328
572
|
{
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
573
|
+
className: cx(
|
|
574
|
+
"feedtack-modal-textarea",
|
|
575
|
+
commentError && "error"
|
|
576
|
+
),
|
|
577
|
+
placeholder: "What's on your mind? (required)",
|
|
578
|
+
value: comment,
|
|
579
|
+
onChange: (e) => onCommentChange(e.target.value),
|
|
580
|
+
onKeyDown: (e) => {
|
|
581
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
582
|
+
e.preventDefault();
|
|
583
|
+
onSubmit();
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
"aria-invalid": commentError || void 0
|
|
333
587
|
}
|
|
334
588
|
),
|
|
335
|
-
/* @__PURE__ */
|
|
589
|
+
commentError && /* @__PURE__ */ jsx4("span", { className: "feedtack-error-msg", children: "Comment is required" }),
|
|
590
|
+
/* @__PURE__ */ jsxs4("div", { className: "feedtack-sentiment", children: [
|
|
591
|
+
/* @__PURE__ */ jsx4(
|
|
592
|
+
"button",
|
|
593
|
+
{
|
|
594
|
+
type: "button",
|
|
595
|
+
className: sentiment === "good" ? "selected" : "",
|
|
596
|
+
onClick: () => onSentimentChange(sentiment === "good" ? null : "good"),
|
|
597
|
+
children: "Good"
|
|
598
|
+
}
|
|
599
|
+
),
|
|
600
|
+
/* @__PURE__ */ jsx4(
|
|
601
|
+
"button",
|
|
602
|
+
{
|
|
603
|
+
type: "button",
|
|
604
|
+
className: sentiment === "bad" ? "selected" : "",
|
|
605
|
+
onClick: () => onSentimentChange(sentiment === "bad" ? null : "bad"),
|
|
606
|
+
children: "Bad"
|
|
607
|
+
}
|
|
608
|
+
)
|
|
609
|
+
] }),
|
|
610
|
+
/* @__PURE__ */ jsx4(
|
|
336
611
|
"button",
|
|
337
612
|
{
|
|
338
613
|
type: "button",
|
|
339
|
-
className:
|
|
340
|
-
onClick:
|
|
341
|
-
|
|
614
|
+
className: "feedtack-btn-submit",
|
|
615
|
+
onClick: onSubmit,
|
|
616
|
+
disabled: submitting,
|
|
617
|
+
children: submitting ? "Sending\u2026" : "Submit"
|
|
342
618
|
}
|
|
343
619
|
)
|
|
344
|
-
] })
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
"button",
|
|
359
|
-
{
|
|
360
|
-
type: "button",
|
|
361
|
-
className: "feedtack-modal-pin-btn",
|
|
362
|
-
onClick: onPlacePin,
|
|
363
|
-
children: "Place a pin"
|
|
364
|
-
}
|
|
365
|
-
) })
|
|
366
|
-
]
|
|
367
|
-
}
|
|
620
|
+
] })
|
|
621
|
+
] }) }),
|
|
622
|
+
/* @__PURE__ */ jsx4("div", { className: "feedtack-modal-footer", children: /* @__PURE__ */ jsx4(
|
|
623
|
+
"button",
|
|
624
|
+
{
|
|
625
|
+
type: "button",
|
|
626
|
+
className: "feedtack-modal-pin-btn",
|
|
627
|
+
onClick: onPlacePin,
|
|
628
|
+
children: "Place a pin"
|
|
629
|
+
}
|
|
630
|
+
) })
|
|
631
|
+
]
|
|
632
|
+
}
|
|
633
|
+
)
|
|
368
634
|
);
|
|
369
635
|
}
|
|
370
636
|
|
|
371
637
|
// src/react/PinOverlay.tsx
|
|
372
|
-
import { Fragment as
|
|
638
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
373
639
|
function PinOverlay({
|
|
374
640
|
feedbackItems,
|
|
375
641
|
pathname,
|
|
@@ -382,10 +648,10 @@ function PinOverlay({
|
|
|
382
648
|
renderPinIcon,
|
|
383
649
|
pinMarkerClass
|
|
384
650
|
}) {
|
|
385
|
-
return /* @__PURE__ */
|
|
651
|
+
return /* @__PURE__ */ jsx5(Fragment3, { children: feedbackItems.filter((item) => item.payload.page.pathname === pathname).filter((item) => !isArchivedForUser(item)).filter((item) => hasValidPins(item)).map((item) => {
|
|
386
652
|
const pin = item.payload.pins[0];
|
|
387
653
|
const pos = getPosition(item.payload.id, pin);
|
|
388
|
-
return /* @__PURE__ */
|
|
654
|
+
return /* @__PURE__ */ jsxs5(
|
|
389
655
|
"button",
|
|
390
656
|
{
|
|
391
657
|
type: "button",
|
|
@@ -405,7 +671,7 @@ function PinOverlay({
|
|
|
405
671
|
openThreadId === item.payload.id ? null : item.payload.id
|
|
406
672
|
),
|
|
407
673
|
children: [
|
|
408
|
-
renderPinIcon ? /* @__PURE__ */
|
|
674
|
+
renderPinIcon ? /* @__PURE__ */ jsx5("span", { className: "feedtack-pin-icon", children: renderPinIcon(item) }) : item.resolutions.length > 0 && /* @__PURE__ */ jsx5(
|
|
409
675
|
"span",
|
|
410
676
|
{
|
|
411
677
|
className: "feedtack-pin-icon",
|
|
@@ -414,7 +680,7 @@ function PinOverlay({
|
|
|
414
680
|
children: "\u2713"
|
|
415
681
|
}
|
|
416
682
|
),
|
|
417
|
-
hasUnread(item) && /* @__PURE__ */
|
|
683
|
+
hasUnread(item) && /* @__PURE__ */ jsx5("div", { className: "feedtack-pin-badge" })
|
|
418
684
|
]
|
|
419
685
|
},
|
|
420
686
|
item.payload.id
|
|
@@ -423,7 +689,7 @@ function PinOverlay({
|
|
|
423
689
|
}
|
|
424
690
|
|
|
425
691
|
// src/react/ThreadPanel.tsx
|
|
426
|
-
import { jsx as
|
|
692
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
427
693
|
function ThreadPanel({
|
|
428
694
|
item,
|
|
429
695
|
replyBody,
|
|
@@ -439,26 +705,26 @@ function ThreadPanel({
|
|
|
439
705
|
if (!pin) return null;
|
|
440
706
|
const { x, y } = pinPosition ?? pin;
|
|
441
707
|
const pos = getAnchoredPosition(x, y);
|
|
442
|
-
return /* @__PURE__ */
|
|
708
|
+
return /* @__PURE__ */ jsxs6(
|
|
443
709
|
"div",
|
|
444
710
|
{
|
|
445
711
|
className: cx("feedtack-thread", className),
|
|
446
712
|
style: { position: "fixed", ...pos },
|
|
447
713
|
children: [
|
|
448
|
-
/* @__PURE__ */
|
|
449
|
-
/* @__PURE__ */
|
|
450
|
-
item.replies.map((r) => /* @__PURE__ */
|
|
714
|
+
/* @__PURE__ */ jsx6("strong", { style: { fontSize: 13 }, children: item.payload.submittedBy.name }),
|
|
715
|
+
/* @__PURE__ */ jsx6("p", { style: { fontSize: 13 }, children: item.payload.comment }),
|
|
716
|
+
item.replies.map((r) => /* @__PURE__ */ jsxs6(
|
|
451
717
|
"div",
|
|
452
718
|
{
|
|
453
719
|
style: { borderTop: "1px solid var(--ft-border)", paddingTop: 8 },
|
|
454
720
|
children: [
|
|
455
|
-
/* @__PURE__ */
|
|
456
|
-
/* @__PURE__ */
|
|
721
|
+
/* @__PURE__ */ jsx6("span", { style: { fontSize: 12, fontWeight: 600 }, children: r.author.name }),
|
|
722
|
+
/* @__PURE__ */ jsx6("p", { style: { fontSize: 12 }, children: r.body })
|
|
457
723
|
]
|
|
458
724
|
},
|
|
459
725
|
r.id
|
|
460
726
|
)),
|
|
461
|
-
/* @__PURE__ */
|
|
727
|
+
/* @__PURE__ */ jsx6(
|
|
462
728
|
"textarea",
|
|
463
729
|
{
|
|
464
730
|
placeholder: "Reply\u2026",
|
|
@@ -478,8 +744,8 @@ function ThreadPanel({
|
|
|
478
744
|
}
|
|
479
745
|
}
|
|
480
746
|
),
|
|
481
|
-
/* @__PURE__ */
|
|
482
|
-
/* @__PURE__ */
|
|
747
|
+
/* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [
|
|
748
|
+
/* @__PURE__ */ jsx6(
|
|
483
749
|
"button",
|
|
484
750
|
{
|
|
485
751
|
type: "button",
|
|
@@ -489,7 +755,7 @@ function ThreadPanel({
|
|
|
489
755
|
children: "Reply"
|
|
490
756
|
}
|
|
491
757
|
),
|
|
492
|
-
/* @__PURE__ */
|
|
758
|
+
/* @__PURE__ */ jsx6(
|
|
493
759
|
"button",
|
|
494
760
|
{
|
|
495
761
|
type: "button",
|
|
@@ -499,7 +765,7 @@ function ThreadPanel({
|
|
|
499
765
|
children: "Mark Resolved"
|
|
500
766
|
}
|
|
501
767
|
),
|
|
502
|
-
/* @__PURE__ */
|
|
768
|
+
/* @__PURE__ */ jsx6(
|
|
503
769
|
"button",
|
|
504
770
|
{
|
|
505
771
|
type: "button",
|
|
@@ -509,7 +775,7 @@ function ThreadPanel({
|
|
|
509
775
|
children: "Archive"
|
|
510
776
|
}
|
|
511
777
|
),
|
|
512
|
-
/* @__PURE__ */
|
|
778
|
+
/* @__PURE__ */ jsx6(
|
|
513
779
|
"button",
|
|
514
780
|
{
|
|
515
781
|
type: "button",
|
|
@@ -526,12 +792,12 @@ function ThreadPanel({
|
|
|
526
792
|
}
|
|
527
793
|
|
|
528
794
|
// src/react/useAnchoredPins.ts
|
|
529
|
-
import { useCallback, useEffect as useEffect2, useState } from "react";
|
|
795
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
530
796
|
function useAnchoredPins(items, pathname) {
|
|
531
|
-
const [positions, setPositions] =
|
|
797
|
+
const [positions, setPositions] = useState2(
|
|
532
798
|
/* @__PURE__ */ new Map()
|
|
533
799
|
);
|
|
534
|
-
const resolve =
|
|
800
|
+
const resolve = useCallback2(() => {
|
|
535
801
|
const next = /* @__PURE__ */ new Map();
|
|
536
802
|
for (const item of items) {
|
|
537
803
|
if (item.payload.page.pathname !== pathname) continue;
|
|
@@ -559,7 +825,7 @@ function useAnchoredPins(items, pathname) {
|
|
|
559
825
|
window.removeEventListener("scroll", handler);
|
|
560
826
|
};
|
|
561
827
|
}, [resolve]);
|
|
562
|
-
const getPosition =
|
|
828
|
+
const getPosition = useCallback2(
|
|
563
829
|
(itemId, fallbackPin) => {
|
|
564
830
|
return positions.get(itemId) ?? { x: fallbackPin.x, y: fallbackPin.y };
|
|
565
831
|
},
|
|
@@ -587,7 +853,7 @@ function resolvePin(pin) {
|
|
|
587
853
|
}
|
|
588
854
|
|
|
589
855
|
// src/react/useFeedtackState.ts
|
|
590
|
-
import { useCallback as
|
|
856
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useSyncExternalStore } from "react";
|
|
591
857
|
function useFeedtackState({
|
|
592
858
|
adapter,
|
|
593
859
|
currentUser,
|
|
@@ -618,25 +884,25 @@ function useFeedtackState({
|
|
|
618
884
|
engine.mount();
|
|
619
885
|
return () => engine.destroy();
|
|
620
886
|
}, [engine]);
|
|
621
|
-
const subscribe =
|
|
887
|
+
const subscribe = useCallback3(
|
|
622
888
|
(cb) => engine.subscribe(cb),
|
|
623
889
|
[engine]
|
|
624
890
|
);
|
|
625
|
-
const getSnapshot =
|
|
891
|
+
const getSnapshot = useCallback3(() => engine.getState(), [engine]);
|
|
626
892
|
const state = useSyncExternalStore(
|
|
627
893
|
subscribe,
|
|
628
894
|
getSnapshot,
|
|
629
895
|
getSnapshot
|
|
630
896
|
);
|
|
631
|
-
const isArchivedForUser =
|
|
897
|
+
const isArchivedForUser = useCallback3(
|
|
632
898
|
(item) => engine.isArchivedForUser(item),
|
|
633
899
|
[engine]
|
|
634
900
|
);
|
|
635
|
-
const hasUnread =
|
|
901
|
+
const hasUnread = useCallback3(
|
|
636
902
|
(item) => engine.hasUnread(item),
|
|
637
903
|
[engine]
|
|
638
904
|
);
|
|
639
|
-
const hasValidPins =
|
|
905
|
+
const hasValidPins = useCallback3(
|
|
640
906
|
(item) => engine.hasValidPins(item),
|
|
641
907
|
[engine]
|
|
642
908
|
);
|
|
@@ -644,27 +910,27 @@ function useFeedtackState({
|
|
|
644
910
|
// Pin mode
|
|
645
911
|
isPinModeActive: state.isPinModeActive,
|
|
646
912
|
isActive: state.isPinModeActive,
|
|
647
|
-
activatePinMode:
|
|
648
|
-
activate:
|
|
649
|
-
deactivatePinMode:
|
|
650
|
-
deactivate:
|
|
913
|
+
activatePinMode: useCallback3(() => engine.activatePinMode(), [engine]),
|
|
914
|
+
activate: useCallback3(() => engine.activatePinMode(), [engine]),
|
|
915
|
+
deactivatePinMode: useCallback3(() => engine.deactivatePinMode(), [engine]),
|
|
916
|
+
deactivate: useCallback3(() => engine.deactivatePinMode(), [engine]),
|
|
651
917
|
pendingPins: state.pendingPins,
|
|
652
918
|
selectedColor: state.selectedColor,
|
|
653
|
-
setSelectedColor:
|
|
919
|
+
setSelectedColor: useCallback3(
|
|
654
920
|
(c) => engine.setSelectedColor(c),
|
|
655
921
|
[engine]
|
|
656
922
|
),
|
|
657
923
|
showForm: state.showForm,
|
|
658
924
|
// Form
|
|
659
925
|
comment: state.comment,
|
|
660
|
-
setComment:
|
|
926
|
+
setComment: useCallback3((v) => engine.setComment(v), [engine]),
|
|
661
927
|
sentiment: state.sentiment,
|
|
662
|
-
setSentiment:
|
|
928
|
+
setSentiment: useCallback3(
|
|
663
929
|
(v) => engine.setSentiment(v),
|
|
664
930
|
[engine]
|
|
665
931
|
),
|
|
666
932
|
commentError: state.commentError,
|
|
667
|
-
setCommentError:
|
|
933
|
+
setCommentError: useCallback3(
|
|
668
934
|
(v) => engine.setCommentError(v),
|
|
669
935
|
[engine]
|
|
670
936
|
),
|
|
@@ -677,30 +943,30 @@ function useFeedtackState({
|
|
|
677
943
|
pathname: state.pathname,
|
|
678
944
|
// Thread
|
|
679
945
|
openThreadId: state.openThreadId,
|
|
680
|
-
setOpenThreadId:
|
|
946
|
+
setOpenThreadId: useCallback3(
|
|
681
947
|
(id) => engine.setOpenThreadId(id),
|
|
682
948
|
[engine]
|
|
683
949
|
),
|
|
684
950
|
replyBody: state.replyBody,
|
|
685
|
-
setReplyBody:
|
|
951
|
+
setReplyBody: useCallback3((v) => engine.setReplyBody(v), [engine]),
|
|
686
952
|
// Modal
|
|
687
953
|
isModalOpen: state.isModalOpen,
|
|
688
|
-
openModal:
|
|
689
|
-
closeModal:
|
|
954
|
+
openModal: useCallback3(() => engine.openModal(), [engine]),
|
|
955
|
+
closeModal: useCallback3(() => engine.closeModal(), [engine]),
|
|
690
956
|
composeScope: state.composeScope,
|
|
691
|
-
setComposeScope:
|
|
957
|
+
setComposeScope: useCallback3(
|
|
692
958
|
(s) => engine.setComposeScope(s),
|
|
693
959
|
[engine]
|
|
694
960
|
),
|
|
695
961
|
// Actions
|
|
696
|
-
handleSubmit:
|
|
697
|
-
handleModalSubmit:
|
|
698
|
-
handleReply:
|
|
699
|
-
handleResolve:
|
|
962
|
+
handleSubmit: useCallback3(() => engine.handleSubmit(), [engine]),
|
|
963
|
+
handleModalSubmit: useCallback3(() => engine.handleModalSubmit(), [engine]),
|
|
964
|
+
handleReply: useCallback3((id) => engine.handleReply(id), [engine]),
|
|
965
|
+
handleResolve: useCallback3(
|
|
700
966
|
(id) => engine.handleResolve(id),
|
|
701
967
|
[engine]
|
|
702
968
|
),
|
|
703
|
-
handleArchive:
|
|
969
|
+
handleArchive: useCallback3(
|
|
704
970
|
(id) => engine.handleArchive(id),
|
|
705
971
|
[engine]
|
|
706
972
|
),
|
|
@@ -712,7 +978,7 @@ function useFeedtackState({
|
|
|
712
978
|
}
|
|
713
979
|
|
|
714
980
|
// src/react/FeedtackProvider.tsx
|
|
715
|
-
import { jsx as
|
|
981
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
716
982
|
function FeedtackProvider({
|
|
717
983
|
children,
|
|
718
984
|
adapter,
|
|
@@ -749,7 +1015,7 @@ function FeedtackProvider({
|
|
|
749
1015
|
state.closeModal();
|
|
750
1016
|
state.activatePinMode();
|
|
751
1017
|
};
|
|
752
|
-
return /* @__PURE__ */
|
|
1018
|
+
return /* @__PURE__ */ jsxs7(
|
|
753
1019
|
FeedtackContext.Provider,
|
|
754
1020
|
{
|
|
755
1021
|
value: {
|
|
@@ -770,7 +1036,7 @@ function FeedtackProvider({
|
|
|
770
1036
|
},
|
|
771
1037
|
children: [
|
|
772
1038
|
children,
|
|
773
|
-
!disabled && showButton && /* @__PURE__ */
|
|
1039
|
+
!disabled && showButton && /* @__PURE__ */ jsx7(
|
|
774
1040
|
"button",
|
|
775
1041
|
{
|
|
776
1042
|
type: "button",
|
|
@@ -785,7 +1051,7 @@ function FeedtackProvider({
|
|
|
785
1051
|
children: "Feedback"
|
|
786
1052
|
}
|
|
787
1053
|
),
|
|
788
|
-
state.isPinModeActive && /* @__PURE__ */
|
|
1054
|
+
state.isPinModeActive && /* @__PURE__ */ jsx7("div", { className: cx("feedtack-color-picker", classes.colorPicker), children: PIN_PALETTE.map((color) => /* @__PURE__ */ jsx7(
|
|
789
1055
|
"button",
|
|
790
1056
|
{
|
|
791
1057
|
type: "button",
|
|
@@ -799,7 +1065,7 @@ function FeedtackProvider({
|
|
|
799
1065
|
},
|
|
800
1066
|
color
|
|
801
1067
|
)) }),
|
|
802
|
-
state.pendingPins.map((pin) => /* @__PURE__ */
|
|
1068
|
+
state.pendingPins.map((pin) => /* @__PURE__ */ jsx7(
|
|
803
1069
|
"div",
|
|
804
1070
|
{
|
|
805
1071
|
className: cx("feedtack-pin-marker", classes.pinMarker),
|
|
@@ -812,7 +1078,7 @@ function FeedtackProvider({
|
|
|
812
1078
|
},
|
|
813
1079
|
`${pin.x}-${pin.y}-${pin.color}`
|
|
814
1080
|
)),
|
|
815
|
-
state.showForm && /* @__PURE__ */
|
|
1081
|
+
state.showForm && /* @__PURE__ */ jsx7(
|
|
816
1082
|
CommentForm,
|
|
817
1083
|
{
|
|
818
1084
|
comment: state.comment,
|
|
@@ -831,7 +1097,7 @@ function FeedtackProvider({
|
|
|
831
1097
|
onCancel: state.deactivatePinMode
|
|
832
1098
|
}
|
|
833
1099
|
),
|
|
834
|
-
!state.loading && /* @__PURE__ */
|
|
1100
|
+
!state.loading && /* @__PURE__ */ jsx7(
|
|
835
1101
|
PinOverlay,
|
|
836
1102
|
{
|
|
837
1103
|
feedbackItems: state.feedbackItems,
|
|
@@ -846,7 +1112,7 @@ function FeedtackProvider({
|
|
|
846
1112
|
pinMarkerClass: classes.pinMarker
|
|
847
1113
|
}
|
|
848
1114
|
),
|
|
849
|
-
openItem && /* @__PURE__ */
|
|
1115
|
+
openItem && /* @__PURE__ */ jsx7(
|
|
850
1116
|
ThreadPanel,
|
|
851
1117
|
{
|
|
852
1118
|
item: openItem,
|
|
@@ -863,7 +1129,7 @@ function FeedtackProvider({
|
|
|
863
1129
|
className: classes.thread
|
|
864
1130
|
}
|
|
865
1131
|
),
|
|
866
|
-
!disabled && /* @__PURE__ */
|
|
1132
|
+
!disabled && /* @__PURE__ */ jsx7(
|
|
867
1133
|
FeedbackModal,
|
|
868
1134
|
{
|
|
869
1135
|
isOpen: state.isModalOpen,
|
|
@@ -892,18 +1158,350 @@ function FeedtackProvider({
|
|
|
892
1158
|
onOpenThread: state.setOpenThreadId
|
|
893
1159
|
}
|
|
894
1160
|
),
|
|
895
|
-
state.loading && /* @__PURE__ */
|
|
1161
|
+
state.loading && /* @__PURE__ */ jsx7("div", { className: "feedtack-loading", children: "Loading feedback\u2026" })
|
|
896
1162
|
]
|
|
897
1163
|
}
|
|
898
1164
|
);
|
|
899
1165
|
}
|
|
900
1166
|
|
|
1167
|
+
// src/react/useContentApproval.ts
|
|
1168
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useState as useState3 } from "react";
|
|
1169
|
+
function useContentApproval(adapter, userId, options) {
|
|
1170
|
+
const [fields, setFields] = useState3([]);
|
|
1171
|
+
const getContentForField = useCallback4(
|
|
1172
|
+
(fieldPath, domContent) => {
|
|
1173
|
+
return options?.storedValues?.get(fieldPath) ?? domContent;
|
|
1174
|
+
},
|
|
1175
|
+
[options?.storedValues]
|
|
1176
|
+
);
|
|
1177
|
+
const rescan = useCallback4(async () => {
|
|
1178
|
+
warnIfNotContentAdapter(adapter, "useContentApproval");
|
|
1179
|
+
if (!isContentAdapter(adapter)) {
|
|
1180
|
+
setFields([]);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const scanned = scanFields();
|
|
1184
|
+
const storedApprovals = await adapter.loadApprovals();
|
|
1185
|
+
const approvalMap = new Map(
|
|
1186
|
+
storedApprovals.map((s) => [s.fieldPath, s.approval])
|
|
1187
|
+
);
|
|
1188
|
+
const states = await Promise.all(
|
|
1189
|
+
scanned.map(async (f) => {
|
|
1190
|
+
const content = getContentForField(f.fieldPath, f.content);
|
|
1191
|
+
const currentHash = await hashField(content);
|
|
1192
|
+
const approval = approvalMap.get(f.fieldPath) ?? null;
|
|
1193
|
+
return {
|
|
1194
|
+
fieldPath: f.fieldPath,
|
|
1195
|
+
approval,
|
|
1196
|
+
stale: approval === null || approval.hash !== currentHash
|
|
1197
|
+
};
|
|
1198
|
+
})
|
|
1199
|
+
);
|
|
1200
|
+
setFields(states);
|
|
1201
|
+
}, [adapter, getContentForField]);
|
|
1202
|
+
useEffect4(() => {
|
|
1203
|
+
void rescan();
|
|
1204
|
+
}, [rescan]);
|
|
1205
|
+
const approve = useCallback4(
|
|
1206
|
+
async (fieldPath) => {
|
|
1207
|
+
warnIfNotContentAdapter(adapter, "approve");
|
|
1208
|
+
if (!isContentAdapter(adapter)) return;
|
|
1209
|
+
const scanned = scanFields();
|
|
1210
|
+
const field = scanned.find((f) => f.fieldPath === fieldPath);
|
|
1211
|
+
if (!field) return;
|
|
1212
|
+
const content = getContentForField(fieldPath, field.content);
|
|
1213
|
+
const hash = await hashField(content);
|
|
1214
|
+
const existing = fields.find((f) => f.fieldPath === fieldPath);
|
|
1215
|
+
const existingBy = existing?.approval?.by ?? [];
|
|
1216
|
+
const by = existingBy.includes(userId) ? existingBy : [...existingBy, userId];
|
|
1217
|
+
await adapter.approve(fieldPath, {
|
|
1218
|
+
hash,
|
|
1219
|
+
by,
|
|
1220
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1221
|
+
});
|
|
1222
|
+
setFields(
|
|
1223
|
+
(prev) => prev.map(
|
|
1224
|
+
(f) => f.fieldPath === fieldPath ? {
|
|
1225
|
+
...f,
|
|
1226
|
+
approval: { hash, by, at: (/* @__PURE__ */ new Date()).toISOString() },
|
|
1227
|
+
stale: false
|
|
1228
|
+
} : f
|
|
1229
|
+
)
|
|
1230
|
+
);
|
|
1231
|
+
},
|
|
1232
|
+
[adapter, userId, fields, getContentForField]
|
|
1233
|
+
);
|
|
1234
|
+
const revoke = useCallback4(
|
|
1235
|
+
async (fieldPath) => {
|
|
1236
|
+
warnIfNotContentAdapter(adapter, "revokeApproval");
|
|
1237
|
+
if (!isContentAdapter(adapter)) return;
|
|
1238
|
+
await adapter.revokeApproval(fieldPath, userId);
|
|
1239
|
+
setFields(
|
|
1240
|
+
(prev) => prev.map((f) => {
|
|
1241
|
+
if (f.fieldPath !== fieldPath || !f.approval) return f;
|
|
1242
|
+
const by = f.approval.by.filter((id) => id !== userId);
|
|
1243
|
+
const approval = by.length > 0 ? { ...f.approval, by } : null;
|
|
1244
|
+
return { ...f, approval, stale: true };
|
|
1245
|
+
})
|
|
1246
|
+
);
|
|
1247
|
+
},
|
|
1248
|
+
[adapter, userId]
|
|
1249
|
+
);
|
|
1250
|
+
const checkDeploy = useCallback4(async () => {
|
|
1251
|
+
warnIfNotContentAdapter(adapter, "checkDeploy");
|
|
1252
|
+
if (!isContentAdapter(adapter)) {
|
|
1253
|
+
return { approved: false, pending: [] };
|
|
1254
|
+
}
|
|
1255
|
+
const scanned = scanFields();
|
|
1256
|
+
const storedApprovals = await adapter.loadApprovals();
|
|
1257
|
+
const approvalMap = new Map(
|
|
1258
|
+
storedApprovals.map((s) => [s.fieldPath, s.approval])
|
|
1259
|
+
);
|
|
1260
|
+
const pending = [];
|
|
1261
|
+
await Promise.all(
|
|
1262
|
+
scanned.map(async (f) => {
|
|
1263
|
+
const content = getContentForField(f.fieldPath, f.content);
|
|
1264
|
+
const currentHash = await hashField(content);
|
|
1265
|
+
const approval = approvalMap.get(f.fieldPath) ?? null;
|
|
1266
|
+
if (approval === null || approval.hash !== currentHash) {
|
|
1267
|
+
pending.push(f.fieldPath);
|
|
1268
|
+
}
|
|
1269
|
+
})
|
|
1270
|
+
);
|
|
1271
|
+
return { approved: pending.length === 0, pending };
|
|
1272
|
+
}, [adapter, getContentForField]);
|
|
1273
|
+
return { fields, approve, revoke, rescan, checkDeploy };
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// src/react/useContentEdit.ts
|
|
1277
|
+
import { useCallback as useCallback5, useRef as useRef3, useState as useState4 } from "react";
|
|
1278
|
+
|
|
1279
|
+
// src/react/fieldDom.ts
|
|
1280
|
+
function getFieldValue(el) {
|
|
1281
|
+
if (isFormField(el)) {
|
|
1282
|
+
return el.placeholder || "";
|
|
1283
|
+
}
|
|
1284
|
+
return el.innerText;
|
|
1285
|
+
}
|
|
1286
|
+
function setFieldValue(el, value) {
|
|
1287
|
+
if (isFormField(el)) {
|
|
1288
|
+
;
|
|
1289
|
+
el.placeholder = value;
|
|
1290
|
+
} else {
|
|
1291
|
+
el.innerText = value;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function isFormField(el) {
|
|
1295
|
+
const tag = el.tagName.toLowerCase();
|
|
1296
|
+
return tag === "input" || tag === "textarea";
|
|
1297
|
+
}
|
|
1298
|
+
function findFieldElement(fieldPath) {
|
|
1299
|
+
return document.querySelector(
|
|
1300
|
+
`[data-feedtack-field="${fieldPath}"]`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
function buildToolbarProps(focusedField, approvalFields, changes, saving, approval, revert) {
|
|
1304
|
+
const approvalState = focusedField ? approvalFields.find((f) => f.fieldPath === focusedField.fieldPath) ?? null : null;
|
|
1305
|
+
return {
|
|
1306
|
+
focusedField,
|
|
1307
|
+
approvalState,
|
|
1308
|
+
changes,
|
|
1309
|
+
saving,
|
|
1310
|
+
onApprove: approval.approve,
|
|
1311
|
+
onRevoke: approval.revoke,
|
|
1312
|
+
onRevert: revert,
|
|
1313
|
+
onCheckDeploy: approval.checkDeploy
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// src/react/useContentEdit.ts
|
|
1318
|
+
function useContentEdit(adapter, userId) {
|
|
1319
|
+
const [active, setActive] = useState4(false);
|
|
1320
|
+
const [changes, setChanges] = useState4([]);
|
|
1321
|
+
const [saving, setSaving] = useState4(null);
|
|
1322
|
+
const [focusedField, setFocusedField] = useState4(
|
|
1323
|
+
null
|
|
1324
|
+
);
|
|
1325
|
+
const storedValuesRef = useRef3(/* @__PURE__ */ new Map());
|
|
1326
|
+
const [storedValuesVersion, setStoredValuesVersion] = useState4(0);
|
|
1327
|
+
const storedValuesMap = storedValuesRef.current;
|
|
1328
|
+
const approval = useContentApproval(adapter, userId, {
|
|
1329
|
+
storedValues: storedValuesMap
|
|
1330
|
+
});
|
|
1331
|
+
const observerRef = useRef3(null);
|
|
1332
|
+
const boundFieldsRef = useRef3(/* @__PURE__ */ new Set());
|
|
1333
|
+
const changesRef = useRef3([]);
|
|
1334
|
+
const updateStoredValue = useCallback5((fieldPath, value) => {
|
|
1335
|
+
storedValuesRef.current.set(fieldPath, value);
|
|
1336
|
+
setStoredValuesVersion((v) => v + 1);
|
|
1337
|
+
}, []);
|
|
1338
|
+
const handleFocus = useCallback5((e) => {
|
|
1339
|
+
const el = e.target;
|
|
1340
|
+
const fieldPath = el.dataset.feedtackField;
|
|
1341
|
+
if (!fieldPath) return;
|
|
1342
|
+
const storedValue = storedValuesRef.current.get(fieldPath) ?? getFieldValue(el);
|
|
1343
|
+
el.dataset.feedtackOriginal = storedValue;
|
|
1344
|
+
setFocusedField({ element: el, fieldPath });
|
|
1345
|
+
}, []);
|
|
1346
|
+
const handleBlur = useCallback5(
|
|
1347
|
+
async (e) => {
|
|
1348
|
+
const el = e.target;
|
|
1349
|
+
const fieldPath = el.dataset.feedtackField;
|
|
1350
|
+
if (!fieldPath) return;
|
|
1351
|
+
setTimeout(() => {
|
|
1352
|
+
const f = document.activeElement;
|
|
1353
|
+
if (!f || f === document.body || f.closest?.("[data-feedtack-edit-ui]"))
|
|
1354
|
+
return;
|
|
1355
|
+
setFocusedField(null);
|
|
1356
|
+
}, 150);
|
|
1357
|
+
const value = getFieldValue(el);
|
|
1358
|
+
const original = el.dataset.feedtackOriginal ?? "";
|
|
1359
|
+
if (value === original) return;
|
|
1360
|
+
if (!isContentEditAdapter(adapter)) return;
|
|
1361
|
+
setSaving(fieldPath);
|
|
1362
|
+
try {
|
|
1363
|
+
await adapter.saveField(fieldPath, value);
|
|
1364
|
+
updateStoredValue(fieldPath, value);
|
|
1365
|
+
const existing = changesRef.current.find(
|
|
1366
|
+
(c) => c.fieldPath === fieldPath
|
|
1367
|
+
);
|
|
1368
|
+
if (existing) {
|
|
1369
|
+
changesRef.current = changesRef.current.map(
|
|
1370
|
+
(c) => c.fieldPath === fieldPath ? { ...c, to: value, savedAt: Date.now() } : c
|
|
1371
|
+
);
|
|
1372
|
+
} else {
|
|
1373
|
+
changesRef.current = [
|
|
1374
|
+
...changesRef.current,
|
|
1375
|
+
{ fieldPath, from: original, to: value, savedAt: Date.now() }
|
|
1376
|
+
];
|
|
1377
|
+
}
|
|
1378
|
+
setChanges([...changesRef.current]);
|
|
1379
|
+
void approval.rescan();
|
|
1380
|
+
} catch {
|
|
1381
|
+
setFieldValue(el, original);
|
|
1382
|
+
}
|
|
1383
|
+
setTimeout(() => setSaving(null), 1500);
|
|
1384
|
+
},
|
|
1385
|
+
[adapter, approval, updateStoredValue]
|
|
1386
|
+
);
|
|
1387
|
+
const bindField = useCallback5(
|
|
1388
|
+
(el) => {
|
|
1389
|
+
if (boundFieldsRef.current.has(el)) return;
|
|
1390
|
+
if (el.querySelector("[data-feedtack-field]")) return;
|
|
1391
|
+
boundFieldsRef.current.add(el);
|
|
1392
|
+
if (isFormField(el)) {
|
|
1393
|
+
el.style.cursor = "pointer";
|
|
1394
|
+
} else {
|
|
1395
|
+
el.setAttribute("contenteditable", "true");
|
|
1396
|
+
el.setAttribute("spellcheck", "true");
|
|
1397
|
+
}
|
|
1398
|
+
el.addEventListener("focus", handleFocus);
|
|
1399
|
+
el.addEventListener("blur", handleBlur);
|
|
1400
|
+
},
|
|
1401
|
+
[handleFocus, handleBlur]
|
|
1402
|
+
);
|
|
1403
|
+
const unbindField = useCallback5(
|
|
1404
|
+
(el) => {
|
|
1405
|
+
boundFieldsRef.current.delete(el);
|
|
1406
|
+
el.removeAttribute("contenteditable");
|
|
1407
|
+
el.removeAttribute("spellcheck");
|
|
1408
|
+
el.style.cursor = "";
|
|
1409
|
+
delete el.dataset.feedtackOriginal;
|
|
1410
|
+
el.removeEventListener("focus", handleFocus);
|
|
1411
|
+
el.removeEventListener("blur", handleBlur);
|
|
1412
|
+
},
|
|
1413
|
+
[handleFocus, handleBlur]
|
|
1414
|
+
);
|
|
1415
|
+
const setupFields = useCallback5(() => {
|
|
1416
|
+
document.querySelectorAll("[data-feedtack-field]").forEach(bindField);
|
|
1417
|
+
}, [bindField]);
|
|
1418
|
+
const activate = useCallback5(async () => {
|
|
1419
|
+
warnIfNotContentEditAdapter(adapter, "activate");
|
|
1420
|
+
if (!isContentEditAdapter(adapter)) return;
|
|
1421
|
+
document.body.setAttribute("data-feedtack-hydrating", "true");
|
|
1422
|
+
try {
|
|
1423
|
+
const fields = await adapter.loadFields();
|
|
1424
|
+
for (const [fp, value] of Object.entries(fields)) {
|
|
1425
|
+
storedValuesRef.current.set(fp, value);
|
|
1426
|
+
const el = findFieldElement(fp);
|
|
1427
|
+
if (el) setFieldValue(el, value);
|
|
1428
|
+
}
|
|
1429
|
+
setStoredValuesVersion((v) => v + 1);
|
|
1430
|
+
} finally {
|
|
1431
|
+
document.body.removeAttribute("data-feedtack-hydrating");
|
|
1432
|
+
}
|
|
1433
|
+
setupFields();
|
|
1434
|
+
const observer = new MutationObserver(() => setupFields());
|
|
1435
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1436
|
+
observerRef.current = observer;
|
|
1437
|
+
setActive(true);
|
|
1438
|
+
void approval.rescan();
|
|
1439
|
+
}, [adapter, setupFields, approval]);
|
|
1440
|
+
const deactivate = useCallback5(() => {
|
|
1441
|
+
observerRef.current?.disconnect();
|
|
1442
|
+
observerRef.current = null;
|
|
1443
|
+
boundFieldsRef.current.forEach(unbindField);
|
|
1444
|
+
boundFieldsRef.current.clear();
|
|
1445
|
+
setActive(false);
|
|
1446
|
+
setFocusedField(null);
|
|
1447
|
+
}, [unbindField]);
|
|
1448
|
+
const revert = useCallback5(
|
|
1449
|
+
async (fieldPath) => {
|
|
1450
|
+
warnIfNotContentEditAdapter(adapter, "revert");
|
|
1451
|
+
if (!isContentEditAdapter(adapter)) return;
|
|
1452
|
+
const change = changesRef.current.find((c) => c.fieldPath === fieldPath);
|
|
1453
|
+
if (!change) return;
|
|
1454
|
+
setSaving(fieldPath);
|
|
1455
|
+
try {
|
|
1456
|
+
await adapter.saveField(fieldPath, change.from);
|
|
1457
|
+
updateStoredValue(fieldPath, change.from);
|
|
1458
|
+
const el = findFieldElement(fieldPath);
|
|
1459
|
+
if (el) setFieldValue(el, change.from);
|
|
1460
|
+
changesRef.current = changesRef.current.filter(
|
|
1461
|
+
(c) => c.fieldPath !== fieldPath
|
|
1462
|
+
);
|
|
1463
|
+
setChanges([...changesRef.current]);
|
|
1464
|
+
void approval.rescan();
|
|
1465
|
+
} finally {
|
|
1466
|
+
setTimeout(() => setSaving(null), 1500);
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
[adapter, approval, updateStoredValue]
|
|
1470
|
+
);
|
|
1471
|
+
const toolbarProps = buildToolbarProps(
|
|
1472
|
+
focusedField,
|
|
1473
|
+
approval.fields,
|
|
1474
|
+
changes,
|
|
1475
|
+
saving,
|
|
1476
|
+
approval,
|
|
1477
|
+
revert
|
|
1478
|
+
);
|
|
1479
|
+
return {
|
|
1480
|
+
active,
|
|
1481
|
+
activate,
|
|
1482
|
+
deactivate,
|
|
1483
|
+
changes,
|
|
1484
|
+
revert,
|
|
1485
|
+
saving,
|
|
1486
|
+
focusedField,
|
|
1487
|
+
toolbarProps,
|
|
1488
|
+
fields: approval.fields,
|
|
1489
|
+
approve: approval.approve,
|
|
1490
|
+
revoke: approval.revoke,
|
|
1491
|
+
rescan: approval.rescan,
|
|
1492
|
+
checkDeploy: approval.checkDeploy
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
|
|
901
1496
|
// src/react/useFeedtack.ts
|
|
902
1497
|
function useFeedtack() {
|
|
903
1498
|
return useFeedtackContext();
|
|
904
1499
|
}
|
|
905
1500
|
export {
|
|
1501
|
+
ContentEditToolbar,
|
|
906
1502
|
FeedtackProvider,
|
|
907
1503
|
PIN_PALETTE,
|
|
1504
|
+
useContentApproval,
|
|
1505
|
+
useContentEdit,
|
|
908
1506
|
useFeedtack
|
|
909
1507
|
};
|