@adatechnology/conversations-ui 0.6.1 → 0.8.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 +221 -19
- package/dist/{ConversationSimulatorPanel-CrfFjiWh.d.ts → ConversationSimulatorPanel-q-wVgBCN.d.ts} +1 -1
- package/dist/{chunk-HOKNKXIT.js → chunk-HMCOTZAX.js} +1 -1
- package/dist/{chunk-FAQLC3TX.js → chunk-L3OFAQPS.js} +2 -4
- package/dist/{chunk-CBYEHPX6.js → chunk-PJNR6UMN.js} +63 -1
- package/dist/flows/index.js +3 -5
- package/dist/index.d.ts +4 -4
- package/dist/index.js +834 -764
- package/dist/participant/index.d.ts +62 -3
- package/dist/participant/index.js +1444 -394
- package/dist/preview/index.d.ts +3 -3
- package/dist/preview/index.js +2 -3
- package/dist/{quickReply.types-B4gmZsSr.d.ts → quickReply.types-DzWH3NYV.d.ts} +1 -1
- package/dist/styles.css +629 -20
- package/package.json +3 -3
- package/src/participant/ConversationThread.test.tsx +165 -0
- package/src/participant/ConversationThread.tsx +117 -0
- package/src/participant/ParticipantAuthorAvatar.test.tsx +30 -0
- package/src/participant/ParticipantAuthorAvatar.tsx +24 -0
- package/src/participant/ParticipantBackButton.tsx +14 -0
- package/src/participant/ParticipantBubbleStatus.tsx +67 -0
- package/src/participant/ParticipantChannelBadges.tsx +13 -4
- package/src/participant/ParticipantComposer.tsx +8 -40
- package/src/participant/ParticipantComposerControls.tsx +83 -0
- package/src/participant/ParticipantConversationAvatar.tsx +13 -0
- package/src/participant/ParticipantConversations.tsx +7 -0
- package/src/participant/ParticipantConversationsOptions.test.tsx +87 -0
- package/src/participant/ParticipantCopyAnnouncer.tsx +48 -0
- package/src/participant/ParticipantCopyableToken.tsx +51 -0
- package/src/participant/ParticipantDayDivider.tsx +18 -0
- package/src/participant/ParticipantInbox.tsx +3 -1
- package/src/participant/ParticipantInboxRow.tsx +6 -4
- package/src/participant/ParticipantMessageBubble.test.tsx +52 -5
- package/src/participant/ParticipantMessageBubble.tsx +32 -84
- package/src/participant/ParticipantMessageNodes.tsx +54 -0
- package/src/participant/ParticipantMessageText.test.tsx +106 -0
- package/src/participant/ParticipantMessageText.tsx +52 -0
- package/src/participant/ParticipantProtocolBadge.tsx +15 -7
- package/src/participant/ParticipantProtocolView.test.tsx +4 -6
- package/src/participant/ParticipantScrollToLatest.test.tsx +153 -0
- package/src/participant/ParticipantScrollToLatest.tsx +40 -0
- package/src/participant/ParticipantThread.test.tsx +37 -0
- package/src/participant/ParticipantThread.tsx +54 -95
- package/src/participant/ParticipantThreadAvatarSequence.test.tsx +65 -0
- package/src/participant/ParticipantThreadHeader.test.tsx +181 -0
- package/src/participant/ParticipantThreadHeader.tsx +83 -0
- package/src/participant/ParticipantThreadScreen.tsx +30 -56
- package/src/participant/ParticipantTimeline.tsx +106 -0
- 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/index.ts +4 -0
- package/src/participant/participantAutoGrow.test.ts +43 -0
- package/src/participant/participantAutoGrow.ts +12 -0
- package/src/participant/participantAvatar.test.ts +74 -0
- package/src/participant/participantAvatar.ts +48 -0
- package/src/participant/participantBubbleTail.test.tsx +107 -0
- package/src/participant/participantChatDesign.test.tsx +198 -0
- package/src/participant/participantContrast.test-helper.ts +116 -0
- package/src/participant/participantCopyAnnouncement.test.ts +52 -0
- package/src/participant/participantCopyAnnouncement.ts +37 -0
- package/src/participant/participantCss.test-helper.ts +34 -0
- package/src/participant/participantDay.test.ts +22 -0
- package/src/participant/participantDay.ts +8 -0
- package/src/participant/participantDocuments.ts +45 -0
- package/src/participant/participantGolden.test.tsx +46 -0
- package/src/participant/participantGoldenScenarios.test-helper.tsx +57 -0
- package/src/participant/participantHeaderA11y.test.tsx +85 -0
- package/src/participant/participantHeaderDesign.test.tsx +111 -0
- package/src/participant/participantLabels.ts +11 -0
- package/src/participant/participantMessageAtoms.ts +53 -0
- package/src/participant/participantMessageCopyables.test.ts +141 -0
- package/src/participant/participantMessageCopyables.ts +76 -0
- package/src/participant/participantMessageFormat.test-helper.ts +21 -0
- package/src/participant/participantMessageFormat.test.ts +177 -0
- package/src/participant/participantMessageFormat.ts +135 -0
- package/src/participant/participantMessageFormat.types.ts +22 -0
- package/src/participant/participantMessageInline.test.ts +82 -0
- package/src/participant/participantMessageInline.ts +99 -0
- package/src/participant/participantMessageLinks.test.ts +100 -0
- package/src/participant/participantMessageLinks.ts +54 -0
- package/src/participant/participantMessageTextStyle.test.ts +137 -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/participantScrollFocus.test.ts +22 -0
- package/src/participant/participantScrollFocus.ts +10 -0
- package/src/participant/participantScrollView.test.ts +74 -0
- package/src/participant/participantScrollView.ts +38 -0
- package/src/participant/participantScrollbars.test.ts +156 -0
- package/src/participant/participantSubjectIcon.test.tsx +27 -0
- package/src/participant/participantSubjectIcon.ts +26 -0
- package/src/participant/participantThemeContrast.test.ts +98 -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/useParticipantConversationView.ts +3 -0
- package/src/participant/useParticipantCopyFeedback.ts +33 -0
- package/src/participant/useParticipantThread.ts +13 -2
- package/src/participant/useParticipantThreadController.ts +112 -0
- package/src/participant/useParticipantThreadScroll.ts +13 -3
- package/src/participant/useParticipantUnread.test.tsx +40 -0
- package/src/participant/useParticipantUnread.ts +38 -0
- package/src/participant/useRevealActiveChip.ts +20 -0
- package/src/participant/useScrollAwayState.ts +24 -0
- package/src/participant/useStickToBottom.ts +16 -2
- package/src/styles.css +151 -23
- package/dist/chunk-MRVFFJEH.js +0 -64
- package/dist/chunk-OVFCLRQQ.js +0 -84
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MessageText
|
|
3
|
-
} from "../chunk-OVFCLRQQ.js";
|
|
4
1
|
import {
|
|
5
2
|
StatusTicks,
|
|
6
3
|
channelCapabilityFor,
|
|
@@ -8,10 +5,9 @@ import {
|
|
|
8
5
|
formatTimestamp,
|
|
9
6
|
isSameDay
|
|
10
7
|
} from "../chunk-O6457XHK.js";
|
|
11
|
-
import "../chunk-CBYEHPX6.js";
|
|
12
8
|
|
|
13
9
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
14
|
-
import { useMemo as
|
|
10
|
+
import { useMemo as useMemo5, useReducer as useReducer5, useRef as useRef10, useState as useState8 } from "react";
|
|
15
11
|
|
|
16
12
|
// src/participant/participantLoadView.ts
|
|
17
13
|
function resolveLoadView({ status, hasItems }) {
|
|
@@ -37,6 +33,7 @@ var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
|
|
|
37
33
|
me: "Me",
|
|
38
34
|
newMessages: "New messages",
|
|
39
35
|
loadOlder: "Load older messages",
|
|
36
|
+
scrollToLatest: "Scroll to latest message",
|
|
40
37
|
closedNotice: "This conversation is closed",
|
|
41
38
|
messageInputLabel: "Message",
|
|
42
39
|
messageInputPlaceholder: "Write a message\u2026",
|
|
@@ -75,7 +72,10 @@ var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
|
|
|
75
72
|
channelWhatsapp: "WhatsApp",
|
|
76
73
|
channelEmail: "Email",
|
|
77
74
|
channelPortal: "Portal",
|
|
78
|
-
channelWebchat: "Web chat"
|
|
75
|
+
channelWebchat: "Web chat",
|
|
76
|
+
externalLinkHint: "opens in a new tab",
|
|
77
|
+
copyValue: "Copy",
|
|
78
|
+
valueCopied: "Copied"
|
|
79
79
|
};
|
|
80
80
|
function formatParticipantLabel(template, count) {
|
|
81
81
|
return template.replace("{count}", String(count));
|
|
@@ -110,14 +110,19 @@ var ICONS = {
|
|
|
110
110
|
Globe,
|
|
111
111
|
MessagesSquare
|
|
112
112
|
};
|
|
113
|
-
function BadgeContent({ descriptor, labels, separator }) {
|
|
113
|
+
function BadgeContent({ descriptor, labels, separator, iconSize }) {
|
|
114
114
|
const Icon = ICONS[descriptor.iconName];
|
|
115
115
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
116
|
-
/* @__PURE__ */ jsx(Icon, { size:
|
|
116
|
+
/* @__PURE__ */ jsx(Icon, { size: iconSize, "aria-hidden": true }),
|
|
117
117
|
/* @__PURE__ */ jsx("span", { className: "cv-p-sr-only", children: `${resolveParticipantChannelLabel(descriptor, labels)}${separator}` })
|
|
118
118
|
] });
|
|
119
119
|
}
|
|
120
|
-
function ParticipantChannelBadges({
|
|
120
|
+
function ParticipantChannelBadges({
|
|
121
|
+
channels,
|
|
122
|
+
labels,
|
|
123
|
+
variant = "list",
|
|
124
|
+
iconSize = 14
|
|
125
|
+
}) {
|
|
121
126
|
const descriptors = resolveParticipantChannels(channels);
|
|
122
127
|
if (descriptors.length === 0) return null;
|
|
123
128
|
if (variant === "inline") {
|
|
@@ -128,12 +133,27 @@ function ParticipantChannelBadges({ channels, labels, variant = "list" }) {
|
|
|
128
133
|
{
|
|
129
134
|
descriptor,
|
|
130
135
|
labels,
|
|
131
|
-
separator: index < descriptors.length - 1 ? ", " : ""
|
|
136
|
+
separator: index < descriptors.length - 1 ? ", " : "",
|
|
137
|
+
iconSize
|
|
132
138
|
}
|
|
133
139
|
) }, descriptor.channel))
|
|
134
140
|
] });
|
|
135
141
|
}
|
|
136
|
-
return /* @__PURE__ */ jsx("ul", { className: "cv-p-channels", "aria-label": labels.channelsGroup, children: descriptors.map((descriptor) => /* @__PURE__ */ jsx("li", { className: `cv-p-channel cv-p-channel--${descriptor.channel}`, children: /* @__PURE__ */ jsx(BadgeContent, { descriptor, labels, separator: "" }) }, descriptor.channel)) });
|
|
142
|
+
return /* @__PURE__ */ jsx("ul", { className: "cv-p-channels", "aria-label": labels.channelsGroup, children: descriptors.map((descriptor) => /* @__PURE__ */ jsx("li", { className: `cv-p-channel cv-p-channel--${descriptor.channel}`, children: /* @__PURE__ */ jsx(BadgeContent, { descriptor, labels, separator: "", iconSize }) }, descriptor.channel)) });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/participant/participantSubjectIcon.ts
|
|
146
|
+
function isDrawable(node) {
|
|
147
|
+
return node !== null && node !== void 0 && node !== false && node !== "";
|
|
148
|
+
}
|
|
149
|
+
function resolveConversationIcon({
|
|
150
|
+
conversation,
|
|
151
|
+
group,
|
|
152
|
+
renderSubjectIcon
|
|
153
|
+
}) {
|
|
154
|
+
const custom = renderSubjectIcon?.(conversation);
|
|
155
|
+
if (isDrawable(custom)) return custom;
|
|
156
|
+
return isDrawable(group?.icon) ? group?.icon : void 0;
|
|
137
157
|
}
|
|
138
158
|
|
|
139
159
|
// src/participant/ParticipantInboxRow.tsx
|
|
@@ -144,11 +164,12 @@ function formatRowTime(iso, locale) {
|
|
|
144
164
|
return date.toLocaleDateString(locale, { day: "2-digit", month: "2-digit" });
|
|
145
165
|
}
|
|
146
166
|
function RowIcon({ props, kind }) {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
const { conversation, group, renderSubjectIcon } = props;
|
|
168
|
+
const hostIcon = renderSubjectIcon?.(conversation);
|
|
169
|
+
if (isDrawable(hostIcon)) {
|
|
170
|
+
return /* @__PURE__ */ jsx2("span", { className: "cv-p-row__icon cv-p-inbox__icon", "aria-hidden": "true", children: hostIcon });
|
|
150
171
|
}
|
|
151
|
-
return /* @__PURE__ */ jsx2("span", { className: "cv-p-row__icon", "aria-hidden": "true", children:
|
|
172
|
+
return /* @__PURE__ */ jsx2("span", { className: "cv-p-row__icon", "aria-hidden": "true", children: resolveConversationIcon({ conversation, group }) ?? kind.slice(0, 2) });
|
|
152
173
|
}
|
|
153
174
|
function ParticipantInboxRow(props) {
|
|
154
175
|
const { conversation, group, labels, locale, onSelect } = props;
|
|
@@ -220,6 +241,41 @@ function ParticipantLoading({ labels }) {
|
|
|
220
241
|
] });
|
|
221
242
|
}
|
|
222
243
|
|
|
244
|
+
// src/participant/useRevealActiveChip.ts
|
|
245
|
+
import { useEffect, useRef } from "react";
|
|
246
|
+
|
|
247
|
+
// src/participant/participantScrollView.ts
|
|
248
|
+
var BADGE_CAP = 99;
|
|
249
|
+
function resolveScrollToLatestView(params) {
|
|
250
|
+
const { isAwayFromBottom, newMessagesCount, hasMessages } = params;
|
|
251
|
+
if (!isAwayFromBottom || !hasMessages) return { isVisible: false, badge: void 0 };
|
|
252
|
+
if (newMessagesCount <= 0) return { isVisible: true, badge: void 0 };
|
|
253
|
+
return { isVisible: true, badge: newMessagesCount > BADGE_CAP ? `${BADGE_CAP}+` : String(newMessagesCount) };
|
|
254
|
+
}
|
|
255
|
+
function scrollToLatestOptions(params) {
|
|
256
|
+
return { top: params.scrollHeight, behavior: params.isReducedMotion ? "auto" : "smooth" };
|
|
257
|
+
}
|
|
258
|
+
function scrollIntoViewOptions(isReducedMotion) {
|
|
259
|
+
return { block: "nearest", inline: "nearest", behavior: isReducedMotion ? "auto" : "smooth" };
|
|
260
|
+
}
|
|
261
|
+
function readPrefersReducedMotion() {
|
|
262
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
263
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/participant/useRevealActiveChip.ts
|
|
267
|
+
function useRevealActiveChip(active) {
|
|
268
|
+
const strip = useRef(null);
|
|
269
|
+
const previousActive = useRef(void 0);
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
const wasActive = previousActive.current;
|
|
272
|
+
previousActive.current = active;
|
|
273
|
+
if (wasActive === void 0 || wasActive === active) return;
|
|
274
|
+
strip.current?.querySelector('[aria-pressed="true"]')?.scrollIntoView(scrollIntoViewOptions(readPrefersReducedMotion()));
|
|
275
|
+
}, [active]);
|
|
276
|
+
return strip;
|
|
277
|
+
}
|
|
278
|
+
|
|
223
279
|
// src/participant/ParticipantInbox.tsx
|
|
224
280
|
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
225
281
|
var ALL_FILTER = "all";
|
|
@@ -234,7 +290,8 @@ function sectionTitle(section, props) {
|
|
|
234
290
|
return props.subjectGroups.find((group) => group.subjectType === section.key)?.label ?? section.key;
|
|
235
291
|
}
|
|
236
292
|
function Filters({ filters, active, labels, onChange }) {
|
|
237
|
-
|
|
293
|
+
const strip = useRevealActiveChip(active);
|
|
294
|
+
return /* @__PURE__ */ jsx5("div", { className: "cv-p-filters", role: "group", "aria-label": labels.filtersGroup, ref: strip, children: filters.map((filter) => /* @__PURE__ */ jsxs5(
|
|
238
295
|
"button",
|
|
239
296
|
{
|
|
240
297
|
type: "button",
|
|
@@ -304,10 +361,90 @@ function ParticipantInbox(props) {
|
|
|
304
361
|
}
|
|
305
362
|
|
|
306
363
|
// src/participant/ParticipantThreadScreen.tsx
|
|
307
|
-
import { useEffect as
|
|
364
|
+
import { useEffect as useEffect11, useState as useState7 } from "react";
|
|
365
|
+
|
|
366
|
+
// src/participant/ParticipantBackButton.tsx
|
|
367
|
+
import { ArrowLeft } from "lucide-react";
|
|
368
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
369
|
+
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" }) });
|
|
371
|
+
}
|
|
308
372
|
|
|
309
373
|
// src/participant/ParticipantComposer.tsx
|
|
310
|
-
import {
|
|
374
|
+
import { useState } from "react";
|
|
375
|
+
|
|
376
|
+
// src/participant/ParticipantComposerControls.tsx
|
|
377
|
+
import { useLayoutEffect, useRef as useRef2 } from "react";
|
|
378
|
+
import { Paperclip, Send } from "lucide-react";
|
|
379
|
+
|
|
380
|
+
// src/participant/participantAutoGrow.ts
|
|
381
|
+
function fitTextareaHeight(target) {
|
|
382
|
+
target.style.height = "auto";
|
|
383
|
+
target.style.height = `${target.scrollHeight + (target.offsetHeight - target.clientHeight)}px`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/participant/ParticipantComposerControls.tsx
|
|
387
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
388
|
+
function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
|
|
389
|
+
const fileInputRef = useRef2(null);
|
|
390
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
391
|
+
/* @__PURE__ */ jsx7(
|
|
392
|
+
"input",
|
|
393
|
+
{
|
|
394
|
+
ref: fileInputRef,
|
|
395
|
+
className: "cv-p-composer__file-input",
|
|
396
|
+
type: "file",
|
|
397
|
+
multiple: true,
|
|
398
|
+
hidden: true,
|
|
399
|
+
accept: acceptedTypes?.join(","),
|
|
400
|
+
onChange: onChosen
|
|
401
|
+
}
|
|
402
|
+
),
|
|
403
|
+
/* @__PURE__ */ jsx7(
|
|
404
|
+
"button",
|
|
405
|
+
{
|
|
406
|
+
type: "button",
|
|
407
|
+
className: "cv-p-composer__attach",
|
|
408
|
+
"aria-label": labels.attach,
|
|
409
|
+
disabled,
|
|
410
|
+
onClick: () => fileInputRef.current?.click(),
|
|
411
|
+
children: /* @__PURE__ */ jsx7(Paperclip, { size: 24, "aria-hidden": "true", focusable: "false" })
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
] });
|
|
415
|
+
}
|
|
416
|
+
function SendButton({ label, disabled }) {
|
|
417
|
+
return /* @__PURE__ */ jsx7(
|
|
418
|
+
"button",
|
|
419
|
+
{
|
|
420
|
+
type: "submit",
|
|
421
|
+
className: "cv-p-button cv-p-button--primary cv-p-composer__send",
|
|
422
|
+
"aria-label": label,
|
|
423
|
+
disabled,
|
|
424
|
+
children: /* @__PURE__ */ jsx7(Send, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
function MessageField({ value, label, placeholder, disabled, maxLength, onChange }) {
|
|
429
|
+
const fieldRef = useRef2(null);
|
|
430
|
+
useLayoutEffect(() => {
|
|
431
|
+
if (fieldRef.current) fitTextareaHeight(fieldRef.current);
|
|
432
|
+
}, [value]);
|
|
433
|
+
return /* @__PURE__ */ jsx7(
|
|
434
|
+
"textarea",
|
|
435
|
+
{
|
|
436
|
+
ref: fieldRef,
|
|
437
|
+
className: "cv-p-composer__input",
|
|
438
|
+
"aria-label": label,
|
|
439
|
+
placeholder,
|
|
440
|
+
rows: 1,
|
|
441
|
+
value,
|
|
442
|
+
maxLength,
|
|
443
|
+
disabled,
|
|
444
|
+
onChange: (event) => onChange(event.target.value)
|
|
445
|
+
}
|
|
446
|
+
);
|
|
447
|
+
}
|
|
311
448
|
|
|
312
449
|
// src/participant/participantFileValidation.ts
|
|
313
450
|
function matchesAcceptedType(mimeType, acceptedTypes) {
|
|
@@ -342,45 +479,24 @@ function applyQuickReply(currentText, replyText) {
|
|
|
342
479
|
}
|
|
343
480
|
|
|
344
481
|
// src/participant/ParticipantComposer.tsx
|
|
345
|
-
import {
|
|
482
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
346
483
|
function QuickReplyStrip({ quickReplies, labels, onPick }) {
|
|
347
|
-
return /* @__PURE__ */
|
|
484
|
+
return /* @__PURE__ */ jsx8("div", { className: "cv-p-quick", role: "group", "aria-label": labels.quickRepliesGroup, children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx8("button", { type: "button", className: "cv-p-chip", onClick: () => onPick(quickReply.body), children: quickReply.title }, quickReply.id)) });
|
|
348
485
|
}
|
|
349
486
|
function FileList({ files, labels, onRemove }) {
|
|
350
487
|
if (files.length === 0) return null;
|
|
351
|
-
return /* @__PURE__ */
|
|
352
|
-
/* @__PURE__ */
|
|
353
|
-
/* @__PURE__ */
|
|
354
|
-
/* @__PURE__ */
|
|
355
|
-
/* @__PURE__ */
|
|
356
|
-
/* @__PURE__ */
|
|
488
|
+
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
|
+
/* @__PURE__ */ jsx8("span", { className: "cv-p-files__name", children: file.name }),
|
|
490
|
+
/* @__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: [
|
|
492
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\xD7" }),
|
|
493
|
+
/* @__PURE__ */ jsx8("span", { className: "cv-p-sr-only", children: labels.removeAttachment })
|
|
357
494
|
] })
|
|
358
495
|
] }, `${file.name}-${file.size}-${index}`)) });
|
|
359
496
|
}
|
|
360
497
|
function rejectionText(rejection, labels) {
|
|
361
498
|
return rejection === "tooLarge" ? labels.attachmentTooLarge : labels.attachmentTypeNotAccepted;
|
|
362
499
|
}
|
|
363
|
-
function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
|
|
364
|
-
const fileInputRef = useRef(null);
|
|
365
|
-
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
366
|
-
/* @__PURE__ */ jsx6(
|
|
367
|
-
"input",
|
|
368
|
-
{
|
|
369
|
-
ref: fileInputRef,
|
|
370
|
-
className: "cv-p-composer__file-input",
|
|
371
|
-
type: "file",
|
|
372
|
-
multiple: true,
|
|
373
|
-
hidden: true,
|
|
374
|
-
accept: acceptedTypes?.join(","),
|
|
375
|
-
onChange: onChosen
|
|
376
|
-
}
|
|
377
|
-
),
|
|
378
|
-
/* @__PURE__ */ jsxs6("button", { type: "button", className: "cv-p-button", disabled, onClick: () => fileInputRef.current?.click(), children: [
|
|
379
|
-
/* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\uFF0B" }),
|
|
380
|
-
/* @__PURE__ */ jsx6("span", { className: "cv-p-sr-only", children: labels.attach })
|
|
381
|
-
] })
|
|
382
|
-
] });
|
|
383
|
-
}
|
|
384
500
|
function useFileSelection(props) {
|
|
385
501
|
const [rejection, setRejection] = useState(void 0);
|
|
386
502
|
const { files, onFilesChange, channel } = props;
|
|
@@ -406,51 +522,737 @@ function ParticipantComposer(props) {
|
|
|
406
522
|
event.preventDefault();
|
|
407
523
|
if (canSend) onSend();
|
|
408
524
|
}
|
|
409
|
-
return /* @__PURE__ */
|
|
410
|
-
props.quickReplies && props.quickReplies.length > 0 ? /* @__PURE__ */
|
|
411
|
-
/* @__PURE__ */
|
|
412
|
-
rejection ? /* @__PURE__ */
|
|
413
|
-
/* @__PURE__ */
|
|
414
|
-
channelCapabilityFor(channel).attachments.accepted ? /* @__PURE__ */
|
|
415
|
-
/* @__PURE__ */
|
|
416
|
-
|
|
525
|
+
return /* @__PURE__ */ jsxs7("form", { className: "cv-p-composer", onSubmit: handleSubmit, children: [
|
|
526
|
+
props.quickReplies && props.quickReplies.length > 0 ? /* @__PURE__ */ jsx8(QuickReplyStrip, { quickReplies: props.quickReplies, labels, onPick: (body) => onChange(applyQuickReply(value, body)) }) : null,
|
|
527
|
+
/* @__PURE__ */ jsx8(FileList, { files, labels, onRemove: handleRemoveFile }),
|
|
528
|
+
rejection ? /* @__PURE__ */ jsx8("p", { className: "cv-p-composer__error", role: "alert", children: rejectionText(rejection, labels) }) : null,
|
|
529
|
+
/* @__PURE__ */ jsxs7("div", { className: "cv-p-composer__row", children: [
|
|
530
|
+
channelCapabilityFor(channel).attachments.accepted ? /* @__PURE__ */ jsx8(AttachControl, { labels, disabled, acceptedTypes: props.acceptedTypes, onChosen: handleFilesChosen }) : null,
|
|
531
|
+
/* @__PURE__ */ jsx8(
|
|
532
|
+
MessageField,
|
|
417
533
|
{
|
|
418
|
-
className: "cv-p-composer__input",
|
|
419
|
-
"aria-label": labels.messageInputLabel,
|
|
420
|
-
placeholder: labels.messageInputPlaceholder,
|
|
421
|
-
rows: 1,
|
|
422
534
|
value,
|
|
423
|
-
|
|
535
|
+
label: labels.messageInputLabel,
|
|
536
|
+
placeholder: labels.messageInputPlaceholder,
|
|
424
537
|
disabled,
|
|
425
|
-
|
|
538
|
+
maxLength: props.maxLength,
|
|
539
|
+
onChange
|
|
426
540
|
}
|
|
427
541
|
),
|
|
428
|
-
/* @__PURE__ */
|
|
429
|
-
/* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\u27A4" }),
|
|
430
|
-
/* @__PURE__ */ jsx6("span", { className: "cv-p-sr-only", children: labels.send })
|
|
431
|
-
] })
|
|
542
|
+
/* @__PURE__ */ jsx8(SendButton, { label: labels.send, disabled: !canSend })
|
|
432
543
|
] })
|
|
433
544
|
] });
|
|
434
545
|
}
|
|
435
546
|
|
|
436
|
-
// src/participant/
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
547
|
+
// src/participant/ParticipantCopyAnnouncer.tsx
|
|
548
|
+
import { createContext, useCallback, useContext, useEffect as useEffect2, useReducer, useRef as useRef3 } from "react";
|
|
549
|
+
|
|
550
|
+
// src/participant/participantCopyAnnouncement.ts
|
|
551
|
+
var INITIAL_COPY_ANNOUNCEMENT = { text: "", tick: 0 };
|
|
552
|
+
var REPEAT_MARKER = "\u200B";
|
|
553
|
+
function copyAnnouncementReducer(state, action) {
|
|
554
|
+
if (action.type === "announce") return { text: action.text, tick: state.tick + 1 };
|
|
555
|
+
return { ...state, text: "" };
|
|
556
|
+
}
|
|
557
|
+
function resolveLiveText(params) {
|
|
558
|
+
const { copy, notice, baselineNotice } = params;
|
|
559
|
+
if (copy.text !== "") {
|
|
560
|
+
const marker = copy.tick % 2 === 0 ? REPEAT_MARKER : "";
|
|
561
|
+
return { text: `${copy.text}${marker}`, baselineNotice: baselineNotice ?? notice };
|
|
562
|
+
}
|
|
563
|
+
if (baselineNotice !== void 0 && baselineNotice === notice) return { text: "", baselineNotice };
|
|
564
|
+
return { text: notice, baselineNotice: void 0 };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/participant/ParticipantCopyAnnouncer.tsx
|
|
568
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
569
|
+
var ANNOUNCEMENT_MS = 3e3;
|
|
570
|
+
var AnnounceContext = createContext(() => void 0);
|
|
571
|
+
var AnnouncementContext = createContext(INITIAL_COPY_ANNOUNCEMENT);
|
|
572
|
+
function useAnnounceCopy() {
|
|
573
|
+
return useContext(AnnounceContext);
|
|
574
|
+
}
|
|
575
|
+
function ParticipantCopyAnnouncer({ children }) {
|
|
576
|
+
const [announcement, dispatch] = useReducer(copyAnnouncementReducer, INITIAL_COPY_ANNOUNCEMENT);
|
|
577
|
+
const announce = useCallback((text) => dispatch({ type: "announce", text }), []);
|
|
578
|
+
useEffect2(() => {
|
|
579
|
+
if (announcement.text === "") return void 0;
|
|
580
|
+
const timer = setTimeout(() => dispatch({ type: "expired" }), ANNOUNCEMENT_MS);
|
|
581
|
+
return () => clearTimeout(timer);
|
|
582
|
+
}, [announcement.tick, announcement.text]);
|
|
583
|
+
return /* @__PURE__ */ jsx9(AnnounceContext.Provider, { value: announce, children: /* @__PURE__ */ jsx9(AnnouncementContext.Provider, { value: announcement, children }) });
|
|
584
|
+
}
|
|
585
|
+
function ParticipantThreadLive({ notice }) {
|
|
586
|
+
const copy = useContext(AnnouncementContext);
|
|
587
|
+
const baselineNotice = useRef3(void 0);
|
|
588
|
+
const resolution = resolveLiveText({ copy, notice, baselineNotice: baselineNotice.current });
|
|
589
|
+
baselineNotice.current = resolution.baselineNotice;
|
|
590
|
+
return /* @__PURE__ */ jsx9("div", { className: "cv-p-thread__live", role: "status", "aria-live": "polite", children: resolution.text });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/participant/ParticipantScrollToLatest.tsx
|
|
594
|
+
import { ChevronDown } from "lucide-react";
|
|
595
|
+
|
|
596
|
+
// src/participant/participantScrollFocus.ts
|
|
597
|
+
var SCROLLER_SELECTOR = ".cv-p-thread__scroll";
|
|
598
|
+
function focusScrollerOf(button) {
|
|
599
|
+
button.closest(SCROLLER_SELECTOR)?.focus({ preventScroll: true });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/participant/ParticipantScrollToLatest.tsx
|
|
603
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
604
|
+
function ParticipantScrollToLatest({
|
|
605
|
+
label,
|
|
606
|
+
isAwayFromBottom,
|
|
607
|
+
hasMessages,
|
|
608
|
+
newMessagesCount,
|
|
609
|
+
onClick
|
|
610
|
+
}) {
|
|
611
|
+
const { isVisible, badge } = resolveScrollToLatestView({ isAwayFromBottom, newMessagesCount, hasMessages });
|
|
612
|
+
if (!isVisible || onClick === void 0) return null;
|
|
613
|
+
return /* @__PURE__ */ jsx10("div", { className: "cv-p-thread__latest", children: /* @__PURE__ */ jsxs8(
|
|
614
|
+
"button",
|
|
615
|
+
{
|
|
616
|
+
type: "button",
|
|
617
|
+
className: "cv-p-thread__latest-button",
|
|
618
|
+
"aria-label": label,
|
|
619
|
+
onClick: (event) => {
|
|
620
|
+
focusScrollerOf(event.currentTarget);
|
|
621
|
+
onClick();
|
|
622
|
+
},
|
|
623
|
+
children: [
|
|
624
|
+
/* @__PURE__ */ jsx10(ChevronDown, { size: 24, "aria-hidden": "true", focusable: "false" }),
|
|
625
|
+
badge ? /* @__PURE__ */ jsx10("span", { className: "cv-p-thread__latest-badge", "aria-hidden": "true", children: badge }) : null
|
|
626
|
+
]
|
|
627
|
+
}
|
|
628
|
+
) });
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/participant/ParticipantAuthorAvatar.tsx
|
|
632
|
+
import { User } from "lucide-react";
|
|
633
|
+
|
|
634
|
+
// src/participant/participantAvatar.ts
|
|
635
|
+
var NAME_PARTICLES = /* @__PURE__ */ new Set([
|
|
636
|
+
"a",
|
|
637
|
+
"e",
|
|
638
|
+
"o",
|
|
639
|
+
"da",
|
|
640
|
+
"das",
|
|
641
|
+
"de",
|
|
642
|
+
"del",
|
|
643
|
+
"di",
|
|
644
|
+
"do",
|
|
645
|
+
"dos",
|
|
646
|
+
"du",
|
|
647
|
+
"la",
|
|
648
|
+
"le",
|
|
649
|
+
"van",
|
|
650
|
+
"von"
|
|
651
|
+
]);
|
|
652
|
+
function lettersOf(word) {
|
|
653
|
+
return word.replace(/[^\p{L}\p{M}]/gu, "");
|
|
654
|
+
}
|
|
655
|
+
function firstLetter(word) {
|
|
656
|
+
return (Array.from(word.normalize("NFC"))[0] ?? "").toUpperCase();
|
|
657
|
+
}
|
|
658
|
+
function authorInitials(name) {
|
|
659
|
+
const [first, ...rest] = (name ?? "").split(/\s+/).map(lettersOf).filter(Boolean);
|
|
660
|
+
if (first === void 0) return void 0;
|
|
661
|
+
const last = rest.filter((word) => !NAME_PARTICLES.has(word.toLowerCase())).pop();
|
|
662
|
+
return `${firstLetter(first)}${last === void 0 ? "" : firstLetter(last)}`;
|
|
663
|
+
}
|
|
664
|
+
function shouldShowAvatar({ previousAuthor, author, isMine }) {
|
|
665
|
+
if (isMine) return false;
|
|
666
|
+
if (previousAuthor === void 0) return true;
|
|
667
|
+
return previousAuthor !== author;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/participant/ParticipantAuthorAvatar.tsx
|
|
671
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
672
|
+
function ParticipantAuthorAvatar({ name, render }) {
|
|
673
|
+
const fromHost = render?.({ name });
|
|
674
|
+
const hasHostContent = !(fromHost == null || fromHost === false || fromHost === "");
|
|
675
|
+
const initials = authorInitials(name);
|
|
676
|
+
const fallback = initials ?? /* @__PURE__ */ jsx11(User, { size: 16, "aria-hidden": true });
|
|
677
|
+
return /* @__PURE__ */ jsx11("span", { className: "cv-p-avatar", "aria-hidden": "true", children: hasHostContent ? fromHost : fallback });
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/participant/participantDay.ts
|
|
681
|
+
function pad(value) {
|
|
682
|
+
return String(value).padStart(2, "0");
|
|
683
|
+
}
|
|
684
|
+
function toDateTimeAttribute(date) {
|
|
685
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/participant/ParticipantDayDivider.tsx
|
|
689
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
690
|
+
function ParticipantDayDivider({ day, locale }) {
|
|
691
|
+
const label = day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" });
|
|
692
|
+
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
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/participant/ParticipantBubbleStatus.tsx
|
|
696
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
697
|
+
function statusText(status, labels) {
|
|
698
|
+
const byStatus = {
|
|
699
|
+
sending: labels.statusSending,
|
|
700
|
+
queued: labels.statusQueued,
|
|
701
|
+
sent: labels.statusSent,
|
|
702
|
+
delivered: labels.statusDelivered,
|
|
703
|
+
read: labels.statusRead,
|
|
704
|
+
failed: labels.statusFailed
|
|
444
705
|
};
|
|
706
|
+
return byStatus[status];
|
|
445
707
|
}
|
|
446
|
-
function
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
708
|
+
function OwnStatus({ status, labels, onRetry }) {
|
|
709
|
+
const ticksStatus = status === "sending" ? "queued" : status;
|
|
710
|
+
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
|
+
const text = status === "failed" ? labels.statusFailedShort : statusText(status, labels);
|
|
718
|
+
return /* @__PURE__ */ jsxs9("span", { className: "cv-p-bubble__status", children: [
|
|
719
|
+
/* @__PURE__ */ jsx13(StatusTicks, { status: ticksStatus, appearance: "stylesheet" }),
|
|
720
|
+
/* @__PURE__ */ jsx13("span", { className: isVisibleText ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
|
|
721
|
+
] });
|
|
722
|
+
}
|
|
723
|
+
function FailedActions({ labels, onDiscard, onEdit }) {
|
|
724
|
+
if (!onDiscard && !onEdit) return null;
|
|
725
|
+
return /* @__PURE__ */ jsxs9("span", { className: "cv-p-bubble__actions", children: [
|
|
726
|
+
onEdit ? /* @__PURE__ */ jsx13("button", { type: "button", className: "cv-p-bubble__action", onClick: onEdit, children: labels.edit }) : null,
|
|
727
|
+
onDiscard ? /* @__PURE__ */ jsx13("button", { type: "button", className: "cv-p-bubble__action", onClick: onDiscard, children: labels.discard }) : null
|
|
728
|
+
] });
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/participant/ParticipantMessageText.tsx
|
|
732
|
+
import { useMemo } from "react";
|
|
733
|
+
|
|
734
|
+
// src/participant/ParticipantCopyableToken.tsx
|
|
735
|
+
import { Check, Copy } from "lucide-react";
|
|
736
|
+
|
|
737
|
+
// src/participant/useParticipantCopyFeedback.ts
|
|
738
|
+
import { useEffect as useEffect3, useReducer as useReducer2 } from "react";
|
|
739
|
+
|
|
740
|
+
// src/participant/participantProtocolCopy.ts
|
|
741
|
+
async function copyProtocolToClipboard(protocol, clipboard) {
|
|
742
|
+
if (clipboard === void 0) return false;
|
|
743
|
+
try {
|
|
744
|
+
await clipboard.writeText(protocol);
|
|
745
|
+
return true;
|
|
746
|
+
} catch {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
var INITIAL_PROTOCOL_COPY_STATE = { copyCount: 0, isCopied: false };
|
|
751
|
+
function protocolCopyReducer(state, action) {
|
|
752
|
+
if (action.type === "copied") return { copyCount: state.copyCount + 1, isCopied: true };
|
|
753
|
+
return { ...state, isCopied: false };
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/participant/useParticipantCopyFeedback.ts
|
|
757
|
+
var COPIED_FEEDBACK_MS = 2e3;
|
|
758
|
+
function useParticipantCopyFeedback() {
|
|
759
|
+
const [state, dispatch] = useReducer2(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
|
|
760
|
+
const announce = useAnnounceCopy();
|
|
761
|
+
const { isCopied, copyCount } = state;
|
|
762
|
+
useEffect3(() => {
|
|
763
|
+
if (!isCopied) return void 0;
|
|
764
|
+
const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_FEEDBACK_MS);
|
|
765
|
+
return () => clearTimeout(timer);
|
|
766
|
+
}, [isCopied, copyCount]);
|
|
767
|
+
async function copyValue(value, announcement) {
|
|
768
|
+
const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
|
|
769
|
+
if (!await copyProtocolToClipboard(value, clipboard)) return;
|
|
770
|
+
dispatch({ type: "copied" });
|
|
771
|
+
announce(announcement);
|
|
772
|
+
}
|
|
773
|
+
return { isCopied, copy: (value, announcement) => void copyValue(value, announcement) };
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/participant/ParticipantCopyableToken.tsx
|
|
777
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
778
|
+
function ParticipantCopyableToken({ copyKind, value, labels }) {
|
|
779
|
+
const { isCopied, copy } = useParticipantCopyFeedback();
|
|
780
|
+
return /* @__PURE__ */ jsxs10(
|
|
781
|
+
"button",
|
|
782
|
+
{
|
|
783
|
+
type: "button",
|
|
784
|
+
className: `cv-p-copyable cv-p-copyable--${copyKind}`,
|
|
785
|
+
"aria-label": `${labels.copyValue} ${value}`,
|
|
786
|
+
onClick: () => copy(value, labels.valueCopied),
|
|
787
|
+
children: [
|
|
788
|
+
value,
|
|
789
|
+
isCopied ? /* @__PURE__ */ jsxs10("span", { className: "cv-p-copyable__done", "aria-hidden": "true", children: [
|
|
790
|
+
/* @__PURE__ */ jsx14(Check, { size: 12, "aria-hidden": true }),
|
|
791
|
+
" ",
|
|
792
|
+
labels.valueCopied
|
|
793
|
+
] }) : null
|
|
794
|
+
]
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
function ParticipantLinkCopyButton({ value, labels }) {
|
|
799
|
+
const { isCopied, copy } = useParticipantCopyFeedback();
|
|
800
|
+
return /* @__PURE__ */ jsx14(
|
|
801
|
+
"button",
|
|
802
|
+
{
|
|
803
|
+
type: "button",
|
|
804
|
+
className: "cv-p-text__link-copy",
|
|
805
|
+
"aria-label": `${labels.copyValue} ${value}`,
|
|
806
|
+
onClick: () => copy(value, labels.valueCopied),
|
|
807
|
+
children: isCopied ? /* @__PURE__ */ jsx14(Check, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx14(Copy, { size: 14, "aria-hidden": true })
|
|
808
|
+
}
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/participant/ParticipantMessageNodes.tsx
|
|
813
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
814
|
+
var LINK_REL = "noopener noreferrer nofollow ugc";
|
|
815
|
+
function renderLink(node, labels, key) {
|
|
816
|
+
const isExternal = /^https?:/i.test(node.href);
|
|
817
|
+
return /* @__PURE__ */ jsxs11("span", { className: "cv-p-text__link-group", children: [
|
|
818
|
+
/* @__PURE__ */ jsxs11("a", { className: "cv-p-text__link", href: node.href, rel: LINK_REL, target: isExternal ? "_blank" : void 0, children: [
|
|
819
|
+
node.label,
|
|
820
|
+
isExternal ? /* @__PURE__ */ jsxs11("span", { className: "cv-p-sr-only", children: [
|
|
821
|
+
" (",
|
|
822
|
+
labels.externalLinkHint,
|
|
823
|
+
")"
|
|
824
|
+
] }) : null
|
|
825
|
+
] }),
|
|
826
|
+
/* @__PURE__ */ jsx15(ParticipantLinkCopyButton, { value: node.href, labels })
|
|
827
|
+
] }, key);
|
|
828
|
+
}
|
|
829
|
+
function renderInlineNodes(nodes, labels) {
|
|
830
|
+
return nodes.map((node, key) => {
|
|
831
|
+
switch (node.kind) {
|
|
832
|
+
case "text":
|
|
833
|
+
return node.value;
|
|
834
|
+
case "strong":
|
|
835
|
+
return /* @__PURE__ */ jsx15("strong", { children: renderInlineNodes(node.children, labels) }, key);
|
|
836
|
+
case "em":
|
|
837
|
+
return /* @__PURE__ */ jsx15("em", { children: renderInlineNodes(node.children, labels) }, key);
|
|
838
|
+
case "del":
|
|
839
|
+
return /* @__PURE__ */ jsx15("del", { children: renderInlineNodes(node.children, labels) }, key);
|
|
840
|
+
case "code":
|
|
841
|
+
return /* @__PURE__ */ jsx15("code", { className: "cv-p-text__code", children: node.value }, key);
|
|
842
|
+
case "link":
|
|
843
|
+
return renderLink(node, labels, key);
|
|
844
|
+
case "copyable":
|
|
845
|
+
return /* @__PURE__ */ jsx15(ParticipantCopyableToken, { copyKind: node.copyKind, value: node.value, labels }, key);
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/participant/participantDocuments.ts
|
|
851
|
+
var CNPJ_FIRST_WEIGHTS = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
|
|
852
|
+
var CNPJ_SECOND_WEIGHTS = [6, ...CNPJ_FIRST_WEIGHTS];
|
|
853
|
+
function charValue(character) {
|
|
854
|
+
return character.charCodeAt(0) - 48;
|
|
855
|
+
}
|
|
856
|
+
function isRepeatedSequence(value) {
|
|
857
|
+
return new Set(value).size === 1;
|
|
858
|
+
}
|
|
859
|
+
function weightedSum(characters, weights) {
|
|
860
|
+
let sum = 0;
|
|
861
|
+
for (let index = 0; index < weights.length; index += 1) {
|
|
862
|
+
sum += charValue(characters.charAt(index)) * (weights[index] ?? 0);
|
|
863
|
+
}
|
|
864
|
+
return sum;
|
|
865
|
+
}
|
|
866
|
+
function cnpjCheckDigit(characters, weights) {
|
|
867
|
+
const remainder = weightedSum(characters, weights) % 11;
|
|
868
|
+
return remainder < 2 ? 0 : 11 - remainder;
|
|
869
|
+
}
|
|
870
|
+
function cpfCheckDigit(digits, length) {
|
|
871
|
+
const weights = Array.from({ length }, (_, index) => length + 1 - index);
|
|
872
|
+
return weightedSum(digits, weights) * 10 % 11 % 10;
|
|
873
|
+
}
|
|
874
|
+
function isValidCpf(digits) {
|
|
875
|
+
if (!/^\d{11}$/.test(digits) || isRepeatedSequence(digits)) return false;
|
|
876
|
+
return cpfCheckDigit(digits, 9) === charValue(digits.charAt(9)) && cpfCheckDigit(digits, 10) === charValue(digits.charAt(10));
|
|
877
|
+
}
|
|
878
|
+
function isValidCnpj(characters) {
|
|
879
|
+
if (!/^[A-Z0-9]{12}\d{2}$/.test(characters) || isRepeatedSequence(characters)) return false;
|
|
880
|
+
const first = cnpjCheckDigit(characters, CNPJ_FIRST_WEIGHTS);
|
|
881
|
+
if (first !== charValue(characters.charAt(12))) return false;
|
|
882
|
+
return cnpjCheckDigit(characters, CNPJ_SECOND_WEIGHTS) === charValue(characters.charAt(13));
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/participant/participantMessageCopyables.ts
|
|
886
|
+
var DOCUMENT_SEPARATORS = /[.\-/\s]/g;
|
|
887
|
+
var ACCESS_KEY_PATTERN = /\d{44}|\d{4}(?: \d{4}){10}/gu;
|
|
888
|
+
var CNPJ_PATTERN = /[A-Z0-9]{2}\.[A-Z0-9]{3}\.[A-Z0-9]{3}\/[A-Z0-9]{4}-\d{2}|[A-Z0-9]{12}\d{2}/gu;
|
|
889
|
+
var CPF_PATTERN = /\d{3}\.\d{3}\.\d{3}-\d{2}|\d{11}/gu;
|
|
890
|
+
var BRAZIL_PHONE_PATTERN = /(?:\+?55[\s.-]?)?(?:\([1-9][1-9]\)|[1-9][1-9])[\s.-]?(?:9\d{4}|[2-5]\d{3})[\s.-]?\d{4}/gu;
|
|
891
|
+
var INTERNATIONAL_PHONE_PATTERN = /\+\d{1,3}(?:[\s.-]?\d{2,5}){2,4}/gu;
|
|
892
|
+
var EMAIL_LOCAL = /^[\p{L}\p{N}._%+-]+$/u;
|
|
893
|
+
var EMAIL_DOMAIN = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)*\.\p{L}{2,}$/u;
|
|
894
|
+
function bare(raw) {
|
|
895
|
+
return raw.replace(DOCUMENT_SEPARATORS, "");
|
|
896
|
+
}
|
|
897
|
+
function isWordCharacter(character) {
|
|
898
|
+
return character !== void 0 && /[\p{L}\p{N}]/u.test(character);
|
|
899
|
+
}
|
|
900
|
+
function hasPhoneDigitCount(raw) {
|
|
901
|
+
const count = raw.replace(/\D/g, "").length;
|
|
902
|
+
return count >= 10 && count <= 15;
|
|
903
|
+
}
|
|
904
|
+
function isFormattedBrazilPhone(raw) {
|
|
905
|
+
return /^\+|\(|^\d{2}[\s.-]/u.test(raw) && hasPhoneDigitCount(raw);
|
|
906
|
+
}
|
|
907
|
+
var SCANNERS = [
|
|
908
|
+
{ copyKind: "accessKey", pattern: ACCESS_KEY_PATTERN, isValid: (raw) => !/^(\d)\1+$/.test(bare(raw)) },
|
|
909
|
+
{ copyKind: "cnpj", pattern: CNPJ_PATTERN, isValid: (raw) => isValidCnpj(bare(raw)) },
|
|
910
|
+
{ copyKind: "cpf", pattern: CPF_PATTERN, isValid: (raw) => isValidCpf(bare(raw)) },
|
|
911
|
+
{ copyKind: "phone", pattern: BRAZIL_PHONE_PATTERN, isValid: isFormattedBrazilPhone },
|
|
912
|
+
{
|
|
913
|
+
copyKind: "phone",
|
|
914
|
+
pattern: INTERNATIONAL_PHONE_PATTERN,
|
|
915
|
+
isValid: (raw) => hasPhoneDigitCount(raw) && !raw.startsWith("+55")
|
|
916
|
+
}
|
|
917
|
+
];
|
|
918
|
+
function scan(text, scanner) {
|
|
919
|
+
const atoms = [];
|
|
920
|
+
for (const match of text.matchAll(scanner.pattern)) {
|
|
921
|
+
const [value] = match;
|
|
922
|
+
const start = match.index;
|
|
923
|
+
const end = start + value.length;
|
|
924
|
+
if (isWordCharacter(text[start - 1]) || isWordCharacter(text[end])) continue;
|
|
925
|
+
if (!scanner.isValid(value)) continue;
|
|
926
|
+
atoms.push({ start, end, node: { kind: "copyable", copyKind: scanner.copyKind, value } });
|
|
927
|
+
}
|
|
928
|
+
return atoms;
|
|
929
|
+
}
|
|
930
|
+
function findEmailAtoms(text) {
|
|
931
|
+
const atoms = [];
|
|
932
|
+
for (let at = text.indexOf("@"); at !== -1; at = text.indexOf("@", at + 1)) {
|
|
933
|
+
let start = at;
|
|
934
|
+
while (start > 0 && /[\p{L}\p{N}._%+-]/u.test(text[start - 1] ?? "")) start -= 1;
|
|
935
|
+
let end = at + 1;
|
|
936
|
+
while (end < text.length && /[\p{L}\p{N}.-]/u.test(text[end] ?? "")) end += 1;
|
|
937
|
+
while (end > at + 1 && /[.-]/.test(text[end - 1] ?? "")) end -= 1;
|
|
938
|
+
const isValid = EMAIL_LOCAL.test(text.slice(start, at)) && EMAIL_DOMAIN.test(text.slice(at + 1, end));
|
|
939
|
+
if (isValid) {
|
|
940
|
+
atoms.push({ start, end, node: { kind: "copyable", copyKind: "email", value: text.slice(start, end) } });
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return atoms;
|
|
944
|
+
}
|
|
945
|
+
function findCopyableAtoms(text) {
|
|
946
|
+
return [...SCANNERS.flatMap((scanner) => scan(text, scanner)), ...findEmailAtoms(text)];
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// src/participant/participantMessageLinks.ts
|
|
950
|
+
var ALLOWED_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
|
|
951
|
+
var URL_PATTERN = /https?:\/\/[^\s<>`\u0000-\u001f]+/giu;
|
|
952
|
+
var TRAILING_PUNCTUATION = `.,;:!?*_~'"]}>`;
|
|
953
|
+
function isAllowedLinkHref(href) {
|
|
954
|
+
try {
|
|
955
|
+
const url = new URL(href);
|
|
956
|
+
return ALLOWED_LINK_PROTOCOLS.has(url.protocol) && url.username === "" && url.password === "";
|
|
957
|
+
} catch {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
function isAlphanumeric(character) {
|
|
962
|
+
return character !== void 0 && /[\p{L}\p{N}]/u.test(character);
|
|
963
|
+
}
|
|
964
|
+
function countOf(text, character) {
|
|
965
|
+
let count = 0;
|
|
966
|
+
for (const current of text) if (current === character) count += 1;
|
|
967
|
+
return count;
|
|
968
|
+
}
|
|
969
|
+
function trimUrl(raw) {
|
|
970
|
+
let openCount = countOf(raw, "(");
|
|
971
|
+
let closeCount = countOf(raw, ")");
|
|
972
|
+
let end = raw.length;
|
|
973
|
+
while (end > 0) {
|
|
974
|
+
const last = raw.charAt(end - 1);
|
|
975
|
+
if (last === ")" && closeCount > openCount) closeCount -= 1;
|
|
976
|
+
else if (!TRAILING_PUNCTUATION.includes(last)) break;
|
|
977
|
+
end -= 1;
|
|
978
|
+
}
|
|
979
|
+
return raw.slice(0, end);
|
|
980
|
+
}
|
|
981
|
+
function toLinkAtom(text, start, raw) {
|
|
982
|
+
const label = trimUrl(raw);
|
|
983
|
+
if (/^https?:\/\/$/i.test(label) || isAlphanumeric(text[start - 1])) return void 0;
|
|
984
|
+
const href = label.replace(/^https?/i, (scheme) => scheme.toLowerCase());
|
|
985
|
+
if (!isAllowedLinkHref(href)) return void 0;
|
|
986
|
+
return { start, end: start + label.length, node: { kind: "link", href, label } };
|
|
987
|
+
}
|
|
988
|
+
function findLinkAtoms(text) {
|
|
989
|
+
const atoms = [];
|
|
990
|
+
for (const match of text.matchAll(URL_PATTERN)) {
|
|
991
|
+
const atom = toLinkAtom(text, match.index, match[0]);
|
|
992
|
+
if (atom) atoms.push(atom);
|
|
993
|
+
}
|
|
994
|
+
return atoms;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// src/participant/participantMessageAtoms.ts
|
|
998
|
+
var TRIPLE_FENCE = "```";
|
|
999
|
+
function spanAt(text, open) {
|
|
1000
|
+
const isTriple = text.startsWith(TRIPLE_FENCE, open);
|
|
1001
|
+
const width = isTriple ? 3 : 1;
|
|
1002
|
+
const close = text.indexOf(isTriple ? TRIPLE_FENCE : "`", open + width);
|
|
1003
|
+
if (close === -1) return void 0;
|
|
1004
|
+
return { contentStart: open + width, close, width };
|
|
1005
|
+
}
|
|
1006
|
+
function findCodeAtoms(text) {
|
|
1007
|
+
const atoms = [];
|
|
1008
|
+
let cursor = text.indexOf("`");
|
|
1009
|
+
while (cursor !== -1) {
|
|
1010
|
+
const span = spanAt(text, cursor);
|
|
1011
|
+
if (span === void 0) {
|
|
1012
|
+
if (!text.startsWith(TRIPLE_FENCE, cursor)) break;
|
|
1013
|
+
cursor = text.indexOf("`", cursor + 3);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (span.close === span.contentStart) {
|
|
1017
|
+
cursor = text.indexOf("`", cursor + span.width);
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
const end = span.close + span.width;
|
|
1021
|
+
atoms.push({ start: cursor, end, node: { kind: "code", value: text.slice(span.contentStart, span.close) } });
|
|
1022
|
+
cursor = text.indexOf("`", end);
|
|
1023
|
+
}
|
|
1024
|
+
return atoms;
|
|
1025
|
+
}
|
|
1026
|
+
function findInlineAtoms(text) {
|
|
1027
|
+
const candidates = [...findCodeAtoms(text), ...findLinkAtoms(text), ...findCopyableAtoms(text)];
|
|
1028
|
+
const ordered = candidates.map((atom, rank) => ({ atom, rank })).sort((a, b) => a.atom.start - b.atom.start || a.rank - b.rank);
|
|
1029
|
+
const accepted = [];
|
|
1030
|
+
let lastEnd = 0;
|
|
1031
|
+
for (const { atom } of ordered) {
|
|
1032
|
+
if (atom.start < lastEnd) continue;
|
|
1033
|
+
accepted.push(atom);
|
|
1034
|
+
lastEnd = atom.end;
|
|
1035
|
+
}
|
|
1036
|
+
return accepted;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/participant/participantMessageInline.ts
|
|
1040
|
+
var MARKS = { "*": "strong", _: "em", "~": "del" };
|
|
1041
|
+
function isWord(character) {
|
|
1042
|
+
return character !== void 0 && /[\p{L}\p{N}]/u.test(character);
|
|
1043
|
+
}
|
|
1044
|
+
function isSpace(character) {
|
|
1045
|
+
return character === void 0 || /\s/u.test(character);
|
|
1046
|
+
}
|
|
1047
|
+
function characterBefore(text, index) {
|
|
1048
|
+
if (index <= 0) return void 0;
|
|
1049
|
+
const code = text.charCodeAt(index - 1);
|
|
1050
|
+
const isLowSurrogate = code >= 56320 && code <= 57343;
|
|
1051
|
+
if (isLowSurrogate && index >= 2) return String.fromCodePoint(text.codePointAt(index - 2) ?? code);
|
|
1052
|
+
return text.charAt(index - 1);
|
|
1053
|
+
}
|
|
1054
|
+
function buildContext(text) {
|
|
1055
|
+
const atoms = findInlineAtoms(text);
|
|
1056
|
+
const isMasked = new Array(text.length).fill(false);
|
|
1057
|
+
for (const atom of atoms) isMasked.fill(true, atom.start, atom.end);
|
|
1058
|
+
const closers = { "*": [], _: [], "~": [] };
|
|
1059
|
+
for (let index = 1; index < text.length; index += 1) {
|
|
1060
|
+
const character = text.charAt(index);
|
|
1061
|
+
const list = closers[character];
|
|
1062
|
+
const isCloser = list !== void 0 && !isMasked[index] && !isSpace(text[index - 1]) && !isWord(text[index + 1]);
|
|
1063
|
+
if (isCloser) list.push(index);
|
|
1064
|
+
}
|
|
1065
|
+
return { text, atomAt: new Map(atoms.map((atom) => [atom.start, atom])), isMasked, closers };
|
|
1066
|
+
}
|
|
1067
|
+
function isOpener(context, index) {
|
|
1068
|
+
const { text } = context;
|
|
1069
|
+
const next = text[index + 1];
|
|
1070
|
+
if (context.isMasked[index] || next === void 0 || isSpace(next) || next === text[index]) return false;
|
|
1071
|
+
return !isWord(characterBefore(text, index));
|
|
1072
|
+
}
|
|
1073
|
+
function findCloser(context, mark, from, limit) {
|
|
1074
|
+
const list = context.closers[mark] ?? [];
|
|
1075
|
+
let low = 0;
|
|
1076
|
+
let high = list.length;
|
|
1077
|
+
while (low < high) {
|
|
1078
|
+
const middle = low + high >> 1;
|
|
1079
|
+
if ((list[middle] ?? 0) < from) low = middle + 1;
|
|
1080
|
+
else high = middle;
|
|
1081
|
+
}
|
|
1082
|
+
const found = list[low];
|
|
1083
|
+
return found !== void 0 && found < limit ? found : void 0;
|
|
1084
|
+
}
|
|
1085
|
+
function parseRange(context, start, end) {
|
|
1086
|
+
const nodes = [];
|
|
1087
|
+
let textStart = start;
|
|
1088
|
+
let index = start;
|
|
1089
|
+
const flush = (until) => {
|
|
1090
|
+
if (until > textStart) nodes.push({ kind: "text", value: context.text.slice(textStart, until) });
|
|
1091
|
+
};
|
|
1092
|
+
while (index < end) {
|
|
1093
|
+
const atom = context.atomAt.get(index);
|
|
1094
|
+
const mark = MARKS[context.text.charAt(index)];
|
|
1095
|
+
const closer = mark !== void 0 && !atom && isOpener(context, index) ? findCloser(context, context.text.charAt(index), index + 2, end) : void 0;
|
|
1096
|
+
if (atom) {
|
|
1097
|
+
flush(index);
|
|
1098
|
+
nodes.push(atom.node);
|
|
1099
|
+
index = atom.end;
|
|
1100
|
+
} else if (mark !== void 0 && closer !== void 0) {
|
|
1101
|
+
flush(index);
|
|
1102
|
+
nodes.push({ kind: mark, children: parseRange(context, index + 1, closer) });
|
|
1103
|
+
index = closer + 1;
|
|
1104
|
+
} else {
|
|
1105
|
+
index += 1;
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
textStart = index;
|
|
1109
|
+
}
|
|
1110
|
+
flush(end);
|
|
1111
|
+
return nodes;
|
|
1112
|
+
}
|
|
1113
|
+
function parseInline(line) {
|
|
1114
|
+
return parseRange(buildContext(line), 0, line.length);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/participant/participantMessageFormat.ts
|
|
1118
|
+
var MAX_FORMATTED_LENGTH = 8e3;
|
|
1119
|
+
var FENCE = "```";
|
|
1120
|
+
var LANGUAGE_LABEL = /^[a-z][a-z0-9+#.-]{0,19}$/i;
|
|
1121
|
+
var QUOTE_PREFIX = /^> /;
|
|
1122
|
+
var BULLET_PREFIX = /^[-*•] +(?=\S)/;
|
|
1123
|
+
var ORDERED_PREFIX = /^(\d{1,3})[.)] +(?=\S)/;
|
|
1124
|
+
function joinLines(lines) {
|
|
1125
|
+
const nodes = [];
|
|
1126
|
+
lines.forEach((line, index) => {
|
|
1127
|
+
if (index > 0) nodes.push({ kind: "text", value: "\n" });
|
|
1128
|
+
nodes.push(...parseInline(line));
|
|
1129
|
+
});
|
|
1130
|
+
return mergeText(nodes);
|
|
1131
|
+
}
|
|
1132
|
+
function mergeText(nodes) {
|
|
1133
|
+
const merged = [];
|
|
1134
|
+
for (const node of nodes) {
|
|
1135
|
+
const previous = merged[merged.length - 1];
|
|
1136
|
+
if (node.kind === "text" && previous?.kind === "text") {
|
|
1137
|
+
merged[merged.length - 1] = { kind: "text", value: previous.value + node.value };
|
|
1138
|
+
} else {
|
|
1139
|
+
merged.push(node);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return merged;
|
|
1143
|
+
}
|
|
1144
|
+
function plainParagraph(text) {
|
|
1145
|
+
return [{ kind: "paragraph", children: [{ kind: "text", value: text }] }];
|
|
1146
|
+
}
|
|
1147
|
+
function readFence(lines, index) {
|
|
1148
|
+
const first = (lines[index] ?? "").trimStart();
|
|
1149
|
+
if (!first.startsWith(FENCE)) return void 0;
|
|
1150
|
+
const inner = first.slice(FENCE.length);
|
|
1151
|
+
if (inner.trimEnd().endsWith(FENCE) && inner.trim() !== FENCE) {
|
|
1152
|
+
return { value: inner.trimEnd().slice(0, -FENCE.length), nextIndex: index + 1 };
|
|
1153
|
+
}
|
|
1154
|
+
const hasLabel = LANGUAGE_LABEL.test(inner.trim());
|
|
1155
|
+
const body = inner === "" || hasLabel ? [] : [inner];
|
|
1156
|
+
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
1157
|
+
const line = (lines[cursor] ?? "").trimEnd();
|
|
1158
|
+
if (!line.endsWith(FENCE)) {
|
|
1159
|
+
body.push(line);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
const last = line.slice(0, -FENCE.length);
|
|
1163
|
+
if (last !== "") body.push(last);
|
|
1164
|
+
if (last !== "" && hasLabel && body.length === 1) body.unshift(inner);
|
|
1165
|
+
return { value: body.join("\n"), nextIndex: cursor + 1 };
|
|
1166
|
+
}
|
|
1167
|
+
return void 0;
|
|
1168
|
+
}
|
|
1169
|
+
function startsSpecialBlock(line) {
|
|
1170
|
+
const trimmed = line.trimStart();
|
|
1171
|
+
return trimmed.startsWith(FENCE) || QUOTE_PREFIX.test(line) || BULLET_PREFIX.test(line) || ORDERED_PREFIX.test(line);
|
|
1172
|
+
}
|
|
1173
|
+
function readQuote(lines, index) {
|
|
1174
|
+
const quoted = [];
|
|
1175
|
+
let cursor = index;
|
|
1176
|
+
while (cursor < lines.length && QUOTE_PREFIX.test(lines[cursor] ?? "")) {
|
|
1177
|
+
quoted.push((lines[cursor] ?? "").slice(2));
|
|
1178
|
+
cursor += 1;
|
|
1179
|
+
}
|
|
1180
|
+
return { block: { kind: "quote", children: joinLines(quoted) }, nextIndex: cursor };
|
|
1181
|
+
}
|
|
1182
|
+
function readList(lines, index) {
|
|
1183
|
+
const ordered = ORDERED_PREFIX.test(lines[index] ?? "");
|
|
1184
|
+
const prefix = ordered ? ORDERED_PREFIX : BULLET_PREFIX;
|
|
1185
|
+
const start = ordered ? Number(ORDERED_PREFIX.exec(lines[index] ?? "")?.[1] ?? 1) : 1;
|
|
1186
|
+
const items = [];
|
|
1187
|
+
let cursor = index;
|
|
1188
|
+
while (cursor < lines.length && prefix.test(lines[cursor] ?? "")) {
|
|
1189
|
+
items.push(parseInline((lines[cursor] ?? "").replace(prefix, "")));
|
|
1190
|
+
cursor += 1;
|
|
1191
|
+
}
|
|
1192
|
+
return { block: { kind: "list", ordered, start, items }, nextIndex: cursor };
|
|
1193
|
+
}
|
|
1194
|
+
function readParagraph(lines, index) {
|
|
1195
|
+
const collected = [lines[index] ?? ""];
|
|
1196
|
+
let cursor = index + 1;
|
|
1197
|
+
while (cursor < lines.length && !startsSpecialBlock(lines[cursor] ?? "")) {
|
|
1198
|
+
collected.push(lines[cursor] ?? "");
|
|
1199
|
+
cursor += 1;
|
|
1200
|
+
}
|
|
1201
|
+
return { block: { kind: "paragraph", children: joinLines(collected) }, nextIndex: cursor };
|
|
1202
|
+
}
|
|
1203
|
+
function readBlock(lines, index, canFence) {
|
|
1204
|
+
const line = lines[index] ?? "";
|
|
1205
|
+
const fence = canFence ? readFence(lines, index) : void 0;
|
|
1206
|
+
if (fence) return { block: { kind: "pre", value: fence.value }, nextIndex: fence.nextIndex };
|
|
1207
|
+
if (QUOTE_PREFIX.test(line)) return readQuote(lines, index);
|
|
1208
|
+
if (BULLET_PREFIX.test(line) || ORDERED_PREFIX.test(line)) return readList(lines, index);
|
|
1209
|
+
return readParagraph(lines, index);
|
|
1210
|
+
}
|
|
1211
|
+
function parseParticipantMessage(text) {
|
|
1212
|
+
if (typeof text !== "string" || text.trim() === "") return [];
|
|
1213
|
+
if (text.length > MAX_FORMATTED_LENGTH) return plainParagraph(text);
|
|
1214
|
+
const lines = text.split(/\r\n?|\n/);
|
|
1215
|
+
const blocks = [];
|
|
1216
|
+
let canFence = true;
|
|
1217
|
+
let index = 0;
|
|
1218
|
+
while (index < lines.length) {
|
|
1219
|
+
const { block, nextIndex } = readBlock(lines, index, canFence);
|
|
1220
|
+
if (block.kind === "paragraph" && startsFenceLine(lines[index])) canFence = false;
|
|
1221
|
+
blocks.push(block);
|
|
1222
|
+
index = nextIndex;
|
|
1223
|
+
}
|
|
1224
|
+
return blocks;
|
|
1225
|
+
}
|
|
1226
|
+
function startsFenceLine(line) {
|
|
1227
|
+
return (line ?? "").trimStart().startsWith(FENCE);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// src/participant/ParticipantMessageText.tsx
|
|
1231
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1232
|
+
function renderBlock(block, labels, key) {
|
|
1233
|
+
if (block.kind === "paragraph") return /* @__PURE__ */ jsx16("span", { children: renderInlineNodes(block.children, labels) }, key);
|
|
1234
|
+
if (block.kind === "pre") {
|
|
1235
|
+
return /* @__PURE__ */ jsx16("pre", { className: "cv-p-text__pre", children: /* @__PURE__ */ jsx16("code", { children: block.value }) }, key);
|
|
1236
|
+
}
|
|
1237
|
+
if (block.kind === "quote") {
|
|
1238
|
+
return /* @__PURE__ */ jsx16("blockquote", { className: "cv-p-text__quote", children: renderInlineNodes(block.children, labels) }, key);
|
|
1239
|
+
}
|
|
1240
|
+
const items = block.items.map((item, index) => /* @__PURE__ */ jsx16("li", { children: renderInlineNodes(item, labels) }, index));
|
|
1241
|
+
if (block.ordered) {
|
|
1242
|
+
return /* @__PURE__ */ jsx16("ol", { className: "cv-p-text__list", start: block.start === 1 ? void 0 : block.start, children: items }, key);
|
|
1243
|
+
}
|
|
1244
|
+
return /* @__PURE__ */ jsx16("ul", { className: "cv-p-text__list", children: items }, key);
|
|
1245
|
+
}
|
|
1246
|
+
function ParticipantMessageText({ text, labels }) {
|
|
1247
|
+
const blocks = useMemo(() => parseParticipantMessage(text), [text]);
|
|
1248
|
+
if (blocks.length === 0) return null;
|
|
1249
|
+
const [only] = blocks;
|
|
1250
|
+
const children = blocks.length === 1 && only?.kind === "paragraph" ? renderInlineNodes(only.children, labels) : blocks.map((block, key) => renderBlock(block, labels, key));
|
|
1251
|
+
return /* @__PURE__ */ jsx16("div", { className: "cv-p-text", children });
|
|
450
1252
|
}
|
|
451
1253
|
|
|
452
1254
|
// src/participant/ParticipantAttachmentItem.tsx
|
|
453
|
-
import { useEffect, useState as useState2 } from "react";
|
|
1255
|
+
import { useEffect as useEffect4, useState as useState2 } from "react";
|
|
454
1256
|
|
|
455
1257
|
// src/participant/participantAttachmentUrl.ts
|
|
456
1258
|
var SAFE_ATTACHMENT_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:", "blob:"]);
|
|
@@ -463,11 +1265,11 @@ function isSafeAttachmentUrl(url) {
|
|
|
463
1265
|
}
|
|
464
1266
|
|
|
465
1267
|
// src/participant/ParticipantAttachmentItem.tsx
|
|
466
|
-
import { jsx as
|
|
1268
|
+
import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
467
1269
|
function useAttachmentUrl(attachment, resolveAttachmentUrl) {
|
|
468
1270
|
const [url, setUrl] = useState2(void 0);
|
|
469
1271
|
const disposition = attachment.kind === "document" ? "attachment" : "inline";
|
|
470
|
-
|
|
1272
|
+
useEffect4(() => {
|
|
471
1273
|
let isCurrent = true;
|
|
472
1274
|
resolveAttachmentUrl(attachment, disposition).then(
|
|
473
1275
|
(resolved) => {
|
|
@@ -486,18 +1288,18 @@ function useAttachmentUrl(attachment, resolveAttachmentUrl) {
|
|
|
486
1288
|
function ParticipantAttachmentItem({ attachment, resolveAttachmentUrl, labels }) {
|
|
487
1289
|
const url = useAttachmentUrl(attachment, resolveAttachmentUrl);
|
|
488
1290
|
if (attachment.kind === "image") {
|
|
489
|
-
return /* @__PURE__ */
|
|
490
|
-
url ? /* @__PURE__ */
|
|
491
|
-
/* @__PURE__ */
|
|
1291
|
+
return /* @__PURE__ */ jsxs12("figure", { className: "cv-p-attachment cv-p-attachment--image", children: [
|
|
1292
|
+
url ? /* @__PURE__ */ jsx17("img", { className: "cv-p-attachment__image", src: url, alt: attachment.filename }) : null,
|
|
1293
|
+
/* @__PURE__ */ jsx17("figcaption", { className: "cv-p-attachment__name", children: attachment.filename })
|
|
492
1294
|
] });
|
|
493
1295
|
}
|
|
494
1296
|
if (attachment.kind === "audio") {
|
|
495
|
-
return /* @__PURE__ */
|
|
496
|
-
/* @__PURE__ */
|
|
497
|
-
/* @__PURE__ */
|
|
1297
|
+
return /* @__PURE__ */ jsxs12("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
|
|
1298
|
+
/* @__PURE__ */ jsx17("span", { className: "cv-p-attachment__name", children: attachment.filename }),
|
|
1299
|
+
/* @__PURE__ */ jsx17("audio", { className: "cv-p-attachment__audio", controls: true, preload: "none", src: url })
|
|
498
1300
|
] });
|
|
499
1301
|
}
|
|
500
|
-
return /* @__PURE__ */
|
|
1302
|
+
return /* @__PURE__ */ jsxs12(
|
|
501
1303
|
"a",
|
|
502
1304
|
{
|
|
503
1305
|
className: "cv-p-attachment cv-p-attachment--document",
|
|
@@ -505,13 +1307,37 @@ function ParticipantAttachmentItem({ attachment, resolveAttachmentUrl, labels })
|
|
|
505
1307
|
download: attachment.filename,
|
|
506
1308
|
"aria-label": `${labels.downloadAttachment}: ${attachment.filename}`,
|
|
507
1309
|
children: [
|
|
508
|
-
/* @__PURE__ */
|
|
509
|
-
/* @__PURE__ */
|
|
1310
|
+
/* @__PURE__ */ jsx17("span", { className: "cv-p-attachment__name", children: attachment.filename }),
|
|
1311
|
+
/* @__PURE__ */ jsx17("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
|
|
510
1312
|
]
|
|
511
1313
|
}
|
|
512
1314
|
);
|
|
513
1315
|
}
|
|
514
1316
|
|
|
1317
|
+
// src/participant/participantPerspective.ts
|
|
1318
|
+
var DEFAULT_PARTICIPANT_PERSPECTIVE = "participant";
|
|
1319
|
+
var OWN_DIRECTION = {
|
|
1320
|
+
participant: "inbound",
|
|
1321
|
+
operator: "outbound"
|
|
1322
|
+
};
|
|
1323
|
+
function isMessageMine(message, perspective = DEFAULT_PARTICIPANT_PERSPECTIVE) {
|
|
1324
|
+
return message.direction === OWN_DIRECTION[perspective];
|
|
1325
|
+
}
|
|
1326
|
+
function isUnreadByViewer(message, perspective) {
|
|
1327
|
+
if (isMessageMine(message, perspective)) return false;
|
|
1328
|
+
return !message.readAt && message.status !== "read";
|
|
1329
|
+
}
|
|
1330
|
+
function countOtherSideMessages(messages, perspective) {
|
|
1331
|
+
return messages.filter((message) => !isMessageMine(message, perspective)).length;
|
|
1332
|
+
}
|
|
1333
|
+
function countUnreadByViewer(messages, perspective) {
|
|
1334
|
+
return messages.filter((message) => isUnreadByViewer(message, perspective)).length;
|
|
1335
|
+
}
|
|
1336
|
+
function resolveViewerUnread({ messages, perspective, acknowledgedCount }) {
|
|
1337
|
+
if (countOtherSideMessages(messages, perspective) <= acknowledgedCount) return 0;
|
|
1338
|
+
return countUnreadByViewer(messages, perspective);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
515
1341
|
// src/participant/participantMessages.ts
|
|
516
1342
|
function isSameSubject(left, right) {
|
|
517
1343
|
return left.subjectType === right.subjectType && left.subjectId === right.subjectId;
|
|
@@ -537,8 +1363,8 @@ function mergeParticipantMessages(params) {
|
|
|
537
1363
|
}
|
|
538
1364
|
return items.sort((left, right) => createdAtOf(left) - createdAtOf(right));
|
|
539
1365
|
}
|
|
540
|
-
function isOwnMessage(message) {
|
|
541
|
-
return message
|
|
1366
|
+
function isOwnMessage(message, perspective) {
|
|
1367
|
+
return isMessageMine(message, perspective);
|
|
542
1368
|
}
|
|
543
1369
|
function resolveOwnMessageStatus(params) {
|
|
544
1370
|
const { displayState, serverStatus, confirmsRead } = params;
|
|
@@ -550,12 +1376,12 @@ function resolveOwnMessageStatus(params) {
|
|
|
550
1376
|
}
|
|
551
1377
|
|
|
552
1378
|
// src/participant/ParticipantMessageBubble.tsx
|
|
553
|
-
import { jsx as
|
|
554
|
-
function describeItem(item) {
|
|
1379
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1380
|
+
function describeItem(item, perspective) {
|
|
555
1381
|
if (item.kind === "server") {
|
|
556
1382
|
const { message } = item;
|
|
557
1383
|
return {
|
|
558
|
-
isMine: isOwnMessage(message),
|
|
1384
|
+
isMine: isOwnMessage(message, perspective),
|
|
559
1385
|
authorName: message.authorName ?? void 0,
|
|
560
1386
|
text: message.text ?? void 0,
|
|
561
1387
|
createdAt: message.createdAt,
|
|
@@ -573,48 +1399,11 @@ function describeItem(item) {
|
|
|
573
1399
|
displayState: item.displayState
|
|
574
1400
|
};
|
|
575
1401
|
}
|
|
576
|
-
function
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
delivered: labels.statusDelivered,
|
|
582
|
-
read: labels.statusRead,
|
|
583
|
-
failed: labels.statusFailed
|
|
584
|
-
};
|
|
585
|
-
return byStatus[status];
|
|
586
|
-
}
|
|
587
|
-
function OwnStatus({ status, labels, onRetry }) {
|
|
588
|
-
const ticksStatus = status === "sending" ? "queued" : status;
|
|
589
|
-
const isVisibleText = status === "sending" || status === "queued" || status === "failed";
|
|
590
|
-
if (status === "failed" && onRetry) {
|
|
591
|
-
return /* @__PURE__ */ jsxs8("button", { type: "button", className: "cv-p-bubble__retry", onClick: onRetry, children: [
|
|
592
|
-
/* @__PURE__ */ jsx8(StatusTicks, { status: "failed", appearance: "stylesheet" }),
|
|
593
|
-
/* @__PURE__ */ jsx8("span", { children: labels.statusFailed })
|
|
594
|
-
] });
|
|
595
|
-
}
|
|
596
|
-
const text = status === "failed" ? labels.statusFailedShort : statusText(status, labels);
|
|
597
|
-
return /* @__PURE__ */ jsxs8("span", { className: "cv-p-bubble__status", children: [
|
|
598
|
-
/* @__PURE__ */ jsx8(StatusTicks, { status: ticksStatus, appearance: "stylesheet" }),
|
|
599
|
-
/* @__PURE__ */ jsx8("span", { className: isVisibleText ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
|
|
600
|
-
] });
|
|
601
|
-
}
|
|
602
|
-
function FailedActions({ labels, onDiscard, onEdit }) {
|
|
603
|
-
if (!onDiscard && !onEdit) return null;
|
|
604
|
-
return /* @__PURE__ */ jsxs8("span", { className: "cv-p-bubble__actions", children: [
|
|
605
|
-
onEdit ? /* @__PURE__ */ jsx8("button", { type: "button", className: "cv-p-bubble__action", onClick: onEdit, children: labels.edit }) : null,
|
|
606
|
-
onDiscard ? /* @__PURE__ */ jsx8("button", { type: "button", className: "cv-p-bubble__action", onClick: onDiscard, children: labels.discard }) : null
|
|
607
|
-
] });
|
|
608
|
-
}
|
|
609
|
-
function toTextPayload(text, createdAt, isMine) {
|
|
610
|
-
return {
|
|
611
|
-
id: createdAt,
|
|
612
|
-
type: "text",
|
|
613
|
-
content: text,
|
|
614
|
-
direction: isMine ? "inbound" : "outbound",
|
|
615
|
-
sender: isMine ? "customer" : "agent",
|
|
616
|
-
timestamp: createdAt
|
|
617
|
-
};
|
|
1402
|
+
function bubbleClassName(isMine, hasTail) {
|
|
1403
|
+
const classes = ["cv-p-bubble"];
|
|
1404
|
+
if (isMine) classes.push("cv-p-bubble--mine");
|
|
1405
|
+
if (!hasTail) classes.push("cv-p-bubble--no-tail");
|
|
1406
|
+
return classes.join(" ");
|
|
618
1407
|
}
|
|
619
1408
|
function ParticipantMessageBubble({
|
|
620
1409
|
item,
|
|
@@ -623,15 +1412,18 @@ function ParticipantMessageBubble({
|
|
|
623
1412
|
resolveAttachmentUrl,
|
|
624
1413
|
onRetry,
|
|
625
1414
|
onDiscard,
|
|
626
|
-
onEdit
|
|
1415
|
+
onEdit,
|
|
1416
|
+
avatar,
|
|
1417
|
+
tail = true,
|
|
1418
|
+
perspective
|
|
627
1419
|
}) {
|
|
628
|
-
const content = describeItem(item);
|
|
1420
|
+
const content = describeItem(item, perspective);
|
|
629
1421
|
const { isMine } = content;
|
|
630
1422
|
const ownStatus = isMine ? resolveOwnMessageStatus({ displayState: content.displayState, serverStatus: content.serverStatus, confirmsRead }) : void 0;
|
|
631
|
-
|
|
632
|
-
isMine ? /* @__PURE__ */
|
|
633
|
-
content.text ? /* @__PURE__ */
|
|
634
|
-
content.attachments.map((attachment) => /* @__PURE__ */
|
|
1423
|
+
const bubble = /* @__PURE__ */ jsxs13("div", { className: bubbleClassName(isMine, tail), children: [
|
|
1424
|
+
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,
|
|
1425
|
+
content.text ? /* @__PURE__ */ jsx18(ParticipantMessageText, { text: content.text, labels }) : null,
|
|
1426
|
+
content.attachments.map((attachment) => /* @__PURE__ */ jsx18(
|
|
635
1427
|
ParticipantAttachmentItem,
|
|
636
1428
|
{
|
|
637
1429
|
attachment,
|
|
@@ -640,36 +1432,110 @@ function ParticipantMessageBubble({
|
|
|
640
1432
|
},
|
|
641
1433
|
attachment.id
|
|
642
1434
|
)),
|
|
643
|
-
content.pendingFilenames.map((filename, index) => /* @__PURE__ */
|
|
644
|
-
/* @__PURE__ */
|
|
645
|
-
/* @__PURE__ */
|
|
646
|
-
ownStatus ? /* @__PURE__ */
|
|
1435
|
+
content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx18("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
|
|
1436
|
+
/* @__PURE__ */ jsxs13("span", { className: "cv-p-bubble__meta", children: [
|
|
1437
|
+
/* @__PURE__ */ jsx18("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
|
|
1438
|
+
ownStatus ? /* @__PURE__ */ jsx18(OwnStatus, { status: ownStatus, labels, onRetry }) : null
|
|
647
1439
|
] }),
|
|
648
|
-
ownStatus === "failed" ? /* @__PURE__ */
|
|
1440
|
+
ownStatus === "failed" ? /* @__PURE__ */ jsx18(FailedActions, { labels, onDiscard, onEdit }) : null
|
|
649
1441
|
] });
|
|
1442
|
+
if (avatar === void 0 || isMine) return bubble;
|
|
1443
|
+
return /* @__PURE__ */ jsxs13("div", { className: "cv-p-bubble-row", children: [
|
|
1444
|
+
/* @__PURE__ */ jsx18("span", { className: "cv-p-bubble-row__avatar", children: avatar }),
|
|
1445
|
+
bubble
|
|
1446
|
+
] });
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/participant/participantBubbleActions.ts
|
|
1450
|
+
function bindPendingActions(actions, clientMessageId) {
|
|
1451
|
+
const { retryHost } = actions;
|
|
1452
|
+
return {
|
|
1453
|
+
retryLocal: () => actions.retryLocal(clientMessageId),
|
|
1454
|
+
discardLocal: () => actions.discardLocal(clientMessageId),
|
|
1455
|
+
editLocal: () => actions.editLocal(clientMessageId),
|
|
1456
|
+
...retryHost ? { retryHost: () => retryHost(clientMessageId) } : {}
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function resolveParticipantBubbleActions(item, handlers) {
|
|
1460
|
+
if (item.kind === "server" || item.displayState !== "failed") return {};
|
|
1461
|
+
if (item.origin === "host") return handlers.retryHost ? { onRetry: handlers.retryHost } : {};
|
|
1462
|
+
return { onRetry: handlers.retryLocal, onDiscard: handlers.discardLocal, onEdit: handlers.editLocal };
|
|
650
1463
|
}
|
|
651
1464
|
|
|
652
|
-
// src/participant/
|
|
653
|
-
import {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1465
|
+
// src/participant/ParticipantTimeline.tsx
|
|
1466
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1467
|
+
function createdAtOf2(item) {
|
|
1468
|
+
return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
|
|
1469
|
+
}
|
|
1470
|
+
function keyOf(item) {
|
|
1471
|
+
return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
|
|
1472
|
+
}
|
|
1473
|
+
function authorOf(item, perspective) {
|
|
1474
|
+
if (item.kind === "pending") return { isMine: true, author: null };
|
|
1475
|
+
return { isMine: isOwnMessage(item.message, perspective), author: item.message.authorName ?? null };
|
|
1476
|
+
}
|
|
1477
|
+
function resolveActions(item, pendingActions) {
|
|
1478
|
+
if (!pendingActions || item.kind === "server") return {};
|
|
1479
|
+
return resolveParticipantBubbleActions(item, bindPendingActions(pendingActions, item.pending.clientMessageId));
|
|
1480
|
+
}
|
|
1481
|
+
function resolveAvatarSlot({ isEnabled, isMine, showAvatar, author, render }) {
|
|
1482
|
+
if (!isEnabled || isMine) return void 0;
|
|
1483
|
+
return showAvatar ? /* @__PURE__ */ jsx19(ParticipantAuthorAvatar, { name: author, render }) : null;
|
|
1484
|
+
}
|
|
1485
|
+
function ParticipantTimeline(props) {
|
|
1486
|
+
const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
|
|
1487
|
+
const confirmsRead = channelCapabilityFor(props.channel ?? "app").confirmsRead;
|
|
1488
|
+
const isAvatarEnabled = props.avatars === "initials" || props.renderAuthorAvatar !== void 0;
|
|
1489
|
+
const nodes = [];
|
|
1490
|
+
let previousDay;
|
|
1491
|
+
let previousAuthor;
|
|
1492
|
+
for (const item of items) {
|
|
1493
|
+
const day = new Date(createdAtOf2(item));
|
|
1494
|
+
if (previousDay === void 0 || !isSameDay(previousDay, day)) {
|
|
1495
|
+
nodes.push(/* @__PURE__ */ jsx19(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
|
|
1496
|
+
previousAuthor = void 0;
|
|
1497
|
+
}
|
|
1498
|
+
previousDay = day;
|
|
1499
|
+
const { isMine, author } = authorOf(item, props.perspective);
|
|
1500
|
+
const showAvatar = shouldShowAvatar({ previousAuthor, author, isMine });
|
|
1501
|
+
previousAuthor = isMine ? void 0 : author;
|
|
1502
|
+
const avatar = resolveAvatarSlot({
|
|
1503
|
+
isEnabled: isAvatarEnabled,
|
|
1504
|
+
isMine,
|
|
1505
|
+
showAvatar,
|
|
1506
|
+
author,
|
|
1507
|
+
render: props.renderAuthorAvatar
|
|
1508
|
+
});
|
|
1509
|
+
nodes.push(
|
|
1510
|
+
/* @__PURE__ */ jsx19(
|
|
1511
|
+
ParticipantMessageBubble,
|
|
1512
|
+
{
|
|
1513
|
+
item,
|
|
1514
|
+
labels,
|
|
1515
|
+
confirmsRead,
|
|
1516
|
+
resolveAttachmentUrl,
|
|
1517
|
+
tail: props.tail,
|
|
1518
|
+
perspective: props.perspective,
|
|
1519
|
+
avatar,
|
|
1520
|
+
...resolveActions(item, pendingActions)
|
|
1521
|
+
},
|
|
1522
|
+
keyOf(item)
|
|
1523
|
+
)
|
|
1524
|
+
);
|
|
663
1525
|
}
|
|
1526
|
+
return /* @__PURE__ */ jsx19("div", { className: "cv-p-thread__messages", children: nodes });
|
|
664
1527
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1528
|
+
|
|
1529
|
+
// src/participant/ParticipantConversationAvatar.tsx
|
|
1530
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
1531
|
+
function ParticipantConversationAvatar({ icon }) {
|
|
1532
|
+
return /* @__PURE__ */ jsx20("span", { className: "cv-p-thread__avatar", "aria-hidden": "true", children: icon });
|
|
669
1533
|
}
|
|
670
1534
|
|
|
671
1535
|
// src/participant/ParticipantProtocolBadge.tsx
|
|
672
|
-
import {
|
|
1536
|
+
import { Check as Check2, Copy as Copy2 } from "lucide-react";
|
|
1537
|
+
import { useEffect as useEffect5, useReducer as useReducer3 } from "react";
|
|
1538
|
+
import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
673
1539
|
var COPIED_ANNOUNCEMENT_MS = 3e3;
|
|
674
1540
|
function ParticipantProtocolBadgeView({
|
|
675
1541
|
protocol,
|
|
@@ -678,31 +1544,36 @@ function ParticipantProtocolBadgeView({
|
|
|
678
1544
|
onCopy
|
|
679
1545
|
}) {
|
|
680
1546
|
const { isCopied, copyCount } = copyState;
|
|
681
|
-
return /* @__PURE__ */
|
|
682
|
-
/* @__PURE__ */
|
|
683
|
-
/* @__PURE__ */
|
|
684
|
-
/* @__PURE__ */
|
|
1547
|
+
return /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
1548
|
+
/* @__PURE__ */ jsxs14("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
|
|
1549
|
+
/* @__PURE__ */ jsxs14("span", { className: "cv-p-protocol__code", children: [
|
|
1550
|
+
/* @__PURE__ */ jsxs14("span", { className: "cv-p-sr-only", children: [
|
|
685
1551
|
labels.protocolPrefix,
|
|
686
1552
|
" "
|
|
687
1553
|
] }),
|
|
688
1554
|
protocol
|
|
689
1555
|
] }),
|
|
690
|
-
/* @__PURE__ */
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
protocol
|
|
696
|
-
|
|
697
|
-
|
|
1556
|
+
/* @__PURE__ */ jsxs14(
|
|
1557
|
+
"button",
|
|
1558
|
+
{
|
|
1559
|
+
type: "button",
|
|
1560
|
+
className: "cv-p-protocol__copy",
|
|
1561
|
+
"aria-label": `${labels.copyProtocol} ${protocol}`,
|
|
1562
|
+
onClick: onCopy,
|
|
1563
|
+
children: [
|
|
1564
|
+
isCopied ? /* @__PURE__ */ jsx21(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx21(Copy2, { size: 18, "aria-hidden": true }),
|
|
1565
|
+
isCopied ? /* @__PURE__ */ jsx21("span", { className: "cv-p-protocol__copied", "aria-hidden": "true", children: labels.protocolCopied }) : null
|
|
1566
|
+
]
|
|
1567
|
+
}
|
|
1568
|
+
)
|
|
698
1569
|
] }),
|
|
699
|
-
/* @__PURE__ */
|
|
1570
|
+
/* @__PURE__ */ jsx21("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx21("span", { children: labels.protocolCopied }, copyCount) : null })
|
|
700
1571
|
] });
|
|
701
1572
|
}
|
|
702
1573
|
function ParticipantProtocolBadge({ protocol, labels }) {
|
|
703
|
-
const [copyState, dispatch] =
|
|
1574
|
+
const [copyState, dispatch] = useReducer3(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
|
|
704
1575
|
const { isCopied, copyCount } = copyState;
|
|
705
|
-
|
|
1576
|
+
useEffect5(() => {
|
|
706
1577
|
if (!isCopied) return void 0;
|
|
707
1578
|
const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_ANNOUNCEMENT_MS);
|
|
708
1579
|
return () => clearTimeout(timer);
|
|
@@ -711,7 +1582,7 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
711
1582
|
const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
|
|
712
1583
|
if (await copyProtocolToClipboard(protocol, clipboard)) dispatch({ type: "copied" });
|
|
713
1584
|
}
|
|
714
|
-
return /* @__PURE__ */
|
|
1585
|
+
return /* @__PURE__ */ jsx21(
|
|
715
1586
|
ParticipantProtocolBadgeView,
|
|
716
1587
|
{
|
|
717
1588
|
protocol,
|
|
@@ -722,77 +1593,58 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
722
1593
|
);
|
|
723
1594
|
}
|
|
724
1595
|
|
|
725
|
-
// src/participant/
|
|
726
|
-
import { jsx as
|
|
727
|
-
function
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1596
|
+
// src/participant/ParticipantThreadHeader.tsx
|
|
1597
|
+
import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1598
|
+
function Title({ conversation, labels, onOpenSubject }) {
|
|
1599
|
+
const { subjectType, subjectId, subjectLabel } = conversation;
|
|
1600
|
+
return /* @__PURE__ */ jsx22("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx22(
|
|
1601
|
+
"button",
|
|
1602
|
+
{
|
|
1603
|
+
type: "button",
|
|
1604
|
+
className: "cv-p-thread__title-button",
|
|
1605
|
+
"aria-label": `${labels.openSubject}: ${subjectLabel}`,
|
|
1606
|
+
onClick: () => onOpenSubject({ subjectType, subjectId }),
|
|
1607
|
+
children: /* @__PURE__ */ jsx22("span", { className: "cv-p-thread__title-text", children: subjectLabel })
|
|
1608
|
+
}
|
|
1609
|
+
) : /* @__PURE__ */ jsx22("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
|
|
736
1610
|
}
|
|
737
|
-
function
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
/* @__PURE__ */
|
|
758
|
-
|
|
1611
|
+
function ParticipantThreadHeader({
|
|
1612
|
+
conversation,
|
|
1613
|
+
labels,
|
|
1614
|
+
subjectGroups,
|
|
1615
|
+
renderSubjectIcon,
|
|
1616
|
+
onBack,
|
|
1617
|
+
onOpenSubject,
|
|
1618
|
+
counterpartLabel,
|
|
1619
|
+
actions
|
|
1620
|
+
}) {
|
|
1621
|
+
const group = subjectGroups?.find((candidate) => candidate.subjectType === conversation.subjectType);
|
|
1622
|
+
const icon = resolveConversationIcon({ conversation, group, renderSubjectIcon });
|
|
1623
|
+
const hasChannels = resolveParticipantChannels(conversation.channels).length > 0;
|
|
1624
|
+
return /* @__PURE__ */ jsxs15("header", { className: "cv-p-thread__header", children: [
|
|
1625
|
+
onBack ? /* @__PURE__ */ jsx22(ParticipantBackButton, { label: labels.back, onBack }) : null,
|
|
1626
|
+
icon !== void 0 ? /* @__PURE__ */ jsx22(ParticipantConversationAvatar, { icon }) : null,
|
|
1627
|
+
/* @__PURE__ */ jsxs15("div", { className: "cv-p-thread__heading", children: [
|
|
1628
|
+
icon === void 0 && group ? /* @__PURE__ */ jsx22("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
|
|
1629
|
+
/* @__PURE__ */ jsx22(Title, { conversation, labels, onOpenSubject }),
|
|
1630
|
+
counterpartLabel ? /* @__PURE__ */ jsx22("p", { className: "cv-p-thread__counterpart", children: counterpartLabel }) : null,
|
|
1631
|
+
conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs15("div", { className: "cv-p-thread__meta", children: [
|
|
1632
|
+
conversation.protocol ? /* @__PURE__ */ jsx22(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
|
|
1633
|
+
/* @__PURE__ */ jsx22(ParticipantChannelBadges, { channels: conversation.channels, labels, variant: "inline", iconSize: 16 })
|
|
1634
|
+
] }) : null
|
|
1635
|
+
] }),
|
|
1636
|
+
actions ? /* @__PURE__ */ jsx22("div", { className: "cv-p-thread__actions", children: actions }) : null
|
|
759
1637
|
] });
|
|
760
1638
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const nodes = [];
|
|
765
|
-
let previousDay;
|
|
766
|
-
for (const item of items) {
|
|
767
|
-
const day = new Date(createdAtOf2(item));
|
|
768
|
-
if (previousDay === void 0 || !isSameDay(previousDay, day)) {
|
|
769
|
-
nodes.push(
|
|
770
|
-
/* @__PURE__ */ jsx10("div", { className: "cv-p-day", role: "separator", children: day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" }) }, `day:${keyOf(item)}`)
|
|
771
|
-
);
|
|
772
|
-
}
|
|
773
|
-
previousDay = day;
|
|
774
|
-
nodes.push(
|
|
775
|
-
/* @__PURE__ */ jsx10(
|
|
776
|
-
ParticipantMessageBubble,
|
|
777
|
-
{
|
|
778
|
-
item,
|
|
779
|
-
labels,
|
|
780
|
-
confirmsRead,
|
|
781
|
-
resolveAttachmentUrl,
|
|
782
|
-
...resolveActions(item, pendingActions)
|
|
783
|
-
},
|
|
784
|
-
keyOf(item)
|
|
785
|
-
)
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
return /* @__PURE__ */ jsx10("div", { className: "cv-p-thread__messages", children: nodes });
|
|
789
|
-
}
|
|
1639
|
+
|
|
1640
|
+
// src/participant/ParticipantThread.tsx
|
|
1641
|
+
import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
790
1642
|
function Footer({ props }) {
|
|
791
1643
|
const { conversation, labels, draft } = props;
|
|
792
1644
|
if (conversation.status === "closed") {
|
|
793
|
-
return /* @__PURE__ */
|
|
1645
|
+
return /* @__PURE__ */ jsx23("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
|
|
794
1646
|
}
|
|
795
|
-
return /* @__PURE__ */
|
|
1647
|
+
return /* @__PURE__ */ jsx23(
|
|
796
1648
|
ParticipantComposer,
|
|
797
1649
|
{
|
|
798
1650
|
value: draft.value,
|
|
@@ -809,23 +1661,51 @@ function Footer({ props }) {
|
|
|
809
1661
|
}
|
|
810
1662
|
);
|
|
811
1663
|
}
|
|
812
|
-
function
|
|
1664
|
+
function ThreadBody(props) {
|
|
813
1665
|
const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
|
|
814
1666
|
const subjectCard = renderSubjectCard?.(conversation);
|
|
815
1667
|
const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
|
|
816
|
-
return /* @__PURE__ */
|
|
817
|
-
/* @__PURE__ */
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1668
|
+
return /* @__PURE__ */ jsxs16("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, children: [
|
|
1669
|
+
/* @__PURE__ */ jsx23(
|
|
1670
|
+
ParticipantThreadHeader,
|
|
1671
|
+
{
|
|
1672
|
+
conversation,
|
|
1673
|
+
labels,
|
|
1674
|
+
subjectGroups: props.subjectGroups,
|
|
1675
|
+
renderSubjectIcon: props.renderSubjectIcon,
|
|
1676
|
+
onBack: props.onBack,
|
|
1677
|
+
onOpenSubject: props.onOpenSubject,
|
|
1678
|
+
counterpartLabel: props.counterpartLabel,
|
|
1679
|
+
actions: props.headerActions
|
|
1680
|
+
}
|
|
1681
|
+
),
|
|
1682
|
+
subjectCard ? /* @__PURE__ */ jsx23("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
|
|
1683
|
+
/* @__PURE__ */ jsxs16("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
|
|
1684
|
+
loadView.isLoading ? /* @__PURE__ */ jsx23(ParticipantLoading, { labels }) : null,
|
|
1685
|
+
loadView.hasError ? /* @__PURE__ */ jsx23(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
|
|
1686
|
+
hasMore && onLoadOlder ? /* @__PURE__ */ jsx23("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
|
|
1687
|
+
/* @__PURE__ */ jsx23(ParticipantTimeline, { ...props }),
|
|
1688
|
+
/* @__PURE__ */ jsx23(
|
|
1689
|
+
ParticipantScrollToLatest,
|
|
1690
|
+
{
|
|
1691
|
+
label: labels.scrollToLatest,
|
|
1692
|
+
isAwayFromBottom: props.scroll.isAwayFromBottom === true,
|
|
1693
|
+
hasMessages: props.items.length > 0,
|
|
1694
|
+
newMessagesCount: props.newMessagesCount,
|
|
1695
|
+
onClick: props.scroll.scrollToLatest
|
|
1696
|
+
}
|
|
1697
|
+
)
|
|
824
1698
|
] }),
|
|
825
|
-
/* @__PURE__ */
|
|
826
|
-
/* @__PURE__ */
|
|
1699
|
+
/* @__PURE__ */ jsx23(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
|
|
1700
|
+
/* @__PURE__ */ jsx23(Footer, { props })
|
|
827
1701
|
] });
|
|
828
1702
|
}
|
|
1703
|
+
function ParticipantThread(props) {
|
|
1704
|
+
return /* @__PURE__ */ jsx23(ParticipantCopyAnnouncer, { children: /* @__PURE__ */ jsx23(ThreadBody, { ...props }) });
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// src/participant/useParticipantThreadController.ts
|
|
1708
|
+
import { useMemo as useMemo4 } from "react";
|
|
829
1709
|
|
|
830
1710
|
// src/participant/bindAttachmentUrlResolver.ts
|
|
831
1711
|
function bindAttachmentUrlResolver(api) {
|
|
@@ -908,8 +1788,8 @@ function appendEntryToDraft(entry, current) {
|
|
|
908
1788
|
return { text, files: [...entry.files, ...current.files] };
|
|
909
1789
|
}
|
|
910
1790
|
|
|
911
|
-
// src/participant/
|
|
912
|
-
import {
|
|
1791
|
+
// src/participant/useParticipantConversationView.ts
|
|
1792
|
+
import { useCallback as useCallback5, useMemo as useMemo3 } from "react";
|
|
913
1793
|
|
|
914
1794
|
// src/participant/participantDrafts.ts
|
|
915
1795
|
var EMPTY_PARTICIPANT_DRAFT = { text: "", files: [] };
|
|
@@ -937,30 +1817,6 @@ function takeDraftForSend(drafts, subject) {
|
|
|
937
1817
|
};
|
|
938
1818
|
}
|
|
939
1819
|
|
|
940
|
-
// src/participant/useParticipantDraft.ts
|
|
941
|
-
function useParticipantDraft(draftsRef, subject) {
|
|
942
|
-
const [draft, setDraftState] = useState3(() => getDraft(draftsRef.current, subject));
|
|
943
|
-
function replaceDrafts(next) {
|
|
944
|
-
draftsRef.current = next;
|
|
945
|
-
setDraftState(getDraft(next, subject));
|
|
946
|
-
}
|
|
947
|
-
function update(change) {
|
|
948
|
-
replaceDrafts(setDraft(draftsRef.current, subject, { ...getDraft(draftsRef.current, subject), ...change }));
|
|
949
|
-
}
|
|
950
|
-
function takeForSend() {
|
|
951
|
-
const taken = takeDraftForSend(draftsRef.current, subject);
|
|
952
|
-
if (taken.content) replaceDrafts(taken.drafts);
|
|
953
|
-
return taken.content;
|
|
954
|
-
}
|
|
955
|
-
function restoreFromEntry(entry) {
|
|
956
|
-
update(appendEntryToDraft(entry, getDraft(draftsRef.current, subject)));
|
|
957
|
-
}
|
|
958
|
-
return { draft, setText: (text) => update({ text }), setFiles: (files) => update({ files }), takeForSend, restoreFromEntry };
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// src/participant/useParticipantConversationView.ts
|
|
962
|
-
import { useCallback as useCallback3, useMemo as useMemo2 } from "react";
|
|
963
|
-
|
|
964
1820
|
// src/participant/participantSendStates.ts
|
|
965
1821
|
var NO_ENTRIES = [];
|
|
966
1822
|
function selectSendEntries(states, subject) {
|
|
@@ -977,7 +1833,7 @@ function participantSendStatesReducer(states, { subject, action }) {
|
|
|
977
1833
|
}
|
|
978
1834
|
|
|
979
1835
|
// src/participant/useParticipantThread.ts
|
|
980
|
-
import { useCallback as
|
|
1836
|
+
import { useCallback as useCallback4, useEffect as useEffect9, useMemo as useMemo2, useReducer as useReducer4, useRef as useRef7 } from "react";
|
|
981
1837
|
|
|
982
1838
|
// src/participant/participantThreadState.ts
|
|
983
1839
|
var INITIAL_PARTICIPANT_THREAD_STATE = {
|
|
@@ -1013,7 +1869,7 @@ function participantThreadReducer(state, action) {
|
|
|
1013
1869
|
}
|
|
1014
1870
|
|
|
1015
1871
|
// src/participant/useParticipantMarkRead.ts
|
|
1016
|
-
import { useEffect as
|
|
1872
|
+
import { useEffect as useEffect7 } from "react";
|
|
1017
1873
|
|
|
1018
1874
|
// src/participant/shouldMarkRead.ts
|
|
1019
1875
|
function shouldMarkParticipantRead(params) {
|
|
@@ -1025,12 +1881,12 @@ function shouldMarkParticipantRead(params) {
|
|
|
1025
1881
|
}
|
|
1026
1882
|
|
|
1027
1883
|
// src/participant/useDocumentVisibility.ts
|
|
1028
|
-
import { useEffect as
|
|
1884
|
+
import { useEffect as useEffect6, useState as useState3 } from "react";
|
|
1029
1885
|
function useDocumentVisibility() {
|
|
1030
|
-
const [visibilityState, setVisibilityState] =
|
|
1886
|
+
const [visibilityState, setVisibilityState] = useState3(
|
|
1031
1887
|
() => typeof document === "undefined" ? "visible" : document.visibilityState
|
|
1032
1888
|
);
|
|
1033
|
-
|
|
1889
|
+
useEffect6(() => {
|
|
1034
1890
|
function handleVisibilityChange() {
|
|
1035
1891
|
setVisibilityState(document.visibilityState);
|
|
1036
1892
|
}
|
|
@@ -1045,18 +1901,18 @@ function useDocumentVisibility() {
|
|
|
1045
1901
|
function useParticipantMarkRead(params) {
|
|
1046
1902
|
const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
|
|
1047
1903
|
const visibilityState = useDocumentVisibility();
|
|
1048
|
-
|
|
1904
|
+
useEffect7(() => {
|
|
1049
1905
|
if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
|
|
1050
1906
|
void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
|
|
1051
1907
|
}, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
|
|
1052
1908
|
}
|
|
1053
1909
|
|
|
1054
1910
|
// src/participant/useParticipantRevalidation.ts
|
|
1055
|
-
import { useEffect as
|
|
1911
|
+
import { useEffect as useEffect8, useRef as useRef4 } from "react";
|
|
1056
1912
|
function useParticipantRevalidation(api, onRevalidate) {
|
|
1057
|
-
const handlerRef =
|
|
1913
|
+
const handlerRef = useRef4(onRevalidate);
|
|
1058
1914
|
handlerRef.current = onRevalidate;
|
|
1059
|
-
|
|
1915
|
+
useEffect8(() => {
|
|
1060
1916
|
function handleFocus() {
|
|
1061
1917
|
handlerRef.current();
|
|
1062
1918
|
}
|
|
@@ -1083,12 +1939,12 @@ function useParticipantRevalidation(api, onRevalidate) {
|
|
|
1083
1939
|
}
|
|
1084
1940
|
|
|
1085
1941
|
// src/participant/useParticipantSend.ts
|
|
1086
|
-
import { useCallback, useRef as
|
|
1942
|
+
import { useCallback as useCallback2, useRef as useRef5 } from "react";
|
|
1087
1943
|
function useParticipantSend(params) {
|
|
1088
1944
|
const { apiRef, subject, dispatchSend, onSentMessage } = params;
|
|
1089
|
-
const entriesRef =
|
|
1945
|
+
const entriesRef = useRef5(params.entries);
|
|
1090
1946
|
entriesRef.current = params.entries;
|
|
1091
|
-
const deliver =
|
|
1947
|
+
const deliver = useCallback2(
|
|
1092
1948
|
async (entry) => {
|
|
1093
1949
|
const { clientMessageId } = entry;
|
|
1094
1950
|
try {
|
|
@@ -1112,7 +1968,7 @@ function useParticipantSend(params) {
|
|
|
1112
1968
|
},
|
|
1113
1969
|
[apiRef, dispatchSend, onSentMessage]
|
|
1114
1970
|
);
|
|
1115
|
-
const send =
|
|
1971
|
+
const send = useCallback2(
|
|
1116
1972
|
async (content) => {
|
|
1117
1973
|
const entry = createParticipantSendEntry({
|
|
1118
1974
|
clientMessageId: crypto.randomUUID(),
|
|
@@ -1125,7 +1981,7 @@ function useParticipantSend(params) {
|
|
|
1125
1981
|
},
|
|
1126
1982
|
[deliver, dispatchSend, subject]
|
|
1127
1983
|
);
|
|
1128
|
-
const retry =
|
|
1984
|
+
const retry = useCallback2(
|
|
1129
1985
|
async (clientMessageId) => {
|
|
1130
1986
|
const entry = findFailedEntry(entriesRef.current, clientMessageId);
|
|
1131
1987
|
if (!entry) return;
|
|
@@ -1137,6 +1993,24 @@ function useParticipantSend(params) {
|
|
|
1137
1993
|
return { send, retry };
|
|
1138
1994
|
}
|
|
1139
1995
|
|
|
1996
|
+
// src/participant/useParticipantUnread.ts
|
|
1997
|
+
import { useCallback as useCallback3, useRef as useRef6, useState as useState4 } from "react";
|
|
1998
|
+
function useParticipantUnread(params) {
|
|
1999
|
+
const { messages, perspective, reportedUnreadCount, onMarkedRead } = params;
|
|
2000
|
+
const [acknowledgedCount, setAcknowledgedCount] = useState4(0);
|
|
2001
|
+
const messagesRef = useRef6(messages);
|
|
2002
|
+
messagesRef.current = messages;
|
|
2003
|
+
const handleMarkedRead = useCallback3(
|
|
2004
|
+
(subject) => {
|
|
2005
|
+
if (perspective) setAcknowledgedCount(countOtherSideMessages(messagesRef.current, perspective));
|
|
2006
|
+
onMarkedRead?.(subject);
|
|
2007
|
+
},
|
|
2008
|
+
[perspective, onMarkedRead]
|
|
2009
|
+
);
|
|
2010
|
+
const unreadCount = perspective ? resolveViewerUnread({ messages, perspective, acknowledgedCount }) : reportedUnreadCount;
|
|
2011
|
+
return { unreadCount, handleMarkedRead };
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1140
2014
|
// src/participant/useParticipantThread.ts
|
|
1141
2015
|
var THREAD_PAGE_SIZE = 30;
|
|
1142
2016
|
function describeError(error) {
|
|
@@ -1147,15 +2021,15 @@ function isForSubject(event, subject) {
|
|
|
1147
2021
|
return event.subject.subjectType === subject.subjectType && event.subject.subjectId === subject.subjectId;
|
|
1148
2022
|
}
|
|
1149
2023
|
function useParticipantThread(params) {
|
|
1150
|
-
const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [] } = params;
|
|
2024
|
+
const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [], perspective } = params;
|
|
1151
2025
|
const { subjectType, subjectId } = subject;
|
|
1152
|
-
const stableSubject =
|
|
1153
|
-
const [state, dispatch] =
|
|
1154
|
-
const apiRef =
|
|
2026
|
+
const stableSubject = useMemo2(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
2027
|
+
const [state, dispatch] = useReducer4(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
|
|
2028
|
+
const apiRef = useRef7(api);
|
|
1155
2029
|
apiRef.current = api;
|
|
1156
|
-
const activeSubjectKey =
|
|
2030
|
+
const activeSubjectKey = useRef7("");
|
|
1157
2031
|
const confirmsRead = channelCapabilityFor(channel).confirmsRead;
|
|
1158
|
-
const refresh =
|
|
2032
|
+
const refresh = useCallback4(async () => {
|
|
1159
2033
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
1160
2034
|
dispatch({ type: "started" });
|
|
1161
2035
|
try {
|
|
@@ -1168,7 +2042,7 @@ function useParticipantThread(params) {
|
|
|
1168
2042
|
}
|
|
1169
2043
|
}, [stableSubject, subjectType, subjectId]);
|
|
1170
2044
|
const oldestMessageId = state.messages[0]?.id;
|
|
1171
|
-
const loadOlder =
|
|
2045
|
+
const loadOlder = useCallback4(async () => {
|
|
1172
2046
|
if (!oldestMessageId) return;
|
|
1173
2047
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
1174
2048
|
try {
|
|
@@ -1180,9 +2054,9 @@ function useParticipantThread(params) {
|
|
|
1180
2054
|
if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
|
|
1181
2055
|
}
|
|
1182
2056
|
}, [stableSubject, subjectType, subjectId, oldestMessageId]);
|
|
1183
|
-
const handleSentMessage =
|
|
2057
|
+
const handleSentMessage = useCallback4((message) => dispatch({ type: "sendConfirmed", message }), []);
|
|
1184
2058
|
const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
|
|
1185
|
-
|
|
2059
|
+
useEffect9(() => {
|
|
1186
2060
|
activeSubjectKey.current = `${subjectType}:${subjectId}`;
|
|
1187
2061
|
dispatch({ type: "reset" });
|
|
1188
2062
|
void refresh();
|
|
@@ -1190,12 +2064,19 @@ function useParticipantThread(params) {
|
|
|
1190
2064
|
useParticipantRevalidation(api, (event) => {
|
|
1191
2065
|
if (isForSubject(event, stableSubject)) void refresh();
|
|
1192
2066
|
});
|
|
1193
|
-
|
|
2067
|
+
useEffect9(() => {
|
|
1194
2068
|
if (sendEntries.length === 0) return;
|
|
1195
2069
|
dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
|
|
1196
2070
|
}, [sendEntries, state.messages, hostPending, dispatchSend]);
|
|
1197
|
-
|
|
1198
|
-
|
|
2071
|
+
const unread = useParticipantUnread({ messages: state.messages, perspective, reportedUnreadCount: unreadCount, onMarkedRead });
|
|
2072
|
+
useParticipantMarkRead({
|
|
2073
|
+
apiRef,
|
|
2074
|
+
subject: stableSubject,
|
|
2075
|
+
unreadCount: unread.unreadCount,
|
|
2076
|
+
messageCount: state.messages.length,
|
|
2077
|
+
onMarkedRead: unread.handleMarkedRead
|
|
2078
|
+
});
|
|
2079
|
+
const localPending = useMemo2(() => toLocalPending(sendEntries), [sendEntries]);
|
|
1199
2080
|
return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
|
|
1200
2081
|
}
|
|
1201
2082
|
|
|
@@ -1204,7 +2085,7 @@ function useParticipantConversationView(params) {
|
|
|
1204
2085
|
const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
|
|
1205
2086
|
const { subjectType, subjectId } = subject;
|
|
1206
2087
|
const sendEntries = selectSendEntries(sendStates, subject);
|
|
1207
|
-
const dispatchSend =
|
|
2088
|
+
const dispatchSend = useCallback5(
|
|
1208
2089
|
(action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
|
|
1209
2090
|
[dispatchSendStates, subjectType, subjectId]
|
|
1210
2091
|
);
|
|
@@ -1216,9 +2097,10 @@ function useParticipantConversationView(params) {
|
|
|
1216
2097
|
onMarkedRead: params.onMarkedRead,
|
|
1217
2098
|
sendEntries,
|
|
1218
2099
|
dispatchSend,
|
|
1219
|
-
hostPending: pendingMessages
|
|
2100
|
+
hostPending: pendingMessages,
|
|
2101
|
+
perspective: params.perspective
|
|
1220
2102
|
});
|
|
1221
|
-
const items =
|
|
2103
|
+
const items = useMemo3(
|
|
1222
2104
|
() => mergeParticipantMessages({
|
|
1223
2105
|
serverMessages: thread.messages,
|
|
1224
2106
|
hostPending: pendingMessages ?? [],
|
|
@@ -1230,8 +2112,30 @@ function useParticipantConversationView(params) {
|
|
|
1230
2112
|
return { thread, items, sendEntries, dispatchSend };
|
|
1231
2113
|
}
|
|
1232
2114
|
|
|
2115
|
+
// src/participant/useParticipantDraft.ts
|
|
2116
|
+
import { useState as useState5 } from "react";
|
|
2117
|
+
function useParticipantDraft(draftsRef, subject) {
|
|
2118
|
+
const [draft, setDraftState] = useState5(() => getDraft(draftsRef.current, subject));
|
|
2119
|
+
function replaceDrafts(next) {
|
|
2120
|
+
draftsRef.current = next;
|
|
2121
|
+
setDraftState(getDraft(next, subject));
|
|
2122
|
+
}
|
|
2123
|
+
function update(change) {
|
|
2124
|
+
replaceDrafts(setDraft(draftsRef.current, subject, { ...getDraft(draftsRef.current, subject), ...change }));
|
|
2125
|
+
}
|
|
2126
|
+
function takeForSend() {
|
|
2127
|
+
const taken = takeDraftForSend(draftsRef.current, subject);
|
|
2128
|
+
if (taken.content) replaceDrafts(taken.drafts);
|
|
2129
|
+
return taken.content;
|
|
2130
|
+
}
|
|
2131
|
+
function restoreFromEntry(entry) {
|
|
2132
|
+
update(appendEntryToDraft(entry, getDraft(draftsRef.current, subject)));
|
|
2133
|
+
}
|
|
2134
|
+
return { draft, setText: (text) => update({ text }), setFiles: (files) => update({ files }), takeForSend, restoreFromEntry };
|
|
2135
|
+
}
|
|
2136
|
+
|
|
1233
2137
|
// src/participant/useParticipantThreadScroll.ts
|
|
1234
|
-
import { useEffect as
|
|
2138
|
+
import { useEffect as useEffect10, useRef as useRef9 } from "react";
|
|
1235
2139
|
|
|
1236
2140
|
// src/participant/participantNewMessages.ts
|
|
1237
2141
|
function collectServerMessageIds(items) {
|
|
@@ -1248,15 +2152,28 @@ function latestSeenTime(previousIds, items) {
|
|
|
1248
2152
|
}
|
|
1249
2153
|
return latest;
|
|
1250
2154
|
}
|
|
1251
|
-
function countNewIncomingMessages(previousIds, currentItems) {
|
|
2155
|
+
function countNewIncomingMessages(previousIds, currentItems, perspective) {
|
|
1252
2156
|
const latestSeen = latestSeenTime(previousIds, currentItems);
|
|
1253
2157
|
return currentItems.filter(
|
|
1254
|
-
(item) => item.kind === "server" && item.message
|
|
2158
|
+
(item) => item.kind === "server" && !isMessageMine(item.message, perspective) && !previousIds.has(item.message.id) && Date.parse(item.message.createdAt) > latestSeen
|
|
1255
2159
|
).length;
|
|
1256
2160
|
}
|
|
1257
2161
|
|
|
2162
|
+
// src/participant/useScrollAwayState.ts
|
|
2163
|
+
import { useCallback as useCallback6 } from "react";
|
|
2164
|
+
function useScrollAwayState(ref, isNearBottom) {
|
|
2165
|
+
const scrollToLatest = useCallback6(() => {
|
|
2166
|
+
const element = ref.current;
|
|
2167
|
+
if (!element) return;
|
|
2168
|
+
element.scrollTo(
|
|
2169
|
+
scrollToLatestOptions({ scrollHeight: element.scrollHeight, isReducedMotion: readPrefersReducedMotion() })
|
|
2170
|
+
);
|
|
2171
|
+
}, [ref]);
|
|
2172
|
+
return { isAwayFromBottom: !isNearBottom, scrollToLatest };
|
|
2173
|
+
}
|
|
2174
|
+
|
|
1258
2175
|
// src/participant/useStickToBottom.ts
|
|
1259
|
-
import { useCallback as
|
|
2176
|
+
import { useCallback as useCallback7, useLayoutEffect as useLayoutEffect2, useRef as useRef8, useState as useState6 } from "react";
|
|
1260
2177
|
|
|
1261
2178
|
// src/participant/participantScroll.ts
|
|
1262
2179
|
var NEAR_BOTTOM_THRESHOLD_PX = 80;
|
|
@@ -1278,11 +2195,17 @@ function resolveScrollAction(params) {
|
|
|
1278
2195
|
|
|
1279
2196
|
// src/participant/useStickToBottom.ts
|
|
1280
2197
|
function useStickToBottom({ firstKey, lastKey }) {
|
|
1281
|
-
const ref =
|
|
1282
|
-
const snapshot =
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
2198
|
+
const ref = useRef8(null);
|
|
2199
|
+
const snapshot = useRef8({
|
|
2200
|
+
hasPositioned: false,
|
|
2201
|
+
firstKey: void 0,
|
|
2202
|
+
lastKey: void 0,
|
|
2203
|
+
scrollTop: 0,
|
|
2204
|
+
scrollHeight: 0
|
|
2205
|
+
});
|
|
2206
|
+
const [isNearBottom, setIsNearBottom] = useState6(true);
|
|
2207
|
+
const isNearBottomRef = useRef8(true);
|
|
2208
|
+
const onScroll = useCallback7((event) => {
|
|
1286
2209
|
const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
|
|
1287
2210
|
const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
|
|
1288
2211
|
snapshot.current.scrollTop = scrollTop;
|
|
@@ -1290,7 +2213,7 @@ function useStickToBottom({ firstKey, lastKey }) {
|
|
|
1290
2213
|
isNearBottomRef.current = isNear;
|
|
1291
2214
|
setIsNearBottom(isNear);
|
|
1292
2215
|
}, []);
|
|
1293
|
-
|
|
2216
|
+
useLayoutEffect2(() => {
|
|
1294
2217
|
const element = ref.current;
|
|
1295
2218
|
const previous = snapshot.current;
|
|
1296
2219
|
if (!element) return;
|
|
@@ -1330,22 +2253,73 @@ function keyOf2(item) {
|
|
|
1330
2253
|
if (!item) return void 0;
|
|
1331
2254
|
return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
|
|
1332
2255
|
}
|
|
1333
|
-
function useParticipantThreadScroll(items) {
|
|
1334
|
-
const { scroll, isNearBottom } = useStickToBottom({
|
|
1335
|
-
|
|
1336
|
-
|
|
2256
|
+
function useParticipantThreadScroll(items, perspective) {
|
|
2257
|
+
const { scroll: base, isNearBottom } = useStickToBottom({
|
|
2258
|
+
firstKey: keyOf2(items[0]),
|
|
2259
|
+
lastKey: keyOf2(items[items.length - 1])
|
|
2260
|
+
});
|
|
2261
|
+
const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
|
|
2262
|
+
const scroll = { ...base, isAwayFromBottom, scrollToLatest };
|
|
2263
|
+
const seenIds = useRef9(/* @__PURE__ */ new Set());
|
|
2264
|
+
useEffect10(() => {
|
|
1337
2265
|
if (isNearBottom) seenIds.current = collectServerMessageIds(items);
|
|
1338
2266
|
}, [isNearBottom, items]);
|
|
1339
|
-
return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items) };
|
|
2267
|
+
return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items, perspective) };
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
// src/participant/useParticipantThreadController.ts
|
|
2271
|
+
function buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending }) {
|
|
2272
|
+
return {
|
|
2273
|
+
retryLocal: (clientMessageId) => void thread.retry(clientMessageId),
|
|
2274
|
+
discardLocal: (clientMessageId) => dispatchSend({ type: "discarded", clientMessageId }),
|
|
2275
|
+
editLocal: handleEdit,
|
|
2276
|
+
...onRetryPending ? { retryHost: onRetryPending } : {}
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
function useParticipantThreadController(params) {
|
|
2280
|
+
const { api, subject, conversation, draftsRef } = params;
|
|
2281
|
+
const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, subject);
|
|
2282
|
+
const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView(params);
|
|
2283
|
+
const resolveAttachmentUrl = useMemo4(() => bindAttachmentUrlResolver(api), [api]);
|
|
2284
|
+
const { scroll, newMessagesCount } = useParticipantThreadScroll(items, params.perspective);
|
|
2285
|
+
function handleSend() {
|
|
2286
|
+
const content = takeForSend();
|
|
2287
|
+
if (content) void thread.send(content);
|
|
2288
|
+
}
|
|
2289
|
+
function handleEdit(clientMessageId) {
|
|
2290
|
+
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
2291
|
+
if (!entry) return;
|
|
2292
|
+
restoreFromEntry(entry);
|
|
2293
|
+
dispatchSend({ type: "discarded", clientMessageId });
|
|
2294
|
+
}
|
|
2295
|
+
return {
|
|
2296
|
+
conversation,
|
|
2297
|
+
items,
|
|
2298
|
+
hasMore: thread.hasMore,
|
|
2299
|
+
labels: params.labels,
|
|
2300
|
+
resolveAttachmentUrl,
|
|
2301
|
+
draft: { value: draft.text, onChange: setText, files: draft.files, onFilesChange: setFiles },
|
|
2302
|
+
onSend: handleSend,
|
|
2303
|
+
status: thread.status,
|
|
2304
|
+
refresh: () => void thread.refresh(),
|
|
2305
|
+
scroll,
|
|
2306
|
+
newMessagesCount,
|
|
2307
|
+
isSending: hasSendingEntry(sendEntries),
|
|
2308
|
+
channel: params.channel,
|
|
2309
|
+
locale: params.locale,
|
|
2310
|
+
onLoadOlder: () => void thread.loadOlder(),
|
|
2311
|
+
pendingActions: buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending: params.onRetryPending }),
|
|
2312
|
+
perspective: params.perspective
|
|
2313
|
+
};
|
|
1340
2314
|
}
|
|
1341
2315
|
|
|
1342
2316
|
// src/participant/ParticipantThreadScreen.tsx
|
|
1343
|
-
import { jsx as
|
|
2317
|
+
import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1344
2318
|
function useOpenedConversation(api, subject, isNeeded) {
|
|
1345
|
-
const [opened, setOpened] =
|
|
2319
|
+
const [opened, setOpened] = useState7({ status: "idle" });
|
|
1346
2320
|
const { subjectType, subjectId } = subject;
|
|
1347
2321
|
const canOpen = api.openConversation !== void 0;
|
|
1348
|
-
|
|
2322
|
+
useEffect11(() => {
|
|
1349
2323
|
if (!isNeeded || !canOpen) return;
|
|
1350
2324
|
let isCurrent = true;
|
|
1351
2325
|
setOpened({ status: "loading" });
|
|
@@ -1359,13 +2333,13 @@ function useOpenedConversation(api, subject, isNeeded) {
|
|
|
1359
2333
|
}, [api, canOpen, isNeeded, subjectType, subjectId]);
|
|
1360
2334
|
return opened;
|
|
1361
2335
|
}
|
|
1362
|
-
function NotFound({
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
/* @__PURE__ */
|
|
2336
|
+
function NotFound({
|
|
2337
|
+
labels,
|
|
2338
|
+
onBack
|
|
2339
|
+
}) {
|
|
2340
|
+
return /* @__PURE__ */ jsxs17("div", { className: "cv-p cv-p-thread", children: [
|
|
2341
|
+
/* @__PURE__ */ jsx24("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx24(ParticipantBackButton, { label: labels.back, onBack }) : null }),
|
|
2342
|
+
/* @__PURE__ */ jsx24("p", { className: "cv-p-empty", children: labels.notFound })
|
|
1369
2343
|
] });
|
|
1370
2344
|
}
|
|
1371
2345
|
function ParticipantThreadScreen(props) {
|
|
@@ -1375,60 +2349,32 @@ function ParticipantThreadScreen(props) {
|
|
|
1375
2349
|
);
|
|
1376
2350
|
const opened = useOpenedConversation(api, selected, listed === void 0);
|
|
1377
2351
|
const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
|
|
1378
|
-
if (conversation) return /* @__PURE__ */
|
|
2352
|
+
if (conversation) return /* @__PURE__ */ jsx24(LoadedThread, { ...props, conversation });
|
|
1379
2353
|
const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
|
|
1380
2354
|
const isListLoading = inbox.status === "idle" || inbox.status === "loading";
|
|
1381
|
-
if (isResolving || api.openConversation === void 0 && isListLoading)
|
|
1382
|
-
|
|
2355
|
+
if (isResolving || api.openConversation === void 0 && isListLoading)
|
|
2356
|
+
return /* @__PURE__ */ jsx24("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
|
|
2357
|
+
return /* @__PURE__ */ jsx24(NotFound, { labels, onBack: props.onBack });
|
|
1383
2358
|
}
|
|
1384
2359
|
function LoadedThread(props) {
|
|
1385
|
-
const
|
|
1386
|
-
const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, selected);
|
|
1387
|
-
const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView({
|
|
2360
|
+
const core = useParticipantThreadController({
|
|
1388
2361
|
...props,
|
|
1389
|
-
subject: selected,
|
|
1390
|
-
onMarkedRead: inbox.markSubjectRead
|
|
2362
|
+
subject: props.selected,
|
|
2363
|
+
onMarkedRead: props.inbox.markSubjectRead
|
|
1391
2364
|
});
|
|
1392
|
-
|
|
1393
|
-
const { scroll, newMessagesCount } = useParticipantThreadScroll(items);
|
|
1394
|
-
function handleSend() {
|
|
1395
|
-
const content = takeForSend();
|
|
1396
|
-
if (content) void thread.send(content);
|
|
1397
|
-
}
|
|
1398
|
-
function handleEdit(clientMessageId) {
|
|
1399
|
-
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
1400
|
-
if (!entry) return;
|
|
1401
|
-
restoreFromEntry(entry);
|
|
1402
|
-
dispatchSend({ type: "discarded", clientMessageId });
|
|
1403
|
-
}
|
|
1404
|
-
return /* @__PURE__ */ jsx11(
|
|
2365
|
+
return /* @__PURE__ */ jsx24(
|
|
1405
2366
|
ParticipantThread,
|
|
1406
2367
|
{
|
|
1407
|
-
|
|
1408
|
-
items,
|
|
1409
|
-
hasMore: thread.hasMore,
|
|
1410
|
-
labels: props.labels,
|
|
1411
|
-
resolveAttachmentUrl,
|
|
1412
|
-
draft: { value: draft.text, onChange: setText, files: draft.files, onFilesChange: setFiles },
|
|
1413
|
-
onSend: handleSend,
|
|
1414
|
-
status: thread.status,
|
|
1415
|
-
refresh: () => void thread.refresh(),
|
|
1416
|
-
scroll,
|
|
1417
|
-
newMessagesCount,
|
|
1418
|
-
isSending: hasSendingEntry(sendEntries),
|
|
1419
|
-
channel: props.channel,
|
|
1420
|
-
locale: props.locale,
|
|
2368
|
+
...core,
|
|
1421
2369
|
onBack: props.onBack,
|
|
1422
2370
|
onOpenSubject: props.onOpenSubject,
|
|
1423
|
-
onLoadOlder: () => void thread.loadOlder(),
|
|
1424
|
-
pendingActions: {
|
|
1425
|
-
retryLocal: (clientMessageId) => void thread.retry(clientMessageId),
|
|
1426
|
-
discardLocal: (clientMessageId) => dispatchSend({ type: "discarded", clientMessageId }),
|
|
1427
|
-
editLocal: handleEdit,
|
|
1428
|
-
...props.onRetryPending ? { retryHost: props.onRetryPending } : {}
|
|
1429
|
-
},
|
|
1430
2371
|
quickReplies: props.quickReplies,
|
|
1431
|
-
renderSubjectCard: props.renderSubjectCard
|
|
2372
|
+
renderSubjectCard: props.renderSubjectCard,
|
|
2373
|
+
subjectGroups: props.subjectGroups,
|
|
2374
|
+
renderSubjectIcon: props.renderSubjectIcon,
|
|
2375
|
+
avatars: props.avatars,
|
|
2376
|
+
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
2377
|
+
tail: props.tail
|
|
1432
2378
|
}
|
|
1433
2379
|
);
|
|
1434
2380
|
}
|
|
@@ -1554,16 +2500,16 @@ function buildParticipantThemeStyle(theme) {
|
|
|
1554
2500
|
}
|
|
1555
2501
|
|
|
1556
2502
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
1557
|
-
import { jsx as
|
|
2503
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
1558
2504
|
var ALL_FILTER3 = "all";
|
|
1559
2505
|
function ParticipantConversationsScreen(props) {
|
|
1560
2506
|
const { selected, inbox, subjectGroups, classNames } = props;
|
|
1561
|
-
const [filter, setFilter] =
|
|
1562
|
-
const [searchQuery, setSearchQuery] =
|
|
1563
|
-
const draftsRef =
|
|
1564
|
-
const [sendStates, dispatchSendStates] =
|
|
1565
|
-
const labels =
|
|
1566
|
-
const view =
|
|
2507
|
+
const [filter, setFilter] = useState8(ALL_FILTER3);
|
|
2508
|
+
const [searchQuery, setSearchQuery] = useState8("");
|
|
2509
|
+
const draftsRef = useRef10(/* @__PURE__ */ new Map());
|
|
2510
|
+
const [sendStates, dispatchSendStates] = useReducer5(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2511
|
+
const labels = useMemo5(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
2512
|
+
const view = useMemo5(
|
|
1567
2513
|
() => groupParticipantConversations({
|
|
1568
2514
|
conversations: inbox.conversations,
|
|
1569
2515
|
subjectGroups,
|
|
@@ -1574,7 +2520,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
1574
2520
|
);
|
|
1575
2521
|
const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
|
|
1576
2522
|
const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
|
|
1577
|
-
return /* @__PURE__ */
|
|
2523
|
+
return /* @__PURE__ */ jsx25("div", { className: rootClassName, style: buildParticipantThemeStyle(props.theme), children: selected === void 0 ? /* @__PURE__ */ jsx25("div", { className: ["cv-p-root__screen", classNames?.inbox].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx25(
|
|
1578
2524
|
ParticipantInbox,
|
|
1579
2525
|
{
|
|
1580
2526
|
view,
|
|
@@ -1591,7 +2537,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
1591
2537
|
renderSubjectIcon: props.renderSubjectIcon,
|
|
1592
2538
|
search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
|
|
1593
2539
|
}
|
|
1594
|
-
) }) : /* @__PURE__ */
|
|
2540
|
+
) }) : /* @__PURE__ */ jsx25("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx25(
|
|
1595
2541
|
ParticipantThreadScreen,
|
|
1596
2542
|
{
|
|
1597
2543
|
...props,
|
|
@@ -1606,7 +2552,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
1606
2552
|
}
|
|
1607
2553
|
|
|
1608
2554
|
// src/participant/useParticipantInbox.ts
|
|
1609
|
-
import { useCallback as
|
|
2555
|
+
import { useCallback as useCallback8, useEffect as useEffect12, useReducer as useReducer6 } from "react";
|
|
1610
2556
|
|
|
1611
2557
|
// src/participant/participantInboxState.ts
|
|
1612
2558
|
var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
|
|
@@ -1656,8 +2602,8 @@ function describeError2(error) {
|
|
|
1656
2602
|
return error instanceof Error ? error.message : "unknown error";
|
|
1657
2603
|
}
|
|
1658
2604
|
function useParticipantInbox(api) {
|
|
1659
|
-
const [state, dispatch] =
|
|
1660
|
-
const refresh =
|
|
2605
|
+
const [state, dispatch] = useReducer6(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
|
|
2606
|
+
const refresh = useCallback8(async () => {
|
|
1661
2607
|
dispatch({ type: "started" });
|
|
1662
2608
|
try {
|
|
1663
2609
|
const page = await api.listConversations();
|
|
@@ -1667,7 +2613,7 @@ function useParticipantInbox(api) {
|
|
|
1667
2613
|
}
|
|
1668
2614
|
}, [api]);
|
|
1669
2615
|
const { nextCursor } = state;
|
|
1670
|
-
const loadMore =
|
|
2616
|
+
const loadMore = useCallback8(async () => {
|
|
1671
2617
|
if (!nextCursor) return;
|
|
1672
2618
|
try {
|
|
1673
2619
|
const page = await api.listConversations({ cursor: nextCursor });
|
|
@@ -1676,10 +2622,10 @@ function useParticipantInbox(api) {
|
|
|
1676
2622
|
dispatch({ type: "failed", error: describeError2(error) });
|
|
1677
2623
|
}
|
|
1678
2624
|
}, [api, nextCursor]);
|
|
1679
|
-
const markSubjectRead =
|
|
2625
|
+
const markSubjectRead = useCallback8((subject) => {
|
|
1680
2626
|
dispatch({ type: "markedRead", subject });
|
|
1681
2627
|
}, []);
|
|
1682
|
-
|
|
2628
|
+
useEffect12(() => {
|
|
1683
2629
|
void refresh();
|
|
1684
2630
|
}, [refresh]);
|
|
1685
2631
|
useParticipantRevalidation(api, () => {
|
|
@@ -1689,11 +2635,11 @@ function useParticipantInbox(api) {
|
|
|
1689
2635
|
}
|
|
1690
2636
|
|
|
1691
2637
|
// src/participant/ParticipantConversations.tsx
|
|
1692
|
-
import { jsx as
|
|
2638
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
1693
2639
|
function ParticipantConversations(props) {
|
|
1694
2640
|
const inbox = useParticipantInbox(props.api);
|
|
1695
2641
|
const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
|
|
1696
|
-
return /* @__PURE__ */
|
|
2642
|
+
return /* @__PURE__ */ jsx26(
|
|
1697
2643
|
ParticipantConversationsScreen,
|
|
1698
2644
|
{
|
|
1699
2645
|
...props,
|
|
@@ -1701,7 +2647,111 @@ function ParticipantConversations(props) {
|
|
|
1701
2647
|
}
|
|
1702
2648
|
);
|
|
1703
2649
|
}
|
|
2650
|
+
|
|
2651
|
+
// src/participant/ConversationThread.tsx
|
|
2652
|
+
import { useCallback as useCallback9, useMemo as useMemo6, useReducer as useReducer7, useRef as useRef11 } from "react";
|
|
2653
|
+
|
|
2654
|
+
// src/participant/conversationThreadApi.ts
|
|
2655
|
+
function toParticipantApi(api) {
|
|
2656
|
+
const { subscribe } = api;
|
|
2657
|
+
return {
|
|
2658
|
+
listConversations: async () => ({ data: [] }),
|
|
2659
|
+
fetchMessages: (subject, params) => api.fetchMessages(subject, params),
|
|
2660
|
+
sendMessage: (input) => api.sendMessage(input),
|
|
2661
|
+
markRead: async (subject) => {
|
|
2662
|
+
if (!api.markRead) throw new Error("The host api has no markRead");
|
|
2663
|
+
await api.markRead(subject);
|
|
2664
|
+
},
|
|
2665
|
+
resolveAttachmentUrl: (attachment, disposition) => api.resolveAttachmentUrl(attachment, disposition),
|
|
2666
|
+
...subscribe ? { subscribe: (listener) => api.subscribe?.(listener) ?? (() => void 0) } : {}
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// src/participant/conversationThreadSummary.ts
|
|
2671
|
+
function buildThreadSummary({
|
|
2672
|
+
subject,
|
|
2673
|
+
title,
|
|
2674
|
+
protocol,
|
|
2675
|
+
channels,
|
|
2676
|
+
status
|
|
2677
|
+
}) {
|
|
2678
|
+
return {
|
|
2679
|
+
subjectType: subject.subjectType,
|
|
2680
|
+
subjectId: subject.subjectId,
|
|
2681
|
+
subjectLabel: title,
|
|
2682
|
+
lastMessageAt: null,
|
|
2683
|
+
unreadCount: 0,
|
|
2684
|
+
awaitingParticipant: false,
|
|
2685
|
+
status: status ?? "open",
|
|
2686
|
+
...protocol ? { protocol } : {},
|
|
2687
|
+
...channels ? { channels } : {}
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// src/participant/ConversationThread.tsx
|
|
2692
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
2693
|
+
function useConversationThreadCore(props) {
|
|
2694
|
+
const { perspective = "participant" } = props;
|
|
2695
|
+
const { subjectType, subjectId } = props.subject;
|
|
2696
|
+
const subject = useMemo6(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
2697
|
+
const onMarkedReadRef = useRef11(props.onMarkedRead);
|
|
2698
|
+
onMarkedReadRef.current = props.onMarkedRead;
|
|
2699
|
+
const handleMarkedRead = useCallback9((marked) => onMarkedReadRef.current?.(marked), []);
|
|
2700
|
+
const api = useMemo6(() => toParticipantApi(props.api), [props.api]);
|
|
2701
|
+
const draftsRef = useRef11(/* @__PURE__ */ new Map());
|
|
2702
|
+
const [sendStates, dispatchSendStates] = useReducer7(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2703
|
+
const labels = useMemo6(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
2704
|
+
const { title, protocol, channels, status } = props;
|
|
2705
|
+
const conversation = useMemo6(
|
|
2706
|
+
() => buildThreadSummary({ subject, title, protocol, channels, status }),
|
|
2707
|
+
[subject, title, protocol, channels, status]
|
|
2708
|
+
);
|
|
2709
|
+
return useParticipantThreadController({
|
|
2710
|
+
api,
|
|
2711
|
+
subject,
|
|
2712
|
+
conversation,
|
|
2713
|
+
labels,
|
|
2714
|
+
draftsRef,
|
|
2715
|
+
sendStates,
|
|
2716
|
+
dispatchSendStates,
|
|
2717
|
+
onMarkedRead: handleMarkedRead,
|
|
2718
|
+
channel: props.channel,
|
|
2719
|
+
locale: props.locale,
|
|
2720
|
+
pendingMessages: props.pendingMessages,
|
|
2721
|
+
onRetryPending: props.onRetryPending,
|
|
2722
|
+
perspective
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
function ConversationThreadBody(props) {
|
|
2726
|
+
const core = useConversationThreadCore(props);
|
|
2727
|
+
return /* @__PURE__ */ jsx27(
|
|
2728
|
+
"div",
|
|
2729
|
+
{
|
|
2730
|
+
className: ["cv-p-root", props.className].filter(Boolean).join(" "),
|
|
2731
|
+
style: buildParticipantThemeStyle(props.theme),
|
|
2732
|
+
children: /* @__PURE__ */ jsx27("div", { className: "cv-p-root__screen", children: /* @__PURE__ */ jsx27(
|
|
2733
|
+
ParticipantThread,
|
|
2734
|
+
{
|
|
2735
|
+
...core,
|
|
2736
|
+
onBack: props.onBack,
|
|
2737
|
+
quickReplies: props.quickReplies,
|
|
2738
|
+
subjectGroups: props.subjectGroups,
|
|
2739
|
+
renderSubjectIcon: props.renderSubjectIcon,
|
|
2740
|
+
avatars: props.avatars,
|
|
2741
|
+
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
2742
|
+
tail: props.tail,
|
|
2743
|
+
counterpartLabel: props.counterpartLabel,
|
|
2744
|
+
headerActions: props.headerActions
|
|
2745
|
+
}
|
|
2746
|
+
) })
|
|
2747
|
+
}
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2750
|
+
function ConversationThread(props) {
|
|
2751
|
+
return /* @__PURE__ */ jsx27(ConversationThreadBody, { ...props }, draftKey(props.subject));
|
|
2752
|
+
}
|
|
1704
2753
|
export {
|
|
2754
|
+
ConversationThread,
|
|
1705
2755
|
DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS,
|
|
1706
2756
|
ParticipantConversations,
|
|
1707
2757
|
groupParticipantConversations
|