stream-chat-react 12.0.0-rc.8 → 12.0.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.
Files changed (158) hide show
  1. package/dist/components/Avatar/Avatar.js +5 -1
  2. package/dist/components/Channel/Channel.d.ts +3 -4
  3. package/dist/components/Channel/Channel.js +95 -56
  4. package/dist/components/Channel/hooks/useCreateChannelStateContext.js +2 -1
  5. package/dist/components/ChannelHeader/ChannelHeader.js +4 -5
  6. package/dist/components/ChannelPreview/hooks/useChannelPreviewInfo.js +14 -16
  7. package/dist/components/ChannelPreview/utils.js +9 -20
  8. package/dist/components/ChannelSearch/hooks/useChannelSearch.js +2 -3
  9. package/dist/components/Chat/Chat.d.ts +1 -1
  10. package/dist/components/Chat/hooks/useChat.d.ts +2 -2
  11. package/dist/components/Chat/hooks/useChat.js +11 -8
  12. package/dist/components/ChatView/ChatView.d.ts +18 -0
  13. package/dist/components/ChatView/ChatView.js +103 -0
  14. package/dist/components/ChatView/index.d.ts +1 -0
  15. package/dist/components/ChatView/index.js +1 -0
  16. package/dist/components/DateSeparator/DateSeparator.d.ts +1 -1
  17. package/dist/components/Dialog/DialogAnchor.d.ts +25 -0
  18. package/dist/components/Dialog/DialogAnchor.js +68 -0
  19. package/dist/components/Dialog/DialogManager.d.ts +43 -0
  20. package/dist/components/Dialog/DialogManager.js +98 -0
  21. package/dist/components/Dialog/DialogPortal.d.ts +7 -0
  22. package/dist/components/Dialog/DialogPortal.js +25 -0
  23. package/dist/components/Dialog/hooks/index.d.ts +1 -0
  24. package/dist/components/Dialog/hooks/index.js +1 -0
  25. package/dist/components/Dialog/hooks/useDialog.d.ts +4 -0
  26. package/dist/components/Dialog/hooks/useDialog.js +26 -0
  27. package/dist/components/Dialog/index.d.ts +4 -0
  28. package/dist/components/Dialog/index.js +4 -0
  29. package/dist/components/EventComponent/EventComponent.d.ts +1 -1
  30. package/dist/components/Message/Message.js +5 -6
  31. package/dist/components/Message/MessageOptions.d.ts +1 -2
  32. package/dist/components/Message/MessageOptions.js +14 -11
  33. package/dist/components/Message/MessageSimple.js +6 -14
  34. package/dist/components/Message/MessageTimestamp.d.ts +1 -1
  35. package/dist/components/Message/QuotedMessage.js +2 -1
  36. package/dist/components/Message/Timestamp.d.ts +1 -1
  37. package/dist/components/Message/Timestamp.js +2 -2
  38. package/dist/components/Message/hooks/useReactionHandler.d.ts +1 -7
  39. package/dist/components/Message/hooks/useReactionHandler.js +8 -63
  40. package/dist/components/Message/utils.d.ts +10 -1
  41. package/dist/components/Message/utils.js +19 -7
  42. package/dist/components/MessageActions/MessageActions.d.ts +1 -2
  43. package/dist/components/MessageActions/MessageActions.js +26 -55
  44. package/dist/components/MessageActions/MessageActionsBox.d.ts +1 -1
  45. package/dist/components/MessageActions/MessageActionsBox.js +6 -6
  46. package/dist/components/MessageInput/MessageInputFlat.js +2 -2
  47. package/dist/components/MessageInput/QuotedMessagePreview.js +2 -1
  48. package/dist/components/MessageInput/hooks/useUserTrigger.js +0 -1
  49. package/dist/components/MessageList/MessageList.js +7 -7
  50. package/dist/components/MessageList/VirtualizedMessageList.d.ts +2 -1
  51. package/dist/components/MessageList/VirtualizedMessageList.js +44 -39
  52. package/dist/components/MessageList/VirtualizedMessageListComponents.d.ts +1 -1
  53. package/dist/components/MessageList/VirtualizedMessageListComponents.js +6 -6
  54. package/dist/components/MessageList/renderMessages.d.ts +2 -2
  55. package/dist/components/MessageList/renderMessages.js +4 -1
  56. package/dist/components/MessageList/utils.js +1 -1
  57. package/dist/components/Reactions/ReactionSelector.d.ts +6 -3
  58. package/dist/components/Reactions/ReactionSelector.js +34 -24
  59. package/dist/components/Reactions/ReactionSelectorWithButton.d.ts +13 -0
  60. package/dist/components/Reactions/ReactionSelectorWithButton.js +22 -0
  61. package/dist/components/Reactions/ReactionsList.d.ts +4 -4
  62. package/dist/components/Reactions/hooks/useProcessReactions.js +2 -1
  63. package/dist/components/Thread/Thread.js +38 -10
  64. package/dist/components/Threads/ThreadContext.d.ts +9 -0
  65. package/dist/components/Threads/ThreadContext.js +9 -0
  66. package/dist/components/Threads/ThreadList/ThreadList.d.ts +9 -0
  67. package/dist/components/Threads/ThreadList/ThreadList.js +41 -0
  68. package/dist/components/Threads/ThreadList/ThreadListEmptyPlaceholder.d.ts +2 -0
  69. package/dist/components/Threads/ThreadList/ThreadListEmptyPlaceholder.js +5 -0
  70. package/dist/components/Threads/ThreadList/ThreadListItem.d.ts +9 -0
  71. package/dist/components/Threads/ThreadList/ThreadListItem.js +52 -0
  72. package/dist/components/Threads/ThreadList/ThreadListItemUI.d.ts +15 -0
  73. package/dist/components/Threads/ThreadList/ThreadListItemUI.js +75 -0
  74. package/dist/components/Threads/ThreadList/ThreadListLoadingIndicator.d.ts +2 -0
  75. package/dist/components/Threads/ThreadList/ThreadListLoadingIndicator.js +14 -0
  76. package/dist/components/Threads/ThreadList/ThreadListUnseenThreadsBanner.d.ts +2 -0
  77. package/dist/components/Threads/ThreadList/ThreadListUnseenThreadsBanner.js +16 -0
  78. package/dist/components/Threads/ThreadList/index.d.ts +3 -0
  79. package/dist/components/Threads/ThreadList/index.js +3 -0
  80. package/dist/components/Threads/UnreadCountBadge.d.ts +6 -0
  81. package/dist/components/Threads/UnreadCountBadge.js +5 -0
  82. package/dist/components/Threads/hooks/useThreadManagerState.d.ts +2 -0
  83. package/dist/components/Threads/hooks/useThreadManagerState.js +6 -0
  84. package/dist/components/Threads/hooks/useThreadState.d.ts +5 -0
  85. package/dist/components/Threads/hooks/useThreadState.js +11 -0
  86. package/dist/components/Threads/icons.d.ts +8 -0
  87. package/dist/components/Threads/icons.js +13 -0
  88. package/dist/components/Threads/index.d.ts +2 -0
  89. package/dist/components/Threads/index.js +2 -0
  90. package/dist/components/index.d.ts +3 -0
  91. package/dist/components/index.js +3 -0
  92. package/dist/context/ComponentContext.d.ts +15 -40
  93. package/dist/context/ComponentContext.js +7 -9
  94. package/dist/context/DialogManagerContext.d.ts +10 -0
  95. package/dist/context/DialogManagerContext.js +14 -0
  96. package/dist/context/MessageContext.d.ts +3 -11
  97. package/dist/context/MessageContext.js +3 -2
  98. package/dist/context/TranslationContext.d.ts +1 -11
  99. package/dist/context/TranslationContext.js +1 -9
  100. package/dist/context/WithComponents.d.ts +5 -0
  101. package/dist/context/WithComponents.js +7 -0
  102. package/dist/context/index.d.ts +2 -0
  103. package/dist/context/index.js +2 -0
  104. package/dist/css/v2/index.css +2 -2
  105. package/dist/css/v2/index.layout.css +2 -2
  106. package/dist/i18n/Streami18n.d.ts +1 -3
  107. package/dist/i18n/Streami18n.js +1 -2
  108. package/dist/i18n/index.d.ts +2 -1
  109. package/dist/i18n/index.js +2 -0
  110. package/dist/i18n/types.d.ts +26 -0
  111. package/dist/i18n/types.js +1 -0
  112. package/dist/i18n/utils.d.ts +9 -20
  113. package/dist/i18n/utils.js +10 -1
  114. package/dist/index.browser.cjs +10827 -10601
  115. package/dist/index.browser.cjs.map +4 -4
  116. package/dist/index.d.ts +1 -0
  117. package/dist/index.js +1 -0
  118. package/dist/index.node.cjs +10720 -10510
  119. package/dist/index.node.cjs.map +4 -4
  120. package/dist/plugins/Emojis/index.browser.cjs +7 -169
  121. package/dist/plugins/Emojis/index.browser.cjs.map +4 -4
  122. package/dist/plugins/Emojis/index.node.cjs +7 -169
  123. package/dist/plugins/Emojis/index.node.cjs.map +4 -4
  124. package/dist/plugins/encoders/mp3.browser.cjs +2 -4
  125. package/dist/plugins/encoders/mp3.browser.cjs.map +1 -1
  126. package/dist/plugins/encoders/mp3.node.cjs +2 -4
  127. package/dist/plugins/encoders/mp3.node.cjs.map +1 -1
  128. package/dist/scss/v2/Avatar/Avatar-layout.scss +10 -2
  129. package/dist/scss/v2/Avatar/Avatar-theme.scss +5 -0
  130. package/dist/scss/v2/ChatView/ChatView-layout.scss +43 -0
  131. package/dist/scss/v2/ChatView/ChatView-theme.scss +32 -0
  132. package/dist/scss/v2/Dialog/Dialog-layout.scss +8 -0
  133. package/dist/scss/v2/LoadingIndicator/LoadingIndicator-layout.scss +16 -0
  134. package/dist/scss/v2/Message/Message-layout.scss +8 -0
  135. package/dist/scss/v2/MessageActionsBox/MessageActionsBox-theme.scss +8 -0
  136. package/dist/scss/v2/MessageList/MessageList-layout.scss +0 -6
  137. package/dist/scss/v2/MessageList/VirtualizedMessageList-layout.scss +0 -12
  138. package/dist/scss/v2/MessageReactions/MessageReactionsSelector-layout.scss +16 -0
  139. package/dist/scss/v2/MessageReactions/MessageReactionsSelector-theme.scss +6 -0
  140. package/dist/scss/v2/Thread/Thread-layout.scss +15 -1
  141. package/dist/scss/v2/ThreadList/ThreadList-layout.scss +152 -0
  142. package/dist/scss/v2/ThreadList/ThreadList-theme.scss +75 -0
  143. package/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-layout.scss +49 -0
  144. package/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-theme.scss +11 -0
  145. package/dist/scss/v2/_base.scss +31 -0
  146. package/dist/scss/v2/index.layout.scss +4 -0
  147. package/dist/scss/v2/index.scss +3 -0
  148. package/dist/store/hooks/index.d.ts +1 -0
  149. package/dist/store/hooks/index.js +1 -0
  150. package/dist/store/hooks/useStateStore.d.ts +3 -0
  151. package/dist/store/hooks/useStateStore.js +15 -0
  152. package/dist/store/index.d.ts +1 -0
  153. package/dist/store/index.js +1 -0
  154. package/package.json +7 -6
  155. package/dist/assets/Poweredby_100px-White_VertText.png +0 -0
  156. package/dist/assets/str-chat__reaction-list-sprite@1x.png +0 -0
  157. package/dist/assets/str-chat__reaction-list-sprite@2x.png +0 -0
  158. package/dist/assets/str-chat__reaction-list-sprite@3x.png +0 -0
