@adatechnology/conversations-ui 0.9.0 → 0.11.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 +107 -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 +55 -3
- package/dist/participant/index.js +1369 -319
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +2 -2
- package/dist/styles.css +187 -0
- package/package.json +2 -2
- package/src/AudioRecorderButton.tsx +4 -28
- package/src/audioRecorderFormat.ts +27 -0
- package/src/participant/ConversationThread.tsx +7 -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 +5 -0
- package/src/participant/ParticipantRecorderParts.tsx +118 -0
- package/src/participant/ParticipantThread.test.tsx +46 -0
- package/src/participant/ParticipantThread.tsx +17 -0
- package/src/participant/ParticipantThreadScreen.tsx +2 -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 +30 -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,34 @@ 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 });
|
|
2910
|
+
}
|
|
2911
|
+
const lock = props.renderComposerLock?.(conversation);
|
|
2912
|
+
if (lock !== void 0 && lock !== null) {
|
|
2913
|
+
return /* @__PURE__ */ jsx33("div", { className: "cv-p-thread__lock", role: "region", "aria-live": "polite", children: lock });
|
|
1898
2914
|
}
|
|
1899
|
-
return /* @__PURE__ */
|
|
2915
|
+
return /* @__PURE__ */ jsx33(
|
|
1900
2916
|
ParticipantComposer,
|
|
1901
2917
|
{
|
|
1902
2918
|
value: draft.value,
|
|
@@ -1909,12 +2925,14 @@ function Footer({ props }) {
|
|
|
1909
2925
|
isSending: props.isSending,
|
|
1910
2926
|
maxLength: props.maxLength,
|
|
1911
2927
|
acceptedTypes: props.acceptedTypes,
|
|
1912
|
-
quickReplies: props.quickReplies
|
|
2928
|
+
quickReplies: props.quickReplies,
|
|
2929
|
+
audioRecording: props.audioRecording,
|
|
2930
|
+
onSendAudio: props.onSendAudio
|
|
1913
2931
|
}
|
|
1914
2932
|
);
|
|
1915
2933
|
}
|
|
1916
2934
|
function Header({ props }) {
|
|
1917
|
-
return /* @__PURE__ */
|
|
2935
|
+
return /* @__PURE__ */ jsx33(
|
|
1918
2936
|
ParticipantThreadHeader,
|
|
1919
2937
|
{
|
|
1920
2938
|
conversation: props.conversation,
|
|
@@ -1932,17 +2950,17 @@ function ThreadBody(props) {
|
|
|
1932
2950
|
const { conversation, labels, hasMore, onLoadOlder, renderSubjectCard } = props;
|
|
1933
2951
|
const subjectCard = renderSubjectCard?.(conversation);
|
|
1934
2952
|
const loadView = resolveLoadView({ status: props.status, hasItems: props.items.length > 0 });
|
|
1935
|
-
const rootRef =
|
|
2953
|
+
const rootRef = useRef13(null);
|
|
1936
2954
|
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__ */
|
|
2955
|
+
return /* @__PURE__ */ jsxs25("div", { className: "cv-p cv-p-thread", "aria-busy": loadView.isLoading, ref: rootRef, children: [
|
|
2956
|
+
/* @__PURE__ */ jsx33(Header, { props }),
|
|
2957
|
+
subjectCard ? /* @__PURE__ */ jsx33("div", { className: "cv-p-thread__subject-card", children: subjectCard }) : null,
|
|
2958
|
+
/* @__PURE__ */ jsxs25("div", { className: "cv-p-thread__scroll", ref: props.scroll.ref, onScroll: props.scroll.onScroll, tabIndex: -1, children: [
|
|
2959
|
+
loadView.isLoading ? /* @__PURE__ */ jsx33(ParticipantLoading, { labels }) : null,
|
|
2960
|
+
loadView.hasError ? /* @__PURE__ */ jsx33(ParticipantLoadError, { labels, onRetry: props.refresh }) : null,
|
|
2961
|
+
hasMore && onLoadOlder ? /* @__PURE__ */ jsx33("button", { type: "button", className: "cv-p-thread__older", onClick: onLoadOlder, children: labels.loadOlder }) : null,
|
|
2962
|
+
/* @__PURE__ */ jsx33(ParticipantTimeline, { ...props }),
|
|
2963
|
+
/* @__PURE__ */ jsx33(
|
|
1946
2964
|
ParticipantScrollToLatest,
|
|
1947
2965
|
{
|
|
1948
2966
|
label: labels.scrollToLatest,
|
|
@@ -1953,19 +2971,19 @@ function ThreadBody(props) {
|
|
|
1953
2971
|
}
|
|
1954
2972
|
)
|
|
1955
2973
|
] }),
|
|
1956
|
-
/* @__PURE__ */
|
|
1957
|
-
/* @__PURE__ */
|
|
2974
|
+
/* @__PURE__ */ jsx33(ParticipantThreadLive, { notice: props.newMessagesCount > 0 ? labels.newMessages : "" }),
|
|
2975
|
+
/* @__PURE__ */ jsx33(Footer, { props })
|
|
1958
2976
|
] });
|
|
1959
2977
|
}
|
|
1960
2978
|
function ParticipantThread(props) {
|
|
1961
|
-
return /* @__PURE__ */
|
|
1962
|
-
/* @__PURE__ */
|
|
1963
|
-
/* @__PURE__ */
|
|
2979
|
+
return /* @__PURE__ */ jsxs25(ParticipantCopyAnnouncer, { children: [
|
|
2980
|
+
/* @__PURE__ */ jsx33(ThreadBody, { ...props }),
|
|
2981
|
+
/* @__PURE__ */ jsx33(TooltipLayer, {})
|
|
1964
2982
|
] });
|
|
1965
2983
|
}
|
|
1966
2984
|
|
|
1967
2985
|
// src/participant/useParticipantThreadController.ts
|
|
1968
|
-
import { useMemo as
|
|
2986
|
+
import { useMemo as useMemo5 } from "react";
|
|
1969
2987
|
|
|
1970
2988
|
// src/participant/bindAttachmentUrlResolver.ts
|
|
1971
2989
|
function bindAttachmentUrlResolver(api) {
|
|
@@ -2049,7 +3067,7 @@ function appendEntryToDraft(entry, current) {
|
|
|
2049
3067
|
}
|
|
2050
3068
|
|
|
2051
3069
|
// src/participant/useParticipantConversationView.ts
|
|
2052
|
-
import { useCallback as
|
|
3070
|
+
import { useCallback as useCallback9, useMemo as useMemo4 } from "react";
|
|
2053
3071
|
|
|
2054
3072
|
// src/participant/participantDrafts.ts
|
|
2055
3073
|
var EMPTY_PARTICIPANT_DRAFT = { text: "", files: [] };
|
|
@@ -2093,7 +3111,7 @@ function participantSendStatesReducer(states, { subject, action }) {
|
|
|
2093
3111
|
}
|
|
2094
3112
|
|
|
2095
3113
|
// src/participant/useParticipantThread.ts
|
|
2096
|
-
import { useCallback as
|
|
3114
|
+
import { useCallback as useCallback8, useEffect as useEffect18, useMemo as useMemo3, useReducer as useReducer5, useRef as useRef17 } from "react";
|
|
2097
3115
|
|
|
2098
3116
|
// src/participant/participantThreadState.ts
|
|
2099
3117
|
var INITIAL_PARTICIPANT_THREAD_STATE = {
|
|
@@ -2129,7 +3147,7 @@ function participantThreadReducer(state, action) {
|
|
|
2129
3147
|
}
|
|
2130
3148
|
|
|
2131
3149
|
// src/participant/useParticipantMarkRead.ts
|
|
2132
|
-
import { useEffect as
|
|
3150
|
+
import { useEffect as useEffect16 } from "react";
|
|
2133
3151
|
|
|
2134
3152
|
// src/participant/shouldMarkRead.ts
|
|
2135
3153
|
function shouldMarkParticipantRead(params) {
|
|
@@ -2141,12 +3159,12 @@ function shouldMarkParticipantRead(params) {
|
|
|
2141
3159
|
}
|
|
2142
3160
|
|
|
2143
3161
|
// src/participant/useDocumentVisibility.ts
|
|
2144
|
-
import { useEffect as
|
|
3162
|
+
import { useEffect as useEffect15, useState as useState9 } from "react";
|
|
2145
3163
|
function useDocumentVisibility() {
|
|
2146
|
-
const [visibilityState, setVisibilityState] =
|
|
3164
|
+
const [visibilityState, setVisibilityState] = useState9(
|
|
2147
3165
|
() => typeof document === "undefined" ? "visible" : document.visibilityState
|
|
2148
3166
|
);
|
|
2149
|
-
|
|
3167
|
+
useEffect15(() => {
|
|
2150
3168
|
function handleVisibilityChange() {
|
|
2151
3169
|
setVisibilityState(document.visibilityState);
|
|
2152
3170
|
}
|
|
@@ -2161,18 +3179,18 @@ function useDocumentVisibility() {
|
|
|
2161
3179
|
function useParticipantMarkRead(params) {
|
|
2162
3180
|
const { apiRef, subject, unreadCount, messageCount, onMarkedRead } = params;
|
|
2163
3181
|
const visibilityState = useDocumentVisibility();
|
|
2164
|
-
|
|
3182
|
+
useEffect16(() => {
|
|
2165
3183
|
if (!shouldMarkParticipantRead({ selected: subject, subject, visibilityState, unreadCount })) return;
|
|
2166
3184
|
void apiRef.current.markRead(subject).then(() => onMarkedRead?.(subject), () => void 0);
|
|
2167
3185
|
}, [apiRef, subject, visibilityState, unreadCount, messageCount, onMarkedRead]);
|
|
2168
3186
|
}
|
|
2169
3187
|
|
|
2170
3188
|
// src/participant/useParticipantRevalidation.ts
|
|
2171
|
-
import { useEffect as
|
|
3189
|
+
import { useEffect as useEffect17, useRef as useRef14 } from "react";
|
|
2172
3190
|
function useParticipantRevalidation(api, onRevalidate) {
|
|
2173
|
-
const handlerRef =
|
|
3191
|
+
const handlerRef = useRef14(onRevalidate);
|
|
2174
3192
|
handlerRef.current = onRevalidate;
|
|
2175
|
-
|
|
3193
|
+
useEffect17(() => {
|
|
2176
3194
|
function handleFocus() {
|
|
2177
3195
|
handlerRef.current();
|
|
2178
3196
|
}
|
|
@@ -2199,12 +3217,12 @@ function useParticipantRevalidation(api, onRevalidate) {
|
|
|
2199
3217
|
}
|
|
2200
3218
|
|
|
2201
3219
|
// src/participant/useParticipantSend.ts
|
|
2202
|
-
import { useCallback as
|
|
3220
|
+
import { useCallback as useCallback6, useRef as useRef15 } from "react";
|
|
2203
3221
|
function useParticipantSend(params) {
|
|
2204
3222
|
const { apiRef, subject, dispatchSend, onSentMessage } = params;
|
|
2205
|
-
const entriesRef =
|
|
3223
|
+
const entriesRef = useRef15(params.entries);
|
|
2206
3224
|
entriesRef.current = params.entries;
|
|
2207
|
-
const deliver =
|
|
3225
|
+
const deliver = useCallback6(
|
|
2208
3226
|
async (entry) => {
|
|
2209
3227
|
const { clientMessageId } = entry;
|
|
2210
3228
|
try {
|
|
@@ -2228,7 +3246,7 @@ function useParticipantSend(params) {
|
|
|
2228
3246
|
},
|
|
2229
3247
|
[apiRef, dispatchSend, onSentMessage]
|
|
2230
3248
|
);
|
|
2231
|
-
const send =
|
|
3249
|
+
const send = useCallback6(
|
|
2232
3250
|
async (content) => {
|
|
2233
3251
|
const entry = createParticipantSendEntry({
|
|
2234
3252
|
clientMessageId: crypto.randomUUID(),
|
|
@@ -2241,7 +3259,7 @@ function useParticipantSend(params) {
|
|
|
2241
3259
|
},
|
|
2242
3260
|
[deliver, dispatchSend, subject]
|
|
2243
3261
|
);
|
|
2244
|
-
const retry =
|
|
3262
|
+
const retry = useCallback6(
|
|
2245
3263
|
async (clientMessageId) => {
|
|
2246
3264
|
const entry = findFailedEntry(entriesRef.current, clientMessageId);
|
|
2247
3265
|
if (!entry) return;
|
|
@@ -2254,13 +3272,13 @@ function useParticipantSend(params) {
|
|
|
2254
3272
|
}
|
|
2255
3273
|
|
|
2256
3274
|
// src/participant/useParticipantUnread.ts
|
|
2257
|
-
import { useCallback as
|
|
3275
|
+
import { useCallback as useCallback7, useRef as useRef16, useState as useState10 } from "react";
|
|
2258
3276
|
function useParticipantUnread(params) {
|
|
2259
3277
|
const { messages, perspective, reportedUnreadCount, onMarkedRead } = params;
|
|
2260
|
-
const [acknowledgedCount, setAcknowledgedCount] =
|
|
2261
|
-
const messagesRef =
|
|
3278
|
+
const [acknowledgedCount, setAcknowledgedCount] = useState10(0);
|
|
3279
|
+
const messagesRef = useRef16(messages);
|
|
2262
3280
|
messagesRef.current = messages;
|
|
2263
|
-
const handleMarkedRead =
|
|
3281
|
+
const handleMarkedRead = useCallback7(
|
|
2264
3282
|
(subject) => {
|
|
2265
3283
|
if (perspective) setAcknowledgedCount(countOtherSideMessages(messagesRef.current, perspective));
|
|
2266
3284
|
onMarkedRead?.(subject);
|
|
@@ -2283,13 +3301,13 @@ function isForSubject(event, subject) {
|
|
|
2283
3301
|
function useParticipantThread(params) {
|
|
2284
3302
|
const { api, subject, channel, unreadCount = 0, onMarkedRead, sendEntries, dispatchSend, hostPending = [], perspective } = params;
|
|
2285
3303
|
const { subjectType, subjectId } = subject;
|
|
2286
|
-
const stableSubject =
|
|
3304
|
+
const stableSubject = useMemo3(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
2287
3305
|
const [state, dispatch] = useReducer5(participantThreadReducer, INITIAL_PARTICIPANT_THREAD_STATE);
|
|
2288
|
-
const apiRef =
|
|
3306
|
+
const apiRef = useRef17(api);
|
|
2289
3307
|
apiRef.current = api;
|
|
2290
|
-
const activeSubjectKey =
|
|
3308
|
+
const activeSubjectKey = useRef17("");
|
|
2291
3309
|
const confirmsRead = channelCapabilityFor(channel).confirmsRead;
|
|
2292
|
-
const refresh =
|
|
3310
|
+
const refresh = useCallback8(async () => {
|
|
2293
3311
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
2294
3312
|
dispatch({ type: "started" });
|
|
2295
3313
|
try {
|
|
@@ -2302,7 +3320,7 @@ function useParticipantThread(params) {
|
|
|
2302
3320
|
}
|
|
2303
3321
|
}, [stableSubject, subjectType, subjectId]);
|
|
2304
3322
|
const oldestMessageId = state.messages[0]?.id;
|
|
2305
|
-
const loadOlder =
|
|
3323
|
+
const loadOlder = useCallback8(async () => {
|
|
2306
3324
|
if (!oldestMessageId) return;
|
|
2307
3325
|
const requestKey = `${subjectType}:${subjectId}`;
|
|
2308
3326
|
try {
|
|
@@ -2314,9 +3332,9 @@ function useParticipantThread(params) {
|
|
|
2314
3332
|
if (activeSubjectKey.current === requestKey) dispatch({ type: "failed", error: describeError(error) });
|
|
2315
3333
|
}
|
|
2316
3334
|
}, [stableSubject, subjectType, subjectId, oldestMessageId]);
|
|
2317
|
-
const handleSentMessage =
|
|
3335
|
+
const handleSentMessage = useCallback8((message) => dispatch({ type: "sendConfirmed", message }), []);
|
|
2318
3336
|
const sending = useParticipantSend({ apiRef, subject: stableSubject, entries: sendEntries, dispatchSend, onSentMessage: handleSentMessage });
|
|
2319
|
-
|
|
3337
|
+
useEffect18(() => {
|
|
2320
3338
|
activeSubjectKey.current = `${subjectType}:${subjectId}`;
|
|
2321
3339
|
dispatch({ type: "reset" });
|
|
2322
3340
|
void refresh();
|
|
@@ -2324,7 +3342,7 @@ function useParticipantThread(params) {
|
|
|
2324
3342
|
useParticipantRevalidation(api, (event) => {
|
|
2325
3343
|
if (isForSubject(event, stableSubject)) void refresh();
|
|
2326
3344
|
});
|
|
2327
|
-
|
|
3345
|
+
useEffect18(() => {
|
|
2328
3346
|
if (sendEntries.length === 0) return;
|
|
2329
3347
|
dispatchSend({ type: "reflected", knownClientMessageIds: collectKnownClientMessageIds({ messages: state.messages, hostPending }) });
|
|
2330
3348
|
}, [sendEntries, state.messages, hostPending, dispatchSend]);
|
|
@@ -2336,7 +3354,7 @@ function useParticipantThread(params) {
|
|
|
2336
3354
|
messageCount: state.messages.length,
|
|
2337
3355
|
onMarkedRead: unread.handleMarkedRead
|
|
2338
3356
|
});
|
|
2339
|
-
const localPending =
|
|
3357
|
+
const localPending = useMemo3(() => toLocalPending(sendEntries), [sendEntries]);
|
|
2340
3358
|
return { ...state, localPending, confirmsRead, refresh, loadOlder, ...sending };
|
|
2341
3359
|
}
|
|
2342
3360
|
|
|
@@ -2345,7 +3363,7 @@ function useParticipantConversationView(params) {
|
|
|
2345
3363
|
const { api, subject, conversation, pendingMessages, sendStates, dispatchSendStates } = params;
|
|
2346
3364
|
const { subjectType, subjectId } = subject;
|
|
2347
3365
|
const sendEntries = selectSendEntries(sendStates, subject);
|
|
2348
|
-
const dispatchSend =
|
|
3366
|
+
const dispatchSend = useCallback9(
|
|
2349
3367
|
(action) => dispatchSendStates({ subject: { subjectType, subjectId }, action }),
|
|
2350
3368
|
[dispatchSendStates, subjectType, subjectId]
|
|
2351
3369
|
);
|
|
@@ -2360,7 +3378,7 @@ function useParticipantConversationView(params) {
|
|
|
2360
3378
|
hostPending: pendingMessages,
|
|
2361
3379
|
perspective: params.perspective
|
|
2362
3380
|
});
|
|
2363
|
-
const items =
|
|
3381
|
+
const items = useMemo4(
|
|
2364
3382
|
() => mergeParticipantMessages({
|
|
2365
3383
|
serverMessages: thread.messages,
|
|
2366
3384
|
hostPending: pendingMessages ?? [],
|
|
@@ -2373,9 +3391,9 @@ function useParticipantConversationView(params) {
|
|
|
2373
3391
|
}
|
|
2374
3392
|
|
|
2375
3393
|
// src/participant/useParticipantDraft.ts
|
|
2376
|
-
import { useState as
|
|
3394
|
+
import { useState as useState11 } from "react";
|
|
2377
3395
|
function useParticipantDraft(draftsRef, subject) {
|
|
2378
|
-
const [draft, setDraftState] =
|
|
3396
|
+
const [draft, setDraftState] = useState11(() => getDraft(draftsRef.current, subject));
|
|
2379
3397
|
function replaceDrafts(next) {
|
|
2380
3398
|
draftsRef.current = next;
|
|
2381
3399
|
setDraftState(getDraft(next, subject));
|
|
@@ -2394,8 +3412,29 @@ function useParticipantDraft(draftsRef, subject) {
|
|
|
2394
3412
|
return { draft, setText: (text) => update({ text }), setFiles: (files) => update({ files }), takeForSend, restoreFromEntry };
|
|
2395
3413
|
}
|
|
2396
3414
|
|
|
3415
|
+
// src/participant/useParticipantSendAudio.ts
|
|
3416
|
+
import { useCallback as useCallback10, useRef as useRef18 } from "react";
|
|
3417
|
+
function useParticipantSendAudio(send) {
|
|
3418
|
+
const sendRef = useRef18(send);
|
|
3419
|
+
sendRef.current = send;
|
|
3420
|
+
const inFlightRef = useRef18(/* @__PURE__ */ new Set());
|
|
3421
|
+
return useCallback10((file) => {
|
|
3422
|
+
const inFlight = inFlightRef.current;
|
|
3423
|
+
if (inFlight.has(file)) return;
|
|
3424
|
+
inFlight.add(file);
|
|
3425
|
+
void (async () => {
|
|
3426
|
+
try {
|
|
3427
|
+
await sendRef.current({ files: [file] });
|
|
3428
|
+
} catch {
|
|
3429
|
+
} finally {
|
|
3430
|
+
inFlight.delete(file);
|
|
3431
|
+
}
|
|
3432
|
+
})();
|
|
3433
|
+
}, []);
|
|
3434
|
+
}
|
|
3435
|
+
|
|
2397
3436
|
// src/participant/useParticipantThreadScroll.ts
|
|
2398
|
-
import { useEffect as
|
|
3437
|
+
import { useEffect as useEffect19, useRef as useRef20 } from "react";
|
|
2399
3438
|
|
|
2400
3439
|
// src/participant/participantNewMessages.ts
|
|
2401
3440
|
function collectServerMessageIds(items) {
|
|
@@ -2420,9 +3459,9 @@ function countNewIncomingMessages(previousIds, currentItems, perspective) {
|
|
|
2420
3459
|
}
|
|
2421
3460
|
|
|
2422
3461
|
// src/participant/useScrollAwayState.ts
|
|
2423
|
-
import { useCallback as
|
|
3462
|
+
import { useCallback as useCallback11 } from "react";
|
|
2424
3463
|
function useScrollAwayState(ref, isNearBottom) {
|
|
2425
|
-
const scrollToLatest =
|
|
3464
|
+
const scrollToLatest = useCallback11(() => {
|
|
2426
3465
|
const element = ref.current;
|
|
2427
3466
|
if (!element) return;
|
|
2428
3467
|
element.scrollTo(
|
|
@@ -2433,7 +3472,7 @@ function useScrollAwayState(ref, isNearBottom) {
|
|
|
2433
3472
|
}
|
|
2434
3473
|
|
|
2435
3474
|
// src/participant/useStickToBottom.ts
|
|
2436
|
-
import { useCallback as
|
|
3475
|
+
import { useCallback as useCallback12, useLayoutEffect as useLayoutEffect3, useRef as useRef19, useState as useState12 } from "react";
|
|
2437
3476
|
|
|
2438
3477
|
// src/participant/participantScroll.ts
|
|
2439
3478
|
var NEAR_BOTTOM_THRESHOLD_PX = 80;
|
|
@@ -2455,17 +3494,17 @@ function resolveScrollAction(params) {
|
|
|
2455
3494
|
|
|
2456
3495
|
// src/participant/useStickToBottom.ts
|
|
2457
3496
|
function useStickToBottom({ firstKey, lastKey }) {
|
|
2458
|
-
const ref =
|
|
2459
|
-
const snapshot =
|
|
3497
|
+
const ref = useRef19(null);
|
|
3498
|
+
const snapshot = useRef19({
|
|
2460
3499
|
hasPositioned: false,
|
|
2461
3500
|
firstKey: void 0,
|
|
2462
3501
|
lastKey: void 0,
|
|
2463
3502
|
scrollTop: 0,
|
|
2464
3503
|
scrollHeight: 0
|
|
2465
3504
|
});
|
|
2466
|
-
const [isNearBottom, setIsNearBottom] =
|
|
2467
|
-
const isNearBottomRef =
|
|
2468
|
-
const onScroll =
|
|
3505
|
+
const [isNearBottom, setIsNearBottom] = useState12(true);
|
|
3506
|
+
const isNearBottomRef = useRef19(true);
|
|
3507
|
+
const onScroll = useCallback12((event) => {
|
|
2469
3508
|
const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
|
|
2470
3509
|
const isNear = shouldStickToBottom({ scrollTop, clientHeight, scrollHeight, threshold: NEAR_BOTTOM_THRESHOLD_PX });
|
|
2471
3510
|
snapshot.current.scrollTop = scrollTop;
|
|
@@ -2520,8 +3559,8 @@ function useParticipantThreadScroll(items, perspective) {
|
|
|
2520
3559
|
});
|
|
2521
3560
|
const { isAwayFromBottom, scrollToLatest } = useScrollAwayState(base.ref, isNearBottom);
|
|
2522
3561
|
const scroll = { ...base, isAwayFromBottom, scrollToLatest };
|
|
2523
|
-
const seenIds =
|
|
2524
|
-
|
|
3562
|
+
const seenIds = useRef20(/* @__PURE__ */ new Set());
|
|
3563
|
+
useEffect19(() => {
|
|
2525
3564
|
if (isNearBottom) seenIds.current = collectServerMessageIds(items);
|
|
2526
3565
|
}, [isNearBottom, items]);
|
|
2527
3566
|
return { scroll, newMessagesCount: isNearBottom ? 0 : countNewIncomingMessages(seenIds.current, items, perspective) };
|
|
@@ -2536,22 +3575,26 @@ function buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending
|
|
|
2536
3575
|
...onRetryPending ? { retryHost: onRetryPending } : {}
|
|
2537
3576
|
};
|
|
2538
3577
|
}
|
|
3578
|
+
function buildEditHandler({ sendEntries, dispatchSend, restoreFromEntry }) {
|
|
3579
|
+
return (clientMessageId) => {
|
|
3580
|
+
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
3581
|
+
if (!entry) return;
|
|
3582
|
+
restoreFromEntry(entry);
|
|
3583
|
+
dispatchSend({ type: "discarded", clientMessageId });
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
2539
3586
|
function useParticipantThreadController(params) {
|
|
2540
3587
|
const { api, subject, conversation, draftsRef } = params;
|
|
2541
3588
|
const { draft, setText, setFiles, takeForSend, restoreFromEntry } = useParticipantDraft(draftsRef, subject);
|
|
2542
3589
|
const { thread, items, sendEntries, dispatchSend } = useParticipantConversationView(params);
|
|
2543
|
-
const resolveAttachmentUrl =
|
|
3590
|
+
const resolveAttachmentUrl = useMemo5(() => bindAttachmentUrlResolver(api), [api]);
|
|
2544
3591
|
const { scroll, newMessagesCount } = useParticipantThreadScroll(items, params.perspective);
|
|
3592
|
+
const handleSendAudio = useParticipantSendAudio(thread.send);
|
|
3593
|
+
const handleEdit = buildEditHandler({ sendEntries, dispatchSend, restoreFromEntry });
|
|
2545
3594
|
function handleSend() {
|
|
2546
3595
|
const content = takeForSend();
|
|
2547
3596
|
if (content) void thread.send(content);
|
|
2548
3597
|
}
|
|
2549
|
-
function handleEdit(clientMessageId) {
|
|
2550
|
-
const entry = findFailedEntry(sendEntries, clientMessageId);
|
|
2551
|
-
if (!entry) return;
|
|
2552
|
-
restoreFromEntry(entry);
|
|
2553
|
-
dispatchSend({ type: "discarded", clientMessageId });
|
|
2554
|
-
}
|
|
2555
3598
|
return {
|
|
2556
3599
|
conversation,
|
|
2557
3600
|
items,
|
|
@@ -2569,17 +3612,18 @@ function useParticipantThreadController(params) {
|
|
|
2569
3612
|
locale: params.locale,
|
|
2570
3613
|
onLoadOlder: () => void thread.loadOlder(),
|
|
2571
3614
|
pendingActions: buildPendingActions({ thread, dispatchSend, handleEdit, onRetryPending: params.onRetryPending }),
|
|
2572
|
-
perspective: params.perspective
|
|
3615
|
+
perspective: params.perspective,
|
|
3616
|
+
onSendAudio: handleSendAudio
|
|
2573
3617
|
};
|
|
2574
3618
|
}
|
|
2575
3619
|
|
|
2576
3620
|
// src/participant/ParticipantThreadScreen.tsx
|
|
2577
|
-
import { jsx as
|
|
3621
|
+
import { jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2578
3622
|
function useOpenedConversation(api, subject, isNeeded) {
|
|
2579
|
-
const [opened, setOpened] =
|
|
3623
|
+
const [opened, setOpened] = useState13({ status: "idle" });
|
|
2580
3624
|
const { subjectType, subjectId } = subject;
|
|
2581
3625
|
const canOpen = api.openConversation !== void 0;
|
|
2582
|
-
|
|
3626
|
+
useEffect20(() => {
|
|
2583
3627
|
if (!isNeeded || !canOpen) return;
|
|
2584
3628
|
let isCurrent = true;
|
|
2585
3629
|
setOpened({ status: "loading" });
|
|
@@ -2597,9 +3641,9 @@ function NotFound({
|
|
|
2597
3641
|
labels,
|
|
2598
3642
|
onBack
|
|
2599
3643
|
}) {
|
|
2600
|
-
return /* @__PURE__ */
|
|
2601
|
-
/* @__PURE__ */
|
|
2602
|
-
/* @__PURE__ */
|
|
3644
|
+
return /* @__PURE__ */ jsxs26("div", { className: "cv-p cv-p-thread", children: [
|
|
3645
|
+
/* @__PURE__ */ jsx34("header", { className: "cv-p-thread__header", children: onBack ? /* @__PURE__ */ jsx34(ParticipantBackButton, { label: labels.back, onBack }) : null }),
|
|
3646
|
+
/* @__PURE__ */ jsx34("p", { className: "cv-p-empty", children: labels.notFound })
|
|
2603
3647
|
] });
|
|
2604
3648
|
}
|
|
2605
3649
|
function ParticipantThreadScreen(props) {
|
|
@@ -2609,12 +3653,12 @@ function ParticipantThreadScreen(props) {
|
|
|
2609
3653
|
);
|
|
2610
3654
|
const opened = useOpenedConversation(api, selected, listed === void 0);
|
|
2611
3655
|
const conversation = listed ?? (opened.status === "found" ? opened.conversation : void 0);
|
|
2612
|
-
if (conversation) return /* @__PURE__ */
|
|
3656
|
+
if (conversation) return /* @__PURE__ */ jsx34(LoadedThread, { ...props, conversation });
|
|
2613
3657
|
const isResolving = opened.status === "loading" || api.openConversation !== void 0 && opened.status === "idle";
|
|
2614
3658
|
const isListLoading = inbox.status === "idle" || inbox.status === "loading";
|
|
2615
3659
|
if (isResolving || api.openConversation === void 0 && isListLoading)
|
|
2616
|
-
return /* @__PURE__ */
|
|
2617
|
-
return /* @__PURE__ */
|
|
3660
|
+
return /* @__PURE__ */ jsx34("div", { className: "cv-p cv-p-thread", "aria-busy": "true" });
|
|
3661
|
+
return /* @__PURE__ */ jsx34(NotFound, { labels, onBack: props.onBack });
|
|
2618
3662
|
}
|
|
2619
3663
|
function LoadedThread(props) {
|
|
2620
3664
|
const core = useParticipantThreadController({
|
|
@@ -2622,7 +3666,7 @@ function LoadedThread(props) {
|
|
|
2622
3666
|
subject: props.selected,
|
|
2623
3667
|
onMarkedRead: props.inbox.markSubjectRead
|
|
2624
3668
|
});
|
|
2625
|
-
return /* @__PURE__ */
|
|
3669
|
+
return /* @__PURE__ */ jsx34(
|
|
2626
3670
|
ParticipantThread,
|
|
2627
3671
|
{
|
|
2628
3672
|
...core,
|
|
@@ -2634,7 +3678,9 @@ function LoadedThread(props) {
|
|
|
2634
3678
|
renderSubjectIcon: props.renderSubjectIcon,
|
|
2635
3679
|
avatars: props.avatars,
|
|
2636
3680
|
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
2637
|
-
tail: props.tail
|
|
3681
|
+
tail: props.tail,
|
|
3682
|
+
audioRecording: props.audioRecording,
|
|
3683
|
+
renderComposerLock: props.renderComposerLock
|
|
2638
3684
|
}
|
|
2639
3685
|
);
|
|
2640
3686
|
}
|
|
@@ -2760,16 +3806,16 @@ function buildParticipantThemeStyle(theme) {
|
|
|
2760
3806
|
}
|
|
2761
3807
|
|
|
2762
3808
|
// src/participant/ParticipantConversationsScreen.tsx
|
|
2763
|
-
import { jsx as
|
|
3809
|
+
import { jsx as jsx35 } from "react/jsx-runtime";
|
|
2764
3810
|
var ALL_FILTER3 = "all";
|
|
2765
3811
|
function ParticipantConversationsScreen(props) {
|
|
2766
3812
|
const { selected, inbox, subjectGroups, classNames } = props;
|
|
2767
|
-
const [filter, setFilter] =
|
|
2768
|
-
const [searchQuery, setSearchQuery] =
|
|
2769
|
-
const draftsRef =
|
|
3813
|
+
const [filter, setFilter] = useState14(ALL_FILTER3);
|
|
3814
|
+
const [searchQuery, setSearchQuery] = useState14("");
|
|
3815
|
+
const draftsRef = useRef21(/* @__PURE__ */ new Map());
|
|
2770
3816
|
const [sendStates, dispatchSendStates] = useReducer6(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2771
|
-
const labels =
|
|
2772
|
-
const view =
|
|
3817
|
+
const labels = useMemo6(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
3818
|
+
const view = useMemo6(
|
|
2773
3819
|
() => groupParticipantConversations({
|
|
2774
3820
|
conversations: inbox.conversations,
|
|
2775
3821
|
subjectGroups,
|
|
@@ -2780,7 +3826,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2780
3826
|
);
|
|
2781
3827
|
const isSearchVisible = isInboxSearchVisible({ conversations: inbox.conversations, searchQuery });
|
|
2782
3828
|
const rootClassName = ["cv-p-root", props.className, classNames?.root].filter(Boolean).join(" ");
|
|
2783
|
-
return /* @__PURE__ */
|
|
3829
|
+
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
3830
|
ParticipantInbox,
|
|
2785
3831
|
{
|
|
2786
3832
|
view,
|
|
@@ -2797,7 +3843,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2797
3843
|
renderSubjectIcon: props.renderSubjectIcon,
|
|
2798
3844
|
search: { value: searchQuery, onChange: setSearchQuery, isVisible: isSearchVisible }
|
|
2799
3845
|
}
|
|
2800
|
-
) }) : /* @__PURE__ */
|
|
3846
|
+
) }) : /* @__PURE__ */ jsx35("div", { className: ["cv-p-root__screen", classNames?.thread].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx35(
|
|
2801
3847
|
ParticipantThreadScreen,
|
|
2802
3848
|
{
|
|
2803
3849
|
...props,
|
|
@@ -2812,7 +3858,7 @@ function ParticipantConversationsScreen(props) {
|
|
|
2812
3858
|
}
|
|
2813
3859
|
|
|
2814
3860
|
// src/participant/useParticipantInbox.ts
|
|
2815
|
-
import { useCallback as
|
|
3861
|
+
import { useCallback as useCallback13, useEffect as useEffect21, useReducer as useReducer7 } from "react";
|
|
2816
3862
|
|
|
2817
3863
|
// src/participant/participantInboxState.ts
|
|
2818
3864
|
var INITIAL_PARTICIPANT_INBOX_STATE = { status: "idle", conversations: [] };
|
|
@@ -2863,7 +3909,7 @@ function describeError2(error) {
|
|
|
2863
3909
|
}
|
|
2864
3910
|
function useParticipantInbox(api) {
|
|
2865
3911
|
const [state, dispatch] = useReducer7(participantInboxReducer, INITIAL_PARTICIPANT_INBOX_STATE);
|
|
2866
|
-
const refresh =
|
|
3912
|
+
const refresh = useCallback13(async () => {
|
|
2867
3913
|
dispatch({ type: "started" });
|
|
2868
3914
|
try {
|
|
2869
3915
|
const page = await api.listConversations();
|
|
@@ -2873,7 +3919,7 @@ function useParticipantInbox(api) {
|
|
|
2873
3919
|
}
|
|
2874
3920
|
}, [api]);
|
|
2875
3921
|
const { nextCursor } = state;
|
|
2876
|
-
const loadMore =
|
|
3922
|
+
const loadMore = useCallback13(async () => {
|
|
2877
3923
|
if (!nextCursor) return;
|
|
2878
3924
|
try {
|
|
2879
3925
|
const page = await api.listConversations({ cursor: nextCursor });
|
|
@@ -2882,10 +3928,10 @@ function useParticipantInbox(api) {
|
|
|
2882
3928
|
dispatch({ type: "failed", error: describeError2(error) });
|
|
2883
3929
|
}
|
|
2884
3930
|
}, [api, nextCursor]);
|
|
2885
|
-
const markSubjectRead =
|
|
3931
|
+
const markSubjectRead = useCallback13((subject) => {
|
|
2886
3932
|
dispatch({ type: "markedRead", subject });
|
|
2887
3933
|
}, []);
|
|
2888
|
-
|
|
3934
|
+
useEffect21(() => {
|
|
2889
3935
|
void refresh();
|
|
2890
3936
|
}, [refresh]);
|
|
2891
3937
|
useParticipantRevalidation(api, () => {
|
|
@@ -2895,11 +3941,11 @@ function useParticipantInbox(api) {
|
|
|
2895
3941
|
}
|
|
2896
3942
|
|
|
2897
3943
|
// src/participant/ParticipantConversations.tsx
|
|
2898
|
-
import { jsx as
|
|
3944
|
+
import { jsx as jsx36 } from "react/jsx-runtime";
|
|
2899
3945
|
function ParticipantConversations(props) {
|
|
2900
3946
|
const inbox = useParticipantInbox(props.api);
|
|
2901
3947
|
const { status, conversations, hasMore, refresh, loadMore, markSubjectRead } = inbox;
|
|
2902
|
-
return /* @__PURE__ */
|
|
3948
|
+
return /* @__PURE__ */ jsx36(
|
|
2903
3949
|
ParticipantConversationsScreen,
|
|
2904
3950
|
{
|
|
2905
3951
|
...props,
|
|
@@ -2909,7 +3955,7 @@ function ParticipantConversations(props) {
|
|
|
2909
3955
|
}
|
|
2910
3956
|
|
|
2911
3957
|
// src/participant/ConversationThread.tsx
|
|
2912
|
-
import { useCallback as
|
|
3958
|
+
import { useCallback as useCallback14, useMemo as useMemo7, useReducer as useReducer8, useRef as useRef22 } from "react";
|
|
2913
3959
|
|
|
2914
3960
|
// src/participant/conversationThreadApi.ts
|
|
2915
3961
|
function toParticipantApi(api) {
|
|
@@ -2949,20 +3995,20 @@ function buildThreadSummary({
|
|
|
2949
3995
|
}
|
|
2950
3996
|
|
|
2951
3997
|
// src/participant/ConversationThread.tsx
|
|
2952
|
-
import { jsx as
|
|
3998
|
+
import { jsx as jsx37 } from "react/jsx-runtime";
|
|
2953
3999
|
function useConversationThreadCore(props) {
|
|
2954
4000
|
const { perspective = "participant" } = props;
|
|
2955
4001
|
const { subjectType, subjectId } = props.subject;
|
|
2956
|
-
const subject =
|
|
2957
|
-
const onMarkedReadRef =
|
|
4002
|
+
const subject = useMemo7(() => ({ subjectType, subjectId }), [subjectType, subjectId]);
|
|
4003
|
+
const onMarkedReadRef = useRef22(props.onMarkedRead);
|
|
2958
4004
|
onMarkedReadRef.current = props.onMarkedRead;
|
|
2959
|
-
const handleMarkedRead =
|
|
2960
|
-
const api =
|
|
2961
|
-
const draftsRef =
|
|
4005
|
+
const handleMarkedRead = useCallback14((marked) => onMarkedReadRef.current?.(marked), []);
|
|
4006
|
+
const api = useMemo7(() => toParticipantApi(props.api), [props.api]);
|
|
4007
|
+
const draftsRef = useRef22(/* @__PURE__ */ new Map());
|
|
2962
4008
|
const [sendStates, dispatchSendStates] = useReducer8(participantSendStatesReducer, /* @__PURE__ */ new Map());
|
|
2963
|
-
const labels =
|
|
4009
|
+
const labels = useMemo7(() => ({ ...DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, ...props.labels }), [props.labels]);
|
|
2964
4010
|
const { title, protocol, channels, status } = props;
|
|
2965
|
-
const conversation =
|
|
4011
|
+
const conversation = useMemo7(
|
|
2966
4012
|
() => buildThreadSummary({ subject, title, protocol, channels, status }),
|
|
2967
4013
|
[subject, title, protocol, channels, status]
|
|
2968
4014
|
);
|
|
@@ -2984,12 +4030,12 @@ function useConversationThreadCore(props) {
|
|
|
2984
4030
|
}
|
|
2985
4031
|
function ConversationThreadBody(props) {
|
|
2986
4032
|
const core = useConversationThreadCore(props);
|
|
2987
|
-
return /* @__PURE__ */
|
|
4033
|
+
return /* @__PURE__ */ jsx37(
|
|
2988
4034
|
"div",
|
|
2989
4035
|
{
|
|
2990
4036
|
className: ["cv-p-root", props.className].filter(Boolean).join(" "),
|
|
2991
4037
|
style: buildParticipantThemeStyle(props.theme),
|
|
2992
|
-
children: /* @__PURE__ */
|
|
4038
|
+
children: /* @__PURE__ */ jsx37("div", { className: "cv-p-root__screen", children: /* @__PURE__ */ jsx37(
|
|
2993
4039
|
ParticipantThread,
|
|
2994
4040
|
{
|
|
2995
4041
|
...core,
|
|
@@ -3001,17 +4047,21 @@ function ConversationThreadBody(props) {
|
|
|
3001
4047
|
renderAuthorAvatar: props.renderAuthorAvatar,
|
|
3002
4048
|
tail: props.tail,
|
|
3003
4049
|
counterpartLabel: props.counterpartLabel,
|
|
3004
|
-
headerActions: props.headerActions
|
|
4050
|
+
headerActions: props.headerActions,
|
|
4051
|
+
audioRecording: props.audioRecording,
|
|
4052
|
+
renderComposerLock: props.renderComposerLock
|
|
3005
4053
|
}
|
|
3006
4054
|
) })
|
|
3007
4055
|
}
|
|
3008
4056
|
);
|
|
3009
4057
|
}
|
|
3010
4058
|
function ConversationThread(props) {
|
|
3011
|
-
return /* @__PURE__ */
|
|
4059
|
+
return /* @__PURE__ */ jsx37(ConversationThreadBody, { ...props }, draftKey(props.subject));
|
|
3012
4060
|
}
|
|
3013
4061
|
export {
|
|
3014
4062
|
ConversationThread,
|
|
4063
|
+
DEFAULT_MAX_RECORDING_BYTES,
|
|
4064
|
+
DEFAULT_PARTICIPANT_AUDIO_LABELS,
|
|
3015
4065
|
DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS,
|
|
3016
4066
|
ParticipantConversations,
|
|
3017
4067
|
groupParticipantConversations
|