stream-chat-react 13.8.1 → 13.10.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 (104) hide show
  1. package/dist/components/Channel/Channel.d.ts +2 -2
  2. package/dist/components/Channel/Channel.js +3 -3
  3. package/dist/components/Channel/hooks/useCreateChannelStateContext.js +3 -1
  4. package/dist/components/ChannelPreview/hooks/useMessageDeliveryStatus.d.ts +1 -0
  5. package/dist/components/ChannelPreview/hooks/useMessageDeliveryStatus.js +31 -14
  6. package/dist/components/Chat/hooks/useChat.js +1 -1
  7. package/dist/components/Dialog/ButtonWithSubmenu.d.ts +2 -2
  8. package/dist/components/Dialog/ButtonWithSubmenu.js +6 -6
  9. package/dist/components/Dialog/DialogAnchor.d.ts +5 -11
  10. package/dist/components/Dialog/DialogAnchor.js +22 -26
  11. package/dist/components/Dialog/DialogPortal.d.ts +2 -1
  12. package/dist/components/Dialog/DialogPortal.js +23 -7
  13. package/dist/components/Dialog/hooks/index.d.ts +1 -0
  14. package/dist/components/Dialog/hooks/useDialog.d.ts +4 -0
  15. package/dist/components/Dialog/hooks/useDialog.js +9 -1
  16. package/dist/components/Dialog/hooks/usePopoverPosition.d.ts +68 -0
  17. package/dist/components/Dialog/hooks/usePopoverPosition.js +54 -0
  18. package/dist/components/Form/Dropdown.d.ts +2 -2
  19. package/dist/components/Message/Message.js +1 -1
  20. package/dist/components/Message/MessageRepliesCountButton.js +3 -1
  21. package/dist/components/Message/MessageStatus.d.ts +1 -0
  22. package/dist/components/Message/MessageStatus.js +22 -12
  23. package/dist/components/Message/hooks/useDeleteHandler.js +2 -2
  24. package/dist/components/Message/icons.d.ts +1 -0
  25. package/dist/components/Message/icons.js +6 -2
  26. package/dist/components/Message/renderText/remarkPlugins/index.d.ts +1 -0
  27. package/dist/components/Message/renderText/remarkPlugins/index.js +1 -0
  28. package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.d.ts +10 -2
  29. package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.js +46 -26
  30. package/dist/components/Message/renderText/remarkPlugins/remarkIgnoreMarkdown.d.ts +8 -0
  31. package/dist/components/Message/renderText/remarkPlugins/remarkIgnoreMarkdown.js +11 -0
  32. package/dist/components/Message/types.d.ts +2 -0
  33. package/dist/components/Message/utils.d.ts +4 -1
  34. package/dist/components/Message/utils.js +9 -0
  35. package/dist/components/MessageActions/MessageActions.js +4 -4
  36. package/dist/components/MessageActions/MessageActionsBox.js +3 -1
  37. package/dist/components/MessageInput/AttachmentSelector.d.ts +1 -1
  38. package/dist/components/MessageInput/AttachmentSelector.js +9 -4
  39. package/dist/components/MessageList/MessageList.js +2 -2
  40. package/dist/components/MessageList/VirtualizedMessageList.d.ts +2 -0
  41. package/dist/components/MessageList/VirtualizedMessageList.js +10 -4
  42. package/dist/components/MessageList/VirtualizedMessageListComponents.js +2 -2
  43. package/dist/components/MessageList/hooks/MessageList/useMessageListElements.d.ts +2 -2
  44. package/dist/components/MessageList/hooks/MessageList/useMessageListElements.js +13 -5
  45. package/dist/components/MessageList/hooks/useLastDeliveredData.d.ts +8 -0
  46. package/dist/components/MessageList/hooks/useLastDeliveredData.js +16 -0
  47. package/dist/components/MessageList/hooks/useLastReadData.d.ts +3 -8
  48. package/dist/components/MessageList/hooks/useLastReadData.js +10 -7
  49. package/dist/components/MessageList/renderMessages.d.ts +5 -4
  50. package/dist/components/MessageList/renderMessages.js +2 -2
  51. package/dist/components/MessageList/utils.d.ts +1 -5
  52. package/dist/components/MessageList/utils.js +0 -30
  53. package/dist/components/Modal/GlobalModal.js +2 -2
  54. package/dist/components/Reactions/ReactionSelectorWithButton.js +4 -4
  55. package/dist/components/Tooltip/Tooltip.d.ts +2 -2
  56. package/dist/components/Tooltip/Tooltip.js +11 -12
  57. package/dist/context/ChannelActionContext.d.ts +2 -2
  58. package/dist/context/DialogManagerContext.d.ts +1 -0
  59. package/dist/context/DialogManagerContext.js +1 -0
  60. package/dist/context/MessageContext.d.ts +5 -3
  61. package/dist/css/v2/index.css +1 -1
  62. package/dist/css/v2/index.layout.css +1 -1
  63. package/dist/experimental/MessageActions/MessageActions.js +5 -5
  64. package/dist/experimental/index.browser.cjs +324 -249
  65. package/dist/experimental/index.browser.cjs.map +4 -4
  66. package/dist/experimental/index.node.cjs +324 -249
  67. package/dist/experimental/index.node.cjs.map +4 -4
  68. package/dist/i18n/Streami18n.d.ts +2 -0
  69. package/dist/i18n/de.json +2 -0
  70. package/dist/i18n/en.json +2 -0
  71. package/dist/i18n/es.json +2 -0
  72. package/dist/i18n/fr.json +2 -0
  73. package/dist/i18n/hi.json +2 -0
  74. package/dist/i18n/it.json +2 -0
  75. package/dist/i18n/ja.json +2 -0
  76. package/dist/i18n/ko.json +2 -0
  77. package/dist/i18n/nl.json +2 -0
  78. package/dist/i18n/pt.json +2 -0
  79. package/dist/i18n/ru.json +2 -0
  80. package/dist/i18n/tr.json +2 -0
  81. package/dist/index.browser.cjs +1587 -1380
  82. package/dist/index.browser.cjs.map +4 -4
  83. package/dist/index.node.cjs +1592 -1381
  84. package/dist/index.node.cjs.map +4 -4
  85. package/dist/plugins/Emojis/EmojiPicker.d.ts +9 -4
  86. package/dist/plugins/Emojis/EmojiPicker.js +10 -5
  87. package/dist/plugins/Emojis/index.browser.cjs +89 -29
  88. package/dist/plugins/Emojis/index.browser.cjs.map +4 -4
  89. package/dist/plugins/Emojis/index.node.cjs +89 -29
  90. package/dist/plugins/Emojis/index.node.cjs.map +4 -4
  91. package/dist/scss/v2/Dialog/Dialog-layout.scss +12 -3
  92. package/dist/scss/v2/Icon/Icon-layout.scss +6 -0
  93. package/dist/scss/v2/Icon/Icon-theme.scss +4 -0
  94. package/dist/scss/v2/Message/Message-layout.scss +34 -3
  95. package/dist/scss/v2/Message/Message-theme.scss +9 -0
  96. package/dist/scss/v2/MessageActionsBox/MessageActionsBox-layout.scss +1 -0
  97. package/dist/scss/v2/MessageInput/MessageInput-layout.scss +8 -0
  98. package/dist/scss/v2/MessageInput/MessageInput-theme.scss +2 -1
  99. package/dist/scss/v2/Poll/Poll-layout.scss +29 -7
  100. package/package.json +7 -8
  101. package/dist/components/MessageActions/hooks/index.d.ts +0 -1
  102. package/dist/components/MessageActions/hooks/index.js +0 -1
  103. package/dist/components/MessageActions/hooks/useMessageActionsBoxPopper.d.ts +0 -18
  104. package/dist/components/MessageActions/hooks/useMessageActionsBoxPopper.js +0 -31