@@ -1,5 +1,6 @@
1
1
  import clsx from 'clsx';
2
2
  import React, { useEffect, useState } from 'react';
3
+ import { Icon } from '../Threads/icons';
3
4
  import { getWholeChar } from '../../utils';
4
5
  /**
5
6
  * A round avatar image with fallback to username's first letter
@@ -15,6 +16,9 @@ export const Avatar = (props) => {
15
16
  const showImage = image && !error;
16
17
  return (React.createElement("div", { className: clsx(`str-chat__avatar str-chat__message-sender-avatar`, className, {
17
18
  ['str-chat__avatar--multiple-letters']: initials.length > 1,
19
+ ['str-chat__avatar--no-letters']: !initials.length,
18
20
  ['str-chat__avatar--one-letter']: initials.length === 1,
19
- }), "data-testid": 'avatar', onClick: onClick, onMouseOver: onMouseOver, title: name }, showImage ? (React.createElement("img", { alt: initials, className: clsx(`str-chat__avatar-image`), "data-testid": 'avatar-img', onError: () => setError(true), src: image })) : (React.createElement("div", { className: 'str-chat__avatar-fallback', "data-testid": 'avatar-fallback' }, initials))));
21
+ }), "data-testid": 'avatar', onClick: onClick, onMouseOver: onMouseOver, role: 'button', title: name }, showImage ? (React.createElement("img", { alt: initials, className: 'str-chat__avatar-image', "data-testid": 'avatar-img', onError: () => setError(true), src: image })) : (React.createElement(React.Fragment, null,
22
+ !!initials.length && (React.createElement("div", { className: clsx('str-chat__avatar-fallback'), "data-testid": 'avatar-fallback' }, initials)),
23
+ !initials.length && React.createElement(Icon.User, null)))));
20
24
  };
@@ -2,11 +2,10 @@ import React, { PropsWithChildren } from 'react';
2
2
  import { ChannelQueryOptions, EventAPIResponse, Message, MessageResponse, Channel as StreamChannel, StreamChat, UpdatedMessage } from 'stream-chat';
3
3
  import { OnMentionAction } from './hooks/useMentionsHandlers';
4
4
  import { LoadingErrorIndicatorProps } from '../Loading';
5
- import { StreamMessage } from '../../context/ChannelStateContext';
6
- import { ComponentContextValue } from '../../context/ComponentContext';
5
+ import { ComponentContextValue, StreamMessage } from '../../context';
7
6
  import type { UnreadMessagesNotificationProps } from '../MessageList';
8
- import type { MessageProps } from '../Message/types';
9
- import type { MessageInputProps } from '../MessageInput/MessageInput';
7
+ import type { MessageProps } from '../Message';
8
+ import type { MessageInputProps } from '../MessageInput';
10
9
  import type { ChannelUnreadUiState, CustomTrigger, DefaultStreamChatGenerics, GiphyVersions, ImageAttachmentSizeHandler, SendMessageOptions, UpdateMessageOptions, VideoAttachmentSizeHandler } from '../../types/types';
11
10
  import type { URLEnrichmentConfig } from '../MessageInput/hooks/useLinkPreviews';
12
11
  import { ReactionOptions } from '../Reactions';
@@ -10,26 +10,17 @@ import { useCreateTypingContext } from './hooks/useCreateTypingContext';
10
10
  import { useEditMessageHandler } from './hooks/useEditMessageHandler';
11
11
  import { useIsMounted } from './hooks/useIsMounted';
12
12
  import { useMentionsHandlers } from './hooks/useMentionsHandlers';
13
- import { Attachment as DefaultAttachment } from '../Attachment/Attachment';
14
13
  import { LoadingErrorIndicator as DefaultLoadingErrorIndicator, } from '../Loading';
15
14
  import { LoadingChannel as DefaultLoadingIndicator } from './LoadingChannel';
16
- import { MessageSimple } from '../Message/MessageSimple';
17
15
  import { DropzoneProvider } from '../MessageInput/DropzoneProvider';
18
- import { ChannelActionProvider, } from '../../context/ChannelActionContext';
19
- import { ChannelStateProvider, } from '../../context/ChannelStateContext';
20
- import { ComponentProvider } from '../../context/ComponentContext';
21
- import { useChatContext } from '../../context/ChatContext';
22
- import { useTranslationContext } from '../../context/TranslationContext';
23
- import { TypingProvider } from '../../context/TypingContext';
16
+ import { ChannelActionProvider, ChannelStateProvider, TypingProvider, useChatContext, useTranslationContext, WithComponents, } from '../../context';
24
17
  import { DEFAULT_HIGHLIGHT_DURATION, DEFAULT_INITIAL_CHANNEL_PAGE_SIZE, DEFAULT_JUMP_TO_PAGE_SIZE, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, DEFAULT_THREAD_PAGE_SIZE, } from '../../constants/limits';
25
- import { hasMoreMessagesProbably, UnreadMessagesSeparator } from '../MessageList';
18
+ import { hasMoreMessagesProbably } from '../MessageList';
26
19
  import { useChannelContainerClasses } from './hooks/useChannelContainerClasses';
27
20
  import { findInMsgSetByDate, findInMsgSetById, makeAddNotifications } from './utils';
28
21
  import { getChannel } from '../../utils';
29
22
  import { getImageAttachmentConfiguration, getVideoAttachmentConfiguration, } from '../Attachment/attachment-sizing';
30
- import { defaultReactionOptions } from '../Reactions';
31
- import { EventComponent } from '../EventComponent';
32
- import { DateSeparator } from '../DateSeparator';
23
+ import { useThreadContext } from '../Threads';
33
24
  const isUserResponseArray = (output) => output[0]?.id != null;
34
25
  const UnMemoizedChannel = (props) => {
35
26
  const { channel: propsChannel, EmptyPlaceholder = null, LoadingErrorIndicator, LoadingIndicator = DefaultLoadingIndicator, } = props;
@@ -60,6 +51,7 @@ const ChannelInner = (props) => {
60
51
  const { client, customClasses, latestMessageDatesByChannels, mutes, theme, } = useChatContext('Channel');
61
52
  const { t } = useTranslationContext('Channel');
62
53
  const { channelClass, chatClass, chatContainerClass, windowsEmojiClass, } = useChannelContainerClasses({ customClasses });
54
+ const thread = useThreadContext();
63
55
  const [channelConfig, setChannelConfig] = useState(channel.getConfig());
64
56
  const [notifications, setNotifications] = useState([]);
65
57
  const [quotedMessage, setQuotedMessage] = useState();
@@ -68,7 +60,11 @@ const ChannelInner = (props) => {
68
60
  const [state, dispatch] = useReducer(channelReducer,
69
61
  // channel.initialized === false if client.channel().query() was not called, e.g. ChannelList is not used
70
62
  // => Channel will call channel.watch() in useLayoutEffect => state.loading is used to signal the watch() call state
71
- { ...initialState, loading: !channel.initialized });
63
+ {
64
+ ...initialState,
65
+ hasMore: channel.state.messagePagination.hasPrev,
66
+ loading: !channel.initialized,
67
+ });
72
68
  const isMounted = useIsMounted();
73
69
  const originalTitle = useRef('');
74
70
  const lastRead = useRef();
@@ -184,7 +180,6 @@ const ChannelInner = (props) => {
184
180
  useLayoutEffect(() => {
185
181
  let errored = false;
186
182
  let done = false;
187
- let channelInitializedExternally = true;
188
183
  (async () => {
189
184
  if (!channel.initialized && initializeOnMount) {
190
185
  try {
@@ -210,7 +205,6 @@ const ChannelInner = (props) => {
210
205
  await getChannel({ channel, client, members, options: channelQueryOptions });
211
206
  const config = channel.getConfig();
212
207
  setChannelConfig(config);
213
- channelInitializedExternally = false;
214
208
  }
215
209
  catch (e) {
216
210
  dispatch({ error: e, type: 'setError' });
@@ -222,8 +216,7 @@ const ChannelInner = (props) => {
222
216
  if (!errored) {
223
217
  dispatch({
224
218
  channel,
225
- hasMore: channelInitializedExternally ||
226
- hasMoreMessagesProbably(channel.state.messages.length, channelQueryOptions.messages.limit),
219
+ hasMore: channel.state.messagePagination.hasPrev,
227
220
  type: 'initStateFromChannel',
228
221
  });
229
222
  if (client.user?.id && channel.state.read[client.user.id]) {
@@ -283,7 +276,7 @@ const ChannelInner = (props) => {
283
276
  dispatch({ hasMore, messages, type: 'loadMoreFinished' });
284
277
  }, 2000, { leading: true, trailing: true }), []);
285
278
  const loadMore = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
286
- if (!online.current || !window.navigator.onLine || !state.hasMore)
279
+ if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasPrev)
287
280
  return 0;
288
281
  // prevent duplicate loading events...
289
282
  const oldestMessage = state?.messages?.[0];
@@ -305,12 +298,11 @@ const ChannelInner = (props) => {
305
298
  dispatch({ loadingMore: false, type: 'setLoadingMore' });
306
299
  return 0;
307
300
  }
308
- const hasMoreMessages = queryResponse.messages.length === perPage;
309
- loadMoreFinished(hasMoreMessages, channel.state.messages);
301
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
310
302
  return queryResponse.messages.length;
311
303
  };
312
304
  const loadMoreNewer = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => {
313
- if (!online.current || !window.navigator.onLine || !state.hasMoreNewer)
305
+ if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasNext)
314
306
  return 0;
315
307
  const newestMessage = state?.messages?.[state?.messages?.length - 1];
316
308
  if (state.loadingMore || state.loadingMoreNewer)
@@ -330,9 +322,8 @@ const ChannelInner = (props) => {
330
322
  dispatch({ loadingMoreNewer: false, type: 'setLoadingMoreNewer' });
331
323
  return 0;
332
324
  }
333
- const hasMoreNewerMessages = channel.state.messages !== channel.state.latestMessages;
334
325
  dispatch({
335
- hasMoreNewer: hasMoreNewerMessages,
326
+ hasMoreNewer: channel.state.messagePagination.hasNext,
336
327
  messages: channel.state.messages,
337
328
  type: 'loadMoreNewerFinished',
338
329
  });
@@ -342,15 +333,9 @@ const ChannelInner = (props) => {
342
333
  const jumpToMessage = useCallback(async (messageId, messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE, highlightDuration = DEFAULT_HIGHLIGHT_DURATION) => {
343
334
  dispatch({ loadingMore: true, type: 'setLoadingMore' });
344
335
  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);
336
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
352
337
  dispatch({
353
- hasMoreNewer: channel.state.messages !== channel.state.latestMessages,
338
+ hasMoreNewer: channel.state.messagePagination.hasNext,
354
339
  highlightedMessageId: messageId,
355
340
  type: 'jumpToMessageFinished',
356
341
  });
@@ -364,9 +349,7 @@ const ChannelInner = (props) => {
364
349
  }, [channel, loadMoreFinished]);
365
350
  const jumpToLatestMessage = useCallback(async () => {
366
351
  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);
352
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
370
353
  dispatch({
371
354
  type: 'jumpToLatestMessage',
372
355
  });
@@ -377,7 +360,6 @@ const ChannelInner = (props) => {
377
360
  let lastReadMessageId = channelUnreadUiState?.last_read_message_id;
378
361
  let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id;
379
362
  let isInCurrentMessageSet = false;
380
- let hasMoreMessages = true;
381
363
  if (firstUnreadMessageId) {
382
364
  const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages);
383
365
  isInCurrentMessageSet = result.index !== -1;
@@ -409,27 +391,25 @@ const ChannelInner = (props) => {
409
391
  }
410
392
  catch (e) {
411
393
  addNotification(t('Failed to jump to the first unread message'), 'error');
412
- loadMoreFinished(hasMoreMessages, channel.state.messages);
394
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
413
395
  return;
414
396
  }
415
397
  const firstMessageWithCreationDate = messages.find((msg) => msg.created_at);
416
398
  if (!firstMessageWithCreationDate) {
417
399
  addNotification(t('Failed to jump to the first unread message'), 'error');
418
- loadMoreFinished(hasMoreMessages, channel.state.messages);
400
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
419
401
  return;
420
402
  }
421
403
  const firstMessageTimestamp = new Date(firstMessageWithCreationDate.created_at).getTime();
422
404
  if (lastReadTimestamp < firstMessageTimestamp) {
423
405
  // whole channel is unread
424
406
  firstUnreadMessageId = firstMessageWithCreationDate.id;
425
- hasMoreMessages = false;
426
407
  }
427
408
  else {
428
409
  const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages);
429
410
  lastReadMessageId = result.target?.id;
430
- hasMoreMessages = result.index >= Math.floor(queryMessageLimit / 2);
431
411
  }
432
- loadMoreFinished(hasMoreMessages, channel.state.messages);
412
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
433
413
  }
434
414
  }
435
415
  if (!firstUnreadMessageId && !lastReadMessageId) {
@@ -446,14 +426,13 @@ const ChannelInner = (props) => {
446
426
  * we have arrived to the oldest page of the channel
447
427
  */
