@adatechnology/conversations-ui 0.6.0 → 0.7.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 +126 -19
- 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.js +834 -764
- package/dist/participant/index.d.ts +18 -1
- package/dist/participant/index.js +1188 -313
- package/dist/preview/index.js +2 -3
- package/dist/styles.css +591 -19
- package/package.json +1 -1
- package/src/cssBlocks.test-helper.ts +31 -0
- package/src/legacyStyles.golden.test.ts +2 -31
- 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 +25 -81
- 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 +45 -95
- package/src/participant/ParticipantThreadAvatarSequence.test.tsx +65 -0
- package/src/participant/ParticipantThreadHeader.test.tsx +181 -0
- package/src/participant/ParticipantThreadHeader.tsx +73 -0
- package/src/participant/ParticipantThreadScreen.tsx +24 -10
- package/src/participant/ParticipantTimeline.tsx +103 -0
- package/src/participant/index.ts +1 -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/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/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/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/useParticipantCopyFeedback.ts +33 -0
- package/src/participant/useParticipantThreadScroll.ts +10 -2
- package/src/participant/useRevealActiveChip.ts +20 -0
- package/src/participant/useScrollAwayState.ts +24 -0
- package/src/participant/useStickToBottom.ts +16 -2
- package/src/participantLayoutShrink.test.ts +82 -0
- package/src/styles.css +140 -21
- 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 useRef9, useState as useState7 } 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, useMemo as useMemo4, useState as useState6 } 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,8 +1307,8 @@ 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
|
);
|
|
@@ -550,7 +1352,7 @@ function resolveOwnMessageStatus(params) {
|
|
|
550
1352
|
}
|
|
551
1353
|
|
|
552
1354
|
// src/participant/ParticipantMessageBubble.tsx
|
|
553
|
-
import { jsx as
|
|
1355
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
554
1356
|
function describeItem(item) {
|
|
555
1357
|
if (item.kind === "server") {
|
|
556
1358
|
const { message } = item;
|
|
@@ -573,48 +1375,11 @@ function describeItem(item) {
|
|
|
573
1375
|
displayState: item.displayState
|
|
574
1376
|
};
|
|
575
1377
|
}
|
|
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
|
-
};
|
|
1378
|
+
function bubbleClassName(isMine, hasTail) {
|
|
1379
|
+
const classes = ["cv-p-bubble"];
|
|
1380
|
+
if (isMine) classes.push("cv-p-bubble--mine");
|
|
1381
|
+
if (!hasTail) classes.push("cv-p-bubble--no-tail");
|
|
1382
|
+
return classes.join(" ");
|
|
618
1383
|
}
|
|
619
1384
|
function ParticipantMessageBubble({
|
|
620
1385
|
item,
|
|
@@ -623,15 +1388,17 @@ function ParticipantMessageBubble({
|
|
|
623
1388
|
resolveAttachmentUrl,
|
|
624
1389
|
onRetry,
|
|
625
1390
|
onDiscard,
|
|
626
|
-
onEdit
|
|
1391
|
+
onEdit,
|
|
1392
|
+
avatar,
|
|
1393
|
+
tail = true
|
|
627
1394
|
}) {
|
|
628
1395
|
const content = describeItem(item);
|
|
629
1396
|
const { isMine } = content;
|
|
630
1397
|
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__ */
|
|
1398
|
+
const bubble = /* @__PURE__ */ jsxs13("div", { className: bubbleClassName(isMine, tail), children: [
|
|
1399
|
+
isMine ? /* @__PURE__ */ jsx18("span", { className: "cv-p-sr-only", children: labels.me }) : content.authorName ? /* @__PURE__ */ jsx18("span", { className: "cv-p-bubble__author", children: content.authorName }) : null,
|
|
1400
|
+
content.text ? /* @__PURE__ */ jsx18(ParticipantMessageText, { text: content.text, labels }) : null,
|
|
1401
|
+
content.attachments.map((attachment) => /* @__PURE__ */ jsx18(
|
|
635
1402
|
ParticipantAttachmentItem,
|
|
636
1403
|
{
|
|
637
1404
|
attachment,
|
|
@@ -640,36 +1407,109 @@ function ParticipantMessageBubble({
|
|
|
640
1407
|
},
|
|
641
1408
|
attachment.id
|
|
642
1409
|
)),
|
|
643
|
-
content.pendingFilenames.map((filename, index) => /* @__PURE__ */
|
|
644
|
-
/* @__PURE__ */
|
|
645
|
-
/* @__PURE__ */
|
|
646
|
-
ownStatus ? /* @__PURE__ */
|
|
1410
|
+
content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx18("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
|
|
1411
|
+
/* @__PURE__ */ jsxs13("span", { className: "cv-p-bubble__meta", children: [
|
|
1412
|
+
/* @__PURE__ */ jsx18("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
|
|
1413
|
+
ownStatus ? /* @__PURE__ */ jsx18(OwnStatus, { status: ownStatus, labels, onRetry }) : null
|
|
647
1414
|
] }),
|
|
648
|
-
ownStatus === "failed" ? /* @__PURE__ */
|
|
1415
|
+
ownStatus === "failed" ? /* @__PURE__ */ jsx18(FailedActions, { labels, onDiscard, onEdit }) : null
|
|
1416
|
+
] });
|
|
1417
|
+
if (avatar === void 0 || isMine) return bubble;
|
|
1418
|
+
return /* @__PURE__ */ jsxs13("div", { className: "cv-p-bubble-row", children: [
|
|
1419
|
+
/* @__PURE__ */ jsx18("span", { className: "cv-p-bubble-row__avatar", children: avatar }),
|
|
1420
|
+
bubble
|
|
649
1421
|
] });
|
|
650
1422
|
}
|
|
651
1423
|
|
|
652
|
-
// src/participant/
|
|
653
|
-
|
|
1424
|
+
// src/participant/participantBubbleActions.ts
|
|
1425
|
+
function bindPendingActions(actions, clientMessageId) {
|
|
1426
|
+
const { retryHost } = actions;
|
|
1427
|
+
return {
|
|
1428
|
+
retryLocal: () => actions.retryLocal(clientMessageId),
|
|
1429
|
+
discardLocal: () => actions.discardLocal(clientMessageId),
|
|
1430
|
+
editLocal: () => actions.editLocal(clientMessageId),
|
|
1431
|
+
...retryHost ? { retryHost: () => retryHost(clientMessageId) } : {}
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
function resolveParticipantBubbleActions(item, handlers) {
|
|
1435
|
+
if (item.kind === "server" || item.displayState !== "failed") return {};
|
|
1436
|
+
if (item.origin === "host") return handlers.retryHost ? { onRetry: handlers.retryHost } : {};
|
|
1437
|
+
return { onRetry: handlers.retryLocal, onDiscard: handlers.discardLocal, onEdit: handlers.editLocal };
|
|
1438
|
+
}
|
|
654
1439
|
|
|
655
|
-
// src/participant/
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1440
|
+
// src/participant/ParticipantTimeline.tsx
|
|
1441
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1442
|
+
function createdAtOf2(item) {
|
|
1443
|
+
return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
|
|
1444
|
+
}
|
|
1445
|
+
function keyOf(item) {
|
|
1446
|
+
return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
|
|
1447
|
+
}
|
|
1448
|
+
function authorOf(item) {
|
|
1449
|
+
if (item.kind === "pending") return { isMine: true, author: null };
|
|
1450
|
+
return { isMine: isOwnMessage(item.message), author: item.message.authorName ?? null };
|
|
1451
|
+
}
|
|
1452
|
+
function resolveActions(item, pendingActions) {
|
|
1453
|
+
if (!pendingActions || item.kind === "server") return {};
|
|
1454
|
+
return resolveParticipantBubbleActions(item, bindPendingActions(pendingActions, item.pending.clientMessageId));
|
|
1455
|
+
}
|
|
1456
|
+
function resolveAvatarSlot({ isEnabled, isMine, showAvatar, author, render }) {
|
|
1457
|
+
if (!isEnabled || isMine) return void 0;
|
|
1458
|
+
return showAvatar ? /* @__PURE__ */ jsx19(ParticipantAuthorAvatar, { name: author, render }) : null;
|
|
1459
|
+
}
|
|
1460
|
+
function ParticipantTimeline(props) {
|
|
1461
|
+
const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
|
|
1462
|
+
const confirmsRead = channelCapabilityFor(props.channel ?? "app").confirmsRead;
|
|
1463
|
+
const isAvatarEnabled = props.avatars === "initials" || props.renderAuthorAvatar !== void 0;
|
|
1464
|
+
const nodes = [];
|
|
1465
|
+
let previousDay;
|
|
1466
|
+
let previousAuthor;
|
|
1467
|
+
for (const item of items) {
|
|
1468
|
+
const day = new Date(createdAtOf2(item));
|
|
1469
|
+
if (previousDay === void 0 || !isSameDay(previousDay, day)) {
|
|
1470
|
+
nodes.push(/* @__PURE__ */ jsx19(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
|
|
1471
|
+
previousAuthor = void 0;
|
|
1472
|
+
}
|
|
1473
|
+
previousDay = day;
|
|
1474
|
+
const { isMine, author } = authorOf(item);
|
|
1475
|
+
const showAvatar = shouldShowAvatar({ previousAuthor, author, isMine });
|
|
1476
|
+
previousAuthor = isMine ? void 0 : author;
|
|
1477
|
+
const avatar = resolveAvatarSlot({
|
|
1478
|
+
isEnabled: isAvatarEnabled,
|
|
1479
|
+
isMine,
|
|
1480
|
+
showAvatar,
|
|
1481
|
+
author,
|
|
1482
|
+
render: props.renderAuthorAvatar
|
|
1483
|
+
});
|
|
1484
|
+
nodes.push(
|
|
1485
|
+
/* @__PURE__ */ jsx19(
|
|
1486
|
+
ParticipantMessageBubble,
|
|
1487
|
+
{
|
|
1488
|
+
item,
|
|
1489
|
+
labels,
|
|
1490
|
+
confirmsRead,
|
|
1491
|
+
resolveAttachmentUrl,
|
|
1492
|
+
tail: props.tail,
|
|
1493
|
+
avatar,
|
|
1494
|
+
...resolveActions(item, pendingActions)
|
|
1495
|
+
},
|
|
1496
|
+
keyOf(item)
|
|
1497
|
+
)
|
|
1498
|
+
);
|
|
663
1499
|
}
|
|
1500
|
+
return /* @__PURE__ */ jsx19("div", { className: "cv-p-thread__messages", children: nodes });
|
|
664
1501
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1502
|
+
|
|
1503
|
+
// src/participant/ParticipantConversationAvatar.tsx
|
|
1504
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
1505
|
+
function ParticipantConversationAvatar({ icon }) {
|
|
1506
|
+
return /* @__PURE__ */ jsx20("span", { className: "cv-p-thread__avatar", "aria-hidden": "true", children: icon });
|
|
669
1507
|
}
|
|
670
1508
|
|
|
671
1509
|
// src/participant/ParticipantProtocolBadge.tsx
|
|
672
|
-
import {
|
|
1510
|
+
import { Check as Check2, Copy as Copy2 } from "lucide-react";
|
|
1511
|
+
import { useEffect as useEffect5, useReducer as useReducer3 } from "react";
|
|
1512
|
+
import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
673
1513
|
var COPIED_ANNOUNCEMENT_MS = 3e3;
|
|
674
1514
|
function ParticipantProtocolBadgeView({
|
|
675
1515
|
protocol,
|
|
@@ -678,31 +1518,36 @@ function ParticipantProtocolBadgeView({
|
|
|
678
1518
|
onCopy
|
|
679
1519
|
}) {
|
|
680
1520
|
const { isCopied, copyCount } = copyState;
|
|
681
|
-
return /* @__PURE__ */
|
|
682
|
-
/* @__PURE__ */
|
|
683
|
-
/* @__PURE__ */
|
|
684
|
-
/* @__PURE__ */
|
|
1521
|
+
return /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
1522
|
+
/* @__PURE__ */ jsxs14("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
|
|
1523
|
+
/* @__PURE__ */ jsxs14("span", { className: "cv-p-protocol__code", children: [
|
|
1524
|
+
/* @__PURE__ */ jsxs14("span", { className: "cv-p-sr-only", children: [
|
|
685
1525
|
labels.protocolPrefix,
|
|
686
1526
|
" "
|
|
687
1527
|
] }),
|
|
688
1528
|
protocol
|
|
689
1529
|
] }),
|
|
690
|
-
/* @__PURE__ */
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
protocol
|
|
696
|
-
|
|
697
|
-
|
|
1530
|
+
/* @__PURE__ */ jsxs14(
|
|
1531
|
+
"button",
|
|
1532
|
+
{
|
|
1533
|
+
type: "button",
|
|
1534
|
+
className: "cv-p-protocol__copy",
|
|
1535
|
+
"aria-label": `${labels.copyProtocol} ${protocol}`,
|
|
1536
|
+
onClick: onCopy,
|
|
1537
|
+
children: [
|
|
1538
|
+
isCopied ? /* @__PURE__ */ jsx21(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx21(Copy2, { size: 18, "aria-hidden": true }),
|
|
1539
|
+
isCopied ? /* @__PURE__ */ jsx21("span", { className: "cv-p-protocol__copied", "aria-hidden": "true", children: labels.protocolCopied }) : null
|
|
1540
|
+
]
|
|
1541
|
+
}
|
|
1542
|
+
)
|
|
698
1543
|
] }),
|
|
699
|
-
/* @__PURE__ */
|
|
1544
|
+
/* @__PURE__ */ jsx21("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx21("span", { children: labels.protocolCopied }, copyCount) : null })
|
|
700
1545
|
] });
|
|
701
1546
|
}
|
|
702
1547
|
function ParticipantProtocolBadge({ protocol, labels }) {
|
|
703
|
-
const [copyState, dispatch] =
|
|
1548
|
+
const [copyState, dispatch] = useReducer3(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
|
|
704
1549
|
const { isCopied, copyCount } = copyState;
|
|
705
|
-
|
|
1550
|
+
useEffect5(() => {
|
|
706
1551
|
if (!isCopied) return void 0;
|
|
707
1552
|
const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_ANNOUNCEMENT_MS);
|
|
708
1553
|
return () => clearTimeout(timer);
|
|
@@ -711,7 +1556,7 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
711
1556
|
const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
|
|
712
1557
|
if (await copyProtocolToClipboard(protocol, clipboard)) dispatch({ type: "copied" });
|
|
713
1558
|
}
|
|
714
|
-
return /* @__PURE__ */
|
|
1559
|
+
return /* @__PURE__ */ jsx21(
|
|
715
1560
|
ParticipantProtocolBadgeView,
|
|
716
1561
|
{
|
|
717
1562
|
protocol,
|
|
@@ -722,77 +1567,54 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
722
1567
|
);
|
|
723
1568
|
}
|
|
724
1569
|
|
|
725
|
-
// src/participant/
|
|
726
|
-
import { jsx as
|
|
727
|
-
function
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1570
|
+
// src/participant/ParticipantThreadHeader.tsx
|
|
1571
|
+
import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1572
|
+
function Title({ conversation, labels, onOpenSubject }) {
|
|
1573
|
+
const { subjectType, subjectId, subjectLabel } = conversation;
|
|
1574
|
+
return /* @__PURE__ */ jsx22("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx22(
|
|
1575
|
+
"button",
|
|
1576
|
+
{
|
|
1577
|
+
type: "button",
|
|
1578
|
+
className: "cv-p-thread__title-button",
|
|
1579
|
+
"aria-label": `${labels.openSubject}: ${subjectLabel}`,
|
|
1580
|
+
onClick: () => onOpenSubject({ subjectType, subjectId }),
|
|
1581
|
+
children: /* @__PURE__ */ jsx22("span", { className: "cv-p-thread__title-text", children: subjectLabel })
|
|
1582
|
+
}
|
|
1583
|
+
) : /* @__PURE__ */ jsx22("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
|
|
736
1584
|
}
|
|
737
|
-
function
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1585
|
+
function ParticipantThreadHeader({
|
|
1586
|
+
conversation,
|
|
1587
|
+
labels,
|
|
1588
|
+
subjectGroups,
|
|
1589
|
+
renderSubjectIcon,
|
|
1590
|
+
onBack,
|
|
1591
|
+
onOpenSubject
|
|
1592
|
+
}) {
|
|
1593
|
+
const group = subjectGroups?.find((candidate) => candidate.subjectType === conversation.subjectType);
|
|
1594
|
+
const icon = resolveConversationIcon({ conversation, group, renderSubjectIcon });
|
|
1595
|
+
const hasChannels = resolveParticipantChannels(conversation.channels).length > 0;
|
|
1596
|
+
return /* @__PURE__ */ jsxs15("header", { className: "cv-p-thread__header", children: [
|
|
1597
|
+
onBack ? /* @__PURE__ */ jsx22(ParticipantBackButton, { label: labels.back, onBack }) : null,
|
|
1598
|
+
icon !== void 0 ? /* @__PURE__ */ jsx22(ParticipantConversationAvatar, { icon }) : null,
|
|
1599
|
+
/* @__PURE__ */ jsxs15("div", { className: "cv-p-thread__heading", children: [
|
|
1600
|
+
icon === void 0 && group ? /* @__PURE__ */ jsx22("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
|
|
1601
|
+
/* @__PURE__ */ jsx22(Title, { conversation, labels, onOpenSubject }),
|
|
1602
|
+
conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs15("div", { className: "cv-p-thread__meta", children: [
|
|
1603
|
+
conversation.protocol ? /* @__PURE__ */ jsx22(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
|
|
1604
|
+
/* @__PURE__ */ jsx22(ParticipantChannelBadges, { channels: conversation.channels, labels, variant: "inline", iconSize: 16 })
|
|
1605
|
+
] }) : null
|
|
758
1606
|
] })
|
|
759
1607
|
] });
|
|
760
1608
|
}
|
|
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
|
-
}
|
|
1609
|
+
|
|
1610
|
+
// src/participant/ParticipantThread.tsx
|
|
1611
|
+
import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
790
1612
|
function Footer({ props }) {
|
|
791
1613
|
const { conversation, labels, draft } = props;
|
|
792
1614
|
if (conversation.status === "closed") {
|
|
793
|
-
return /* @__PURE__ */
|
|
1615
|
+
return /* @__PURE__ */ jsx23("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
|
|
794
1616
|
}
|
|
795
|
-
return /* @__PURE__ */
|
|
1617
|
+
return /* @__PURE__ */ jsx23(
|
|
796
1618
|
ParticipantComposer,
|
|
797
1619
|
{
|
|
798
1620
|
value: draft.value,
|
|
@@ -809,23 +1631,46 @@ function Footer({ props }) {
|
|
|
809
1631
|
}
|
|
810
1632
|
);
|
|
811
1633
|
}
|
|
812
|
-
function
|
|
1634
|
+
function ThreadBody(props) {
|
|
813
1635
|
const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
|
|
814
1636
|
const subjectCard = renderSubjectCard?.(conversation);
|
|
815
1637
|
const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
|
|
816
|
-
return /* @__PURE__ */
|
|
817
|
-
/* @__PURE__ */
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1638
|
+
return /* @__PURE__ */ jsxs16("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, children: [
|
|
1639
|
+
/* @__PURE__ */ jsx23(
|
|
1640
|
+
ParticipantThreadHeader,
|
|
1641
|
+
{
|
|
1642
|
+
conversation,
|
|
1643
|
+
labels,
|
|
1644
|
+
subjectGroups: props.subjectGroups,
|
|
1645
|
+
renderSubjectIcon: props.renderSubjectIcon,
|
|
1646
|
+
onBack: props.onBack,
|
|
1647
|
+
onOpenSubject: props.onOpenSubject
|
|
1648
|
+
}
|
|
1649
|
+
),
|
|
1650
|
+
subjectCard ? /* @__PURE__ */ jsx23("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
|
|
1651
|
+
/* @__PURE__ */ jsxs16("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
|
|
1652
|
+
loadView.isLoading ? /* @__PURE__ */ jsx23(ParticipantLoading, { labels }) : null,
|
|
1653
|
+
loadView.hasError ? /* @__PURE__ */ jsx23(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
|
|
1654
|
+
hasMore && onLoadOlder ? /* @__PURE__ */ jsx23("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
|
|
1655
|
+
/* @__PURE__ */ jsx23(ParticipantTimeline, { ...props }),
|
|
1656
|
+
/* @__PURE__ */ jsx23(
|
|
1657
|
+
ParticipantScrollToLatest,
|
|
1658
|
+
{
|
|
1659
|
+
label: labels.scrollToLatest,
|
|
1660
|
+
isAwayFromBottom: props.scroll.isAwayFromBottom === true,
|
|
1661
|
+
hasMessages: props.items.length > 0,
|
|
1662
|
+
newMessagesCount: props.newMessagesCount,
|
|
1663
|
+
onClick: props.scroll.scrollToLatest
|
|
1664
|
+
}
|
|
1665
|
+
)
|
|
824
1666
|
] }),
|
|
825
|
-
/* @__PURE__ */
|
|
826
|
-
/* @__PURE__ */
|
|
1667
|
+
/* @__PURE__ */ jsx23(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
|
|
1668
|
+
/* @__PURE__ */ jsx23(Footer, { props })
|
|
827
1669
|
] });
|
|
828
1670
|
}
|
|
1671
|
+
function ParticipantThread(props) {
|
|
1672
|
+
return /* @__PURE__ */ jsx23(ParticipantCopyAnnouncer, { children: /* @__PURE__ */ jsx23(ThreadBody, { ...props }) });
|
|
1673
|
+
}
|
|
829
1674
|
|
|
830
1675
|
// src/participant/bindAttachmentUrlResolver.ts
|
|
831
1676
|
function bindAttachmentUrlResolver(api) {
|
|
@@ -959,7 +1804,7 @@ function useParticipantDraft(draftsRef, subject) {
|
|
|
959
1804
|
}
|
|
960
1805
|
|
|
961
1806
|
// src/participant/useParticipantConversationView.ts
|
|
962
|
-
import { useCallback as
|
|
1807
|
+
import { useCallback as useCallback4, useMemo as useMemo3 } from "react";
|
|
963
1808
|
|
|
964
1809
|
// src/participant/participantSendStates.ts
|
|
965
1810
|
var NO_ENTRIES = [];
|
|
@@ -977,7 +1822,7 @@ function participantSendStatesReducer(states, { subject, action }) {
|
|
|
977
1822
|
}
|
|
978
1823
|
|
|
979
1824
|
// src/participant/useParticipantThread.ts
|
|
980
|
-
import { useCallback as
|
|
1825
|
+
import { useCallback as useCallback3, useEffect as useEffect9, useMemo as useMemo2, useReducer as useReducer4, useRef as useRef6 } from "react";
|
|
981
1826
|
|
|
982
1827
|
// src/participant/participantThreadState.ts
|
|
983
1828
|
var INITIAL_PARTICIPANT_THREAD_STATE = {
|
|
@@ -1013,7 +1858,7 @@ function participantThreadReducer(state, action) {
|
|
|
1013
1858
|
}
|
|
1014
1859
|
|
|
1015
1860
|
// src/participant/useParticipantMarkRead.ts
|
|
1016
|
-
import { useEffect as
|
|
1861
|
+
import { useEffect as useEffect7 } from "react";
|
|
1017
1862
|
|
|
1018
1863
|
// src/participant/shouldMarkRead.ts
|
|
1019
1864
|
function shouldMarkParticipantRead(params) {
|
|
@@ -1025,12 +1870,12 @@ function shouldMarkParticipantRead(params) {
|
|
|
1025
1870
|
}
|
|
1026
1871
|
|
|
1027
1872
|
// src/participant/useDocumentVisibility.ts
|
|
1028
|
-
import { useEffect as
|
|
1873
|
+
import { useEffect as useEffect6, useState as useState4 } from "react";
|
|
1029
1874
|
function useDocumentVisibility() {
|
|
1030
1875
|
const [visibilityState, setVisibilityState] = useState4(
|
|
1031
1876
|
() => typeof document === "undefined" ? "visible" : document.visibilityState
|
|
1032
1877
|
);
|
|
1033
|
-
|
|
1878
|
+
useEffect6(() => {
|
|
1034
1879
|
function handleVisibilityChange() {
|
|
1035
1880
|
setVisibilityState(document.visibilityState);
|
|
1036
1881
|
}
|
|
@@ -1045,18 +1890,18 @@ function useDocumentVisibility() {
|
|
|
1045
1890
|
function useParticipantMarkRead(params) {
|
|
1046
1891
|
const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
|
|
1047
1892
|
const visibilityState = useDocumentVisibility();
|
|
1048
|
-
|
|
1893
|
+
useEffect7(() => {
|
|
1049
1894
|
if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
|
|
1050
1895
|
void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
|
|
1051
1896
|
}, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
|
|
1052
1897
|
}
|
|
1053
1898
|
|
|
1054
1899
|
// src/participant/useParticipantRevalidation.ts
|
|
1055
|
-
import { useEffect as
|
|
1900
|
+
import { useEffect as useEffect8, useRef as useRef4 } from "react";
|
|
1056
1901
|
function useParticipantRevalidation(api, onRevalidate) {
|
|
1057
|
-
const handlerRef =
|
|
1902
|
+
const handlerRef = useRef4(onRevalidate);
|
|
1058
1903
|
handlerRef.current = onRevalidate;
|
|
1059
|
-
|
|
1904
|
+
useEffect8(() => {
|
|
1060
1905
|
function handleFocus() {
|
|
1061
1906
|
handlerRef.current();
|
|
1062
1907
|
}
|
|
@@ -1083,12 +1928,12 @@ function useParticipantRevalidation(api, onRevalidate) {
|
|
|
1083
1928
|
}
|
|
1084
1929
|
|
|
1085
1930
|
// src/participant/useParticipantSend.ts
|
|
1086
|
-
import { useCallback, useRef as
|
|
1931
|
+
import { useCallback as useCallback2, useRef as useRef5 } from "react";
|
|
1087
1932
|
function useParticipantSend(params) {
|
|
1088
1933
|
const { apiRef, subject, dispatchSend, onSentMessage } = params;
|
|
1089
|
-
const entriesRef =
|
|
1934
|
+
const entriesRef = useRef5(params.entries);
|
|
1090
1935
|
entriesRef.current = params.entries;
|
|
1091
|
-
const deliver =
|
|
1936
|
+
const deliver = useCallback2(
|
|
1092
1937
|
async (entry) => {
|
|
1093
1938
|
const { clientMessageId } = entry;
|
|
1094
1939
|
try {
|
|
@@ -1112,7 +1957,7 @@ function useParticipantSend(params) {
|
|
|
1112
1957
|
},
|
|
1113
1958
|
[apiRef, dispatchSend, onSentMessage]
|
|
1114
1959
|
);
|
|
1115
|
-
const send =
|
|
1960
|
+
const send = useCallback2(
|
|
1116
1961
|
async (content) => {
|
|
1117
1962
|
const entry = createParticipantSendEntry({
|
|
1118
1963
|
clientMessageId: crypto.randomUUID(),
|
|
@@ -1125,7 +1970,7 @@ function useParticipantSend(params) {
|
|
|
1125
1970
|
},
|
|
1126
1971
|
[deliver, dispatchSend, subject]
|
|
1127
1972
|
);
|
|
1128
|
-
const retry =
|
|
1973
|
+
const retry = useCallback2(
|
|
1129
1974
|
async (clientMessageId) => {
|
|
1130
1975
|
const entry = findFailedEntry(entriesRef.current, clientMessageId);
|
|
1131
1976
|
if (!entry) return;
|
|
@@ -1149,13 +1994,13 @@ function isForSubject(event, subject) {
|
|
|
1149
1994
|
function useParticipantThread(params) {
|
|
1150
1995
|
const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [] } = params;
|
|
1151
1996
|
const { subjectType, subjectId } = subject;
|
|
1152
|
-
const stableSubject =
|
|
1153
|
-
const [state, dispatch] =
|
|
1154
|
-
const apiRef =
|
|
1997
|
+
const stableSubject = useMemo2(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
1998
|
+
const [state, dispatch] = useReducer4(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
|
|
1999
|
+
const apiRef = useRef6(api);
|
|
1155
2000
|
apiRef.current = api;
|
|
1156
|
-
const activeSubjectKey =
|
|
2001
|
+
const activeSubjectKey = useRef6("");
|
|
1157
2002
|
const confirmsRead = channelCapabilityFor(channel).confirmsRead;
|
|
1158
|
-
const refresh =
|
|
2003
|
+
const refresh = useCallback3(async () => {
|
|
1159
2004
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
1160
2005
|
dispatch({ type: "started" });
|
|
1161
2006
|
try {
|
|
@@ -1168,7 +2013,7 @@ function useParticipantThread(params) {
|
|
|
1168
2013
|
}
|
|
1169
2014
|
}, [stableSubject, subjectType, subjectId]);
|
|
1170
2015
|
const oldestMessageId = state.messages[0]?.id;
|
|
1171
|
-
const loadOlder =
|
|
2016
|
+
const loadOlder = useCallback3(async () => {
|
|
1172
2017
|
if (!oldestMessageId) return;
|
|
1173
2018
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
1174
2019
|
try {
|
|
@@ -1180,9 +2025,9 @@ function useParticipantThread(params) {
|
|
|
1180
2025
|
if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
|
|
1181
2026
|
}
|
|
1182
2027
|
}, [stableSubject, subjectType, subjectId, oldestMessageId]);
|
|
1183
|
-
const handleSentMessage =
|
|
2028
|
+
const handleSentMessage = useCallback3((message) => dispatch({ type: "sendConfirmed", message }), []);
|
|
1184
2029
|
const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
|
|
1185
|
-
|
|
2030
|
+
useEffect9(() => {
|
|
1186
2031
|
activeSubjectKey.current = `${subjectType}:${subjectId}`;
|
|
1187
2032
|
dispatch({ type: "reset" });
|
|
1188
2033
|
void refresh();
|
|
@@ -1190,12 +2035,12 @@ function useParticipantThread(params) {
|
|
|
1190
2035
|
useParticipantRevalidation(api, (event) => {
|
|
1191
2036
|
if (isForSubject(event, stableSubject)) void refresh();
|
|
1192
2037
|
});
|
|
1193
|
-
|
|
2038
|
+
useEffect9(() => {
|
|
1194
2039
|
if (sendEntries.length === 0) return;
|
|
1195
2040
|
dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
|
|
1196
2041
|
}, [sendEntries, state.messages, hostPending, dispatchSend]);
|
|
1197
2042
|
useParticipantMarkRead({ apiRef, subject: stableSubject, unreadCount, messageCount: state.messages.length, onMarkedRead });
|
|
1198
|
-
const localPending =
|
|
2043
|
+
const localPending = useMemo2(() => toLocalPending(sendEntries), [sendEntries]);
|
|
1199
2044
|
return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
|
|
1200
2045
|
}
|
|
1201
2046
|
|
|
@@ -1204,7 +2049,7 @@ function useParticipantConversationView(params) {
|
|
|
1204
2049
|
const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
|
|
1205
2050
|
const { subjectType, subjectId } = subject;
|
|
1206
2051
|
const sendEntries = selectSendEntries(sendStates, subject);
|
|
1207
|
-
const dispatchSend =
|
|
2052
|
+
const dispatchSend = useCallback4(
|
|
1208
2053
|
(action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
|
|
1209
2054
|
[dispatchSendStates, subjectType, subjectId]
|
|
1210
2055
|
);
|
|
@@ -1218,7 +2063,7 @@ function useParticipantConversationView(params) {
|
|
|
1218
2063
|
dispatchSend,
|
|
1219
2064
|
hostPending: pendingMessages
|
|
1220
2065
|
});
|
|
1221
|
-
const items =
|
|
2066
|
+
const items = useMemo3(
|
|
1222
2067
|
() => mergeParticipantMessages({
|
|
1223
2068
|
serverMessages: thread.messages,
|
|
1224
2069
|
hostPending: pendingMessages ?? [],
|
|
@@ -1231,7 +2076,7 @@ function useParticipantConversationView(params) {
|
|
|
1231
2076
|
}
|
|
1232
2077
|
|
|
1233
2078
|
// src/participant/useParticipantThreadScroll.ts
|
|
1234
|
-
import { useEffect as
|
|
2079
|
+
import { useEffect as useEffect10, useRef as useRef8 } from "react";
|
|
1235
2080
|
|
|
1236
2081
|
// src/participant/participantNewMessages.ts
|
|
1237
2082
|
function collectServerMessageIds(items) {
|
|
@@ -1255,8 +2100,21 @@ function countNewIncomingMessages(previousIds, currentItems) {
|
|
|
1255
2100
|
).length;
|
|
1256
2101
|
}
|
|
1257
2102
|
|
|
2103
|
+
// src/participant/useScrollAwayState.ts
|
|
2104
|
+
import { useCallback as useCallback5 } from "react";
|
|
2105
|
+
function useScrollAwayState(ref, isNearBottom) {
|
|
2106
|
+
const scrollToLatest = useCallback5(() => {
|
|
2107
|
+
const element = ref.current;
|
|
2108
|
+
if (!element) return;
|
|
2109
|
+
element.scrollTo(
|
|
2110
|
+
scrollToLatestOptions({ scrollHeight: element.scrollHeight, isReducedMotion: readPrefersReducedMotion() })
|
|
2111
|
+
);
|
|
2112
|
+
}, [ref]);
|
|
2113
|
+
return { isAwayFromBottom: !isNearBottom, scrollToLatest };
|
|
2114
|
+
}
|
|
2115
|
+
|
|
1258
2116
|
// src/participant/useStickToBottom.ts
|
|
1259
|
-
import { useCallback as
|
|
2117
|
+
import { useCallback as useCallback6, useLayoutEffect as useLayoutEffect2, useRef as useRef7, useState as useState5 } from "react";
|
|
1260
2118
|
|
|
1261
2119
|
// src/participant/participantScroll.ts
|
|
1262
2120
|
var NEAR_BOTTOM_THRESHOLD_PX = 80;
|
|
@@ -1278,11 +2136,17 @@ function resolveScrollAction(params) {
|
|
|
1278
2136
|
|
|
1279
2137
|
// src/participant/useStickToBottom.ts
|
|
1280
2138
|
function useStickToBottom({ firstKey, lastKey }) {
|
|
1281
|
-
const ref =
|
|
1282
|
-
const snapshot =
|
|
2139
|
+
const ref = useRef7(null);
|
|
2140
|
+
const snapshot = useRef7({
|
|
2141
|
+
hasPositioned: false,
|
|
2142
|
+
firstKey: void 0,
|
|
2143
|
+
lastKey: void 0,
|
|
2144
|
+
scrollTop: 0,
|
|
2145
|
+
scrollHeight: 0
|
|
2146
|
+
});
|
|
1283
2147
|
const [isNearBottom, setIsNearBottom] = useState5(true);
|
|
1284
|
-
const isNearBottomRef =
|
|
1285
|
-
const onScroll =
|
|
2148
|
+
const isNearBottomRef = useRef7(true);
|
|
2149
|
+
const onScroll = useCallback6((event) => {
|
|
1286
2150
|
const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
|
|
1287
2151
|
const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
|
|
1288
2152
|
snapshot.current.scrollTop = scrollTop;
|
|
@@ -1290,7 +2154,7 @@ function useStickToBottom({ firstKey, lastKey }) {
|
|
|
1290
2154
|
isNearBottomRef.current = isNear;
|
|
1291
2155
|
setIsNearBottom(isNear);
|
|
1292
2156
|
}, []);
|
|
1293
|
-
|
|
2157
|
+
useLayoutEffect2(() => {
|
|
1294
2158
|
const element = ref.current;
|
|
1295
2159
|
const previous = snapshot.current;
|
|
1296
2160
|
if (!element) return;
|
|
@@ -1331,21 +2195,26 @@ function keyOf2(item) {
|
|
|
1331
2195
|
return item.kind === "server" ? item.message.id : `pending:${item.pending.clientMessageId}`;
|
|
1332
2196
|
}
|
|
1333
2197
|
function useParticipantThreadScroll(items) {
|
|
1334
|
-
const { scroll, isNearBottom } = useStickToBottom({
|
|
1335
|
-
|
|
1336
|
-
|
|
2198
|
+
const { scroll: base, isNearBottom } = useStickToBottom({
|
|
2199
|
+
firstKey: keyOf2(items[0]),
|
|
2200
|
+
lastKey: keyOf2(items[items.length - 1])
|
|
2201
|
+
});
|
|
2202
|
+
const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
|
|
2203
|
+
const scroll = { ...base, isAwayFromBottom, scrollToLatest };
|
|
2204
|
+
const seenIds = useRef8(/* @__PURE__ */ new Set());
|
|
2205
|
+
useEffect10(() => {
|
|
1337
2206
|
if (isNearBottom) seenIds.current = collectServerMessageIds(items);
|
|
1338
2207
|
}, [isNearBottom, items]);
|
|
1339
2208
|
return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items) };
|
|
1340
2209
|
}
|
|
1341
2210
|
|
|
1342
2211
|
// src/participant/ParticipantThreadScreen.tsx
|
|
1343
|
-
import { jsx as
|
|
2212
|
+
import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1344
2213
|
function useOpenedConversation(api, subject, isNeeded) {
|
|
1345
2214
|
const [opened, setOpened] = useState6({ status: "idle" });
|
|
1346
2215
|
const { subjectType, subjectId } = subject;
|
|
1347
2216
|
const canOpen = api.openConversation !== void 0;
|
|
1348
|
-
|
|
2217
|
+
useEffect11(() => {
|
|
1349
2218
|
if (!isNeeded || !canOpen) return;
|
|
1350
2219
|
let isCurrent = true;
|
|
1351
2220
|
setOpened({ status: "loading" });
|
|
@@ -1359,13 +2228,13 @@ function useOpenedConversation(api, subject, isNeeded) {
|
|
|
1359
2228
|
}, [api, canOpen, isNeeded, subjectType, subjectId]);
|
|
1360
2229
|
return opened;
|
|
1361
2230
|
}
|
|
1362
|
-
function NotFound({
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
/* @__PURE__ */
|
|
2231
|
+
function NotFound({
|
|
2232
|
+
labels,
|
|
2233
|
+
onBack
|
|
2234
|
+
}) {
|
|
2235
|
+
return /* @__PURE__ */ jsxs17("div", { className: "cv-p cv-p-thread", children: [
|
|
2236
|
+
/* @__PURE__ */ jsx24("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx24(ParticipantBackButton, { label: labels.back, onBack }) : null }),
|
|
2237
|
+
/* @__PURE__ */ jsx24("p", { className: "cv-p-empty", children: labels.notFound })
|
|
1369
2238
|
] });
|
|
1370
2239
|
}
|
|
1371
2240
|
function ParticipantThreadScreen(props) {
|
|
@@ -1375,11 +2244,12 @@ function ParticipantThreadScreen(props) {
|
|
|
1375
2244
|
);
|
|
1376
2245
|
const opened = useOpenedConversation(api, selected, listed === void 0);
|
|
1377
2246
|
const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
|
|
1378
|
-
if (conversation) return /* @__PURE__ */
|
|
2247
|
+
if (conversation) return /* @__PURE__ */ jsx24(LoadedThread, { ...props, conversation });
|
|
1379
2248
|
const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
|
|
1380
2249
|
const isListLoading = inbox.status === "idle" || inbox.status === "loading";
|
|
1381
|
-
if (isResolving || api.openConversation === void 0 && isListLoading)
|
|
1382
|
-
|
|
2250
|
+
if (isResolving || api.openConversation === void 0 && isListLoading)
|
|
2251
|
+
return /* @__PURE__ */ jsx24("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
|
|
2252
|
+
return /* @__PURE__ */ jsx24(NotFound, { labels, onBack: props.onBack });
|
|
1383
2253
|
}
|
|
1384
2254
|
function LoadedThread(props) {
|
|
1385
2255
|
const { api, selected, conversation, draftsRef, inbox } = props;
|
|
@@ -1389,7 +2259,7 @@ function LoadedThread(props) {
|
|
|
1389
2259
|
subject: selected,
|
|
1390
2260
|
onMarkedRead: inbox.markSubjectRead
|
|
1391
2261
|
});
|
|
1392
|
-
const resolveAttachmentUrl =
|
|
2262
|
+
const resolveAttachmentUrl = useMemo4(() => bindAttachmentUrlResolver(api), [api]);
|
|
1393
2263
|
const { scroll, newMessagesCount } = useParticipantThreadScroll(items);
|
|
1394
2264
|
function handleSend() {
|
|
1395
2265
|
const content = takeForSend();
|
|
@@ -1401,7 +2271,7 @@ function LoadedThread(props) {
|
|
|
1401
2271
|
restoreFromEntry(entry);
|
|
1402
2272
|
dispatchSend({ type: "discarded", clientMessageId });
|
|
1403
2273
|
}
|
|
1404
|
-
return /* @__PURE__ */
|
|
2274
|
+
return /* @__PURE__ */ jsx24(
|
|
1405
2275
|
ParticipantThread,
|
|
1406
2276
|
{
|
|
1407
2277
|
conversation,
|
|
@@ -1428,7 +2298,12 @@ function LoadedThread(props) {
|
|
|
1428
2298
|
...props.onRetryPending ? { retryHost: props.onRetryPending } : {}
|
|
1429
2299
|
},
|
|
1430
2300
|
quickReplies: props.quickReplies,
|
|
1431
|
-
renderSubjectCard: props.renderSubjectCard
|
|
2301
|
+
renderSubjectCard: props.renderSubjectCard,
|
|
2302
|
+
subjectGroups: props.subjectGroups,
|
|
2303
|
+
renderSubjectIcon: props.renderSubjectIcon,
|
|
2304
|
+
avatars: props.avatars,
|
|
2305
|
+
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
2306
|
+
tail: props.tail
|
|
1432
2307
|
}
|
|
1433
2308
|
);
|
|
1434
2309
|
}
|
|
@@ -1554,16 +2429,16 @@ function buildParticipantThemeStyle(theme) {
|
|
|
1554
2429
|
}
|
|
1555
2430
|
|
|
1556
2431
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
1557
|
-
import { jsx as
|
|
2432
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
1558
2433
|
var ALL_FILTER3 = "all";
|
|
1559
2434
|
function ParticipantConversationsScreen(props) {
|
|
1560
2435
|
const { selected, inbox, subjectGroups, classNames } = props;
|
|
1561
2436
|
const [filter, setFilter] = useState7(ALL_FILTER3);
|
|
1562
2437
|
const [searchQuery, setSearchQuery] = useState7("");
|
|
1563
|
-
const draftsRef =
|
|
1564
|
-
const [sendStates, dispatchSendStates] =
|
|
1565
|
-
const labels =
|
|
1566
|
-
const view =
|
|
2438
|
+
const draftsRef = useRef9(/* @__PURE__ */ new Map());
|
|
2439
|
+
const [sendStates, dispatchSendStates] = useReducer5(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2440
|
+
const labels = useMemo5(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
2441
|
+
const view = useMemo5(
|
|
1567
2442
|
() => groupParticipantConversations({
|
|
1568
2443
|
conversations: inbox.conversations,
|
|
1569
2444
|
subjectGroups,
|
|
@@ -1574,7 +2449,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
1574
2449
|
);
|
|
1575
2450
|
const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
|
|
1576
2451
|
const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
|
|
1577
|
-
return /* @__PURE__ */
|
|
2452
|
+
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
2453
|
ParticipantInbox,
|
|
1579
2454
|
{
|
|
1580
2455
|
view,
|
|
@@ -1591,7 +2466,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
1591
2466
|
renderSubjectIcon: props.renderSubjectIcon,
|
|
1592
2467
|
search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
|
|
1593
2468
|
}
|
|
1594
|
-
) }) : /* @__PURE__ */
|
|
2469
|
+
) }) : /* @__PURE__ */ jsx25("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx25(
|
|
1595
2470
|
ParticipantThreadScreen,
|
|
1596
2471
|
{
|
|
1597
2472
|
...props,
|
|
@@ -1606,7 +2481,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
1606
2481
|
}
|
|
1607
2482
|
|
|
1608
2483
|
// src/participant/useParticipantInbox.ts
|
|
1609
|
-
import { useCallback as
|
|
2484
|
+
import { useCallback as useCallback7, useEffect as useEffect12, useReducer as useReducer6 } from "react";
|
|
1610
2485
|
|
|
1611
2486
|
// src/participant/participantInboxState.ts
|
|
1612
2487
|
var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
|
|
@@ -1656,8 +2531,8 @@ function describeError2(error) {
|
|
|
1656
2531
|
return error instanceof Error ? error.message : "unknown error";
|
|
1657
2532
|
}
|
|
1658
2533
|
function useParticipantInbox(api) {
|
|
1659
|
-
const [state, dispatch] =
|
|
1660
|
-
const refresh =
|
|
2534
|
+
const [state, dispatch] = useReducer6(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
|
|
2535
|
+
const refresh = useCallback7(async () => {
|
|
1661
2536
|
dispatch({ type: "started" });
|
|
1662
2537
|
try {
|
|
1663
2538
|
const page = await api.listConversations();
|
|
@@ -1667,7 +2542,7 @@ function useParticipantInbox(api) {
|
|
|
1667
2542
|
}
|
|
1668
2543
|
}, [api]);
|
|
1669
2544
|
const { nextCursor } = state;
|
|
1670
|
-
const loadMore =
|
|
2545
|
+
const loadMore = useCallback7(async () => {
|
|
1671
2546
|
if (!nextCursor) return;
|
|
1672
2547
|
try {
|
|
1673
2548
|
const page = await api.listConversations({ cursor: nextCursor });
|
|
@@ -1676,10 +2551,10 @@ function useParticipantInbox(api) {
|
|
|
1676
2551
|
dispatch({ type: "failed", error: describeError2(error) });
|
|
1677
2552
|
}
|
|
1678
2553
|
}, [api, nextCursor]);
|
|
1679
|
-
const markSubjectRead =
|
|
2554
|
+
const markSubjectRead = useCallback7((subject) => {
|
|
1680
2555
|
dispatch({ type: "markedRead", subject });
|
|
1681
2556
|
}, []);
|
|
1682
|
-
|
|
2557
|
+
useEffect12(() => {
|
|
1683
2558
|
void refresh();
|
|
1684
2559
|
}, [refresh]);
|
|
1685
2560
|
useParticipantRevalidation(api, () => {
|
|
@@ -1689,11 +2564,11 @@ function useParticipantInbox(api) {
|
|
|
1689
2564
|
}
|
|
1690
2565
|
|
|
1691
2566
|
// src/participant/ParticipantConversations.tsx
|
|
1692
|
-
import { jsx as
|
|
2567
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
1693
2568
|
function ParticipantConversations(props) {
|
|
1694
2569
|
const inbox = useParticipantInbox(props.api);
|
|
1695
2570
|
const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
|
|
1696
|
-
return /* @__PURE__ */
|
|
2571
|
+
return /* @__PURE__ */ jsx26(
|
|
1697
2572
|
ParticipantConversationsScreen,
|
|
1698
2573
|
{
|
|
1699
2574
|
...props,
|