@adatechnology/conversations-ui 0.7.0 → 0.9.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 +96 -1
- package/dist/{ConversationSimulatorPanel-CrfFjiWh.d.ts → ConversationSimulatorPanel-q-wVgBCN.d.ts} +1 -1
- package/dist/{chunk-HMCOTZAX.js → chunk-NSOAUYNE.js} +33 -28
- package/dist/chunk-ZSOD5AQT.js +19 -0
- package/dist/flows/index.js +4 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -3
- package/dist/participant/index.d.ts +48 -3
- package/dist/participant/index.js +782 -347
- package/dist/preview/index.d.ts +3 -3
- package/dist/{quickReply.types-B4gmZsSr.d.ts → quickReply.types-DzWH3NYV.d.ts} +1 -1
- package/dist/styles.css +173 -52
- package/package.json +2 -1
- package/src/Tooltip.tsx +5 -12
- package/src/TooltipDom.test.tsx +78 -0
- package/src/domHarness.test-helper.ts +53 -0
- package/src/participant/ConversationThread.test.tsx +165 -0
- package/src/participant/ConversationThread.tsx +117 -0
- package/src/participant/ParticipantBackButton.tsx +1 -1
- package/src/participant/ParticipantBubbleBody.tsx +54 -0
- package/src/participant/ParticipantBubbleStatus.tsx +22 -28
- package/src/participant/ParticipantChannelBadges.tsx +10 -2
- package/src/participant/ParticipantComposer.tsx +1 -1
- package/src/participant/ParticipantComposerControls.tsx +2 -0
- package/src/participant/ParticipantCopyableToken.tsx +1 -0
- package/src/participant/ParticipantFailedMenu.tsx +40 -0
- package/src/participant/ParticipantFailedMenuList.tsx +41 -0
- package/src/participant/ParticipantMessageBubble.test.tsx +6 -6
- package/src/participant/ParticipantMessageBubble.tsx +26 -54
- package/src/participant/ParticipantMessageText.test.tsx +1 -1
- package/src/participant/ParticipantMessageText.tsx +4 -2
- package/src/participant/ParticipantProtocolBadge.tsx +1 -0
- package/src/participant/ParticipantScrollToLatest.tsx +1 -1
- package/src/participant/ParticipantThread.tsx +30 -10
- package/src/participant/ParticipantThreadHeader.test.tsx +1 -1
- package/src/participant/ParticipantThreadHeader.tsx +10 -0
- package/src/participant/ParticipantThreadScreen.tsx +6 -46
- package/src/participant/ParticipantTimeline.tsx +6 -3
- package/src/participant/conversationThreadApi.test.ts +27 -0
- package/src/participant/conversationThreadApi.ts +24 -0
- package/src/participant/conversationThreadCss.test.ts +116 -0
- package/src/participant/conversationThreadSummary.ts +34 -0
- package/src/participant/failedMenuKeyboard.ts +29 -0
- package/src/participant/index.ts +3 -0
- package/src/participant/participantContrast.test-helper.ts +116 -0
- package/src/participant/participantFailedMenuDom.test.tsx +154 -0
- package/src/participant/participantFailedMenuState.test.ts +61 -0
- package/src/participant/participantFailedMenuState.ts +57 -0
- package/src/participant/participantFailedMessage.test.tsx +83 -0
- package/src/participant/participantGolden.test.tsx +46 -0
- package/src/participant/participantGoldenScenarios.test-helper.tsx +57 -0
- package/src/participant/participantLabels.ts +6 -0
- package/src/participant/participantMessages.ts +4 -3
- package/src/participant/participantNewMessages.test.ts +21 -0
- package/src/participant/participantNewMessages.ts +8 -3
- package/src/participant/participantPerspective.test.ts +67 -0
- package/src/participant/participantPerspective.ts +50 -0
- package/src/participant/participantPerspectiveRender.test.tsx +94 -0
- package/src/participant/participantThemeContrast.test.ts +106 -0
- package/src/participant/participantTooltips.test.tsx +74 -0
- package/src/participant/test-fixtures/conversations-list.golden.html +1 -0
- package/src/participant/test-fixtures/conversations-selected.golden.html +1 -0
- package/src/participant/test-fixtures/thread-avatars-initials.golden.html +1 -0
- package/src/participant/test-fixtures/thread-closed.golden.html +1 -0
- package/src/participant/test-fixtures/thread-default.golden.html +1 -0
- package/src/participant/test-fixtures/thread-error.golden.html +1 -0
- package/src/participant/test-fixtures/thread-loading.golden.html +1 -0
- package/src/participant/test-fixtures/thread-plain.golden.html +1 -0
- package/src/participant/test-fixtures/thread-tail-off.golden.html +1 -0
- package/src/participant/useFailedMenuEffects.ts +59 -0
- package/src/participant/useParticipantConversationView.ts +3 -0
- package/src/participant/useParticipantFailedMenu.ts +60 -0
- package/src/participant/useParticipantThread.ts +13 -2
- package/src/participant/useParticipantThreadController.ts +112 -0
- package/src/participant/useParticipantThreadScroll.ts +3 -1
- package/src/participant/useParticipantUnread.test.tsx +40 -0
- package/src/participant/useParticipantUnread.ts +38 -0
- package/src/participant/useSuppressTouchTooltips.ts +38 -0
- package/src/styles.css +43 -17
- package/src/tooltipLayerRegistry.test.ts +56 -0
- package/src/tooltipLayerRegistry.ts +34 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TooltipLayer
|
|
3
|
+
} from "../chunk-NSOAUYNE.js";
|
|
1
4
|
import {
|
|
2
5
|
StatusTicks,
|
|
3
6
|
channelCapabilityFor,
|
|
@@ -7,7 +10,7 @@ import {
|
|
|
7
10
|
} from "../chunk-O6457XHK.js";
|
|
8
11
|
|
|
9
12
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
10
|
-
import { useMemo as useMemo5, useReducer as
|
|
13
|
+
import { useMemo as useMemo5, useReducer as useReducer6, useRef as useRef12, useState as useState9 } from "react";
|
|
11
14
|
|
|
12
15
|
// src/participant/participantLoadView.ts
|
|
13
16
|
function resolveLoadView({ status, hasItems }) {
|
|
@@ -19,6 +22,7 @@ function resolveLoadView({ status, hasItems }) {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
// src/participant/participantLabels.ts
|
|
25
|
+
var DEFAULT_MESSAGE_OPTIONS_LABEL = "Message options";
|
|
22
26
|
var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
|
|
23
27
|
inboxTitle: "Conversations",
|
|
24
28
|
sectionAwaiting: "Waiting for your reply",
|
|
@@ -55,6 +59,7 @@ var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
|
|
|
55
59
|
retry: "Retry",
|
|
56
60
|
discard: "Discard",
|
|
57
61
|
edit: "Edit",
|
|
62
|
+
messageOptions: DEFAULT_MESSAGE_OPTIONS_LABEL,
|
|
58
63
|
notFound: "Conversation not found",
|
|
59
64
|
loading: "Loading\u2026",
|
|
60
65
|
loadError: "Could not load. Check your connection and try again.",
|
|
@@ -128,18 +133,34 @@ function ParticipantChannelBadges({
|
|
|
128
133
|
if (variant === "inline") {
|
|
129
134
|
return /* @__PURE__ */ jsxs("span", { className: "cv-p-channels", children: [
|
|
130
135
|
/* @__PURE__ */ jsx("span", { className: "cv-p-sr-only", children: `${labels.channelsGroup}: ` }),
|
|
131
|
-
descriptors.map((descriptor, index) => /* @__PURE__ */ jsx(
|
|
132
|
-
|
|
136
|
+
descriptors.map((descriptor, index) => /* @__PURE__ */ jsx(
|
|
137
|
+
"span",
|
|
133
138
|
{
|
|
134
|
-
descriptor
|
|
135
|
-
labels,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
className: `cv-p-channel cv-p-channel--${descriptor.channel}`,
|
|
140
|
+
"data-cv-tooltip": resolveParticipantChannelLabel(descriptor, labels),
|
|
141
|
+
children: /* @__PURE__ */ jsx(
|
|
142
|
+
BadgeContent,
|
|
143
|
+
{
|
|
144
|
+
descriptor,
|
|
145
|
+
labels,
|
|
146
|
+
separator: index < descriptors.length - 1 ? ", " : "",
|
|
147
|
+
iconSize
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
},
|
|
151
|
+
descriptor.channel
|
|
152
|
+
))
|
|
140
153
|
] });
|
|
141
154
|
}
|
|
142
|
-
return /* @__PURE__ */ jsx("ul", { className: "cv-p-channels", "aria-label": labels.channelsGroup, children: descriptors.map((descriptor) => /* @__PURE__ */ jsx(
|
|
155
|
+
return /* @__PURE__ */ jsx("ul", { className: "cv-p-channels", "aria-label": labels.channelsGroup, children: descriptors.map((descriptor) => /* @__PURE__ */ jsx(
|
|
156
|
+
"li",
|
|
157
|
+
{
|
|
158
|
+
className: `cv-p-channel cv-p-channel--${descriptor.channel}`,
|
|
159
|
+
"data-cv-tooltip": resolveParticipantChannelLabel(descriptor, labels),
|
|
160
|
+
children: /* @__PURE__ */ jsx(BadgeContent, { descriptor, labels, separator: "", iconSize })
|
|
161
|
+
},
|
|
162
|
+
descriptor.channel
|
|
163
|
+
)) });
|
|
143
164
|
}
|
|
144
165
|
|
|
145
166
|
// src/participant/participantSubjectIcon.ts
|
|
@@ -361,15 +382,18 @@ function ParticipantInbox(props) {
|
|
|
361
382
|
}
|
|
362
383
|
|
|
363
384
|
// src/participant/ParticipantThreadScreen.tsx
|
|
364
|
-
import { useEffect as
|
|
385
|
+
import { useEffect as useEffect13, useState as useState8 } from "react";
|
|
365
386
|
|
|
366
387
|
// src/participant/ParticipantBackButton.tsx
|
|
367
388
|
import { ArrowLeft } from "lucide-react";
|
|
368
389
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
369
390
|
function ParticipantBackButton({ label, onBack }) {
|
|
370
|
-
return /* @__PURE__ */ jsx6("button", { type: "button", className: "cv-p-thread__back", "aria-label": label, onClick: onBack, children: /* @__PURE__ */ jsx6(ArrowLeft, { size: 24, "aria-hidden": "true", focusable: "false" }) });
|
|
391
|
+
return /* @__PURE__ */ jsx6("button", { type: "button", className: "cv-p-thread__back", "aria-label": label, "data-cv-tooltip": label, onClick: onBack, children: /* @__PURE__ */ jsx6(ArrowLeft, { size: 24, "aria-hidden": "true", focusable: "false" }) });
|
|
371
392
|
}
|
|
372
393
|
|
|
394
|
+
// src/participant/ParticipantThread.tsx
|
|
395
|
+
import { useRef as useRef5 } from "react";
|
|
396
|
+
|
|
373
397
|
// src/participant/ParticipantComposer.tsx
|
|
374
398
|
import { useState } from "react";
|
|
375
399
|
|
|
@@ -406,6 +430,7 @@ function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
|
|
|
406
430
|
type: "button",
|
|
407
431
|
className: "cv-p-composer__attach",
|
|
408
432
|
"aria-label": labels.attach,
|
|
433
|
+
"data-cv-tooltip": labels.attach,
|
|
409
434
|
disabled,
|
|
410
435
|
onClick: () => fileInputRef.current?.click(),
|
|
411
436
|
children: /* @__PURE__ */ jsx7(Paperclip, { size: 24, "aria-hidden": "true", focusable: "false" })
|
|
@@ -420,6 +445,7 @@ function SendButton({ label, disabled }) {
|
|
|
420
445
|
type: "submit",
|
|
421
446
|
className: "cv-p-button cv-p-button--primary cv-p-composer__send",
|
|
422
447
|
"aria-label": label,
|
|
448
|
+
"data-cv-tooltip": label,
|
|
423
449
|
disabled,
|
|
424
450
|
children: /* @__PURE__ */ jsx7(Send, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
425
451
|
}
|
|
@@ -488,7 +514,7 @@ function FileList({ files, labels, onRemove }) {
|
|
|
488
514
|
return /* @__PURE__ */ jsx8("ul", { className: "cv-p-files", "aria-label": labels.attachmentsList, children: files.map((file, index) => /* @__PURE__ */ jsxs7("li", { className: "cv-p-files__item", children: [
|
|
489
515
|
/* @__PURE__ */ jsx8("span", { className: "cv-p-files__name", children: file.name }),
|
|
490
516
|
/* @__PURE__ */ jsx8("span", { className: "cv-p-files__size", children: formatFileSize(file.size) }),
|
|
491
|
-
/* @__PURE__ */ jsxs7("button", { type: "button", className: "cv-p-files__remove", "aria-label": `${labels.removeAttachment}: ${file.name}`, onClick: () => onRemove(file), children: [
|
|
517
|
+
/* @__PURE__ */ jsxs7("button", { type: "button", className: "cv-p-files__remove", "aria-label": `${labels.removeAttachment}: ${file.name}`, "data-cv-tooltip": labels.removeAttachment, onClick: () => onRemove(file), children: [
|
|
492
518
|
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\xD7" }),
|
|
493
519
|
/* @__PURE__ */ jsx8("span", { className: "cv-p-sr-only", children: labels.removeAttachment })
|
|
494
520
|
] })
|
|
@@ -544,8 +570,40 @@ function ParticipantComposer(props) {
|
|
|
544
570
|
] });
|
|
545
571
|
}
|
|
546
572
|
|
|
573
|
+
// src/participant/useSuppressTouchTooltips.ts
|
|
574
|
+
import { useEffect as useEffect2 } from "react";
|
|
575
|
+
function useSuppressTouchTooltips(rootRef) {
|
|
576
|
+
useEffect2(() => {
|
|
577
|
+
const root = rootRef.current;
|
|
578
|
+
if (!root) return void 0;
|
|
579
|
+
let isTouching = false;
|
|
580
|
+
function handlePointerDown(event) {
|
|
581
|
+
isTouching = event.pointerType === "touch";
|
|
582
|
+
}
|
|
583
|
+
function handlePointerOver(event) {
|
|
584
|
+
if (event.pointerType === "touch") event.stopPropagation();
|
|
585
|
+
}
|
|
586
|
+
function handleFocusIn(event) {
|
|
587
|
+
if (isTouching) event.stopPropagation();
|
|
588
|
+
}
|
|
589
|
+
function handleKeyDown() {
|
|
590
|
+
isTouching = false;
|
|
591
|
+
}
|
|
592
|
+
root.addEventListener("pointerdown", handlePointerDown, true);
|
|
593
|
+
root.addEventListener("pointerover", handlePointerOver);
|
|
594
|
+
root.addEventListener("focusin", handleFocusIn);
|
|
595
|
+
root.addEventListener("keydown", handleKeyDown, true);
|
|
596
|
+
return () => {
|
|
597
|
+
root.removeEventListener("pointerdown", handlePointerDown, true);
|
|
598
|
+
root.removeEventListener("pointerover", handlePointerOver);
|
|
599
|
+
root.removeEventListener("focusin", handleFocusIn);
|
|
600
|
+
root.removeEventListener("keydown", handleKeyDown, true);
|
|
601
|
+
};
|
|
602
|
+
}, [rootRef]);
|
|
603
|
+
}
|
|
604
|
+
|
|
547
605
|
// src/participant/ParticipantCopyAnnouncer.tsx
|
|
548
|
-
import { createContext, useCallback, useContext, useEffect as
|
|
606
|
+
import { createContext, useCallback, useContext, useEffect as useEffect3, useReducer, useRef as useRef3 } from "react";
|
|
549
607
|
|
|
550
608
|
// src/participant/participantCopyAnnouncement.ts
|
|
551
609
|
var INITIAL_COPY_ANNOUNCEMENT = { text: "", tick: 0 };
|
|
@@ -575,7 +633,7 @@ function useAnnounceCopy() {
|
|
|
575
633
|
function ParticipantCopyAnnouncer({ children }) {
|
|
576
634
|
const [announcement, dispatch] = useReducer(copyAnnouncementReducer, INITIAL_COPY_ANNOUNCEMENT);
|
|
577
635
|
const announce = useCallback((text) => dispatch({ type: "announce", text }), []);
|
|
578
|
-
|
|
636
|
+
useEffect3(() => {
|
|
579
637
|
if (announcement.text === "") return void 0;
|
|
580
638
|
const timer = setTimeout(() => dispatch({ type: "expired" }), ANNOUNCEMENT_MS);
|
|
581
639
|
return () => clearTimeout(timer);
|
|
@@ -616,6 +674,7 @@ function ParticipantScrollToLatest({
|
|
|
616
674
|
type: "button",
|
|
617
675
|
className: "cv-p-thread__latest-button",
|
|
618
676
|
"aria-label": label,
|
|
677
|
+
"data-cv-tooltip": label,
|
|
619
678
|
onClick: (event) => {
|
|
620
679
|
focusScrollerOf(event.currentTarget);
|
|
621
680
|
onClick();
|
|
@@ -692,8 +751,74 @@ function ParticipantDayDivider({ day, locale }) {
|
|
|
692
751
|
return /* @__PURE__ */ jsx12("div", { className: "cv-p-day", role: "separator", "aria-label": label, children: /* @__PURE__ */ jsx12("time", { className: "cv-p-day__label", dateTime: toDateTimeAttribute(day), children: label }) });
|
|
693
752
|
}
|
|
694
753
|
|
|
695
|
-
// src/participant/
|
|
754
|
+
// src/participant/ParticipantMessageBubble.tsx
|
|
755
|
+
import { useId as useId3 } from "react";
|
|
756
|
+
|
|
757
|
+
// src/participant/ParticipantAttachmentItem.tsx
|
|
758
|
+
import { useEffect as useEffect4, useState as useState2 } from "react";
|
|
759
|
+
|
|
760
|
+
// src/participant/participantAttachmentUrl.ts
|
|
761
|
+
var SAFE_ATTACHMENT_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:", "blob:"]);
|
|
762
|
+
function isSafeAttachmentUrl(url) {
|
|
763
|
+
try {
|
|
764
|
+
return SAFE_ATTACHMENT_PROTOCOLS.has(new URL(url.trim()).protocol);
|
|
765
|
+
} catch {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/participant/ParticipantAttachmentItem.tsx
|
|
696
771
|
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
772
|
+
function useAttachmentUrl(attachment, resolveAttachmentUrl) {
|
|
773
|
+
const [url, setUrl] = useState2(void 0);
|
|
774
|
+
const disposition = attachment.kind === "document" ? "attachment" : "inline";
|
|
775
|
+
useEffect4(() => {
|
|
776
|
+
let isCurrent = true;
|
|
777
|
+
resolveAttachmentUrl(attachment, disposition).then(
|
|
778
|
+
(resolved) => {
|
|
779
|
+
if (isCurrent) setUrl(isSafeAttachmentUrl(resolved) ? resolved.trim() : void 0);
|
|
780
|
+
},
|
|
781
|
+
() => {
|
|
782
|
+
if (isCurrent) setUrl(void 0);
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
return () => {
|
|
786
|
+
isCurrent = false;
|
|
787
|
+
};
|
|
788
|
+
}, [attachment, disposition, resolveAttachmentUrl]);
|
|
789
|
+
return url;
|
|
790
|
+
}
|
|
791
|
+
function ParticipantAttachmentItem({ attachment, resolveAttachmentUrl, labels }) {
|
|
792
|
+
const url = useAttachmentUrl(attachment, resolveAttachmentUrl);
|
|
793
|
+
if (attachment.kind === "image") {
|
|
794
|
+
return /* @__PURE__ */ jsxs9("figure", { className: "cv-p-attachment cv-p-attachment--image", children: [
|
|
795
|
+
url ? /* @__PURE__ */ jsx13("img", { className: "cv-p-attachment__image", src: url, alt: attachment.filename }) : null,
|
|
796
|
+
/* @__PURE__ */ jsx13("figcaption", { className: "cv-p-attachment__name", children: attachment.filename })
|
|
797
|
+
] });
|
|
798
|
+
}
|
|
799
|
+
if (attachment.kind === "audio") {
|
|
800
|
+
return /* @__PURE__ */ jsxs9("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
|
|
801
|
+
/* @__PURE__ */ jsx13("span", { className: "cv-p-attachment__name", children: attachment.filename }),
|
|
802
|
+
/* @__PURE__ */ jsx13("audio", { className: "cv-p-attachment__audio", controls: true, preload: "none", src: url })
|
|
803
|
+
] });
|
|
804
|
+
}
|
|
805
|
+
return /* @__PURE__ */ jsxs9(
|
|
806
|
+
"a",
|
|
807
|
+
{
|
|
808
|
+
className: "cv-p-attachment cv-p-attachment--document",
|
|
809
|
+
href: url,
|
|
810
|
+
download: attachment.filename,
|
|
811
|
+
"aria-label": `${labels.downloadAttachment}: ${attachment.filename}`,
|
|
812
|
+
children: [
|
|
813
|
+
/* @__PURE__ */ jsx13("span", { className: "cv-p-attachment__name", children: attachment.filename }),
|
|
814
|
+
/* @__PURE__ */ jsx13("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
|
|
815
|
+
]
|
|
816
|
+
}
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/participant/ParticipantBubbleStatus.tsx
|
|
821
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
697
822
|
function statusText(status, labels) {
|
|
698
823
|
const byStatus = {
|
|
699
824
|
sending: labels.statusSending,
|
|
@@ -708,23 +833,204 @@ function statusText(status, labels) {
|
|
|
708
833
|
function OwnStatus({ status, labels, onRetry }) {
|
|
709
834
|
const ticksStatus = status === "sending" ? "queued" : status;
|
|
710
835
|
const isVisibleText = status === "sending" || status === "queued" || status === "failed";
|
|
711
|
-
if (status === "failed" && onRetry) {
|
|
712
|
-
return /* @__PURE__ */ jsxs9("button", { type: "button", className: "cv-p-bubble__retry", onClick: onRetry, children: [
|
|
713
|
-
/* @__PURE__ */ jsx13(StatusTicks, { status: "failed", appearance: "stylesheet" }),
|
|
714
|
-
/* @__PURE__ */ jsx13("span", { children: labels.statusFailed })
|
|
715
|
-
] });
|
|
716
|
-
}
|
|
717
836
|
const text = status === "failed" ? labels.statusFailedShort : statusText(status, labels);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
/* @__PURE__ */
|
|
837
|
+
const isTextVisible = isVisibleText && !(status === "failed" && onRetry);
|
|
838
|
+
return /* @__PURE__ */ jsxs10("span", { className: "cv-p-bubble__status", children: [
|
|
839
|
+
/* @__PURE__ */ jsx14(StatusTicks, { status: ticksStatus, appearance: "stylesheet", title: isTextVisible ? void 0 : text }),
|
|
840
|
+
/* @__PURE__ */ jsx14("span", { className: isTextVisible ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
|
|
841
|
+
] });
|
|
842
|
+
}
|
|
843
|
+
function RetryButton({ labels, onRetry, describedBy }) {
|
|
844
|
+
return /* @__PURE__ */ jsx14(
|
|
845
|
+
"button",
|
|
846
|
+
{
|
|
847
|
+
type: "button",
|
|
848
|
+
className: "cv-p-failed-retry",
|
|
849
|
+
"aria-label": labels.statusFailed,
|
|
850
|
+
"aria-describedby": describedBy,
|
|
851
|
+
"data-cv-tooltip": labels.statusFailed,
|
|
852
|
+
onClick: onRetry,
|
|
853
|
+
children: /* @__PURE__ */ jsx14("span", { className: "cv-p-failed-retry__icon", children: /* @__PURE__ */ jsx14("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx14("path", { d: "M13 8a5 5 0 1 1-1.6-3.7M13 2.5v3h-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }) }) })
|
|
854
|
+
}
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/participant/ParticipantFailedMenu.tsx
|
|
859
|
+
import { useId as useId2 } from "react";
|
|
860
|
+
|
|
861
|
+
// src/participant/ParticipantFailedMenuList.tsx
|
|
862
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
863
|
+
function ParticipantFailedMenuList({ menu, labelledBy, labels, onEdit, onDiscard }) {
|
|
864
|
+
function runAndClose(action) {
|
|
865
|
+
return () => {
|
|
866
|
+
menu.close();
|
|
867
|
+
action();
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const className = `cv-p-failed-menu__list${menu.placement === "below" ? " cv-p-failed-menu__list--below" : ""}`;
|
|
871
|
+
return /* @__PURE__ */ jsxs11("div", { role: "menu", "aria-labelledby": labelledBy, ref: menu.menuRef, className, onKeyDown: menu.handleMenuKeyDown, children: [
|
|
872
|
+
onEdit ? /* @__PURE__ */ jsx15("button", { type: "button", role: "menuitem", tabIndex: -1, className: "cv-p-failed-menu__item", onClick: runAndClose(onEdit), children: labels.edit }) : null,
|
|
873
|
+
onDiscard ? /* @__PURE__ */ jsx15(
|
|
874
|
+
"button",
|
|
875
|
+
{
|
|
876
|
+
type: "button",
|
|
877
|
+
role: "menuitem",
|
|
878
|
+
tabIndex: -1,
|
|
879
|
+
className: "cv-p-failed-menu__item cv-p-failed-menu__item--danger",
|
|
880
|
+
onClick: runAndClose(onDiscard),
|
|
881
|
+
children: labels.discard
|
|
882
|
+
}
|
|
883
|
+
) : null
|
|
721
884
|
] });
|
|
722
885
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
886
|
+
|
|
887
|
+
// src/participant/useParticipantFailedMenu.ts
|
|
888
|
+
import { useReducer as useReducer2, useRef as useRef4 } from "react";
|
|
889
|
+
|
|
890
|
+
// src/participant/participantFailedMenuState.ts
|
|
891
|
+
var CLOSED_FAILED_MENU = { isOpen: false, initialFocus: "first" };
|
|
892
|
+
function failedMenuReducer(state, action) {
|
|
893
|
+
if (action.type === "open") return { isOpen: true, initialFocus: action.initialFocus };
|
|
894
|
+
if (action.type === "close") return state.isOpen ? CLOSED_FAILED_MENU : state;
|
|
895
|
+
return state.isOpen ? CLOSED_FAILED_MENU : { isOpen: true, initialFocus: "first" };
|
|
896
|
+
}
|
|
897
|
+
function resolveFailedMenuKey({ key, index, count }) {
|
|
898
|
+
if (key === "Escape") return { type: "close", shouldRestoreFocus: true };
|
|
899
|
+
if (key === "Tab") return { type: "close", shouldRestoreFocus: false };
|
|
900
|
+
if (key === "ArrowDown") return { type: "focus", index: (index + 1) % count };
|
|
901
|
+
if (key === "ArrowUp") return { type: "focus", index: (index - 1 + count) % count };
|
|
902
|
+
if (key === "Home") return { type: "focus", index: 0 };
|
|
903
|
+
if (key === "End") return { type: "focus", index: count - 1 };
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
906
|
+
function resolveFailedMenuTriggerKey(key) {
|
|
907
|
+
if (key === "ArrowDown") return "first";
|
|
908
|
+
if (key === "ArrowUp") return "last";
|
|
909
|
+
return void 0;
|
|
910
|
+
}
|
|
911
|
+
var MENU_GAP_PX = 8;
|
|
912
|
+
function resolveFailedMenuPlacement({ anchorTop, scrollerTop, menuHeight }) {
|
|
913
|
+
return anchorTop - scrollerTop >= menuHeight + MENU_GAP_PX ? "above" : "below";
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/participant/failedMenuKeyboard.ts
|
|
917
|
+
function handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }) {
|
|
918
|
+
const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
919
|
+
const index = items.findIndex((item) => item === document.activeElement);
|
|
920
|
+
const result = resolveFailedMenuKey({ key: event.key, index, count: items.length });
|
|
921
|
+
if (!result) return;
|
|
922
|
+
if (result.type === "focus") {
|
|
923
|
+
event.preventDefault();
|
|
924
|
+
items[result.index]?.focus();
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (result.shouldRestoreFocus) {
|
|
928
|
+
event.preventDefault();
|
|
929
|
+
event.stopPropagation();
|
|
930
|
+
triggerRef.current?.focus();
|
|
931
|
+
}
|
|
932
|
+
dispatch({ type: "close" });
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/participant/useFailedMenuEffects.ts
|
|
936
|
+
import { useEffect as useEffect5, useLayoutEffect as useLayoutEffect2, useState as useState3 } from "react";
|
|
937
|
+
var SCROLLER_SELECTOR2 = ".cv-p-thread__scroll";
|
|
938
|
+
function useFailedMenuInitialFocus(state, menuRef) {
|
|
939
|
+
useEffect5(() => {
|
|
940
|
+
if (!state.isOpen) return;
|
|
941
|
+
const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
942
|
+
const target = state.initialFocus === "last" ? items[items.length - 1] : items[0];
|
|
943
|
+
target?.focus();
|
|
944
|
+
}, [state, menuRef]);
|
|
945
|
+
}
|
|
946
|
+
function useFailedMenuOutsideClose(isOpen, containerRef, dispatch) {
|
|
947
|
+
useEffect5(() => {
|
|
948
|
+
if (!isOpen) return;
|
|
949
|
+
function handlePointerDown(event) {
|
|
950
|
+
if (event.target instanceof Node && containerRef.current?.contains(event.target)) return;
|
|
951
|
+
dispatch({ type: "close" });
|
|
952
|
+
}
|
|
953
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
954
|
+
return () => document.removeEventListener("pointerdown", handlePointerDown);
|
|
955
|
+
}, [isOpen, containerRef, dispatch]);
|
|
956
|
+
}
|
|
957
|
+
function useFailedMenuPlacement(isOpen, { triggerRef, menuRef }) {
|
|
958
|
+
const [placement, setPlacement] = useState3("above");
|
|
959
|
+
useLayoutEffect2(() => {
|
|
960
|
+
const trigger = triggerRef.current;
|
|
961
|
+
const menu = menuRef.current;
|
|
962
|
+
if (!isOpen || !trigger || !menu) return;
|
|
963
|
+
const anchor = trigger.closest(".cv-p-bubble") ?? trigger;
|
|
964
|
+
const scroller = trigger.closest(SCROLLER_SELECTOR2);
|
|
965
|
+
setPlacement(
|
|
966
|
+
resolveFailedMenuPlacement({
|
|
967
|
+
anchorTop: anchor.getBoundingClientRect().top,
|
|
968
|
+
scrollerTop: scroller?.getBoundingClientRect().top ?? 0,
|
|
969
|
+
menuHeight: menu.getBoundingClientRect().height
|
|
970
|
+
})
|
|
971
|
+
);
|
|
972
|
+
}, [isOpen, triggerRef, menuRef]);
|
|
973
|
+
return placement;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/participant/useParticipantFailedMenu.ts
|
|
977
|
+
function useParticipantFailedMenu() {
|
|
978
|
+
const [state, dispatch] = useReducer2(failedMenuReducer, CLOSED_FAILED_MENU);
|
|
979
|
+
const containerRef = useRef4(null);
|
|
980
|
+
const triggerRef = useRef4(null);
|
|
981
|
+
const menuRef = useRef4(null);
|
|
982
|
+
useFailedMenuInitialFocus(state, menuRef);
|
|
983
|
+
useFailedMenuOutsideClose(state.isOpen, containerRef, dispatch);
|
|
984
|
+
const placement = useFailedMenuPlacement(state.isOpen, { triggerRef, menuRef });
|
|
985
|
+
function handleTriggerKeyDown(event) {
|
|
986
|
+
const initialFocus = resolveFailedMenuTriggerKey(event.key);
|
|
987
|
+
if (!initialFocus) return;
|
|
988
|
+
event.preventDefault();
|
|
989
|
+
dispatch({ type: "open", initialFocus });
|
|
990
|
+
}
|
|
991
|
+
function handleBlur(event) {
|
|
992
|
+
const next = event.relatedTarget;
|
|
993
|
+
if (next instanceof Node && !containerRef.current?.contains(next)) dispatch({ type: "close" });
|
|
994
|
+
}
|
|
995
|
+
return {
|
|
996
|
+
state,
|
|
997
|
+
placement,
|
|
998
|
+
containerRef,
|
|
999
|
+
triggerRef,
|
|
1000
|
+
menuRef,
|
|
1001
|
+
handleTriggerClick: () => dispatch({ type: "toggle" }),
|
|
1002
|
+
handleTriggerKeyDown,
|
|
1003
|
+
handleMenuKeyDown: (event) => handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }),
|
|
1004
|
+
handleBlur,
|
|
1005
|
+
close: () => dispatch({ type: "close" })
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/participant/ParticipantFailedMenu.tsx
|
|
1010
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1011
|
+
function ParticipantFailedMenu({ labels, onEdit, onDiscard }) {
|
|
1012
|
+
const menu = useParticipantFailedMenu();
|
|
1013
|
+
const triggerId = useId2();
|
|
1014
|
+
if (!onEdit && !onDiscard) return null;
|
|
1015
|
+
const label = labels.messageOptions ?? DEFAULT_MESSAGE_OPTIONS_LABEL;
|
|
1016
|
+
return /* @__PURE__ */ jsxs12("span", { className: "cv-p-failed-menu", ref: menu.containerRef, onBlur: menu.handleBlur, children: [
|
|
1017
|
+
/* @__PURE__ */ jsx16(
|
|
1018
|
+
"button",
|
|
1019
|
+
{
|
|
1020
|
+
type: "button",
|
|
1021
|
+
id: triggerId,
|
|
1022
|
+
ref: menu.triggerRef,
|
|
1023
|
+
className: "cv-p-failed-menu__trigger",
|
|
1024
|
+
"aria-haspopup": "menu",
|
|
1025
|
+
"aria-expanded": menu.state.isOpen,
|
|
1026
|
+
"aria-label": label,
|
|
1027
|
+
"data-cv-tooltip": menu.state.isOpen ? void 0 : label,
|
|
1028
|
+
onClick: menu.handleTriggerClick,
|
|
1029
|
+
onKeyDown: menu.handleTriggerKeyDown,
|
|
1030
|
+
children: /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", children: "\u22EF" })
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
menu.state.isOpen ? /* @__PURE__ */ jsx16(ParticipantFailedMenuList, { menu, labelledBy: triggerId, labels, onEdit, onDiscard }) : null
|
|
728
1034
|
] });
|
|
729
1035
|
}
|
|
730
1036
|
|
|
@@ -735,7 +1041,7 @@ import { useMemo } from "react";
|
|
|
735
1041
|
import { Check, Copy } from "lucide-react";
|
|
736
1042
|
|
|
737
1043
|
// src/participant/useParticipantCopyFeedback.ts
|
|
738
|
-
import { useEffect as
|
|
1044
|
+
import { useEffect as useEffect6, useReducer as useReducer3 } from "react";
|
|
739
1045
|
|
|
740
1046
|
// src/participant/participantProtocolCopy.ts
|
|
741
1047
|
async function copyProtocolToClipboard(protocol, clipboard) {
|
|
@@ -756,10 +1062,10 @@ function protocolCopyReducer(state, action) {
|
|
|
756
1062
|
// src/participant/useParticipantCopyFeedback.ts
|
|
757
1063
|
var COPIED_FEEDBACK_MS = 2e3;
|
|
758
1064
|
function useParticipantCopyFeedback() {
|
|
759
|
-
const [state, dispatch] =
|
|
1065
|
+
const [state, dispatch] = useReducer3(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
|
|
760
1066
|
const announce = useAnnounceCopy();
|
|
761
1067
|
const { isCopied, copyCount } = state;
|
|
762
|
-
|
|
1068
|
+
useEffect6(() => {
|
|
763
1069
|
if (!isCopied) return void 0;
|
|
764
1070
|
const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_FEEDBACK_MS);
|
|
765
1071
|
return () => clearTimeout(timer);
|
|
@@ -774,10 +1080,10 @@ function useParticipantCopyFeedback() {
|
|
|
774
1080
|
}
|
|
775
1081
|
|
|
776
1082
|
// src/participant/ParticipantCopyableToken.tsx
|
|
777
|
-
import { jsx as
|
|
1083
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
778
1084
|
function ParticipantCopyableToken({ copyKind, value, labels }) {
|
|
779
1085
|
const { isCopied, copy } = useParticipantCopyFeedback();
|
|
780
|
-
return /* @__PURE__ */
|
|
1086
|
+
return /* @__PURE__ */ jsxs13(
|
|
781
1087
|
"button",
|
|
782
1088
|
{
|
|
783
1089
|
type: "button",
|
|
@@ -786,8 +1092,8 @@ function ParticipantCopyableToken({ copyKind, value, labels }) {
|
|
|
786
1092
|
onClick: () => copy(value, labels.valueCopied),
|
|
787
1093
|
children: [
|
|
788
1094
|
value,
|
|
789
|
-
isCopied ? /* @__PURE__ */
|
|
790
|
-
/* @__PURE__ */
|
|
1095
|
+
isCopied ? /* @__PURE__ */ jsxs13("span", { className: "cv-p-copyable__done", "aria-hidden": "true", children: [
|
|
1096
|
+
/* @__PURE__ */ jsx17(Check, { size: 12, "aria-hidden": true }),
|
|
791
1097
|
" ",
|
|
792
1098
|
labels.valueCopied
|
|
793
1099
|
] }) : null
|
|
@@ -797,33 +1103,34 @@ function ParticipantCopyableToken({ copyKind, value, labels }) {
|
|
|
797
1103
|
}
|
|
798
1104
|
function ParticipantLinkCopyButton({ value, labels }) {
|
|
799
1105
|
const { isCopied, copy } = useParticipantCopyFeedback();
|
|
800
|
-
return /* @__PURE__ */
|
|
1106
|
+
return /* @__PURE__ */ jsx17(
|
|
801
1107
|
"button",
|
|
802
1108
|
{
|
|
803
1109
|
type: "button",
|
|
804
1110
|
className: "cv-p-text__link-copy",
|
|
805
1111
|
"aria-label": `${labels.copyValue} ${value}`,
|
|
1112
|
+
"data-cv-tooltip": labels.copyValue,
|
|
806
1113
|
onClick: () => copy(value, labels.valueCopied),
|
|
807
|
-
children: isCopied ? /* @__PURE__ */
|
|
1114
|
+
children: isCopied ? /* @__PURE__ */ jsx17(Check, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx17(Copy, { size: 14, "aria-hidden": true })
|
|
808
1115
|
}
|
|
809
1116
|
);
|
|
810
1117
|
}
|
|
811
1118
|
|
|
812
1119
|
// src/participant/ParticipantMessageNodes.tsx
|
|
813
|
-
import { jsx as
|
|
1120
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
814
1121
|
var LINK_REL = "noopener noreferrer nofollow ugc";
|
|
815
1122
|
function renderLink(node, labels, key) {
|
|
816
1123
|
const isExternal = /^https?:/i.test(node.href);
|
|
817
|
-
return /* @__PURE__ */
|
|
818
|
-
/* @__PURE__ */
|
|
1124
|
+
return /* @__PURE__ */ jsxs14("span", { className: "cv-p-text__link-group", children: [
|
|
1125
|
+
/* @__PURE__ */ jsxs14("a", { className: "cv-p-text__link", href: node.href, rel: LINK_REL, target: isExternal ? "_blank" : void 0, children: [
|
|
819
1126
|
node.label,
|
|
820
|
-
isExternal ? /* @__PURE__ */
|
|
1127
|
+
isExternal ? /* @__PURE__ */ jsxs14("span", { className: "cv-p-sr-only", children: [
|
|
821
1128
|
" (",
|
|
822
1129
|
labels.externalLinkHint,
|
|
823
1130
|
")"
|
|
824
1131
|
] }) : null
|
|
825
1132
|
] }),
|
|
826
|
-
/* @__PURE__ */
|
|
1133
|
+
/* @__PURE__ */ jsx18(ParticipantLinkCopyButton, { value: node.href, labels })
|
|
827
1134
|
] }, key);
|
|
828
1135
|
}
|
|
829
1136
|
function renderInlineNodes(nodes, labels) {
|
|
@@ -832,17 +1139,17 @@ function renderInlineNodes(nodes, labels) {
|
|
|
832
1139
|
case "text":
|
|
833
1140
|
return node.value;
|
|
834
1141
|
case "strong":
|
|
835
|
-
return /* @__PURE__ */
|
|
1142
|
+
return /* @__PURE__ */ jsx18("strong", { children: renderInlineNodes(node.children, labels) }, key);
|
|
836
1143
|
case "em":
|
|
837
|
-
return /* @__PURE__ */
|
|
1144
|
+
return /* @__PURE__ */ jsx18("em", { children: renderInlineNodes(node.children, labels) }, key);
|
|
838
1145
|
case "del":
|
|
839
|
-
return /* @__PURE__ */
|
|
1146
|
+
return /* @__PURE__ */ jsx18("del", { children: renderInlineNodes(node.children, labels) }, key);
|
|
840
1147
|
case "code":
|
|
841
|
-
return /* @__PURE__ */
|
|
1148
|
+
return /* @__PURE__ */ jsx18("code", { className: "cv-p-text__code", children: node.value }, key);
|
|
842
1149
|
case "link":
|
|
843
1150
|
return renderLink(node, labels, key);
|
|
844
1151
|
case "copyable":
|
|
845
|
-
return /* @__PURE__ */
|
|
1152
|
+
return /* @__PURE__ */ jsx18(ParticipantCopyableToken, { copyKind: node.copyKind, value: node.value, labels }, key);
|
|
846
1153
|
}
|
|
847
1154
|
});
|
|
848
1155
|
}
|
|
@@ -1228,90 +1535,80 @@ function startsFenceLine(line) {
|
|
|
1228
1535
|
}
|
|
1229
1536
|
|
|
1230
1537
|
// src/participant/ParticipantMessageText.tsx
|
|
1231
|
-
import { jsx as
|
|
1538
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1232
1539
|
function renderBlock(block, labels, key) {
|
|
1233
|
-
if (block.kind === "paragraph") return /* @__PURE__ */
|
|
1540
|
+
if (block.kind === "paragraph") return /* @__PURE__ */ jsx19("span", { children: renderInlineNodes(block.children, labels) }, key);
|
|
1234
1541
|
if (block.kind === "pre") {
|
|
1235
|
-
return /* @__PURE__ */
|
|
1542
|
+
return /* @__PURE__ */ jsx19("pre", { className: "cv-p-text__pre", children: /* @__PURE__ */ jsx19("code", { children: block.value }) }, key);
|
|
1236
1543
|
}
|
|
1237
1544
|
if (block.kind === "quote") {
|
|
1238
|
-
return /* @__PURE__ */
|
|
1545
|
+
return /* @__PURE__ */ jsx19("blockquote", { className: "cv-p-text__quote", children: renderInlineNodes(block.children, labels) }, key);
|
|
1239
1546
|
}
|
|
1240
|
-
const items = block.items.map((item, index) => /* @__PURE__ */
|
|
1547
|
+
const items = block.items.map((item, index) => /* @__PURE__ */ jsx19("li", { children: renderInlineNodes(item, labels) }, index));
|
|
1241
1548
|
if (block.ordered) {
|
|
1242
|
-
return /* @__PURE__ */
|
|
1549
|
+
return /* @__PURE__ */ jsx19("ol", { className: "cv-p-text__list", start: block.start === 1 ? void 0 : block.start, children: items }, key);
|
|
1243
1550
|
}
|
|
1244
|
-
return /* @__PURE__ */
|
|
1551
|
+
return /* @__PURE__ */ jsx19("ul", { className: "cv-p-text__list", children: items }, key);
|
|
1245
1552
|
}
|
|
1246
|
-
function ParticipantMessageText({ text, labels }) {
|
|
1553
|
+
function ParticipantMessageText({ text, labels, id }) {
|
|
1247
1554
|
const blocks = useMemo(() => parseParticipantMessage(text), [text]);
|
|
1248
1555
|
if (blocks.length === 0) return null;
|
|
1249
1556
|
const [only] = blocks;
|
|
1250
1557
|
const children = blocks.length === 1 && only?.kind === "paragraph" ? renderInlineNodes(only.children, labels) : blocks.map((block, key) => renderBlock(block, labels, key));
|
|
1251
|
-
return /* @__PURE__ */
|
|
1558
|
+
return /* @__PURE__ */ jsx19("div", { className: "cv-p-text", id, children });
|
|
1252
1559
|
}
|
|
1253
1560
|
|
|
1254
|
-
// src/participant/
|
|
1255
|
-
import {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
try {
|
|
1261
|
-
return SAFE_ATTACHMENT_PROTOCOLS.has(new URL(url.trim()).protocol);
|
|
1262
|
-
} catch {
|
|
1263
|
-
return false;
|
|
1264
|
-
}
|
|
1561
|
+
// src/participant/ParticipantBubbleBody.tsx
|
|
1562
|
+
import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1563
|
+
function BubbleHeading({ content, labels }) {
|
|
1564
|
+
if (content.isMine) return /* @__PURE__ */ jsx20("span", { className: "cv-p-sr-only", children: labels.me });
|
|
1565
|
+
if (!content.authorName) return null;
|
|
1566
|
+
return /* @__PURE__ */ jsx20("span", { className: "cv-p-bubble__author", children: content.authorName });
|
|
1265
1567
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
if (isCurrent) setUrl(isSafeAttachmentUrl(resolved) ? resolved.trim() : void 0);
|
|
1568
|
+
function BubbleBody({ content, labels, ownStatus, textId, resolveAttachmentUrl, onRetry, onDiscard, onEdit }) {
|
|
1569
|
+
return /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
1570
|
+
/* @__PURE__ */ jsx20(BubbleHeading, { content, labels }),
|
|
1571
|
+
content.text ? /* @__PURE__ */ jsx20(ParticipantMessageText, { text: content.text, labels, id: textId }) : null,
|
|
1572
|
+
content.attachments.map((attachment) => /* @__PURE__ */ jsx20(
|
|
1573
|
+
ParticipantAttachmentItem,
|
|
1574
|
+
{
|
|
1575
|
+
attachment,
|
|
1576
|
+
resolveAttachmentUrl,
|
|
1577
|
+
labels
|
|
1277
1578
|
},
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1579
|
+
attachment.id
|
|
1580
|
+
)),
|
|
1581
|
+
content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx20("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
|
|
1582
|
+
/* @__PURE__ */ jsxs15("span", { className: "cv-p-bubble__meta", children: [
|
|
1583
|
+
/* @__PURE__ */ jsx20("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
|
|
1584
|
+
ownStatus ? /* @__PURE__ */ jsx20(OwnStatus, { status: ownStatus, labels, onRetry }) : null,
|
|
1585
|
+
ownStatus === "failed" ? /* @__PURE__ */ jsx20(ParticipantFailedMenu, { labels, onEdit, onDiscard }) : null
|
|
1586
|
+
] })
|
|
1587
|
+
] });
|
|
1287
1588
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
/* @__PURE__ */ jsx17("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
|
|
1312
|
-
]
|
|
1313
|
-
}
|
|
1314
|
-
);
|
|
1589
|
+
|
|
1590
|
+
// src/participant/participantPerspective.ts
|
|
1591
|
+
var DEFAULT_PARTICIPANT_PERSPECTIVE = "participant";
|
|
1592
|
+
var OWN_DIRECTION = {
|
|
1593
|
+
participant: "inbound",
|
|
1594
|
+
operator: "outbound"
|
|
1595
|
+
};
|
|
1596
|
+
function isMessageMine(message, perspective = DEFAULT_PARTICIPANT_PERSPECTIVE) {
|
|
1597
|
+
return message.direction === OWN_DIRECTION[perspective];
|
|
1598
|
+
}
|
|
1599
|
+
function isUnreadByViewer(message, perspective) {
|
|
1600
|
+
if (isMessageMine(message, perspective)) return false;
|
|
1601
|
+
return !message.readAt && message.status !== "read";
|
|
1602
|
+
}
|
|
1603
|
+
function countOtherSideMessages(messages, perspective) {
|
|
1604
|
+
return messages.filter((message) => !isMessageMine(message, perspective)).length;
|
|
1605
|
+
}
|
|
1606
|
+
function countUnreadByViewer(messages, perspective) {
|
|
1607
|
+
return messages.filter((message) => isUnreadByViewer(message, perspective)).length;
|
|
1608
|
+
}
|
|
1609
|
+
function resolveViewerUnread({ messages, perspective, acknowledgedCount }) {
|
|
1610
|
+
if (countOtherSideMessages(messages, perspective) <= acknowledgedCount) return 0;
|
|
1611
|
+
return countUnreadByViewer(messages, perspective);
|
|
1315
1612
|
}
|
|
1316
1613
|
|
|
1317
1614
|
// src/participant/participantMessages.ts
|
|
@@ -1339,8 +1636,8 @@ function mergeParticipantMessages(params) {
|
|
|
1339
1636
|
}
|
|
1340
1637
|
return items.sort((left, right) => createdAtOf(left) - createdAtOf(right));
|
|
1341
1638
|
}
|
|
1342
|
-
function isOwnMessage(message) {
|
|
1343
|
-
return message
|
|
1639
|
+
function isOwnMessage(message, perspective) {
|
|
1640
|
+
return isMessageMine(message, perspective);
|
|
1344
1641
|
}
|
|
1345
1642
|
function resolveOwnMessageStatus(params) {
|
|
1346
1643
|
const { displayState, serverStatus, confirmsRead } = params;
|
|
@@ -1352,12 +1649,12 @@ function resolveOwnMessageStatus(params) {
|
|
|
1352
1649
|
}
|
|
1353
1650
|
|
|
1354
1651
|
// src/participant/ParticipantMessageBubble.tsx
|
|
1355
|
-
import { jsx as
|
|
1356
|
-
function describeItem(item) {
|
|
1652
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1653
|
+
function describeItem(item, perspective) {
|
|
1357
1654
|
if (item.kind === "server") {
|
|
1358
1655
|
const { message } = item;
|
|
1359
1656
|
return {
|
|
1360
|
-
isMine: isOwnMessage(message),
|
|
1657
|
+
isMine: isOwnMessage(message, perspective),
|
|
1361
1658
|
authorName: message.authorName ?? void 0,
|
|
1362
1659
|
text: message.text ?? void 0,
|
|
1363
1660
|
createdAt: message.createdAt,
|
|
@@ -1375,48 +1672,27 @@ function describeItem(item) {
|
|
|
1375
1672
|
displayState: item.displayState
|
|
1376
1673
|
};
|
|
1377
1674
|
}
|
|
1378
|
-
function bubbleClassName(isMine, hasTail) {
|
|
1675
|
+
function bubbleClassName(isMine, hasTail, hasFailed) {
|
|
1379
1676
|
const classes = ["cv-p-bubble"];
|
|
1380
1677
|
if (isMine) classes.push("cv-p-bubble--mine");
|
|
1678
|
+
if (hasFailed) classes.push("cv-p-bubble--failed");
|
|
1381
1679
|
if (!hasTail) classes.push("cv-p-bubble--no-tail");
|
|
1382
1680
|
return classes.join(" ");
|
|
1383
1681
|
}
|
|
1384
|
-
function ParticipantMessageBubble({
|
|
1385
|
-
item,
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
}) {
|
|
1395
|
-
const content = describeItem(item);
|
|
1396
|
-
const { isMine } = content;
|
|
1397
|
-
const ownStatus = isMine ? resolveOwnMessageStatus({ displayState: content.displayState, serverStatus: content.serverStatus, confirmsRead }) : void 0;
|
|
1398
|
-
const bubble = /* @__PURE__ */ jsxs13("div", { className: bubbleClassName(isMine, tail), children: [
|
|
1399
|
-
isMine ? /* @__PURE__ */ jsx18("span", { className: "cv-p-sr-only", children: labels.me }) : content.authorName ? /* @__PURE__ */ jsx18("span", { className: "cv-p-bubble__author", children: content.authorName }) : null,
|
|
1400
|
-
content.text ? /* @__PURE__ */ jsx18(ParticipantMessageText, { text: content.text, labels }) : null,
|
|
1401
|
-
content.attachments.map((attachment) => /* @__PURE__ */ jsx18(
|
|
1402
|
-
ParticipantAttachmentItem,
|
|
1403
|
-
{
|
|
1404
|
-
attachment,
|
|
1405
|
-
resolveAttachmentUrl,
|
|
1406
|
-
labels
|
|
1407
|
-
},
|
|
1408
|
-
attachment.id
|
|
1409
|
-
)),
|
|
1410
|
-
content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx18("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
|
|
1411
|
-
/* @__PURE__ */ jsxs13("span", { className: "cv-p-bubble__meta", children: [
|
|
1412
|
-
/* @__PURE__ */ jsx18("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
|
|
1413
|
-
ownStatus ? /* @__PURE__ */ jsx18(OwnStatus, { status: ownStatus, labels, onRetry }) : null
|
|
1414
|
-
] }),
|
|
1415
|
-
ownStatus === "failed" ? /* @__PURE__ */ jsx18(FailedActions, { labels, onDiscard, onEdit }) : null
|
|
1682
|
+
function ParticipantMessageBubble(props) {
|
|
1683
|
+
const { item, labels, confirmsRead, onRetry, avatar, tail = true, perspective } = props;
|
|
1684
|
+
const textId = useId3();
|
|
1685
|
+
const content = describeItem(item, perspective);
|
|
1686
|
+
const ownStatus = content.isMine ? resolveOwnMessageStatus({ displayState: content.displayState, serverStatus: content.serverStatus, confirmsRead }) : void 0;
|
|
1687
|
+
const isFailed = ownStatus === "failed";
|
|
1688
|
+
const canRetry = isFailed && onRetry !== void 0;
|
|
1689
|
+
const bubble = /* @__PURE__ */ jsxs16("div", { className: bubbleClassName(content.isMine, tail, isFailed), children: [
|
|
1690
|
+
canRetry ? /* @__PURE__ */ jsx21(RetryButton, { labels, onRetry, describedBy: textId }) : null,
|
|
1691
|
+
/* @__PURE__ */ jsx21(BubbleBody, { ...props, content, ownStatus, textId: canRetry ? textId : void 0 })
|
|
1416
1692
|
] });
|
|
1417
|
-
if (avatar === void 0 || isMine) return bubble;
|
|
1418
|
-
return /* @__PURE__ */
|
|
1419
|
-
/* @__PURE__ */
|
|
1693
|
+
if (avatar === void 0 || content.isMine) return bubble;
|
|
1694
|
+
return /* @__PURE__ */ jsxs16("div", { className: "cv-p-bubble-row", children: [
|
|
1695
|
+
/* @__PURE__ */ jsx21("span", { className: "cv-p-bubble-row__avatar", children: avatar }),
|
|
1420
1696
|
bubble
|
|
1421
1697
|
] });
|
|
1422
1698
|
}
|
|
@@ -1438,16 +1714,16 @@ function resolveParticipantBubbleActions(item, handlers) {
|
|
|
1438
1714
|
}
|
|
1439
1715
|
|
|
1440
1716
|
// src/participant/ParticipantTimeline.tsx
|
|
1441
|
-
import { jsx as
|
|
1717
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
1442
1718
|
function createdAtOf2(item) {
|
|
1443
1719
|
return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
|
|
1444
1720
|
}
|
|
1445
1721
|
function keyOf(item) {
|
|
1446
1722
|
return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
|
|
1447
1723
|
}
|
|
1448
|
-
function authorOf(item) {
|
|
1724
|
+
function authorOf(item, perspective) {
|
|
1449
1725
|
if (item.kind === "pending") return { isMine: true, author: null };
|
|
1450
|
-
return { isMine: isOwnMessage(item.message), author: item.message.authorName ?? null };
|
|
1726
|
+
return { isMine: isOwnMessage(item.message, perspective), author: item.message.authorName ?? null };
|
|
1451
1727
|
}
|
|
1452
1728
|
function resolveActions(item, pendingActions) {
|
|
1453
1729
|
if (!pendingActions || item.kind === "server") return {};
|
|
@@ -1455,7 +1731,7 @@ function resolveActions(item, pendingActions) {
|
|
|
1455
1731
|
}
|
|
1456
1732
|
function resolveAvatarSlot({ isEnabled, isMine, showAvatar, author, render }) {
|
|
1457
1733
|
if (!isEnabled || isMine) return void 0;
|
|
1458
|
-
return showAvatar ? /* @__PURE__ */
|
|
1734
|
+
return showAvatar ? /* @__PURE__ */ jsx22(ParticipantAuthorAvatar, { name: author, render }) : null;
|
|
1459
1735
|
}
|
|
1460
1736
|
function ParticipantTimeline(props) {
|
|
1461
1737
|
const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
|
|
@@ -1467,11 +1743,11 @@ function ParticipantTimeline(props) {
|
|
|
1467
1743
|
for (const item of items) {
|
|
1468
1744
|
const day = new Date(createdAtOf2(item));
|
|
1469
1745
|
if (previousDay === void 0 || !isSameDay(previousDay, day)) {
|
|
1470
|
-
nodes.push(/* @__PURE__ */
|
|
1746
|
+
nodes.push(/* @__PURE__ */ jsx22(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
|
|
1471
1747
|
previousAuthor = void 0;
|
|
1472
1748
|
}
|
|
1473
1749
|
previousDay = day;
|
|
1474
|
-
const { isMine, author } = authorOf(item);
|
|
1750
|
+
const { isMine, author } = authorOf(item, props.perspective);
|
|
1475
1751
|
const showAvatar = shouldShowAvatar({ previousAuthor, author, isMine });
|
|
1476
1752
|
previousAuthor = isMine ? void 0 : author;
|
|
1477
1753
|
const avatar = resolveAvatarSlot({
|
|
@@ -1482,7 +1758,7 @@ function ParticipantTimeline(props) {
|
|
|
1482
1758
|
render: props.renderAuthorAvatar
|
|
1483
1759
|
});
|
|
1484
1760
|
nodes.push(
|
|
1485
|
-
/* @__PURE__ */
|
|
1761
|
+
/* @__PURE__ */ jsx22(
|
|
1486
1762
|
ParticipantMessageBubble,
|
|
1487
1763
|
{
|
|
1488
1764
|
item,
|
|
@@ -1490,6 +1766,7 @@ function ParticipantTimeline(props) {
|
|
|
1490
1766
|
confirmsRead,
|
|
1491
1767
|
resolveAttachmentUrl,
|
|
1492
1768
|
tail: props.tail,
|
|
1769
|
+
perspective: props.perspective,
|
|
1493
1770
|
avatar,
|
|
1494
1771
|
...resolveActions(item, pendingActions)
|
|
1495
1772
|
},
|
|
@@ -1497,19 +1774,19 @@ function ParticipantTimeline(props) {
|
|
|
1497
1774
|
)
|
|
1498
1775
|
);
|
|
1499
1776
|
}
|
|
1500
|
-
return /* @__PURE__ */
|
|
1777
|
+
return /* @__PURE__ */ jsx22("div", { className: "cv-p-thread__messages", children: nodes });
|
|
1501
1778
|
}
|
|
1502
1779
|
|
|
1503
1780
|
// src/participant/ParticipantConversationAvatar.tsx
|
|
1504
|
-
import { jsx as
|
|
1781
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
1505
1782
|
function ParticipantConversationAvatar({ icon }) {
|
|
1506
|
-
return /* @__PURE__ */
|
|
1783
|
+
return /* @__PURE__ */ jsx23("span", { className: "cv-p-thread__avatar", "aria-hidden": "true", children: icon });
|
|
1507
1784
|
}
|
|
1508
1785
|
|
|
1509
1786
|
// src/participant/ParticipantProtocolBadge.tsx
|
|
1510
1787
|
import { Check as Check2, Copy as Copy2 } from "lucide-react";
|
|
1511
|
-
import { useEffect as
|
|
1512
|
-
import { Fragment as
|
|
1788
|
+
import { useEffect as useEffect7, useReducer as useReducer4 } from "react";
|
|
1789
|
+
import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1513
1790
|
var COPIED_ANNOUNCEMENT_MS = 3e3;
|
|
1514
1791
|
function ParticipantProtocolBadgeView({
|
|
1515
1792
|
protocol,
|
|
@@ -1518,36 +1795,37 @@ function ParticipantProtocolBadgeView({
|
|
|
1518
1795
|
onCopy
|
|
1519
1796
|
}) {
|
|
1520
1797
|
const { isCopied, copyCount } = copyState;
|
|
1521
|
-
return /* @__PURE__ */
|
|
1522
|
-
/* @__PURE__ */
|
|
1523
|
-
/* @__PURE__ */
|
|
1524
|
-
/* @__PURE__ */
|
|
1798
|
+
return /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
1799
|
+
/* @__PURE__ */ jsxs17("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
|
|
1800
|
+
/* @__PURE__ */ jsxs17("span", { className: "cv-p-protocol__code", children: [
|
|
1801
|
+
/* @__PURE__ */ jsxs17("span", { className: "cv-p-sr-only", children: [
|
|
1525
1802
|
labels.protocolPrefix,
|
|
1526
1803
|
" "
|
|
1527
1804
|
] }),
|
|
1528
1805
|
protocol
|
|
1529
1806
|
] }),
|
|
1530
|
-
/* @__PURE__ */
|
|
1807
|
+
/* @__PURE__ */ jsxs17(
|
|
1531
1808
|
"button",
|
|
1532
1809
|
{
|
|
1533
1810
|
type: "button",
|
|
1534
1811
|
className: "cv-p-protocol__copy",
|
|
1535
1812
|
"aria-label": `${labels.copyProtocol} ${protocol}`,
|
|
1813
|
+
"data-cv-tooltip": labels.copyProtocol,
|
|
1536
1814
|
onClick: onCopy,
|
|
1537
1815
|
children: [
|
|
1538
|
-
isCopied ? /* @__PURE__ */
|
|
1539
|
-
isCopied ? /* @__PURE__ */
|
|
1816
|
+
isCopied ? /* @__PURE__ */ jsx24(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx24(Copy2, { size: 18, "aria-hidden": true }),
|
|
1817
|
+
isCopied ? /* @__PURE__ */ jsx24("span", { className: "cv-p-protocol__copied", "aria-hidden": "true", children: labels.protocolCopied }) : null
|
|
1540
1818
|
]
|
|
1541
1819
|
}
|
|
1542
1820
|
)
|
|
1543
1821
|
] }),
|
|
1544
|
-
/* @__PURE__ */
|
|
1822
|
+
/* @__PURE__ */ jsx24("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx24("span", { children: labels.protocolCopied }, copyCount) : null })
|
|
1545
1823
|
] });
|
|
1546
1824
|
}
|
|
1547
1825
|
function ParticipantProtocolBadge({ protocol, labels }) {
|
|
1548
|
-
const [copyState, dispatch] =
|
|
1826
|
+
const [copyState, dispatch] = useReducer4(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
|
|
1549
1827
|
const { isCopied, copyCount } = copyState;
|
|
1550
|
-
|
|
1828
|
+
useEffect7(() => {
|
|
1551
1829
|
if (!isCopied) return void 0;
|
|
1552
1830
|
const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_ANNOUNCEMENT_MS);
|
|
1553
1831
|
return () => clearTimeout(timer);
|
|
@@ -1556,7 +1834,7 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
1556
1834
|
const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
|
|
1557
1835
|
if (await copyProtocolToClipboard(protocol, clipboard)) dispatch({ type: "copied" });
|
|
1558
1836
|
}
|
|
1559
|
-
return /* @__PURE__ */
|
|
1837
|
+
return /* @__PURE__ */ jsx24(
|
|
1560
1838
|
ParticipantProtocolBadgeView,
|
|
1561
1839
|
{
|
|
1562
1840
|
protocol,
|
|
@@ -1568,19 +1846,19 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
1568
1846
|
}
|
|
1569
1847
|
|
|
1570
1848
|
// src/participant/ParticipantThreadHeader.tsx
|
|
1571
|
-
import { jsx as
|
|
1849
|
+
import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1572
1850
|
function Title({ conversation, labels, onOpenSubject }) {
|
|
1573
1851
|
const { subjectType, subjectId, subjectLabel } = conversation;
|
|
1574
|
-
return /* @__PURE__ */
|
|
1852
|
+
return /* @__PURE__ */ jsx25("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx25(
|
|
1575
1853
|
"button",
|
|
1576
1854
|
{
|
|
1577
1855
|
type: "button",
|
|
1578
1856
|
className: "cv-p-thread__title-button",
|
|
1579
1857
|
"aria-label": `${labels.openSubject}: ${subjectLabel}`,
|
|
1580
1858
|
onClick: () => onOpenSubject({ subjectType, subjectId }),
|
|
1581
|
-
children: /* @__PURE__ */
|
|
1859
|
+
children: /* @__PURE__ */ jsx25("span", { className: "cv-p-thread__title-text", children: subjectLabel })
|
|
1582
1860
|
}
|
|
1583
|
-
) : /* @__PURE__ */
|
|
1861
|
+
) : /* @__PURE__ */ jsx25("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
|
|
1584
1862
|
}
|
|
1585
1863
|
function ParticipantThreadHeader({
|
|
1586
1864
|
conversation,
|
|
@@ -1588,33 +1866,37 @@ function ParticipantThreadHeader({
|
|
|
1588
1866
|
subjectGroups,
|
|
1589
1867
|
renderSubjectIcon,
|
|
1590
1868
|
onBack,
|
|
1591
|
-
onOpenSubject
|
|
1869
|
+
onOpenSubject,
|
|
1870
|
+
counterpartLabel,
|
|
1871
|
+
actions
|
|
1592
1872
|
}) {
|
|
1593
1873
|
const group = subjectGroups?.find((candidate) => candidate.subjectType === conversation.subjectType);
|
|
1594
1874
|
const icon = resolveConversationIcon({ conversation, group, renderSubjectIcon });
|
|
1595
1875
|
const hasChannels = resolveParticipantChannels(conversation.channels).length > 0;
|
|
1596
|
-
return /* @__PURE__ */
|
|
1597
|
-
onBack ? /* @__PURE__ */
|
|
1598
|
-
icon !== void 0 ? /* @__PURE__ */
|
|
1599
|
-
/* @__PURE__ */
|
|
1600
|
-
icon === void 0 && group ? /* @__PURE__ */
|
|
1601
|
-
/* @__PURE__ */
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
/* @__PURE__ */
|
|
1876
|
+
return /* @__PURE__ */ jsxs18("header", { className: "cv-p-thread__header", children: [
|
|
1877
|
+
onBack ? /* @__PURE__ */ jsx25(ParticipantBackButton, { label: labels.back, onBack }) : null,
|
|
1878
|
+
icon !== void 0 ? /* @__PURE__ */ jsx25(ParticipantConversationAvatar, { icon }) : null,
|
|
1879
|
+
/* @__PURE__ */ jsxs18("div", { className: "cv-p-thread__heading", children: [
|
|
1880
|
+
icon === void 0 && group ? /* @__PURE__ */ jsx25("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
|
|
1881
|
+
/* @__PURE__ */ jsx25(Title, { conversation, labels, onOpenSubject }),
|
|
1882
|
+
counterpartLabel ? /* @__PURE__ */ jsx25("p", { className: "cv-p-thread__counterpart", children: counterpartLabel }) : null,
|
|
1883
|
+
conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs18("div", { className: "cv-p-thread__meta", children: [
|
|
1884
|
+
conversation.protocol ? /* @__PURE__ */ jsx25(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
|
|
1885
|
+
/* @__PURE__ */ jsx25(ParticipantChannelBadges, { channels: conversation.channels, labels, variant: "inline", iconSize: 16 })
|
|
1605
1886
|
] }) : null
|
|
1606
|
-
] })
|
|
1887
|
+
] }),
|
|
1888
|
+
actions ? /* @__PURE__ */ jsx25("div", { className: "cv-p-thread__actions", children: actions }) : null
|
|
1607
1889
|
] });
|
|
1608
1890
|
}
|
|
1609
1891
|
|
|
1610
1892
|
// src/participant/ParticipantThread.tsx
|
|
1611
|
-
import { jsx as
|
|
1893
|
+
import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1612
1894
|
function Footer({ props }) {
|
|
1613
1895
|
const { conversation, labels, draft } = props;
|
|
1614
1896
|
if (conversation.status === "closed") {
|
|
1615
|
-
return /* @__PURE__ */
|
|
1897
|
+
return /* @__PURE__ */ jsx26("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
|
|
1616
1898
|
}
|
|
1617
|
-
return /* @__PURE__ */
|
|
1899
|
+
return /* @__PURE__ */ jsx26(
|
|
1618
1900
|
ParticipantComposer,
|
|
1619
1901
|
{
|
|
1620
1902
|
value: draft.value,
|
|
@@ -1631,29 +1913,36 @@ function Footer({ props }) {
|
|
|
1631
1913
|
}
|
|
1632
1914
|
);
|
|
1633
1915
|
}
|
|
1916
|
+
function Header({ props }) {
|
|
1917
|
+
return /* @__PURE__ */ jsx26(
|
|
1918
|
+
ParticipantThreadHeader,
|
|
1919
|
+
{
|
|
1920
|
+
conversation: props.conversation,
|
|
1921
|
+
labels: props.labels,
|
|
1922
|
+
subjectGroups: props.subjectGroups,
|
|
1923
|
+
renderSubjectIcon: props.renderSubjectIcon,
|
|
1924
|
+
onBack: props.onBack,
|
|
1925
|
+
onOpenSubject: props.onOpenSubject,
|
|
1926
|
+
counterpartLabel: props.counterpartLabel,
|
|
1927
|
+
actions: props.headerActions
|
|
1928
|
+
}
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1634
1931
|
function ThreadBody(props) {
|
|
1635
1932
|
const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
|
|
1636
1933
|
const subjectCard = renderSubjectCard?.(conversation);
|
|
1637
1934
|
const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
),
|
|
1650
|
-
subjectCard ? /* @__PURE__ */ jsx23("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
|
|
1651
|
-
/* @__PURE__ */ jsxs16("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
|
|
1652
|
-
loadView.isLoading ? /* @__PURE__ */ jsx23(ParticipantLoading, { labels }) : null,
|
|
1653
|
-
loadView.hasError ? /* @__PURE__ */ jsx23(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
|
|
1654
|
-
hasMore && onLoadOlder ? /* @__PURE__ */ jsx23("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
|
|
1655
|
-
/* @__PURE__ */ jsx23(ParticipantTimeline, { ...props }),
|
|
1656
|
-
/* @__PURE__ */ jsx23(
|
|
1935
|
+
const rootRef = useRef5(null);
|
|
1936
|
+
useSuppressTouchTooltips(rootRef);
|
|
1937
|
+
return /* @__PURE__ */ jsxs19("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, ref: rootRef, children: [
|
|
1938
|
+
/* @__PURE__ */ jsx26(Header, { props }),
|
|
1939
|
+
subjectCard ? /* @__PURE__ */ jsx26("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
|
|
1940
|
+
/* @__PURE__ */ jsxs19("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
|
|
1941
|
+
loadView.isLoading ? /* @__PURE__ */ jsx26(ParticipantLoading, { labels }) : null,
|
|
1942
|
+
loadView.hasError ? /* @__PURE__ */ jsx26(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
|
|
1943
|
+
hasMore && onLoadOlder ? /* @__PURE__ */ jsx26("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
|
|
1944
|
+
/* @__PURE__ */ jsx26(ParticipantTimeline, { ...props }),
|
|
1945
|
+
/* @__PURE__ */ jsx26(
|
|
1657
1946
|
ParticipantScrollToLatest,
|
|
1658
1947
|
{
|
|
1659
1948
|
label: labels.scrollToLatest,
|
|
@@ -1664,14 +1953,20 @@ function ThreadBody(props) {
|
|
|
1664
1953
|
}
|
|
1665
1954
|
)
|
|
1666
1955
|
] }),
|
|
1667
|
-
/* @__PURE__ */
|
|
1668
|
-
/* @__PURE__ */
|
|
1956
|
+
/* @__PURE__ */ jsx26(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
|
|
1957
|
+
/* @__PURE__ */ jsx26(Footer, { props })
|
|
1669
1958
|
] });
|
|
1670
1959
|
}
|
|
1671
1960
|
function ParticipantThread(props) {
|
|
1672
|
-
return /* @__PURE__ */
|
|
1961
|
+
return /* @__PURE__ */ jsxs19(ParticipantCopyAnnouncer, { children: [
|
|
1962
|
+
/* @__PURE__ */ jsx26(ThreadBody, { ...props }),
|
|
1963
|
+
/* @__PURE__ */ jsx26(TooltipLayer, {})
|
|
1964
|
+
] });
|
|
1673
1965
|
}
|
|
1674
1966
|
|
|
1967
|
+
// src/participant/useParticipantThreadController.ts
|
|
1968
|
+
import { useMemo as useMemo4 } from "react";
|
|
1969
|
+
|
|
1675
1970
|
// src/participant/bindAttachmentUrlResolver.ts
|
|
1676
1971
|
function bindAttachmentUrlResolver(api) {
|
|
1677
1972
|
return (attachment, disposition) => api.resolveAttachmentUrl(attachment, disposition);
|
|
@@ -1753,8 +2048,8 @@ function appendEntryToDraft(entry, current) {
|
|
|
1753
2048
|
return { text, files: [...entry.files, ...current.files] };
|
|
1754
2049
|
}
|
|
1755
2050
|
|
|
1756
|
-
// src/participant/
|
|
1757
|
-
import {
|
|
2051
|
+
// src/participant/useParticipantConversationView.ts
|
|
2052
|
+
import { useCallback as useCallback5, useMemo as useMemo3 } from "react";
|
|
1758
2053
|
|
|
1759
2054
|
// src/participant/participantDrafts.ts
|
|
1760
2055
|
var EMPTY_PARTICIPANT_DRAFT = { text: "", files: [] };
|
|
@@ -1782,30 +2077,6 @@ function takeDraftForSend(drafts, subject) {
|
|
|
1782
2077
|
};
|
|
1783
2078
|
}
|
|
1784
2079
|
|
|
1785
|
-
// src/participant/useParticipantDraft.ts
|
|
1786
|
-
function useParticipantDraft(draftsRef, subject) {
|
|
1787
|
-
const [draft, setDraftState] = useState3(() => getDraft(draftsRef.current, subject));
|
|
1788
|
-
function replaceDrafts(next) {
|
|
1789
|
-
draftsRef.current = next;
|
|
1790
|
-
setDraftState(getDraft(next, subject));
|
|
1791
|
-
}
|
|
1792
|
-
function update(change) {
|
|
1793
|
-
replaceDrafts(setDraft(draftsRef.current, subject, { ...getDraft(draftsRef.current, subject), ...change }));
|
|
1794
|
-
}
|
|
1795
|
-
function takeForSend() {
|
|
1796
|
-
const taken = takeDraftForSend(draftsRef.current, subject);
|
|
1797
|
-
if (taken.content) replaceDrafts(taken.drafts);
|
|
1798
|
-
return taken.content;
|
|
1799
|
-
}
|
|
1800
|
-
function restoreFromEntry(entry) {
|
|
1801
|
-
update(appendEntryToDraft(entry, getDraft(draftsRef.current, subject)));
|
|
1802
|
-
}
|
|
1803
|
-
return { draft, setText: (text) => update({ text }), setFiles: (files) => update({ files }), takeForSend, restoreFromEntry };
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
// src/participant/useParticipantConversationView.ts
|
|
1807
|
-
import { useCallback as useCallback4, useMemo as useMemo3 } from "react";
|
|
1808
|
-
|
|
1809
2080
|
// src/participant/participantSendStates.ts
|
|
1810
2081
|
var NO_ENTRIES = [];
|
|
1811
2082
|
function selectSendEntries(states, subject) {
|
|
@@ -1822,7 +2093,7 @@ function participantSendStatesReducer(states, { subject, action }) {
|
|
|
1822
2093
|
}
|
|
1823
2094
|
|
|
1824
2095
|
// src/participant/useParticipantThread.ts
|
|
1825
|
-
import { useCallback as
|
|
2096
|
+
import { useCallback as useCallback4, useEffect as useEffect11, useMemo as useMemo2, useReducer as useReducer5, useRef as useRef9 } from "react";
|
|
1826
2097
|
|
|
1827
2098
|
// src/participant/participantThreadState.ts
|
|
1828
2099
|
var INITIAL_PARTICIPANT_THREAD_STATE = {
|
|
@@ -1858,7 +2129,7 @@ function participantThreadReducer(state, action) {
|
|
|
1858
2129
|
}
|
|
1859
2130
|
|
|
1860
2131
|
// src/participant/useParticipantMarkRead.ts
|
|
1861
|
-
import { useEffect as
|
|
2132
|
+
import { useEffect as useEffect9 } from "react";
|
|
1862
2133
|
|
|
1863
2134
|
// src/participant/shouldMarkRead.ts
|
|
1864
2135
|
function shouldMarkParticipantRead(params) {
|
|
@@ -1870,12 +2141,12 @@ function shouldMarkParticipantRead(params) {
|
|
|
1870
2141
|
}
|
|
1871
2142
|
|
|
1872
2143
|
// src/participant/useDocumentVisibility.ts
|
|
1873
|
-
import { useEffect as
|
|
2144
|
+
import { useEffect as useEffect8, useState as useState4 } from "react";
|
|
1874
2145
|
function useDocumentVisibility() {
|
|
1875
2146
|
const [visibilityState, setVisibilityState] = useState4(
|
|
1876
2147
|
() => typeof document === "undefined" ? "visible" : document.visibilityState
|
|
1877
2148
|
);
|
|
1878
|
-
|
|
2149
|
+
useEffect8(() => {
|
|
1879
2150
|
function handleVisibilityChange() {
|
|
1880
2151
|
setVisibilityState(document.visibilityState);
|
|
1881
2152
|
}
|
|
@@ -1890,18 +2161,18 @@ function useDocumentVisibility() {
|
|
|
1890
2161
|
function useParticipantMarkRead(params) {
|
|
1891
2162
|
const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
|
|
1892
2163
|
const visibilityState = useDocumentVisibility();
|
|
1893
|
-
|
|
2164
|
+
useEffect9(() => {
|
|
1894
2165
|
if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
|
|
1895
2166
|
void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
|
|
1896
2167
|
}, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
|
|
1897
2168
|
}
|
|
1898
2169
|
|
|
1899
2170
|
// src/participant/useParticipantRevalidation.ts
|
|
1900
|
-
import { useEffect as
|
|
2171
|
+
import { useEffect as useEffect10, useRef as useRef6 } from "react";
|
|
1901
2172
|
function useParticipantRevalidation(api, onRevalidate) {
|
|
1902
|
-
const handlerRef =
|
|
2173
|
+
const handlerRef = useRef6(onRevalidate);
|
|
1903
2174
|
handlerRef.current = onRevalidate;
|
|
1904
|
-
|
|
2175
|
+
useEffect10(() => {
|
|
1905
2176
|
function handleFocus() {
|
|
1906
2177
|
handlerRef.current();
|
|
1907
2178
|
}
|
|
@@ -1928,10 +2199,10 @@ function useParticipantRevalidation(api, onRevalidate) {
|
|
|
1928
2199
|
}
|
|
1929
2200
|
|
|
1930
2201
|
// src/participant/useParticipantSend.ts
|
|
1931
|
-
import { useCallback as useCallback2, useRef as
|
|
2202
|
+
import { useCallback as useCallback2, useRef as useRef7 } from "react";
|
|
1932
2203
|
function useParticipantSend(params) {
|
|
1933
2204
|
const { apiRef, subject, dispatchSend, onSentMessage } = params;
|
|
1934
|
-
const entriesRef =
|
|
2205
|
+
const entriesRef = useRef7(params.entries);
|
|
1935
2206
|
entriesRef.current = params.entries;
|
|
1936
2207
|
const deliver = useCallback2(
|
|
1937
2208
|
async (entry) => {
|
|
@@ -1982,6 +2253,24 @@ function useParticipantSend(params) {
|
|
|
1982
2253
|
return { send, retry };
|
|
1983
2254
|
}
|
|
1984
2255
|
|
|
2256
|
+
// src/participant/useParticipantUnread.ts
|
|
2257
|
+
import { useCallback as useCallback3, useRef as useRef8, useState as useState5 } from "react";
|
|
2258
|
+
function useParticipantUnread(params) {
|
|
2259
|
+
const { messages, perspective, reportedUnreadCount, onMarkedRead } = params;
|
|
2260
|
+
const [acknowledgedCount, setAcknowledgedCount] = useState5(0);
|
|
2261
|
+
const messagesRef = useRef8(messages);
|
|
2262
|
+
messagesRef.current = messages;
|
|
2263
|
+
const handleMarkedRead = useCallback3(
|
|
2264
|
+
(subject) => {
|
|
2265
|
+
if (perspective) setAcknowledgedCount(countOtherSideMessages(messagesRef.current, perspective));
|
|
2266
|
+
onMarkedRead?.(subject);
|
|
2267
|
+
},
|
|
2268
|
+
[perspective, onMarkedRead]
|
|
2269
|
+
);
|
|
2270
|
+
const unreadCount = perspective ? resolveViewerUnread({ messages, perspective, acknowledgedCount }) : reportedUnreadCount;
|
|
2271
|
+
return { unreadCount, handleMarkedRead };
|
|
2272
|
+
}
|
|
2273
|
+
|
|
1985
2274
|
// src/participant/useParticipantThread.ts
|
|
1986
2275
|
var THREAD_PAGE_SIZE = 30;
|
|
1987
2276
|
function describeError(error) {
|
|
@@ -1992,15 +2281,15 @@ function isForSubject(event, subject) {
|
|
|
1992
2281
|
return event.subject.subjectType === subject.subjectType && event.subject.subjectId === subject.subjectId;
|
|
1993
2282
|
}
|
|
1994
2283
|
function useParticipantThread(params) {
|
|
1995
|
-
const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [] } = params;
|
|
2284
|
+
const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [], perspective } = params;
|
|
1996
2285
|
const { subjectType, subjectId } = subject;
|
|
1997
2286
|
const stableSubject = useMemo2(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
1998
|
-
const [state, dispatch] =
|
|
1999
|
-
const apiRef =
|
|
2287
|
+
const [state, dispatch] = useReducer5(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
|
|
2288
|
+
const apiRef = useRef9(api);
|
|
2000
2289
|
apiRef.current = api;
|
|
2001
|
-
const activeSubjectKey =
|
|
2290
|
+
const activeSubjectKey = useRef9("");
|
|
2002
2291
|
const confirmsRead = channelCapabilityFor(channel).confirmsRead;
|
|
2003
|
-
const refresh =
|
|
2292
|
+
const refresh = useCallback4(async () => {
|
|
2004
2293
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
2005
2294
|
dispatch({ type: "started" });
|
|
2006
2295
|
try {
|
|
@@ -2013,7 +2302,7 @@ function useParticipantThread(params) {
|
|
|
2013
2302
|
}
|
|
2014
2303
|
}, [stableSubject, subjectType, subjectId]);
|
|
2015
2304
|
const oldestMessageId = state.messages[0]?.id;
|
|
2016
|
-
const loadOlder =
|
|
2305
|
+
const loadOlder = useCallback4(async () => {
|
|
2017
2306
|
if (!oldestMessageId) return;
|
|
2018
2307
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
2019
2308
|
try {
|
|
@@ -2025,9 +2314,9 @@ function useParticipantThread(params) {
|
|
|
2025
2314
|
if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
|
|
2026
2315
|
}
|
|
2027
2316
|
}, [stableSubject, subjectType, subjectId, oldestMessageId]);
|
|
2028
|
-
const handleSentMessage =
|
|
2317
|
+
const handleSentMessage = useCallback4((message) => dispatch({ type: "sendConfirmed", message }), []);
|
|
2029
2318
|
const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
|
|
2030
|
-
|
|
2319
|
+
useEffect11(() => {
|
|
2031
2320
|
activeSubjectKey.current = `${subjectType}:${subjectId}`;
|
|
2032
2321
|
dispatch({ type: "reset" });
|
|
2033
2322
|
void refresh();
|
|
@@ -2035,11 +2324,18 @@ function useParticipantThread(params) {
|
|
|
2035
2324
|
useParticipantRevalidation(api, (event) => {
|
|
2036
2325
|
if (isForSubject(event, stableSubject)) void refresh();
|
|
2037
2326
|
});
|
|
2038
|
-
|
|
2327
|
+
useEffect11(() => {
|
|
2039
2328
|
if (sendEntries.length === 0) return;
|
|
2040
2329
|
dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
|
|
2041
2330
|
}, [sendEntries, state.messages, hostPending, dispatchSend]);
|
|
2042
|
-
|
|
2331
|
+
const unread = useParticipantUnread({ messages: state.messages, perspective, reportedUnreadCount: unreadCount, onMarkedRead });
|
|
2332
|
+
useParticipantMarkRead({
|
|
2333
|
+
apiRef,
|
|
2334
|
+
subject: stableSubject,
|
|
2335
|
+
unreadCount: unread.unreadCount,
|
|
2336
|
+
messageCount: state.messages.length,
|
|
2337
|
+
onMarkedRead: unread.handleMarkedRead
|
|
2338
|
+
});
|
|
2043
2339
|
const localPending = useMemo2(() => toLocalPending(sendEntries), [sendEntries]);
|
|
2044
2340
|
return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
|
|
2045
2341
|
}
|
|
@@ -2049,7 +2345,7 @@ function useParticipantConversationView(params) {
|
|
|
2049
2345
|
const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
|
|
2050
2346
|
const { subjectType, subjectId } = subject;
|
|
2051
2347
|
const sendEntries = selectSendEntries(sendStates, subject);
|
|
2052
|
-
const dispatchSend =
|
|
2348
|
+
const dispatchSend = useCallback5(
|
|
2053
2349
|
(action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
|
|
2054
2350
|
[dispatchSendStates, subjectType, subjectId]
|
|
2055
2351
|
);
|
|
@@ -2061,7 +2357,8 @@ function useParticipantConversationView(params) {
|
|
|
2061
2357
|
onMarkedRead: params.onMarkedRead,
|
|
2062
2358
|
sendEntries,
|
|
2063
2359
|
dispatchSend,
|
|
2064
|
-
hostPending: pendingMessages
|
|
2360
|
+
hostPending: pendingMessages,
|
|
2361
|
+
perspective: params.perspective
|
|
2065
2362
|
});
|
|
2066
2363
|
const items = useMemo3(
|
|
2067
2364
|
() => mergeParticipantMessages({
|
|
@@ -2075,8 +2372,30 @@ function useParticipantConversationView(params) {
|
|
|
2075
2372
|
return { thread, items, sendEntries, dispatchSend };
|
|
2076
2373
|
}
|
|
2077
2374
|
|
|
2375
|
+
// src/participant/useParticipantDraft.ts
|
|
2376
|
+
import { useState as useState6 } from "react";
|
|
2377
|
+
function useParticipantDraft(draftsRef, subject) {
|
|
2378
|
+
const [draft, setDraftState] = useState6(() => getDraft(draftsRef.current, subject));
|
|
2379
|
+
function replaceDrafts(next) {
|
|
2380
|
+
draftsRef.current = next;
|
|
2381
|
+
setDraftState(getDraft(next, subject));
|
|
2382
|
+
}
|
|
2383
|
+
function update(change) {
|
|
2384
|
+
replaceDrafts(setDraft(draftsRef.current, subject, { ...getDraft(draftsRef.current, subject), ...change }));
|
|
2385
|
+
}
|
|
2386
|
+
function takeForSend() {
|
|
2387
|
+
const taken = takeDraftForSend(draftsRef.current, subject);
|
|
2388
|
+
if (taken.content) replaceDrafts(taken.drafts);
|
|
2389
|
+
return taken.content;
|
|
2390
|
+
}
|
|
2391
|
+
function restoreFromEntry(entry) {
|
|
2392
|
+
update(appendEntryToDraft(entry, getDraft(draftsRef.current, subject)));
|
|
2393
|
+
}
|
|
2394
|
+
return { draft, setText: (text) => update({ text }), setFiles: (files) => update({ files }), takeForSend, restoreFromEntry };
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2078
2397
|
// src/participant/useParticipantThreadScroll.ts
|
|
2079
|
-
import { useEffect as
|
|
2398
|
+
import { useEffect as useEffect12, useRef as useRef11 } from "react";
|
|
2080
2399
|
|
|
2081
2400
|
// src/participant/participantNewMessages.ts
|
|
2082
2401
|
function collectServerMessageIds(items) {
|
|
@@ -2093,17 +2412,17 @@ function latestSeenTime(previousIds, items) {
|
|
|
2093
2412
|
}
|
|
2094
2413
|
return latest;
|
|
2095
2414
|
}
|
|
2096
|
-
function countNewIncomingMessages(previousIds, currentItems) {
|
|
2415
|
+
function countNewIncomingMessages(previousIds, currentItems, perspective) {
|
|
2097
2416
|
const latestSeen = latestSeenTime(previousIds, currentItems);
|
|
2098
2417
|
return currentItems.filter(
|
|
2099
|
-
(item) => item.kind === "server" && item.message
|
|
2418
|
+
(item) => item.kind === "server" && !isMessageMine(item.message, perspective) && !previousIds.has(item.message.id) && Date.parse(item.message.createdAt) > latestSeen
|
|
2100
2419
|
).length;
|
|
2101
2420
|
}
|
|
2102
2421
|
|
|
2103
2422
|
// src/participant/useScrollAwayState.ts
|
|
2104
|
-
import { useCallback as
|
|
2423
|
+
import { useCallback as useCallback6 } from "react";
|
|
2105
2424
|
function useScrollAwayState(ref, isNearBottom) {
|
|
2106
|
-
const scrollToLatest =
|
|
2425
|
+
const scrollToLatest = useCallback6(() => {
|
|
2107
2426
|
const element = ref.current;
|
|
2108
2427
|
if (!element) return;
|
|
2109
2428
|
element.scrollTo(
|
|
@@ -2114,7 +2433,7 @@ function useScrollAwayState(ref, isNearBottom) {
|
|
|
2114
2433
|
}
|
|
2115
2434
|
|
|
2116
2435
|
// src/participant/useStickToBottom.ts
|
|
2117
|
-
import { useCallback as
|
|
2436
|
+
import { useCallback as useCallback7, useLayoutEffect as useLayoutEffect3, useRef as useRef10, useState as useState7 } from "react";
|
|
2118
2437
|
|
|
2119
2438
|
// src/participant/participantScroll.ts
|
|
2120
2439
|
var NEAR_BOTTOM_THRESHOLD_PX = 80;
|
|
@@ -2136,17 +2455,17 @@ function resolveScrollAction(params) {
|
|
|
2136
2455
|
|
|
2137
2456
|
// src/participant/useStickToBottom.ts
|
|
2138
2457
|
function useStickToBottom({ firstKey, lastKey }) {
|
|
2139
|
-
const ref =
|
|
2140
|
-
const snapshot =
|
|
2458
|
+
const ref = useRef10(null);
|
|
2459
|
+
const snapshot = useRef10({
|
|
2141
2460
|
hasPositioned: false,
|
|
2142
2461
|
firstKey: void 0,
|
|
2143
2462
|
lastKey: void 0,
|
|
2144
2463
|
scrollTop: 0,
|
|
2145
2464
|
scrollHeight: 0
|
|
2146
2465
|
});
|
|
2147
|
-
const [isNearBottom, setIsNearBottom] =
|
|
2148
|
-
const isNearBottomRef =
|
|
2149
|
-
const onScroll =
|
|
2466
|
+
const [isNearBottom, setIsNearBottom] = useState7(true);
|
|
2467
|
+
const isNearBottomRef = useRef10(true);
|
|
2468
|
+
const onScroll = useCallback7((event) => {
|
|
2150
2469
|
const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
|
|
2151
2470
|
const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
|
|
2152
2471
|
snapshot.current.scrollTop = scrollTop;
|
|
@@ -2154,7 +2473,7 @@ function useStickToBottom({ firstKey, lastKey }) {
|
|
|
2154
2473
|
isNearBottomRef.current = isNear;
|
|
2155
2474
|
setIsNearBottom(isNear);
|
|
2156
2475
|
}, []);
|
|
2157
|
-
|
|
2476
|
+
useLayoutEffect3(() => {
|
|
2158
2477
|
const element = ref.current;
|
|
2159
2478
|
const previous = snapshot.current;
|
|
2160
2479
|
if (!element) return;
|
|
@@ -2194,27 +2513,73 @@ function keyOf2(item) {
|
|
|
2194
2513
|
if (!item) return void 0;
|
|
2195
2514
|
return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
|
|
2196
2515
|
}
|
|
2197
|
-
function useParticipantThreadScroll(items) {
|
|
2516
|
+
function useParticipantThreadScroll(items, perspective) {
|
|
2198
2517
|
const { scroll: base, isNearBottom } = useStickToBottom({
|
|
2199
2518
|
firstKey: keyOf2(items[0]),
|
|
2200
2519
|
lastKey: keyOf2(items[items.length - 1])
|
|
2201
2520
|
});
|
|
2202
2521
|
const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
|
|
2203
2522
|
const scroll = { ...base, isAwayFromBottom, scrollToLatest };
|
|
2204
|
-
const seenIds =
|
|
2205
|
-
|
|
2523
|
+
const seenIds = useRef11(/* @__PURE__ */ new Set());
|
|
2524
|
+
useEffect12(() => {
|
|
2206
2525
|
if (isNearBottom) seenIds.current = collectServerMessageIds(items);
|
|
2207
2526
|
}, [isNearBottom, items]);
|
|
2208
|
-
return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items) };
|
|
2527
|
+
return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items, perspective) };
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// src/participant/useParticipantThreadController.ts
|
|
2531
|
+
function buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending }) {
|
|
2532
|
+
return {
|
|
2533
|
+
retryLocal: (clientMessageId) => void thread.retry(clientMessageId),
|
|
2534
|
+
discardLocal: (clientMessageId) => dispatchSend({ type: "discarded", clientMessageId }),
|
|
2535
|
+
editLocal: handleEdit,
|
|
2536
|
+
...onRetryPending ? { retryHost: onRetryPending } : {}
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function useParticipantThreadController(params) {
|
|
2540
|
+
const { api, subject, conversation, draftsRef } = params;
|
|
2541
|
+
const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, subject);
|
|
2542
|
+
const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView(params);
|
|
2543
|
+
const resolveAttachmentUrl = useMemo4(() => bindAttachmentUrlResolver(api), [api]);
|
|
2544
|
+
const { scroll, newMessagesCount } = useParticipantThreadScroll(items, params.perspective);
|
|
2545
|
+
function handleSend() {
|
|
2546
|
+
const content = takeForSend();
|
|
2547
|
+
if (content) void thread.send(content);
|
|
2548
|
+
}
|
|
2549
|
+
function handleEdit(clientMessageId) {
|
|
2550
|
+
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
2551
|
+
if (!entry) return;
|
|
2552
|
+
restoreFromEntry(entry);
|
|
2553
|
+
dispatchSend({ type: "discarded", clientMessageId });
|
|
2554
|
+
}
|
|
2555
|
+
return {
|
|
2556
|
+
conversation,
|
|
2557
|
+
items,
|
|
2558
|
+
hasMore: thread.hasMore,
|
|
2559
|
+
labels: params.labels,
|
|
2560
|
+
resolveAttachmentUrl,
|
|
2561
|
+
draft: { value: draft.text, onChange: setText, files: draft.files, onFilesChange: setFiles },
|
|
2562
|
+
onSend: handleSend,
|
|
2563
|
+
status: thread.status,
|
|
2564
|
+
refresh: () => void thread.refresh(),
|
|
2565
|
+
scroll,
|
|
2566
|
+
newMessagesCount,
|
|
2567
|
+
isSending: hasSendingEntry(sendEntries),
|
|
2568
|
+
channel: params.channel,
|
|
2569
|
+
locale: params.locale,
|
|
2570
|
+
onLoadOlder: () => void thread.loadOlder(),
|
|
2571
|
+
pendingActions: buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending: params.onRetryPending }),
|
|
2572
|
+
perspective: params.perspective
|
|
2573
|
+
};
|
|
2209
2574
|
}
|
|
2210
2575
|
|
|
2211
2576
|
// src/participant/ParticipantThreadScreen.tsx
|
|
2212
|
-
import { jsx as
|
|
2577
|
+
import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2213
2578
|
function useOpenedConversation(api, subject, isNeeded) {
|
|
2214
|
-
const [opened, setOpened] =
|
|
2579
|
+
const [opened, setOpened] = useState8({ status: "idle" });
|
|
2215
2580
|
const { subjectType, subjectId } = subject;
|
|
2216
2581
|
const canOpen = api.openConversation !== void 0;
|
|
2217
|
-
|
|
2582
|
+
useEffect13(() => {
|
|
2218
2583
|
if (!isNeeded || !canOpen) return;
|
|
2219
2584
|
let isCurrent = true;
|
|
2220
2585
|
setOpened({ status: "loading" });
|
|
@@ -2232,9 +2597,9 @@ function NotFound({
|
|
|
2232
2597
|
labels,
|
|
2233
2598
|
onBack
|
|
2234
2599
|
}) {
|
|
2235
|
-
return /* @__PURE__ */
|
|
2236
|
-
/* @__PURE__ */
|
|
2237
|
-
/* @__PURE__ */
|
|
2600
|
+
return /* @__PURE__ */ jsxs20("div", { className: "cv-p cv-p-thread", children: [
|
|
2601
|
+
/* @__PURE__ */ jsx27("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx27(ParticipantBackButton, { label: labels.back, onBack }) : null }),
|
|
2602
|
+
/* @__PURE__ */ jsx27("p", { className: "cv-p-empty", children: labels.notFound })
|
|
2238
2603
|
] });
|
|
2239
2604
|
}
|
|
2240
2605
|
function ParticipantThreadScreen(props) {
|
|
@@ -2244,59 +2609,25 @@ function ParticipantThreadScreen(props) {
|
|
|
2244
2609
|
);
|
|
2245
2610
|
const opened = useOpenedConversation(api, selected, listed === void 0);
|
|
2246
2611
|
const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
|
|
2247
|
-
if (conversation) return /* @__PURE__ */
|
|
2612
|
+
if (conversation) return /* @__PURE__ */ jsx27(LoadedThread, { ...props, conversation });
|
|
2248
2613
|
const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
|
|
2249
2614
|
const isListLoading = inbox.status === "idle" || inbox.status === "loading";
|
|
2250
2615
|
if (isResolving || api.openConversation === void 0 && isListLoading)
|
|
2251
|
-
return /* @__PURE__ */
|
|
2252
|
-
return /* @__PURE__ */
|
|
2616
|
+
return /* @__PURE__ */ jsx27("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
|
|
2617
|
+
return /* @__PURE__ */ jsx27(NotFound, { labels, onBack: props.onBack });
|
|
2253
2618
|
}
|
|
2254
2619
|
function LoadedThread(props) {
|
|
2255
|
-
const
|
|
2256
|
-
const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, selected);
|
|
2257
|
-
const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView({
|
|
2620
|
+
const core = useParticipantThreadController({
|
|
2258
2621
|
...props,
|
|
2259
|
-
subject: selected,
|
|
2260
|
-
onMarkedRead: inbox.markSubjectRead
|
|
2622
|
+
subject: props.selected,
|
|
2623
|
+
onMarkedRead: props.inbox.markSubjectRead
|
|
2261
2624
|
});
|
|
2262
|
-
|
|
2263
|
-
const { scroll, newMessagesCount } = useParticipantThreadScroll(items);
|
|
2264
|
-
function handleSend() {
|
|
2265
|
-
const content = takeForSend();
|
|
2266
|
-
if (content) void thread.send(content);
|
|
2267
|
-
}
|
|
2268
|
-
function handleEdit(clientMessageId) {
|
|
2269
|
-
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
2270
|
-
if (!entry) return;
|
|
2271
|
-
restoreFromEntry(entry);
|
|
2272
|
-
dispatchSend({ type: "discarded", clientMessageId });
|
|
2273
|
-
}
|
|
2274
|
-
return /* @__PURE__ */ jsx24(
|
|
2625
|
+
return /* @__PURE__ */ jsx27(
|
|
2275
2626
|
ParticipantThread,
|
|
2276
2627
|
{
|
|
2277
|
-
|
|
2278
|
-
items,
|
|
2279
|
-
hasMore: thread.hasMore,
|
|
2280
|
-
labels: props.labels,
|
|
2281
|
-
resolveAttachmentUrl,
|
|
2282
|
-
draft: { value: draft.text, onChange: setText, files: draft.files, onFilesChange: setFiles },
|
|
2283
|
-
onSend: handleSend,
|
|
2284
|
-
status: thread.status,
|
|
2285
|
-
refresh: () => void thread.refresh(),
|
|
2286
|
-
scroll,
|
|
2287
|
-
newMessagesCount,
|
|
2288
|
-
isSending: hasSendingEntry(sendEntries),
|
|
2289
|
-
channel: props.channel,
|
|
2290
|
-
locale: props.locale,
|
|
2628
|
+
...core,
|
|
2291
2629
|
onBack: props.onBack,
|
|
2292
2630
|
onOpenSubject: props.onOpenSubject,
|
|
2293
|
-
onLoadOlder: () => void thread.loadOlder(),
|
|
2294
|
-
pendingActions: {
|
|
2295
|
-
retryLocal: (clientMessageId) => void thread.retry(clientMessageId),
|
|
2296
|
-
discardLocal: (clientMessageId) => dispatchSend({ type: "discarded", clientMessageId }),
|
|
2297
|
-
editLocal: handleEdit,
|
|
2298
|
-
...props.onRetryPending ? { retryHost: props.onRetryPending } : {}
|
|
2299
|
-
},
|
|
2300
2631
|
quickReplies: props.quickReplies,
|
|
2301
2632
|
renderSubjectCard: props.renderSubjectCard,
|
|
2302
2633
|
subjectGroups: props.subjectGroups,
|
|
@@ -2429,14 +2760,14 @@ function buildParticipantThemeStyle(theme) {
|
|
|
2429
2760
|
}
|
|
2430
2761
|
|
|
2431
2762
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
2432
|
-
import { jsx as
|
|
2763
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
2433
2764
|
var ALL_FILTER3 = "all";
|
|
2434
2765
|
function ParticipantConversationsScreen(props) {
|
|
2435
2766
|
const { selected, inbox, subjectGroups, classNames } = props;
|
|
2436
|
-
const [filter, setFilter] =
|
|
2437
|
-
const [searchQuery, setSearchQuery] =
|
|
2438
|
-
const draftsRef =
|
|
2439
|
-
const [sendStates, dispatchSendStates] =
|
|
2767
|
+
const [filter, setFilter] = useState9(ALL_FILTER3);
|
|
2768
|
+
const [searchQuery, setSearchQuery] = useState9("");
|
|
2769
|
+
const draftsRef = useRef12(/* @__PURE__ */ new Map());
|
|
2770
|
+
const [sendStates, dispatchSendStates] = useReducer6(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2440
2771
|
const labels = useMemo5(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
2441
2772
|
const view = useMemo5(
|
|
2442
2773
|
() => groupParticipantConversations({
|
|
@@ -2449,7 +2780,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2449
2780
|
);
|
|
2450
2781
|
const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
|
|
2451
2782
|
const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
|
|
2452
|
-
return /* @__PURE__ */
|
|
2783
|
+
return /* @__PURE__ */ jsx28("div", { className: rootClassName, style: buildParticipantThemeStyle(props.theme), children: selected === void 0 ? /* @__PURE__ */ jsx28("div", { className: ["cv-p-root__screen", classNames?.inbox].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx28(
|
|
2453
2784
|
ParticipantInbox,
|
|
2454
2785
|
{
|
|
2455
2786
|
view,
|
|
@@ -2466,7 +2797,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2466
2797
|
renderSubjectIcon: props.renderSubjectIcon,
|
|
2467
2798
|
search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
|
|
2468
2799
|
}
|
|
2469
|
-
) }) : /* @__PURE__ */
|
|
2800
|
+
) }) : /* @__PURE__ */ jsx28("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx28(
|
|
2470
2801
|
ParticipantThreadScreen,
|
|
2471
2802
|
{
|
|
2472
2803
|
...props,
|
|
@@ -2481,7 +2812,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2481
2812
|
}
|
|
2482
2813
|
|
|
2483
2814
|
// src/participant/useParticipantInbox.ts
|
|
2484
|
-
import { useCallback as
|
|
2815
|
+
import { useCallback as useCallback8, useEffect as useEffect14, useReducer as useReducer7 } from "react";
|
|
2485
2816
|
|
|
2486
2817
|
// src/participant/participantInboxState.ts
|
|
2487
2818
|
var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
|
|
@@ -2531,8 +2862,8 @@ function describeError2(error) {
|
|
|
2531
2862
|
return error instanceof Error ? error.message : "unknown error";
|
|
2532
2863
|
}
|
|
2533
2864
|
function useParticipantInbox(api) {
|
|
2534
|
-
const [state, dispatch] =
|
|
2535
|
-
const refresh =
|
|
2865
|
+
const [state, dispatch] = useReducer7(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
|
|
2866
|
+
const refresh = useCallback8(async () => {
|
|
2536
2867
|
dispatch({ type: "started" });
|
|
2537
2868
|
try {
|
|
2538
2869
|
const page = await api.listConversations();
|
|
@@ -2542,7 +2873,7 @@ function useParticipantInbox(api) {
|
|
|
2542
2873
|
}
|
|
2543
2874
|
}, [api]);
|
|
2544
2875
|
const { nextCursor } = state;
|
|
2545
|
-
const loadMore =
|
|
2876
|
+
const loadMore = useCallback8(async () => {
|
|
2546
2877
|
if (!nextCursor) return;
|
|
2547
2878
|
try {
|
|
2548
2879
|
const page = await api.listConversations({ cursor: nextCursor });
|
|
@@ -2551,10 +2882,10 @@ function useParticipantInbox(api) {
|
|
|
2551
2882
|
dispatch({ type: "failed", error: describeError2(error) });
|
|
2552
2883
|
}
|
|
2553
2884
|
}, [api, nextCursor]);
|
|
2554
|
-
const markSubjectRead =
|
|
2885
|
+
const markSubjectRead = useCallback8((subject) => {
|
|
2555
2886
|
dispatch({ type: "markedRead", subject });
|
|
2556
2887
|
}, []);
|
|
2557
|
-
|
|
2888
|
+
useEffect14(() => {
|
|
2558
2889
|
void refresh();
|
|
2559
2890
|
}, [refresh]);
|
|
2560
2891
|
useParticipantRevalidation(api, () => {
|
|
@@ -2564,11 +2895,11 @@ function useParticipantInbox(api) {
|
|
|
2564
2895
|
}
|
|
2565
2896
|
|
|
2566
2897
|
// src/participant/ParticipantConversations.tsx
|
|
2567
|
-
import { jsx as
|
|
2898
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
2568
2899
|
function ParticipantConversations(props) {
|
|
2569
2900
|
const inbox = useParticipantInbox(props.api);
|
|
2570
2901
|
const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
|
|
2571
|
-
return /* @__PURE__ */
|
|
2902
|
+
return /* @__PURE__ */ jsx29(
|
|
2572
2903
|
ParticipantConversationsScreen,
|
|
2573
2904
|
{
|
|
2574
2905
|
...props,
|
|
@@ -2576,7 +2907,111 @@ function ParticipantConversations(props) {
|
|
|
2576
2907
|
}
|
|
2577
2908
|
);
|
|
2578
2909
|
}
|
|
2910
|
+
|
|
2911
|
+
// src/participant/ConversationThread.tsx
|
|
2912
|
+
import { useCallback as useCallback9, useMemo as useMemo6, useReducer as useReducer8, useRef as useRef13 } from "react";
|
|
2913
|
+
|
|
2914
|
+
// src/participant/conversationThreadApi.ts
|
|
2915
|
+
function toParticipantApi(api) {
|
|
2916
|
+
const { subscribe } = api;
|
|
2917
|
+
return {
|
|
2918
|
+
listConversations: async () => ({ data: [] }),
|
|
2919
|
+
fetchMessages: (subject, params) => api.fetchMessages(subject, params),
|
|
2920
|
+
sendMessage: (input) => api.sendMessage(input),
|
|
2921
|
+
markRead: async (subject) => {
|
|
2922
|
+
if (!api.markRead) throw new Error("The host api has no markRead");
|
|
2923
|
+
await api.markRead(subject);
|
|
2924
|
+
},
|
|
2925
|
+
resolveAttachmentUrl: (attachment, disposition) => api.resolveAttachmentUrl(attachment, disposition),
|
|
2926
|
+
...subscribe ? { subscribe: (listener) => api.subscribe?.(listener) ?? (() => void 0) } : {}
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
// src/participant/conversationThreadSummary.ts
|
|
2931
|
+
function buildThreadSummary({
|
|
2932
|
+
subject,
|
|
2933
|
+
title,
|
|
2934
|
+
protocol,
|
|
2935
|
+
channels,
|
|
2936
|
+
status
|
|
2937
|
+
}) {
|
|
2938
|
+
return {
|
|
2939
|
+
subjectType: subject.subjectType,
|
|
2940
|
+
subjectId: subject.subjectId,
|
|
2941
|
+
subjectLabel: title,
|
|
2942
|
+
lastMessageAt: null,
|
|
2943
|
+
unreadCount: 0,
|
|
2944
|
+
awaitingParticipant: false,
|
|
2945
|
+
status: status ?? "open",
|
|
2946
|
+
...protocol ? { protocol } : {},
|
|
2947
|
+
...channels ? { channels } : {}
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
// src/participant/ConversationThread.tsx
|
|
2952
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
2953
|
+
function useConversationThreadCore(props) {
|
|
2954
|
+
const { perspective = "participant" } = props;
|
|
2955
|
+
const { subjectType, subjectId } = props.subject;
|
|
2956
|
+
const subject = useMemo6(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
2957
|
+
const onMarkedReadRef = useRef13(props.onMarkedRead);
|
|
2958
|
+
onMarkedReadRef.current = props.onMarkedRead;
|
|
2959
|
+
const handleMarkedRead = useCallback9((marked) => onMarkedReadRef.current?.(marked), []);
|
|
2960
|
+
const api = useMemo6(() => toParticipantApi(props.api), [props.api]);
|
|
2961
|
+
const draftsRef = useRef13(/* @__PURE__ */ new Map());
|
|
2962
|
+
const [sendStates, dispatchSendStates] = useReducer8(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2963
|
+
const labels = useMemo6(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
2964
|
+
const { title, protocol, channels, status } = props;
|
|
2965
|
+
const conversation = useMemo6(
|
|
2966
|
+
() => buildThreadSummary({ subject, title, protocol, channels, status }),
|
|
2967
|
+
[subject, title, protocol, channels, status]
|
|
2968
|
+
);
|
|
2969
|
+
return useParticipantThreadController({
|
|
2970
|
+
api,
|
|
2971
|
+
subject,
|
|
2972
|
+
conversation,
|
|
2973
|
+
labels,
|
|
2974
|
+
draftsRef,
|
|
2975
|
+
sendStates,
|
|
2976
|
+
dispatchSendStates,
|
|
2977
|
+
onMarkedRead: handleMarkedRead,
|
|
2978
|
+
channel: props.channel,
|
|
2979
|
+
locale: props.locale,
|
|
2980
|
+
pendingMessages: props.pendingMessages,
|
|
2981
|
+
onRetryPending: props.onRetryPending,
|
|
2982
|
+
perspective
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
function ConversationThreadBody(props) {
|
|
2986
|
+
const core = useConversationThreadCore(props);
|
|
2987
|
+
return /* @__PURE__ */ jsx30(
|
|
2988
|
+
"div",
|
|
2989
|
+
{
|
|
2990
|
+
className: ["cv-p-root", props.className].filter(Boolean).join(" "),
|
|
2991
|
+
style: buildParticipantThemeStyle(props.theme),
|
|
2992
|
+
children: /* @__PURE__ */ jsx30("div", { className: "cv-p-root__screen", children: /* @__PURE__ */ jsx30(
|
|
2993
|
+
ParticipantThread,
|
|
2994
|
+
{
|
|
2995
|
+
...core,
|
|
2996
|
+
onBack: props.onBack,
|
|
2997
|
+
quickReplies: props.quickReplies,
|
|
2998
|
+
subjectGroups: props.subjectGroups,
|
|
2999
|
+
renderSubjectIcon: props.renderSubjectIcon,
|
|
3000
|
+
avatars: props.avatars,
|
|
3001
|
+
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
3002
|
+
tail: props.tail,
|
|
3003
|
+
counterpartLabel: props.counterpartLabel,
|
|
3004
|
+
headerActions: props.headerActions
|
|
3005
|
+
}
|
|
3006
|
+
) })
|
|
3007
|
+
}
|
|
3008
|
+
);
|
|
3009
|
+
}
|
|
3010
|
+
function ConversationThread(props) {
|
|
3011
|
+
return /* @__PURE__ */ jsx30(ConversationThreadBody, { ...props }, draftKey(props.subject));
|
|
3012
|
+
}
|
|
2579
3013
|
export {
|
|
3014
|
+
ConversationThread,
|
|
2580
3015
|
DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS,
|
|
2581
3016
|
ParticipantConversations,
|
|
2582
3017
|
groupParticipantConversations
|