stream-chat-react 13.3.0 → 13.5.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 (36) hide show
  1. package/dist/components/Channel/Channel.js +5 -0
  2. package/dist/components/ChannelPreview/ChannelPreview.js +7 -2
  3. package/dist/components/Chat/hooks/useChat.js +1 -1
  4. package/dist/components/Message/renderText/renderText.d.ts +1 -1
  5. package/dist/components/Message/renderText/renderText.js +1 -1
  6. package/dist/components/MessageInput/AttachmentPreviewList/index.d.ts +1 -0
  7. package/dist/components/MessageInput/index.d.ts +1 -1
  8. package/dist/components/Modal/Modal.d.ts +8 -3
  9. package/dist/components/Modal/Modal.js +19 -8
  10. package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.js +4 -1
  11. package/dist/experimental/index.browser.cjs +6 -2
  12. package/dist/experimental/index.browser.cjs.map +2 -2
  13. package/dist/experimental/index.node.cjs +6 -2
  14. package/dist/experimental/index.node.cjs.map +2 -2
  15. package/dist/i18n/Streami18n.d.ts +1 -0
  16. package/dist/i18n/TranslationBuilder/notifications/NotificationTranslationTopic.js +2 -0
  17. package/dist/i18n/TranslationBuilder/notifications/pollVoteCountTrespass.d.ts +3 -0
  18. package/dist/i18n/TranslationBuilder/notifications/pollVoteCountTrespass.js +1 -0
  19. package/dist/i18n/de.json +1 -0
  20. package/dist/i18n/en.json +1 -0
  21. package/dist/i18n/es.json +1 -0
  22. package/dist/i18n/fr.json +1 -0
  23. package/dist/i18n/hi.json +1 -0
  24. package/dist/i18n/it.json +1 -0
  25. package/dist/i18n/ja.json +1 -0
  26. package/dist/i18n/ko.json +1 -0
  27. package/dist/i18n/nl.json +1 -0
  28. package/dist/i18n/pt.json +1 -0
  29. package/dist/i18n/ru.json +1 -0
  30. package/dist/i18n/tr.json +1 -0
  31. package/dist/index.browser.cjs +1168 -1128
  32. package/dist/index.browser.cjs.map +3 -3
  33. package/dist/index.node.cjs +1168 -1128
  34. package/dist/index.node.cjs.map +3 -3
  35. package/dist/types/types.d.ts +3 -0
  36. package/package.json +5 -5
@@ -134,6 +134,10 @@ const ChannelInner = (props) => {
134
134
  type: 'updateThreadOnEvent',
135
135
  });
136
136
  }
137
+ // ignore the event if it is not targeted at the current channel.
138
+ // Event targeted at this channel or globally targeted event should lead to state refresh
139
+ if (event.type === 'user.messages.deleted' && event.cid && event.cid !== channel.cid)
140
+ return;
137
141
  if (event.type === 'user.watching.start' || event.type === 'user.watching.stop')
138
142
  return;
139
143
  if (event.type === 'typing.start' || event.type === 'typing.stop') {
@@ -257,6 +261,7 @@ const ChannelInner = (props) => {
257
261
  client.on('connection.recovered', handleEvent);
258
262
  client.on('user.updated', handleEvent);
259
263
  client.on('user.deleted', handleEvent);
264
+ client.on('user.messages.deleted', handleEvent);
260
265
  channel.on(handleEvent);
261
266
  }
262
267
  })();
@@ -55,23 +55,28 @@ export const ChannelPreview = (props) => {
55
55
  }, 400), [channel, muted]);
56
56
  useEffect(() => {
57
57
  refreshUnreadCount();
58
- const handleEvent = () => {
58
+ const handleEvent = (event) => {
59
+ const deletedMessagesInAnotherChannel = event.type === 'user.messages.deleted' && event.cid && event.cid !== channel.cid;
60
+ if (deletedMessagesInAnotherChannel)
61
+ return;
59
62
  setLastMessage(channel.state.latestMessages[channel.state.latestMessages.length - 1]);
60
63
  refreshUnreadCount();
61
64
  };
62
65
  channel.on('message.new', handleEvent);
63
66
  channel.on('message.updated', handleEvent);
64
67
  channel.on('message.deleted', handleEvent);
68
+ client.on('user.messages.deleted', handleEvent);
65
69
  channel.on('message.undeleted', handleEvent);
66
70
  channel.on('channel.truncated', handleEvent);
67
71
  return () => {
68
72
  channel.off('message.new', handleEvent);
69
73
  channel.off('message.updated', handleEvent);
70
74
  channel.off('message.deleted', handleEvent);
75
+ client.off('user.messages.deleted', handleEvent);
71
76
  channel.off('message.undeleted', handleEvent);
72
77
  channel.off('channel.truncated', handleEvent);
73
78
  };
74
- }, [channel, refreshUnreadCount, channelUpdateCount]);
79
+ }, [channel, client, refreshUnreadCount, channelUpdateCount]);
75
80
  if (!Preview)