@@ -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 { isMyMessage, lastReceivedId, message, readBy, threadList } = useMessageContext('MessageStatus');
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 rootClassName = `str-chat__message-${messageType}-status str-chat__message-status`;
25
+ const deliveredOnlyToMe = deliveredTo?.length === 1 && deliveredTo[0].id === client.user?.id;
26
26
  const sending = message.status === 'sending';
27
- const delivered = message.status === 'received' && message.id === lastReceivedId && !threadList;
28
- const deliveredAndRead = !!(readBy?.length && !threadList && !justReadByMe);
29
- const readersWithoutOwnUser = deliveredAndRead
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: rootClassName, "data-testid": clsx({
34
- 'message-status-read-by': deliveredAndRead,
35
- 'message-status-received': delivered && !deliveredAndRead,
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
- deliveredAndRead &&
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 MessageDeliveredIcon = () => (React.createElement("svg", { "data-testid": 'delivered-icon', fill: 'none', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg' },
27
- React.createElement("path", { clipRule: 'evenodd', d: 'M8.9999 16.2L4.7999 12L3.3999 13.4L8.9999 19L20.9999 6.99998L19.5999 5.59998L8.9999 16.2Z', fill: 'black', fillRule: 'evenodd' })));
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' }),
@@ -2,3 +2,4 @@ export * from './htmlToTextPlugin';
2
2
  export * from './imageToLink';
3
3
  export * from './keepLineBreaksPlugin';
4
4
  export * from './plusPlusToEmphasis';
5
+ export * from './remarkIgnoreMarkdown';
@@ -2,3 +2,4 @@ export * from './htmlToTextPlugin';
2
2
  export * from './imageToLink';
3
3
  export * from './keepLineBreaksPlugin';
4
4
  export * from './plusPlusToEmphasis';
5
+ export * from './remarkIgnoreMarkdown';
@@ -1,2 +1,10 @@
1
- import type { Nodes } from 'hast-util-find-and-replace/lib';
2
- export declare const keepLineBreaksPlugin: () => (tree: Nodes) => void;
1
+ import type { Plugin } from 'unified';
2
+ import type { Root } from 'mdast';
3
+ /**
4
+ * Inserts runs of <br> between sibling block nodes to preserve the exact
5
+ * number of *blank source lines* between them. No paragraph wrappers are added.
6
+ *
7
+ * Works because `mdast-util-to-hast` respects `data.hName`, turning our
8
+ * `thematicBreak` into `<br>`. Multiple blank lines -> multiple `<br>` siblings.
9
+ */
10
+ export declare const keepLineBreaksPlugin: Plugin<[], Root>;
@@ -1,28 +1,48 @@
1
1
  import { visit } from 'unist-util-visit';
2
- import { u } from 'unist-builder';
3
- const visitor = (node, index, parent) => {
4
- if (!(index && parent && node.position))
5
- return;
6
- const prevSibling = parent.children.at(index - 1);
7
- if (!prevSibling?.position)
8
- return;
9
- if (node.position.start.line === prevSibling.position.start.line)
10
- return;
11
- const ownStartLine = node.position.start.line;
12
- const prevEndLine = prevSibling.position.end.line;
13
- // the -1 is adjustment for the single line break into which multiple line breaks are converted
14
- const countTruncatedLineBreaks = ownStartLine - prevEndLine - 1;
15
- if (countTruncatedLineBreaks < 1)
16
- return;
17
- const lineBreaks = Array.from({ length: countTruncatedLineBreaks }, () => u('break', { tagName: 'br' }));
18
- parent.children = [
19
- ...parent.children.slice(0, index),
20
- ...lineBreaks,
21
- ...parent.children.slice(index),
22
- ];
23
- return;
2
+ /** Type guard: does the node have mdast children? */
3
+ function isParentWithChildren(node) {
4
+ const maybe = node;
5
+ return Array.isArray(maybe.children);
6
+ }
7
+ /** Build a single <br> by mapping a standard mdast node via data.hName */
8
+ function brNode() {
9
+ return { data: { hName: 'br' }, type: 'thematicBreak' };
10
+ }
11
+ /**
12
+ * Inserts runs of <br> between sibling block nodes to preserve the exact
13
+ * number of *blank source lines* between them. No paragraph wrappers are added.
14
+ *
15
+ * Works because `mdast-util-to-hast` respects `data.hName`, turning our
16
+ * `thematicBreak` into `<br>`. Multiple blank lines -> multiple `<br>` siblings.
17
+ */
18
+ export const keepLineBreaksPlugin = () => (tree) => {
19
+ visit(tree, // visit needs a Unist parent-like root
20
+ isParentWithChildren, // limit to parents with children
21
+ (parent) => {
22
+ const children = parent.children;
23
+ if (children.length < 2)
24
+ return;
25
+ const out = [];
26
+ for (let i = 0; i < children.length; i++) {
27
+ const curr = children[i];
28
+ out.push(curr);
29
+ if (i === children.length - 1)
30
+ break;
31
+ const next = children[i + 1];
32
+ const currEndLine = curr.position && curr.position.end ? curr.position.end.line : undefined;
33
+ const nextStartLine = next.position && next.position.start ? next.position.start.line : undefined;
34
+ if (typeof currEndLine !== 'number' || typeof nextStartLine !== 'number') {
35
+ continue;
36
+ }
37
+ // Markdown already separates blocks by at least one visual gap.
38
+ // We add back only the *extra* blank lines from the source.
39
+ const extraBlankLines = Math.max(0, nextStartLine - currEndLine - 1);
40
+ if (extraBlankLines > 0) {
41
+ for (let k = 0; k < extraBlankLines; k++) {
42
+ out.push(brNode());
43
+ }
44
+ }
45
+ }
46
+ parent.children = out;
47
+ });
24
48
  };
25
- const transform = (tree) => {
26
- visit(tree, visitor);
27
- };
28
- export const keepLineBreaksPlugin = () => transform;
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from 'unified';
2
+ import type { Root } from 'mdast';
3
+ /**
4
+ * Replace the parsed Markdown tree with a single paragraph containing the
5
+ * original source as a plain text node. No Markdown formatting is interpreted.
6
+ * React will escape it.
7
+ */
8
+ export declare const remarkIgnoreMarkdown: Plugin<[], Root>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Replace the parsed Markdown tree with a single paragraph containing the
3
+ * original source as a plain text node. No Markdown formatting is interpreted.
4
+ * React will escape it.
5
+ */
6
+ export const remarkIgnoreMarkdown = () => (tree, file) => {
7
+ const source = String(file.value ?? '');
8
+ const text = { type: 'text', value: source };
9
+ const paragraph = { children: [text], type: 'paragraph' };
10
+ tree.children = [paragraph];
11
+ };
@@ -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) {
@@ -1,7 +1,7 @@
1
1
  import clsx from 'clsx';
2
2
  import React, { useCallback, useRef } from 'react';
3
3
  import { MessageActionsBox } from './MessageActionsBox';
4
- import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
4
+ import { DialogAnchor, useDialogIsOpen, useDialogOnNearestManager } from '../Dialog';
5
5
  import { ActionsIcon as DefaultActionsIcon } from '../Message/icons';
6
6
  import { isUserMuted, shouldRenderMessageActions } from '../Message/utils';
7
7
  import { useChatContext } from '../../context/ChatContext';
@@ -24,8 +24,8 @@ export const MessageActions = (props) => {
24
24
  const isMuted = useCallback(() => isUserMuted(message, mutes), [message, mutes]);
25
25
  const dialogIdNamespace = threadList ? '-thread-' : '';
26
26
  const dialogId = `message-actions${dialogIdNamespace}--${message.id}`;
27
- const dialog = useDialog({ id: dialogId });
28
- const dialogIsOpen = useDialogIsOpen(dialogId);
27
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId });
28
+ const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager?.id);
29
29
  const messageActions = getMessageActions();
30
30
  const renderMessageActions = shouldRenderMessageActions({
31
31
  customMessageActions,
@@ -37,7 +37,7 @@ export const MessageActions = (props) => {
37
37
  if (!renderMessageActions)
38
38
  return null;
39
39
  return (React.createElement(MessageActionsWrapper, { customWrapperClass: customWrapperClass, inline: inline, toggleOpen: dialog?.toggle },
40
- React.createElement(DialogAnchor, { id: dialogId, placement: isMine ? 'top-end' : 'top-start', referenceElement: actionsBoxButtonRef.current, tabIndex: -1, trapFocus: true },
40
+ React.createElement(DialogAnchor, { dialogManagerId: dialogManager?.id, id: dialogId, placement: isMine ? 'top-end' : 'top-start', referenceElement: actionsBoxButtonRef.current, tabIndex: -1, trapFocus: true },
41
41
  React.createElement(MessageActionsBox, { getMessageActions: getMessageActions, handleDelete: handleDelete, handleEdit: setEditingState, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handlePin: handlePin, isUserMuted: isMuted, mine: isMine, open: dialogIsOpen })),
42
42
  React.createElement("button", { "aria-expanded": dialogIsOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Message Actions Menu'), className: 'str-chat__message-actions-box-button', "data-testid": 'message-actions-toggle-button', ref: actionsBoxButtonRef },
43
43
  React.createElement(ActionsIcon, { className: 'str-chat__message-action-icon' }))));
@@ -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)
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- export declare const SimpleAttachmentSelector: () => React.JSX.Element;
2
+ export declare const SimpleAttachmentSelector: () => React.JSX.Element | null;
3
3
  export type AttachmentSelectorModalContentProps = {
4
4
  close: () => void;
5
5
  };
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { UploadIcon as DefaultUploadIcon } from './icons';
3
3
  import { useAttachmentManagerState } from './hooks/useAttachmentManagerState';
4
4
  import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
5
- import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
5
+ import { DialogAnchor, useDialogIsOpen, useDialogOnNearestManager } from '../Dialog';
6
6
  import { DialogMenuButton } from '../Dialog/DialogMenu';
7
7
  import { Modal as DefaultModal } from '../Modal';
8
8
  import { ShareLocationDialog as DefaultLocationDialog } from '../Location';
@@ -16,6 +16,7 @@ import clsx from 'clsx';
16
16
  import { useMessageComposer } from './hooks';
17
17
  export const SimpleAttachmentSelector = () => {
18
18
  const { AttachmentSelectorInitiationButtonContents, FileUploadIcon = DefaultUploadIcon, } = useComponentContext();
19
+ const { channelCapabilities } = useChannelStateContext();
19
20
  const inputRef = useRef(null);
20
21
  const [labelElement, setLabelElement] = useState(null);
21
22
  const id = useStableId();
@@ -33,6 +34,8 @@ export const SimpleAttachmentSelector = () => {
33
34
  labelElement.removeEventListener('keyup', handleKeyUp);
34
35
  };
35
36
  }, [labelElement]);
37
+ if (!channelCapabilities['upload-file'])
38
+ return null;
36
39
  return (React.createElement("div", { className: 'str-chat__file-input-container', "data-testid": 'file-upload-button' },
37
40
  React.createElement(UploadFileInput, { id: id, ref: inputRef }),
38
41
  React.createElement("label", { className: 'str-chat__file-input-label', htmlFor: id, ref: setLabelElement, tabIndex: 0 }, AttachmentSelectorInitiationButtonContents ? (React.createElement(AttachmentSelectorInitiationButtonContents, null)) : (React.createElement(FileUploadIcon, null)))));
@@ -119,8 +122,10 @@ export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttach
119
122
  const messageComposer = useMessageComposer();
120
123
  const actions = useAttachmentSelectorActionsFiltered(attachmentSelectorActionSet);
121
124
  const menuDialogId = `attachment-actions-menu${messageComposer.threadId ? '-thread' : ''}`;
122
- const menuDialog = useDialog({ id: menuDialogId });
123
- const menuDialogIsOpen = useDialogIsOpen(menuDialogId);
125
+ const { dialog: menuDialog, dialogManager } = useDialogOnNearestManager({
126
+ id: menuDialogId,
127
+ });
128
+ const menuDialogIsOpen = useDialogIsOpen(menuDialogId, dialogManager?.id);
124
129
  const [modalContentAction, setModalContentActionAction] = useState();
125
130
  const openModal = useCallback((actionType) => {
126
131
  const action = actions.find((a) => a.type === actionType);
@@ -143,7 +148,7 @@ export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttach
143
148
  channelCapabilities['upload-file'] && React.createElement(UploadFileInput, { ref: setFileInput }),
144
149
  React.createElement("button", { "aria-expanded": menuDialogIsOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Attachment Selector'), className: 'str-chat__attachment-selector__menu-button', "data-testid": 'invoke-attachment-selector-button', onClick: () => menuDialog?.toggle(), ref: menuButtonRef },
145
150
  React.createElement(AttachmentSelectorMenuInitButtonIcon, null)),
146
- React.createElement(DialogAnchor, { id: menuDialogId, placement: 'top-start', referenceElement: menuButtonRef.current, tabIndex: -1, trapFocus: true },
151
+ React.createElement(DialogAnchor, { dialogManagerId: dialogManager?.id, id: menuDialogId, placement: 'top-start', referenceElement: menuButtonRef.current, tabIndex: -1, trapFocus: true },
147
152
  React.createElement("div", { className: 'str-chat__attachment-selector-actions-menu str-chat__dialog-menu', "data-testid": 'attachment-selector-actions-menu' }, actions.map(({ ActionButton, type }) => (React.createElement(ActionButton, { closeMenu: menuDialog.close, key: `attachment-selector-item-${type}`, openModalForAction: openModal }))))),
148
153
  React.createElement(Portal, { getPortalDestination: getModalPortalDestination ?? getDefaultPortalDestination, isOpen: modalIsOpen },
149
154
  React.createElement(Modal, { className: clsx({
@@ -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, read, renderMessages = defaultRenderMessages, returnAllReadData = false, reviewProcessedMessage, showUnreadNotificationAlways, sortReactionDetails, sortReactions, suppressAutoscroll, threadList = false, unsafeHTML = false, } = props;
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
- read,
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, read, returnAllReadData = false, reviewProcessedMessage, scrollSeekPlaceHolder, scrollToLatestMessageOnFocus = false, separateGiphyPreview = false, shouldGroupByUser = false, showUnreadNotificationAlways, sortReactionDetails, sortReactions, stickToBottomScrollBehavior = 'smooth', suppressAutoscroll, threadList, } = props;
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
- messages: processedMessages,
106
- read,
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, read, renderMessages, returnAllReadData, threadList, } = props;
8
- const { client, customClasses } = useChatContext('useMessageListElements');
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
- messages: enrichedMessages,
13
- read,
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,16 @@
1
+ import { useCallback, useEffect, useState } from 'react';
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]);
13
+ const [deliveredTo, setDeliveredTo] = useState(calculate);
14
+ useEffect(() => channel.on('message.delivered', () => setDeliveredTo(calculate)).unsubscribe, [channel, calculate]);
15
+ return deliveredTo;
16
+ };
@@ -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
- messages: RenderedMessage[];
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 {};