448
428
  const indexOfTarget = channel.state.messages.findIndex((message) => message.id === targetId);
449
- hasMoreMessages = indexOfTarget >= Math.floor(queryMessageLimit / 2);
450
- loadMoreFinished(hasMoreMessages, channel.state.messages);
429
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
451
430
  firstUnreadMessageId =
452
431
  firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id;
453
432
  }
454
433
  catch (e) {
455
434
  addNotification(t('Failed to jump to the first unread message'), 'error');
456
- loadMoreFinished(hasMoreMessages, channel.state.messages);
435
+ loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages);
457
436
  return;
458
437
  }
459
438
  }
@@ -468,7 +447,7 @@ const ChannelInner = (props) => {
468
447
  last_read_message_id: lastReadMessageId,
469
448
  });
470
449
  dispatch({
471
- hasMoreNewer: channel.state.messages !== channel.state.latestMessages,
450
+ hasMoreNewer: channel.state.messagePagination.hasNext,
472
451
  highlightedMessageId: firstUnreadMessageId,
473
452
  type: 'jumpToMessageFinished',
474
453
  });
@@ -578,25 +557,37 @@ const ChannelInner = (props) => {
578
557
  errorStatusCode: parsedError.status || undefined,
579
558
  status: 'failed',
580
559
  });
