stream-chat-react 12.0.0-rc.1 → 12.0.0-rc.10

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.
Files changed (148) hide show
  1. package/README.md +11 -1
  2. package/dist/components/Attachment/components/WaveProgressBar.d.ts +3 -1
  3. package/dist/components/Attachment/components/WaveProgressBar.js +44 -9
  4. package/dist/components/Channel/Channel.js +19 -32
  5. package/dist/components/Channel/channelState.js +1 -0
  6. package/dist/components/Channel/hooks/useCreateChannelStateContext.js +1 -1
  7. package/dist/components/ChannelList/ChannelList.js +1 -1
  8. package/dist/components/Chat/Chat.d.ts +1 -1
  9. package/dist/components/Chat/hooks/useChat.d.ts +2 -2
  10. package/dist/components/Chat/hooks/useChat.js +1 -2
  11. package/dist/components/Chat/hooks/useCreateChatClient.d.ts +4 -2
  12. package/dist/components/Chat/hooks/useCreateChatClient.js +5 -4
  13. package/dist/components/DateSeparator/DateSeparator.d.ts +1 -1
  14. package/dist/components/DateSeparator/DateSeparator.js +1 -1
  15. package/dist/components/EventComponent/EventComponent.d.ts +1 -1
  16. package/dist/components/EventComponent/EventComponent.js +1 -1
  17. package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +9 -3
  18. package/dist/components/MediaRecorder/AudioRecorder/AudioRecordingInProgress.js +3 -0
  19. package/dist/components/MediaRecorder/classes/MediaRecorderController.d.ts +6 -7
  20. package/dist/components/MediaRecorder/classes/MediaRecorderController.js +0 -5
  21. package/dist/components/MediaRecorder/hooks/index.d.ts +1 -1
  22. package/dist/components/MediaRecorder/hooks/useMediaRecorder.d.ts +1 -2
  23. package/dist/components/MediaRecorder/hooks/useMediaRecorder.js +1 -1
  24. package/dist/components/MediaRecorder/index.d.ts +1 -0
  25. package/dist/components/MediaRecorder/transcode/index.d.ts +6 -5
  26. package/dist/components/MediaRecorder/transcode/index.js +5 -15
  27. package/dist/components/Message/MessageSimple.js +1 -1
  28. package/dist/components/Message/MessageStatus.js +3 -2
  29. package/dist/components/Message/MessageTimestamp.d.ts +1 -2
  30. package/dist/components/Message/MessageTimestamp.js +0 -1
  31. package/dist/components/Message/Timestamp.d.ts +1 -2
  32. package/dist/components/Message/Timestamp.js +4 -5
  33. package/dist/components/Message/renderText/rehypePlugins/mentionsMarkdownPlugin.d.ts +1 -1
  34. package/dist/components/Message/renderText/remarkPlugins/htmlToTextPlugin.d.ts +1 -1
  35. package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.d.ts +1 -1
  36. package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.js +2 -6
  37. package/dist/components/Message/renderText/renderText.d.ts +2 -2
  38. package/dist/components/Message/renderText/renderText.js +8 -6
  39. package/dist/components/Message/utils.js +2 -0
  40. package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.js +23 -27
  41. package/dist/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.d.ts +1 -0
  42. package/dist/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.js +1 -1
  43. package/dist/components/MessageInput/AttachmentPreviewList/ImageAttachmentPreview.js +2 -1
  44. package/dist/components/MessageInput/MessageInput.d.ts +4 -6
  45. package/dist/components/MessageInput/MessageInputFlat.js +4 -7
  46. package/dist/components/MessageInput/hooks/useAttachments.d.ts +1 -5
  47. package/dist/components/MessageInput/hooks/useAttachments.js +65 -52
  48. package/dist/components/MessageInput/hooks/useCreateMessageInputContext.js +2 -19
  49. package/dist/components/MessageInput/hooks/useMessageInputState.d.ts +2 -35
  50. package/dist/components/MessageInput/hooks/useMessageInputState.js +2 -107
  51. package/dist/components/MessageInput/hooks/usePasteHandler.js +1 -3
  52. package/dist/components/MessageInput/hooks/useSubmitHandler.js +19 -71
  53. package/dist/components/MessageInput/hooks/useTimeElapsed.js +5 -4
  54. package/dist/components/MessageInput/hooks/utils.d.ts +1 -2
  55. package/dist/components/MessageInput/icons.d.ts +0 -1
  56. package/dist/components/MessageInput/icons.js +0 -3
  57. package/dist/components/MessageInput/types.d.ts +3 -30
  58. package/dist/components/MessageList/MessageList.d.ts +3 -1
  59. package/dist/components/MessageList/MessageList.js +2 -1
  60. package/dist/components/MessageList/VirtualizedMessageList.d.ts +3 -1
  61. package/dist/components/MessageList/VirtualizedMessageList.js +3 -3
  62. package/dist/components/MessageList/VirtualizedMessageListComponents.js +3 -2
  63. package/dist/components/MessageList/hooks/MessageList/useEnrichedMessages.d.ts +2 -1
  64. package/dist/components/MessageList/hooks/MessageList/useEnrichedMessages.js +3 -3
  65. package/dist/components/MessageList/utils.d.ts +1 -1
  66. package/dist/components/MessageList/utils.js +17 -7
  67. package/dist/components/ReactFileUtilities/types.d.ts +0 -29
  68. package/dist/components/ReactFileUtilities/utils.d.ts +2 -0
  69. package/dist/components/ReactFileUtilities/utils.js +2 -0
  70. package/dist/components/Thread/Thread.d.ts +0 -2
  71. package/dist/components/Thread/Thread.js +1 -2
  72. package/dist/components/UtilityComponents/ErrorBoundary.d.ts +16 -0
  73. package/dist/components/UtilityComponents/ErrorBoundary.js +19 -0
  74. package/dist/components/UtilityComponents/index.d.ts +1 -0
  75. package/dist/components/UtilityComponents/index.js +1 -0
  76. package/dist/components/Window/Window.d.ts +1 -3
  77. package/dist/components/Window/Window.js +2 -2
  78. package/dist/context/ChannelActionContext.d.ts +2 -2
  79. package/dist/context/MessageInputContext.d.ts +1 -5
  80. package/dist/context/TranslationContext.d.ts +1 -11
  81. package/dist/context/TranslationContext.js +1 -9
  82. package/dist/css/v2/emoji-replacement.css +1 -1
  83. package/dist/css/v2/index.css +2 -2
  84. package/dist/css/v2/index.layout.css +2 -2
  85. package/dist/i18n/Streami18n.d.ts +3 -3
  86. package/dist/i18n/Streami18n.js +1 -2
  87. package/dist/i18n/de.json +3 -1
  88. package/dist/i18n/en.json +3 -1
  89. package/dist/i18n/es.json +3 -1
  90. package/dist/i18n/fr.json +3 -1
  91. package/dist/i18n/hi.json +3 -1
  92. package/dist/i18n/index.d.ts +2 -1
  93. package/dist/i18n/index.js +2 -0
  94. package/dist/i18n/it.json +3 -1
  95. package/dist/i18n/ja.json +3 -1
  96. package/dist/i18n/ko.json +3 -1
  97. package/dist/i18n/nl.json +3 -1
  98. package/dist/i18n/pt.json +3 -1
  99. package/dist/i18n/ru.json +3 -1
  100. package/dist/i18n/tr.json +3 -1
  101. package/dist/i18n/types.d.ts +26 -0
  102. package/dist/i18n/types.js +1 -0
  103. package/dist/i18n/utils.d.ts +9 -20
  104. package/dist/i18n/utils.js +10 -1
  105. package/dist/index.browser.cjs +47221 -0
  106. package/dist/index.browser.cjs.map +7 -0
  107. package/dist/{index.cjs.js → index.node.cjs} +17604 -29018
  108. package/dist/index.node.cjs.map +7 -0
  109. package/dist/{components → plugins}/Emojis/EmojiPicker.js +1 -1
  110. package/dist/plugins/Emojis/icons.d.ts +2 -0
  111. package/dist/plugins/Emojis/icons.js +4 -0
  112. package/dist/plugins/Emojis/index.browser.cjs +167 -0
  113. package/dist/plugins/Emojis/index.browser.cjs.map +7 -0
  114. package/dist/plugins/Emojis/index.d.ts +2 -0
  115. package/dist/plugins/Emojis/index.js +2 -0
  116. package/dist/{components/Emojis/index.cjs.js → plugins/Emojis/index.node.cjs} +31 -192
  117. package/dist/plugins/Emojis/index.node.cjs.map +7 -0
  118. package/dist/plugins/encoders/mp3.browser.cjs +105 -0
  119. package/dist/plugins/encoders/mp3.browser.cjs.map +7 -0
  120. package/dist/{components/MediaRecorder/transcode → plugins/encoders}/mp3.js +3 -3
  121. package/dist/plugins/encoders/mp3.node.cjs +109 -0
  122. package/dist/plugins/encoders/mp3.node.cjs.map +7 -0
  123. package/dist/scss/v2/Autocomplete/Autocomplete-layout.scss +1 -1
  124. package/dist/scss/v2/Autocomplete/Autocomplete-theme.scss +4 -2
  125. package/dist/scss/v2/Avatar/Avatar-layout.scss +31 -23
  126. package/dist/scss/v2/Channel/Channel-layout.scss +0 -4
  127. package/dist/scss/v2/ChannelList/ChannelList-layout.scss +0 -5
  128. package/dist/scss/v2/ChannelSearch/ChannelSearch-layout.scss +1 -0
  129. package/dist/scss/v2/EditMessageForm/EditMessageForm-theme.scss +9 -9
  130. package/dist/scss/v2/Message/Message-layout.scss +39 -6
  131. package/dist/scss/v2/MessageReactions/MessageReactionsSelector-layout.scss +18 -0
  132. package/dist/scss/v2/MessageReactions/MessageReactionsSelector-theme.scss +5 -0
  133. package/dist/scss/v2/Thread/Thread-layout.scss +0 -5
  134. package/dist/scss/v2/_base.scss +1 -0
  135. package/dist/scss/v2/_emoji-replacement.scss +4 -2
  136. package/package.json +50 -18
  137. package/dist/components/Emojis/index.cjs.js.map +0 -7
  138. package/dist/components/Emojis/index.d.ts +0 -1
  139. package/dist/components/Emojis/index.js +0 -1
  140. package/dist/components/MessageInput/AttachmentPreviewList/UploadPreviewItem.d.ts +0 -11
  141. package/dist/components/MessageInput/AttachmentPreviewList/UploadPreviewItem.js +0 -51
  142. package/dist/components/MessageInput/hooks/useFileUploads.d.ts +0 -7
  143. package/dist/components/MessageInput/hooks/useFileUploads.js +0 -85
  144. package/dist/components/MessageInput/hooks/useImageUploads.d.ts +0 -8
  145. package/dist/components/MessageInput/hooks/useImageUploads.js +0 -94
  146. package/dist/index.cjs.js.map +0 -7
  147. /package/dist/{components → plugins}/Emojis/EmojiPicker.d.ts +0 -0
  148. /package/dist/{components/MediaRecorder/transcode → plugins/encoders}/mp3.d.ts +0 -0
