stream-chat-react 13.10.0 → 13.10.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.
Files changed (31) hide show
  1. package/dist/components/Channel/hooks/useCreateChannelStateContext.js +7 -3
  2. package/dist/components/Chat/hooks/useChat.js +1 -1
  3. package/dist/components/Message/Message.js +1 -1
  4. package/dist/components/Message/MessageStatus.js +6 -2
  5. package/dist/components/Message/types.d.ts +4 -0
  6. package/dist/components/MessageList/MessageList.js +6 -0
  7. package/dist/components/MessageList/VirtualizedMessageList.d.ts +3 -1
  8. package/dist/components/MessageList/VirtualizedMessageList.js +6 -0
  9. package/dist/components/MessageList/VirtualizedMessageListComponents.js +2 -2
  10. package/dist/components/MessageList/hooks/MessageList/useMessageListElements.d.ts +1 -0
  11. package/dist/components/MessageList/hooks/MessageList/useMessageListElements.js +7 -2
  12. package/dist/components/MessageList/hooks/useLastDeliveredData.d.ts +1 -0
  13. package/dist/components/MessageList/hooks/useLastDeliveredData.js +20 -10
  14. package/dist/components/MessageList/hooks/useLastOwnMessage.d.ts +5 -0
  15. package/dist/components/MessageList/hooks/useLastOwnMessage.js +4 -0
  16. package/dist/components/MessageList/hooks/useLastReadData.d.ts +1 -0
  17. package/dist/components/MessageList/hooks/useLastReadData.js +20 -10
  18. package/dist/components/MessageList/renderMessages.d.ts +4 -2
  19. package/dist/components/MessageList/renderMessages.js +2 -2
  20. package/dist/context/MessageContext.d.ts +4 -0
  21. package/dist/experimental/index.browser.cjs.map +2 -2
  22. package/dist/experimental/index.node.cjs.map +2 -2
  23. package/dist/index.browser.cjs +836 -761
  24. package/dist/index.browser.cjs.map +4 -4
  25. package/dist/index.node.cjs +836 -761
  26. package/dist/index.node.cjs.map +4 -4
  27. package/dist/plugins/Emojis/index.browser.cjs.map +2 -2
  28. package/dist/plugins/Emojis/index.node.cjs.map +2 -2
  29. package/dist/utils/findReverse.d.ts +1 -0
  30. package/dist/utils/findReverse.js +9 -0
  31. package/package.json +1 -1