560
+ thread?.upsertReplyLocally({
561
+ // @ts-expect-error
562
+ message: {
563
+ ...message,
564
+ error: parsedError,
565
+ errorStatusCode: parsedError.status || undefined,
566
+ status: 'failed',
567
+ },
568
+ });
581
569
  }
582
570
  }
583
571
  };
584
572
  const sendMessage = async ({ attachments = [], mentioned_users = [], parent, text = '', }, customMessageData, options) => {
585
573
  channel.state.filterErrorMessages();
586
574
  const messagePreview = {
587
- __html: text,
588
575
  attachments,
589
576
  created_at: new Date(),
590
577
  html: text,
591
578
  id: customMessageData?.id ?? `${client.userID}-${nanoid()}`,
592
579
  mentioned_users,
580
+ parent_id: parent?.id,
593
581
  reactions: [],
594
582
  status: 'sending',
595
583
  text,
596
584
  type: 'regular',
597
585
  user: client.user,
598
- ...(parent?.id ? { parent_id: parent.id } : null),
599
586
  };
587
+ thread?.upsertReplyLocally({
588
+ // @ts-expect-error
589
+ message: messagePreview,
590
+ });
600
591
  updateMessage(messagePreview);
601
592
  await doSendMessage(messagePreview, customMessageData, options);
602
593
  };
