stream-chat-react 13.8.0 → 13.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Channel/Channel.d.ts +2 -2
- package/dist/components/Channel/Channel.js +3 -3
- package/dist/components/Channel/hooks/useCreateChannelStateContext.js +3 -1
- package/dist/components/ChannelPreview/hooks/useMessageDeliveryStatus.d.ts +1 -0
- package/dist/components/ChannelPreview/hooks/useMessageDeliveryStatus.js +26 -14
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Message/Message.js +1 -1
- package/dist/components/Message/MessageStatus.d.ts +1 -0
- package/dist/components/Message/MessageStatus.js +22 -12
- package/dist/components/Message/hooks/useDeleteHandler.js +2 -2
- package/dist/components/Message/icons.d.ts +1 -0
- package/dist/components/Message/icons.js +6 -2
- package/dist/components/Message/types.d.ts +2 -0
- package/dist/components/Message/utils.d.ts +4 -1
- package/dist/components/Message/utils.js +9 -0
- package/dist/components/MessageActions/MessageActionsBox.js +3 -1
- package/dist/components/MessageList/MessageList.js +2 -2
- package/dist/components/MessageList/VirtualizedMessageList.d.ts +2 -0
- package/dist/components/MessageList/VirtualizedMessageList.js +10 -4
- package/dist/components/MessageList/VirtualizedMessageListComponents.js +2 -2
- package/dist/components/MessageList/hooks/MessageList/useMessageListElements.d.ts +2 -2
- package/dist/components/MessageList/hooks/MessageList/useMessageListElements.js +13 -5
- package/dist/components/MessageList/hooks/useLastDeliveredData.d.ts +8 -0
- package/dist/components/MessageList/hooks/useLastDeliveredData.js +13 -0
- package/dist/components/MessageList/hooks/useLastReadData.d.ts +3 -8
- package/dist/components/MessageList/hooks/useLastReadData.js +10 -7
- package/dist/components/MessageList/renderMessages.d.ts +5 -4
- package/dist/components/MessageList/renderMessages.js +2 -2
- package/dist/components/MessageList/utils.d.ts +1 -5
- package/dist/components/MessageList/utils.js +0 -30
- package/dist/context/ChannelActionContext.d.ts +2 -2
- package/dist/context/MessageContext.d.ts +5 -3
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/experimental/index.browser.cjs +19 -13
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs +19 -13
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/i18n/Streami18n.d.ts +2 -0
- package/dist/i18n/de.json +2 -0
- package/dist/i18n/en.json +2 -0
- package/dist/i18n/es.json +2 -0
- package/dist/i18n/fr.json +2 -0
- package/dist/i18n/hi.json +2 -0
- package/dist/i18n/it.json +2 -0
- package/dist/i18n/ja.json +2 -0
- package/dist/i18n/ko.json +2 -0
- package/dist/i18n/nl.json +2 -0
- package/dist/i18n/pt.json +2 -0
- package/dist/i18n/ru.json +2 -0
- package/dist/i18n/tr.json +2 -0
- package/dist/index.browser.cjs +984 -885
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +986 -886
- package/dist/index.node.cjs.map +4 -4
- package/dist/plugins/Emojis/index.browser.cjs.map +2 -2
- package/dist/plugins/Emojis/index.node.cjs.map +2 -2
- package/dist/scss/v2/Dialog/Dialog-layout.scss +12 -3
- package/dist/scss/v2/Icon/Icon-layout.scss +6 -0
- package/dist/scss/v2/Icon/Icon-theme.scss +4 -0
- package/dist/scss/v2/Message/Message-layout.scss +30 -3
- package/dist/scss/v2/Message/Message-theme.scss +9 -0
- package/dist/scss/v2/MessageInput/MessageInput-layout.scss +8 -0
- package/dist/scss/v2/MessageInput/MessageInput-theme.scss +2 -1
- package/dist/scss/v2/Poll/Poll-layout.scss +29 -7
- package/dist/types/defaultDataInterfaces.d.ts +1 -0
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PropsWithChildren } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import type { ChannelQueryOptions, EventAPIResponse, LocalMessage, Message, MessageResponse, SendMessageOptions, Channel as StreamChannel, StreamChat, UpdateMessageOptions } from 'stream-chat';
|
|
3
|
+
import type { ChannelQueryOptions, DeleteMessageOptions, EventAPIResponse, LocalMessage, Message, MessageResponse, SendMessageOptions, Channel as StreamChannel, StreamChat, UpdateMessageOptions } from 'stream-chat';
|
|
4
4
|
import type { OnMentionAction } from './hooks/useMentionsHandlers';
|
|
5
5
|
import type { LoadingErrorIndicatorProps } from '../Loading';
|
|
6
6
|
import type { ComponentContextValue } from '../../context';
|
|
@@ -19,7 +19,7 @@ export type ChannelProps = ChannelPropsForwardedToComponentContext & {
|
|
|
19
19
|
*/
|
|
20
20
|
channelQueryOptions?: ChannelQueryOptions;
|
|
21
21
|
/** Custom action handler to override the default `client.deleteMessage(message.id)` function */
|
|
22
|
-
doDeleteMessageRequest?: (message: LocalMessage) => Promise<MessageResponse>;
|
|
22
|
+
doDeleteMessageRequest?: (message: LocalMessage, options?: DeleteMessageOptions) => Promise<MessageResponse>;
|
|
23
23
|
/** Custom action handler to override the default `channel.markRead` request function (advanced usage only) */
|
|
24
24
|
doMarkReadRequest?: (channel: StreamChannel, setChannelUnreadUiState?: (state: ChannelUnreadUiState) => void) => Promise<EventAPIResponse> | void;
|
|
25
25
|
/** Custom action handler to override the default `channel.sendMessage` request function (advanced usage only) */
|
|
@@ -500,16 +500,16 @@ const ChannelInner = (props) => {
|
|
|
500
500
|
t,
|
|
501
501
|
channelUnreadUiState,
|
|
502
502
|
]);
|
|
503
|
-
const deleteMessage = useCallback(async (message) => {
|
|
503
|
+
const deleteMessage = useCallback(async (message, options) => {
|
|
504
504
|
if (!message?.id) {
|
|
505
505
|
throw new Error('Cannot delete a message - missing message ID.');
|
|
506
506
|
}
|
|
507
507
|
let deletedMessage;
|
|
508
508
|
if (doDeleteMessageRequest) {
|
|
509
|
-
deletedMessage = await doDeleteMessageRequest(message);
|
|
509
|
+
deletedMessage = await doDeleteMessageRequest(message, options);
|
|
510
510
|
}
|
|
511
511
|
else {
|
|
512
|
-
const result = await client.deleteMessage(message.id);
|
|
512
|
+
const result = await client.deleteMessage(message.id, options);
|
|
513
513
|
deletedMessage = result.message;
|
|
514
514
|
}
|
|
515
515
|
return deletedMessage;
|
|
@@ -16,10 +16,12 @@ export const useCreateChannelStateContext = (value) => {
|
|
|
16
16
|
channelCapabilitiesArray.forEach((capability) => {
|
|
17
17
|
channelCapabilities[capability] = true;
|
|
18
18
|
});
|
|
19
|
+
// FIXME: this is crazy - I could not find out why the messages were not getting updated when only message properties that are not part
|
|
20
|
+
// of this serialization has been changed. A great example of memoization gone wrong.
|
|
19
21
|
const memoizedMessageData = skipMessageDataMemoization
|
|
20
22
|
? messages
|
|
21
23
|
: messages
|
|
22
|
-
.map(({ deleted_at, latest_reactions, pinned, reply_count, status, updated_at, user, }) => `${deleted_at}${latest_reactions ? latest_reactions.map(({ type }) => type).join() : ''}${pinned}${reply_count}${status}${updated_at && (isDayOrMoment(updated_at) || isDate(updated_at))
|
|
24
|
+
.map(({ deleted_at, latest_reactions, pinned, reply_count, status, type, updated_at, user, }) => `${type}${deleted_at}${latest_reactions ? latest_reactions.map(({ type }) => type).join() : ''}${pinned}${reply_count}${status}${updated_at && (isDayOrMoment(updated_at) || isDate(updated_at))
|
|
23
25
|
? updated_at.toISOString()
|
|
24
26
|
: updated_at || ''}${user?.updated_at}`)
|
|
25
27
|
.join();
|
|
@@ -2,53 +2,65 @@ import { useCallback, useEffect, useState } from 'react';
|
|
|
2
2
|
import { useChatContext } from '../../../context';
|
|
3
3
|
export var MessageDeliveryStatus;
|
|
4
4
|
(function (MessageDeliveryStatus) {
|
|
5
|
+
MessageDeliveryStatus["SENT"] = "sent";
|
|
5
6
|
MessageDeliveryStatus["DELIVERED"] = "delivered";
|
|
6
7
|
MessageDeliveryStatus["READ"] = "read";
|
|
7
8
|
})(MessageDeliveryStatus || (MessageDeliveryStatus = {}));
|
|
8
9
|
export const useMessageDeliveryStatus = ({ channel, lastMessage, }) => {
|
|
9
10
|
const { client } = useChatContext();
|
|
10
11
|
const [messageDeliveryStatus, setMessageDeliveryStatus] = useState();
|
|
11
|
-
const isOwnMessage = useCallback((message) => client.user && message
|
|
12
|
+
const isOwnMessage = useCallback((message) => client.user && message && message.user?.id === client.user.id, [client]);
|
|
12
13
|
useEffect(() => {
|
|
14
|
+
// empty channel
|
|
15
|
+
if (!lastMessage) {
|
|
16
|
+
setMessageDeliveryStatus(undefined);
|
|
17
|
+
}
|
|
13
18
|
const lastMessageIsOwn = isOwnMessage(lastMessage);
|
|
14
19
|
if (!lastMessage?.created_at || !lastMessageIsOwn)
|
|
15
20
|
return;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
: lastMessage.created_at
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return ignoreOwnReadStatus && lastMessageCreatedAtDate < channelLastMarkedReadDate;
|
|
22
|
-
});
|
|
23
|
-
setMessageDeliveryStatus(channelReadByOthersAfterLastMessageUpdate
|
|
21
|
+
const msgRef = {
|
|
22
|
+
msgId: lastMessage.id,
|
|
23
|
+
timestampMs: lastMessage.created_at.getTime(),
|
|
24
|
+
};
|
|
25
|
+
setMessageDeliveryStatus(channel.messageReceiptsTracker.readersForMessage(msgRef).length > 0
|
|
24
26
|
? MessageDeliveryStatus.READ
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
+
: channel.messageReceiptsTracker.deliveredForMessage(msgRef).length > 0
|
|
28
|
+
? MessageDeliveryStatus.DELIVERED
|
|
29
|
+
: MessageDeliveryStatus.SENT);
|
|
30
|
+
}, [channel, isOwnMessage, lastMessage]);
|
|
27
31
|
useEffect(() => {
|
|
28
32
|
const handleMessageNew = (event) => {
|
|
29
33
|
// the last message is not mine, so do not show the delivery status
|
|
30
34
|
if (!isOwnMessage(event.message)) {
|
|
31
35
|
return setMessageDeliveryStatus(undefined);
|
|
32
36
|
}
|
|
33
|
-
return setMessageDeliveryStatus(MessageDeliveryStatus.
|
|
37
|
+
return setMessageDeliveryStatus(MessageDeliveryStatus.SENT);
|
|
34
38
|
};
|
|
35
39
|
channel.on('message.new', handleMessageNew);
|
|
36
40
|
return () => {
|
|
37
41
|
channel.off('message.new', handleMessageNew);
|
|
38
42
|
};
|
|
39
|
-
}, [channel,
|
|
43
|
+
}, [channel, isOwnMessage]);
|
|
40
44
|
useEffect(() => {
|
|
41
45
|
if (!isOwnMessage(lastMessage))
|
|
42
46
|
return;
|
|
47
|
+
const handleMessageDelivered = (event) => {
|
|
48
|
+
if (event.user?.id !== client.user?.id &&
|
|
49
|
+
lastMessage &&
|
|
50
|
+
lastMessage.id === event.last_delivered_message_id)
|
|
51
|
+
setMessageDeliveryStatus(MessageDeliveryStatus.DELIVERED);
|
|
52
|
+
};
|
|
43
53
|
const handleMarkRead = (event) => {
|
|
44
54
|
if (event.user?.id !== client.user?.id)
|
|
45
55
|
setMessageDeliveryStatus(MessageDeliveryStatus.READ);
|
|
46
56
|
};
|
|
57
|
+
channel.on('message.delivered', handleMessageDelivered);
|
|
47
58
|
channel.on('message.read', handleMarkRead);
|
|
48
59
|
return () => {
|
|
60
|
+
channel.off('message.delivered', handleMessageDelivered);
|
|
49
61
|
channel.off('message.read', handleMarkRead);
|
|
50
62
|
};
|
|
51
|
-
}, [channel, client,
|
|
63
|
+
}, [channel, client, isOwnMessage, lastMessage]);
|
|
52
64
|
return {
|
|
53
65
|
messageDeliveryStatus,
|
|
54
66
|
};
|
|
@@ -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.
|
|
27
|
+
const version = "13.9.0";
|
|
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, 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, 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 }));
|
|
120
120
|
};
|
|
@@ -6,6 +6,7 @@ export type MessageStatusProps = {
|
|
|
6
6
|
MessageDeliveredStatus?: React.ComponentType;
|
|
7
7
|
MessageReadStatus?: React.ComponentType;
|
|
8
8
|
MessageSendingStatus?: React.ComponentType;
|
|
9
|
+
MessageSentStatus?: React.ComponentType;
|
|
9
10
|
messageType?: string;
|
|
10
11
|
tooltipUserNameMapper?: TooltipUsernameMapper;
|
|
11
12
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import { MessageDeliveredIcon } from './icons';
|
|
3
|
+
import { MessageDeliveredIcon, MessageSentIcon } from './icons';
|
|
4
4
|
import { getReadByTooltipText, mapToUserNameOrId } from './utils';
|
|
5
5
|
import { Avatar as DefaultAvatar } from '../Avatar';
|
|
6
6
|
import { LoadingIndicator } from '../Loading';
|
|
@@ -11,40 +11,50 @@ import { useComponentContext } from '../../context/ComponentContext';
|
|
|
11
11
|
import { useMessageContext } from '../../context/MessageContext';
|
|
12
12
|
import { useTranslationContext } from '../../context/TranslationContext';
|
|
13
13
|
const UnMemoizedMessageStatus = (props) => {
|
|
14
|
-
const { Avatar: propAvatar, MessageDeliveredStatus, MessageReadStatus, MessageSendingStatus, messageType = 'simple', tooltipUserNameMapper = mapToUserNameOrId, } = props;
|
|
14
|
+
const { Avatar: propAvatar, MessageDeliveredStatus, MessageReadStatus, MessageSendingStatus, MessageSentStatus, messageType = 'simple', tooltipUserNameMapper = mapToUserNameOrId, } = props;
|
|
15
15
|
const { handleEnter, handleLeave, tooltipVisible } = useEnterLeaveHandlers();
|
|
16
16
|
const { client } = useChatContext('MessageStatus');
|
|
17
17
|
const { Avatar: contextAvatar } = useComponentContext('MessageStatus');
|
|
18
|
-
const {
|
|
18
|
+
const { deliveredTo, isMyMessage, message, readBy, threadList } = useMessageContext('MessageStatus');
|
|
19
19
|
const { t } = useTranslationContext('MessageStatus');
|
|
20
20
|
const [referenceElement, setReferenceElement] = useState(null);
|
|
21
21
|
const Avatar = propAvatar || contextAvatar || DefaultAvatar;
|
|
22
22
|
if (!isMyMessage() || message.type === 'error')
|
|
23
23
|
return null;
|
|
24
24
|
const justReadByMe = readBy?.length === 1 && readBy[0].id === client.user?.id;
|
|
25
|
-
const
|
|
25
|
+
const deliveredOnlyToMe = deliveredTo?.length === 1 && deliveredTo[0].id === client.user?.id;
|
|
26
26
|
const sending = message.status === 'sending';
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
27
|
+
const read = !!(readBy?.length && !justReadByMe && !threadList);
|
|
28
|
+
const delivered = !!(deliveredTo?.length && !deliveredOnlyToMe && !read && !threadList);
|
|
29
|
+
const sent = message.status === 'received' && !delivered && !read && !threadList;
|
|
30
|
+
const readersWithoutOwnUser = read
|
|
30
31
|
? readBy.filter((item) => item.id !== client.user?.id)
|
|
31
32
|
: [];
|
|
32
33
|
const [lastReadUser] = readersWithoutOwnUser;
|
|
33
|
-
return (React.createElement("span", { className:
|
|
34
|
-
'
|
|
35
|
-
'
|
|
34
|
+
return (React.createElement("span", { className: clsx(`str-chat__message-${messageType}-status str-chat__message-status`, {
|
|
35
|
+
'str-chat__message-status-delivered': delivered,
|
|
36
|
+
'str-chat__message-status-read-by': read,
|
|
37
|
+
'str-chat__message-status-sending': sending,
|
|
38
|
+
'str-chat__message-status-sent': sent,
|
|
39
|
+
}), "data-testid": clsx({
|
|
40
|
+
'message-status-delivered': delivered,
|
|
41
|
+
'message-status-read-by': read,
|
|
36
42
|
'message-status-sending': sending,
|
|
43
|
+
'message-status-sent': sent,
|
|
37
44
|
}), onMouseEnter: handleEnter, onMouseLeave: handleLeave, ref: setReferenceElement },
|
|
38
45
|
sending &&
|
|
39
46
|
(MessageSendingStatus ? (React.createElement(MessageSendingStatus, null)) : (React.createElement(React.Fragment, null,
|
|
40
47
|
React.createElement(PopperTooltip, { offset: [0, 5], referenceElement: referenceElement, visible: tooltipVisible }, t('Sending...')),
|
|
41
48
|
React.createElement(LoadingIndicator, null)))),
|
|
49
|
+
sent &&
|
|
50
|
+
(MessageSentStatus ? (React.createElement(MessageSentStatus, null)) : (React.createElement(React.Fragment, null,
|
|
51
|
+
React.createElement(PopperTooltip, { offset: [0, 5], referenceElement: referenceElement, visible: tooltipVisible }, t('Sent')),
|
|
52
|
+
React.createElement(MessageSentIcon, null)))),
|
|
42
53
|
delivered &&
|
|
43
|
-
!deliveredAndRead &&
|
|
44
54
|
(MessageDeliveredStatus ? (React.createElement(MessageDeliveredStatus, null)) : (React.createElement(React.Fragment, null,
|
|
45
55
|
React.createElement(PopperTooltip, { offset: [0, 5], referenceElement: referenceElement, visible: tooltipVisible }, t('Delivered')),
|
|
46
56
|
React.createElement(MessageDeliveredIcon, null)))),
|
|
47
|
-
|
|
57
|
+
read &&
|
|
48
58
|
(MessageReadStatus ? (React.createElement(MessageReadStatus, null)) : (React.createElement(React.Fragment, null,
|
|
49
59
|
React.createElement(PopperTooltip, { offset: [0, 5], referenceElement: referenceElement, visible: tooltipVisible }, getReadByTooltipText(readBy, t, client, tooltipUserNameMapper)),
|
|
50
60
|
React.createElement(Avatar, { className: 'str-chat__avatar--message-status', image: lastReadUser.image, name: lastReadUser.name || lastReadUser.id, user: lastReadUser }),
|
|
@@ -7,13 +7,13 @@ export const useDeleteHandler = (message, notifications = {}) => {
|
|
|
7
7
|
const { deleteMessage, updateMessage } = useChannelActionContext('useDeleteHandler');
|
|
8
8
|
const { client } = useChatContext('useDeleteHandler');
|
|
9
9
|
const { t } = useTranslationContext('useDeleteHandler');
|
|
10
|
-
return async (event) => {
|
|
10
|
+
return async (event, options) => {
|
|
11
11
|
event.preventDefault();
|
|
12
12
|
if (!message?.id || !client || !updateMessage) {
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
try {
|
|
16
|
-
const deletedMessage = await deleteMessage(message);
|
|
16
|
+
const deletedMessage = await deleteMessage(message, options);
|
|
17
17
|
updateMessage(deletedMessage);
|
|
18
18
|
}
|
|
19
19
|
catch (e) {
|
|
@@ -6,5 +6,6 @@ export declare const ReactionIcon: ({ className }: IconProps) => React.JSX.Eleme
|
|
|
6
6
|
export declare const ThreadIcon: ({ className }: IconProps) => React.JSX.Element;
|
|
7
7
|
export declare const PinIcon: () => React.JSX.Element;
|
|
8
8
|
export declare const PinIndicator: ({ message, t }: PinIndicatorProps) => React.JSX.Element | null;
|
|
9
|
+
export declare const MessageSentIcon: () => React.JSX.Element;
|
|
9
10
|
export declare const MessageDeliveredIcon: () => React.JSX.Element;
|
|
10
11
|
export declare const MessageErrorIcon: () => React.JSX.Element;
|
|
@@ -23,8 +23,12 @@ export const PinIndicator = ({ message, t }) => {
|
|
|
23
23
|
? `${t('Pinned by')} ${message.pinned_by?.name || message.pinned_by?.id}`
|
|
24
24
|
: t('Message pinned'))));
|
|
25
25
|
};
|
|
26
|
-
export const
|
|
27
|
-
React.createElement("path", { clipRule: 'evenodd', d: '
|
|
26
|
+
export const MessageSentIcon = () => (React.createElement("svg", { "data-testid": 'message-sent-icon', fill: 'currentColor', viewBox: '0 0 10 8', xmlns: 'http://www.w3.org/2000/svg' },
|
|
27
|
+
React.createElement("path", { clipRule: 'evenodd', d: 'M9.47116 1.80482C9.73151 1.54447 9.73151 1.12236 9.47116 0.862011C9.21081 0.601661 8.7887 0.601661 8.52835 0.862011L3.66646 5.7239L1.47108 3.52851C1.21073 3.26816 0.788619 3.26816 0.52827 3.52851C0.26792 3.78886 0.26792 4.21097 0.52827 4.47132L3.18877 7.13182C3.19083 7.13394 3.19292 7.13605 3.19502 7.13815C3.45537 7.3985 3.87748 7.3985 4.13783 7.13815L9.47116 1.80482Z', fillRule: 'evenodd' })));
|
|
28
|
+
export const MessageDeliveredIcon = () => (React.createElement("svg", { "data-testid": 'delivered-icon', fill: 'currentColor', viewBox: '0 0 14 8', xmlns: 'http://www.w3.org/2000/svg' },
|
|
29
|
+
React.createElement("path", { clipRule: 'evenodd', d: 'M9.50041 0.862011C9.76149 1.12236 9.76149 1.54447 9.50041 1.80482L6.63046 4.66681L7.69051 5.72392L12.566 0.862011C12.827 0.601661 13.2503 0.601661 13.5114 0.862011C13.7725 1.12236 13.7725 1.54447 13.5114 1.80482L8.16321 7.13815C7.90214 7.3985 7.47885 7.3985 7.21778 7.13815C7.2164 7.13678 7.21502 7.13539 7.21366 7.13401L5.68502 5.60962L4.15223 7.13815C3.89115 7.3985 3.46787 7.3985 3.20679 7.13815L3.19746 7.12866L0.53272 4.47132C0.271645 4.21097 0.271645 3.78886 0.53272 3.52851C0.793794 3.26816 1.21708 3.26816 1.47815 3.52851L3.6796 5.72385L5.20067 4.207L5.21216 4.19526L5.22393 4.1838L8.55498 0.862011C8.81605 0.601661 9.23934 0.601661 9.50041 0.862011Z',
|
|
30
|
+
// fill='#005DFF'
|
|
31
|
+
fillRule: 'evenodd' })));
|
|
28
32
|
export const MessageErrorIcon = () => (React.createElement("div", { className: 'str-chat__message-error-icon' },
|
|
29
33
|
React.createElement("svg", { "data-testid": 'error', fill: 'none', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' },
|
|
30
34
|
React.createElement("path", { d: 'M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2Z', fill: 'black', id: 'background' }),
|
|
@@ -23,6 +23,8 @@ export type MessageProps = {
|
|
|
23
23
|
closeReactionSelectorOnClick?: boolean;
|
|
24
24
|
/** Object containing custom message actions and function handlers */
|
|
25
25
|
customMessageActions?: MessageContextValue['customMessageActions'];
|
|
26
|
+
/** An array of user IDs that have confirmed the message delivery to their device */
|
|
27
|
+
deliveredTo?: UserResponse[];
|
|
26
28
|
/** If true, disables the ability for users to quote messages, defaults to false */
|
|
27
29
|
disableQuotedMessages?: boolean;
|
|
28
30
|
/** When true, the message is the last one in a group sent by a specific user (only used in the `VirtualizedMessageList`) */
|
|
@@ -12,6 +12,9 @@ export declare const validateAndGetMessage: <T extends unknown[]>(func: (...args
|
|
|
12
12
|
* Tell if the owner of the current message is muted
|
|
13
13
|
*/
|
|
14
14
|
export declare const isUserMuted: (message: LocalMessage, mutes?: Mute[]) => boolean;
|
|
15
|
+
export declare const OPTIONAL_MESSAGE_ACTIONS: {
|
|
16
|
+
deleteForMe: string;
|
|
17
|
+
};
|
|
15
18
|
export declare const MESSAGE_ACTIONS: {
|
|
16
19
|
delete: string;
|
|
17
20
|
edit: string;
|
|
@@ -25,7 +28,7 @@ export declare const MESSAGE_ACTIONS: {
|
|
|
25
28
|
reply: string;
|
|
26
29
|
saveForLater: string;
|
|
27
30
|
};
|
|
28
|
-
export type MessageActionsArray<T extends string = string> = Array<keyof typeof MESSAGE_ACTIONS | T>;
|
|
31
|
+
export type MessageActionsArray<T extends string = string> = Array<keyof typeof MESSAGE_ACTIONS | keyof typeof OPTIONAL_MESSAGE_ACTIONS | T>;
|
|
29
32
|
export declare const defaultPinPermissions: PinPermissions;
|
|
30
33
|
export type Capabilities = {
|
|
31
34
|
canDelete?: boolean;
|
|
@@ -26,6 +26,9 @@ export const isUserMuted = (message, mutes) => {
|
|
|
26
26
|
const userMuted = mutes.filter((el) => el.target.id === message.user?.id);
|
|
27
27
|
return !!userMuted.length;
|
|
28
28
|
};
|
|
29
|
+
export const OPTIONAL_MESSAGE_ACTIONS = {
|
|
30
|
+
deleteForMe: 'deleteForMe',
|
|
31
|
+
};
|
|
29
32
|
export const MESSAGE_ACTIONS = {
|
|
30
33
|
delete: 'delete',
|
|
31
34
|
edit: 'edit',
|
|
@@ -113,6 +116,9 @@ export const getMessageActions = (actions, { canDelete, canEdit, canFlag, canMar
|
|
|
113
116
|
if (canDelete && messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1) {
|
|
114
117
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.delete);
|
|
115
118
|
}
|
|
119
|
+
if (canDelete && messageActions.indexOf(OPTIONAL_MESSAGE_ACTIONS.deleteForMe) > -1) {
|
|
120
|
+
messageActionsAfterPermission.push(OPTIONAL_MESSAGE_ACTIONS.deleteForMe);
|
|
121
|
+
}
|
|
116
122
|
if (canEdit && messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1) {
|
|
117
123
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.edit);
|
|
118
124
|
}
|
|
@@ -213,6 +219,7 @@ export const areMessagePropsEqual = (prevProps, nextProps) => {
|
|
|
213
219
|
return false;
|
|
214
220
|
const deepEqualProps = deepequal(nextProps.messageActions, prevProps.messageActions) &&
|
|
215
221
|
deepequal(nextProps.readBy, prevProps.readBy) &&
|
|
222
|
+
deepequal(nextProps.deliveredTo, prevProps.deliveredTo) &&
|
|
216
223
|
deepequal(nextProps.highlighted, prevProps.highlighted) &&
|
|
217
224
|
deepequal(nextProps.groupStyles, prevProps.groupStyles) && // last 3 messages can have different group styles
|
|
218
225
|
deepequal(nextProps.mutes, prevProps.mutes) &&
|
|
@@ -235,6 +242,8 @@ export const areMessageUIPropsEqual = (prevProps, nextProps) => {
|
|
|
235
242
|
return false;
|
|
236
243
|
if (prevProps.readBy?.length !== nextProps.readBy?.length)
|
|
237
244
|
return false;
|
|
245
|
+
if (prevProps.deliveredTo?.length !== nextProps.deliveredTo?.length)
|
|
246
|
+
return false;
|
|
238
247
|
if (prevProps.groupStyles !== nextProps.groupStyles)
|
|
239
248
|
return false;
|
|
240
249
|
if (prevProps.showDetailedReactions !== nextProps.showDetailedReactions) {
|
|
@@ -2,7 +2,7 @@ import clsx from 'clsx';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { CustomMessageActionsList as DefaultCustomMessageActionsList } from './CustomMessageActionsList';
|
|
4
4
|
import { RemindMeActionButton } from './RemindMeSubmenu';
|
|
5
|
-
import { useMessageReminder } from '../Message';
|
|
5
|
+
import { OPTIONAL_MESSAGE_ACTIONS, useMessageReminder } from '../Message';
|
|
6
6
|
import { useMessageComposer } from '../MessageInput';
|
|
7
7
|
import { useChatContext, useComponentContext, useMessageContext, useTranslationContext, } from '../../context';
|
|
8
8
|
import { MESSAGE_ACTIONS } from '../Message/utils';
|
|
@@ -41,6 +41,8 @@ const UnMemoizedMessageActionsBox = (props) => {
|
|
|
41
41
|
messageActions.indexOf(MESSAGE_ACTIONS.mute) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleMute, role: 'option' }, isUserMuted() ? t('Unmute') : t('Mute'))),
|
|
42
42
|
messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleEdit, role: 'option' }, t('Edit Message'))),
|
|
43
43
|
messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleDelete, role: 'option' }, t('Delete'))),
|
|
44
|
+
messageActions.indexOf(OPTIONAL_MESSAGE_ACTIONS.deleteForMe) > -1 &&
|
|
45
|
+
!message.deleted_for_me && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: (e) => handleDelete(e, { deleteForMe: true }), role: 'option' }, t('Delete for me'))),
|
|
44
46
|
messageActions.indexOf(MESSAGE_ACTIONS.remindMe) > -1 && (React.createElement(RemindMeActionButton, { className: buttonClassName, isMine: mine })),
|
|
45
47
|
messageActions.indexOf(MESSAGE_ACTIONS.saveForLater) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: () => reminder
|
|
46
48
|
? client.reminders.deleteReminder(reminder.id)
|
|
@@ -22,7 +22,7 @@ import { useStableId } from '../UtilityComponents/useStableId';
|
|
|
22
22
|
import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, } from '../../constants/limits';
|
|
23
23
|
const MessageListWithContext = (props) => {
|
|
24
24
|
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
|
-
maxTimeBetweenGroupedMessages, messageActions = Object.keys(MESSAGE_ACTIONS), messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE, messages = [], noGroupByUser = false, notifications, pinPermissions = defaultPinPermissions, reactionDetailsSort,
|
|
25
|
+
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;
|
|
26
26
|
const [listElement, setListElement] = React.useState(null);
|
|
27
27
|
const [ulElement, setUlElement] = React.useState(null);
|
|
28
28
|
const { customClasses } = useChatContext('MessageList');
|
|
@@ -92,7 +92,7 @@ const MessageListWithContext = (props) => {
|
|
|
92
92
|
unsafeHTML,
|
|
93
93
|
},
|
|
94
94
|
messageGroupStyles,
|
|
95
|
-
|
|
95
|
+
messages,
|
|
96
96
|
renderMessages,
|
|
97
97
|
returnAllReadData,
|
|
98
98
|
threadList,
|
|
@@ -21,6 +21,8 @@ export type VirtuosoContext = Required<Pick<ComponentContextValue, 'DateSeparato
|
|
|
21
21
|
messageGroupStyles: Record<string, GroupStyle>;
|
|
22
22
|
/** Number of messages prepended before the first page of messages. This is needed to calculate the virtual position in the virtual list. */
|
|
23
23
|
numItemsPrepended: number;
|
|
24
|
+
/** Mapping of message ID of own messages to the array of users, who were delivered the given message */
|
|
25
|
+
ownMessagesDeliveredToOthers: Record<string, UserResponse[]>;
|
|
24
26
|
/** Mapping of message ID of own messages to the array of users, who read the given message */
|
|
25
27
|
ownMessagesReadByOthers: Record<string, UserResponse[]>;
|
|
26
28
|
/** The original message list enriched with date separators, omitted deleted messages or giphy previews. */
|
|
@@ -22,6 +22,7 @@ import { useComponentContext } from '../../context/ComponentContext';
|
|
|
22
22
|
import { VirtualizedMessageListContextProvider } from '../../context/VirtualizedMessageListContext';
|
|
23
23
|
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
|
|
24
24
|
import { useStableId } from '../UtilityComponents/useStableId';
|
|
25
|
+
import { useLastDeliveredData } from './hooks/useLastDeliveredData';
|
|
25
26
|
function captureResizeObserverExceededError(e) {
|
|
26
27
|
if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
|
|
27
28
|
e.message === 'ResizeObserver loop limit exceeded') {
|
|
@@ -54,7 +55,7 @@ function calculateInitialTopMostItemIndex(messages, highlightedMessageId) {
|
|
|
54
55
|
const VirtualizedMessageListWithContext = (props) => {
|
|
55
56
|
const { additionalMessageInputProps, additionalVirtuosoProps = {}, channel, channelUnreadUiState, closeReactionSelectorOnClick, customMessageActions, customMessageRenderer, defaultItemHeight, disableDateSeparator = true, formatDate, groupStyles, hasMoreNewer, head, hideDeletedMessages = false, hideNewMessageSeparator = false, highlightedMessageId, jumpToLatestMessage, loadingMore, loadMore, loadMoreNewer, maxTimeBetweenGroupedMessages, Message: MessageUIComponentFromProps, messageActions, messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE, messages, notifications, openThread,
|
|
56
57
|
// TODO: refactor to scrollSeekPlaceHolderConfiguration and components.ScrollSeekPlaceholder, like the Virtuoso Component
|
|
57
|
-
overscan = 0, reactionDetailsSort,
|
|
58
|
+
overscan = 0, reactionDetailsSort, returnAllReadData = false, reviewProcessedMessage, scrollSeekPlaceHolder, scrollToLatestMessageOnFocus = false, separateGiphyPreview = false, shouldGroupByUser = false, showUnreadNotificationAlways, sortReactionDetails, sortReactions, stickToBottomScrollBehavior = 'smooth', suppressAutoscroll, threadList, } = props;
|
|
58
59
|
const { components: virtuosoComponentsFromProps, ...overridingVirtuosoProps } = additionalVirtuosoProps;
|
|
59
60
|
// Stops errors generated from react-virtuoso to bubble up
|
|
60
61
|
// to Sentry or other tracking tools.
|
|
@@ -102,10 +103,14 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
102
103
|
]);
|
|
103
104
|
// get the mapping of own messages to array of users who read them
|
|
104
105
|
const ownMessagesReadByOthers = useLastReadData({
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
channel,
|
|
107
|
+
messages: messages || [],
|
|
108
|
+
returnAllReadData,
|
|
109
|
+
});
|
|
110
|
+
const ownMessagesDeliveredToOthers = useLastDeliveredData({
|
|
111
|
+
channel,
|
|
112
|
+
messages: messages || [],
|
|
107
113
|
returnAllReadData,
|
|
108
|
-
userID: client.userID,
|
|
109
114
|
});
|
|
110
115
|
const lastReceivedMessageId = useMemo(() => getLastReceived(processedMessages), [processedMessages]);
|
|
111
116
|
const groupStylesFn = groupStyles || getGroupStyles;
|
|
@@ -231,6 +236,7 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
231
236
|
MessageSystem,
|
|
232
237
|
numItemsPrepended,
|
|
233
238
|
openThread,
|
|
239
|
+
ownMessagesDeliveredToOthers,
|
|
234
240
|
ownMessagesReadByOthers,
|
|
235
241
|
processedMessages,
|
|
236
242
|
reactionDetailsSort,
|
|
@@ -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, ownMessagesReadByOthers, processedMessages: messageList, reactionDetailsSort, shouldGroupByUser, sortReactionDetails, sortReactions, threadList, unreadMessageCount = 0, UnreadMessagesSeparator, virtuosoRef, } = 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;
|
|
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, 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] ?? ''], lastReceivedId: lastReceivedMessageId, message: message, Message: MessageUIComponent, messageActions: messageActions, openThread: openThread, reactionDetailsSort: reactionDetailsSort, readBy: ownMessagesReadByOthers[message.id] || [], sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: threadList })));
|
|
92
92
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import type { ChannelState as StreamChannelState } from 'stream-chat';
|
|
3
2
|
import type { GroupStyle, RenderedMessage } from '../../utils';
|
|
3
|
+
import type { LocalMessage } from 'stream-chat';
|
|
4
4
|
import type { ChannelUnreadUiState } from '../../../../types/types';
|
|
5
5
|
import type { MessageRenderer, SharedMessageProps } from '../../renderMessages';
|
|
6
6
|
type UseMessageListElementsProps = {
|
|
7
|
+
messages: LocalMessage[];
|
|
7
8
|
enrichedMessages: RenderedMessage[];
|
|
8
9
|
internalMessageProps: SharedMessageProps;
|
|
9
10
|
messageGroupStyles: Record<string, GroupStyle>;
|
|
@@ -11,7 +12,6 @@ type UseMessageListElementsProps = {
|
|
|
11
12
|
returnAllReadData: boolean;
|
|
12
13
|
threadList: boolean;
|
|
13
14
|
channelUnreadUiState?: ChannelUnreadUiState;
|
|
14
|
-
read?: StreamChannelState['read'];
|
|
15
15
|
};
|
|
16
16
|
export declare const useMessageListElements: (props: UseMessageListElementsProps) => React.ReactNode[];
|
|
17
17
|
export {};
|
|
@@ -3,16 +3,23 @@ import { useLastReadData } from '../useLastReadData';
|
|
|
3
3
|
import { getLastReceived } from '../../utils';
|
|
4
4
|
import { useChatContext } from '../../../../context/ChatContext';
|
|
5
5
|
import { useComponentContext } from '../../../../context/ComponentContext';
|
|
6
|
+
import { useChannelStateContext } from '../../../../context';
|
|
7
|
+
import { useLastDeliveredData } from '../useLastDeliveredData';
|
|
6
8
|
export const useMessageListElements = (props) => {
|
|
7
|
-
const { channelUnreadUiState, enrichedMessages, internalMessageProps, messageGroupStyles,
|
|
8
|
-
const {
|
|
9
|
+
const { channelUnreadUiState, enrichedMessages, internalMessageProps, messageGroupStyles, messages, renderMessages, returnAllReadData, threadList, } = props;
|
|
10
|
+
const { customClasses } = useChatContext('useMessageListElements');
|
|
11
|
+
const { channel } = useChannelStateContext();
|
|
9
12
|
const components = useComponentContext('useMessageListElements');
|
|
10
13
|
// get the readData, but only for messages submitted by the user themselves
|
|
11
14
|
const readData = useLastReadData({
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
channel,
|
|
16
|
+
messages,
|
|
17
|
+
returnAllReadData,
|
|
18
|
+
});
|
|
19
|
+
const ownMessagesDeliveredToOthers = useLastDeliveredData({
|
|
20
|
+
channel,
|
|
21
|
+
messages,
|
|
14
22
|
returnAllReadData,
|
|
15
|
-
userID: client.userID,
|
|
16
23
|
});
|
|
17
24
|
const lastReceivedMessageId = useMemo(() => getLastReceived(enrichedMessages), [enrichedMessages]);
|
|
18
25
|
const elements = useMemo(() => renderMessages({
|
|
@@ -22,6 +29,7 @@ export const useMessageListElements = (props) => {
|
|
|
22
29
|
lastReceivedMessageId,
|
|
23
30
|
messageGroupStyles,
|
|
24
31
|
messages: enrichedMessages,
|
|
32
|
+
ownMessagesDeliveredToOthers,
|
|
25
33
|
readData,
|
|
26
34
|
sharedMessageProps: { ...internalMessageProps, threadList },
|
|
27
35
|
}),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Channel, LocalMessage, UserResponse } from 'stream-chat';
|
|
2
|
+
type UseLastDeliveredDataParams = {
|
|
3
|
+
channel: Channel;
|
|
4
|
+
messages: LocalMessage[];
|
|
5
|
+
returnAllReadData: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare const useLastDeliveredData: (props: UseLastDeliveredDataParams) => Record<string, UserResponse[]>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
export const useLastDeliveredData = (props) => {
|
|
3
|
+
const { channel, messages, returnAllReadData } = props;
|
|
4
|
+
return useMemo(() => 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]);
|
|
13
|
+
};
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import type { UserResponse } from 'stream-chat';
|
|
2
|
-
import type { RenderedMessage } from '../utils';
|
|
1
|
+
import type { Channel, LocalMessage, UserResponse } from 'stream-chat';
|
|
3
2
|
type UseLastReadDataParams = {
|
|
4
|
-
|
|
3
|
+
channel: Channel;
|
|
4
|
+
messages: LocalMessage[];
|
|
5
5
|
returnAllReadData: boolean;
|
|
6
|
-
userID: string | undefined;
|
|
7
|
-
read?: Record<string, {
|
|
8
|
-
last_read: Date;
|
|
9
|
-
user: UserResponse;
|
|
10
|
-
}>;
|
|
11
6
|
};
|
|
12
7
|
export declare const useLastReadData: (props: UseLastReadDataParams) => Record<string, UserResponse[]>;
|
|
13
8
|
export {};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
-
import { isLocalMessage } from '../utils';
|
|
3
|
-
import { getReadStates } from '../utils';
|
|
4
2
|
export const useLastReadData = (props) => {
|
|
5
|
-
const {
|
|
6
|
-
return useMemo(() =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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]);
|
|
10
13
|
};
|