@@ -8,9 +8,13 @@ export const useCreateChannelStateContext = (value) => {
8
8
  const notificationsLength = notifications.length;
9
9
  const readUsers = Object.values(read);
10
10
  const readUsersLength = readUsers.length;
11
- const readUsersLastReads = readUsers
12
- .map(({ last_read }) => last_read.toISOString())
13
- .join();
11
+ const readUsersLastReadDateStrings = [];
12
+ for (const { last_read } of readUsers) {
13
+ if (!lastRead)
14
+ continue;
15
+ readUsersLastReadDateStrings.push(last_read?.toISOString());
16
+ }
17
+ const readUsersLastReads = readUsersLastReadDateStrings.join();
14
18
  const threadMessagesLength = threadMessages?.length;
15
19
  const channelCapabilities = {};
16
20
  channelCapabilitiesArray.forEach((capability) => {
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
24
24
  useEffect(() => {
25
25
  if (!client)
26
26
  return;
27
- const version = "13.10.0";
27
+ const version = "13.10.1";
28
28
  const userAgent = client.getUserAgent();
29
29
  if (!userAgent.includes('stream-chat-react')) {
30
30
  // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
@@ -116,5 +116,5 @@ export const Message = (props) => {
116
116
  notify: addNotification,
117
117
  });
118
118
  const highlighted = highlightedMessageId === message.id;
119
- return (React.createElement(MemoizedMessage, { additionalMessageInputProps: props.additionalMessageInputProps, autoscrollToBottom: props.autoscrollToBottom, canPin: canPin, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: props.customMessageActions, deliveredTo: props.deliveredTo, disableQuotedMessages: props.disableQuotedMessages, endOfGroup: props.endOfGroup, firstOfGroup: props.firstOfGroup, formatDate: props.formatDate, groupedByUser: props.groupedByUser, groupStyles: props.groupStyles, handleAction: handleAction, handleDelete: handleDelete, handleFetchReactions: handleFetchReactions, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handleOpenThread: handleOpenThread, handlePin: handlePin, handleReaction: handleReaction, handleRetry: handleRetry, highlighted: highlighted, initialMessage: props.initialMessage, lastReceivedId: props.lastReceivedId, message: message, Message: props.Message, messageActions: props.messageActions, messageListRect: props.messageListRect, mutes: mutes, onMentionsClickMessage: onMentionsClick, onMentionsHoverMessage: onMentionsHover, onUserClick: props.onUserClick, onUserHover: props.onUserHover, pinPermissions: props.pinPermissions, reactionDetailsSort: reactionDetailsSort, readBy: props.readBy, renderText: props.renderText, sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: props.threadList, unsafeHTML: props.unsafeHTML, userRoles: userRoles }));
119
+ return (React.createElement(MemoizedMessage, { additionalMessageInputProps: props.additionalMessageInputProps, autoscrollToBottom: props.autoscrollToBottom, canPin: canPin, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: props.customMessageActions, deliveredTo: props.deliveredTo, disableQuotedMessages: props.disableQuotedMessages, endOfGroup: props.endOfGroup, firstOfGroup: props.firstOfGroup, formatDate: props.formatDate, groupedByUser: props.groupedByUser, groupStyles: props.groupStyles, handleAction: handleAction, handleDelete: handleDelete, handleFetchReactions: handleFetchReactions, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handleOpenThread: handleOpenThread, handlePin: handlePin, handleReaction: handleReaction, handleRetry: handleRetry, highlighted: highlighted, initialMessage: props.initialMessage, lastOwnMessage: props.lastOwnMessage, lastReceivedId: props.lastReceivedId, message: message, Message: props.Message, messageActions: props.messageActions, messageListRect: props.messageListRect, mutes: mutes, onMentionsClickMessage: onMentionsClick, onMentionsHoverMessage: onMentionsHover, onUserClick: props.onUserClick, onUserHover: props.onUserHover, pinPermissions: props.pinPermissions, reactionDetailsSort: reactionDetailsSort, readBy: props.readBy, renderText: props.renderText, returnAllReadData: props.returnAllReadData, sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: props.threadList, unsafeHTML: props.unsafeHTML, userRoles: userRoles }));
120
120
  };
@@ -15,7 +15,7 @@ const UnMemoizedMessageStatus = (props) => {
15
15
  const { handleEnter, handleLeave, tooltipVisible } = useEnterLeaveHandlers();
16
16
  const { client } = useChatContext('MessageStatus');
17
17
  const { Avatar: contextAvatar } = useComponentContext('MessageStatus');
18
- const { deliveredTo, isMyMessage, message, readBy, threadList } = useMessageContext('MessageStatus');
18
+ const { deliveredTo, isMyMessage, lastOwnMessage, message, readBy, returnAllReadData, threadList, } = useMessageContext('MessageStatus');
19
19
  const { t } = useTranslationContext('MessageStatus');
20
20
  const [referenceElement, setReferenceElement] = useState(null);
21
21
  const Avatar = propAvatar || contextAvatar || DefaultAvatar;
@@ -26,7 +26,11 @@ const UnMemoizedMessageStatus = (props) => {
26
26
  const sending = message.status === 'sending';
27
27
  const read = !!(readBy?.length && !justReadByMe && !threadList);
28
28
  const delivered = !!(deliveredTo?.length && !deliveredOnlyToMe && !read && !threadList);
29
- const sent = message.status === 'received' && !delivered && !read && !threadList;
29
+ const sent = (returnAllReadData || lastOwnMessage?.id === message.id) &&
30
+ message.status === 'received' &&
31
+ !delivered &&
32
+ !read &&
33
+ !threadList;
30
34
  const readersWithoutOwnUser = read
31
35
  ? readBy.filter((item) => item.id !== client.user?.id)
32
36
  : [];
@@ -59,6 +59,8 @@ export type MessageProps = {
59
59
  highlighted?: boolean;
60
60
  /** Whether the threaded message is the first in the thread list */
61
61
  initialMessage?: boolean;
62
+ /** Latest own message in currently displayed message set. */
63
+ lastOwnMessage?: LocalMessage;
62
64
  /** Latest message id on current channel */
63
65
  lastReceivedId?: string | null;
64
66
  /** UI component to display a Message in MessageList, overrides value in [ComponentContext](https://getstream.io/chat/docs/sdk/react/contexts/component_context/#message) */
@@ -89,6 +91,8 @@ export type MessageProps = {
89
91
  renderText?: (text?: string, mentioned_users?: UserResponse[], options?: RenderTextOptions) => ReactNode;
90
92
  /** Custom retry send message handler to override default in [ChannelActionContext](https://getstream.io/chat/docs/sdk/react/contexts/channel_action_context/) */
91
93
  retrySendMessage?: ChannelActionContextValue['retrySendMessage'];
94
+ /** Keep track of read receipts for each message sent by the user. When disabled, only the last own message delivery / read status is rendered. */
95
+ returnAllReadData?: boolean;
92
96
  /** Comparator function to sort the list of reacted users
93
97
  * @deprecated use `reactionDetailsSort` instead
94
98
  */
@@ -20,6 +20,7 @@ import { MessageListMainPanel as DefaultMessageListMainPanel } from './MessageLi
20
20
  import { defaultRenderMessages } from './renderMessages';
21
21
  import { useStableId } from '../UtilityComponents/useStableId';
22
22
  import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, } from '../../constants/limits';
23
+ import { useLastOwnMessage } from './hooks/useLastOwnMessage';
23
24
  const MessageListWithContext = (props) => {
24
25
  const { channel, channelUnreadUiState, disableDateSeparator = false, groupStyles, hasMoreNewer = false, headerPosition, hideDeletedMessages = false, hideNewMessageSeparator = false, highlightedMessageId, internalInfiniteScrollProps: { threshold: loadMoreScrollThreshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, ...restInternalInfiniteScrollProps } = {}, jumpToLatestMessage = () => Promise.resolve(), loadMore: loadMoreCallback, loadMoreNewer: loadMoreNewerCallback, // @deprecated in favor of `channelCapabilities` - TODO: remove in next major release
25
26
  maxTimeBetweenGroupedMessages, messageActions = Object.keys(MESSAGE_ACTIONS), messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE, messages = [], noGroupByUser = false, notifications, pinPermissions = defaultPinPermissions, reactionDetailsSort, renderMessages = defaultRenderMessages, returnAllReadData = false, reviewProcessedMessage, showUnreadNotificationAlways, sortReactionDetails, sortReactions, suppressAutoscroll, threadList = false, unsafeHTML = false, } = props;
@@ -57,6 +58,10 @@ const MessageListWithContext = (props) => {
57
58
  noGroupByUser,
58
59
  reviewProcessedMessage,
59
60
  });
61
+ const lastOwnMessage = useLastOwnMessage({
62
+ messages,
63
+ ownUserId: channel.getClient().user?.id,
64
+ });
60
65
  const elements = useMessageListElements({
61
66
  channelUnreadUiState,
62
67
  enrichedMessages,
@@ -91,6 +96,7 @@ const MessageListWithContext = (props) => {
91
96
  sortReactions,
92
97
  unsafeHTML,
93
98
  },
99
+ lastOwnMessage,
94
100
  messageGroupStyles,
95
101
  messages,
96
102
  renderMessages,
@@ -10,7 +10,7 @@ import type { ComponentContextValue } from '../../context/ComponentContext';
10
10
  import type { LocalMessage, UserResponse } from 'stream-chat';
11
11
  import type { UnknownType } from '../../types/types';
12
12
  type PropsDrilledToMessage = 'additionalMessageInputProps' | 'customMessageActions' | 'formatDate' | 'messageActions' | 'openThread' | 'reactionDetailsSort' | 'sortReactions' | 'sortReactionDetails';
13
- type VirtualizedMessageListPropsForContext = PropsDrilledToMessage | 'closeReactionSelectorOnClick' | 'customMessageRenderer' | 'head' | 'loadingMore' | 'Message' | 'shouldGroupByUser' | 'threadList';
13
+ type VirtualizedMessageListPropsForContext = PropsDrilledToMessage | 'closeReactionSelectorOnClick' | 'customMessageRenderer' | 'head' | 'loadingMore' | 'Message' | 'returnAllReadData' | 'shouldGroupByUser' | 'threadList';
14
14
  /**
15
15
  * Context object provided to some Virtuoso props that are functions (components rendered by Virtuoso and other functions)
16
16
  */
@@ -29,6 +29,8 @@ export type VirtuosoContext = Required<Pick<ComponentContextValue, 'DateSeparato
29
29
  processedMessages: RenderedMessage[];
30
30
  /** Instance of VirtuosoHandle object providing the API to navigate in the virtualized list by various scroll actions. */
31
31
  virtuosoRef: RefObject<VirtuosoHandle | null>;
32
+ /** Latest own message in currently displayed message set. */
33
+ lastOwnMessage?: LocalMessage;
32
34
  /** Message id which was marked as unread. ALl the messages following this message are considered unrea. */
33
35
  firstUnreadMessageId?: string;
34
36
  lastReadDate?: Date;
@@ -23,6 +23,7 @@ import { VirtualizedMessageListContextProvider } from '../../context/Virtualized
23
23
  import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
24
24
  import { useStableId } from '../UtilityComponents/useStableId';
25
25
  import { useLastDeliveredData } from './hooks/useLastDeliveredData';
26
+ import { useLastOwnMessage } from './hooks/useLastOwnMessage';
26
27
  function captureResizeObserverExceededError(e) {
27
28
  if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
28
29
  e.message === 'ResizeObserver loop limit exceeded') {
@@ -101,14 +102,17 @@ const VirtualizedMessageListWithContext = (props) => {
101
102
  messages?.length,
102
103
  client.userID,
103
104
  ]);
105
+ const lastOwnMessage = useLastOwnMessage({ messages, ownUserId: client.user?.id });
104
106
  // get the mapping of own messages to array of users who read them
105
107
  const ownMessagesReadByOthers = useLastReadData({
106
108
  channel,
109
+ lastOwnMessage,
107
110
  messages: messages || [],
108
111
  returnAllReadData,
109
112
  });
110
113
  const ownMessagesDeliveredToOthers = useLastDeliveredData({
111
114
  channel,
115
+ lastOwnMessage,
112
116
  messages: messages || [],
113
117
  returnAllReadData,
114
118
  });
@@ -226,6 +230,7 @@ const VirtualizedMessageListWithContext = (props) => {
226
230
  firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id,
227
231
  formatDate,
228
232
  head,
233
+ lastOwnMessage,
229
234
  lastReadDate: channelUnreadUiState?.last_read,
230
235
  lastReadMessageId: channelUnreadUiState?.last_read_message_id,
231
236
  lastReceivedMessageId,
@@ -240,6 +245,7 @@ const VirtualizedMessageListWithContext = (props) => {
240
245
  ownMessagesReadByOthers,
241
246
  processedMessages,
242
247
  reactionDetailsSort,
248
+ returnAllReadData,
243
249
  shouldGroupByUser,
244
250
  sortReactionDetails,
245
251
  sortReactions,
@@ -51,7 +51,7 @@ export const EmptyPlaceholder = ({ context }) => {
51
51
  return (React.createElement(React.Fragment, null, EmptyStateIndicator && (React.createElement(EmptyStateIndicator, { listType: context?.threadList ? 'thread' : 'message' }))));
52
52
  };
53
53
  export const messageRenderer = (virtuosoIndex, _data, virtuosoContext) => {
54
- const { additionalMessageInputProps, closeReactionSelectorOnClick, customMessageActions, customMessageRenderer, DateSeparator, firstUnreadMessageId, formatDate, lastReadDate, lastReadMessageId, lastReceivedMessageId, Message: MessageUIComponent, messageActions, messageGroupStyles, MessageSystem, numItemsPrepended, openThread, ownMessagesDeliveredToOthers, ownMessagesReadByOthers, processedMessages: messageList, reactionDetailsSort, shouldGroupByUser, sortReactionDetails, sortReactions, threadList, unreadMessageCount = 0, UnreadMessagesSeparator, virtuosoRef, } = virtuosoContext;
54
+ const { additionalMessageInputProps, closeReactionSelectorOnClick, customMessageActions, customMessageRenderer, DateSeparator, firstUnreadMessageId, formatDate, lastOwnMessage, lastReadDate, lastReadMessageId, lastReceivedMessageId, Message: MessageUIComponent, messageActions, messageGroupStyles, MessageSystem, numItemsPrepended, openThread, ownMessagesDeliveredToOthers, ownMessagesReadByOthers, processedMessages: messageList, reactionDetailsSort, returnAllReadData, shouldGroupByUser, sortReactionDetails, sortReactions, threadList, unreadMessageCount = 0, UnreadMessagesSeparator, virtuosoRef, } = virtuosoContext;
55
55
  const streamMessageIndex = calculateItemIndex(virtuosoIndex, numItemsPrepended);
56
56
  if (customMessageRenderer) {
57
57
  return customMessageRenderer(messageList, streamMessageIndex);
@@ -88,5 +88,5 @@ export const messageRenderer = (virtuosoIndex, _data, virtuosoContext) => {
88
88
  return (React.createElement(React.Fragment, null,
89
89
  isFirstUnreadMessage && (React.createElement("div", { className: 'str-chat__unread-messages-separator-wrapper' },
90
90
  React.createElement(UnreadMessagesSeparator, { unreadCount: unreadMessageCount }))),
91
- React.createElement(Message, { additionalMessageInputProps: additionalMessageInputProps, autoscrollToBottom: virtuosoRef.current?.autoscrollToBottom, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: customMessageActions, deliveredTo: ownMessagesDeliveredToOthers[message.id] || [], endOfGroup: endOfGroup, firstOfGroup: firstOfGroup, formatDate: formatDate, groupedByUser: groupedByUser, groupStyles: [messageGroupStyles[message.id] ?? ''], lastReceivedId: lastReceivedMessageId, message: message, Message: MessageUIComponent, messageActions: messageActions, openThread: openThread, reactionDetailsSort: reactionDetailsSort, readBy: ownMessagesReadByOthers[message.id] || [], sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: threadList })));
91
+ React.createElement(Message, { additionalMessageInputProps: additionalMessageInputProps, autoscrollToBottom: virtuosoRef.current?.autoscrollToBottom, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: customMessageActions, deliveredTo: ownMessagesDeliveredToOthers[message.id] || [], endOfGroup: endOfGroup, firstOfGroup: firstOfGroup, formatDate: formatDate, groupedByUser: groupedByUser, groupStyles: [messageGroupStyles[message.id] ?? ''], lastOwnMessage: lastOwnMessage, lastReceivedId: lastReceivedMessageId, message: message, Message: MessageUIComponent, messageActions: messageActions, openThread: openThread, reactionDetailsSort: reactionDetailsSort, readBy: ownMessagesReadByOthers[message.id] || [], returnAllReadData: returnAllReadData, sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: threadList })));
92
92
  };
@@ -12,6 +12,7 @@ type UseMessageListElementsProps = {
12
12
  returnAllReadData: boolean;
13
13
  threadList: boolean;
14
14
  channelUnreadUiState?: ChannelUnreadUiState;
15
+ lastOwnMessage?: LocalMessage;
15
16
  };
16
17
  export declare const useMessageListElements: (props: UseMessageListElementsProps) => React.ReactNode[];
17
18
  export {};
@@ -6,18 +6,20 @@ import { useComponentContext } from '../../../../context/ComponentContext';
6
6
  import { useChannelStateContext } from '../../../../context';
7
7
  import { useLastDeliveredData } from '../useLastDeliveredData';
8
8
  export const useMessageListElements = (props) => {
9
- const { channelUnreadUiState, enrichedMessages, internalMessageProps, messageGroupStyles, messages, renderMessages, returnAllReadData, threadList, } = props;
9
+ const { channelUnreadUiState, enrichedMessages, internalMessageProps, lastOwnMessage, messageGroupStyles, messages, renderMessages, returnAllReadData, threadList, } = props;
10
10
  const { customClasses } = useChatContext('useMessageListElements');
11
11
  const { channel } = useChannelStateContext();
12
12
  const components = useComponentContext('useMessageListElements');
13
13
  // get the readData, but only for messages submitted by the user themselves
14
14
  const readData = useLastReadData({
15
15
  channel,
16
+ lastOwnMessage,
16
17
  messages,
17
18
  returnAllReadData,
18
19
  });
19
20
  const ownMessagesDeliveredToOthers = useLastDeliveredData({
20
21
  channel,
22
+ lastOwnMessage,
21
23
  messages,
22
24
  returnAllReadData,
23
25
  });
@@ -26,22 +28,25 @@ export const useMessageListElements = (props) => {
26
28
  channelUnreadUiState,
27
29
  components,
28
30
  customClasses,
31
+ lastOwnMessage,
29
32
  lastReceivedMessageId,
30
33
  messageGroupStyles,
31
34
  messages: enrichedMessages,
32
35
  ownMessagesDeliveredToOthers,
33
36
  readData,
34
- sharedMessageProps: { ...internalMessageProps, threadList },
37
+ sharedMessageProps: { ...internalMessageProps, returnAllReadData, threadList },
35
38
  }),
36
39
  // eslint-disable-next-line react-hooks/exhaustive-deps
37
40
  [
38
41
  enrichedMessages,
39
42
  internalMessageProps,
43
+ lastOwnMessage,
40
44
  lastReceivedMessageId,
41
45
  messageGroupStyles,
42
46
  channelUnreadUiState,
43
47
  readData,
44
48
  renderMessages,
49
+ returnAllReadData,
45
50
  threadList,
46
51
  ]);
47
52
  return elements;
@@ -3,6 +3,7 @@ type UseLastDeliveredDataParams = {
3
3
  channel: Channel;
4
4
  messages: LocalMessage[];
5
5
  returnAllReadData: boolean;
6
+ lastOwnMessage?: LocalMessage;
6
7
  };
7
8
  export declare const useLastDeliveredData: (props: UseLastDeliveredDataParams) => Record<string, UserResponse[]>;
8
9
  export {};
@@ -1,15 +1,25 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
2
  export const useLastDeliveredData = (props) => {
3
- const { channel, messages, returnAllReadData } = props;
4
- const calculate = useCallback(() => returnAllReadData
5
- ? messages.reduce((acc, msg) => {
6
- acc[msg.id] = channel.messageReceiptsTracker.deliveredForMessage({
7
- msgId: msg.id,
8
- timestampMs: msg.created_at.getTime(),
9
- });
10
- return acc;
11
- }, {})
12
- : channel.messageReceiptsTracker.groupUsersByLastDeliveredMessage(), [channel, messages, returnAllReadData]);
3
+ const { channel, lastOwnMessage, messages, returnAllReadData } = props;
4
+ const calculate = useCallback(() => {
5
+ if (returnAllReadData) {
6
+ return messages.reduce((acc, msg) => {
7
+ acc[msg.id] = channel.messageReceiptsTracker.deliveredForMessage({
8
+ msgId: msg.id,
9
+ timestampMs: msg.created_at.getTime(),
10
+ });
11
+ return acc;
12
+ }, {});
13
+ }
14
+ if (!lastOwnMessage)
15
+ return {};
16
+ return {
17
+ [lastOwnMessage.id]: channel.messageReceiptsTracker.deliveredForMessage({
18
+ msgId: lastOwnMessage.id,
19
+ timestampMs: lastOwnMessage.created_at.getTime(),
20
+ }),
21
+ };
22
+ }, [channel, lastOwnMessage, messages, returnAllReadData]);
13
23
  const [deliveredTo, setDeliveredTo] = useState(calculate);
14
24
  useEffect(() => channel.on('message.delivered', () => setDeliveredTo(calculate)).unsubscribe, [channel, calculate]);
15
25
  return deliveredTo;
@@ -0,0 +1,5 @@
1
+ import type { LocalMessage } from 'stream-chat';
2
+ export declare const useLastOwnMessage: ({ messages, ownUserId, }: {
3
+ messages?: LocalMessage[];
4
+ ownUserId?: string;
5
+ }) => LocalMessage | undefined;
@@ -0,0 +1,4 @@
1
+ import { useMemo } from 'react';
2
+ import { findReverse } from '../../../utils/findReverse';
3
+ // fixme: we should be able to retrieve last own message quickly from the LLC. Should be done when refactoring the LLC Channel state to reactive.
4
+ export const useLastOwnMessage = ({ messages, ownUserId, }) => useMemo(() => messages && findReverse(messages, (msg) => (msg.user && msg.user.id) === ownUserId), [messages, ownUserId]);
@@ -3,6 +3,7 @@ type UseLastReadDataParams = {
3
3
  channel: Channel;
4
4
  messages: LocalMessage[];
5
5
  returnAllReadData: boolean;
6
+ lastOwnMessage?: LocalMessage;
6
7
  };
7
8
  export declare const useLastReadData: (props: UseLastReadDataParams) => Record<string, UserResponse[]>;
8
9
  export {};
@@ -1,13 +1,23 @@
1
1
  import { useMemo } from 'react';
2
2
  export const useLastReadData = (props) => {
3
- const { channel, messages, returnAllReadData } = props;
4
- return useMemo(() => returnAllReadData
5
- ? messages.reduce((acc, msg) => {
6
- acc[msg.id] = channel.messageReceiptsTracker.readersForMessage({
7
- msgId: msg.id,
8
- timestampMs: msg.created_at.getTime(),
9
- });
10
- return acc;
11
- }, {})
12
- : channel.messageReceiptsTracker.groupUsersByLastReadMessage(), [channel, messages, returnAllReadData]);
3
+ const { channel, lastOwnMessage, messages, returnAllReadData } = props;
4
+ return useMemo(() => {
5
+ if (returnAllReadData) {
6
+ return messages.reduce((acc, msg) => {
7
+ acc[msg.id] = channel.messageReceiptsTracker.readersForMessage({
8
+ msgId: msg.id,
9
+ timestampMs: msg.created_at.getTime(),
10
+ });
11
+ return acc;
12
+ }, {});
13
+ }
14
+ if (!lastOwnMessage)
15
+ return {};
16
+ return {
17
+ [lastOwnMessage.id]: channel.messageReceiptsTracker.readersForMessage({
18
+ msgId: lastOwnMessage.id,
19
+ timestampMs: lastOwnMessage.created_at.getTime(),
20
+ }),
21
+ };
22
+ }, [channel, lastOwnMessage, messages, returnAllReadData]);
13
23
  };
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react';
2
2
  import React from 'react';
3
3
  import type { GroupStyle, RenderedMessage } from './utils';
4
4
  import type { MessageProps } from '../Message';
5
- import type { UserResponse } from 'stream-chat';
5
+ import type { LocalMessage, UserResponse } from 'stream-chat';
6
6
  import type { ComponentContextValue, CustomClasses } from '../../context';
7
7
  import type { ChannelUnreadUiState } from '../../types';
8
8
  export interface RenderMessagesOptions {
@@ -19,6 +19,8 @@ export interface RenderMessagesOptions {
19
19
  * Props forwarded to the Message component.
20
20
  */
21
21
  sharedMessageProps: SharedMessageProps;
22
+ /** Latest own message in currently displayed message set. */
23
+ lastOwnMessage?: LocalMessage;
22
24
  /**
23
25
  * Current user's channel read state used to render components reflecting unread state.
24
26
  * It does not reflect the back-end state if a channel is marked read on mount.
@@ -30,5 +32,5 @@ export interface RenderMessagesOptions {
30
32
  export type SharedMessageProps = Omit<MessageProps, MessagePropsToOmit>;
31
33
  export type MessageRenderer = (options: RenderMessagesOptions) => Array<ReactNode>;
32
34
  type MessagePropsToOmit = 'channel' | 'deliveredTo' | 'groupStyles' | 'initialMessage' | 'lastReceivedId' | 'message' | 'readBy';
33
- export declare function defaultRenderMessages({ channelUnreadUiState, components, customClasses, lastReceivedMessageId: lastReceivedId, messageGroupStyles, messages, ownMessagesDeliveredToOthers, readData, sharedMessageProps: messageProps, }: RenderMessagesOptions): React.JSX.Element[];
35
+ export declare function defaultRenderMessages({ channelUnreadUiState, components, customClasses, lastOwnMessage, lastReceivedMessageId: lastReceivedId, messageGroupStyles, messages, ownMessagesDeliveredToOthers, readData, sharedMessageProps: messageProps, }: RenderMessagesOptions): React.JSX.Element[];
34
36
  export {};
@@ -4,7 +4,7 @@ import { Message } from '../Message';
4
4
  import { DateSeparator as DefaultDateSeparator } from '../DateSeparator';
5
5
  import { EventComponent as DefaultMessageSystem } from '../EventComponent';
6
6
  import { UnreadMessagesSeparator as DefaultUnreadMessagesSeparator } from './UnreadMessagesSeparator';
7
- export function defaultRenderMessages({ channelUnreadUiState, components, customClasses, lastReceivedMessageId: lastReceivedId, messageGroupStyles, messages, ownMessagesDeliveredToOthers, readData, sharedMessageProps: messageProps, }) {
7
+ export function defaultRenderMessages({ channelUnreadUiState, components, customClasses, lastOwnMessage, lastReceivedMessageId: lastReceivedId, messageGroupStyles, messages, ownMessagesDeliveredToOthers, readData, sharedMessageProps: messageProps, }) {
8
8
  const { DateSeparator = DefaultDateSeparator, HeaderComponent, MessageSystem = DefaultMessageSystem, UnreadMessagesSeparator = DefaultUnreadMessagesSeparator, } = components;
9
9
  const renderedMessages = [];
10
10
  let firstMessage;
@@ -44,7 +44,7 @@ export function defaultRenderMessages({ channelUnreadUiState, components, custom
44
44
  isFirstUnreadMessage && UnreadMessagesSeparator && (React.createElement("li", { className: 'str-chat__li str-chat__unread-messages-separator-wrapper' },
45
45
  React.createElement(UnreadMessagesSeparator, { unreadCount: channelUnreadUiState?.unread_messages }))),
46
46
  React.createElement("li", { className: messageClass, "data-message-id": message.id, "data-testid": messageClass },
47
- React.createElement(Message, { deliveredTo: ownMessagesDeliveredToOthers[message.id] || [], groupStyles: [groupStyles], lastReceivedId: lastReceivedId, message: message, readBy: readData[message.id] || [], ...messageProps }))));
47
+ React.createElement(Message, { deliveredTo: ownMessagesDeliveredToOthers[message.id] || [], groupStyles: [groupStyles], lastOwnMessage: lastOwnMessage, lastReceivedId: lastReceivedId, message: message, readBy: readData[message.id] || [], ...messageProps }))));
48
48
  previousMessage = message;
49
49
  }
50
50
  }
@@ -92,6 +92,8 @@ export type MessageContextValue = {
92
92
  * A factory function that determines whether a message is AI generated or not.
93
93
  */
94
94
  isMessageAIGenerated?: (message: LocalMessage) => boolean;
95
+ /** Latest own message in currently displayed message set. */
96
+ lastOwnMessage?: LocalMessage;
95
97
  /** Latest message id on current channel */
96
98
  lastReceivedId?: string | null;
97
99
  /** DOMRect object for parent MessageList component */
@@ -106,6 +108,8 @@ export type MessageContextValue = {
106
108
  readBy?: UserResponse[];
107
109
  /** Custom function to render message text content, defaults to the renderText function: [utils](https://github.com/GetStream/stream-chat-react/blob/master/src/utils.tsx) */
108
110
  renderText?: (text?: string, mentioned_users?: UserResponse[], options?: RenderTextOptions) => ReactNode;
111
+ /** Keep track of read receipts for each message sent by the user. When disabled, only the last own message delivery / read status is rendered. */
112
+ returnAllReadData?: boolean;
109
113
  /** Comparator function to sort the list of reacted users
110
114
  * @deprecated use `reactionDetailsSort` instead
111
115
  */