76
81
  return null;
77
82
  const latestMessagePreview = getLatestMessagePreview(channel, t, userLanguage, isMessageAIGenerated);
@@ -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.3.0";
27
+ const version = "13.5.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'
@@ -16,4 +16,4 @@ export type RenderTextOptions = {
16
16
  getRehypePlugins?: RenderTextPluginConfigurator;
17
17
  getRemarkPlugins?: RenderTextPluginConfigurator;
18
18
  };
19
- export declare const renderText: (text?: string, mentionedUsers?: UserResponse[], { allowedTagNames, customMarkDownRenderers, getRehypePlugins, getRemarkPlugins, }?: RenderTextOptions) => React.JSX.Element | null | undefined;
19
+ export declare const renderText: (text?: string, mentionedUsers?: UserResponse[], { allowedTagNames, customMarkDownRenderers, getRehypePlugins, getRemarkPlugins, }?: RenderTextOptions) => React.JSX.Element | null;
@@ -83,7 +83,7 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
83
83
  return strippedHref.includes(strippedText) || strippedText.includes(strippedHref);
84
84
  });
85
85
  if (noParsingNeeded.length > 0 || linkIsInBlock)
86
- return;
86
+ continue;
87
87
  try {
88
88
  // special case for mentions:
89
89
  // it could happen that a user's name matches with an e-mail format pattern.
@@ -1,5 +1,6 @@
1
1
  export * from './AttachmentPreviewList';
2
2
  export type { FileAttachmentPreviewProps } from './FileAttachmentPreview';
3
+ export type { GeolocationPreviewProps } from './GeolocationPreview';
3
4
  export type { ImageAttachmentPreviewProps } from './ImageAttachmentPreview';
4
5
  export type { UploadAttachmentPreviewProps as AttachmentPreviewProps } from './types';
5
6
  export type { UnsupportedAttachmentPreviewProps } from './UnsupportedAttachmentPreview';
@@ -1,6 +1,6 @@
1
1
  export * from './AttachmentSelector';
2
2
  export { AttachmentPreviewList } from './AttachmentPreviewList';
3
- export type { AttachmentPreviewListProps, FileAttachmentPreviewProps, ImageAttachmentPreviewProps, AttachmentPreviewProps, UnsupportedAttachmentPreviewProps, VoiceRecordingPreviewProps, } from './AttachmentPreviewList';
3
+ export type { AttachmentPreviewListProps, FileAttachmentPreviewProps, GeolocationPreviewProps, ImageAttachmentPreviewProps, AttachmentPreviewProps, UnsupportedAttachmentPreviewProps, VoiceRecordingPreviewProps, } from './AttachmentPreviewList';
4
4
  export * from './CooldownTimer';
5
5
  export * from './EditMessageForm';
6
6
  export * from './hooks';
@@ -1,11 +1,16 @@
1
- import type { PropsWithChildren } from 'react';
1
+ import { type PropsWithChildren } from 'react';
2
2
  import React from 'react';
3
+ type CloseEvent = KeyboardEvent | React.KeyboardEvent | React.MouseEvent<HTMLButtonElement | HTMLDivElement>;
4
+ export type ModalCloseSource = 'overlay' | 'button' | 'escape';
3
5
  export type ModalProps = {
4
6
  /** If true, modal is opened or visible. */
5
7
  open: boolean;
6
8
  /** Custom class to be applied to the modal root div */
7
9
  className?: string;
8
10
  /** Callback handler for closing of modal. */
9
- onClose?: (event: React.KeyboardEvent | React.MouseEvent<HTMLButtonElement | HTMLDivElement>) => void;
11
+ onClose?: (event: CloseEvent) => void;
12
+ /** Optional handler to intercept closing logic. Return false to prevent onClose. */
13
+ onCloseAttempt?: (source: ModalCloseSource, event: CloseEvent) => boolean;
10
14
  };
11
- export declare const Modal: ({ children, className, onClose, open, }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
15
+ export declare const Modal: ({ children, className, onClose, onCloseAttempt, open, }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
16
+ export {};
@@ -1,34 +1,45 @@
1
1
  import clsx from 'clsx';
2
+ import { useCallback } from 'react';
2
3
  import React, { useEffect, useRef } from 'react';
3
4
  import { FocusScope } from '@react-aria/focus';
4
5
  import { CloseIconRound } from './icons';
5
6
  import { useTranslationContext } from '../../context';
6
- export const Modal = ({ children, className, onClose, open, }) => {
7
+ export const Modal = ({ children, className, onClose, onCloseAttempt, open, }) => {
7
8
  const { t } = useTranslationContext('Modal');
8
9
  const innerRef = useRef(null);
9
- const closeRef = useRef(null);
10
+ const closeButtonRef = useRef(null);
11
+ const maybeClose = useCallback((source, event) => {
12
+ const allow = onCloseAttempt?.(source, event);
13
+ if (allow !== false) {
14
+ onClose?.(event);
15
+ }
16
+ }, [onClose, onCloseAttempt]);
10
17
  const handleClick = (event) => {
11
18
  const target = event.target;
12
- if (!innerRef.current || !closeRef.current)
19
+ if (!innerRef.current || !closeButtonRef.current)
13
20
  return;
14
- if (!innerRef.current.contains(target) || closeRef.current.contains(target))
15
- onClose?.(event);
21
+ if (closeButtonRef.current.contains(target)) {
22
+ maybeClose('button', event);
23
+ }
24
+ else if (!innerRef.current.contains(target)) {
25
+ maybeClose('overlay', event);
26
+ }
16
27
  };
17
28
  useEffect(() => {
18
29
  if (!open)
19
30
  return;
20
31
  const handleKeyDown = (event) => {
21
32
  if (event.key === 'Escape')
22
- onClose?.(event);
33
+ maybeClose('escape', event);
23
34
  };
24
35
  document.addEventListener('keydown', handleKeyDown);
25
36
  return () => document.removeEventListener('keydown', handleKeyDown);
26
- }, [onClose, open]);
37
+ }, [maybeClose, open]);
27
38
  if (!open)
28
39
  return null;
29
40
  return (React.createElement("div", { className: clsx('str-chat__modal str-chat__modal--open', className), onClick: handleClick },
30
41
  React.createElement(FocusScope, { autoFocus: true, contain: true },
31
- React.createElement("button", { className: 'str-chat__modal__close-button', ref: closeRef, title: t('Close') },
42
+ React.createElement("button", { className: 'str-chat__modal__close-button', ref: closeButtonRef, title: t('Close') },
32
43
  React.createElement(CloseIconRound, null)),
33
44
  React.createElement("div", { className: 'str-chat__modal__inner str-chat-react__modal__inner', ref: innerRef }, children))));
34
45
  };
@@ -15,7 +15,10 @@ export const PollCreationDialogControls = ({ close, }) => {
15
15
  messageComposer
16
16
  .createPoll()
17
17
  .then(() => handleSubmitMessage())
18
- .then(close)
18
+ .then(() => {
19
+ messageComposer.pollComposer.initState();
20
+ close();
21
+ })
19
22
  .catch(console.error);
20
23
  }, type: 'submit' }, t('Create'))));