@@ -735,8 +726,9 @@ const ChannelInner = (props) => {
735
726
  jumpToLatestMessage,
736
727
  setChannelUnreadUiState,
737
728
  ]);
729
+ // @ts-expect-error
738
730
  const componentContextValue = useMemo(() => ({
739
- Attachment: props.Attachment || DefaultAttachment,
731
+ Attachment: props.Attachment,
740
732
  AttachmentPreviewList: props.AttachmentPreviewList,
741
733
  AudioRecorder: props.AudioRecorder,
742
734
  AutocompleteSuggestionItem: props.AutocompleteSuggestionItem,
@@ -745,7 +737,7 @@ const ChannelInner = (props) => {
745
737
  BaseImage: props.BaseImage,
746
738
  CooldownTimer: props.CooldownTimer,
747
739
  CustomMessageActionsList: props.CustomMessageActionsList,
748
- DateSeparator: props.DateSeparator || DateSeparator,
740
+ DateSeparator: props.DateSeparator,
749
741
  EditMessageInput: props.EditMessageInput,
750
742
  EmojiPicker: props.EmojiPicker,
751
743
  emojiSearchIndex: props.emojiSearchIndex,
@@ -756,7 +748,7 @@ const ChannelInner = (props) => {
756
748
  Input: props.Input,
757
749
  LinkPreviewList: props.LinkPreviewList,
758
750
  LoadingIndicator: props.LoadingIndicator,
759
- Message: props.Message || MessageSimple,
751
+ Message: props.Message,
760
752
  MessageBouncePrompt: props.MessageBouncePrompt,
761
753
  MessageDeleted: props.MessageDeleted,
762
754
  MessageListNotifications: props.MessageListNotifications,
@@ -764,13 +756,13 @@ const ChannelInner = (props) => {
764
756
  MessageOptions: props.MessageOptions,
765
757
  MessageRepliesCountButton: props.MessageRepliesCountButton,
766
758
  MessageStatus: props.MessageStatus,
767
- MessageSystem: props.MessageSystem || EventComponent,
759
+ MessageSystem: props.MessageSystem,
768
760
  MessageTimestamp: props.MessageTimestamp,
769
761
  ModalGallery: props.ModalGallery,
770
762
  PinIndicator: props.PinIndicator,
771
763
  QuotedMessage: props.QuotedMessage,
772
764
  QuotedMessagePreview: props.QuotedMessagePreview,
773
- reactionOptions: props.reactionOptions ?? defaultReactionOptions,
765
+ reactionOptions: props.reactionOptions,
774
766
  ReactionSelector: props.ReactionSelector,
775
767
  ReactionsList: props.ReactionsList,
776
768
  SendButton: props.SendButton,
@@ -782,11 +774,58 @@ const ChannelInner = (props) => {
782
774
  TriggerProvider: props.TriggerProvider,
783
775
  TypingIndicator: props.TypingIndicator,
784
776
  UnreadMessagesNotification: props.UnreadMessagesNotification,
785
- UnreadMessagesSeparator: props.UnreadMessagesSeparator || UnreadMessagesSeparator,
777
+ UnreadMessagesSeparator: props.UnreadMessagesSeparator,
786
778
  VirtualMessage: props.VirtualMessage,
787
- }),
788
- // eslint-disable-next-line react-hooks/exhaustive-deps
789
- [props.reactionOptions]);
779
+ }), [
780
+ props.Attachment,
781
+ props.AttachmentPreviewList,
782
+ props.AudioRecorder,
783
+ props.AutocompleteSuggestionItem,
784
+ props.AutocompleteSuggestionList,
785
+ props.Avatar,
786
+ props.BaseImage,
787
+ props.CooldownTimer,
788
+ props.CustomMessageActionsList,
789
+ props.DateSeparator,
790
+ props.EditMessageInput,
791
+ props.EmojiPicker,
792
+ props.EmptyStateIndicator,
793
+ props.FileUploadIcon,
794
+ props.GiphyPreviewMessage,
795
+ props.HeaderComponent,
796
+ props.Input,
797
+ props.LinkPreviewList,
798
+ props.LoadingIndicator,
799
+ props.Message,
800
+ props.MessageBouncePrompt,
801
+ props.MessageDeleted,
802
+ props.MessageListNotifications,
803
+ props.MessageNotification,
804
+ props.MessageOptions,
805
+ props.MessageRepliesCountButton,
806
+ props.MessageStatus,
807
+ props.MessageSystem,
808
+ props.MessageTimestamp,
809
+ props.ModalGallery,
810
+ props.PinIndicator,
811
+ props.QuotedMessage,
812
+ props.QuotedMessagePreview,
813
+ props.ReactionSelector,
814
+ props.ReactionsList,
815
+ props.SendButton,
816
+ props.StartRecordingAudioButton,
817
+ props.ThreadHead,
818
+ props.ThreadHeader,
819
+ props.ThreadStart,
820
+ props.Timestamp,
821
+ props.TriggerProvider,
822
+ props.TypingIndicator,
823
+ props.UnreadMessagesNotification,
824
+ props.UnreadMessagesSeparator,
825
+ props.VirtualMessage,
826
+ props.emojiSearchIndex,
827
+ props.reactionOptions,
828
+ ]);
790
829
  const typingContextValue = useCreateTypingContext({
791
830
  typing,
792
831
  });
@@ -806,7 +845,7 @@ const ChannelInner = (props) => {
806
845
  return (React.createElement("div", { className: clsx(className, windowsEmojiClass) },
807
846
  React.createElement(ChannelStateProvider, { value: channelStateContextValue },
808
847
  React.createElement(ChannelActionProvider, { value: channelActionContextValue },
809
- React.createElement(ComponentProvider, { value: componentContextValue },
848
+ React.createElement(WithComponents, { overrides: componentContextValue },
810
849
  React.createElement(TypingProvider, { value: typingContextValue },
811
850
  React.createElement("div", { className: `${chatContainerClass}` },
812
851
  dragAndDropWindow && (React.createElement(DropzoneProvider, { ...optionalMessageInputProps }, children)),
@@ -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;
@@ -67,6 +67,7 @@ export const useCreateChannelStateContext = (value) => {
67
67
  }),
68
68
  // eslint-disable-next-line react-hooks/exhaustive-deps
69
69
  [
70
+ channel.data?.name, // otherwise ChannelHeader will not be updated
70
71
  channelId,
71
72
  channelUnreadUiState,
72
73
  debounceURLEnrichmentMs,
@@ -5,7 +5,10 @@ import { useChannelPreviewInfo } from '../ChannelPreview/hooks/useChannelPreview
5
5
  import { useChannelStateContext } from '../../context/ChannelStateContext';
6
6
  import { useChatContext } from '../../context/ChatContext';
7
7
  import { useTranslationContext } from '../../context/TranslationContext';
8
- const UnMemoizedChannelHeader = (props) => {
8
+ /**
9
+ * The ChannelHeader component renders some basic information about a Channel.
10
+ */
11
+ export const ChannelHeader = (props) => {
9
12
  const { Avatar = DefaultAvatar, MenuIcon = DefaultMenuIcon, image: overrideImage, live, title: overrideTitle, } = props;
10
13
  const { channel, watcher_count } = useChannelStateContext('ChannelHeader');
11
14
  const { openMobileNav } = useChatContext('ChannelHeader');
@@ -35,7 +38,3 @@ const UnMemoizedChannelHeader = (props) => {
35
38
  ' ')),
36
39
  t('{{ watcherCount }} online', { watcherCount: watcher_count })))));
37
40
  };
38
- /**
39
- * The ChannelHeader component renders some basic information about a Channel.
40
- */
41
- export const ChannelHeader = React.memo(UnMemoizedChannelHeader);
@@ -3,26 +3,24 @@ import { getDisplayImage, getDisplayTitle } from '../utils';
3
3
  import { useChatContext } from '../../../context';
4
4
  export const useChannelPreviewInfo = (props) => {
5
5
  const { channel, overrideImage, overrideTitle } = props;
6
- const { client } = useChatContext('ChannelPreview');
7
- const [displayTitle, setDisplayTitle] = useState(getDisplayTitle(channel, client.user));
8
- const [displayImage, setDisplayImage] = useState(getDisplayImage(channel, client.user));
6
+ const { client } = useChatContext('useChannelPreviewInfo');
7
+ const [displayTitle, setDisplayTitle] = useState(() => overrideTitle || getDisplayTitle(channel, client.user));
8
+ const [displayImage, setDisplayImage] = useState(() => overrideImage || getDisplayImage(channel, client.user));
9
9
  useEffect(() => {
10
- const handleEvent = () => {
11
- setDisplayTitle((displayTitle) => {
12
- const newDisplayTitle = getDisplayTitle(channel, client.user);
13
- return displayTitle !== newDisplayTitle ? newDisplayTitle : displayTitle;
14
- });
15
- setDisplayImage((displayImage) => {
16
- const newDisplayImage = getDisplayImage(channel, client.user);
17
- return displayImage !== newDisplayImage ? newDisplayImage : displayImage;
18
- });
10
+ if (overrideTitle && overrideImage)
11
+ return;
12
+ const updateTitles = () => {
13
+ if (!overrideTitle)
14
+ setDisplayTitle(getDisplayTitle(channel, client.user));
15
+ if (!overrideImage)
16
+ setDisplayImage(getDisplayImage(channel, client.user));
19
17
  };
20
- client.on('user.updated', handleEvent);
18
+ updateTitles();
19
+ client.on('user.updated', updateTitles);
21
20
  return () => {
22
- client.off('user.updated', handleEvent);
21
+ client.off('user.updated', updateTitles);
23
22
  };
24
- // eslint-disable-next-line react-hooks/exhaustive-deps
25
- }, []);
23
+ }, [channel, channel.data, client, overrideImage, overrideTitle]);
26
24
  return {
27
25
  displayImage: overrideImage || displayImage,
28
26
  displayTitle: overrideTitle || displayTitle,
@@ -23,25 +23,14 @@ export const getLatestMessagePreview = (channel, t, userLanguage = 'en') => {
23
23
  }
24
24
  return t('Empty message...');
25
25
  };
26
- export const getDisplayTitle = (channel, currentUser) => {
27
- let title = channel.data?.name;
26
+ const getChannelDisplayInfo = (info, channel, currentUser) => {
27
+ if (channel.data?.[info])
28
+ return channel.data[info];
28
29
  const members = Object.values(channel.state.members);
29
- if (!title && members.length === 2) {
30
- const otherMember = members.find((member) => member.user?.id !== currentUser?.id);
31
- if (otherMember?.user?.name) {
32
- title = otherMember.user.name;
33
- }
34
- }
35
- return title;
36
- };
37
- export const getDisplayImage = (channel, currentUser) => {
38
- let image = channel.data?.image;
39
- const members = Object.values(channel.state.members);
40
- if (!image && members.length === 2) {
41
- const otherMember = members.find((member) => member.user?.id !== currentUser?.id);
42
- if (otherMember?.user?.image) {
43
- image = otherMember.user.image;
44
- }
45
- }
46
- return image;
30
+ if (members.length !== 2)
31
+ return;
32
+ const otherMember = members.find((member) => member.user?.id !== currentUser?.id);
33
+ return otherMember?.user?.[info];
47
34
  };
35
+ export const getDisplayTitle = (channel, currentUser) => getChannelDisplayInfo('name', channel, currentUser);
36
+ export const getDisplayImage = (channel, currentUser) => getChannelDisplayInfo('image', channel, currentUser);
@@ -97,13 +97,12 @@ export const useChannelSearch = ({ channelType = 'messaging', clearSearchOnClick
97
97
  // @ts-expect-error
98
98
  {
99
99
  $or: [{ id: { $autocomplete: text } }, { name: { $autocomplete: text } }],
100
- id: { $ne: client.userID },
101
100
  ...searchQueryParams?.userFilters?.filters,
102
101
  }, { id: 1, ...searchQueryParams?.userFilters?.sort }, { limit: 8, ...searchQueryParams?.userFilters?.options });
103
102
  if (!searchForChannels) {
104
103
  searchQueryPromiseInProgress.current = userQueryPromise;
105
104
  const { users } = await searchQueryPromiseInProgress.current;
106
- results = users;
105
+ results = users.filter((u) => u.id !== client.user?.id);
107
106
  }
108
107
  else {
109
108
  const channelQueryPromise = client.queryChannels(
@@ -117,7 +116,7 @@ export const useChannelSearch = ({ channelType = 'messaging', clearSearchOnClick
117
116
  userQueryPromise,
118
117
  ]);
119
118
  const [channels, { users }] = await searchQueryPromiseInProgress.current;
120
- results = [...channels, ...users];
119
+ results = [...channels, ...users.filter((u) => u.id !== client.user?.id)];
121
120
  }
122
121
  }
123
122
  catch (error) {
@@ -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({
@@ -24,13 +23,17 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
24
23
  return appSettings.current;
25
24
  };
26
25
  useEffect(() => {
27
- if (client) {
28
- const userAgent = client.getUserAgent();
29
- if (!userAgent.includes('stream-chat-react')) {
30
- // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
31
- client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
32
- }
26
+ if (!client)
27
+ return;
28
+ const userAgent = client.getUserAgent();
29
+ if (!userAgent.includes('stream-chat-react')) {
30
+ // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
31
+ client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
33
32
  }
33
+ client.threads.registerSubscriptions();
34
+ return () => {
35
+ client.threads.unregisterSubscriptions();
36
+ };
34
37
  }, [client]);
35
38
  useEffect(() => {
36
39
  setMutes(clientMutes);
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { PropsWithChildren } from 'react';
3
+ import type { Thread } from 'stream-chat';
4
+ export declare const ChatView: {
5
+ ({ children }: PropsWithChildren): React.JSX.Element;
6
+ Channels: ({ children }: PropsWithChildren) => React.JSX.Element | null;
7
+ Threads: ({ children }: PropsWithChildren) => React.JSX.Element | null;
8
+ ThreadAdapter: ({ children }: PropsWithChildren) => React.JSX.Element;
9
+ Selector: () => React.JSX.Element;
10
+ };
11
+ export type ThreadsViewContextValue = {
12
+ activeThread: Thread | undefined;
13
+ setActiveThread: (cv: ThreadsViewContextValue['activeThread']) => void;
14
+ };
15
+ export declare const useThreadsViewContext: () => ThreadsViewContextValue;
16
+ export declare const useActiveThread: ({ activeThread }: {
17
+ activeThread?: Thread;
18
+ }) => void;