@adatechnology/conversations-ui 0.9.0 → 0.10.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 +106 -0
- package/dist/{ConversationSimulatorPanel-q-wVgBCN.d.ts → ConversationSimulatorPanel-P6eqTSAO.d.ts} +1 -16
- package/dist/{chunk-O6457XHK.js → chunk-WCZNIJ3M.js} +16 -1
- package/dist/{chunk-L3OFAQPS.js → chunk-Y326LLEP.js} +4 -14
- package/dist/index.d.ts +10 -3
- package/dist/index.js +3 -3
- package/dist/participant/index.d.ts +51 -3
- package/dist/participant/index.js +1363 -319
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +2 -2
- package/dist/styles.css +182 -0
- package/package.json +2 -2
- package/src/AudioRecorderButton.tsx +4 -28
- package/src/audioRecorderFormat.ts +27 -0
- package/src/participant/ConversationThread.tsx +4 -0
- package/src/participant/ParticipantAttachmentItem.tsx +7 -10
- package/src/participant/ParticipantAudioAttachment.tsx +34 -0
- package/src/participant/ParticipantAudioPlayer.tsx +68 -0
- package/src/participant/ParticipantAudioPlayerControls.tsx +68 -0
- package/src/participant/ParticipantAudioRecorderView.tsx +55 -0
- package/src/participant/ParticipantAudioTranscription.tsx +90 -0
- package/src/participant/ParticipantComposer.tsx +50 -15
- package/src/participant/ParticipantComposerAudio.tsx +95 -0
- package/src/participant/ParticipantConversations.tsx +3 -0
- package/src/participant/ParticipantRecorderParts.tsx +118 -0
- package/src/participant/ParticipantThread.tsx +7 -0
- package/src/participant/ParticipantThreadScreen.tsx +1 -0
- package/src/participant/index.ts +3 -0
- package/src/participant/participantAudio.types.ts +10 -0
- package/src/participant/participantAudioCapture.test.ts +15 -0
- package/src/participant/participantAudioCapture.ts +85 -0
- package/src/participant/participantAudioCoordinator.ts +13 -0
- package/src/participant/participantAudioFormat.test.ts +93 -0
- package/src/participant/participantAudioFormat.ts +72 -0
- package/src/participant/participantAudioGolden.test.tsx +168 -0
- package/src/participant/participantAudioLabels.test.ts +22 -0
- package/src/participant/participantAudioLabels.ts +76 -0
- package/src/participant/participantAudioPlayerControlsDom.test.tsx +123 -0
- package/src/participant/participantAudioPlayerCss.test.ts +63 -0
- package/src/participant/participantAudioPlayerDom.test.tsx +154 -0
- package/src/participant/participantAudioPlayerDomKit.test-helper.tsx +84 -0
- package/src/participant/participantAudioPlayerState.ts +35 -0
- package/src/participant/participantAudioRecorderDom.test.tsx +127 -0
- package/src/participant/participantAudioRecorderInterruptionDom.test.tsx +135 -0
- package/src/participant/participantAudioRecorderSpecDom.test.tsx +110 -0
- package/src/participant/participantAudioRecorderState.test.ts +144 -0
- package/src/participant/participantAudioRecorderState.ts +93 -0
- package/src/participant/participantAudioRecorderView.test.tsx +98 -0
- package/src/participant/participantAudioSupport.ts +36 -0
- package/src/participant/participantAudioTranscriptionDom.test.tsx +198 -0
- package/src/participant/participantComposerAudioDom.test.tsx +185 -0
- package/src/participant/participantComposerAudioGolden.test.tsx +68 -0
- package/src/participant/participantComposerAudioSsr.test.tsx +74 -0
- package/src/participant/participantFileValidation.ts +1 -1
- package/src/participant/participantGolden.test.tsx +2 -0
- package/src/participant/participantGoldenScenarios.test-helper.tsx +14 -1
- package/src/participant/participantLabels.ts +4 -1
- package/src/participant/participantMediaFakes.test-helper.ts +82 -0
- package/src/participant/participantNoMicrophoneDom.test.tsx +44 -0
- package/src/participant/participantPublicTypes.test.ts +11 -0
- package/src/participant/participantRecorderDomKit.test-helper.tsx +131 -0
- package/src/participant/participantRecorderFakes.test-helper.ts +47 -0
- package/src/participant/participantSpecRecorderFake.test-helper.ts +52 -0
- package/src/participant/participantThemeContrast.test.ts +9 -0
- package/src/participant/test-fixtures/composer-audio-denied.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-idle.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-recording.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-review.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-native.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-player.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-empty.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-pending.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-ready.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-too_long.golden.html +1 -0
- package/src/participant/test-fixtures/thread-composer-microphone-idle.golden.html +1 -0
- package/src/participant/test-fixtures/thread-empty-draft.golden.html +1 -0
- package/src/participant/useParticipantAudioMediaEvents.ts +46 -0
- package/src/participant/useParticipantAudioPlayer.ts +52 -0
- package/src/participant/useParticipantAudioPlayerActions.ts +47 -0
- package/src/participant/useParticipantAudioRecorder.ts +153 -0
- package/src/participant/useParticipantAudioRecorderCore.ts +55 -0
- package/src/participant/useParticipantAudioRecorderEffects.ts +70 -0
- package/src/participant/useParticipantAudioSourceRetry.ts +42 -0
- package/src/participant/useParticipantSendAudio.test.tsx +85 -0
- package/src/participant/useParticipantSendAudio.ts +28 -0
- package/src/participant/useParticipantThreadController.ts +20 -7
- package/src/participant/useStableAttachmentUrl.ts +71 -0
- package/src/participant/useTranscriptionOverflow.ts +25 -0
- package/src/participant/useTranscriptionReadyAnnouncement.ts +24 -0
- package/src/styles.css +29 -0
|
@@ -2,15 +2,17 @@ import {
|
|
|
2
2
|
TooltipLayer
|
|
3
3
|
} from "../chunk-NSOAUYNE.js";
|
|
4
4
|
import {
|
|
5
|
+
DEFAULT_MAX_RECORDING_MILLISECONDS,
|
|
5
6
|
StatusTicks,
|
|
6
7
|
channelCapabilityFor,
|
|
7
8
|
formatFileSize,
|
|
8
9
|
formatTimestamp,
|
|
9
|
-
isSameDay
|
|
10
|
-
|
|
10
|
+
isSameDay,
|
|
11
|
+
resolveRecordingFormat
|
|
12
|
+
} from "../chunk-WCZNIJ3M.js";
|
|
11
13
|
|
|
12
14
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
13
|
-
import { useMemo as
|
|
15
|
+
import { useMemo as useMemo6, useReducer as useReducer6, useRef as useRef21, useState as useState14 } from "react";
|
|
14
16
|
|
|
15
17
|
// src/participant/participantLoadView.ts
|
|
16
18
|
function resolveLoadView({ status, hasItems }) {
|
|
@@ -21,6 +23,45 @@ function resolveLoadView({ status, hasItems }) {
|
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
// src/participant/participantAudioLabels.ts
|
|
27
|
+
var DEFAULT_PARTICIPANT_AUDIO_LABELS = {
|
|
28
|
+
recordAudio: "Record audio",
|
|
29
|
+
recordingInProgress: "Recording",
|
|
30
|
+
stopRecording: "Stop recording",
|
|
31
|
+
recordingElapsed: "{elapsed} of {limit}",
|
|
32
|
+
reviewAudio: "Listen before sending",
|
|
33
|
+
sendAudio: "Send audio",
|
|
34
|
+
discardAudio: "Discard audio",
|
|
35
|
+
microphoneDenied: "No permission to use the microphone.",
|
|
36
|
+
recordingUnsupported: "This device cannot record audio.",
|
|
37
|
+
audioEmpty: "Nothing was captured by the microphone.",
|
|
38
|
+
audioTooLarge: "Audio too large to send.",
|
|
39
|
+
audioPlayer: "Audio",
|
|
40
|
+
playAudio: "Play audio",
|
|
41
|
+
pauseAudio: "Pause audio",
|
|
42
|
+
audioPosition: "Audio position",
|
|
43
|
+
audioPositionValue: "{position} of {duration}",
|
|
44
|
+
playbackRate: "Playback speed {rate}",
|
|
45
|
+
audioUnavailable: "Audio unavailable",
|
|
46
|
+
transcribing: "Transcribing\u2026",
|
|
47
|
+
transcriptionReady: "Transcription ready",
|
|
48
|
+
machineTranscription: "Transcription generated by machine",
|
|
49
|
+
emptyTranscription: "Nothing was said in the audio",
|
|
50
|
+
transcriptionTooLong: "Transcription only for audios shorter than {minutes} minutes",
|
|
51
|
+
showFullTranscription: "Show all",
|
|
52
|
+
collapseTranscription: "Show less"
|
|
53
|
+
};
|
|
54
|
+
function resolveParticipantAudioLabels(labels) {
|
|
55
|
+
const resolved = { ...DEFAULT_PARTICIPANT_AUDIO_LABELS };
|
|
56
|
+
for (const key of Object.keys(resolved)) {
|
|
57
|
+
resolved[key] = labels?.[key] ?? DEFAULT_PARTICIPANT_AUDIO_LABELS[key];
|
|
58
|
+
}
|
|
59
|
+
return resolved;
|
|
60
|
+
}
|
|
61
|
+
function formatRecordingElapsedLabel(template, elapsed, limit) {
|
|
62
|
+
return template.replace("{elapsed}", elapsed).replace("{limit}", limit);
|
|
63
|
+
}
|
|
64
|
+
|
|
24
65
|
// src/participant/participantLabels.ts
|
|
25
66
|
var DEFAULT_MESSAGE_OPTIONS_LABEL = "Message options";
|
|
26
67
|
var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
|
|
@@ -80,7 +121,8 @@ var DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS = {
|
|
|
80
121
|
channelWebchat: "Web chat",
|
|
81
122
|
externalLinkHint: "opens in a new tab",
|
|
82
123
|
copyValue: "Copy",
|
|
83
|
-
valueCopied: "Copied"
|
|
124
|
+
valueCopied: "Copied",
|
|
125
|
+
...DEFAULT_PARTICIPANT_AUDIO_LABELS
|
|
84
126
|
};
|
|
85
127
|
function formatParticipantLabel(template, count) {
|
|
86
128
|
return template.replace("{count}", String(count));
|
|
@@ -382,7 +424,7 @@ function ParticipantInbox(props) {
|
|
|
382
424
|
}
|
|
383
425
|
|
|
384
426
|
// src/participant/ParticipantThreadScreen.tsx
|
|
385
|
-
import { useEffect as
|
|
427
|
+
import { useEffect as useEffect20, useState as useState13 } from "react";
|
|
386
428
|
|
|
387
429
|
// src/participant/ParticipantBackButton.tsx
|
|
388
430
|
import { ArrowLeft } from "lucide-react";
|
|
@@ -392,14 +434,818 @@ function ParticipantBackButton({ label, onBack }) {
|
|
|
392
434
|
}
|
|
393
435
|
|
|
394
436
|
// src/participant/ParticipantThread.tsx
|
|
395
|
-
import { useRef as
|
|
437
|
+
import { useRef as useRef13 } from "react";
|
|
396
438
|
|
|
397
439
|
// src/participant/ParticipantComposer.tsx
|
|
398
|
-
import { useState } from "react";
|
|
440
|
+
import { useState as useState3 } from "react";
|
|
441
|
+
|
|
442
|
+
// src/participant/ParticipantComposerAudio.tsx
|
|
443
|
+
import { useEffect as useEffect5, useRef as useRef6 } from "react";
|
|
444
|
+
|
|
445
|
+
// src/participant/participantFileValidation.ts
|
|
446
|
+
function matchesAcceptedType(mimeType, acceptedTypes) {
|
|
447
|
+
return acceptedTypes.some(
|
|
448
|
+
(accepted) => accepted.endsWith("/*") ? mimeType.startsWith(accepted.slice(0, -1)) : mimeType === accepted
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
function rejectionOf(file, params) {
|
|
452
|
+
if (params.acceptedTypes && !matchesAcceptedType(file.type, params.acceptedTypes)) return "typeNotAccepted";
|
|
453
|
+
if (file.size > params.maxBytes) return "tooLarge";
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
function reduceFilesSelection(params) {
|
|
457
|
+
const files = [...params.current];
|
|
458
|
+
const rejected = [];
|
|
459
|
+
for (const file of params.incoming) {
|
|
460
|
+
const reason = rejectionOf(file, params);
|
|
461
|
+
if (reason === void 0) files.push(file);
|
|
462
|
+
else rejected.push({ file, reason });
|
|
463
|
+
}
|
|
464
|
+
return { files, rejected };
|
|
465
|
+
}
|
|
466
|
+
function maxBytesForChannel(channel = "app") {
|
|
467
|
+
return channelCapabilityFor(channel).attachments.maxBytes;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/participant/participantAudio.types.ts
|
|
471
|
+
var DEFAULT_MAX_RECORDING_BYTES = 16 * 1024 * 1024;
|
|
472
|
+
|
|
473
|
+
// src/participant/participantAudioRecorderState.ts
|
|
474
|
+
var MINIMUM_RECORDING_MILLISECONDS = 500;
|
|
475
|
+
var MUTE_INTERRUPTION_DELAY_MS = 3e3;
|
|
476
|
+
var RETRYABLE = ["idle", "denied", "unsupported", "empty", "tooLarge"];
|
|
477
|
+
var TRANSITIONS = {
|
|
478
|
+
request: [[RETRYABLE, "requesting"]],
|
|
479
|
+
granted: [[["requesting"], "recording"]],
|
|
480
|
+
microphoneDenied: [[["requesting"], "denied"]],
|
|
481
|
+
microphoneUnavailable: [[["requesting"], "unsupported"]],
|
|
482
|
+
capturedReview: [[["recording"], "review"]],
|
|
483
|
+
capturedEmpty: [[["recording"], "empty"]],
|
|
484
|
+
capturedTooLarge: [[["recording"], "tooLarge"]],
|
|
485
|
+
cancel: [[["requesting", "recording", "review"], "idle"]]
|
|
486
|
+
};
|
|
487
|
+
function canStartRecording(status) {
|
|
488
|
+
return RETRYABLE.includes(status);
|
|
489
|
+
}
|
|
490
|
+
function reduceRecorderStatus(status, event) {
|
|
491
|
+
const match = TRANSITIONS[event].find(([from]) => from.includes(status));
|
|
492
|
+
return match ? match[1] : status;
|
|
493
|
+
}
|
|
494
|
+
function resolveInterruptionAction(status) {
|
|
495
|
+
if (status === "recording") return "stop";
|
|
496
|
+
if (status === "review") return "pause";
|
|
497
|
+
if (status === "requesting") return "cancel";
|
|
498
|
+
return "ignore";
|
|
499
|
+
}
|
|
500
|
+
function lowerOnly(requested, ceiling) {
|
|
501
|
+
if (requested === void 0 || !Number.isFinite(requested) || requested <= 0) return ceiling;
|
|
502
|
+
return Math.min(requested, ceiling);
|
|
503
|
+
}
|
|
504
|
+
function resolveRecordingLimits({ options, channelMaxBytes }) {
|
|
505
|
+
const sizeCeiling = lowerOnly(channelMaxBytes, DEFAULT_MAX_RECORDING_BYTES);
|
|
506
|
+
return {
|
|
507
|
+
maxDurationMs: lowerOnly(options?.maxDurationMs, DEFAULT_MAX_RECORDING_MILLISECONDS),
|
|
508
|
+
maxSizeBytes: lowerOnly(options?.maxSizeBytes, sizeCeiling)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function classifyCapture({ sizeBytes, durationMs }, limits) {
|
|
512
|
+
if (sizeBytes === 0 || durationMs < MINIMUM_RECORDING_MILLISECONDS) return "capturedEmpty";
|
|
513
|
+
if (sizeBytes > limits.maxSizeBytes) return "capturedTooLarge";
|
|
514
|
+
return "capturedReview";
|
|
515
|
+
}
|
|
516
|
+
function classifyMicrophoneError(error) {
|
|
517
|
+
const name = typeof error === "object" && error !== null && "name" in error ? error.name : void 0;
|
|
518
|
+
return name === "NotAllowedError" || name === "SecurityError" ? "microphoneDenied" : "microphoneUnavailable";
|
|
519
|
+
}
|
|
520
|
+
function formatRecordingClock(milliseconds) {
|
|
521
|
+
const totalSeconds = Math.floor(Math.max(0, milliseconds) / 1e3);
|
|
522
|
+
const seconds = String(totalSeconds % 60).padStart(2, "0");
|
|
523
|
+
return `${Math.floor(totalSeconds / 60)}:${seconds}`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/participant/ParticipantRecorderParts.tsx
|
|
527
|
+
import { Mic, Send, Square, Trash2 } from "lucide-react";
|
|
528
|
+
|
|
529
|
+
// src/participant/participantAudioFormat.ts
|
|
530
|
+
var PLAYBACK_RATES = [1, 1.5, 2];
|
|
531
|
+
var SEEK_STEP_SECONDS = 5;
|
|
532
|
+
var UNKNOWN_CLOCK = "--:--";
|
|
533
|
+
function formatAudioClock(seconds) {
|
|
534
|
+
if (seconds === void 0 || !Number.isFinite(seconds) || seconds < 0) return UNKNOWN_CLOCK;
|
|
535
|
+
const wholeSeconds = Math.floor(seconds);
|
|
536
|
+
const remainder = String(wholeSeconds % 60).padStart(2, "0");
|
|
537
|
+
return `${Math.floor(wholeSeconds / 60)}:${remainder}`;
|
|
538
|
+
}
|
|
539
|
+
function nextRateIndex(index) {
|
|
540
|
+
return (index + 1) % PLAYBACK_RATES.length;
|
|
541
|
+
}
|
|
542
|
+
function formatPlaybackRate(rate) {
|
|
543
|
+
return `${rate}\xD7`;
|
|
544
|
+
}
|
|
545
|
+
function resolveServerDurationSeconds(durationMs) {
|
|
546
|
+
if (durationMs === void 0 || !Number.isFinite(durationMs) || durationMs <= 0) return void 0;
|
|
547
|
+
return durationMs / 1e3;
|
|
548
|
+
}
|
|
549
|
+
function resolveMediaDurationSeconds(duration) {
|
|
550
|
+
return Number.isFinite(duration) && duration > 0 ? duration : void 0;
|
|
551
|
+
}
|
|
552
|
+
function clampPosition(position, duration) {
|
|
553
|
+
const floored = Math.max(0, position);
|
|
554
|
+
return duration === void 0 ? floored : Math.min(duration, floored);
|
|
555
|
+
}
|
|
556
|
+
function resolveSeekKeyTarget({ key, position, duration }) {
|
|
557
|
+
if (key === "ArrowRight" || key === "ArrowUp") return clampPosition(position + SEEK_STEP_SECONDS, duration);
|
|
558
|
+
if (key === "ArrowLeft" || key === "ArrowDown") return clampPosition(position - SEEK_STEP_SECONDS, duration);
|
|
559
|
+
if (key === "Home") return 0;
|
|
560
|
+
if (key === "End") return duration;
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
function resolveSeekMax({ duration, maxSeen, position }) {
|
|
564
|
+
if (duration !== void 0) return Math.ceil(duration);
|
|
565
|
+
return Math.ceil(Math.max(maxSeen, position));
|
|
566
|
+
}
|
|
567
|
+
function formatPositionValueText({ template, position, duration }) {
|
|
568
|
+
const positionText = formatAudioClock(position);
|
|
569
|
+
if (duration === void 0) return positionText;
|
|
570
|
+
return template.replace("{position}", positionText).replace("{duration}", formatAudioClock(duration));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/participant/ParticipantAudioPlayerControls.tsx
|
|
574
|
+
import { Pause, Play } from "lucide-react";
|
|
575
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
576
|
+
function AudioToggleButton({ label, isPlaying, isPending, isDisabled, onToggle }) {
|
|
577
|
+
return /* @__PURE__ */ jsx7(
|
|
578
|
+
"button",
|
|
579
|
+
{
|
|
580
|
+
type: "button",
|
|
581
|
+
className: "cv-p-audio__toggle",
|
|
582
|
+
"aria-label": label,
|
|
583
|
+
"data-cv-tooltip": label,
|
|
584
|
+
"aria-busy": isPending ? true : void 0,
|
|
585
|
+
disabled: isDisabled,
|
|
586
|
+
onClick: onToggle,
|
|
587
|
+
children: isPlaying ? /* @__PURE__ */ jsx7(Pause, { size: 20, "aria-hidden": "true", focusable: "false" }) : /* @__PURE__ */ jsx7(Play, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
588
|
+
}
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
function AudioSeekRange({ label, max, position, valueText, isDisabled, onSeek, onKeyDown }) {
|
|
592
|
+
return /* @__PURE__ */ jsx7(
|
|
593
|
+
"input",
|
|
594
|
+
{
|
|
595
|
+
type: "range",
|
|
596
|
+
className: "cv-p-audio__seek",
|
|
597
|
+
"aria-label": label,
|
|
598
|
+
min: 0,
|
|
599
|
+
max,
|
|
600
|
+
step: 1,
|
|
601
|
+
value: Math.min(position, max),
|
|
602
|
+
"aria-valuetext": valueText,
|
|
603
|
+
disabled: isDisabled,
|
|
604
|
+
onChange: (event) => onSeek(Number(event.currentTarget.value)),
|
|
605
|
+
onKeyDown
|
|
606
|
+
}
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
function AudioRateButton({ label, text, onClick }) {
|
|
610
|
+
return /* @__PURE__ */ jsx7("button", { type: "button", className: "cv-p-audio__rate", "aria-label": label, onClick, children: text });
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/participant/useParticipantAudioPlayer.ts
|
|
614
|
+
import { useCallback, useEffect as useEffect3, useRef as useRef3, useState } from "react";
|
|
615
|
+
|
|
616
|
+
// src/participant/participantAudioCoordinator.ts
|
|
617
|
+
var activeHandle;
|
|
618
|
+
function claimAudioPlayback(handle) {
|
|
619
|
+
if (activeHandle && activeHandle !== handle) activeHandle.pause();
|
|
620
|
+
activeHandle = handle;
|
|
621
|
+
}
|
|
622
|
+
function releaseAudioPlayback(handle) {
|
|
623
|
+
if (activeHandle === handle) activeHandle = void 0;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/participant/participantAudioPlayerState.ts
|
|
627
|
+
var INITIAL_PLAYER_STATE = {
|
|
628
|
+
isPlaying: false,
|
|
629
|
+
position: 0,
|
|
630
|
+
mediaDuration: void 0,
|
|
631
|
+
maxSeen: 0,
|
|
632
|
+
rateIndex: 0,
|
|
633
|
+
hasFailed: false
|
|
634
|
+
};
|
|
635
|
+
function applyPlaybackRate(audio, rateIndex) {
|
|
636
|
+
const rate = PLAYBACK_RATES[rateIndex] ?? 1;
|
|
637
|
+
audio.defaultPlaybackRate = rate;
|
|
638
|
+
audio.playbackRate = rate;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/participant/useParticipantAudioSourceRetry.ts
|
|
642
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
643
|
+
function useParticipantAudioSourceRetry({ context, url, onSourceError }) {
|
|
644
|
+
const { audioRef, update } = context;
|
|
645
|
+
const canRetryRef = useRef2(true);
|
|
646
|
+
const resumeAtRef = useRef2(void 0);
|
|
647
|
+
useEffect2(() => {
|
|
648
|
+
const audio = audioRef.current;
|
|
649
|
+
const resumeAt = resumeAtRef.current;
|
|
650
|
+
if (!audio || resumeAt === void 0 || !url) return;
|
|
651
|
+
resumeAtRef.current = void 0;
|
|
652
|
+
audio.currentTime = resumeAt;
|
|
653
|
+
Promise.resolve(audio.play()).catch(() => update({ isPlaying: false }));
|
|
654
|
+
}, [url, audioRef, update]);
|
|
655
|
+
function allowRetry() {
|
|
656
|
+
canRetryRef.current = true;
|
|
657
|
+
}
|
|
658
|
+
function handleError(event) {
|
|
659
|
+
if (!onSourceError || !canRetryRef.current) return update({ hasFailed: true, isPlaying: false });
|
|
660
|
+
canRetryRef.current = false;
|
|
661
|
+
resumeAtRef.current = event.currentTarget.currentTime;
|
|
662
|
+
onSourceError().then((fresh) => {
|
|
663
|
+
if (fresh && fresh !== url) return;
|
|
664
|
+
resumeAtRef.current = void 0;
|
|
665
|
+
update({ hasFailed: true, isPlaying: false });
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
return { handleError, allowRetry };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/participant/useParticipantAudioMediaEvents.ts
|
|
672
|
+
function useParticipantAudioMediaEvents({ context, url, onSourceError }) {
|
|
673
|
+
const { handleRef, state, update } = context;
|
|
674
|
+
const { handleError, allowRetry } = useParticipantAudioSourceRetry({ context, url, onSourceError });
|
|
675
|
+
function handleStop() {
|
|
676
|
+
releaseAudioPlayback(handleRef.current);
|
|
677
|
+
update({ isPlaying: false });
|
|
678
|
+
}
|
|
679
|
+
function handleDuration(event) {
|
|
680
|
+
update({ mediaDuration: resolveMediaDurationSeconds(event.currentTarget.duration) });
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
onPlay: () => {
|
|
684
|
+
claimAudioPlayback(handleRef.current);
|
|
685
|
+
update({ isPlaying: true });
|
|
686
|
+
},
|
|
687
|
+
onPause: handleStop,
|
|
688
|
+
onEnded: handleStop,
|
|
689
|
+
onTimeUpdate: (event) => {
|
|
690
|
+
const position = event.currentTarget.currentTime;
|
|
691
|
+
update({ position, maxSeen: Math.max(state.maxSeen, position) });
|
|
692
|
+
},
|
|
693
|
+
onLoadedMetadata: (event) => {
|
|
694
|
+
allowRetry();
|
|
695
|
+
handleDuration(event);
|
|
696
|
+
},
|
|
697
|
+
onDurationChange: handleDuration,
|
|
698
|
+
onError: handleError
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/participant/useParticipantAudioPlayerActions.ts
|
|
703
|
+
function useParticipantAudioPlayerActions({ context, url, duration }) {
|
|
704
|
+
const { audioRef, handleRef, state, update } = context;
|
|
705
|
+
function handleToggle() {
|
|
706
|
+
const audio = audioRef.current;
|
|
707
|
+
if (!audio || !url) return;
|
|
708
|
+
if (!audio.paused) return audio.pause();
|
|
709
|
+
claimAudioPlayback(handleRef.current);
|
|
710
|
+
applyPlaybackRate(audio, state.rateIndex);
|
|
711
|
+
Promise.resolve(audio.play()).catch(() => update({ isPlaying: false }));
|
|
712
|
+
}
|
|
713
|
+
function seekTo(seconds) {
|
|
714
|
+
const audio = audioRef.current;
|
|
715
|
+
if (!audio || !url) return;
|
|
716
|
+
audio.currentTime = seconds;
|
|
717
|
+
update({ position: seconds, maxSeen: Math.max(state.maxSeen, seconds) });
|
|
718
|
+
}
|
|
719
|
+
function handleKeyDown(event) {
|
|
720
|
+
const position = audioRef.current?.currentTime ?? state.position;
|
|
721
|
+
const target = resolveSeekKeyTarget({ key: event.key, position, duration });
|
|
722
|
+
if (target === void 0) return;
|
|
723
|
+
event.preventDefault();
|
|
724
|
+
seekTo(target);
|
|
725
|
+
}
|
|
726
|
+
function handleRateClick() {
|
|
727
|
+
const rateIndex = nextRateIndex(state.rateIndex);
|
|
728
|
+
if (audioRef.current) applyPlaybackRate(audioRef.current, rateIndex);
|
|
729
|
+
update({ rateIndex });
|
|
730
|
+
}
|
|
731
|
+
return { handleToggle, seekTo, handleKeyDown, handleRateClick };
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/participant/useParticipantAudioPlayer.ts
|
|
735
|
+
function assignRef(target, element) {
|
|
736
|
+
if (typeof target === "function") target(element);
|
|
737
|
+
else if (target) target.current = element;
|
|
738
|
+
}
|
|
739
|
+
function useParticipantAudioPlayer({ url, durationMs, onSourceError, mediaRef }) {
|
|
740
|
+
const audioRef = useRef3(null);
|
|
741
|
+
const handleRef = useRef3({ pause: () => audioRef.current?.pause() });
|
|
742
|
+
const [state, setState2] = useState(INITIAL_PLAYER_STATE);
|
|
743
|
+
const update = useCallback((patch) => setState2((previous) => ({ ...previous, ...patch })), []);
|
|
744
|
+
const mediaEvents = useParticipantAudioMediaEvents({ context: { audioRef, handleRef, state, update }, url, onSourceError });
|
|
745
|
+
const duration = resolveServerDurationSeconds(durationMs) ?? state.mediaDuration;
|
|
746
|
+
const setMedia = useCallback(
|
|
747
|
+
(element) => {
|
|
748
|
+
audioRef.current = element;
|
|
749
|
+
assignRef(mediaRef, element);
|
|
750
|
+
},
|
|
751
|
+
[mediaRef]
|
|
752
|
+
);
|
|
753
|
+
useEffect3(() => {
|
|
754
|
+
const handle = handleRef.current;
|
|
755
|
+
return () => releaseAudioPlayback(handle);
|
|
756
|
+
}, []);
|
|
757
|
+
const actions = useParticipantAudioPlayerActions({ context: { audioRef, handleRef, state, update }, url, duration });
|
|
758
|
+
return {
|
|
759
|
+
state,
|
|
760
|
+
duration,
|
|
761
|
+
rate: PLAYBACK_RATES[state.rateIndex] ?? 1,
|
|
762
|
+
mediaProps: { ...mediaEvents, ref: setMedia },
|
|
763
|
+
...actions
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// src/participant/ParticipantAudioPlayer.tsx
|
|
768
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
769
|
+
function fillRate(template, rate) {
|
|
770
|
+
return template.replace("{rate}", formatPlaybackRate(rate));
|
|
771
|
+
}
|
|
772
|
+
function ParticipantAudioPlayer({
|
|
773
|
+
labels,
|
|
774
|
+
url,
|
|
775
|
+
durationMs,
|
|
776
|
+
filename,
|
|
777
|
+
isPending = false,
|
|
778
|
+
isUnavailable = false,
|
|
779
|
+
onSourceError,
|
|
780
|
+
mediaRef
|
|
781
|
+
}) {
|
|
782
|
+
const player = useParticipantAudioPlayer({ url, durationMs, onSourceError, mediaRef });
|
|
783
|
+
const { state, duration, rate } = player;
|
|
784
|
+
const position = Math.floor(state.position);
|
|
785
|
+
const hasFailed = isUnavailable || state.hasFailed;
|
|
786
|
+
return /* @__PURE__ */ jsxs6("div", { className: "cv-p-audio", role: "group", "aria-label": filename ? `${labels.audioPlayer}: ${filename}` : labels.audioPlayer, children: [
|
|
787
|
+
/* @__PURE__ */ jsx8(
|
|
788
|
+
AudioToggleButton,
|
|
789
|
+
{
|
|
790
|
+
label: state.isPlaying ? labels.pauseAudio : labels.playAudio,
|
|
791
|
+
isPlaying: state.isPlaying,
|
|
792
|
+
isPending,
|
|
793
|
+
isDisabled: hasFailed,
|
|
794
|
+
onToggle: player.handleToggle
|
|
795
|
+
}
|
|
796
|
+
),
|
|
797
|
+
/* @__PURE__ */ jsx8(
|
|
798
|
+
AudioSeekRange,
|
|
799
|
+
{
|
|
800
|
+
label: labels.audioPosition,
|
|
801
|
+
max: resolveSeekMax({ duration, maxSeen: state.maxSeen, position }),
|
|
802
|
+
position,
|
|
803
|
+
valueText: formatPositionValueText({ template: labels.audioPositionValue, position, duration }),
|
|
804
|
+
isDisabled: hasFailed,
|
|
805
|
+
onSeek: player.seekTo,
|
|
806
|
+
onKeyDown: player.handleKeyDown
|
|
807
|
+
}
|
|
808
|
+
),
|
|
809
|
+
/* @__PURE__ */ jsx8("span", { className: "cv-p-audio__time", children: `${formatAudioClock(position)} / ${formatAudioClock(duration)}` }),
|
|
810
|
+
/* @__PURE__ */ jsx8(AudioRateButton, { label: fillRate(labels.playbackRate, rate), text: formatPlaybackRate(rate), onClick: player.handleRateClick }),
|
|
811
|
+
/* @__PURE__ */ jsx8("audio", { className: "cv-p-audio__media", preload: "none", src: url, ...player.mediaProps }),
|
|
812
|
+
hasFailed ? /* @__PURE__ */ jsx8("span", { className: "cv-p-audio__error", children: labels.audioUnavailable }) : null
|
|
813
|
+
] });
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/participant/ParticipantRecorderParts.tsx
|
|
817
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
818
|
+
function RecordAudioButton({ label, isBusy, disabled, onClick, buttonRef }) {
|
|
819
|
+
return /* @__PURE__ */ jsx9(
|
|
820
|
+
"button",
|
|
821
|
+
{
|
|
822
|
+
ref: buttonRef,
|
|
823
|
+
type: "button",
|
|
824
|
+
className: "cv-p-button cv-p-button--primary cv-p-composer__record",
|
|
825
|
+
"aria-label": label,
|
|
826
|
+
"data-cv-tooltip": label,
|
|
827
|
+
"aria-busy": isBusy ? true : void 0,
|
|
828
|
+
disabled: disabled || isBusy,
|
|
829
|
+
onClick,
|
|
830
|
+
children: /* @__PURE__ */ jsx9(Mic, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
function RecorderError({ message }) {
|
|
835
|
+
return /* @__PURE__ */ jsx9("p", { className: "cv-p-composer__error", role: "alert", children: message });
|
|
836
|
+
}
|
|
837
|
+
function focusOnMount(element) {
|
|
838
|
+
element?.focus();
|
|
839
|
+
}
|
|
840
|
+
function DiscardButton({ labels, onDiscard }) {
|
|
841
|
+
return /* @__PURE__ */ jsx9(
|
|
842
|
+
"button",
|
|
843
|
+
{
|
|
844
|
+
type: "button",
|
|
845
|
+
className: "cv-p-button cv-p-recorder__discard",
|
|
846
|
+
"aria-label": labels.discardAudio,
|
|
847
|
+
"data-cv-tooltip": labels.discardAudio,
|
|
848
|
+
onClick: onDiscard,
|
|
849
|
+
children: /* @__PURE__ */ jsx9(Trash2, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
function RecordingGroup({ labels, clockText, onStop, onDiscard }) {
|
|
854
|
+
return /* @__PURE__ */ jsxs7("div", { className: "cv-p-recorder", role: "group", "aria-label": labels.recordingInProgress, children: [
|
|
855
|
+
/* @__PURE__ */ jsx9(DiscardButton, { labels, onDiscard }),
|
|
856
|
+
/* @__PURE__ */ jsx9("span", { className: "cv-p-recorder__dot", "aria-hidden": "true" }),
|
|
857
|
+
/* @__PURE__ */ jsx9("span", { className: "cv-p-recorder__elapsed", role: "timer", children: clockText }),
|
|
858
|
+
/* @__PURE__ */ jsx9(
|
|
859
|
+
"button",
|
|
860
|
+
{
|
|
861
|
+
type: "button",
|
|
862
|
+
ref: focusOnMount,
|
|
863
|
+
className: "cv-p-button cv-p-button--primary cv-p-recorder__stop",
|
|
864
|
+
"aria-label": labels.stopRecording,
|
|
865
|
+
"data-cv-tooltip": labels.stopRecording,
|
|
866
|
+
onClick: onStop,
|
|
867
|
+
children: /* @__PURE__ */ jsx9(Square, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
868
|
+
}
|
|
869
|
+
)
|
|
870
|
+
] });
|
|
871
|
+
}
|
|
872
|
+
function ReviewGroup({ labels, url, audioRef, durationMs, onSend, onDiscard, disabled = false }) {
|
|
873
|
+
return /* @__PURE__ */ jsxs7("div", { className: "cv-p-recorder cv-p-recorder--review", role: "group", "aria-label": labels.reviewAudio, children: [
|
|
874
|
+
/* @__PURE__ */ jsx9("p", { className: "cv-p-recorder__hint", "aria-hidden": "true", children: labels.reviewAudio }),
|
|
875
|
+
/* @__PURE__ */ jsx9(DiscardButton, { labels, onDiscard }),
|
|
876
|
+
/* @__PURE__ */ jsx9(ParticipantAudioPlayer, { labels, url, durationMs, mediaRef: audioRef }),
|
|
877
|
+
/* @__PURE__ */ jsx9(
|
|
878
|
+
"button",
|
|
879
|
+
{
|
|
880
|
+
type: "button",
|
|
881
|
+
ref: focusOnMount,
|
|
882
|
+
className: "cv-p-button cv-p-button--primary cv-p-recorder__send",
|
|
883
|
+
"aria-label": labels.sendAudio,
|
|
884
|
+
"data-cv-tooltip": labels.sendAudio,
|
|
885
|
+
disabled,
|
|
886
|
+
onClick: onSend,
|
|
887
|
+
children: /* @__PURE__ */ jsx9(Send, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
888
|
+
}
|
|
889
|
+
)
|
|
890
|
+
] });
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/participant/ParticipantAudioRecorderView.tsx
|
|
894
|
+
import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
895
|
+
var ERROR_LABEL_BY_STATE = {
|
|
896
|
+
denied: "microphoneDenied",
|
|
897
|
+
unsupported: "recordingUnsupported",
|
|
898
|
+
empty: "audioEmpty",
|
|
899
|
+
tooLarge: "audioTooLarge"
|
|
900
|
+
};
|
|
901
|
+
function resolveRecorderErrorMessage(state, labels) {
|
|
902
|
+
return state in ERROR_LABEL_BY_STATE ? labels[ERROR_LABEL_BY_STATE[state]] : void 0;
|
|
903
|
+
}
|
|
904
|
+
function ParticipantAudioRecorderView(props) {
|
|
905
|
+
const { state, elapsedMs, maxDurationMs = DEFAULT_MAX_RECORDING_MILLISECONDS, review, actions, disabled = false } = props;
|
|
906
|
+
const labels = resolveParticipantAudioLabels(props.labels);
|
|
907
|
+
if (state === "recording") {
|
|
908
|
+
const clockText = formatRecordingElapsedLabel(labels.recordingElapsed, formatRecordingClock(elapsedMs), formatRecordingClock(maxDurationMs));
|
|
909
|
+
return /* @__PURE__ */ jsx10(RecordingGroup, { labels, clockText, onStop: actions?.onStop, onDiscard: actions?.onDiscard });
|
|
910
|
+
}
|
|
911
|
+
if (state === "review") {
|
|
912
|
+
return /* @__PURE__ */ jsx10(ReviewGroup, { labels, url: review?.url, audioRef: review?.audioRef, durationMs: review?.durationMs, onSend: actions?.onSend, onDiscard: actions?.onDiscard, disabled });
|
|
913
|
+
}
|
|
914
|
+
const errorMessage = resolveRecorderErrorMessage(state, labels);
|
|
915
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
916
|
+
errorMessage ? /* @__PURE__ */ jsx10(RecorderError, { message: errorMessage }) : null,
|
|
917
|
+
/* @__PURE__ */ jsx10(RecordAudioButton, { label: labels.recordAudio, isBusy: state === "requesting", disabled, onClick: actions?.onStart })
|
|
918
|
+
] });
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/participant/useParticipantAudioRecorder.ts
|
|
922
|
+
import { useCallback as useCallback3, useMemo } from "react";
|
|
923
|
+
|
|
924
|
+
// src/participant/participantAudioCapture.ts
|
|
925
|
+
function stopStream(stream) {
|
|
926
|
+
for (const track of stream.getTracks()) track.stop();
|
|
927
|
+
}
|
|
928
|
+
function formatRecordedFileName(moment, extension) {
|
|
929
|
+
const iso = moment.toISOString();
|
|
930
|
+
const day = iso.slice(0, 10).replace(/-/g, "");
|
|
931
|
+
const time = iso.slice(11, 19).replace(/:/g, "");
|
|
932
|
+
return `audio-${day}-${time}.${extension}`;
|
|
933
|
+
}
|
|
934
|
+
function buildRecordedFile(blob, format, moment = /* @__PURE__ */ new Date()) {
|
|
935
|
+
return new File([blob], formatRecordedFileName(moment, format.extension), { type: format.uploadMimeType });
|
|
936
|
+
}
|
|
937
|
+
function createMuteWatch(onPersistentMute) {
|
|
938
|
+
let timer;
|
|
939
|
+
const clear = () => {
|
|
940
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
941
|
+
timer = void 0;
|
|
942
|
+
};
|
|
943
|
+
const arm = () => {
|
|
944
|
+
clear();
|
|
945
|
+
timer = setTimeout(onPersistentMute, MUTE_INTERRUPTION_DELAY_MS);
|
|
946
|
+
};
|
|
947
|
+
return { arm, clear };
|
|
948
|
+
}
|
|
949
|
+
function openCaptureSession({ stream, format, onInterrupt, onStopped }) {
|
|
950
|
+
const recorder = new MediaRecorder(stream, { mimeType: format.mimeType });
|
|
951
|
+
const chunks = [];
|
|
952
|
+
const startedAt = Date.now();
|
|
953
|
+
const muteWatch = createMuteWatch(onInterrupt);
|
|
954
|
+
for (const track of stream.getTracks()) {
|
|
955
|
+
track.onended = onInterrupt;
|
|
956
|
+
track.onmute = muteWatch.arm;
|
|
957
|
+
track.onunmute = muteWatch.clear;
|
|
958
|
+
}
|
|
959
|
+
recorder.ondataavailable = (event) => {
|
|
960
|
+
if (event.data.size > 0) chunks.push(event.data);
|
|
961
|
+
};
|
|
962
|
+
recorder.onerror = onInterrupt;
|
|
963
|
+
recorder.onstop = () => onStopped(new Blob(chunks, { type: format.uploadMimeType }), Date.now() - startedAt);
|
|
964
|
+
recorder.start();
|
|
965
|
+
let isStopRequested = false;
|
|
966
|
+
function requestStop() {
|
|
967
|
+
if (isStopRequested || recorder.state === "inactive") return;
|
|
968
|
+
isStopRequested = true;
|
|
969
|
+
recorder.stop();
|
|
970
|
+
}
|
|
971
|
+
function release() {
|
|
972
|
+
muteWatch.clear();
|
|
973
|
+
recorder.ondataavailable = null;
|
|
974
|
+
recorder.onerror = null;
|
|
975
|
+
recorder.onstop = null;
|
|
976
|
+
for (const track of stream.getTracks()) {
|
|
977
|
+
track.onended = null;
|
|
978
|
+
track.onmute = null;
|
|
979
|
+
track.onunmute = null;
|
|
980
|
+
}
|
|
981
|
+
stopStream(stream);
|
|
982
|
+
}
|
|
983
|
+
return { format, startedAt, requestStop, release };
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/participant/useParticipantAudioRecorderCore.ts
|
|
987
|
+
import { useCallback as useCallback2, useRef as useRef4, useState as useState2 } from "react";
|
|
988
|
+
function useRecorderCore(limits) {
|
|
989
|
+
const [status, setStatus] = useState2("idle");
|
|
990
|
+
const [elapsedMs, setElapsedMs] = useState2(0);
|
|
991
|
+
const [recorded, setRecorded] = useState2();
|
|
992
|
+
const statusRef = useRef4("idle");
|
|
993
|
+
const limitsRef = useRef4(limits);
|
|
994
|
+
const tokenRef = useRef4(0);
|
|
995
|
+
const sessionRef = useRef4(void 0);
|
|
996
|
+
const recordedRef = useRef4(void 0);
|
|
997
|
+
const audioRef = useRef4(null);
|
|
998
|
+
limitsRef.current = limits;
|
|
999
|
+
const moveTo = useCallback2((event) => {
|
|
1000
|
+
statusRef.current = reduceRecorderStatus(statusRef.current, event);
|
|
1001
|
+
setStatus(statusRef.current);
|
|
1002
|
+
}, []);
|
|
1003
|
+
const releaseSession = useCallback2(() => {
|
|
1004
|
+
sessionRef.current?.release();
|
|
1005
|
+
sessionRef.current = void 0;
|
|
1006
|
+
}, []);
|
|
1007
|
+
const releaseRecorded = useCallback2(() => {
|
|
1008
|
+
if (recordedRef.current) URL.revokeObjectURL(recordedRef.current.objectUrl);
|
|
1009
|
+
recordedRef.current = void 0;
|
|
1010
|
+
setRecorded(void 0);
|
|
1011
|
+
}, []);
|
|
1012
|
+
return {
|
|
1013
|
+
status,
|
|
1014
|
+
elapsedMs,
|
|
1015
|
+
setElapsedMs,
|
|
1016
|
+
recorded,
|
|
1017
|
+
setRecorded,
|
|
1018
|
+
limitsRef,
|
|
1019
|
+
statusRef,
|
|
1020
|
+
tokenRef,
|
|
1021
|
+
sessionRef,
|
|
1022
|
+
recordedRef,
|
|
1023
|
+
audioRef,
|
|
1024
|
+
moveTo,
|
|
1025
|
+
releaseSession,
|
|
1026
|
+
releaseRecorded
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/participant/useParticipantAudioRecorderEffects.ts
|
|
1031
|
+
import { useEffect as useEffect4, useRef as useRef5 } from "react";
|
|
1032
|
+
var ELAPSED_TICK_MS = 250;
|
|
1033
|
+
function useRecorderInterruptionListeners(onInterruption) {
|
|
1034
|
+
useEffect4(() => {
|
|
1035
|
+
const handleVisibilityChange = () => {
|
|
1036
|
+
if (document.visibilityState === "hidden") onInterruption();
|
|
1037
|
+
};
|
|
1038
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1039
|
+
window.addEventListener("pagehide", onInterruption);
|
|
1040
|
+
return () => {
|
|
1041
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1042
|
+
window.removeEventListener("pagehide", onInterruption);
|
|
1043
|
+
};
|
|
1044
|
+
}, [onInterruption]);
|
|
1045
|
+
}
|
|
1046
|
+
function useRecordingClock({ status, maxDurationMs, sessionRef, setElapsedMs, stop }) {
|
|
1047
|
+
useEffect4(() => {
|
|
1048
|
+
if (status !== "recording") return void 0;
|
|
1049
|
+
const startedAt = sessionRef.current?.startedAt ?? Date.now();
|
|
1050
|
+
const tick = setInterval(() => setElapsedMs(Date.now() - startedAt), ELAPSED_TICK_MS);
|
|
1051
|
+
const ceiling = setTimeout(stop, maxDurationMs);
|
|
1052
|
+
return () => {
|
|
1053
|
+
clearInterval(tick);
|
|
1054
|
+
clearTimeout(ceiling);
|
|
1055
|
+
};
|
|
1056
|
+
}, [status, maxDurationMs, sessionRef, setElapsedMs, stop]);
|
|
1057
|
+
}
|
|
1058
|
+
function useRecorderLifecycle({ status, onCaptureChange, teardown }) {
|
|
1059
|
+
const isCapturing = status === "requesting" || status === "recording" || status === "review";
|
|
1060
|
+
const reportedRef = useRef5(false);
|
|
1061
|
+
const onCaptureChangeRef = useRef5(onCaptureChange);
|
|
1062
|
+
onCaptureChangeRef.current = onCaptureChange;
|
|
1063
|
+
useEffect4(() => {
|
|
1064
|
+
if (reportedRef.current === isCapturing) return;
|
|
1065
|
+
reportedRef.current = isCapturing;
|
|
1066
|
+
onCaptureChangeRef.current?.(isCapturing);
|
|
1067
|
+
}, [isCapturing]);
|
|
1068
|
+
useEffect4(
|
|
1069
|
+
() => () => {
|
|
1070
|
+
teardown();
|
|
1071
|
+
if (!reportedRef.current) return;
|
|
1072
|
+
reportedRef.current = false;
|
|
1073
|
+
onCaptureChangeRef.current?.(false);
|
|
1074
|
+
},
|
|
1075
|
+
[teardown]
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/participant/useParticipantAudioRecorder.ts
|
|
1080
|
+
function useRecorderFinalize(core) {
|
|
1081
|
+
const { statusRef, sessionRef, recordedRef, limitsRef, setRecorded, moveTo, releaseSession } = core;
|
|
1082
|
+
return useCallback3(
|
|
1083
|
+
(blob, durationMs) => {
|
|
1084
|
+
const format = sessionRef.current?.format;
|
|
1085
|
+
if (statusRef.current !== "recording" || !format) return;
|
|
1086
|
+
releaseSession();
|
|
1087
|
+
const event = classifyCapture({ sizeBytes: blob.size, durationMs }, limitsRef.current);
|
|
1088
|
+
if (event === "capturedReview") {
|
|
1089
|
+
const file = buildRecordedFile(blob, format);
|
|
1090
|
+
recordedRef.current = { file, objectUrl: URL.createObjectURL(file), durationMs };
|
|
1091
|
+
setRecorded(recordedRef.current);
|
|
1092
|
+
}
|
|
1093
|
+
moveTo(event);
|
|
1094
|
+
},
|
|
1095
|
+
[statusRef, sessionRef, recordedRef, limitsRef, setRecorded, moveTo, releaseSession]
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
function useRecorderInterruption(core) {
|
|
1099
|
+
const { statusRef, sessionRef, tokenRef, audioRef, moveTo } = core;
|
|
1100
|
+
return useCallback3(() => {
|
|
1101
|
+
const action = resolveInterruptionAction(statusRef.current);
|
|
1102
|
+
if (action === "stop") sessionRef.current?.requestStop();
|
|
1103
|
+
if (action === "pause") audioRef.current?.pause();
|
|
1104
|
+
if (action === "cancel") {
|
|
1105
|
+
tokenRef.current += 1;
|
|
1106
|
+
moveTo("cancel");
|
|
1107
|
+
}
|
|
1108
|
+
}, [statusRef, sessionRef, tokenRef, audioRef, moveTo]);
|
|
1109
|
+
}
|
|
1110
|
+
function useRecorderStart(core, { onInterrupt, onStopped }) {
|
|
1111
|
+
const { statusRef, tokenRef, sessionRef, setElapsedMs, moveTo } = core;
|
|
1112
|
+
const beginRecording = useCallback3(
|
|
1113
|
+
(stream, format) => {
|
|
1114
|
+
try {
|
|
1115
|
+
sessionRef.current = openCaptureSession({ stream, format, onInterrupt, onStopped });
|
|
1116
|
+
} catch {
|
|
1117
|
+
stopStream(stream);
|
|
1118
|
+
moveTo("microphoneUnavailable");
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
setElapsedMs(0);
|
|
1122
|
+
moveTo("granted");
|
|
1123
|
+
},
|
|
1124
|
+
[sessionRef, setElapsedMs, onInterrupt, onStopped, moveTo]
|
|
1125
|
+
);
|
|
1126
|
+
return useCallback3(async () => {
|
|
1127
|
+
if (!canStartRecording(statusRef.current)) return;
|
|
1128
|
+
const token = tokenRef.current += 1;
|
|
1129
|
+
moveTo("request");
|
|
1130
|
+
const format = resolveRecordingFormat();
|
|
1131
|
+
if (!format || !navigator.mediaDevices?.getUserMedia) return moveTo("microphoneUnavailable");
|
|
1132
|
+
let stream;
|
|
1133
|
+
try {
|
|
1134
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
if (token === tokenRef.current) moveTo(classifyMicrophoneError(error));
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if (token !== tokenRef.current) return stopStream(stream);
|
|
1140
|
+
beginRecording(stream, format);
|
|
1141
|
+
}, [statusRef, tokenRef, moveTo, beginRecording]);
|
|
1142
|
+
}
|
|
1143
|
+
function useRecorderActions(core) {
|
|
1144
|
+
const { statusRef, tokenRef, sessionRef, recordedRef, moveTo, releaseSession, releaseRecorded } = core;
|
|
1145
|
+
const stop = useCallback3(() => sessionRef.current?.requestStop(), [sessionRef]);
|
|
1146
|
+
const discard = useCallback3(() => {
|
|
1147
|
+
tokenRef.current += 1;
|
|
1148
|
+
releaseSession();
|
|
1149
|
+
releaseRecorded();
|
|
1150
|
+
moveTo("cancel");
|
|
1151
|
+
}, [tokenRef, moveTo, releaseRecorded, releaseSession]);
|
|
1152
|
+
const confirm = useCallback3(() => {
|
|
1153
|
+
if (statusRef.current !== "review") return void 0;
|
|
1154
|
+
const file = recordedRef.current?.file;
|
|
1155
|
+
releaseRecorded();
|
|
1156
|
+
moveTo("cancel");
|
|
1157
|
+
return file;
|
|
1158
|
+
}, [statusRef, recordedRef, moveTo, releaseRecorded]);
|
|
1159
|
+
const teardown = useCallback3(() => {
|
|
1160
|
+
tokenRef.current += 1;
|
|
1161
|
+
releaseSession();
|
|
1162
|
+
if (recordedRef.current) URL.revokeObjectURL(recordedRef.current.objectUrl);
|
|
1163
|
+
recordedRef.current = void 0;
|
|
1164
|
+
}, [tokenRef, recordedRef, releaseSession]);
|
|
1165
|
+
return { stop, discard, confirm, teardown };
|
|
1166
|
+
}
|
|
1167
|
+
function useParticipantAudioRecorder({ options, channelMaxBytes } = {}) {
|
|
1168
|
+
const limits = useMemo(
|
|
1169
|
+
() => resolveRecordingLimits({ options, channelMaxBytes }),
|
|
1170
|
+
[options?.maxDurationMs, options?.maxSizeBytes, channelMaxBytes]
|
|
1171
|
+
);
|
|
1172
|
+
const core = useRecorderCore(limits);
|
|
1173
|
+
const onInterrupt = useRecorderInterruption(core);
|
|
1174
|
+
const onStopped = useRecorderFinalize(core);
|
|
1175
|
+
const start = useRecorderStart(core, { onInterrupt, onStopped });
|
|
1176
|
+
const { stop, discard, confirm, teardown } = useRecorderActions(core);
|
|
1177
|
+
useRecorderInterruptionListeners(onInterrupt);
|
|
1178
|
+
useRecordingClock({ status: core.status, maxDurationMs: limits.maxDurationMs, sessionRef: core.sessionRef, setElapsedMs: core.setElapsedMs, stop });
|
|
1179
|
+
useRecorderLifecycle({ status: core.status, onCaptureChange: options?.onCaptureChange, teardown });
|
|
1180
|
+
const { status, elapsedMs, recorded, audioRef } = core;
|
|
1181
|
+
return { status, elapsedMs, limits, recorded, audioRef, start, stop, discard, confirm };
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// src/participant/ParticipantComposerAudio.tsx
|
|
1185
|
+
import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1186
|
+
function isCapturingStatus(status) {
|
|
1187
|
+
return status === "recording" || status === "review";
|
|
1188
|
+
}
|
|
1189
|
+
function useRestoreMicrophoneFocus(status) {
|
|
1190
|
+
const microphoneRef = useRef6(null);
|
|
1191
|
+
const wasCapturingRef = useRef6(false);
|
|
1192
|
+
useEffect5(() => {
|
|
1193
|
+
const isCapturing = isCapturingStatus(status);
|
|
1194
|
+
if (wasCapturingRef.current && !isCapturing) microphoneRef.current?.focus();
|
|
1195
|
+
wasCapturingRef.current = isCapturing;
|
|
1196
|
+
}, [status]);
|
|
1197
|
+
return microphoneRef;
|
|
1198
|
+
}
|
|
1199
|
+
function RecorderLine({ recorder, labels, onSendAudio, disabled }) {
|
|
1200
|
+
function handleSendAudio() {
|
|
1201
|
+
const file = recorder.confirm();
|
|
1202
|
+
if (file) onSendAudio(file);
|
|
1203
|
+
}
|
|
1204
|
+
return /* @__PURE__ */ jsx11(
|
|
1205
|
+
ParticipantAudioRecorderView,
|
|
1206
|
+
{
|
|
1207
|
+
state: recorder.status,
|
|
1208
|
+
elapsedMs: recorder.elapsedMs,
|
|
1209
|
+
labels,
|
|
1210
|
+
disabled,
|
|
1211
|
+
maxDurationMs: recorder.limits.maxDurationMs,
|
|
1212
|
+
review: { url: recorder.recorded?.objectUrl, audioRef: recorder.audioRef, durationMs: recorder.recorded?.durationMs },
|
|
1213
|
+
actions: { onStop: recorder.stop, onSend: handleSendAudio, onDiscard: recorder.discard }
|
|
1214
|
+
}
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
function ComposerAudioRow({ settings, labels, disabled, isDraftEmpty, sendButton, children }) {
|
|
1218
|
+
const recorder = useParticipantAudioRecorder({
|
|
1219
|
+
options: settings.audioRecording,
|
|
1220
|
+
channelMaxBytes: maxBytesForChannel(settings.channel)
|
|
1221
|
+
});
|
|
1222
|
+
const microphoneRef = useRestoreMicrophoneFocus(recorder.status);
|
|
1223
|
+
const audioLabels = resolveParticipantAudioLabels(labels);
|
|
1224
|
+
const errorMessage = resolveRecorderErrorMessage(recorder.status, audioLabels);
|
|
1225
|
+
if (isCapturingStatus(recorder.status)) {
|
|
1226
|
+
return /* @__PURE__ */ jsx11(RecorderLine, { recorder, labels, onSendAudio: settings.onSendAudio, disabled });
|
|
1227
|
+
}
|
|
1228
|
+
return /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1229
|
+
errorMessage ? /* @__PURE__ */ jsx11(RecorderError, { message: errorMessage }) : null,
|
|
1230
|
+
/* @__PURE__ */ jsxs9("div", { className: "cv-p-composer__row", children: [
|
|
1231
|
+
children,
|
|
1232
|
+
isDraftEmpty ? /* @__PURE__ */ jsx11(
|
|
1233
|
+
RecordAudioButton,
|
|
1234
|
+
{
|
|
1235
|
+
label: audioLabels.recordAudio,
|
|
1236
|
+
isBusy: recorder.status === "requesting",
|
|
1237
|
+
disabled,
|
|
1238
|
+
onClick: () => void recorder.start(),
|
|
1239
|
+
buttonRef: microphoneRef
|
|
1240
|
+
}
|
|
1241
|
+
) : sendButton
|
|
1242
|
+
] })
|
|
1243
|
+
] });
|
|
1244
|
+
}
|
|
399
1245
|
|
|
400
1246
|
// src/participant/ParticipantComposerControls.tsx
|
|
401
|
-
import { useLayoutEffect, useRef as
|
|
402
|
-
import { Paperclip, Send } from "lucide-react";
|
|
1247
|
+
import { useLayoutEffect, useRef as useRef7 } from "react";
|
|
1248
|
+
import { Paperclip, Send as Send2 } from "lucide-react";
|
|
403
1249
|
|
|
404
1250
|
// src/participant/participantAutoGrow.ts
|
|
405
1251
|
function fitTextareaHeight(target) {
|
|
@@ -408,11 +1254,11 @@ function fitTextareaHeight(target) {
|
|
|
408
1254
|
}
|
|
409
1255
|
|
|
410
1256
|
// src/participant/ParticipantComposerControls.tsx
|
|
411
|
-
import { Fragment as
|
|
1257
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
412
1258
|
function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
|
|
413
|
-
const fileInputRef =
|
|
414
|
-
return /* @__PURE__ */
|
|
415
|
-
/* @__PURE__ */
|
|
1259
|
+
const fileInputRef = useRef7(null);
|
|
1260
|
+
return /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
1261
|
+
/* @__PURE__ */ jsx12(
|
|
416
1262
|
"input",
|
|
417
1263
|
{
|
|
418
1264
|
ref: fileInputRef,
|
|
@@ -424,7 +1270,7 @@ function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
|
|
|
424
1270
|
onChange: onChosen
|
|
425
1271
|
}
|
|
426
1272
|
),
|
|
427
|
-
/* @__PURE__ */
|
|
1273
|
+
/* @__PURE__ */ jsx12(
|
|
428
1274
|
"button",
|
|
429
1275
|
{
|
|
430
1276
|
type: "button",
|
|
@@ -433,13 +1279,13 @@ function AttachControl({ labels, disabled, acceptedTypes, onChosen }) {
|
|
|
433
1279
|
"data-cv-tooltip": labels.attach,
|
|
434
1280
|
disabled,
|
|
435
1281
|
onClick: () => fileInputRef.current?.click(),
|
|
436
|
-
children: /* @__PURE__ */
|
|
1282
|
+
children: /* @__PURE__ */ jsx12(Paperclip, { size: 24, "aria-hidden": "true", focusable: "false" })
|
|
437
1283
|
}
|
|
438
1284
|
)
|
|
439
1285
|
] });
|
|
440
1286
|
}
|
|
441
1287
|
function SendButton({ label, disabled }) {
|
|
442
|
-
return /* @__PURE__ */
|
|
1288
|
+
return /* @__PURE__ */ jsx12(
|
|
443
1289
|
"button",
|
|
444
1290
|
{
|
|
445
1291
|
type: "submit",
|
|
@@ -447,16 +1293,16 @@ function SendButton({ label, disabled }) {
|
|
|
447
1293
|
"aria-label": label,
|
|
448
1294
|
"data-cv-tooltip": label,
|
|
449
1295
|
disabled,
|
|
450
|
-
children: /* @__PURE__ */
|
|
1296
|
+
children: /* @__PURE__ */ jsx12(Send2, { size: 20, "aria-hidden": "true", focusable: "false" })
|
|
451
1297
|
}
|
|
452
1298
|
);
|
|
453
1299
|
}
|
|
454
1300
|
function MessageField({ value, label, placeholder, disabled, maxLength, onChange }) {
|
|
455
|
-
const fieldRef =
|
|
1301
|
+
const fieldRef = useRef7(null);
|
|
456
1302
|
useLayoutEffect(() => {
|
|
457
1303
|
if (fieldRef.current) fitTextareaHeight(fieldRef.current);
|
|
458
1304
|
}, [value]);
|
|
459
|
-
return /* @__PURE__ */
|
|
1305
|
+
return /* @__PURE__ */ jsx12(
|
|
460
1306
|
"textarea",
|
|
461
1307
|
{
|
|
462
1308
|
ref: fieldRef,
|
|
@@ -472,29 +1318,20 @@ function MessageField({ value, label, placeholder, disabled, maxLength, onChange
|
|
|
472
1318
|
);
|
|
473
1319
|
}
|
|
474
1320
|
|
|
475
|
-
// src/participant/
|
|
476
|
-
function
|
|
477
|
-
return
|
|
478
|
-
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
function rejectionOf(file, params) {
|
|
482
|
-
if (params.acceptedTypes && !matchesAcceptedType(file.type, params.acceptedTypes)) return "typeNotAccepted";
|
|
483
|
-
if (file.size > params.maxBytes) return "tooLarge";
|
|
484
|
-
return void 0;
|
|
485
|
-
}
|
|
486
|
-
function reduceFilesSelection(params) {
|
|
487
|
-
const files = [...params.current];
|
|
488
|
-
const rejected = [];
|
|
489
|
-
for (const file of params.incoming) {
|
|
490
|
-
const reason = rejectionOf(file, params);
|
|
491
|
-
if (reason === void 0) files.push(file);
|
|
492
|
-
else rejected.push({ file, reason });
|
|
493
|
-
}
|
|
494
|
-
return { files, rejected };
|
|
1321
|
+
// src/participant/participantAudioSupport.ts
|
|
1322
|
+
function resolveBrowserRecordingFormat() {
|
|
1323
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) return void 0;
|
|
1324
|
+
return resolveRecordingFormat();
|
|
495
1325
|
}
|
|
496
|
-
function
|
|
497
|
-
|
|
1326
|
+
function resolveComposerAudio(params) {
|
|
1327
|
+
const { audioRecording, onSendAudio, channel = "app", acceptedTypes } = params;
|
|
1328
|
+
if (!audioRecording || !onSendAudio) return void 0;
|
|
1329
|
+
const capability = channelCapabilityFor(channel);
|
|
1330
|
+
if (!capability.audio.records || !capability.attachments.accepted) return void 0;
|
|
1331
|
+
const format = resolveBrowserRecordingFormat();
|
|
1332
|
+
if (!format) return void 0;
|
|
1333
|
+
if (acceptedTypes && !matchesAcceptedType(format.uploadMimeType, acceptedTypes)) return void 0;
|
|
1334
|
+
return { audioRecording, channel, onSendAudio };
|
|
498
1335
|
}
|
|
499
1336
|
|
|
500
1337
|
// src/participant/participantQuickReply.ts
|
|
@@ -505,18 +1342,18 @@ function applyQuickReply(currentText, replyText) {
|
|
|
505
1342
|
}
|
|
506
1343
|
|
|
507
1344
|
// src/participant/ParticipantComposer.tsx
|
|
508
|
-
import { jsx as
|
|
1345
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
509
1346
|
function QuickReplyStrip({ quickReplies, labels, onPick }) {
|
|
510
|
-
return /* @__PURE__ */
|
|
1347
|
+
return /* @__PURE__ */ jsx13("div", { className: "cv-p-quick", role: "group", "aria-label": labels.quickRepliesGroup, children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx13("button", { type: "button", className: "cv-p-chip", onClick: () => onPick(quickReply.body), children: quickReply.title }, quickReply.id)) });
|
|
511
1348
|
}
|
|
512
1349
|
function FileList({ files, labels, onRemove }) {
|
|
513
1350
|
if (files.length === 0) return null;
|
|
514
|
-
return /* @__PURE__ */
|
|
515
|
-
/* @__PURE__ */
|
|
516
|
-
/* @__PURE__ */
|
|
517
|
-
/* @__PURE__ */
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
/* @__PURE__ */
|
|
1351
|
+
return /* @__PURE__ */ jsx13("ul", { className: "cv-p-files", "aria-label": labels.attachmentsList, children: files.map((file, index) => /* @__PURE__ */ jsxs11("li", { className: "cv-p-files__item", children: [
|
|
1352
|
+
/* @__PURE__ */ jsx13("span", { className: "cv-p-files__name", children: file.name }),
|
|
1353
|
+
/* @__PURE__ */ jsx13("span", { className: "cv-p-files__size", children: formatFileSize(file.size) }),
|
|
1354
|
+
/* @__PURE__ */ jsxs11("button", { type: "button", className: "cv-p-files__remove", "aria-label": `${labels.removeAttachment}: ${file.name}`, "data-cv-tooltip": labels.removeAttachment, onClick: () => onRemove(file), children: [
|
|
1355
|
+
/* @__PURE__ */ jsx13("span", { "aria-hidden": "true", children: "\xD7" }),
|
|
1356
|
+
/* @__PURE__ */ jsx13("span", { className: "cv-p-sr-only", children: labels.removeAttachment })
|
|
520
1357
|
] })
|
|
521
1358
|
] }, `${file.name}-${file.size}-${index}`)) });
|
|
522
1359
|
}
|
|
@@ -524,7 +1361,7 @@ function rejectionText(rejection, labels) {
|
|
|
524
1361
|
return rejection === "tooLarge" ? labels.attachmentTooLarge : labels.attachmentTypeNotAccepted;
|
|
525
1362
|
}
|
|
526
1363
|
function useFileSelection(props) {
|
|
527
|
-
const [rejection, setRejection] =
|
|
1364
|
+
const [rejection, setRejection] = useState3(void 0);
|
|
528
1365
|
const { files, onFilesChange, channel } = props;
|
|
529
1366
|
const maxBytes = maxBytesForChannel(channel);
|
|
530
1367
|
function handleFilesChosen(event) {
|
|
@@ -540,40 +1377,55 @@ function useFileSelection(props) {
|
|
|
540
1377
|
}
|
|
541
1378
|
return { rejection, handleFilesChosen, handleRemoveFile };
|
|
542
1379
|
}
|
|
1380
|
+
function ComposerRow({ composer, canSend, onFilesChosen }) {
|
|
1381
|
+
const { value, onChange, files, labels, channel = "app", disabled = false } = composer;
|
|
1382
|
+
const attach = channelCapabilityFor(channel).attachments.accepted ? /* @__PURE__ */ jsx13(AttachControl, { labels, disabled, acceptedTypes: composer.acceptedTypes, onChosen: onFilesChosen }) : null;
|
|
1383
|
+
const field = /* @__PURE__ */ jsx13(
|
|
1384
|
+
MessageField,
|
|
1385
|
+
{
|
|
1386
|
+
value,
|
|
1387
|
+
label: labels.messageInputLabel,
|
|
1388
|
+
placeholder: labels.messageInputPlaceholder,
|
|
1389
|
+
disabled,
|
|
1390
|
+
maxLength: composer.maxLength,
|
|
1391
|
+
onChange
|
|
1392
|
+
}
|
|
1393
|
+
);
|
|
1394
|
+
const sendButton = /* @__PURE__ */ jsx13(SendButton, { label: labels.send, disabled: !canSend });
|
|
1395
|
+
const audio = resolveComposerAudio(composer);
|
|
1396
|
+
if (!audio) {
|
|
1397
|
+
return /* @__PURE__ */ jsxs11("div", { className: "cv-p-composer__row", children: [
|
|
1398
|
+
attach,
|
|
1399
|
+
field,
|
|
1400
|
+
sendButton
|
|
1401
|
+
] });
|
|
1402
|
+
}
|
|
1403
|
+
const isDraftEmpty = value.trim().length === 0 && files.length === 0;
|
|
1404
|
+
return /* @__PURE__ */ jsxs11(ComposerAudioRow, { settings: audio, labels, disabled, isDraftEmpty, sendButton, children: [
|
|
1405
|
+
attach,
|
|
1406
|
+
field
|
|
1407
|
+
] });
|
|
1408
|
+
}
|
|
543
1409
|
function ParticipantComposer(props) {
|
|
544
|
-
const { value, onChange, files, onSend, labels,
|
|
1410
|
+
const { value, onChange, files, onSend, labels, disabled = false, isSending = false } = props;
|
|
545
1411
|
const { rejection, handleFilesChosen, handleRemoveFile } = useFileSelection(props);
|
|
546
1412
|
const canSend = !disabled && !isSending && (value.trim().length > 0 || files.length > 0);
|
|
547
1413
|
function handleSubmit(event) {
|
|
548
1414
|
event.preventDefault();
|
|
549
1415
|
if (canSend) onSend();
|
|
550
1416
|
}
|
|
551
|
-
return /* @__PURE__ */
|
|
552
|
-
props.quickReplies && props.quickReplies.length > 0 ? /* @__PURE__ */
|
|
553
|
-
/* @__PURE__ */
|
|
554
|
-
rejection ? /* @__PURE__ */
|
|
555
|
-
/* @__PURE__ */
|
|
556
|
-
channelCapabilityFor(channel).attachments.accepted ? /* @__PURE__ */ jsx8(AttachControl, { labels, disabled, acceptedTypes: props.acceptedTypes, onChosen: handleFilesChosen }) : null,
|
|
557
|
-
/* @__PURE__ */ jsx8(
|
|
558
|
-
MessageField,
|
|
559
|
-
{
|
|
560
|
-
value,
|
|
561
|
-
label: labels.messageInputLabel,
|
|
562
|
-
placeholder: labels.messageInputPlaceholder,
|
|
563
|
-
disabled,
|
|
564
|
-
maxLength: props.maxLength,
|
|
565
|
-
onChange
|
|
566
|
-
}
|
|
567
|
-
),
|
|
568
|
-
/* @__PURE__ */ jsx8(SendButton, { label: labels.send, disabled: !canSend })
|
|
569
|
-
] })
|
|
1417
|
+
return /* @__PURE__ */ jsxs11("form", { className: "cv-p-composer", onSubmit: handleSubmit, children: [
|
|
1418
|
+
props.quickReplies && props.quickReplies.length > 0 ? /* @__PURE__ */ jsx13(QuickReplyStrip, { quickReplies: props.quickReplies, labels, onPick: (body) => onChange(applyQuickReply(value, body)) }) : null,
|
|
1419
|
+
/* @__PURE__ */ jsx13(FileList, { files, labels, onRemove: handleRemoveFile }),
|
|
1420
|
+
rejection ? /* @__PURE__ */ jsx13("p", { className: "cv-p-composer__error", role: "alert", children: rejectionText(rejection, labels) }) : null,
|
|
1421
|
+
/* @__PURE__ */ jsx13(ComposerRow, { composer: props, canSend, onFilesChosen: handleFilesChosen })
|
|
570
1422
|
] });
|
|
571
1423
|
}
|
|
572
1424
|
|
|
573
1425
|
// src/participant/useSuppressTouchTooltips.ts
|
|
574
|
-
import { useEffect as
|
|
1426
|
+
import { useEffect as useEffect6 } from "react";
|
|
575
1427
|
function useSuppressTouchTooltips(rootRef) {
|
|
576
|
-
|
|
1428
|
+
useEffect6(() => {
|
|
577
1429
|
const root = rootRef.current;
|
|
578
1430
|
if (!root) return void 0;
|
|
579
1431
|
let isTouching = false;
|
|
@@ -603,7 +1455,7 @@ function useSuppressTouchTooltips(rootRef) {
|
|
|
603
1455
|
}
|
|
604
1456
|
|
|
605
1457
|
// src/participant/ParticipantCopyAnnouncer.tsx
|
|
606
|
-
import { createContext, useCallback, useContext, useEffect as
|
|
1458
|
+
import { createContext, useCallback as useCallback4, useContext, useEffect as useEffect7, useReducer, useRef as useRef8 } from "react";
|
|
607
1459
|
|
|
608
1460
|
// src/participant/participantCopyAnnouncement.ts
|
|
609
1461
|
var INITIAL_COPY_ANNOUNCEMENT = { text: "", tick: 0 };
|
|
@@ -623,7 +1475,7 @@ function resolveLiveText(params) {
|
|
|
623
1475
|
}
|
|
624
1476
|
|
|
625
1477
|
// src/participant/ParticipantCopyAnnouncer.tsx
|
|
626
|
-
import { jsx as
|
|
1478
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
627
1479
|
var ANNOUNCEMENT_MS = 3e3;
|
|
628
1480
|
var AnnounceContext = createContext(() => void 0);
|
|
629
1481
|
var AnnouncementContext = createContext(INITIAL_COPY_ANNOUNCEMENT);
|
|
@@ -632,20 +1484,20 @@ function useAnnounceCopy() {
|
|
|
632
1484
|
}
|
|
633
1485
|
function ParticipantCopyAnnouncer({ children }) {
|
|
634
1486
|
const [announcement, dispatch] = useReducer(copyAnnouncementReducer, INITIAL_COPY_ANNOUNCEMENT);
|
|
635
|
-
const announce =
|
|
636
|
-
|
|
1487
|
+
const announce = useCallback4((text) => dispatch({ type: "announce", text }), []);
|
|
1488
|
+
useEffect7(() => {
|
|
637
1489
|
if (announcement.text === "") return void 0;
|
|
638
1490
|
const timer = setTimeout(() => dispatch({ type: "expired" }), ANNOUNCEMENT_MS);
|
|
639
1491
|
return () => clearTimeout(timer);
|
|
640
1492
|
}, [announcement.tick, announcement.text]);
|
|
641
|
-
return /* @__PURE__ */
|
|
1493
|
+
return /* @__PURE__ */ jsx14(AnnounceContext.Provider, { value: announce, children: /* @__PURE__ */ jsx14(AnnouncementContext.Provider, { value: announcement, children }) });
|
|
642
1494
|
}
|
|
643
1495
|
function ParticipantThreadLive({ notice }) {
|
|
644
1496
|
const copy = useContext(AnnouncementContext);
|
|
645
|
-
const baselineNotice =
|
|
1497
|
+
const baselineNotice = useRef8(void 0);
|
|
646
1498
|
const resolution = resolveLiveText({ copy, notice, baselineNotice: baselineNotice.current });
|
|
647
1499
|
baselineNotice.current = resolution.baselineNotice;
|
|
648
|
-
return /* @__PURE__ */
|
|
1500
|
+
return /* @__PURE__ */ jsx14("div", { className: "cv-p-thread__live", role: "status", "aria-live": "polite", children: resolution.text });
|
|
649
1501
|
}
|
|
650
1502
|
|
|
651
1503
|
// src/participant/ParticipantScrollToLatest.tsx
|
|
@@ -658,7 +1510,7 @@ function focusScrollerOf(button) {
|
|
|
658
1510
|
}
|
|
659
1511
|
|
|
660
1512
|
// src/participant/ParticipantScrollToLatest.tsx
|
|
661
|
-
import { jsx as
|
|
1513
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
662
1514
|
function ParticipantScrollToLatest({
|
|
663
1515
|
label,
|
|
664
1516
|
isAwayFromBottom,
|
|
@@ -668,7 +1520,7 @@ function ParticipantScrollToLatest({
|
|
|
668
1520
|
}) {
|
|
669
1521
|
const { isVisible, badge } = resolveScrollToLatestView({ isAwayFromBottom, newMessagesCount, hasMessages });
|
|
670
1522
|
if (!isVisible || onClick === void 0) return null;
|
|
671
|
-
return /* @__PURE__ */
|
|
1523
|
+
return /* @__PURE__ */ jsx15("div", { className: "cv-p-thread__latest", children: /* @__PURE__ */ jsxs12(
|
|
672
1524
|
"button",
|
|
673
1525
|
{
|
|
674
1526
|
type: "button",
|
|
@@ -680,8 +1532,8 @@ function ParticipantScrollToLatest({
|
|
|
680
1532
|
onClick();
|
|
681
1533
|
},
|
|
682
1534
|
children: [
|
|
683
|
-
/* @__PURE__ */
|
|
684
|
-
badge ? /* @__PURE__ */
|
|
1535
|
+
/* @__PURE__ */ jsx15(ChevronDown, { size: 24, "aria-hidden": "true", focusable: "false" }),
|
|
1536
|
+
badge ? /* @__PURE__ */ jsx15("span", { className: "cv-p-thread__latest-badge", "aria-hidden": "true", children: badge }) : null
|
|
685
1537
|
]
|
|
686
1538
|
}
|
|
687
1539
|
) });
|
|
@@ -727,13 +1579,13 @@ function shouldShowAvatar({ previousAuthor, author, isMine }) {
|
|
|
727
1579
|
}
|
|
728
1580
|
|
|
729
1581
|
// src/participant/ParticipantAuthorAvatar.tsx
|
|
730
|
-
import { jsx as
|
|
1582
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
731
1583
|
function ParticipantAuthorAvatar({ name, render }) {
|
|
732
1584
|
const fromHost = render?.({ name });
|
|
733
1585
|
const hasHostContent = !(fromHost == null || fromHost === false || fromHost === "");
|
|
734
1586
|
const initials = authorInitials(name);
|
|
735
|
-
const fallback = initials ?? /* @__PURE__ */
|
|
736
|
-
return /* @__PURE__ */
|
|
1587
|
+
const fallback = initials ?? /* @__PURE__ */ jsx16(User, { size: 16, "aria-hidden": true });
|
|
1588
|
+
return /* @__PURE__ */ jsx16("span", { className: "cv-p-avatar", "aria-hidden": "true", children: hasHostContent ? fromHost : fallback });
|
|
737
1589
|
}
|
|
738
1590
|
|
|
739
1591
|
// src/participant/participantDay.ts
|
|
@@ -745,17 +1597,112 @@ function toDateTimeAttribute(date) {
|
|
|
745
1597
|
}
|
|
746
1598
|
|
|
747
1599
|
// src/participant/ParticipantDayDivider.tsx
|
|
748
|
-
import { jsx as
|
|
1600
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
749
1601
|
function ParticipantDayDivider({ day, locale }) {
|
|
750
1602
|
const label = day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" });
|
|
751
|
-
return /* @__PURE__ */
|
|
1603
|
+
return /* @__PURE__ */ jsx17("div", { className: "cv-p-day", role: "separator", "aria-label": label, children: /* @__PURE__ */ jsx17("time", { className: "cv-p-day__label", dateTime: toDateTimeAttribute(day), children: label }) });
|
|
752
1604
|
}
|
|
753
1605
|
|
|
754
1606
|
// src/participant/ParticipantMessageBubble.tsx
|
|
755
1607
|
import { useId as useId3 } from "react";
|
|
756
1608
|
|
|
757
1609
|
// src/participant/ParticipantAttachmentItem.tsx
|
|
758
|
-
import { useEffect as
|
|
1610
|
+
import { useEffect as useEffect11, useState as useState7 } from "react";
|
|
1611
|
+
|
|
1612
|
+
// src/participant/ParticipantAudioTranscription.tsx
|
|
1613
|
+
import { useState as useState5 } from "react";
|
|
1614
|
+
|
|
1615
|
+
// src/participant/useTranscriptionOverflow.ts
|
|
1616
|
+
import { useEffect as useEffect8, useRef as useRef9, useState as useState4 } from "react";
|
|
1617
|
+
function useTranscriptionOverflow(text, isExpanded) {
|
|
1618
|
+
const textRef = useRef9(null);
|
|
1619
|
+
const [isOverflowing, setIsOverflowing] = useState4(false);
|
|
1620
|
+
useEffect8(() => {
|
|
1621
|
+
const element = textRef.current;
|
|
1622
|
+
if (!element || isExpanded) return void 0;
|
|
1623
|
+
const measure = () => setIsOverflowing(element.scrollHeight > element.clientHeight);
|
|
1624
|
+
measure();
|
|
1625
|
+
if (typeof ResizeObserver === "undefined") return void 0;
|
|
1626
|
+
const observer = new ResizeObserver(measure);
|
|
1627
|
+
observer.observe(element);
|
|
1628
|
+
return () => observer.disconnect();
|
|
1629
|
+
}, [text, isExpanded]);
|
|
1630
|
+
return { textRef, isOverflowing };
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/participant/useTranscriptionReadyAnnouncement.ts
|
|
1634
|
+
import { useEffect as useEffect9, useRef as useRef10 } from "react";
|
|
1635
|
+
function useTranscriptionReadyAnnouncement({ attachmentId, status, text }) {
|
|
1636
|
+
const announce = useAnnounceCopy();
|
|
1637
|
+
const previous = useRef10({ attachmentId, status });
|
|
1638
|
+
useEffect9(() => {
|
|
1639
|
+
const before = previous.current;
|
|
1640
|
+
previous.current = { attachmentId, status };
|
|
1641
|
+
if (before.attachmentId !== attachmentId) return;
|
|
1642
|
+
if (before.status === "pending" && status === "ready") announce(text);
|
|
1643
|
+
}, [announce, attachmentId, status, text]);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// src/participant/ParticipantAudioTranscription.tsx
|
|
1647
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1648
|
+
var MILLISECONDS_PER_MINUTE = 6e4;
|
|
1649
|
+
function resolveTooLongMinutes(transcription) {
|
|
1650
|
+
if (transcription?.status !== "failed" || transcription.reason !== "too_long") return void 0;
|
|
1651
|
+
if (typeof transcription.maxDurationMs !== "number") return void 0;
|
|
1652
|
+
const minutes = Math.floor(transcription.maxDurationMs / MILLISECONDS_PER_MINUTE);
|
|
1653
|
+
return minutes >= 1 ? minutes : void 0;
|
|
1654
|
+
}
|
|
1655
|
+
function dropUnusableTranscription(transcription) {
|
|
1656
|
+
if (transcription?.status === "ready" && typeof transcription.text !== "string") return void 0;
|
|
1657
|
+
return transcription;
|
|
1658
|
+
}
|
|
1659
|
+
function ParticipantAudioTranscription({ attachmentId, transcription: received, labels }) {
|
|
1660
|
+
const transcription = dropUnusableTranscription(received);
|
|
1661
|
+
useTranscriptionReadyAnnouncement({
|
|
1662
|
+
attachmentId,
|
|
1663
|
+
status: transcription?.status,
|
|
1664
|
+
text: labels.transcriptionReady
|
|
1665
|
+
});
|
|
1666
|
+
if (transcription?.status === "pending") {
|
|
1667
|
+
return /* @__PURE__ */ jsx18("p", { className: "cv-p-transcription cv-p-transcription--pending", children: labels.transcribing });
|
|
1668
|
+
}
|
|
1669
|
+
if (transcription?.status === "ready") return /* @__PURE__ */ jsx18(ReadyTranscriptionBlock, { transcription, labels });
|
|
1670
|
+
const minutes = resolveTooLongMinutes(transcription);
|
|
1671
|
+
if (minutes === void 0) return null;
|
|
1672
|
+
return /* @__PURE__ */ jsx18("p", { className: "cv-p-transcription cv-p-transcription--too-long", children: labels.transcriptionTooLong.replace("{minutes}", String(minutes)) });
|
|
1673
|
+
}
|
|
1674
|
+
function ReadyTranscriptionBlock({ transcription, labels }) {
|
|
1675
|
+
const [isExpanded, setIsExpanded] = useState5(false);
|
|
1676
|
+
const { textRef, isOverflowing } = useTranscriptionOverflow(transcription.text, isExpanded);
|
|
1677
|
+
const hasText = transcription.text !== "";
|
|
1678
|
+
const showsToggle = hasText && (isOverflowing || isExpanded);
|
|
1679
|
+
return /* @__PURE__ */ jsxs13("div", { className: "cv-p-transcription", children: [
|
|
1680
|
+
/* @__PURE__ */ jsx18("p", { className: "cv-p-transcription__notice", children: labels.machineTranscription }),
|
|
1681
|
+
hasText ? /* @__PURE__ */ jsx18(
|
|
1682
|
+
"p",
|
|
1683
|
+
{
|
|
1684
|
+
ref: textRef,
|
|
1685
|
+
className: "cv-p-transcription__text",
|
|
1686
|
+
lang: transcription.language,
|
|
1687
|
+
"data-expanded": isExpanded ? "true" : void 0,
|
|
1688
|
+
children: transcription.text
|
|
1689
|
+
}
|
|
1690
|
+
) : /* @__PURE__ */ jsx18("p", { className: "cv-p-transcription__empty", children: labels.emptyTranscription }),
|
|
1691
|
+
showsToggle ? /* @__PURE__ */ jsx18(
|
|
1692
|
+
"button",
|
|
1693
|
+
{
|
|
1694
|
+
type: "button",
|
|
1695
|
+
className: "cv-p-transcription__toggle",
|
|
1696
|
+
"aria-expanded": isExpanded,
|
|
1697
|
+
onClick: () => setIsExpanded((current) => !current),
|
|
1698
|
+
children: isExpanded ? labels.collapseTranscription : labels.showFullTranscription
|
|
1699
|
+
}
|
|
1700
|
+
) : null
|
|
1701
|
+
] });
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/participant/useStableAttachmentUrl.ts
|
|
1705
|
+
import { useCallback as useCallback5, useEffect as useEffect10, useRef as useRef11, useState as useState6 } from "react";
|
|
759
1706
|
|
|
760
1707
|
// src/participant/participantAttachmentUrl.ts
|
|
761
1708
|
var SAFE_ATTACHMENT_PROTOCOLS = /* @__PURE__ */ new Set(["https:", "http:", "blob:"]);
|
|
@@ -767,12 +1714,79 @@ function isSafeAttachmentUrl(url) {
|
|
|
767
1714
|
}
|
|
768
1715
|
}
|
|
769
1716
|
|
|
1717
|
+
// src/participant/useStableAttachmentUrl.ts
|
|
1718
|
+
var ATTACHMENT_URL_RETRY_DELAY_MS = 1e3;
|
|
1719
|
+
var PENDING_STATE = { status: "pending", url: void 0 };
|
|
1720
|
+
async function resolveSafely({ attachment, resolveAttachmentUrl }) {
|
|
1721
|
+
try {
|
|
1722
|
+
const resolved = await resolveAttachmentUrl(attachment, "inline");
|
|
1723
|
+
return isSafeAttachmentUrl(resolved) ? resolved.trim() : void 0;
|
|
1724
|
+
} catch {
|
|
1725
|
+
return void 0;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
function useStableAttachmentUrl(params) {
|
|
1729
|
+
const latest = useRef11(params);
|
|
1730
|
+
const currentId = useRef11(params.attachment.id);
|
|
1731
|
+
const [state, setState2] = useState6(PENDING_STATE);
|
|
1732
|
+
const { id } = params.attachment;
|
|
1733
|
+
useEffect10(() => {
|
|
1734
|
+
latest.current = params;
|
|
1735
|
+
});
|
|
1736
|
+
useEffect10(() => {
|
|
1737
|
+
currentId.current = id;
|
|
1738
|
+
setState2(PENDING_STATE);
|
|
1739
|
+
let retryTimer;
|
|
1740
|
+
const settle = (url) => {
|
|
1741
|
+
if (currentId.current === id) setState2(url ? { status: "ready", url } : { status: "failed", url: void 0 });
|
|
1742
|
+
};
|
|
1743
|
+
resolveSafely(latest.current).then((url) => {
|
|
1744
|
+
if (url || currentId.current !== id) return settle(url);
|
|
1745
|
+
retryTimer = setTimeout(() => void resolveSafely(latest.current).then(settle), ATTACHMENT_URL_RETRY_DELAY_MS);
|
|
1746
|
+
});
|
|
1747
|
+
return () => {
|
|
1748
|
+
currentId.current = "";
|
|
1749
|
+
clearTimeout(retryTimer);
|
|
1750
|
+
};
|
|
1751
|
+
}, [id]);
|
|
1752
|
+
const resolveAgain = useCallback5(async () => {
|
|
1753
|
+
const requestedId = currentId.current;
|
|
1754
|
+
const url = await resolveSafely(latest.current);
|
|
1755
|
+
if (url && currentId.current === requestedId) setState2({ status: "ready", url });
|
|
1756
|
+
return url;
|
|
1757
|
+
}, []);
|
|
1758
|
+
return { ...state, resolveAgain };
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// src/participant/ParticipantAudioAttachment.tsx
|
|
1762
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1763
|
+
function ParticipantAudioAttachment({ attachment, resolveAttachmentUrl, labels }) {
|
|
1764
|
+
const source = useStableAttachmentUrl({ attachment, resolveAttachmentUrl });
|
|
1765
|
+
const audioLabels = resolveParticipantAudioLabels(labels);
|
|
1766
|
+
return /* @__PURE__ */ jsxs14("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
|
|
1767
|
+
/* @__PURE__ */ jsx19(
|
|
1768
|
+
ParticipantAudioPlayer,
|
|
1769
|
+
{
|
|
1770
|
+
labels: audioLabels,
|
|
1771
|
+
url: source.url,
|
|
1772
|
+
durationMs: attachment.durationMs,
|
|
1773
|
+
filename: attachment.filename,
|
|
1774
|
+
isPending: source.status === "pending",
|
|
1775
|
+
isUnavailable: source.status === "failed",
|
|
1776
|
+
onSourceError: source.resolveAgain
|
|
1777
|
+
}
|
|
1778
|
+
),
|
|
1779
|
+
/* @__PURE__ */ jsx19("span", { className: "cv-p-attachment__name", children: attachment.filename }),
|
|
1780
|
+
/* @__PURE__ */ jsx19(ParticipantAudioTranscription, { attachmentId: attachment.id, transcription: attachment.transcription, labels: audioLabels })
|
|
1781
|
+
] });
|
|
1782
|
+
}
|
|
1783
|
+
|
|
770
1784
|
// src/participant/ParticipantAttachmentItem.tsx
|
|
771
|
-
import { jsx as
|
|
1785
|
+
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
772
1786
|
function useAttachmentUrl(attachment, resolveAttachmentUrl) {
|
|
773
|
-
const [url, setUrl] =
|
|
1787
|
+
const [url, setUrl] = useState7(void 0);
|
|
774
1788
|
const disposition = attachment.kind === "document" ? "attachment" : "inline";
|
|
775
|
-
|
|
1789
|
+
useEffect11(() => {
|
|
776
1790
|
let isCurrent = true;
|
|
777
1791
|
resolveAttachmentUrl(attachment, disposition).then(
|
|
778
1792
|
(resolved) => {
|
|
@@ -788,21 +1802,19 @@ function useAttachmentUrl(attachment, resolveAttachmentUrl) {
|
|
|
788
1802
|
}, [attachment, disposition, resolveAttachmentUrl]);
|
|
789
1803
|
return url;
|
|
790
1804
|
}
|
|
791
|
-
function ParticipantAttachmentItem(
|
|
1805
|
+
function ParticipantAttachmentItem(props) {
|
|
1806
|
+
if (props.attachment.kind === "audio") return /* @__PURE__ */ jsx20(ParticipantAudioAttachment, { ...props });
|
|
1807
|
+
return /* @__PURE__ */ jsx20(ParticipantFileAttachment, { ...props });
|
|
1808
|
+
}
|
|
1809
|
+
function ParticipantFileAttachment({ attachment, resolveAttachmentUrl, labels }) {
|
|
792
1810
|
const url = useAttachmentUrl(attachment, resolveAttachmentUrl);
|
|
793
1811
|
if (attachment.kind === "image") {
|
|
794
|
-
return /* @__PURE__ */
|
|
795
|
-
url ? /* @__PURE__ */
|
|
796
|
-
/* @__PURE__ */
|
|
1812
|
+
return /* @__PURE__ */ jsxs15("figure", { className: "cv-p-attachment cv-p-attachment--image", children: [
|
|
1813
|
+
url ? /* @__PURE__ */ jsx20("img", { className: "cv-p-attachment__image", src: url, alt: attachment.filename }) : null,
|
|
1814
|
+
/* @__PURE__ */ jsx20("figcaption", { className: "cv-p-attachment__name", children: attachment.filename })
|
|
797
1815
|
] });
|
|
798
1816
|
}
|
|
799
|
-
|
|
800
|
-
return /* @__PURE__ */ jsxs9("div", { className: "cv-p-attachment cv-p-attachment--audio", children: [
|
|
801
|
-
/* @__PURE__ */ jsx13("span", { className: "cv-p-attachment__name", children: attachment.filename }),
|
|
802
|
-
/* @__PURE__ */ jsx13("audio", { className: "cv-p-attachment__audio", controls: true, preload: "none", src: url })
|
|
803
|
-
] });
|
|
804
|
-
}
|
|
805
|
-
return /* @__PURE__ */ jsxs9(
|
|
1817
|
+
return /* @__PURE__ */ jsxs15(
|
|
806
1818
|
"a",
|
|
807
1819
|
{
|
|
808
1820
|
className: "cv-p-attachment cv-p-attachment--document",
|
|
@@ -810,15 +1822,15 @@ function ParticipantAttachmentItem({ attachment, resolveAttachmentUrl, labels })
|
|
|
810
1822
|
download: attachment.filename,
|
|
811
1823
|
"aria-label": `${labels.downloadAttachment}: ${attachment.filename}`,
|
|
812
1824
|
children: [
|
|
813
|
-
/* @__PURE__ */
|
|
814
|
-
/* @__PURE__ */
|
|
1825
|
+
/* @__PURE__ */ jsx20("span", { className: "cv-p-attachment__name", children: attachment.filename }),
|
|
1826
|
+
/* @__PURE__ */ jsx20("span", { className: "cv-p-attachment__size", children: formatFileSize(attachment.sizeBytes) })
|
|
815
1827
|
]
|
|
816
1828
|
}
|
|
817
1829
|
);
|
|
818
1830
|
}
|
|
819
1831
|
|
|
820
1832
|
// src/participant/ParticipantBubbleStatus.tsx
|
|
821
|
-
import { jsx as
|
|
1833
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
822
1834
|
function statusText(status, labels) {
|
|
823
1835
|
const byStatus = {
|
|
824
1836
|
sending: labels.statusSending,
|
|
@@ -835,13 +1847,13 @@ function OwnStatus({ status, labels, onRetry }) {
|
|
|
835
1847
|
const isVisibleText = status === "sending" || status === "queued" || status === "failed";
|
|
836
1848
|
const text = status === "failed" ? labels.statusFailedShort : statusText(status, labels);
|
|
837
1849
|
const isTextVisible = isVisibleText && !(status === "failed" && onRetry);
|
|
838
|
-
return /* @__PURE__ */
|
|
839
|
-
/* @__PURE__ */
|
|
840
|
-
/* @__PURE__ */
|
|
1850
|
+
return /* @__PURE__ */ jsxs16("span", { className: "cv-p-bubble__status", children: [
|
|
1851
|
+
/* @__PURE__ */ jsx21(StatusTicks, { status: ticksStatus, appearance: "stylesheet", title: isTextVisible ? void 0 : text }),
|
|
1852
|
+
/* @__PURE__ */ jsx21("span", { className: isTextVisible ? "cv-p-bubble__status-text" : "cv-p-sr-only", children: text })
|
|
841
1853
|
] });
|
|
842
1854
|
}
|
|
843
1855
|
function RetryButton({ labels, onRetry, describedBy }) {
|
|
844
|
-
return /* @__PURE__ */
|
|
1856
|
+
return /* @__PURE__ */ jsx21(
|
|
845
1857
|
"button",
|
|
846
1858
|
{
|
|
847
1859
|
type: "button",
|
|
@@ -850,7 +1862,7 @@ function RetryButton({ labels, onRetry, describedBy }) {
|
|
|
850
1862
|
"aria-describedby": describedBy,
|
|
851
1863
|
"data-cv-tooltip": labels.statusFailed,
|
|
852
1864
|
onClick: onRetry,
|
|
853
|
-
children: /* @__PURE__ */
|
|
1865
|
+
children: /* @__PURE__ */ jsx21("span", { className: "cv-p-failed-retry__icon", children: /* @__PURE__ */ jsx21("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx21("path", { d: "M13 8a5 5 0 1 1-1.6-3.7M13 2.5v3h-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }) }) })
|
|
854
1866
|
}
|
|
855
1867
|
);
|
|
856
1868
|
}
|
|
@@ -859,7 +1871,7 @@ function RetryButton({ labels, onRetry, describedBy }) {
|
|
|
859
1871
|
import { useId as useId2 } from "react";
|
|
860
1872
|
|
|
861
1873
|
// src/participant/ParticipantFailedMenuList.tsx
|
|
862
|
-
import { jsx as
|
|
1874
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
863
1875
|
function ParticipantFailedMenuList({ menu, labelledBy, labels, onEdit, onDiscard }) {
|
|
864
1876
|
function runAndClose(action) {
|
|
865
1877
|
return () => {
|
|
@@ -868,9 +1880,9 @@ function ParticipantFailedMenuList({ menu, labelledBy, labels, onEdit, onDiscard
|
|
|
868
1880
|
};
|
|
869
1881
|
}
|
|
870
1882
|
const className = `cv-p-failed-menu__list${menu.placement === "below" ? " cv-p-failed-menu__list--below" : ""}`;
|
|
871
|
-
return /* @__PURE__ */
|
|
872
|
-
onEdit ? /* @__PURE__ */
|
|
873
|
-
onDiscard ? /* @__PURE__ */
|
|
1883
|
+
return /* @__PURE__ */ jsxs17("div", { role: "menu", "aria-labelledby": labelledBy, ref: menu.menuRef, className, onKeyDown: menu.handleMenuKeyDown, children: [
|
|
1884
|
+
onEdit ? /* @__PURE__ */ jsx22("button", { type: "button", role: "menuitem", tabIndex: -1, className: "cv-p-failed-menu__item", onClick: runAndClose(onEdit), children: labels.edit }) : null,
|
|
1885
|
+
onDiscard ? /* @__PURE__ */ jsx22(
|
|
874
1886
|
"button",
|
|
875
1887
|
{
|
|
876
1888
|
type: "button",
|
|
@@ -885,7 +1897,7 @@ function ParticipantFailedMenuList({ menu, labelledBy, labels, onEdit, onDiscard
|
|
|
885
1897
|
}
|
|
886
1898
|
|
|
887
1899
|
// src/participant/useParticipantFailedMenu.ts
|
|
888
|
-
import { useReducer as useReducer2, useRef as
|
|
1900
|
+
import { useReducer as useReducer2, useRef as useRef12 } from "react";
|
|
889
1901
|
|
|
890
1902
|
// src/participant/participantFailedMenuState.ts
|
|
891
1903
|
var CLOSED_FAILED_MENU = { isOpen: false, initialFocus: "first" };
|
|
@@ -933,10 +1945,10 @@ function handleFailedMenuKeyDown({ event, menuRef, triggerRef, dispatch }) {
|
|
|
933
1945
|
}
|
|
934
1946
|
|
|
935
1947
|
// src/participant/useFailedMenuEffects.ts
|
|
936
|
-
import { useEffect as
|
|
1948
|
+
import { useEffect as useEffect12, useLayoutEffect as useLayoutEffect2, useState as useState8 } from "react";
|
|
937
1949
|
var SCROLLER_SELECTOR2 = ".cv-p-thread__scroll";
|
|
938
1950
|
function useFailedMenuInitialFocus(state, menuRef) {
|
|
939
|
-
|
|
1951
|
+
useEffect12(() => {
|
|
940
1952
|
if (!state.isOpen) return;
|
|
941
1953
|
const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
942
1954
|
const target = state.initialFocus === "last" ? items[items.length - 1] : items[0];
|
|
@@ -944,7 +1956,7 @@ function useFailedMenuInitialFocus(state, menuRef) {
|
|
|
944
1956
|
}, [state, menuRef]);
|
|
945
1957
|
}
|
|
946
1958
|
function useFailedMenuOutsideClose(isOpen, containerRef, dispatch) {
|
|
947
|
-
|
|
1959
|
+
useEffect12(() => {
|
|
948
1960
|
if (!isOpen) return;
|
|
949
1961
|
function handlePointerDown(event) {
|
|
950
1962
|
if (event.target instanceof Node && containerRef.current?.contains(event.target)) return;
|
|
@@ -955,7 +1967,7 @@ function useFailedMenuOutsideClose(isOpen, containerRef, dispatch) {
|
|
|
955
1967
|
}, [isOpen, containerRef, dispatch]);
|
|
956
1968
|
}
|
|
957
1969
|
function useFailedMenuPlacement(isOpen, { triggerRef, menuRef }) {
|
|
958
|
-
const [placement, setPlacement] =
|
|
1970
|
+
const [placement, setPlacement] = useState8("above");
|
|
959
1971
|
useLayoutEffect2(() => {
|
|
960
1972
|
const trigger = triggerRef.current;
|
|
961
1973
|
const menu = menuRef.current;
|
|
@@ -976,9 +1988,9 @@ function useFailedMenuPlacement(isOpen, { triggerRef, menuRef }) {
|
|
|
976
1988
|
// src/participant/useParticipantFailedMenu.ts
|
|
977
1989
|
function useParticipantFailedMenu() {
|
|
978
1990
|
const [state, dispatch] = useReducer2(failedMenuReducer, CLOSED_FAILED_MENU);
|
|
979
|
-
const containerRef =
|
|
980
|
-
const triggerRef =
|
|
981
|
-
const menuRef =
|
|
1991
|
+
const containerRef = useRef12(null);
|
|
1992
|
+
const triggerRef = useRef12(null);
|
|
1993
|
+
const menuRef = useRef12(null);
|
|
982
1994
|
useFailedMenuInitialFocus(state, menuRef);
|
|
983
1995
|
useFailedMenuOutsideClose(state.isOpen, containerRef, dispatch);
|
|
984
1996
|
const placement = useFailedMenuPlacement(state.isOpen, { triggerRef, menuRef });
|
|
@@ -1007,14 +2019,14 @@ function useParticipantFailedMenu() {
|
|
|
1007
2019
|
}
|
|
1008
2020
|
|
|
1009
2021
|
// src/participant/ParticipantFailedMenu.tsx
|
|
1010
|
-
import { jsx as
|
|
2022
|
+
import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1011
2023
|
function ParticipantFailedMenu({ labels, onEdit, onDiscard }) {
|
|
1012
2024
|
const menu = useParticipantFailedMenu();
|
|
1013
2025
|
const triggerId = useId2();
|
|
1014
2026
|
if (!onEdit && !onDiscard) return null;
|
|
1015
2027
|
const label = labels.messageOptions ?? DEFAULT_MESSAGE_OPTIONS_LABEL;
|
|
1016
|
-
return /* @__PURE__ */
|
|
1017
|
-
/* @__PURE__ */
|
|
2028
|
+
return /* @__PURE__ */ jsxs18("span", { className: "cv-p-failed-menu", ref: menu.containerRef, onBlur: menu.handleBlur, children: [
|
|
2029
|
+
/* @__PURE__ */ jsx23(
|
|
1018
2030
|
"button",
|
|
1019
2031
|
{
|
|
1020
2032
|
type: "button",
|
|
@@ -1027,21 +2039,21 @@ function ParticipantFailedMenu({ labels, onEdit, onDiscard }) {
|
|
|
1027
2039
|
"data-cv-tooltip": menu.state.isOpen ? void 0 : label,
|
|
1028
2040
|
onClick: menu.handleTriggerClick,
|
|
1029
2041
|
onKeyDown: menu.handleTriggerKeyDown,
|
|
1030
|
-
children: /* @__PURE__ */
|
|
2042
|
+
children: /* @__PURE__ */ jsx23("span", { "aria-hidden": "true", children: "\u22EF" })
|
|
1031
2043
|
}
|
|
1032
2044
|
),
|
|
1033
|
-
menu.state.isOpen ? /* @__PURE__ */
|
|
2045
|
+
menu.state.isOpen ? /* @__PURE__ */ jsx23(ParticipantFailedMenuList, { menu, labelledBy: triggerId, labels, onEdit, onDiscard }) : null
|
|
1034
2046
|
] });
|
|
1035
2047
|
}
|
|
1036
2048
|
|
|
1037
2049
|
// src/participant/ParticipantMessageText.tsx
|
|
1038
|
-
import { useMemo } from "react";
|
|
2050
|
+
import { useMemo as useMemo2 } from "react";
|
|
1039
2051
|
|
|
1040
2052
|
// src/participant/ParticipantCopyableToken.tsx
|
|
1041
2053
|
import { Check, Copy } from "lucide-react";
|
|
1042
2054
|
|
|
1043
2055
|
// src/participant/useParticipantCopyFeedback.ts
|
|
1044
|
-
import { useEffect as
|
|
2056
|
+
import { useEffect as useEffect13, useReducer as useReducer3 } from "react";
|
|
1045
2057
|
|
|
1046
2058
|
// src/participant/participantProtocolCopy.ts
|
|
1047
2059
|
async function copyProtocolToClipboard(protocol, clipboard) {
|
|
@@ -1065,7 +2077,7 @@ function useParticipantCopyFeedback() {
|
|
|
1065
2077
|
const [state, dispatch] = useReducer3(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
|
|
1066
2078
|
const announce = useAnnounceCopy();
|
|
1067
2079
|
const { isCopied, copyCount } = state;
|
|
1068
|
-
|
|
2080
|
+
useEffect13(() => {
|
|
1069
2081
|
if (!isCopied) return void 0;
|
|
1070
2082
|
const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_FEEDBACK_MS);
|
|
1071
2083
|
return () => clearTimeout(timer);
|
|
@@ -1080,10 +2092,10 @@ function useParticipantCopyFeedback() {
|
|
|
1080
2092
|
}
|
|
1081
2093
|
|
|
1082
2094
|
// src/participant/ParticipantCopyableToken.tsx
|
|
1083
|
-
import { jsx as
|
|
2095
|
+
import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1084
2096
|
function ParticipantCopyableToken({ copyKind, value, labels }) {
|
|
1085
2097
|
const { isCopied, copy } = useParticipantCopyFeedback();
|
|
1086
|
-
return /* @__PURE__ */
|
|
2098
|
+
return /* @__PURE__ */ jsxs19(
|
|
1087
2099
|
"button",
|
|
1088
2100
|
{
|
|
1089
2101
|
type: "button",
|
|
@@ -1092,8 +2104,8 @@ function ParticipantCopyableToken({ copyKind, value, labels }) {
|
|
|
1092
2104
|
onClick: () => copy(value, labels.valueCopied),
|
|
1093
2105
|
children: [
|
|
1094
2106
|
value,
|
|
1095
|
-
isCopied ? /* @__PURE__ */
|
|
1096
|
-
/* @__PURE__ */
|
|
2107
|
+
isCopied ? /* @__PURE__ */ jsxs19("span", { className: "cv-p-copyable__done", "aria-hidden": "true", children: [
|
|
2108
|
+
/* @__PURE__ */ jsx24(Check, { size: 12, "aria-hidden": true }),
|
|
1097
2109
|
" ",
|
|
1098
2110
|
labels.valueCopied
|
|
1099
2111
|
] }) : null
|
|
@@ -1103,7 +2115,7 @@ function ParticipantCopyableToken({ copyKind, value, labels }) {
|
|
|
1103
2115
|
}
|
|
1104
2116
|
function ParticipantLinkCopyButton({ value, labels }) {
|
|
1105
2117
|
const { isCopied, copy } = useParticipantCopyFeedback();
|
|
1106
|
-
return /* @__PURE__ */
|
|
2118
|
+
return /* @__PURE__ */ jsx24(
|
|
1107
2119
|
"button",
|
|
1108
2120
|
{
|
|
1109
2121
|
type: "button",
|
|
@@ -1111,26 +2123,26 @@ function ParticipantLinkCopyButton({ value, labels }) {
|
|
|
1111
2123
|
"aria-label": `${labels.copyValue} ${value}`,
|
|
1112
2124
|
"data-cv-tooltip": labels.copyValue,
|
|
1113
2125
|
onClick: () => copy(value, labels.valueCopied),
|
|
1114
|
-
children: isCopied ? /* @__PURE__ */
|
|
2126
|
+
children: isCopied ? /* @__PURE__ */ jsx24(Check, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx24(Copy, { size: 14, "aria-hidden": true })
|
|
1115
2127
|
}
|
|
1116
2128
|
);
|
|
1117
2129
|
}
|
|
1118
2130
|
|
|
1119
2131
|
// src/participant/ParticipantMessageNodes.tsx
|
|
1120
|
-
import { jsx as
|
|
2132
|
+
import { jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1121
2133
|
var LINK_REL = "noopener noreferrer nofollow ugc";
|
|
1122
2134
|
function renderLink(node, labels, key) {
|
|
1123
2135
|
const isExternal = /^https?:/i.test(node.href);
|
|
1124
|
-
return /* @__PURE__ */
|
|
1125
|
-
/* @__PURE__ */
|
|
2136
|
+
return /* @__PURE__ */ jsxs20("span", { className: "cv-p-text__link-group", children: [
|
|
2137
|
+
/* @__PURE__ */ jsxs20("a", { className: "cv-p-text__link", href: node.href, rel: LINK_REL, target: isExternal ? "_blank" : void 0, children: [
|
|
1126
2138
|
node.label,
|
|
1127
|
-
isExternal ? /* @__PURE__ */
|
|
2139
|
+
isExternal ? /* @__PURE__ */ jsxs20("span", { className: "cv-p-sr-only", children: [
|
|
1128
2140
|
" (",
|
|
1129
2141
|
labels.externalLinkHint,
|
|
1130
2142
|
")"
|
|
1131
2143
|
] }) : null
|
|
1132
2144
|
] }),
|
|
1133
|
-
/* @__PURE__ */
|
|
2145
|
+
/* @__PURE__ */ jsx25(ParticipantLinkCopyButton, { value: node.href, labels })
|
|
1134
2146
|
] }, key);
|
|
1135
2147
|
}
|
|
1136
2148
|
function renderInlineNodes(nodes, labels) {
|
|
@@ -1139,17 +2151,17 @@ function renderInlineNodes(nodes, labels) {
|
|
|
1139
2151
|
case "text":
|
|
1140
2152
|
return node.value;
|
|
1141
2153
|
case "strong":
|
|
1142
|
-
return /* @__PURE__ */
|
|
2154
|
+
return /* @__PURE__ */ jsx25("strong", { children: renderInlineNodes(node.children, labels) }, key);
|
|
1143
2155
|
case "em":
|
|
1144
|
-
return /* @__PURE__ */
|
|
2156
|
+
return /* @__PURE__ */ jsx25("em", { children: renderInlineNodes(node.children, labels) }, key);
|
|
1145
2157
|
case "del":
|
|
1146
|
-
return /* @__PURE__ */
|
|
2158
|
+
return /* @__PURE__ */ jsx25("del", { children: renderInlineNodes(node.children, labels) }, key);
|
|
1147
2159
|
case "code":
|
|
1148
|
-
return /* @__PURE__ */
|
|
2160
|
+
return /* @__PURE__ */ jsx25("code", { className: "cv-p-text__code", children: node.value }, key);
|
|
1149
2161
|
case "link":
|
|
1150
2162
|
return renderLink(node, labels, key);
|
|
1151
2163
|
case "copyable":
|
|
1152
|
-
return /* @__PURE__ */
|
|
2164
|
+
return /* @__PURE__ */ jsx25(ParticipantCopyableToken, { copyKind: node.copyKind, value: node.value, labels }, key);
|
|
1153
2165
|
}
|
|
1154
2166
|
});
|
|
1155
2167
|
}
|
|
@@ -1535,41 +2547,41 @@ function startsFenceLine(line) {
|
|
|
1535
2547
|
}
|
|
1536
2548
|
|
|
1537
2549
|
// src/participant/ParticipantMessageText.tsx
|
|
1538
|
-
import { jsx as
|
|
2550
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
1539
2551
|
function renderBlock(block, labels, key) {
|
|
1540
|
-
if (block.kind === "paragraph") return /* @__PURE__ */
|
|
2552
|
+
if (block.kind === "paragraph") return /* @__PURE__ */ jsx26("span", { children: renderInlineNodes(block.children, labels) }, key);
|
|
1541
2553
|
if (block.kind === "pre") {
|
|
1542
|
-
return /* @__PURE__ */
|
|
2554
|
+
return /* @__PURE__ */ jsx26("pre", { className: "cv-p-text__pre", children: /* @__PURE__ */ jsx26("code", { children: block.value }) }, key);
|
|
1543
2555
|
}
|
|
1544
2556
|
if (block.kind === "quote") {
|
|
1545
|
-
return /* @__PURE__ */
|
|
2557
|
+
return /* @__PURE__ */ jsx26("blockquote", { className: "cv-p-text__quote", children: renderInlineNodes(block.children, labels) }, key);
|
|
1546
2558
|
}
|
|
1547
|
-
const items = block.items.map((item, index) => /* @__PURE__ */
|
|
2559
|
+
const items = block.items.map((item, index) => /* @__PURE__ */ jsx26("li", { children: renderInlineNodes(item, labels) }, index));
|
|
1548
2560
|
if (block.ordered) {
|
|
1549
|
-
return /* @__PURE__ */
|
|
2561
|
+
return /* @__PURE__ */ jsx26("ol", { className: "cv-p-text__list", start: block.start === 1 ? void 0 : block.start, children: items }, key);
|
|
1550
2562
|
}
|
|
1551
|
-
return /* @__PURE__ */
|
|
2563
|
+
return /* @__PURE__ */ jsx26("ul", { className: "cv-p-text__list", children: items }, key);
|
|
1552
2564
|
}
|
|
1553
2565
|
function ParticipantMessageText({ text, labels, id }) {
|
|
1554
|
-
const blocks =
|
|
2566
|
+
const blocks = useMemo2(() => parseParticipantMessage(text), [text]);
|
|
1555
2567
|
if (blocks.length === 0) return null;
|
|
1556
2568
|
const [only] = blocks;
|
|
1557
2569
|
const children = blocks.length === 1 && only?.kind === "paragraph" ? renderInlineNodes(only.children, labels) : blocks.map((block, key) => renderBlock(block, labels, key));
|
|
1558
|
-
return /* @__PURE__ */
|
|
2570
|
+
return /* @__PURE__ */ jsx26("div", { className: "cv-p-text", id, children });
|
|
1559
2571
|
}
|
|
1560
2572
|
|
|
1561
2573
|
// src/participant/ParticipantBubbleBody.tsx
|
|
1562
|
-
import { Fragment as
|
|
2574
|
+
import { Fragment as Fragment6, jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1563
2575
|
function BubbleHeading({ content, labels }) {
|
|
1564
|
-
if (content.isMine) return /* @__PURE__ */
|
|
2576
|
+
if (content.isMine) return /* @__PURE__ */ jsx27("span", { className: "cv-p-sr-only", children: labels.me });
|
|
1565
2577
|
if (!content.authorName) return null;
|
|
1566
|
-
return /* @__PURE__ */
|
|
2578
|
+
return /* @__PURE__ */ jsx27("span", { className: "cv-p-bubble__author", children: content.authorName });
|
|
1567
2579
|
}
|
|
1568
2580
|
function BubbleBody({ content, labels, ownStatus, textId, resolveAttachmentUrl, onRetry, onDiscard, onEdit }) {
|
|
1569
|
-
return /* @__PURE__ */
|
|
1570
|
-
/* @__PURE__ */
|
|
1571
|
-
content.text ? /* @__PURE__ */
|
|
1572
|
-
content.attachments.map((attachment) => /* @__PURE__ */
|
|
2581
|
+
return /* @__PURE__ */ jsxs21(Fragment6, { children: [
|
|
2582
|
+
/* @__PURE__ */ jsx27(BubbleHeading, { content, labels }),
|
|
2583
|
+
content.text ? /* @__PURE__ */ jsx27(ParticipantMessageText, { text: content.text, labels, id: textId }) : null,
|
|
2584
|
+
content.attachments.map((attachment) => /* @__PURE__ */ jsx27(
|
|
1573
2585
|
ParticipantAttachmentItem,
|
|
1574
2586
|
{
|
|
1575
2587
|
attachment,
|
|
@@ -1578,11 +2590,11 @@ function BubbleBody({ content, labels, ownStatus, textId, resolveAttachmentUrl,
|
|
|
1578
2590
|
},
|
|
1579
2591
|
attachment.id
|
|
1580
2592
|
)),
|
|
1581
|
-
content.pendingFilenames.map((filename, index) => /* @__PURE__ */
|
|
1582
|
-
/* @__PURE__ */
|
|
1583
|
-
/* @__PURE__ */
|
|
1584
|
-
ownStatus ? /* @__PURE__ */
|
|
1585
|
-
ownStatus === "failed" ? /* @__PURE__ */
|
|
2593
|
+
content.pendingFilenames.map((filename, index) => /* @__PURE__ */ jsx27("span", { className: "cv-p-attachment cv-p-attachment--pending", children: filename }, `${filename}-${index}`)),
|
|
2594
|
+
/* @__PURE__ */ jsxs21("span", { className: "cv-p-bubble__meta", children: [
|
|
2595
|
+
/* @__PURE__ */ jsx27("time", { className: "cv-p-bubble__time", dateTime: content.createdAt, children: formatTimestamp(content.createdAt) }),
|
|
2596
|
+
ownStatus ? /* @__PURE__ */ jsx27(OwnStatus, { status: ownStatus, labels, onRetry }) : null,
|
|
2597
|
+
ownStatus === "failed" ? /* @__PURE__ */ jsx27(ParticipantFailedMenu, { labels, onEdit, onDiscard }) : null
|
|
1586
2598
|
] })
|
|
1587
2599
|
] });
|
|
1588
2600
|
}
|
|
@@ -1649,7 +2661,7 @@ function resolveOwnMessageStatus(params) {
|
|
|
1649
2661
|
}
|
|
1650
2662
|
|
|
1651
2663
|
// src/participant/ParticipantMessageBubble.tsx
|
|
1652
|
-
import { jsx as
|
|
2664
|
+
import { jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1653
2665
|
function describeItem(item, perspective) {
|
|
1654
2666
|
if (item.kind === "server") {
|
|
1655
2667
|
const { message } = item;
|
|
@@ -1686,13 +2698,13 @@ function ParticipantMessageBubble(props) {
|
|
|
1686
2698
|
const ownStatus = content.isMine ? resolveOwnMessageStatus({ displayState: content.displayState, serverStatus: content.serverStatus, confirmsRead }) : void 0;
|
|
1687
2699
|
const isFailed = ownStatus === "failed";
|
|
1688
2700
|
const canRetry = isFailed && onRetry !== void 0;
|
|
1689
|
-
const bubble = /* @__PURE__ */
|
|
1690
|
-
canRetry ? /* @__PURE__ */
|
|
1691
|
-
/* @__PURE__ */
|
|
2701
|
+
const bubble = /* @__PURE__ */ jsxs22("div", { className: bubbleClassName(content.isMine, tail, isFailed), children: [
|
|
2702
|
+
canRetry ? /* @__PURE__ */ jsx28(RetryButton, { labels, onRetry, describedBy: textId }) : null,
|
|
2703
|
+
/* @__PURE__ */ jsx28(BubbleBody, { ...props, content, ownStatus, textId: canRetry ? textId : void 0 })
|
|
1692
2704
|
] });
|
|
1693
2705
|
if (avatar === void 0 || content.isMine) return bubble;
|
|
1694
|
-
return /* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
2706
|
+
return /* @__PURE__ */ jsxs22("div", { className: "cv-p-bubble-row", children: [
|
|
2707
|
+
/* @__PURE__ */ jsx28("span", { className: "cv-p-bubble-row__avatar", children: avatar }),
|
|
1696
2708
|
bubble
|
|
1697
2709
|
] });
|
|
1698
2710
|
}
|
|
@@ -1714,7 +2726,7 @@ function resolveParticipantBubbleActions(item, handlers) {
|
|
|
1714
2726
|
}
|
|
1715
2727
|
|
|
1716
2728
|
// src/participant/ParticipantTimeline.tsx
|
|
1717
|
-
import { jsx as
|
|
2729
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
1718
2730
|
function createdAtOf2(item) {
|
|
1719
2731
|
return item.kind === "server" ? item.message.createdAt : item.pending.createdAt;
|
|
1720
2732
|
}
|
|
@@ -1731,7 +2743,7 @@ function resolveActions(item, pendingActions) {
|
|
|
1731
2743
|
}
|
|
1732
2744
|
function resolveAvatarSlot({ isEnabled, isMine, showAvatar, author, render }) {
|
|
1733
2745
|
if (!isEnabled || isMine) return void 0;
|
|
1734
|
-
return showAvatar ? /* @__PURE__ */
|
|
2746
|
+
return showAvatar ? /* @__PURE__ */ jsx29(ParticipantAuthorAvatar, { name: author, render }) : null;
|
|
1735
2747
|
}
|
|
1736
2748
|
function ParticipantTimeline(props) {
|
|
1737
2749
|
const { items, labels, locale, resolveAttachmentUrl, pendingActions } = props;
|
|
@@ -1743,7 +2755,7 @@ function ParticipantTimeline(props) {
|
|
|
1743
2755
|
for (const item of items) {
|
|
1744
2756
|
const day = new Date(createdAtOf2(item));
|
|
1745
2757
|
if (previousDay === void 0 || !isSameDay(previousDay, day)) {
|
|
1746
|
-
nodes.push(/* @__PURE__ */
|
|
2758
|
+
nodes.push(/* @__PURE__ */ jsx29(ParticipantDayDivider, { day, locale }, `day:${keyOf(item)}`));
|
|
1747
2759
|
previousAuthor = void 0;
|
|
1748
2760
|
}
|
|
1749
2761
|
previousDay = day;
|
|
@@ -1758,7 +2770,7 @@ function ParticipantTimeline(props) {
|
|
|
1758
2770
|
render: props.renderAuthorAvatar
|
|
1759
2771
|
});
|
|
1760
2772
|
nodes.push(
|
|
1761
|
-
/* @__PURE__ */
|
|
2773
|
+
/* @__PURE__ */ jsx29(
|
|
1762
2774
|
ParticipantMessageBubble,
|
|
1763
2775
|
{
|
|
1764
2776
|
item,
|
|
@@ -1774,19 +2786,19 @@ function ParticipantTimeline(props) {
|
|
|
1774
2786
|
)
|
|
1775
2787
|
);
|
|
1776
2788
|
}
|
|
1777
|
-
return /* @__PURE__ */
|
|
2789
|
+
return /* @__PURE__ */ jsx29("div", { className: "cv-p-thread__messages", children: nodes });
|
|
1778
2790
|
}
|
|
1779
2791
|
|
|
1780
2792
|
// src/participant/ParticipantConversationAvatar.tsx
|
|
1781
|
-
import { jsx as
|
|
2793
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
1782
2794
|
function ParticipantConversationAvatar({ icon }) {
|
|
1783
|
-
return /* @__PURE__ */
|
|
2795
|
+
return /* @__PURE__ */ jsx30("span", { className: "cv-p-thread__avatar", "aria-hidden": "true", children: icon });
|
|
1784
2796
|
}
|
|
1785
2797
|
|
|
1786
2798
|
// src/participant/ParticipantProtocolBadge.tsx
|
|
1787
2799
|
import { Check as Check2, Copy as Copy2 } from "lucide-react";
|
|
1788
|
-
import { useEffect as
|
|
1789
|
-
import { Fragment as
|
|
2800
|
+
import { useEffect as useEffect14, useReducer as useReducer4 } from "react";
|
|
2801
|
+
import { Fragment as Fragment7, jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1790
2802
|
var COPIED_ANNOUNCEMENT_MS = 3e3;
|
|
1791
2803
|
function ParticipantProtocolBadgeView({
|
|
1792
2804
|
protocol,
|
|
@@ -1795,16 +2807,16 @@ function ParticipantProtocolBadgeView({
|
|
|
1795
2807
|
onCopy
|
|
1796
2808
|
}) {
|
|
1797
2809
|
const { isCopied, copyCount } = copyState;
|
|
1798
|
-
return /* @__PURE__ */
|
|
1799
|
-
/* @__PURE__ */
|
|
1800
|
-
/* @__PURE__ */
|
|
1801
|
-
/* @__PURE__ */
|
|
2810
|
+
return /* @__PURE__ */ jsxs23(Fragment7, { children: [
|
|
2811
|
+
/* @__PURE__ */ jsxs23("span", { className: "cv-p-protocol cv-p-protocol--header", children: [
|
|
2812
|
+
/* @__PURE__ */ jsxs23("span", { className: "cv-p-protocol__code", children: [
|
|
2813
|
+
/* @__PURE__ */ jsxs23("span", { className: "cv-p-sr-only", children: [
|
|
1802
2814
|
labels.protocolPrefix,
|
|
1803
2815
|
" "
|
|
1804
2816
|
] }),
|
|
1805
2817
|
protocol
|
|
1806
2818
|
] }),
|
|
1807
|
-
/* @__PURE__ */
|
|
2819
|
+
/* @__PURE__ */ jsxs23(
|
|
1808
2820
|
"button",
|
|
1809
2821
|
{
|
|
1810
2822
|
type: "button",
|
|
@@ -1813,19 +2825,19 @@ function ParticipantProtocolBadgeView({
|
|
|
1813
2825
|
"data-cv-tooltip": labels.copyProtocol,
|
|
1814
2826
|
onClick: onCopy,
|
|
1815
2827
|
children: [
|
|
1816
|
-
isCopied ? /* @__PURE__ */
|
|
1817
|
-
isCopied ? /* @__PURE__ */
|
|
2828
|
+
isCopied ? /* @__PURE__ */ jsx31(Check2, { size: 18, "aria-hidden": true }) : /* @__PURE__ */ jsx31(Copy2, { size: 18, "aria-hidden": true }),
|
|
2829
|
+
isCopied ? /* @__PURE__ */ jsx31("span", { className: "cv-p-protocol__copied", "aria-hidden": "true", children: labels.protocolCopied }) : null
|
|
1818
2830
|
]
|
|
1819
2831
|
}
|
|
1820
2832
|
)
|
|
1821
2833
|
] }),
|
|
1822
|
-
/* @__PURE__ */
|
|
2834
|
+
/* @__PURE__ */ jsx31("div", { className: "cv-p-sr-only", role: "status", "aria-live": "polite", children: isCopied ? /* @__PURE__ */ jsx31("span", { children: labels.protocolCopied }, copyCount) : null })
|
|
1823
2835
|
] });
|
|
1824
2836
|
}
|
|
1825
2837
|
function ParticipantProtocolBadge({ protocol, labels }) {
|
|
1826
2838
|
const [copyState, dispatch] = useReducer4(protocolCopyReducer, INITIAL_PROTOCOL_COPY_STATE);
|
|
1827
2839
|
const { isCopied, copyCount } = copyState;
|
|
1828
|
-
|
|
2840
|
+
useEffect14(() => {
|
|
1829
2841
|
if (!isCopied) return void 0;
|
|
1830
2842
|
const timer = setTimeout(() => dispatch({ type: "expired" }), COPIED_ANNOUNCEMENT_MS);
|
|
1831
2843
|
return () => clearTimeout(timer);
|
|
@@ -1834,7 +2846,7 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
1834
2846
|
const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
|
|
1835
2847
|
if (await copyProtocolToClipboard(protocol, clipboard)) dispatch({ type: "copied" });
|
|
1836
2848
|
}
|
|
1837
|
-
return /* @__PURE__ */
|
|
2849
|
+
return /* @__PURE__ */ jsx31(
|
|
1838
2850
|
ParticipantProtocolBadgeView,
|
|
1839
2851
|
{
|
|
1840
2852
|
protocol,
|
|
@@ -1846,19 +2858,19 @@ function ParticipantProtocolBadge({ protocol, labels }) {
|
|
|
1846
2858
|
}
|
|
1847
2859
|
|
|
1848
2860
|
// src/participant/ParticipantThreadHeader.tsx
|
|
1849
|
-
import { jsx as
|
|
2861
|
+
import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1850
2862
|
function Title({ conversation, labels, onOpenSubject }) {
|
|
1851
2863
|
const { subjectType, subjectId, subjectLabel } = conversation;
|
|
1852
|
-
return /* @__PURE__ */
|
|
2864
|
+
return /* @__PURE__ */ jsx32("h2", { className: "cv-p-thread__title", children: onOpenSubject ? /* @__PURE__ */ jsx32(
|
|
1853
2865
|
"button",
|
|
1854
2866
|
{
|
|
1855
2867
|
type: "button",
|
|
1856
2868
|
className: "cv-p-thread__title-button",
|
|
1857
2869
|
"aria-label": `${labels.openSubject}: ${subjectLabel}`,
|
|
1858
2870
|
onClick: () => onOpenSubject({ subjectType, subjectId }),
|
|
1859
|
-
children: /* @__PURE__ */
|
|
2871
|
+
children: /* @__PURE__ */ jsx32("span", { className: "cv-p-thread__title-text", children: subjectLabel })
|
|
1860
2872
|
}
|
|
1861
|
-
) : /* @__PURE__ */
|
|
2873
|
+
) : /* @__PURE__ */ jsx32("span", { className: "cv-p-thread__title-text", children: subjectLabel }) });
|
|
1862
2874
|
}
|
|
1863
2875
|
function ParticipantThreadHeader({
|
|
1864
2876
|
conversation,
|
|
@@ -1873,30 +2885,30 @@ function ParticipantThreadHeader({
|
|
|
1873
2885
|
const group = subjectGroups?.find((candidate) => candidate.subjectType === conversation.subjectType);
|
|
1874
2886
|
const icon = resolveConversationIcon({ conversation, group, renderSubjectIcon });
|
|
1875
2887
|
const hasChannels = resolveParticipantChannels(conversation.channels).length > 0;
|
|
1876
|
-
return /* @__PURE__ */
|
|
1877
|
-
onBack ? /* @__PURE__ */
|
|
1878
|
-
icon !== void 0 ? /* @__PURE__ */
|
|
1879
|
-
/* @__PURE__ */
|
|
1880
|
-
icon === void 0 && group ? /* @__PURE__ */
|
|
1881
|
-
/* @__PURE__ */
|
|
1882
|
-
counterpartLabel ? /* @__PURE__ */
|
|
1883
|
-
conversation.protocol || hasChannels ? /* @__PURE__ */
|
|
1884
|
-
conversation.protocol ? /* @__PURE__ */
|
|
1885
|
-
/* @__PURE__ */
|
|
2888
|
+
return /* @__PURE__ */ jsxs24("header", { className: "cv-p-thread__header", children: [
|
|
2889
|
+
onBack ? /* @__PURE__ */ jsx32(ParticipantBackButton, { label: labels.back, onBack }) : null,
|
|
2890
|
+
icon !== void 0 ? /* @__PURE__ */ jsx32(ParticipantConversationAvatar, { icon }) : null,
|
|
2891
|
+
/* @__PURE__ */ jsxs24("div", { className: "cv-p-thread__heading", children: [
|
|
2892
|
+
icon === void 0 && group ? /* @__PURE__ */ jsx32("p", { className: "cv-p-thread__eyebrow", children: group.label }) : null,
|
|
2893
|
+
/* @__PURE__ */ jsx32(Title, { conversation, labels, onOpenSubject }),
|
|
2894
|
+
counterpartLabel ? /* @__PURE__ */ jsx32("p", { className: "cv-p-thread__counterpart", children: counterpartLabel }) : null,
|
|
2895
|
+
conversation.protocol || hasChannels ? /* @__PURE__ */ jsxs24("div", { className: "cv-p-thread__meta", children: [
|
|
2896
|
+
conversation.protocol ? /* @__PURE__ */ jsx32(ParticipantProtocolBadge, { protocol: conversation.protocol, labels }) : null,
|
|
2897
|
+
/* @__PURE__ */ jsx32(ParticipantChannelBadges, { channels: conversation.channels, labels, variant: "inline", iconSize: 16 })
|
|
1886
2898
|
] }) : null
|
|
1887
2899
|
] }),
|
|
1888
|
-
actions ? /* @__PURE__ */
|
|
2900
|
+
actions ? /* @__PURE__ */ jsx32("div", { className: "cv-p-thread__actions", children: actions }) : null
|
|
1889
2901
|
] });
|
|
1890
2902
|
}
|
|
1891
2903
|
|
|
1892
2904
|
// src/participant/ParticipantThread.tsx
|
|
1893
|
-
import { jsx as
|
|
2905
|
+
import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
1894
2906
|
function Footer({ props }) {
|
|
1895
2907
|
const { conversation, labels, draft } = props;
|
|
1896
2908
|
if (conversation.status === "closed") {
|
|
1897
|
-
return /* @__PURE__ */
|
|
2909
|
+
return /* @__PURE__ */ jsx33("p", { className: "cv-p-thread__closed", children: labels.closedNotice });
|
|
1898
2910
|
}
|
|
1899
|
-
return /* @__PURE__ */
|
|
2911
|
+
return /* @__PURE__ */ jsx33(
|
|
1900
2912
|
ParticipantComposer,
|
|
1901
2913
|
{
|
|
1902
2914
|
value: draft.value,
|
|
@@ -1909,12 +2921,14 @@ function Footer({ props }) {
|
|
|
1909
2921
|
isSending: props.isSending,
|
|
1910
2922
|
maxLength: props.maxLength,
|
|
1911
2923
|
acceptedTypes: props.acceptedTypes,
|
|
1912
|
-
quickReplies: props.quickReplies
|
|
2924
|
+
quickReplies: props.quickReplies,
|
|
2925
|
+
audioRecording: props.audioRecording,
|
|
2926
|
+
onSendAudio: props.onSendAudio
|
|
1913
2927
|
}
|
|
1914
2928
|
);
|
|
1915
2929
|
}
|
|
1916
2930
|
function Header({ props }) {
|
|
1917
|
-
return /* @__PURE__ */
|
|
2931
|
+
return /* @__PURE__ */ jsx33(
|
|
1918
2932
|
ParticipantThreadHeader,
|
|
1919
2933
|
{
|
|
1920
2934
|
conversation: props.conversation,
|
|
@@ -1932,17 +2946,17 @@ function ThreadBody(props) {
|
|
|
1932
2946
|
const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
|
|
1933
2947
|
const subjectCard = renderSubjectCard?.(conversation);
|
|
1934
2948
|
const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
|
|
1935
|
-
const rootRef =
|
|
2949
|
+
const rootRef = useRef13(null);
|
|
1936
2950
|
useSuppressTouchTooltips(rootRef);
|
|
1937
|
-
return /* @__PURE__ */
|
|
1938
|
-
/* @__PURE__ */
|
|
1939
|
-
subjectCard ? /* @__PURE__ */
|
|
1940
|
-
/* @__PURE__ */
|
|
1941
|
-
loadView.isLoading ? /* @__PURE__ */
|
|
1942
|
-
loadView.hasError ? /* @__PURE__ */
|
|
1943
|
-
hasMore && onLoadOlder ? /* @__PURE__ */
|
|
1944
|
-
/* @__PURE__ */
|
|
1945
|
-
/* @__PURE__ */
|
|
2951
|
+
return /* @__PURE__ */ jsxs25("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, ref: rootRef, children: [
|
|
2952
|
+
/* @__PURE__ */ jsx33(Header, { props }),
|
|
2953
|
+
subjectCard ? /* @__PURE__ */ jsx33("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
|
|
2954
|
+
/* @__PURE__ */ jsxs25("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
|
|
2955
|
+
loadView.isLoading ? /* @__PURE__ */ jsx33(ParticipantLoading, { labels }) : null,
|
|
2956
|
+
loadView.hasError ? /* @__PURE__ */ jsx33(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
|
|
2957
|
+
hasMore && onLoadOlder ? /* @__PURE__ */ jsx33("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
|
|
2958
|
+
/* @__PURE__ */ jsx33(ParticipantTimeline, { ...props }),
|
|
2959
|
+
/* @__PURE__ */ jsx33(
|
|
1946
2960
|
ParticipantScrollToLatest,
|
|
1947
2961
|
{
|
|
1948
2962
|
label: labels.scrollToLatest,
|
|
@@ -1953,19 +2967,19 @@ function ThreadBody(props) {
|
|
|
1953
2967
|
}
|
|
1954
2968
|
)
|
|
1955
2969
|
] }),
|
|
1956
|
-
/* @__PURE__ */
|
|
1957
|
-
/* @__PURE__ */
|
|
2970
|
+
/* @__PURE__ */ jsx33(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
|
|
2971
|
+
/* @__PURE__ */ jsx33(Footer, { props })
|
|
1958
2972
|
] });
|
|
1959
2973
|
}
|
|
1960
2974
|
function ParticipantThread(props) {
|
|
1961
|
-
return /* @__PURE__ */
|
|
1962
|
-
/* @__PURE__ */
|
|
1963
|
-
/* @__PURE__ */
|
|
2975
|
+
return /* @__PURE__ */ jsxs25(ParticipantCopyAnnouncer, { children: [
|
|
2976
|
+
/* @__PURE__ */ jsx33(ThreadBody, { ...props }),
|
|
2977
|
+
/* @__PURE__ */ jsx33(TooltipLayer, {})
|
|
1964
2978
|
] });
|
|
1965
2979
|
}
|
|
1966
2980
|
|
|
1967
2981
|
// src/participant/useParticipantThreadController.ts
|
|
1968
|
-
import { useMemo as
|
|
2982
|
+
import { useMemo as useMemo5 } from "react";
|
|
1969
2983
|
|
|
1970
2984
|
// src/participant/bindAttachmentUrlResolver.ts
|
|
1971
2985
|
function bindAttachmentUrlResolver(api) {
|
|
@@ -2049,7 +3063,7 @@ function appendEntryToDraft(entry, current) {
|
|
|
2049
3063
|
}
|
|
2050
3064
|
|
|
2051
3065
|
// src/participant/useParticipantConversationView.ts
|
|
2052
|
-
import { useCallback as
|
|
3066
|
+
import { useCallback as useCallback9, useMemo as useMemo4 } from "react";
|
|
2053
3067
|
|
|
2054
3068
|
// src/participant/participantDrafts.ts
|
|
2055
3069
|
var EMPTY_PARTICIPANT_DRAFT = { text: "", files: [] };
|
|
@@ -2093,7 +3107,7 @@ function participantSendStatesReducer(states, { subject, action }) {
|
|
|
2093
3107
|
}
|
|
2094
3108
|
|
|
2095
3109
|
// src/participant/useParticipantThread.ts
|
|
2096
|
-
import { useCallback as
|
|
3110
|
+
import { useCallback as useCallback8, useEffect as useEffect18, useMemo as useMemo3, useReducer as useReducer5, useRef as useRef17 } from "react";
|
|
2097
3111
|
|
|
2098
3112
|
// src/participant/participantThreadState.ts
|
|
2099
3113
|
var INITIAL_PARTICIPANT_THREAD_STATE = {
|
|
@@ -2129,7 +3143,7 @@ function participantThreadReducer(state, action) {
|
|
|
2129
3143
|
}
|
|
2130
3144
|
|
|
2131
3145
|
// src/participant/useParticipantMarkRead.ts
|
|
2132
|
-
import { useEffect as
|
|
3146
|
+
import { useEffect as useEffect16 } from "react";
|
|
2133
3147
|
|
|
2134
3148
|
// src/participant/shouldMarkRead.ts
|
|
2135
3149
|
function shouldMarkParticipantRead(params) {
|
|
@@ -2141,12 +3155,12 @@ function shouldMarkParticipantRead(params) {
|
|
|
2141
3155
|
}
|
|
2142
3156
|
|
|
2143
3157
|
// src/participant/useDocumentVisibility.ts
|
|
2144
|
-
import { useEffect as
|
|
3158
|
+
import { useEffect as useEffect15, useState as useState9 } from "react";
|
|
2145
3159
|
function useDocumentVisibility() {
|
|
2146
|
-
const [visibilityState, setVisibilityState] =
|
|
3160
|
+
const [visibilityState, setVisibilityState] = useState9(
|
|
2147
3161
|
() => typeof document === "undefined" ? "visible" : document.visibilityState
|
|
2148
3162
|
);
|
|
2149
|
-
|
|
3163
|
+
useEffect15(() => {
|
|
2150
3164
|
function handleVisibilityChange() {
|
|
2151
3165
|
setVisibilityState(document.visibilityState);
|
|
2152
3166
|
}
|
|
@@ -2161,18 +3175,18 @@ function useDocumentVisibility() {
|
|
|
2161
3175
|
function useParticipantMarkRead(params) {
|
|
2162
3176
|
const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
|
|
2163
3177
|
const visibilityState = useDocumentVisibility();
|
|
2164
|
-
|
|
3178
|
+
useEffect16(() => {
|
|
2165
3179
|
if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
|
|
2166
3180
|
void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
|
|
2167
3181
|
}, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
|
|
2168
3182
|
}
|
|
2169
3183
|
|
|
2170
3184
|
// src/participant/useParticipantRevalidation.ts
|
|
2171
|
-
import { useEffect as
|
|
3185
|
+
import { useEffect as useEffect17, useRef as useRef14 } from "react";
|
|
2172
3186
|
function useParticipantRevalidation(api, onRevalidate) {
|
|
2173
|
-
const handlerRef =
|
|
3187
|
+
const handlerRef = useRef14(onRevalidate);
|
|
2174
3188
|
handlerRef.current = onRevalidate;
|
|
2175
|
-
|
|
3189
|
+
useEffect17(() => {
|
|
2176
3190
|
function handleFocus() {
|
|
2177
3191
|
handlerRef.current();
|
|
2178
3192
|
}
|
|
@@ -2199,12 +3213,12 @@ function useParticipantRevalidation(api, onRevalidate) {
|
|
|
2199
3213
|
}
|
|
2200
3214
|
|
|
2201
3215
|
// src/participant/useParticipantSend.ts
|
|
2202
|
-
import { useCallback as
|
|
3216
|
+
import { useCallback as useCallback6, useRef as useRef15 } from "react";
|
|
2203
3217
|
function useParticipantSend(params) {
|
|
2204
3218
|
const { apiRef, subject, dispatchSend, onSentMessage } = params;
|
|
2205
|
-
const entriesRef =
|
|
3219
|
+
const entriesRef = useRef15(params.entries);
|
|
2206
3220
|
entriesRef.current = params.entries;
|
|
2207
|
-
const deliver =
|
|
3221
|
+
const deliver = useCallback6(
|
|
2208
3222
|
async (entry) => {
|
|
2209
3223
|
const { clientMessageId } = entry;
|
|
2210
3224
|
try {
|
|
@@ -2228,7 +3242,7 @@ function useParticipantSend(params) {
|
|
|
2228
3242
|
},
|
|
2229
3243
|
[apiRef, dispatchSend, onSentMessage]
|
|
2230
3244
|
);
|
|
2231
|
-
const send =
|
|
3245
|
+
const send = useCallback6(
|
|
2232
3246
|
async (content) => {
|
|
2233
3247
|
const entry = createParticipantSendEntry({
|
|
2234
3248
|
clientMessageId: crypto.randomUUID(),
|
|
@@ -2241,7 +3255,7 @@ function useParticipantSend(params) {
|
|
|
2241
3255
|
},
|
|
2242
3256
|
[deliver, dispatchSend, subject]
|
|
2243
3257
|
);
|
|
2244
|
-
const retry =
|
|
3258
|
+
const retry = useCallback6(
|
|
2245
3259
|
async (clientMessageId) => {
|
|
2246
3260
|
const entry = findFailedEntry(entriesRef.current, clientMessageId);
|
|
2247
3261
|
if (!entry) return;
|
|
@@ -2254,13 +3268,13 @@ function useParticipantSend(params) {
|
|
|
2254
3268
|
}
|
|
2255
3269
|
|
|
2256
3270
|
// src/participant/useParticipantUnread.ts
|
|
2257
|
-
import { useCallback as
|
|
3271
|
+
import { useCallback as useCallback7, useRef as useRef16, useState as useState10 } from "react";
|
|
2258
3272
|
function useParticipantUnread(params) {
|
|
2259
3273
|
const { messages, perspective, reportedUnreadCount, onMarkedRead } = params;
|
|
2260
|
-
const [acknowledgedCount, setAcknowledgedCount] =
|
|
2261
|
-
const messagesRef =
|
|
3274
|
+
const [acknowledgedCount, setAcknowledgedCount] = useState10(0);
|
|
3275
|
+
const messagesRef = useRef16(messages);
|
|
2262
3276
|
messagesRef.current = messages;
|
|
2263
|
-
const handleMarkedRead =
|
|
3277
|
+
const handleMarkedRead = useCallback7(
|
|
2264
3278
|
(subject) => {
|
|
2265
3279
|
if (perspective) setAcknowledgedCount(countOtherSideMessages(messagesRef.current, perspective));
|
|
2266
3280
|
onMarkedRead?.(subject);
|
|
@@ -2283,13 +3297,13 @@ function isForSubject(event, subject) {
|
|
|
2283
3297
|
function useParticipantThread(params) {
|
|
2284
3298
|
const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [], perspective } = params;
|
|
2285
3299
|
const { subjectType, subjectId } = subject;
|
|
2286
|
-
const stableSubject =
|
|
3300
|
+
const stableSubject = useMemo3(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
2287
3301
|
const [state, dispatch] = useReducer5(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
|
|
2288
|
-
const apiRef =
|
|
3302
|
+
const apiRef = useRef17(api);
|
|
2289
3303
|
apiRef.current = api;
|
|
2290
|
-
const activeSubjectKey =
|
|
3304
|
+
const activeSubjectKey = useRef17("");
|
|
2291
3305
|
const confirmsRead = channelCapabilityFor(channel).confirmsRead;
|
|
2292
|
-
const refresh =
|
|
3306
|
+
const refresh = useCallback8(async () => {
|
|
2293
3307
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
2294
3308
|
dispatch({ type: "started" });
|
|
2295
3309
|
try {
|
|
@@ -2302,7 +3316,7 @@ function useParticipantThread(params) {
|
|
|
2302
3316
|
}
|
|
2303
3317
|
}, [stableSubject, subjectType, subjectId]);
|
|
2304
3318
|
const oldestMessageId = state.messages[0]?.id;
|
|
2305
|
-
const loadOlder =
|
|
3319
|
+
const loadOlder = useCallback8(async () => {
|
|
2306
3320
|
if (!oldestMessageId) return;
|
|
2307
3321
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
2308
3322
|
try {
|
|
@@ -2314,9 +3328,9 @@ function useParticipantThread(params) {
|
|
|
2314
3328
|
if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
|
|
2315
3329
|
}
|
|
2316
3330
|
}, [stableSubject, subjectType, subjectId, oldestMessageId]);
|
|
2317
|
-
const handleSentMessage =
|
|
3331
|
+
const handleSentMessage = useCallback8((message) => dispatch({ type: "sendConfirmed", message }), []);
|
|
2318
3332
|
const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
|
|
2319
|
-
|
|
3333
|
+
useEffect18(() => {
|
|
2320
3334
|
activeSubjectKey.current = `${subjectType}:${subjectId}`;
|
|
2321
3335
|
dispatch({ type: "reset" });
|
|
2322
3336
|
void refresh();
|
|
@@ -2324,7 +3338,7 @@ function useParticipantThread(params) {
|
|
|
2324
3338
|
useParticipantRevalidation(api, (event) => {
|
|
2325
3339
|
if (isForSubject(event, stableSubject)) void refresh();
|
|
2326
3340
|
});
|
|
2327
|
-
|
|
3341
|
+
useEffect18(() => {
|
|
2328
3342
|
if (sendEntries.length === 0) return;
|
|
2329
3343
|
dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
|
|
2330
3344
|
}, [sendEntries, state.messages, hostPending, dispatchSend]);
|
|
@@ -2336,7 +3350,7 @@ function useParticipantThread(params) {
|
|
|
2336
3350
|
messageCount: state.messages.length,
|
|
2337
3351
|
onMarkedRead: unread.handleMarkedRead
|
|
2338
3352
|
});
|
|
2339
|
-
const localPending =
|
|
3353
|
+
const localPending = useMemo3(() => toLocalPending(sendEntries), [sendEntries]);
|
|
2340
3354
|
return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
|
|
2341
3355
|
}
|
|
2342
3356
|
|
|
@@ -2345,7 +3359,7 @@ function useParticipantConversationView(params) {
|
|
|
2345
3359
|
const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
|
|
2346
3360
|
const { subjectType, subjectId } = subject;
|
|
2347
3361
|
const sendEntries = selectSendEntries(sendStates, subject);
|
|
2348
|
-
const dispatchSend =
|
|
3362
|
+
const dispatchSend = useCallback9(
|
|
2349
3363
|
(action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
|
|
2350
3364
|
[dispatchSendStates, subjectType, subjectId]
|
|
2351
3365
|
);
|
|
@@ -2360,7 +3374,7 @@ function useParticipantConversationView(params) {
|
|
|
2360
3374
|
hostPending: pendingMessages,
|
|
2361
3375
|
perspective: params.perspective
|
|
2362
3376
|
});
|
|
2363
|
-
const items =
|
|
3377
|
+
const items = useMemo4(
|
|
2364
3378
|
() => mergeParticipantMessages({
|
|
2365
3379
|
serverMessages: thread.messages,
|
|
2366
3380
|
hostPending: pendingMessages ?? [],
|
|
@@ -2373,9 +3387,9 @@ function useParticipantConversationView(params) {
|
|
|
2373
3387
|
}
|
|
2374
3388
|
|
|
2375
3389
|
// src/participant/useParticipantDraft.ts
|
|
2376
|
-
import { useState as
|
|
3390
|
+
import { useState as useState11 } from "react";
|
|
2377
3391
|
function useParticipantDraft(draftsRef, subject) {
|
|
2378
|
-
const [draft, setDraftState] =
|
|
3392
|
+
const [draft, setDraftState] = useState11(() => getDraft(draftsRef.current, subject));
|
|
2379
3393
|
function replaceDrafts(next) {
|
|
2380
3394
|
draftsRef.current = next;
|
|
2381
3395
|
setDraftState(getDraft(next, subject));
|
|
@@ -2394,8 +3408,29 @@ function useParticipantDraft(draftsRef, subject) {
|
|
|
2394
3408
|
return { draft, setText: (text) => update({ text }), setFiles: (files) => update({ files }), takeForSend, restoreFromEntry };
|
|
2395
3409
|
}
|
|
2396
3410
|
|
|
3411
|
+
// src/participant/useParticipantSendAudio.ts
|
|
3412
|
+
import { useCallback as useCallback10, useRef as useRef18 } from "react";
|
|
3413
|
+
function useParticipantSendAudio(send) {
|
|
3414
|
+
const sendRef = useRef18(send);
|
|
3415
|
+
sendRef.current = send;
|
|
3416
|
+
const inFlightRef = useRef18(/* @__PURE__ */ new Set());
|
|
3417
|
+
return useCallback10((file) => {
|
|
3418
|
+
const inFlight = inFlightRef.current;
|
|
3419
|
+
if (inFlight.has(file)) return;
|
|
3420
|
+
inFlight.add(file);
|
|
3421
|
+
void (async () => {
|
|
3422
|
+
try {
|
|
3423
|
+
await sendRef.current({ files: [file] });
|
|
3424
|
+
} catch {
|
|
3425
|
+
} finally {
|
|
3426
|
+
inFlight.delete(file);
|
|
3427
|
+
}
|
|
3428
|
+
})();
|
|
3429
|
+
}, []);
|
|
3430
|
+
}
|
|
3431
|
+
|
|
2397
3432
|
// src/participant/useParticipantThreadScroll.ts
|
|
2398
|
-
import { useEffect as
|
|
3433
|
+
import { useEffect as useEffect19, useRef as useRef20 } from "react";
|
|
2399
3434
|
|
|
2400
3435
|
// src/participant/participantNewMessages.ts
|
|
2401
3436
|
function collectServerMessageIds(items) {
|
|
@@ -2420,9 +3455,9 @@ function countNewIncomingMessages(previousIds, currentItems, perspective) {
|
|
|
2420
3455
|
}
|
|
2421
3456
|
|
|
2422
3457
|
// src/participant/useScrollAwayState.ts
|
|
2423
|
-
import { useCallback as
|
|
3458
|
+
import { useCallback as useCallback11 } from "react";
|
|
2424
3459
|
function useScrollAwayState(ref, isNearBottom) {
|
|
2425
|
-
const scrollToLatest =
|
|
3460
|
+
const scrollToLatest = useCallback11(() => {
|
|
2426
3461
|
const element = ref.current;
|
|
2427
3462
|
if (!element) return;
|
|
2428
3463
|
element.scrollTo(
|
|
@@ -2433,7 +3468,7 @@ function useScrollAwayState(ref, isNearBottom) {
|
|
|
2433
3468
|
}
|
|
2434
3469
|
|
|
2435
3470
|
// src/participant/useStickToBottom.ts
|
|
2436
|
-
import { useCallback as
|
|
3471
|
+
import { useCallback as useCallback12, useLayoutEffect as useLayoutEffect3, useRef as useRef19, useState as useState12 } from "react";
|
|
2437
3472
|
|
|
2438
3473
|
// src/participant/participantScroll.ts
|
|
2439
3474
|
var NEAR_BOTTOM_THRESHOLD_PX = 80;
|
|
@@ -2455,17 +3490,17 @@ function resolveScrollAction(params) {
|
|
|
2455
3490
|
|
|
2456
3491
|
// src/participant/useStickToBottom.ts
|
|
2457
3492
|
function useStickToBottom({ firstKey, lastKey }) {
|
|
2458
|
-
const ref =
|
|
2459
|
-
const snapshot =
|
|
3493
|
+
const ref = useRef19(null);
|
|
3494
|
+
const snapshot = useRef19({
|
|
2460
3495
|
hasPositioned: false,
|
|
2461
3496
|
firstKey: void 0,
|
|
2462
3497
|
lastKey: void 0,
|
|
2463
3498
|
scrollTop: 0,
|
|
2464
3499
|
scrollHeight: 0
|
|
2465
3500
|
});
|
|
2466
|
-
const [isNearBottom, setIsNearBottom] =
|
|
2467
|
-
const isNearBottomRef =
|
|
2468
|
-
const onScroll =
|
|
3501
|
+
const [isNearBottom, setIsNearBottom] = useState12(true);
|
|
3502
|
+
const isNearBottomRef = useRef19(true);
|
|
3503
|
+
const onScroll = useCallback12((event) => {
|
|
2469
3504
|
const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
|
|
2470
3505
|
const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
|
|
2471
3506
|
snapshot.current.scrollTop = scrollTop;
|
|
@@ -2520,8 +3555,8 @@ function useParticipantThreadScroll(items, perspective) {
|
|
|
2520
3555
|
});
|
|
2521
3556
|
const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
|
|
2522
3557
|
const scroll = { ...base, isAwayFromBottom, scrollToLatest };
|
|
2523
|
-
const seenIds =
|
|
2524
|
-
|
|
3558
|
+
const seenIds = useRef20(/* @__PURE__ */ new Set());
|
|
3559
|
+
useEffect19(() => {
|
|
2525
3560
|
if (isNearBottom) seenIds.current = collectServerMessageIds(items);
|
|
2526
3561
|
}, [isNearBottom, items]);
|
|
2527
3562
|
return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items, perspective) };
|
|
@@ -2536,22 +3571,26 @@ function buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending
|
|
|
2536
3571
|
...onRetryPending ? { retryHost: onRetryPending } : {}
|
|
2537
3572
|
};
|
|
2538
3573
|
}
|
|
3574
|
+
function buildEditHandler({ sendEntries, dispatchSend, restoreFromEntry }) {
|
|
3575
|
+
return (clientMessageId) => {
|
|
3576
|
+
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
3577
|
+
if (!entry) return;
|
|
3578
|
+
restoreFromEntry(entry);
|
|
3579
|
+
dispatchSend({ type: "discarded", clientMessageId });
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
2539
3582
|
function useParticipantThreadController(params) {
|
|
2540
3583
|
const { api, subject, conversation, draftsRef } = params;
|
|
2541
3584
|
const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, subject);
|
|
2542
3585
|
const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView(params);
|
|
2543
|
-
const resolveAttachmentUrl =
|
|
3586
|
+
const resolveAttachmentUrl = useMemo5(() => bindAttachmentUrlResolver(api), [api]);
|
|
2544
3587
|
const { scroll, newMessagesCount } = useParticipantThreadScroll(items, params.perspective);
|
|
3588
|
+
const handleSendAudio = useParticipantSendAudio(thread.send);
|
|
3589
|
+
const handleEdit = buildEditHandler({ sendEntries, dispatchSend, restoreFromEntry });
|
|
2545
3590
|
function handleSend() {
|
|
2546
3591
|
const content = takeForSend();
|
|
2547
3592
|
if (content) void thread.send(content);
|
|
2548
3593
|
}
|
|
2549
|
-
function handleEdit(clientMessageId) {
|
|
2550
|
-
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
2551
|
-
if (!entry) return;
|
|
2552
|
-
restoreFromEntry(entry);
|
|
2553
|
-
dispatchSend({ type: "discarded", clientMessageId });
|
|
2554
|
-
}
|
|
2555
3594
|
return {
|
|
2556
3595
|
conversation,
|
|
2557
3596
|
items,
|
|
@@ -2569,17 +3608,18 @@ function useParticipantThreadController(params) {
|
|
|
2569
3608
|
locale: params.locale,
|
|
2570
3609
|
onLoadOlder: () => void thread.loadOlder(),
|
|
2571
3610
|
pendingActions: buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending: params.onRetryPending }),
|
|
2572
|
-
perspective: params.perspective
|
|
3611
|
+
perspective: params.perspective,
|
|
3612
|
+
onSendAudio: handleSendAudio
|
|
2573
3613
|
};
|
|
2574
3614
|
}
|
|
2575
3615
|
|
|
2576
3616
|
// src/participant/ParticipantThreadScreen.tsx
|
|
2577
|
-
import { jsx as
|
|
3617
|
+
import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2578
3618
|
function useOpenedConversation(api, subject, isNeeded) {
|
|
2579
|
-
const [opened, setOpened] =
|
|
3619
|
+
const [opened, setOpened] = useState13({ status: "idle" });
|
|
2580
3620
|
const { subjectType, subjectId } = subject;
|
|
2581
3621
|
const canOpen = api.openConversation !== void 0;
|
|
2582
|
-
|
|
3622
|
+
useEffect20(() => {
|
|
2583
3623
|
if (!isNeeded || !canOpen) return;
|
|
2584
3624
|
let isCurrent = true;
|
|
2585
3625
|
setOpened({ status: "loading" });
|
|
@@ -2597,9 +3637,9 @@ function NotFound({
|
|
|
2597
3637
|
labels,
|
|
2598
3638
|
onBack
|
|
2599
3639
|
}) {
|
|
2600
|
-
return /* @__PURE__ */
|
|
2601
|
-
/* @__PURE__ */
|
|
2602
|
-
/* @__PURE__ */
|
|
3640
|
+
return /* @__PURE__ */ jsxs26("div", { className: "cv-p cv-p-thread", children: [
|
|
3641
|
+
/* @__PURE__ */ jsx34("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx34(ParticipantBackButton, { label: labels.back, onBack }) : null }),
|
|
3642
|
+
/* @__PURE__ */ jsx34("p", { className: "cv-p-empty", children: labels.notFound })
|
|
2603
3643
|
] });
|
|
2604
3644
|
}
|
|
2605
3645
|
function ParticipantThreadScreen(props) {
|
|
@@ -2609,12 +3649,12 @@ function ParticipantThreadScreen(props) {
|
|
|
2609
3649
|
);
|
|
2610
3650
|
const opened = useOpenedConversation(api, selected, listed === void 0);
|
|
2611
3651
|
const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
|
|
2612
|
-
if (conversation) return /* @__PURE__ */
|
|
3652
|
+
if (conversation) return /* @__PURE__ */ jsx34(LoadedThread, { ...props, conversation });
|
|
2613
3653
|
const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
|
|
2614
3654
|
const isListLoading = inbox.status === "idle" || inbox.status === "loading";
|
|
2615
3655
|
if (isResolving || api.openConversation === void 0 && isListLoading)
|
|
2616
|
-
return /* @__PURE__ */
|
|
2617
|
-
return /* @__PURE__ */
|
|
3656
|
+
return /* @__PURE__ */ jsx34("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
|
|
3657
|
+
return /* @__PURE__ */ jsx34(NotFound, { labels, onBack: props.onBack });
|
|
2618
3658
|
}
|
|
2619
3659
|
function LoadedThread(props) {
|
|
2620
3660
|
const core = useParticipantThreadController({
|
|
@@ -2622,7 +3662,7 @@ function LoadedThread(props) {
|
|
|
2622
3662
|
subject: props.selected,
|
|
2623
3663
|
onMarkedRead: props.inbox.markSubjectRead
|
|
2624
3664
|
});
|
|
2625
|
-
return /* @__PURE__ */
|
|
3665
|
+
return /* @__PURE__ */ jsx34(
|
|
2626
3666
|
ParticipantThread,
|
|
2627
3667
|
{
|
|
2628
3668
|
...core,
|
|
@@ -2634,7 +3674,8 @@ function LoadedThread(props) {
|
|
|
2634
3674
|
renderSubjectIcon: props.renderSubjectIcon,
|
|
2635
3675
|
avatars: props.avatars,
|
|
2636
3676
|
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
2637
|
-
tail: props.tail
|
|
3677
|
+
tail: props.tail,
|
|
3678
|
+
audioRecording: props.audioRecording
|
|
2638
3679
|
}
|
|
2639
3680
|
);
|
|
2640
3681
|
}
|
|
@@ -2760,16 +3801,16 @@ function buildParticipantThemeStyle(theme) {
|
|
|
2760
3801
|
}
|
|
2761
3802
|
|
|
2762
3803
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
2763
|
-
import { jsx as
|
|
3804
|
+
import { jsx as jsx35 } from "react/jsx-runtime";
|
|
2764
3805
|
var ALL_FILTER3 = "all";
|
|
2765
3806
|
function ParticipantConversationsScreen(props) {
|
|
2766
3807
|
const { selected, inbox, subjectGroups, classNames } = props;
|
|
2767
|
-
const [filter, setFilter] =
|
|
2768
|
-
const [searchQuery, setSearchQuery] =
|
|
2769
|
-
const draftsRef =
|
|
3808
|
+
const [filter, setFilter] = useState14(ALL_FILTER3);
|
|
3809
|
+
const [searchQuery, setSearchQuery] = useState14("");
|
|
3810
|
+
const draftsRef = useRef21(/* @__PURE__ */ new Map());
|
|
2770
3811
|
const [sendStates, dispatchSendStates] = useReducer6(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2771
|
-
const labels =
|
|
2772
|
-
const view =
|
|
3812
|
+
const labels = useMemo6(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
3813
|
+
const view = useMemo6(
|
|
2773
3814
|
() => groupParticipantConversations({
|
|
2774
3815
|
conversations: inbox.conversations,
|
|
2775
3816
|
subjectGroups,
|
|
@@ -2780,7 +3821,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2780
3821
|
);
|
|
2781
3822
|
const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
|
|
2782
3823
|
const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
|
|
2783
|
-
return /* @__PURE__ */
|
|
3824
|
+
return /* @__PURE__ */ jsx35("div", { className: rootClassName, style: buildParticipantThemeStyle(props.theme), children: selected === void 0 ? /* @__PURE__ */ jsx35("div", { className: ["cv-p-root__screen", classNames?.inbox].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx35(
|
|
2784
3825
|
ParticipantInbox,
|
|
2785
3826
|
{
|
|
2786
3827
|
view,
|
|
@@ -2797,7 +3838,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2797
3838
|
renderSubjectIcon: props.renderSubjectIcon,
|
|
2798
3839
|
search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
|
|
2799
3840
|
}
|
|
2800
|
-
) }) : /* @__PURE__ */
|
|
3841
|
+
) }) : /* @__PURE__ */ jsx35("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx35(
|
|
2801
3842
|
ParticipantThreadScreen,
|
|
2802
3843
|
{
|
|
2803
3844
|
...props,
|
|
@@ -2812,7 +3853,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2812
3853
|
}
|
|
2813
3854
|
|
|
2814
3855
|
// src/participant/useParticipantInbox.ts
|
|
2815
|
-
import { useCallback as
|
|
3856
|
+
import { useCallback as useCallback13, useEffect as useEffect21, useReducer as useReducer7 } from "react";
|
|
2816
3857
|
|
|
2817
3858
|
// src/participant/participantInboxState.ts
|
|
2818
3859
|
var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
|
|
@@ -2863,7 +3904,7 @@ function describeError2(error) {
|
|
|
2863
3904
|
}
|
|
2864
3905
|
function useParticipantInbox(api) {
|
|
2865
3906
|
const [state, dispatch] = useReducer7(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
|
|
2866
|
-
const refresh =
|
|
3907
|
+
const refresh = useCallback13(async () => {
|
|
2867
3908
|
dispatch({ type: "started" });
|
|
2868
3909
|
try {
|
|
2869
3910
|
const page = await api.listConversations();
|
|
@@ -2873,7 +3914,7 @@ function useParticipantInbox(api) {
|
|
|
2873
3914
|
}
|
|
2874
3915
|
}, [api]);
|
|
2875
3916
|
const { nextCursor } = state;
|
|
2876
|
-
const loadMore =
|
|
3917
|
+
const loadMore = useCallback13(async () => {
|
|
2877
3918
|
if (!nextCursor) return;
|
|
2878
3919
|
try {
|
|
2879
3920
|
const page = await api.listConversations({ cursor: nextCursor });
|
|
@@ -2882,10 +3923,10 @@ function useParticipantInbox(api) {
|
|
|
2882
3923
|
dispatch({ type: "failed", error: describeError2(error) });
|
|
2883
3924
|
}
|
|
2884
3925
|
}, [api, nextCursor]);
|
|
2885
|
-
const markSubjectRead =
|
|
3926
|
+
const markSubjectRead = useCallback13((subject) => {
|
|
2886
3927
|
dispatch({ type: "markedRead", subject });
|
|
2887
3928
|
}, []);
|
|
2888
|
-
|
|
3929
|
+
useEffect21(() => {
|
|
2889
3930
|
void refresh();
|
|
2890
3931
|
}, [refresh]);
|
|
2891
3932
|
useParticipantRevalidation(api, () => {
|
|
@@ -2895,11 +3936,11 @@ function useParticipantInbox(api) {
|
|
|
2895
3936
|
}
|
|
2896
3937
|
|
|
2897
3938
|
// src/participant/ParticipantConversations.tsx
|
|
2898
|
-
import { jsx as
|
|
3939
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
2899
3940
|
function ParticipantConversations(props) {
|
|
2900
3941
|
const inbox = useParticipantInbox(props.api);
|
|
2901
3942
|
const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
|
|
2902
|
-
return /* @__PURE__ */
|
|
3943
|
+
return /* @__PURE__ */ jsx36(
|
|
2903
3944
|
ParticipantConversationsScreen,
|
|
2904
3945
|
{
|
|
2905
3946
|
...props,
|
|
@@ -2909,7 +3950,7 @@ function ParticipantConversations(props) {
|
|
|
2909
3950
|
}
|
|
2910
3951
|
|
|
2911
3952
|
// src/participant/ConversationThread.tsx
|
|
2912
|
-
import { useCallback as
|
|
3953
|
+
import { useCallback as useCallback14, useMemo as useMemo7, useReducer as useReducer8, useRef as useRef22 } from "react";
|
|
2913
3954
|
|
|
2914
3955
|
// src/participant/conversationThreadApi.ts
|
|
2915
3956
|
function toParticipantApi(api) {
|
|
@@ -2949,20 +3990,20 @@ function buildThreadSummary({
|
|
|
2949
3990
|
}
|
|
2950
3991
|
|
|
2951
3992
|
// src/participant/ConversationThread.tsx
|
|
2952
|
-
import { jsx as
|
|
3993
|
+
import { jsx as jsx37 } from "react/jsx-runtime";
|
|
2953
3994
|
function useConversationThreadCore(props) {
|
|
2954
3995
|
const { perspective = "participant" } = props;
|
|
2955
3996
|
const { subjectType, subjectId } = props.subject;
|
|
2956
|
-
const subject =
|
|
2957
|
-
const onMarkedReadRef =
|
|
3997
|
+
const subject = useMemo7(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
3998
|
+
const onMarkedReadRef = useRef22(props.onMarkedRead);
|
|
2958
3999
|
onMarkedReadRef.current = props.onMarkedRead;
|
|
2959
|
-
const handleMarkedRead =
|
|
2960
|
-
const api =
|
|
2961
|
-
const draftsRef =
|
|
4000
|
+
const handleMarkedRead = useCallback14((marked) => onMarkedReadRef.current?.(marked), []);
|
|
4001
|
+
const api = useMemo7(() => toParticipantApi(props.api), [props.api]);
|
|
4002
|
+
const draftsRef = useRef22(/* @__PURE__ */ new Map());
|
|
2962
4003
|
const [sendStates, dispatchSendStates] = useReducer8(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2963
|
-
const labels =
|
|
4004
|
+
const labels = useMemo7(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
2964
4005
|
const { title, protocol, channels, status } = props;
|
|
2965
|
-
const conversation =
|
|
4006
|
+
const conversation = useMemo7(
|
|
2966
4007
|
() => buildThreadSummary({ subject, title, protocol, channels, status }),
|
|
2967
4008
|
[subject, title, protocol, channels, status]
|
|
2968
4009
|
);
|
|
@@ -2984,12 +4025,12 @@ function useConversationThreadCore(props) {
|
|
|
2984
4025
|
}
|
|
2985
4026
|
function ConversationThreadBody(props) {
|
|
2986
4027
|
const core = useConversationThreadCore(props);
|
|
2987
|
-
return /* @__PURE__ */
|
|
4028
|
+
return /* @__PURE__ */ jsx37(
|
|
2988
4029
|
"div",
|
|
2989
4030
|
{
|
|
2990
4031
|
className: ["cv-p-root", props.className].filter(Boolean).join(" "),
|
|
2991
4032
|
style: buildParticipantThemeStyle(props.theme),
|
|
2992
|
-
children: /* @__PURE__ */
|
|
4033
|
+
children: /* @__PURE__ */ jsx37("div", { className: "cv-p-root__screen", children: /* @__PURE__ */ jsx37(
|
|
2993
4034
|
ParticipantThread,
|
|
2994
4035
|
{
|
|
2995
4036
|
...core,
|
|
@@ -3001,17 +4042,20 @@ function ConversationThreadBody(props) {
|
|
|
3001
4042
|
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
3002
4043
|
tail: props.tail,
|
|
3003
4044
|
counterpartLabel: props.counterpartLabel,
|
|
3004
|
-
headerActions: props.headerActions
|
|
4045
|
+
headerActions: props.headerActions,
|
|
4046
|
+
audioRecording: props.audioRecording
|
|
3005
4047
|
}
|
|
3006
4048
|
) })
|
|
3007
4049
|
}
|
|
3008
4050
|
);
|
|
3009
4051
|
}
|
|
3010
4052
|
function ConversationThread(props) {
|
|
3011
|
-
return /* @__PURE__ */
|
|
4053
|
+
return /* @__PURE__ */ jsx37(ConversationThreadBody, { ...props }, draftKey(props.subject));
|
|
3012
4054
|
}
|
|
3013
4055
|
export {
|
|
3014
4056
|
ConversationThread,
|
|
4057
|
+
DEFAULT_MAX_RECORDING_BYTES,
|
|
4058
|
+
DEFAULT_PARTICIPANT_AUDIO_LABELS,
|
|
3015
4059
|
DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS,
|
|
3016
4060
|
ParticipantConversations,
|
|
3017
4061
|
groupParticipantConversations
|