feedtack 0.4.0 → 0.5.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/dist/react/index.js +257 -147
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -155,11 +155,13 @@ function ThreadPanel({
|
|
|
155
155
|
onResolve,
|
|
156
156
|
onArchive,
|
|
157
157
|
onClose,
|
|
158
|
-
className
|
|
158
|
+
className,
|
|
159
|
+
pinPosition
|
|
159
160
|
}) {
|
|
160
161
|
const pin = item.payload?.pins?.[0];
|
|
161
162
|
if (!pin) return null;
|
|
162
|
-
const
|
|
163
|
+
const { x, y } = pinPosition ?? pin;
|
|
164
|
+
const pos = getAnchoredPosition(x, y);
|
|
163
165
|
return /* @__PURE__ */ jsxs2(
|
|
164
166
|
"div",
|
|
165
167
|
{
|
|
@@ -246,11 +248,195 @@ function ThreadPanel({
|
|
|
246
248
|
);
|
|
247
249
|
}
|
|
248
250
|
|
|
251
|
+
// src/react/useAnchoredPins.ts
|
|
252
|
+
import { useCallback, useEffect, useState } from "react";
|
|
253
|
+
function useAnchoredPins(items, pathname) {
|
|
254
|
+
const [positions, setPositions] = useState(
|
|
255
|
+
/* @__PURE__ */ new Map()
|
|
256
|
+
);
|
|
257
|
+
const resolve = useCallback(() => {
|
|
258
|
+
const next = /* @__PURE__ */ new Map();
|
|
259
|
+
for (const item of items) {
|
|
260
|
+
if (item.payload.page.pathname !== pathname) continue;
|
|
261
|
+
const pin = item.payload.pins[0];
|
|
262
|
+
if (!pin) continue;
|
|
263
|
+
const pos = resolvePin(pin);
|
|
264
|
+
next.set(item.payload.id, pos);
|
|
265
|
+
}
|
|
266
|
+
setPositions(next);
|
|
267
|
+
}, [items, pathname]);
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
resolve();
|
|
270
|
+
}, [resolve]);
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
let raf = 0;
|
|
273
|
+
const handler = () => {
|
|
274
|
+
cancelAnimationFrame(raf);
|
|
275
|
+
raf = requestAnimationFrame(resolve);
|
|
276
|
+
};
|
|
277
|
+
window.addEventListener("resize", handler, { passive: true });
|
|
278
|
+
window.addEventListener("scroll", handler, { passive: true });
|
|
279
|
+
return () => {
|
|
280
|
+
cancelAnimationFrame(raf);
|
|
281
|
+
window.removeEventListener("resize", handler);
|
|
282
|
+
window.removeEventListener("scroll", handler);
|
|
283
|
+
};
|
|
284
|
+
}, [resolve]);
|
|
285
|
+
const getPosition = useCallback(
|
|
286
|
+
(itemId, fallbackPin) => {
|
|
287
|
+
return positions.get(itemId) ?? { x: fallbackPin.x, y: fallbackPin.y };
|
|
288
|
+
},
|
|
289
|
+
[positions]
|
|
290
|
+
);
|
|
291
|
+
return { getPosition };
|
|
292
|
+
}
|
|
293
|
+
function resolvePin(pin) {
|
|
294
|
+
const { target } = pin;
|
|
295
|
+
if (!target.selector) return { x: pin.x, y: pin.y };
|
|
296
|
+
let el = null;
|
|
297
|
+
try {
|
|
298
|
+
el = document.querySelector(target.selector);
|
|
299
|
+
} catch {
|
|
300
|
+
return { x: pin.x, y: pin.y };
|
|
301
|
+
}
|
|
302
|
+
if (!el) return { x: pin.x, y: pin.y };
|
|
303
|
+
const rect = el.getBoundingClientRect();
|
|
304
|
+
const origRect = target.boundingRect;
|
|
305
|
+
const ratioX = origRect.width > 0 ? (pin.x - origRect.x) / origRect.width : 0.5;
|
|
306
|
+
const ratioY = origRect.height > 0 ? (pin.y - origRect.y) / origRect.height : 0.5;
|
|
307
|
+
const x = rect.x + window.scrollX + ratioX * rect.width;
|
|
308
|
+
const y = rect.y + window.scrollY + ratioY * rect.height;
|
|
309
|
+
return { x, y };
|
|
310
|
+
}
|
|
311
|
+
|
|
249
312
|
// src/react/useFeedtackState.ts
|
|
250
|
-
import { useCallback as
|
|
313
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useState as useState3 } from "react";
|
|
314
|
+
|
|
315
|
+
// src/react/useFeedtackActions.ts
|
|
316
|
+
import { useCallback as useCallback2 } from "react";
|
|
317
|
+
function useFeedtackActions(deps) {
|
|
318
|
+
const { adapter, currentUser, onError } = deps;
|
|
319
|
+
const updateItem = useCallback2(
|
|
320
|
+
(id, fn) => deps.setFeedbackItems(
|
|
321
|
+
(prev) => prev.map((i) => i.payload.id === id ? fn(i) : i)
|
|
322
|
+
),
|
|
323
|
+
[deps.setFeedbackItems]
|
|
324
|
+
);
|
|
325
|
+
const handleSubmit = useCallback2(async () => {
|
|
326
|
+
const comment = deps.getComment();
|
|
327
|
+
if (!comment.trim()) {
|
|
328
|
+
deps.setCommentError(true);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
deps.setSubmitting(true);
|
|
332
|
+
const payload = {
|
|
333
|
+
schemaVersion: SCHEMA_VERSION,
|
|
334
|
+
id: generateId(),
|
|
335
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
336
|
+
submittedBy: currentUser,
|
|
337
|
+
comment: comment.trim(),
|
|
338
|
+
sentiment: deps.getSentiment(),
|
|
339
|
+
pins: deps.getPendingPins().map((p, i) => ({ ...p, index: i + 1 })),
|
|
340
|
+
page: getPageMeta(),
|
|
341
|
+
viewport: getViewportMeta(),
|
|
342
|
+
device: getDeviceMeta()
|
|
343
|
+
};
|
|
344
|
+
try {
|
|
345
|
+
await adapter.submit(payload);
|
|
346
|
+
deps.setFeedbackItems((prev) => [
|
|
347
|
+
...prev,
|
|
348
|
+
{ payload, replies: [], resolutions: [], archives: [] }
|
|
349
|
+
]);
|
|
350
|
+
deps.deactivatePinMode();
|
|
351
|
+
} catch (err) {
|
|
352
|
+
onError?.(err);
|
|
353
|
+
} finally {
|
|
354
|
+
deps.setSubmitting(false);
|
|
355
|
+
}
|
|
356
|
+
}, [adapter, currentUser, onError, deps]);
|
|
357
|
+
const handleReply = useCallback2(
|
|
358
|
+
async (feedbackId) => {
|
|
359
|
+
const body = deps.getReplyBody().trim();
|
|
360
|
+
if (!body) return;
|
|
361
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
362
|
+
try {
|
|
363
|
+
await adapter.reply(feedbackId, {
|
|
364
|
+
author: currentUser,
|
|
365
|
+
body,
|
|
366
|
+
timestamp: ts
|
|
367
|
+
});
|
|
368
|
+
updateItem(feedbackId, (item) => {
|
|
369
|
+
const updated = {
|
|
370
|
+
...item,
|
|
371
|
+
replies: [
|
|
372
|
+
...item.replies,
|
|
373
|
+
{
|
|
374
|
+
id: generateId(),
|
|
375
|
+
feedbackId,
|
|
376
|
+
author: currentUser,
|
|
377
|
+
body,
|
|
378
|
+
timestamp: ts
|
|
379
|
+
}
|
|
380
|
+
]
|
|
381
|
+
};
|
|
382
|
+
const rescope = deps.shouldRescope?.(currentUser.role) ?? currentUser.role !== "agent";
|
|
383
|
+
if (rescope && updated.resolutions.length === 0 && deps.hasFlush) {
|
|
384
|
+
deps.clearFlushed?.(deps.getPathname());
|
|
385
|
+
}
|
|
386
|
+
return updated;
|
|
387
|
+
});
|
|
388
|
+
deps.setReplyBody("");
|
|
389
|
+
} catch (err) {
|
|
390
|
+
onError?.(err);
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
[adapter, currentUser, onError, updateItem, deps]
|
|
394
|
+
);
|
|
395
|
+
const handleResolve = useCallback2(
|
|
396
|
+
async (feedbackId) => {
|
|
397
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
398
|
+
try {
|
|
399
|
+
await adapter.resolve(feedbackId, {
|
|
400
|
+
resolvedBy: currentUser,
|
|
401
|
+
timestamp: ts
|
|
402
|
+
});
|
|
403
|
+
updateItem(feedbackId, (item) => ({
|
|
404
|
+
...item,
|
|
405
|
+
resolutions: [
|
|
406
|
+
...item.resolutions,
|
|
407
|
+
{ feedbackId, resolvedBy: currentUser, timestamp: ts }
|
|
408
|
+
]
|
|
409
|
+
}));
|
|
410
|
+
} catch (err) {
|
|
411
|
+
onError?.(err);
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
[adapter, currentUser, onError, updateItem]
|
|
415
|
+
);
|
|
416
|
+
const handleArchive = useCallback2(
|
|
417
|
+
async (feedbackId) => {
|
|
418
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
419
|
+
try {
|
|
420
|
+
await adapter.archive(feedbackId, currentUser.id);
|
|
421
|
+
updateItem(feedbackId, (item) => ({
|
|
422
|
+
...item,
|
|
423
|
+
archives: [
|
|
424
|
+
...item.archives,
|
|
425
|
+
{ feedbackId, archivedBy: currentUser, timestamp: ts }
|
|
426
|
+
]
|
|
427
|
+
}));
|
|
428
|
+
deps.setOpenThreadId(null);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
onError?.(err);
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
[adapter, currentUser, onError, updateItem, deps]
|
|
434
|
+
);
|
|
435
|
+
return { handleSubmit, handleReply, handleResolve, handleArchive };
|
|
436
|
+
}
|
|
251
437
|
|
|
252
438
|
// src/react/useFeedtackDom.ts
|
|
253
|
-
import { useEffect, useRef } from "react";
|
|
439
|
+
import { useEffect as useEffect2, useRef } from "react";
|
|
254
440
|
|
|
255
441
|
// src/ui/styles.ts
|
|
256
442
|
var FEEDTACK_DEFAULT_TOKENS = `
|
|
@@ -503,7 +689,7 @@ var FEEDTACK_STYLES = `
|
|
|
503
689
|
// src/react/useFeedtackDom.ts
|
|
504
690
|
function useFeedtackDom(theme, disabled) {
|
|
505
691
|
const rootRef = useRef(null);
|
|
506
|
-
|
|
692
|
+
useEffect2(() => {
|
|
507
693
|
if (disabled) return;
|
|
508
694
|
if (document.getElementById("feedtack-styles")) return;
|
|
509
695
|
const style = document.createElement("style");
|
|
@@ -514,7 +700,7 @@ function useFeedtackDom(theme, disabled) {
|
|
|
514
700
|
style.remove();
|
|
515
701
|
};
|
|
516
702
|
}, [disabled]);
|
|
517
|
-
|
|
703
|
+
useEffect2(() => {
|
|
518
704
|
if (disabled) return;
|
|
519
705
|
const root = document.createElement("div");
|
|
520
706
|
root.id = "feedtack-root";
|
|
@@ -524,7 +710,7 @@ function useFeedtackDom(theme, disabled) {
|
|
|
524
710
|
root.remove();
|
|
525
711
|
};
|
|
526
712
|
}, [disabled]);
|
|
527
|
-
|
|
713
|
+
useEffect2(() => {
|
|
528
714
|
if (disabled) return;
|
|
529
715
|
const root = document.getElementById("feedtack-root");
|
|
530
716
|
if (!root || !theme) return;
|
|
@@ -537,7 +723,7 @@ function useFeedtackDom(theme, disabled) {
|
|
|
537
723
|
}
|
|
538
724
|
|
|
539
725
|
// src/react/useFeedtackFlush.ts
|
|
540
|
-
import { useCallback, useEffect as
|
|
726
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
541
727
|
var DEFAULT_IDLE_MS = 5 * 60 * 1e3;
|
|
542
728
|
function useFeedtackFlush({
|
|
543
729
|
pathname,
|
|
@@ -549,7 +735,7 @@ function useFeedtackFlush({
|
|
|
549
735
|
const flushedRef = useRef2(/* @__PURE__ */ new Set());
|
|
550
736
|
const prevPathnameRef = useRef2(pathname);
|
|
551
737
|
const idleTimerRef = useRef2(null);
|
|
552
|
-
const flush =
|
|
738
|
+
const flush = useCallback3(
|
|
553
739
|
(path, items) => {
|
|
554
740
|
if (!onFlush || flushedRef.current.has(path)) return;
|
|
555
741
|
const pageItems = items.filter((i) => i.payload.page.pathname === path);
|
|
@@ -559,7 +745,7 @@ function useFeedtackFlush({
|
|
|
559
745
|
},
|
|
560
746
|
[onFlush]
|
|
561
747
|
);
|
|
562
|
-
|
|
748
|
+
useEffect3(() => {
|
|
563
749
|
if (disabled || !onFlush) return;
|
|
564
750
|
const prev = prevPathnameRef.current;
|
|
565
751
|
prevPathnameRef.current = pathname;
|
|
@@ -567,7 +753,7 @@ function useFeedtackFlush({
|
|
|
567
753
|
flush(prev, feedbackItems);
|
|
568
754
|
}
|
|
569
755
|
}, [pathname, feedbackItems, flush, onFlush, disabled]);
|
|
570
|
-
|
|
756
|
+
useEffect3(() => {
|
|
571
757
|
if (disabled || !onFlush || flushIdleMs <= 0) return;
|
|
572
758
|
const resetTimer = () => {
|
|
573
759
|
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
|
|
@@ -584,38 +770,38 @@ function useFeedtackFlush({
|
|
|
584
770
|
for (const e of events) window.removeEventListener(e, resetTimer);
|
|
585
771
|
};
|
|
586
772
|
}, [pathname, feedbackItems, flush, onFlush, flushIdleMs, disabled]);
|
|
587
|
-
|
|
773
|
+
useEffect3(() => {
|
|
588
774
|
if (disabled || !onFlush) return;
|
|
589
775
|
const handleUnload = () => flush(pathname, feedbackItems);
|
|
590
776
|
window.addEventListener("beforeunload", handleUnload);
|
|
591
777
|
return () => window.removeEventListener("beforeunload", handleUnload);
|
|
592
778
|
}, [pathname, feedbackItems, flush, onFlush, disabled]);
|
|
593
|
-
const clearFlushed =
|
|
779
|
+
const clearFlushed = useCallback3((path) => {
|
|
594
780
|
flushedRef.current.delete(path);
|
|
595
781
|
}, []);
|
|
596
782
|
return { clearFlushed };
|
|
597
783
|
}
|
|
598
784
|
|
|
599
785
|
// src/react/usePinMode.ts
|
|
600
|
-
import { useCallback as
|
|
786
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useState as useState2 } from "react";
|
|
601
787
|
function usePinMode({
|
|
602
788
|
hotkey,
|
|
603
789
|
onDeactivate,
|
|
604
790
|
disabled,
|
|
605
791
|
isModalOpen
|
|
606
792
|
}) {
|
|
607
|
-
const [isActive, setIsActive] =
|
|
608
|
-
const [pendingPins, setPendingPins] =
|
|
609
|
-
const [selectedColor, setSelectedColor] =
|
|
610
|
-
const [showForm, setShowForm] =
|
|
611
|
-
const activate =
|
|
612
|
-
const deactivate =
|
|
793
|
+
const [isActive, setIsActive] = useState2(false);
|
|
794
|
+
const [pendingPins, setPendingPins] = useState2([]);
|
|
795
|
+
const [selectedColor, setSelectedColor] = useState2(PIN_PALETTE[0]);
|
|
796
|
+
const [showForm, setShowForm] = useState2(false);
|
|
797
|
+
const activate = useCallback4(() => setIsActive(true), []);
|
|
798
|
+
const deactivate = useCallback4(() => {
|
|
613
799
|
setIsActive(false);
|
|
614
800
|
setPendingPins([]);
|
|
615
801
|
setShowForm(false);
|
|
616
802
|
onDeactivate?.();
|
|
617
803
|
}, [onDeactivate]);
|
|
618
|
-
|
|
804
|
+
useEffect4(() => {
|
|
619
805
|
if (isActive) {
|
|
620
806
|
document.documentElement.classList.add("feedtack-crosshair");
|
|
621
807
|
} else {
|
|
@@ -623,7 +809,7 @@ function usePinMode({
|
|
|
623
809
|
}
|
|
624
810
|
return () => document.documentElement.classList.remove("feedtack-crosshair");
|
|
625
811
|
}, [isActive]);
|
|
626
|
-
|
|
812
|
+
useEffect4(() => {
|
|
627
813
|
if (disabled) return;
|
|
628
814
|
const handler = (e) => {
|
|
629
815
|
if (e.key === hotkey.toUpperCase() && e.shiftKey) {
|
|
@@ -644,7 +830,7 @@ function usePinMode({
|
|
|
644
830
|
window.addEventListener("keydown", handler);
|
|
645
831
|
return () => window.removeEventListener("keydown", handler);
|
|
646
832
|
}, [hotkey, deactivate, isActive, disabled, isModalOpen, showForm]);
|
|
647
|
-
const placePin =
|
|
833
|
+
const placePin = useCallback4(
|
|
648
834
|
(coords, target) => {
|
|
649
835
|
if (target.closest("#feedtack-root, .feedtack-form, .feedtack-color-picker"))
|
|
650
836
|
return;
|
|
@@ -660,7 +846,7 @@ function usePinMode({
|
|
|
660
846
|
},
|
|
661
847
|
[selectedColor]
|
|
662
848
|
);
|
|
663
|
-
const handlePageClick =
|
|
849
|
+
const handlePageClick = useCallback4(
|
|
664
850
|
(e) => {
|
|
665
851
|
if (!isActive) return;
|
|
666
852
|
e.preventDefault();
|
|
@@ -669,7 +855,7 @@ function usePinMode({
|
|
|
669
855
|
},
|
|
670
856
|
[isActive, placePin]
|
|
671
857
|
);
|
|
672
|
-
const handleTouchEnd =
|
|
858
|
+
const handleTouchEnd = useCallback4(
|
|
673
859
|
(e) => {
|
|
674
860
|
if (!isActive) return;
|
|
675
861
|
const touch = e.changedTouches[0];
|
|
@@ -681,7 +867,7 @@ function usePinMode({
|
|
|
681
867
|
},
|
|
682
868
|
[isActive, placePin]
|
|
683
869
|
);
|
|
684
|
-
|
|
870
|
+
useEffect4(() => {
|
|
685
871
|
if (disabled) return;
|
|
686
872
|
document.addEventListener("click", handlePageClick, true);
|
|
687
873
|
document.addEventListener("touchend", handleTouchEnd, true);
|
|
@@ -714,10 +900,10 @@ function useFeedtackState({
|
|
|
714
900
|
rescopeRoles
|
|
715
901
|
}) {
|
|
716
902
|
useFeedtackDom(theme, disabled);
|
|
717
|
-
const [pathname, setPathname] =
|
|
903
|
+
const [pathname, setPathname] = useState3(
|
|
718
904
|
() => typeof window === "undefined" ? "/" : window.location.pathname
|
|
719
905
|
);
|
|
720
|
-
|
|
906
|
+
useEffect5(() => {
|
|
721
907
|
const update = () => setPathname(window.location.pathname);
|
|
722
908
|
const origPush = history.pushState.bind(history);
|
|
723
909
|
const origReplace = history.replaceState.bind(history);
|
|
@@ -736,15 +922,15 @@ function useFeedtackState({
|
|
|
736
922
|
history.replaceState = origReplace;
|
|
737
923
|
};
|
|
738
924
|
}, []);
|
|
739
|
-
const [comment, setComment] =
|
|
740
|
-
const [sentiment, setSentiment] =
|
|
741
|
-
const [commentError, setCommentError] =
|
|
742
|
-
const [submitting, setSubmitting] =
|
|
743
|
-
const [feedbackItems, setFeedbackItems] =
|
|
744
|
-
const [loading, setLoading] =
|
|
745
|
-
const [openThreadId, setOpenThreadId] =
|
|
746
|
-
const [replyBody, setReplyBody] =
|
|
747
|
-
const resetForm =
|
|
925
|
+
const [comment, setComment] = useState3("");
|
|
926
|
+
const [sentiment, setSentiment] = useState3(null);
|
|
927
|
+
const [commentError, setCommentError] = useState3(false);
|
|
928
|
+
const [submitting, setSubmitting] = useState3(false);
|
|
929
|
+
const [feedbackItems, setFeedbackItems] = useState3([]);
|
|
930
|
+
const [loading, setLoading] = useState3(true);
|
|
931
|
+
const [openThreadId, setOpenThreadId] = useState3(null);
|
|
932
|
+
const [replyBody, setReplyBody] = useState3("");
|
|
933
|
+
const resetForm = useCallback5(() => {
|
|
748
934
|
setComment("");
|
|
749
935
|
setSentiment(null);
|
|
750
936
|
setCommentError(false);
|
|
@@ -765,113 +951,34 @@ function useFeedtackState({
|
|
|
765
951
|
flushIdleMs,
|
|
766
952
|
disabled
|
|
767
953
|
});
|
|
768
|
-
|
|
954
|
+
useEffect5(() => {
|
|
769
955
|
setLoading(true);
|
|
770
956
|
adapter.loadFeedback({ pathname }).then(setFeedbackItems).catch((err) => onError?.(err)).finally(() => setLoading(false));
|
|
771
957
|
}, [adapter, onError, pathname]);
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
);
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
...prev,
|
|
797
|
-
{ payload, replies: [], resolutions: [], archives: [] }
|
|
798
|
-
]);
|
|
799
|
-
pinMode.deactivate();
|
|
800
|
-
} catch (err) {
|
|
801
|
-
onError?.(err);
|
|
802
|
-
} finally {
|
|
803
|
-
setSubmitting(false);
|
|
804
|
-
}
|
|
805
|
-
};
|
|
806
|
-
const handleReply = async (feedbackId) => {
|
|
807
|
-
if (!replyBody.trim()) return;
|
|
808
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
809
|
-
const body = replyBody.trim();
|
|
810
|
-
try {
|
|
811
|
-
await adapter.reply(feedbackId, {
|
|
812
|
-
author: currentUser,
|
|
813
|
-
body,
|
|
814
|
-
timestamp: ts
|
|
815
|
-
});
|
|
816
|
-
updateItem(feedbackId, (item) => {
|
|
817
|
-
const updated = {
|
|
818
|
-
...item,
|
|
819
|
-
replies: [
|
|
820
|
-
...item.replies,
|
|
821
|
-
{
|
|
822
|
-
id: generateId(),
|
|
823
|
-
feedbackId,
|
|
824
|
-
author: currentUser,
|
|
825
|
-
body,
|
|
826
|
-
timestamp: ts
|
|
827
|
-
}
|
|
828
|
-
]
|
|
829
|
-
};
|
|
830
|
-
const triggerRescope = rescopeRoles ? rescopeRoles.includes(currentUser.role) : currentUser.role !== "agent";
|
|
831
|
-
if (triggerRescope && updated.resolutions.length === 0 && onFlush) {
|
|
832
|
-
clearFlushed(pathname);
|
|
833
|
-
}
|
|
834
|
-
return updated;
|
|
835
|
-
});
|
|
836
|
-
setReplyBody("");
|
|
837
|
-
} catch (err) {
|
|
838
|
-
onError?.(err);
|
|
839
|
-
}
|
|
840
|
-
};
|
|
841
|
-
const handleResolve = async (feedbackId) => {
|
|
842
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
843
|
-
try {
|
|
844
|
-
await adapter.resolve(feedbackId, {
|
|
845
|
-
resolvedBy: currentUser,
|
|
846
|
-
timestamp: ts
|
|
847
|
-
});
|
|
848
|
-
updateItem(feedbackId, (item) => ({
|
|
849
|
-
...item,
|
|
850
|
-
resolutions: [
|
|
851
|
-
...item.resolutions,
|
|
852
|
-
{ feedbackId, resolvedBy: currentUser, timestamp: ts }
|
|
853
|
-
]
|
|
854
|
-
}));
|
|
855
|
-
} catch (err) {
|
|
856
|
-
onError?.(err);
|
|
857
|
-
}
|
|
858
|
-
};
|
|
859
|
-
const handleArchive = async (feedbackId) => {
|
|
860
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
861
|
-
try {
|
|
862
|
-
await adapter.archive(feedbackId, currentUser.id);
|
|
863
|
-
updateItem(feedbackId, (item) => ({
|
|
864
|
-
...item,
|
|
865
|
-
archives: [
|
|
866
|
-
...item.archives,
|
|
867
|
-
{ feedbackId, archivedBy: currentUser, timestamp: ts }
|
|
868
|
-
]
|
|
869
|
-
}));
|
|
870
|
-
setOpenThreadId(null);
|
|
871
|
-
} catch (err) {
|
|
872
|
-
onError?.(err);
|
|
873
|
-
}
|
|
874
|
-
};
|
|
958
|
+
const commentRef = () => comment;
|
|
959
|
+
const sentimentRef = () => sentiment;
|
|
960
|
+
const pinsRef = () => pinMode.pendingPins;
|
|
961
|
+
const replyRef = () => replyBody;
|
|
962
|
+
const pathRef = () => pathname;
|
|
963
|
+
const actions = useFeedtackActions({
|
|
964
|
+
adapter,
|
|
965
|
+
currentUser,
|
|
966
|
+
onError,
|
|
967
|
+
getComment: commentRef,
|
|
968
|
+
getSentiment: sentimentRef,
|
|
969
|
+
getPendingPins: pinsRef,
|
|
970
|
+
getReplyBody: replyRef,
|
|
971
|
+
getPathname: pathRef,
|
|
972
|
+
setCommentError,
|
|
973
|
+
setSubmitting,
|
|
974
|
+
setFeedbackItems,
|
|
975
|
+
setReplyBody,
|
|
976
|
+
setOpenThreadId,
|
|
977
|
+
deactivatePinMode: pinMode.deactivate,
|
|
978
|
+
clearFlushed,
|
|
979
|
+
shouldRescope: rescopeRoles ? (role) => rescopeRoles.includes(role) : void 0,
|
|
980
|
+
hasFlush: !!onFlush
|
|
981
|
+
});
|
|
875
982
|
const isArchivedForUser = (item) => item.archives.some((a) => a.archivedBy.id === currentUser.id);
|
|
876
983
|
const hasUnread = (item) => item.replies.length > 0;
|
|
877
984
|
const hasValidPins = (item) => Array.isArray(item.payload?.pins) && item.payload.pins.length > 0;
|
|
@@ -894,10 +1001,7 @@ function useFeedtackState({
|
|
|
894
1001
|
setOpenThreadId,
|
|
895
1002
|
replyBody,
|
|
896
1003
|
setReplyBody,
|
|
897
|
-
|
|
898
|
-
handleReply,
|
|
899
|
-
handleResolve,
|
|
900
|
-
handleArchive,
|
|
1004
|
+
...actions,
|
|
901
1005
|
isArchivedForUser,
|
|
902
1006
|
hasUnread,
|
|
903
1007
|
hasValidPins
|
|
@@ -933,6 +1037,7 @@ function FeedtackProvider({
|
|
|
933
1037
|
flushIdleMs,
|
|
934
1038
|
rescopeRoles
|
|
935
1039
|
});
|
|
1040
|
+
const { getPosition } = useAnchoredPins(state.feedbackItems, state.pathname);
|
|
936
1041
|
const firstPin = state.pendingPins[0];
|
|
937
1042
|
const formPos = firstPin ? getAnchoredPosition(firstPin.x, firstPin.y) : {};
|
|
938
1043
|
const showButton = !adminOnly || currentUser.role === "admin";
|
|
@@ -1017,6 +1122,7 @@ function FeedtackProvider({
|
|
|
1017
1122
|
),
|
|
1018
1123
|
!state.loading && state.feedbackItems.filter((item) => item.payload.page.pathname === state.pathname).filter((item) => !state.isArchivedForUser(item)).filter((item) => state.hasValidPins(item)).map((item) => {
|
|
1019
1124
|
const pin = item.payload.pins[0];
|
|
1125
|
+
const pos = getPosition(item.payload.id, pin);
|
|
1020
1126
|
return /* @__PURE__ */ jsxs3(
|
|
1021
1127
|
"button",
|
|
1022
1128
|
{
|
|
@@ -1028,8 +1134,8 @@ function FeedtackProvider({
|
|
|
1028
1134
|
),
|
|
1029
1135
|
style: {
|
|
1030
1136
|
background: pin.color,
|
|
1031
|
-
left:
|
|
1032
|
-
top:
|
|
1137
|
+
left: pos.x,
|
|
1138
|
+
top: pos.y,
|
|
1033
1139
|
position: "absolute",
|
|
1034
1140
|
cursor: "pointer"
|
|
1035
1141
|
},
|
|
@@ -1062,6 +1168,10 @@ function FeedtackProvider({
|
|
|
1062
1168
|
onResolve: () => state.handleResolve(openItem.payload.id),
|
|
1063
1169
|
onArchive: () => state.handleArchive(openItem.payload.id),
|
|
1064
1170
|
onClose: () => state.setOpenThreadId(null),
|
|
1171
|
+
pinPosition: getPosition(
|
|
1172
|
+
openItem.payload.id,
|
|
1173
|
+
openItem.payload.pins[0]
|
|
1174
|
+
),
|
|
1065
1175
|
className: classes.thread
|
|
1066
1176
|
}
|
|
1067
1177
|
),
|