21
24
  };
@@ -10720,7 +10720,9 @@ var ChannelPreview = (props) => {
10720
10720
  );
10721
10721
  (0, import_react23.useEffect)(() => {
10722
10722
  refreshUnreadCount();
10723
- const handleEvent = () => {
10723
+ const handleEvent = (event) => {
10724
+ const deletedMessagesInAnotherChannel = event.type === "user.messages.deleted" && event.cid && event.cid !== channel.cid;
10725
+ if (deletedMessagesInAnotherChannel) return;
10724
10726
  setLastMessage(
10725
10727
  channel.state.latestMessages[channel.state.latestMessages.length - 1]
10726
10728
  );
@@ -10729,16 +10731,18 @@ var ChannelPreview = (props) => {
10729
10731
  channel.on("message.new", handleEvent);
10730
10732
  channel.on("message.updated", handleEvent);
10731
10733
  channel.on("message.deleted", handleEvent);
10734
+ client.on("user.messages.deleted", handleEvent);
10732
10735
  channel.on("message.undeleted", handleEvent);
10733
10736
  channel.on("channel.truncated", handleEvent);
10734
10737
  return () => {
10735
10738
  channel.off("message.new", handleEvent);
10736
10739
  channel.off("message.updated", handleEvent);
10737
10740
  channel.off("message.deleted", handleEvent);
10741
+ client.off("user.messages.deleted", handleEvent);
10738
10742
  channel.off("message.undeleted", handleEvent);
10739
10743
  channel.off("channel.truncated", handleEvent);
10740
10744
  };
10741
- }, [channel, refreshUnreadCount, channelUpdateCount]);
10745
+ }, [channel, client, refreshUnreadCount, channelUpdateCount]);
10742
10746
  if (!Preview) return null;
10743
10747
  const latestMessagePreview = getLatestMessagePreview2(
10744
10748
  channel,