@natoe/colab 0.1.0 → 0.1.1

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/dist/index.mjs CHANGED
@@ -1919,107 +1919,6 @@ var styles6 = {
1919
1919
  fontStyle: "italic"
1920
1920
  }
1921
1921
  };
1922
- function useAudioRecorder() {
1923
- const [isRecording, setIsRecording] = useState(false);
1924
- const [duration, setDuration] = useState(0);
1925
- const [error, setError] = useState(null);
1926
- const mediaRecorderRef = useRef(null);
1927
- const chunksRef = useRef([]);
1928
- const streamRef = useRef(null);
1929
- const timerRef = useRef(null);
1930
- const startTimeRef = useRef(0);
1931
- const resolveRef = useRef(null);
1932
- const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
1933
- const cleanup = useCallback(() => {
1934
- if (timerRef.current) {
1935
- clearInterval(timerRef.current);
1936
- timerRef.current = null;
1937
- }
1938
- if (streamRef.current) {
1939
- streamRef.current.getTracks().forEach((track) => track.stop());
1940
- streamRef.current = null;
1941
- }
1942
- mediaRecorderRef.current = null;
1943
- chunksRef.current = [];
1944
- setIsRecording(false);
1945
- setDuration(0);
1946
- }, []);
1947
- const start = useCallback(async () => {
1948
- if (!isSupported) {
1949
- setError("Audio recording is not supported in this browser");
1950
- return;
1951
- }
1952
- try {
1953
- setError(null);
1954
- chunksRef.current = [];
1955
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1956
- streamRef.current = stream;
1957
- const mimeType = MediaRecorder.isTypeSupported(AUDIO_MIME_TYPE) ? AUDIO_MIME_TYPE : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
1958
- const recorder = new MediaRecorder(stream, { mimeType });
1959
- mediaRecorderRef.current = recorder;
1960
- recorder.ondataavailable = (event) => {
1961
- if (event.data.size > 0) {
1962
- chunksRef.current.push(event.data);
1963
- }
1964
- };
1965
- recorder.onerror = () => {
1966
- setError("Recording failed");
1967
- cleanup();
1968
- resolveRef.current?.(null);
1969
- resolveRef.current = null;
1970
- };
1971
- recorder.onstop = () => {
1972
- const finalDuration = Math.round((Date.now() - startTimeRef.current) / 1e3);
1973
- const blob = new Blob(chunksRef.current, { type: mimeType });
1974
- cleanup();
1975
- resolveRef.current?.({ blob, duration: finalDuration });
1976
- resolveRef.current = null;
1977
- };
1978
- recorder.start(250);
1979
- startTimeRef.current = Date.now();
1980
- setIsRecording(true);
1981
- timerRef.current = setInterval(() => {
1982
- const elapsed = Math.round((Date.now() - startTimeRef.current) / 1e3);
1983
- setDuration(elapsed);
1984
- }, 1e3);
1985
- } catch (err) {
1986
- if (err instanceof DOMException && err.name === "NotAllowedError") {
1987
- setError("Microphone access denied. Please allow microphone permissions.");
1988
- } else {
1989
- setError("Failed to start recording");
1990
- }
1991
- cleanup();
1992
- }
1993
- }, [isSupported, cleanup]);
1994
- const stop = useCallback(async () => {
1995
- return new Promise((resolve) => {
1996
- if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
1997
- resolve(null);
1998
- return;
1999
- }
2000
- resolveRef.current = resolve;
2001
- mediaRecorderRef.current.stop();
2002
- });
2003
- }, []);
2004
- const cancel = useCallback(() => {
2005
- if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
2006
- mediaRecorderRef.current.onstop = null;
2007
- mediaRecorderRef.current.stop();
2008
- }
2009
- cleanup();
2010
- resolveRef.current?.(null);
2011
- resolveRef.current = null;
2012
- }, [cleanup]);
2013
- return {
2014
- isRecording,
2015
- duration,
2016
- start,
2017
- stop,
2018
- cancel,
2019
- isSupported,
2020
- error
2021
- };
2022
- }
2023
1922
  function ReplyPreview({ message, onCancel, className }) {
2024
1923
  const preview = renderQuotedPreview(message);
2025
1924
  return /* @__PURE__ */ jsxs("div", { className, style: styles7.container, children: [
@@ -2112,23 +2011,10 @@ var styles7 = {
2112
2011
  flexShrink: 0
2113
2012
  }
2114
2013
  };
2115
- function MicIcon({ size = 20, color = "currentColor" }) {
2116
- return /* @__PURE__ */ jsx(
2117
- "svg",
2118
- {
2119
- xmlns: "http://www.w3.org/2000/svg",
2120
- width: size,
2121
- height: size,
2122
- viewBox: "0 0 24 24",
2123
- fill: color,
2124
- "aria-hidden": "true",
2125
- children: /* @__PURE__ */ jsx("path", { d: "M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" })
2126
- }
2127
- );
2128
- }
2129
2014
  function MessageInput({
2130
2015
  onSendText,
2131
- onSendAudio,
2016
+ // MIC DISABLED — onSendAudio still accepted but unused.
2017
+ // onSendAudio,
2132
2018
  onSendFile,
2133
2019
  onTyping,
2134
2020
  replyTo,
@@ -2144,7 +2030,6 @@ function MessageInput({
2144
2030
  const typingTimeoutRef = useRef(null);
2145
2031
  const isTypingRef = useRef(false);
2146
2032
  const sendingRef = useRef(false);
2147
- const { isRecording, duration, start: startRecording, stop: stopRecording, cancel: cancelRecording, isSupported: micSupported, error: micError } = useAudioRecorder();
2148
2033
  const handleTyping = useCallback(() => {
2149
2034
  if (!isTypingRef.current) {
2150
2035
  isTypingRef.current = true;
@@ -2193,17 +2078,6 @@ function MessageInput({
2193
2078
  },
2194
2079
  [handleSendText]
2195
2080
  );
2196
- const handleAudioStop = useCallback(async () => {
2197
- const result = await stopRecording();
2198
- if (result) {
2199
- setIsSending(true);
2200
- try {
2201
- await onSendAudio(result.blob, result.duration);
2202
- } finally {
2203
- setIsSending(false);
2204
- }
2205
- }
2206
- }, [stopRecording, onSendAudio]);
2207
2081
  const handleFileSelect = useCallback(
2208
2082
  async (e) => {
2209
2083
  const file = e.target.files?.[0];
@@ -2225,29 +2099,8 @@ function MessageInput({
2225
2099
  );
2226
2100
  return /* @__PURE__ */ jsxs("div", { className, style: styles8.container, children: [
2227
2101
  replyTo && onCancelReply && /* @__PURE__ */ jsx(ReplyPreview, { message: replyTo, onCancel: onCancelReply }),
2228
- (fileError || micError) && /* @__PURE__ */ jsx("div", { style: styles8.error, children: fileError || micError }),
2229
- isRecording ? /* @__PURE__ */ jsxs("div", { style: styles8.recordingBar, children: [
2230
- /* @__PURE__ */ jsxs("div", { style: styles8.recordingIndicator, children: [
2231
- /* @__PURE__ */ jsx("span", { style: styles8.recordingDot }),
2232
- "Recording ",
2233
- formatDuration2(duration)
2234
- ] }),
2235
- /* @__PURE__ */ jsxs("div", { style: styles8.recordingActions, children: [
2236
- /* @__PURE__ */ jsx("button", { onClick: cancelRecording, style: styles8.cancelButton, type: "button", children: "Cancel" }),
2237
- /* @__PURE__ */ jsx("button", { onClick: handleAudioStop, style: styles8.stopButton, type: "button", children: "Send" })
2238
- ] })
2239
- ] }) : /* @__PURE__ */ jsxs("div", { style: styles8.inputBar, children: [
2240
- micSupported && /* @__PURE__ */ jsx(
2241
- "button",
2242
- {
2243
- onClick: startRecording,
2244
- disabled: disabled || isSending,
2245
- style: styles8.iconButton,
2246
- title: "Record voice message",
2247
- type: "button",
2248
- children: /* @__PURE__ */ jsx(MicIcon, { size: 20, color: "#374151" })
2249
- }
2250
- ),
2102
+ fileError && /* @__PURE__ */ jsx("div", { style: styles8.error, children: fileError }),
2103
+ /* @__PURE__ */ jsxs("div", { style: styles8.inputBar, children: [
2251
2104
  /* @__PURE__ */ jsx(
2252
2105
  "button",
2253
2106
  {
@@ -2300,11 +2153,6 @@ function MessageInput({
2300
2153
  ] })
2301
2154
  ] });
2302
2155
  }
2303
- function formatDuration2(seconds) {
2304
- const m = Math.floor(seconds / 60);
2305
- const s = seconds % 60;
2306
- return `${m}:${s.toString().padStart(2, "0")}`;
2307
- }
2308
2156
  var styles8 = {
2309
2157
  container: {
2310
2158
  borderTop: "1px solid #e5e7eb",
@@ -2360,53 +2208,7 @@ var styles8 = {
2360
2208
  borderRadius: "50%",
2361
2209
  cursor: "pointer",
2362
2210
  flexShrink: 0
2363
- },
2364
- recordingBar: {
2365
- display: "flex",
2366
- justifyContent: "space-between",
2367
- alignItems: "center",
2368
- padding: "12px 16px",
2369
- backgroundColor: "#fef2f2"
2370
- },
2371
- recordingIndicator: {
2372
- display: "flex",
2373
- alignItems: "center",
2374
- gap: "8px",
2375
- fontSize: "14px",
2376
- color: "#dc2626",
2377
- fontWeight: 500
2378
- },
2379
- recordingDot: {
2380
- width: "8px",
2381
- height: "8px",
2382
- borderRadius: "50%",
2383
- backgroundColor: "#dc2626",
2384
- animation: "pulse 1.5s infinite"
2385
- },
2386
- recordingActions: {
2387
- display: "flex",
2388
- gap: "8px"
2389
- },
2390
- cancelButton: {
2391
- padding: "6px 14px",
2392
- fontSize: "13px",
2393
- color: "#6b7280",
2394
- backgroundColor: "#ffffff",
2395
- border: "1px solid #d1d5db",
2396
- borderRadius: "6px",
2397
- cursor: "pointer"
2398
- },
2399
- stopButton: {
2400
- padding: "6px 14px",
2401
- fontSize: "13px",
2402
- fontWeight: 500,
2403
- color: "#ffffff",
2404
- backgroundColor: "#dc2626",
2405
- border: "none",
2406
- borderRadius: "6px",
2407
- cursor: "pointer"
2408
- }
2409
- };
2211
+ }};
2410
2212
  function ParticipantsList({
2411
2213
  participants,
2412
2214
  currentUserId,
@@ -3708,7 +3510,7 @@ function InlineAudioPlayer({ url, duration }) {
3708
3510
  /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "70%" } }),
3709
3511
  /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "40%" } })
3710
3512
  ] }),
3711
- /* @__PURE__ */ jsx("span", { style: styles13.audioDuration, children: formatDuration3(displayTime) }),
3513
+ /* @__PURE__ */ jsx("span", { style: styles13.audioDuration, children: formatDuration2(displayTime) }),
3712
3514
  /* @__PURE__ */ jsx("audio", { ref: audioRef, src: url, preload: "metadata" })
3713
3515
  ] });
3714
3516
  }
@@ -3724,7 +3526,7 @@ function renderMessagePreview(message) {
3724
3526
  return message.body;
3725
3527
  }
3726
3528
  }
3727
- function formatDuration3(seconds) {
3529
+ function formatDuration2(seconds) {
3728
3530
  const safe = Math.max(0, Math.floor(seconds));
3729
3531
  const m = Math.floor(safe / 60);
3730
3532
  const s = safe % 60;
@@ -3732,7 +3534,8 @@ function formatDuration3(seconds) {
3732
3534
  }
3733
3535
  function InlineInputBar({
3734
3536
  onSend,
3735
- onSendAudio,
3537
+ // MIC DISABLED — onSendAudio accepted but unused.
3538
+ // onSendAudio,
3736
3539
  placeholder,
3737
3540
  unreadCount,
3738
3541
  isLive,
@@ -3742,15 +3545,6 @@ function InlineInputBar({
3742
3545
  const [text, setText] = useState("");
3743
3546
  const [isSending, setIsSending] = useState(false);
3744
3547
  const inputRef = useRef(null);
3745
- const {
3746
- isRecording,
3747
- duration,
3748
- start: startRecording,
3749
- stop: stopRecording,
3750
- cancel: cancelRecording,
3751
- isSupported: micSupported,
3752
- error: micError
3753
- } = useAudioRecorder();
3754
3548
  const handleSend = useCallback(async () => {
3755
3549
  const trimmed = text.trim();
3756
3550
  if (!trimmed || isSending) return;
@@ -3772,46 +3566,6 @@ function InlineInputBar({
3772
3566
  },
3773
3567
  [handleSend]
3774
3568
  );
3775
- const handleStopRecording = useCallback(async () => {
3776
- const result = await stopRecording();
3777
- if (result) {
3778
- setIsSending(true);
3779
- try {
3780
- await onSendAudio(result.blob, result.duration);
3781
- } finally {
3782
- setIsSending(false);
3783
- }
3784
- }
3785
- }, [stopRecording, onSendAudio]);
3786
- if (isRecording) {
3787
- return /* @__PURE__ */ jsxs("div", { style: styles13.recordingBar, children: [
3788
- /* @__PURE__ */ jsx("span", { style: styles13.recordingDot }),
3789
- /* @__PURE__ */ jsxs("span", { style: styles13.recordingLabel, children: [
3790
- "Recording ",
3791
- formatDuration3(duration)
3792
- ] }),
3793
- /* @__PURE__ */ jsx(
3794
- "button",
3795
- {
3796
- onClick: cancelRecording,
3797
- style: styles13.recordingCancel,
3798
- title: "Cancel recording",
3799
- type: "button",
3800
- children: "Cancel"
3801
- }
3802
- ),
3803
- /* @__PURE__ */ jsx(
3804
- "button",
3805
- {
3806
- onClick: handleStopRecording,
3807
- style: styles13.recordingSend,
3808
- title: "Send voice message",
3809
- type: "button",
3810
- children: "Send"
3811
- }
3812
- )
3813
- ] });
3814
- }
3815
3569
  return /* @__PURE__ */ jsxs("div", { style: styles13.inputBar, children: [
3816
3570
  /* @__PURE__ */ jsx(
3817
3571
  "input",
@@ -3821,24 +3575,13 @@ function InlineInputBar({
3821
3575
  value: text,
3822
3576
  onChange: (e) => setText(e.target.value),
3823
3577
  onKeyDown: handleKeyDown,
3824
- placeholder: micError ?? placeholder,
3578
+ placeholder,
3825
3579
  disabled: isSending,
3826
3580
  style: styles13.input
3827
3581
  }
3828
3582
  ),
3829
3583
  unreadCount > 0 && /* @__PURE__ */ jsx("span", { style: styles13.unreadBadge, title: `${unreadCount} unread`, children: unreadCount > 99 ? "99+" : unreadCount }),
3830
3584
  isLive && /* @__PURE__ */ jsx("span", { style: styles13.liveDot, title: "Live updates active" }),
3831
- micSupported && !text.trim() && /* @__PURE__ */ jsx(
3832
- "button",
3833
- {
3834
- onClick: startRecording,
3835
- disabled: isSending,
3836
- style: styles13.micButton,
3837
- title: "Record voice message",
3838
- type: "button",
3839
- children: /* @__PURE__ */ jsx(MicIcon, { size: 16, color: "#374151" })
3840
- }
3841
- ),
3842
3585
  text.trim() && /* @__PURE__ */ jsx(
3843
3586
  "button",
3844
3587
  {
@@ -4016,19 +3759,6 @@ var styles13 = {
4016
3759
  borderRadius: "50%",
4017
3760
  backgroundColor: "#22c55e"
4018
3761
  },
4019
- micButton: {
4020
- flexShrink: 0,
4021
- width: "28px",
4022
- height: "28px",
4023
- display: "flex",
4024
- alignItems: "center",
4025
- justifyContent: "center",
4026
- fontSize: "13px",
4027
- backgroundColor: "#f3f4f6",
4028
- border: "none",
4029
- borderRadius: "50%",
4030
- cursor: "pointer"
4031
- },
4032
3762
  sendButton: {
4033
3763
  flexShrink: 0,
4034
3764
  width: "28px",
@@ -4056,54 +3786,7 @@ var styles13 = {
4056
3786
  border: "1px solid #e5e7eb",
4057
3787
  borderRadius: "50%",
4058
3788
  cursor: "pointer"
4059
- },
4060
- // ── Recording mode ──
4061
- recordingBar: {
4062
- display: "flex",
4063
- alignItems: "center",
4064
- gap: "8px",
4065
- padding: "6px 10px",
4066
- backgroundColor: "#fef2f2",
4067
- border: "1px solid #fecaca",
4068
- borderRadius: "16px"
4069
- },
4070
- recordingDot: {
4071
- width: "8px",
4072
- height: "8px",
4073
- borderRadius: "50%",
4074
- backgroundColor: "#dc2626",
4075
- flexShrink: 0,
4076
- animation: "pulse 1.5s infinite"
4077
- },
4078
- recordingLabel: {
4079
- flex: 1,
4080
- fontSize: "12px",
4081
- fontWeight: 500,
4082
- color: "#dc2626",
4083
- fontVariantNumeric: "tabular-nums"
4084
- },
4085
- recordingCancel: {
4086
- padding: "4px 10px",
4087
- fontSize: "11px",
4088
- color: "#6b7280",
4089
- backgroundColor: "#ffffff",
4090
- border: "1px solid #d1d5db",
4091
- borderRadius: "12px",
4092
- cursor: "pointer",
4093
- flexShrink: 0
4094
- },
4095
- recordingSend: {
4096
- padding: "4px 10px",
4097
- fontSize: "11px",
4098
- fontWeight: 500,
4099
- color: "#ffffff",
4100
- backgroundColor: "#dc2626",
4101
- border: "none",
4102
- borderRadius: "12px",
4103
- cursor: "pointer",
4104
- flexShrink: 0
4105
- }
4106
- };
3789
+ }};
4107
3790
  function useConversationList(options) {
4108
3791
  const enabled = options?.enabled ?? true;
4109
3792
  const { fetchConversationList, config, totalUnread: serverTotalUnread } = useCollab();
@@ -4777,6 +4460,107 @@ function useMessages({
4777
4460
  reset
4778
4461
  };
4779
4462
  }
4463
+ function useAudioRecorder() {
4464
+ const [isRecording, setIsRecording] = useState(false);
4465
+ const [duration, setDuration] = useState(0);
4466
+ const [error, setError] = useState(null);
4467
+ const mediaRecorderRef = useRef(null);
4468
+ const chunksRef = useRef([]);
4469
+ const streamRef = useRef(null);
4470
+ const timerRef = useRef(null);
4471
+ const startTimeRef = useRef(0);
4472
+ const resolveRef = useRef(null);
4473
+ const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
4474
+ const cleanup = useCallback(() => {
4475
+ if (timerRef.current) {
4476
+ clearInterval(timerRef.current);
4477
+ timerRef.current = null;
4478
+ }
4479
+ if (streamRef.current) {
4480
+ streamRef.current.getTracks().forEach((track) => track.stop());
4481
+ streamRef.current = null;
4482
+ }
4483
+ mediaRecorderRef.current = null;
4484
+ chunksRef.current = [];
4485
+ setIsRecording(false);
4486
+ setDuration(0);
4487
+ }, []);
4488
+ const start = useCallback(async () => {
4489
+ if (!isSupported) {
4490
+ setError("Audio recording is not supported in this browser");
4491
+ return;
4492
+ }
4493
+ try {
4494
+ setError(null);
4495
+ chunksRef.current = [];
4496
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
4497
+ streamRef.current = stream;
4498
+ const mimeType = MediaRecorder.isTypeSupported(AUDIO_MIME_TYPE) ? AUDIO_MIME_TYPE : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
4499
+ const recorder = new MediaRecorder(stream, { mimeType });
4500
+ mediaRecorderRef.current = recorder;
4501
+ recorder.ondataavailable = (event) => {
4502
+ if (event.data.size > 0) {
4503
+ chunksRef.current.push(event.data);
4504
+ }
4505
+ };
4506
+ recorder.onerror = () => {
4507
+ setError("Recording failed");
4508
+ cleanup();
4509
+ resolveRef.current?.(null);
4510
+ resolveRef.current = null;
4511
+ };
4512
+ recorder.onstop = () => {
4513
+ const finalDuration = Math.round((Date.now() - startTimeRef.current) / 1e3);
4514
+ const blob = new Blob(chunksRef.current, { type: mimeType });
4515
+ cleanup();
4516
+ resolveRef.current?.({ blob, duration: finalDuration });
4517
+ resolveRef.current = null;
4518
+ };
4519
+ recorder.start(250);
4520
+ startTimeRef.current = Date.now();
4521
+ setIsRecording(true);
4522
+ timerRef.current = setInterval(() => {
4523
+ const elapsed = Math.round((Date.now() - startTimeRef.current) / 1e3);
4524
+ setDuration(elapsed);
4525
+ }, 1e3);
4526
+ } catch (err) {
4527
+ if (err instanceof DOMException && err.name === "NotAllowedError") {
4528
+ setError("Microphone access denied. Please allow microphone permissions.");
4529
+ } else {
4530
+ setError("Failed to start recording");
4531
+ }
4532
+ cleanup();
4533
+ }
4534
+ }, [isSupported, cleanup]);
4535
+ const stop = useCallback(async () => {
4536
+ return new Promise((resolve) => {
4537
+ if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
4538
+ resolve(null);
4539
+ return;
4540
+ }
4541
+ resolveRef.current = resolve;
4542
+ mediaRecorderRef.current.stop();
4543
+ });
4544
+ }, []);
4545
+ const cancel = useCallback(() => {
4546
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
4547
+ mediaRecorderRef.current.onstop = null;
4548
+ mediaRecorderRef.current.stop();
4549
+ }
4550
+ cleanup();
4551
+ resolveRef.current?.(null);
4552
+ resolveRef.current = null;
4553
+ }, [cleanup]);
4554
+ return {
4555
+ isRecording,
4556
+ duration,
4557
+ start,
4558
+ stop,
4559
+ cancel,
4560
+ isSupported,
4561
+ error
4562
+ };
4563
+ }
4780
4564
  function useUnreadCount() {
4781
4565
  const { socket, totalUnread } = useCollab();
4782
4566
  const [counts, setCounts] = useState({});