package/README.md CHANGED
@@ -90,7 +90,7 @@ For components that implement significant logic, it's helpful to split the compo
90
90
 
91
91
  ### Customizing Styles
92
92
 
93
- The preferred method for overriding the pre-defined styles in the library is to two step process. First, import our bundled CSS into the file where you instantiate your chat application. Second, locate any Stream styles you want to override using either the browser inspector or by viewing the library code. You can then add selectors to your local CSS file to override our defaults. For example:
93
+ The preferred method for overriding the pre-defined styles in the library is to two-step process. First, import our bundled CSS into the file where you instantiate your chat application. Second, locate any Stream styles you want to override using either the browser inspector or by viewing the library code. You can then add selectors to your local CSS file to override our defaults. For example:
94
94
 
95
95
  ```js
96
96
  import 'stream-chat-react/dist/css/v2/index.css';
@@ -110,3 +110,13 @@ We recently closed a [$38 million Series B funding round](https://techcrunch.com
110
110
  Our APIs are used by more than a billion end-users, and by working at Stream, you have the chance to make a huge impact on a team of very strong engineers.
111
111
 
112
112
  Check out our current openings and apply via [Stream's website](https://getstream.io/team/#jobs).
113
+
114
+ ## Acknowledgements
115
+
116
+ ### Lamejs
117
+
118
+ This project uses `lamejs` library under the LGPL license to convert the recorded audio to mp3 format.
119
+ The library source code is dynamically imported and used only if audio recording is enabled.
120
+
121
+ You can obtain the source code for `lamejs` from the [lamejs repository](https://github.com/gideonstele/lamejs) that is a fork of [the original JS library](https://github.com/zhuker/lamejs).
122
+ You can find the source code for LAME at https://lame.sourceforge.net and its license at: https://lame.sourceforge.net/license.txt
@@ -9,6 +9,8 @@ type WaveProgressBarProps = {
9
9
  amplitudesCount?: number;
10
10
  /** Progress expressed in fractional number value btw 0 and 100. */
11
11
  progress?: number;
12
+ relativeAmplitudeBarWidth?: number;
13
+ relativeAmplitudeGap?: number;
12
14
  };
13
- export declare const WaveProgressBar: ({ amplitudesCount, progress, seek, waveformData, }: WaveProgressBarProps) => React.JSX.Element | null;
15
+ export declare const WaveProgressBar: ({ amplitudesCount, progress, relativeAmplitudeBarWidth, relativeAmplitudeGap, seek, waveformData, }: WaveProgressBarProps) => React.JSX.Element | null;
14
16
  export {};
@@ -1,10 +1,13 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
1
+ import throttle from 'lodash.throttle';
2
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
2
3
  import clsx from 'clsx';
3
4
  import { resampleWaveformData } from '../audioSampling';
4
- export const WaveProgressBar = ({ amplitudesCount = 40, progress = 0, seek, waveformData, }) => {
5
+ export const WaveProgressBar = ({ amplitudesCount = 40, progress = 0, relativeAmplitudeBarWidth = 2, relativeAmplitudeGap = 1, seek, waveformData, }) => {
5
6
  const [progressIndicator, setProgressIndicator] = useState(null);
6
7
  const isDragging = useRef(false);
7
- const rootRef = useRef(null);
8
+ const [root, setRoot] = useState(null);
9
+ const [trackAxisX, setTrackAxisX] = useState();
10
+ const lastRootWidth = useRef();
8
11
  const handleDragStart = (e) => {
9
12
  e.preventDefault();
10
13
  if (!progressIndicator)
@@ -25,22 +28,54 @@ export const WaveProgressBar = ({ amplitudesCount = 40, progress = 0, seek, wave
25
28
  isDragging.current = false;
26
29
  progressIndicator.style.removeProperty('cursor');
27
30
  }, [progressIndicator]);
28
- const resampledWaveformData = useMemo(() => resampleWaveformData(waveformData, amplitudesCount), [
29
- amplitudesCount,
30
- waveformData,
31
- ]);
31
+ const getTrackAxisX = useMemo(() => throttle((rootWidth) => {
32
+ if (rootWidth === lastRootWidth.current)
33
+ return;
34
+ lastRootWidth.current = rootWidth;
35
+ const possibleAmpCount = Math.floor(rootWidth / (relativeAmplitudeGap + relativeAmplitudeBarWidth));
36
+ const tooManyAmplitudesToRender = possibleAmpCount < amplitudesCount;
37
+ const barCount = tooManyAmplitudesToRender ? possibleAmpCount : amplitudesCount;
38
+ const amplitudeBarWidthToGapRatio = relativeAmplitudeBarWidth / (relativeAmplitudeBarWidth + relativeAmplitudeGap);
39
+ const barWidth = barCount && (rootWidth / barCount) * amplitudeBarWidthToGapRatio;
40
+ setTrackAxisX({
41
+ barCount,
42
+ barWidth,
43
+ gap: barWidth * (relativeAmplitudeGap / relativeAmplitudeBarWidth),
44
+ });
45
+ }, 1), [relativeAmplitudeBarWidth, relativeAmplitudeGap, amplitudesCount]);
46
+ const resampledWaveformData = useMemo(() => (trackAxisX ? resampleWaveformData(waveformData, trackAxisX.barCount) : []), [trackAxisX, waveformData]);
32
47
  useEffect(() => {
33
48
  document.addEventListener('pointerup', handleDragStop);
34
49
  return () => {
35
50
  document.removeEventListener('pointerup', handleDragStop);
36
51
  };
37
52
  }, [handleDragStop]);
38
- if (!waveformData.length)
53
+ useEffect(() => {
54
+ if (!root || typeof ResizeObserver === 'undefined')
55
+ return;
56
+ const observer = new ResizeObserver(([entry]) => {
57
+ getTrackAxisX(entry.contentRect.width);
58
+ });
59
+ observer.observe(root);
60
+ return () => {
61
+ observer.disconnect();
62
+ };
63
+ }, [getTrackAxisX, root]);
64
+ useLayoutEffect(() => {
65
+ if (!root)
66
+ return;
67
+ const { width: rootWidth } = root.getBoundingClientRect();
68
+ getTrackAxisX(rootWidth);
69
+ }, [getTrackAxisX, root]);
70
+ if (!waveformData.length || trackAxisX?.barCount === 0)
39
71
  return null;
40
- return (React.createElement("div", { className: 'str-chat__wave-progress-bar__track', "data-testid": 'wave-progress-bar-track', onClick: seek, onPointerDown: handleDragStart, onPointerMove: handleDrag, onPointerUp: handleDragStop, ref: rootRef, role: 'progressbar' },
72
+ return (React.createElement("div", { className: 'str-chat__wave-progress-bar__track', "data-testid": 'wave-progress-bar-track', onClick: seek, onPointerDown: handleDragStart, onPointerMove: handleDrag, onPointerUp: handleDragStop, ref: setRoot, role: 'progressbar', style: {
73
+ '--str-chat__voice-recording-amplitude-bar-gap-width': trackAxisX?.gap + 'px',
74
+ } },
41
75
  resampledWaveformData.map((amplitude, i) => (React.createElement("div", { className: clsx('str-chat__wave-progress-bar__amplitude-bar', {
42
76
  ['str-chat__wave-progress-bar__amplitude-bar--active']: progress > (i / resampledWaveformData.length) * 100,
43
77
  }), "data-testid": 'amplitude-bar', key: `amplitude-${i}`, style: {
78
+ '--str-chat__voice-recording-amplitude-bar-width': trackAxisX?.barWidth + 'px',
44
79
  '--str-chat__wave-progress-bar__amplitude-bar-height': amplitude
45
80
  ? amplitude * 100 + '%'
46
81
  : '0%',
@@ -68,7 +68,11 @@ const ChannelInner = (props) => {
68
68
  const [state, dispatch] = useReducer(channelReducer,
69
69
  // channel.initialized === false if client.channel().query() was not called, e.g. ChannelList is not used
70
70
  // => Channel will call channel.watch() in useLayoutEffect => state.loading is used to signal the watch() call state
71
- { ...initialState, loading: !channel.initialized });
71
+ {
72
+ ...initialState,
73
+ hasMore: channel.state.messagePagination.hasPrev,
74
+ loading: !channel.initialized,
75
+ });
72
76
  const isMounted = useIsMounted();
73
77
  const originalTitle = useRef('');
74
78
  const lastRead = useRef();
@@ -184,7 +188,6 @@ const ChannelInner = (props) => {
184
188
  useLayoutEffect(() => {
185
189
  let errored = false;
186
190
  let done = false;
187
- let channelInitializedExternally = true;
188
191
  (async () => {
189
192
  if (!channel.initialized && initializeOnMount) {
190
193
  try {
@@ -210,7 +213,6 @@ const ChannelInner = (props) => {
210
213
  await getChannel({ channel, client, members, options: channelQueryOptions });
211
214
  const config = channel.getConfig();
212
215
  setChannelConfig(config);
213
- channelInitializedExternally = false;
214
216
  }
215
217
  catch (e) {
216
218
  dispatch({ error: e, type: 'setError' });
@@ -222,8 +224,7 @@ const ChannelInner = (props) => {
222
224
  if (!errored) {
223
225
  dispatch({
224
226
  channel,
225
- hasMore: channelInitializedExternally ||
226
- hasMoreMessagesProbably(channel.state.messages.length, channelQueryOptions.messages.limit),
227
+ hasMore: channel.state.messagePagination.hasPrev,
227
228
  type: 'initStateFromChannel',
228
229
  });
229
230
  if (client.user?.id && channel.state.read[client.user.id]) {
@@ -283,7 +284,7 @@ const ChannelInner = (props) => {
283
284
  dispatch({ hasMore, messages, type: 'loadMoreFinished' });
284
285
  }, 2000, { leading: true, trailing: true }), []);
285
286
  const loadMore = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
286
- if (!online.current || !window.navigator.onLine || !state.hasMore)
287
+ if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasPrev)
287
288
  return 0;
288
289
  // prevent duplicate loading events...
289
290
  const oldestMessage = state?.messages?.[0];
@@ -305,12 +306,11 @@ const ChannelInner = (props) => {
305
306
  dispatch({ loadingMore: false, type: 'setLoadingMore' });
306
307
  return 0;
307
308
  }
308
- const hasMoreMessages = queryResponse.messages.length === perPage;
309
- loadMoreFinished(hasMoreMessages, channel.state.messages);
309
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
310
310
  return queryResponse.messages.length;
311
311
  };
312
312
  const loadMoreNewer = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
313
- if (!online.current || !window.navigator.onLine || !state.hasMoreNewer)
313
+ if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasNext)
314
314
  return 0;
315
315
  const newestMessage = state?.messages?.[state?.messages?.length - 1];
316
316
  if (state.loadingMore || state.loadingMoreNewer)
@@ -330,9 +330,8 @@ const ChannelInner = (props) => {
330
330
  dispatch({ loadingMoreNewer: false, type: 'setLoadingMoreNewer' });
331
331
  return 0;
332
332
  }
333
- const hasMoreNewerMessages = channel.state.messages !== channel.state.latestMessages;
334
333
  dispatch({
335
- hasMoreNewer: hasMoreNewerMessages,
334
+ hasMoreNewer: channel.state.messagePagination.hasNext,
336
335
  messages: channel.state.messages,
337
336
  type: 'loadMoreNewerFinished',
338
337
  });
@@ -342,15 +341,9 @@ const ChannelInner = (props) => {
342
341
  const jumpToMessage = useCallback(async (messageId, messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE, highlightDuration = DEFAULT_HIGHLIGHT_DURATION) => {
343
342
  dispatch({ loadingMore: true, type: 'setLoadingMore' });
344
343
  await channel.state.loadMessageIntoState(messageId, undefined, messageLimit);
345
- /**
346
- * if the message we are jumping to has less than half of the page size older messages,
347
- * we have jumped to the beginning of the channel.
348
- */
349
- const indexOfMessage = channel.state.messages.findIndex((message) => message.id === messageId);
350
- const hasMoreMessages = indexOfMessage >= Math.floor(messageLimit / 2);
351
- loadMoreFinished(hasMoreMessages, channel.state.messages);
344
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
352
345
  dispatch({
353
- hasMoreNewer: channel.state.messages !== channel.state.latestMessages,
346
+ hasMoreNewer: channel.state.messagePagination.hasNext,
354
347
  highlightedMessageId: messageId,
355
348
  type: 'jumpToMessageFinished',
356
349
  });
@@ -364,9 +357,7 @@ const ChannelInner = (props) => {
364
357
  }, [channel, loadMoreFinished]);
365
358
  const jumpToLatestMessage = useCallback(async () => {
366
359
  await channel.state.loadMessageIntoState('latest');
367
- // FIXME: we cannot rely on constant value 25 as the page size can be customized by integrators
368
- const hasMoreOlder = channel.state.messages.length >= 25;
369
- loadMoreFinished(hasMoreOlder, channel.state.messages);
360
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
370
361
  dispatch({
371
362
  type: 'jumpToLatestMessage',
372
363
  });
@@ -377,7 +368,6 @@ const ChannelInner = (props) => {
377
368
  let lastReadMessageId = channelUnreadUiState?.last_read_message_id;
378
369
  let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id;
379
370
  let isInCurrentMessageSet = false;
380
- let hasMoreMessages = true;
381
371
  if (firstUnreadMessageId) {
382
372
  const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages);
383
373
  isInCurrentMessageSet = result.index !== -1;
@@ -409,27 +399,25 @@ const ChannelInner = (props) => {
409
399
  }
410
400
  catch (e) {
411
401
  addNotification(t('Failed to jump to the first unread message'), 'error');
412
- loadMoreFinished(hasMoreMessages, channel.state.messages);
402
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
413
403
  return;
414
404
  }
415
405
  const firstMessageWithCreationDate = messages.find((msg) => msg.created_at);
416
406
  if (!firstMessageWithCreationDate) {
417
407
  addNotification(t('Failed to jump to the first unread message'), 'error');
418
- loadMoreFinished(hasMoreMessages, channel.state.messages);
408
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
419
409
  return;
420
410
  }
421
411
  const firstMessageTimestamp = new Date(firstMessageWithCreationDate.created_at).getTime();
422
412
  if (lastReadTimestamp < firstMessageTimestamp) {
423
413
  // whole channel is unread
424
414
  firstUnreadMessageId = firstMessageWithCreationDate.id;
425
- hasMoreMessages = false;
426
415
  }
427
416
  else {
428
417
  const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages);
429
418
  lastReadMessageId = result.target?.id;
430
- hasMoreMessages = result.index >= Math.floor(queryMessageLimit / 2);
431
419
  }
432
- loadMoreFinished(hasMoreMessages, channel.state.messages);
420
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
433
421
  }
434
422
  }
435
423
  if (!firstUnreadMessageId && !lastReadMessageId) {
@@ -446,14 +434,13 @@ const ChannelInner = (props) => {
446
434
  * we have arrived to the oldest page of the channel
447
435
  */
448
436
  const indexOfTarget = channel.state.messages.findIndex((message) => message.id === targetId);
449
- hasMoreMessages = indexOfTarget >= Math.floor(queryMessageLimit / 2);
450
- loadMoreFinished(hasMoreMessages, channel.state.messages);
437
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
451
438
  firstUnreadMessageId =
452
439
  firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id;
453
440
  }
454
441
  catch (e) {
455
442
  addNotification(t('Failed to jump to the first unread message'), 'error');
456
- loadMoreFinished(hasMoreMessages, channel.state.messages);
443
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
457
444
  return;
458
445
  }
459
446
  }
@@ -468,7 +455,7 @@ const ChannelInner = (props) => {
468
455
  last_read_message_id: lastReadMessageId,
469
456
  });
470
457
  dispatch({
471
- hasMoreNewer: channel.state.messages !== channel.state.latestMessages,
458
+ hasMoreNewer: channel.state.messagePagination.hasNext,
472
459
  highlightedMessageId: firstUnreadMessageId,
473
460
  type: 'jumpToMessageFinished',
474
461
  });
@@ -102,6 +102,7 @@ export const channelReducer = (state, action) => {
102
102
  return {
103
103
  ...state,
104
104
  thread: message,
105
+ threadHasMore: true,
105
106
  threadMessages: message.id ? { ...channel.state.threads }[message.id] || [] : [],
106
107
  threadSuppressAutoscroll: false,
107
108
  };
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { isDate, isDayOrMoment } from '../../../context/TranslationContext';
2
+ import { isDate, isDayOrMoment } from '../../../i18n';
3
3
  export const useCreateChannelStateContext = (value) => {
4
4
  const { acceptedFiles, channel, channelCapabilitiesArray = [], channelConfig, debounceURLEnrichmentMs, dragAndDropWindow, enrichURLForPreview, giphyVersion, error, findURLFn, hasMore, hasMoreNewer, imageAttachmentSizeHandler, suppressAutoscroll, highlightedMessageId, loading, loadingMore, maxNumberOfFiles, members, messages = [], multipleUploads, mutes, notifications, onLinkPreviewDismissed, pinnedMessages, quotedMessage, read = {}, shouldGenerateVideoThumbnail, skipMessageDataMemoization, thread, threadHasMore, threadLoadingMore, threadMessages = [], channelUnreadUiState, videoAttachmentSizeHandler, watcherCount, watcher_count, watchers, } = value;
5
5
  const channelId = channel.cid;
@@ -117,7 +117,7 @@ const UnMemoizedChannelList = (props) => {
117
117
  channel: item,
118
118
  // forces the update of preview component on channel update
119
119
  channelUpdateCount,
120
- key: item.id,
120
+ key: item.cid,
121
121
  Preview,
122
122
  setActiveChannel,
123
123
  watchers,
@@ -1,7 +1,7 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
2
  import { CustomClasses } from '../../context/ChatContext';
3
- import { SupportedTranslations } from '../../context/TranslationContext';
4
3
  import type { StreamChat } from 'stream-chat';
4
+ import type { SupportedTranslations } from '../../i18n/types';
5
5
  import type { Streami18n } from '../../i18n/Streami18n';
6
6
  import type { DefaultStreamChatGenerics } from '../../types/types';
7
7
  export type ChatProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
@@ -1,5 +1,5 @@
1
- import { SupportedTranslations, TranslationContextValue } from '../../../context/TranslationContext';
2
- import { Streami18n } from '../../../i18n';
1
+ import { TranslationContextValue } from '../../../context/TranslationContext';
2
+ import { Streami18n, SupportedTranslations } from '../../../i18n';
3
3
  import type { AppSettingsAPIResponse, Channel, Mute, StreamChat } from 'stream-chat';
4
4
  import type { DefaultStreamChatGenerics } from '../../../types/types';
5
5
  export type UseChatParams<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { defaultDateTimeParser, isLanguageSupported, } from '../../../context/TranslationContext';
3
- import { Streami18n } from '../../../i18n';
2
+ import { defaultDateTimeParser, isLanguageSupported, Streami18n, } from '../../../i18n';
4
3
  import { version } from '../../../version';
5
4
  export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialNavOpen, }) => {
6
5
  const [translators, setTranslators] = useState({
@@ -1,9 +1,11 @@
1
- import { DefaultGenerics, ExtendableGenerics, OwnUserResponse, StreamChat, TokenOrProvider, UserResponse } from 'stream-chat';
1
+ import { StreamChat } from 'stream-chat';
2
+ import type { DefaultGenerics, ExtendableGenerics, OwnUserResponse, StreamChatOptions, TokenOrProvider, UserResponse } from 'stream-chat';
2
3
  /**
3
4
  * React hook to create, connect and return `StreamChat` client.
4
5
  */
5
- export declare const useCreateChatClient: <SCG extends ExtendableGenerics = DefaultGenerics>({ apiKey, tokenOrProvider, userData, }: {
6
+ export declare const useCreateChatClient: <SCG extends ExtendableGenerics = DefaultGenerics>({ apiKey, options, tokenOrProvider, userData, }: {
6
7
  apiKey: string;
7
8
  tokenOrProvider: TokenOrProvider;
8
9
  userData: OwnUserResponse<SCG> | UserResponse<SCG>;
10
+ options?: StreamChatOptions;
9
11
  }) => StreamChat<SCG> | null;
@@ -1,16 +1,17 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { StreamChat, } from 'stream-chat';
2
+ import { StreamChat } from 'stream-chat';
3
3
  /**
4
4
  * React hook to create, connect and return `StreamChat` client.
5
5
  */
6
- export const useCreateChatClient = ({ apiKey, tokenOrProvider, userData, }) => {
6
+ export const useCreateChatClient = ({ apiKey, options, tokenOrProvider, userData, }) => {
7
7
  const [chatClient, setChatClient] = useState(null);
8
8
  const [cachedUserData, setCachedUserData] = useState(userData);
9
9
  if (userData.id !== cachedUserData.id) {
10
10
  setCachedUserData(userData);
11
11
  }
12
+ const [cachedOptions] = useState(options);
12
13
  useEffect(() => {
13
- const client = new StreamChat(apiKey);
14
+ const client = new StreamChat(apiKey, undefined, cachedOptions);
14
15
  let didUserConnectInterrupt = false;
15
16
  const connectionPromise = client.connectUser(cachedUserData, tokenOrProvider).then(() => {
16
17
  if (!didUserConnectInterrupt)
@@ -25,6 +26,6 @@ export const useCreateChatClient = ({ apiKey, tokenOrProvider, userData, }) => {
25
26
  console.log(`Connection for user "${cachedUserData.id}" has been closed`);
26
27
  });
27
28
  };
28
- }, [apiKey, cachedUserData, tokenOrProvider]);
29
+ }, [apiKey, cachedUserData, cachedOptions, tokenOrProvider]);
29
30
  return chatClient;
30
31
  };
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { TimestampFormatterOptions } from '../../i18n/utils';
2
+ import type { TimestampFormatterOptions } from '../../i18n/types';
3
3
  export type DateSeparatorProps = TimestampFormatterOptions & {
4
4
  /** The date to format */
5
5
  date: Date;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { useTranslationContext } from '../../context/TranslationContext';
3
3
  import { getDateString } from '../../i18n/utils';
4
4
  const UnMemoizedDateSeparator = (props) => {
5
- const { calendar = true, date: messageCreatedAt, formatDate, position = 'right', unread, ...restTimestampFormatterOptions } = props;
5
+ const { calendar, date: messageCreatedAt, formatDate, position = 'right', unread, ...restTimestampFormatterOptions } = props;
6
6
  const { t, tDateTimeParser } = useTranslationContext('DateSeparator');
7
7
  const formattedDate = getDateString({
8
8
  calendar,
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { AvatarProps } from '../Avatar';
3
3
  import type { StreamMessage } from '../../context/ChannelStateContext';
4
4
  import type { DefaultStreamChatGenerics } from '../../types/types';
5
- import { TimestampFormatterOptions } from '../../i18n/utils';
5
+ import type { TimestampFormatterOptions } from '../../i18n/types';
6
6
  export type EventComponentProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = TimestampFormatterOptions & {
7
7
  /** Message object */
8
8
  message: StreamMessage<StreamChatGenerics>;
@@ -6,7 +6,7 @@ import { getDateString } from '../../i18n/utils';
6
6
  * Component to display system and channel event messages
7
7
  */
8
8
  const UnMemoizedEventComponent = (props) => {
9
- const { calendar, calendarFormats, format = 'dddd L', Avatar = DefaultAvatar, message } = props;
9
+ const { calendar, calendarFormats, format, Avatar = DefaultAvatar, message } = props;
10
10
  const { t, tDateTimeParser } = useTranslationContext('EventComponent');
11
11
  const { created_at = '', event, text, type } = message;
12
12
  const getDateOptions = { messageCreatedAt: created_at.toString(), tDateTimeParser };
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useLayoutEffect, useRef } from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { deprecationAndReplacementWarning } from '../../utils/deprecationWarning';
3
3
  import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD } from '../../constants/limits';
4
4
  /**
@@ -17,6 +17,8 @@ export const InfiniteScroll = (props) => {
17
17
  const hasNextPageFlag = hasNextPage || hasMoreNewer;
18
18
  const hasPreviousPageFlag = hasPreviousPage || hasMore;
19
19
  const scrollComponent = useRef();
20
+ const previousOffset = useRef();
21
+ const previousReverseOffset = useRef();
20
22
  const scrollListenerRef = useRef();
21
23
  scrollListenerRef.current = () => {
22
24
  const element = scrollComponent.current;
@@ -32,7 +34,11 @@ export const InfiniteScroll = (props) => {
32
34
  }
33
35
  if (isLoading)
34
36
  return;
35
- // FIXME: this triggers loadMore call when a user types messages in thread and the scroll container container expands
37
+ if (previousOffset.current === offset && previousReverseOffset.current === reverseOffset)
38
+ return;
39
+ previousOffset.current = offset;
40
+ previousReverseOffset.current = reverseOffset;
41
+ // FIXME: this triggers loadMore call when a user types messages in thread and the scroll container expands
36
42
  if (reverseOffset < Number(threshold) &&
37
43
  typeof loadPreviousPageFn === 'function' &&
38
44
  hasPreviousPageFlag) {
@@ -51,7 +57,7 @@ export const InfiniteScroll = (props) => {
51
57
  ], 'InfiniteScroll');
52
58
  // eslint-disable-next-line react-hooks/exhaustive-deps
53
59
  }, []);
54
- useLayoutEffect(() => {
60
+ useEffect(() => {
55
61
  const scrollElement = scrollComponent.current?.parentNode;
56
62
  if (!scrollElement)
57
63
  return;
@@ -29,6 +29,9 @@ export const AudioRecordingInProgress = () => {
29
29
  if (!recorder?.mediaRecorder)
30
30
  return;
31
31
  const { mediaRecorder } = recorder;
32
+ if (mediaRecorder.state === 'recording') {
33
+ startCounter();
34
+ }
32
35
  mediaRecorder.addEventListener('start', startCounter);
33
36
  mediaRecorder.addEventListener('resume', startCounter);
34
37
  mediaRecorder.addEventListener('stop', stopCounter);
@@ -1,26 +1,25 @@
1
1
  import { AmplitudeRecorder, AmplitudeRecorderConfig } from './AmplitudeRecorder';
2
2
  import { BrowserPermission } from './BrowserPermission';
3
3
  import { BehaviorSubject, Subject } from '../observable';
4
+ import { TranscoderConfig } from '../transcode';
4
5
  import { RecordedMediaType } from '../../ReactFileUtilities';
5
6
  import { TranslationContextValue } from '../../../context';
6
7
  import type { LocalVoiceRecordingAttachment } from '../../MessageInput';
7
8
  import type { DefaultStreamChatGenerics } from '../../../types';
8
- export declare const POSSIBLE_TRANSCODING_MIME_TYPES: readonly ["audio/wav", "audio/mp3"];
9
9
  export declare const DEFAULT_MEDIA_RECORDER_CONFIG: MediaRecorderConfig;
10
10
  export declare const DEFAULT_AUDIO_TRANSCODER_CONFIG: TranscoderConfig;
11
- type SupportedTranscodeMimeTypes = typeof POSSIBLE_TRANSCODING_MIME_TYPES[number];
12
- export type TranscoderConfig = {
13
- sampleRate: number;
14
- targetMimeType: SupportedTranscodeMimeTypes;
15
- };
16
11
  type MediaRecorderConfig = Omit<MediaRecorderOptions, 'mimeType'> & Required<Pick<MediaRecorderOptions, 'mimeType'>>;
17
12
  export type AudioRecorderConfig = {
18
13
  amplitudeRecorderConfig: AmplitudeRecorderConfig;
19
14
  mediaRecorderConfig: MediaRecorderOptions;
20
15
  transcoderConfig: TranscoderConfig;
21
16
  };
17
+ type PartialValues<T> = {
18
+ [P in keyof T]?: Partial<T[P]>;
19
+ };
20
+ export type CustomAudioRecordingConfig = PartialValues<AudioRecorderConfig>;
22
21
  export type AudioRecorderOptions = {
23
- config?: Partial<AudioRecorderConfig>;
22
+ config?: CustomAudioRecordingConfig;
24
23
  generateRecordingTitle?: (mimeType: string) => string;
25
24
  t?: TranslationContextValue['t'];
26
25
  };
@@ -15,7 +15,6 @@ const RECORDED_MIME_TYPE_BY_BROWSER = {
15
15
  safari: 'audio/mp4;codecs=mp4a.40.2',
16
16
  },
17
17
  };
18
- export const POSSIBLE_TRANSCODING_MIME_TYPES = ['audio/wav', 'audio/mp3'];
19
18
  export const DEFAULT_MEDIA_RECORDER_CONFIG = {
20
19
  mimeType: isSafari()
21
20
  ? RECORDED_MIME_TYPE_BY_BROWSER.audio.safari
@@ -23,7 +22,6 @@ export const DEFAULT_MEDIA_RECORDER_CONFIG = {
23
22
  };
24
23
  export const DEFAULT_AUDIO_TRANSCODER_CONFIG = {
25
24
  sampleRate: 16000,
26
- targetMimeType: 'audio/mp3',
27
25
  };
28
26
  const disposeOfMediaStream = (stream) => {
29
27
  if (!stream?.active)
@@ -245,9 +243,6 @@ export class MediaRecorderController {
245
243
  this.amplitudeRecorderConfig = mergeDeepUndefined({ ...config?.amplitudeRecorderConfig }, DEFAULT_AMPLITUDE_RECORDER_CONFIG);
246
244
  this.mediaRecorderConfig = mergeDeepUndefined({ ...config?.mediaRecorderConfig }, DEFAULT_MEDIA_RECORDER_CONFIG);
247
245
  this.transcoderConfig = mergeDeepUndefined({ ...config?.transcoderConfig }, DEFAULT_AUDIO_TRANSCODER_CONFIG);
248
- if (!POSSIBLE_TRANSCODING_MIME_TYPES.includes(this.transcoderConfig.targetMimeType)) {
249
- this.transcoderConfig.targetMimeType = DEFAULT_AUDIO_TRANSCODER_CONFIG.targetMimeType;
250
- }
251
246
  const mediaType = getRecordedMediaTypeFromMimeType(this.mediaRecorderConfig.mimeType);
252
247
  if (!mediaType) {
253
248
  throw new Error(`Unsupported media type (supported audio or video only). Provided mimeType: ${this.mediaRecorderConfig.mimeType}`);
@@ -1 +1 @@
1
- export type { CustomAudioRecordingConfig, RecordingController } from './useMediaRecorder';
1
+ export type { RecordingController } from './useMediaRecorder';
@@ -1,8 +1,7 @@
1
1
  import { MessageInputContextValue } from '../../../context';
2
- import { AudioRecorderConfig, MediaRecorderController, MediaRecordingState } from '../classes';
2
+ import { CustomAudioRecordingConfig, MediaRecorderController, MediaRecordingState } from '../classes';
3
3
  import type { LocalVoiceRecordingAttachment } from '../../MessageInput';
4
4
  import type { DefaultStreamChatGenerics } from '../../../types';
5
- export type CustomAudioRecordingConfig = Partial<AudioRecorderConfig>;
6
5
  export type RecordingController<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
7
6
  completeRecording: () => void;
8
7
  permissionState?: PermissionState;
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { useTranslationContext } from '../../../context';
3
- import { MediaRecorderController } from '../classes';
3
+ import { MediaRecorderController, } from '../classes';
4
4
  export const useMediaRecorder = ({ asyncMessagesMultiSendEnabled, enabled, generateRecordingTitle, handleSubmit, recordingConfig, uploadAttachment, }) => {
5
5
  const { t } = useTranslationContext('useMediaRecorder');
6
6
  const [recording, setRecording] = useState();
@@ -3,3 +3,4 @@ export * from './AudioRecorder';
3
3
  export * from './hooks';
4
4
  export { MediaRecordingState } from './classes/MediaRecorderController';
5
5
  export { RecordingPermission } from './classes/BrowserPermission';
6
+ export type { CustomAudioRecordingConfig } from './classes';
@@ -1,7 +1,8 @@
1
- type TranscodeParams = {
2
- blob: Blob;
1
+ export type TranscoderConfig = {
3
2
  sampleRate: number;
4
- targetMimeType: string;
3
+ encoder?: (file: File, sampleRate: number) => Promise<Blob>;
4
+ };
5
+ export type TranscodeParams = TranscoderConfig & {
6
+ blob: Blob;
5
7
  };
6
- export declare const transcode: ({ blob, sampleRate, targetMimeType }: TranscodeParams) => Promise<Blob>;
7
- export {};
8
+ export declare const transcode: ({ blob, encoder, sampleRate, }: TranscodeParams) => Promise<Blob>;
@@ -1,17 +1,7 @@
1
1
  import { encodeToWaw } from './wav';
2
- import { encodeToMp3 } from './mp3';
3
2
  import { createFileFromBlobs, getExtensionFromMimeType } from '../../ReactFileUtilities';
4
- export const transcode = ({ blob, sampleRate, targetMimeType }) => {
5
- const file = createFileFromBlobs({
6
- blobsArray: [blob],
7
- fileName: `audio_recording_${new Date().toISOString()}.${getExtensionFromMimeType(blob.type)}`,
8
- mimeType: blob.type,
9
- });
10
- if (targetMimeType.match('audio/wav')) {
11
- return encodeToWaw(file, sampleRate);
12
- }
13
- if (targetMimeType.match('audio/mp3')) {
14
- return encodeToMp3(file, sampleRate);
15
- }
16
- return Promise.resolve(blob);
17
- };
3
+ export const transcode = ({ blob, encoder = encodeToWaw, sampleRate, }) => encoder(createFileFromBlobs({
4
+ blobsArray: [blob],
5
+ fileName: `audio_recording_${new Date().toISOString()}.${getExtensionFromMimeType(blob.type)}`,
6
+ mimeType: blob.type,
7
+ }), sampleRate);
@@ -92,7 +92,7 @@ const MessageSimpleWithContext = (props) => {
92
92
  showMetadata && (React.createElement("div", { className: 'str-chat__message-metadata' },
93
93
  React.createElement(MessageStatus, null),
94
94
  !isMyMessage() && !!message.user && (React.createElement("span", { className: 'str-chat__message-simple-name' }, message.user.name || message.user.id)),
95
- React.createElement(MessageTimestamp, { calendar: true, customClass: 'str-chat__message-simple-timestamp' }),
95
+ React.createElement(MessageTimestamp, { customClass: 'str-chat__message-simple-timestamp' }),
96
96
  isEdited && (React.createElement("span", { className: 'str-chat__mesage-simple-edited' }, t('Edited'))),
97
97
  isEdited && React.createElement(MessageEditedTimestamp, { calendar: true, open: isEditedTimestampOpen }))))));
98
98
  };
@@ -26,9 +26,10 @@ const UnMemoizedMessageStatus = (props) => {
26
26
  const sending = message.status === 'sending';
27
27
  const delivered = message.status === 'received' && message.id === lastReceivedId && !threadList;
28
28
  const deliveredAndRead = !!(readBy?.length && !threadList && !justReadByMe);
29
- const [lastReadUser] = deliveredAndRead
29
+ const readersWithoutOwnUser = deliveredAndRead
30
30
  ? readBy.filter((item) => item.id !== client.user?.id)
31
31
  : [];
32
+ const [lastReadUser] = readersWithoutOwnUser;
32
33
  return (React.createElement("span", { className: rootClassName, "data-testid": clsx({
33
34
  'message-status-read-by': deliveredAndRead,
34
35
  'message-status-received': delivered && !deliveredAndRead,
@@ -47,6 +48,6 @@ const UnMemoizedMessageStatus = (props) => {
47
48
  (MessageReadStatus ? (React.createElement(MessageReadStatus, null)) : (React.createElement(React.Fragment, null,
48
49
  React.createElement(PopperTooltip, { offset: [0, 5], referenceElement: referenceElement, visible: tooltipVisible }, getReadByTooltipText(readBy, t, client, tooltipUserNameMapper)),
49
50
  React.createElement(Avatar, { className: 'str-chat__avatar--message-status', image: lastReadUser.image, name: lastReadUser.name || lastReadUser.id, user: lastReadUser }),
50
- readBy.length > 2 && (React.createElement("span", { className: `str-chat__message-${messageType}-status-number`, "data-testid": 'message-status-read-by-many' }, readBy.length - 1)))))));
51
+ readersWithoutOwnUser.length > 1 && (React.createElement("span", { className: `str-chat__message-${messageType}-status-number`, "data-testid": 'message-status-read-by-many' }, readersWithoutOwnUser.length)))))));
51
52
  };
52
53
  export const MessageStatus = React.memo(UnMemoizedMessageStatus);