stream-chat-react 13.6.6 → 13.8.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 (39) hide show
  1. package/dist/components/Channel/Channel.d.ts +1 -1
  2. package/dist/components/Channel/Channel.js +2 -0
  3. package/dist/components/ChannelPreview/ChannelPreview.js +13 -2
  4. package/dist/components/ChannelPreview/utils.js +3 -1
  5. package/dist/components/Chat/hooks/useChat.js +1 -1
  6. package/dist/components/Message/MessageSimple.js +2 -2
  7. package/dist/components/Message/QuotedMessage.js +3 -1
  8. package/dist/components/Message/hooks/useActionHandler.js +6 -5
  9. package/dist/components/Message/renderText/remarkPlugins/imageToLink.d.ts +12 -0
  10. package/dist/components/Message/renderText/remarkPlugins/imageToLink.js +27 -0
  11. package/dist/components/Message/renderText/remarkPlugins/index.d.ts +2 -0
  12. package/dist/components/Message/renderText/remarkPlugins/index.js +2 -0
  13. package/dist/components/Message/renderText/remarkPlugins/plusPlusToEmphasis.d.ts +6 -0
  14. package/dist/components/Message/renderText/remarkPlugins/plusPlusToEmphasis.js +64 -0
  15. package/dist/components/Message/renderText/renderText.js +4 -1
  16. package/dist/components/MessageInput/EditMessageForm.d.ts +2 -1
  17. package/dist/components/MessageInput/hooks/useMessageComposer.js +3 -1
  18. package/dist/components/Poll/PollActions/PollActions.js +2 -1
  19. package/dist/components/TextareaComposer/TextareaComposer.js +4 -1
  20. package/dist/context/ComponentContext.d.ts +3 -1
  21. package/dist/experimental/index.browser.cjs +125 -49
  22. package/dist/experimental/index.browser.cjs.map +4 -4
  23. package/dist/experimental/index.node.cjs +125 -49
  24. package/dist/experimental/index.node.cjs.map +4 -4
  25. package/dist/i18n/TranslationBuilder/notifications/NotificationTranslationTopic.js +2 -1
  26. package/dist/i18n/TranslationBuilder/notifications/attachmentUpload.d.ts +1 -0
  27. package/dist/i18n/TranslationBuilder/notifications/attachmentUpload.js +5 -0
  28. package/dist/index.browser.cjs +1414 -1314
  29. package/dist/index.browser.cjs.map +4 -4
  30. package/dist/index.node.cjs +1416 -1314
  31. package/dist/index.node.cjs.map +4 -4
  32. package/dist/plugins/Emojis/index.browser.cjs +4 -1
  33. package/dist/plugins/Emojis/index.browser.cjs.map +2 -2
  34. package/dist/plugins/Emojis/index.node.cjs +4 -1
  35. package/dist/plugins/Emojis/index.node.cjs.map +2 -2
  36. package/dist/types/defaultDataInterfaces.d.ts +2 -0
  37. package/dist/utils/useStableCallback.d.ts +25 -0
  38. package/dist/utils/useStableCallback.js +29 -0
  39. package/package.json +3 -4
@@ -5,7 +5,7 @@ import type { OnMentionAction } from './hooks/useMentionsHandlers';
5
5
  import type { LoadingErrorIndicatorProps } from '../Loading';
6
6
  import type { ComponentContextValue } from '../../context';
7
7
  import type { ChannelUnreadUiState, GiphyVersions, ImageAttachmentSizeHandler, VideoAttachmentSizeHandler } from '../../types/types';
8
- type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'ShareLocationDialog' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'Modal' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'ReminderNotification' | 'SendButton' | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
8
+ type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EditMessageModal' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'ShareLocationDialog' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'Modal' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'ReminderNotification' | 'SendButton' | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
9
9
  export type ChannelProps = ChannelPropsForwardedToComponentContext & {
10
10
  /** Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab */
11
11
  activeUnreadHandler?: (unread: number, documentTitle: string) => void;
@@ -724,6 +724,7 @@ const ChannelInner = (props) => {
724
724
  CustomMessageActionsList: props.CustomMessageActionsList,
725
725
  DateSeparator: props.DateSeparator,
726
726
  EditMessageInput: props.EditMessageInput,
727
+ EditMessageModal: props.EditMessageModal,
727
728
  EmojiPicker: props.EmojiPicker,
728
729
  emojiSearchIndex: props.emojiSearchIndex,
729
730
  EmptyStateIndicator: props.EmptyStateIndicator,
@@ -791,6 +792,7 @@ const ChannelInner = (props) => {
791
792
  props.CustomMessageActionsList,
792
793
  props.DateSeparator,
793
794
  props.EditMessageInput,
795
+ props.EditMessageModal,
794
796
  props.EmojiPicker,
795
797
  props.emojiSearchIndex,
796
798
  props.EmptyStateIndicator,
@@ -15,6 +15,7 @@ export const ChannelPreview = (props) => {
15
15
  channel,
16
16
  });
17
17
  const [lastMessage, setLastMessage] = useState(channel.state.messages[channel.state.messages.length - 1]);
18
+ const [latestMessagePreview, setLatestMessagePreview] = useState(() => getLatestMessagePreview(channel, t, userLanguage, isMessageAIGenerated));
18
19
  const [unread, setUnread] = useState(0);
19
20
  const { messageDeliveryStatus } = useMessageDeliveryStatus({
20
21
  channel,
@@ -55,11 +56,13 @@ export const ChannelPreview = (props) => {
55
56
  }, 400), [channel, muted]);
56
57
  useEffect(() => {
57
58
  refreshUnreadCount();
59
+ setLatestMessagePreview(getLatestMessagePreview(channel, t, userLanguage, isMessageAIGenerated));
58
60
  const handleEvent = (event) => {
59
61
  const deletedMessagesInAnotherChannel = event.type === 'user.messages.deleted' && event.cid && event.cid !== channel.cid;
60
62
  if (deletedMessagesInAnotherChannel)
61
63
  return;
62
64
  setLastMessage(channel.state.latestMessages[channel.state.latestMessages.length - 1]);
65
+ setLatestMessagePreview(getLatestMessagePreview(channel, t, userLanguage, isMessageAIGenerated));
63
66
  refreshUnreadCount();
64
67
  };
65
68
  channel.on('message.new', handleEvent);
@@ -76,9 +79,17 @@ export const ChannelPreview = (props) => {
76
79
  channel.off('message.undeleted', handleEvent);
77
80
  channel.off('channel.truncated', handleEvent);
78
81
  };
79
- }, [channel, client, refreshUnreadCount, channelUpdateCount]);
82
+ }, [
83
+ channel,
84
+ client,
85
+ refreshUnreadCount,
86
+ channelUpdateCount,
87
+ getLatestMessagePreview,
88
+ t,
89
+ userLanguage,
90
+ isMessageAIGenerated,
91
+ ]);
80
92
  if (!Preview)
81
93
  return null;
82
- const latestMessagePreview = getLatestMessagePreview(channel, t, userLanguage, isMessageAIGenerated);
83
94
  return (React.createElement(Preview, { ...props, active: isActive, displayImage: displayImage, displayTitle: displayTitle, groupChannelDisplayInfo: groupChannelDisplayInfo, lastMessage: lastMessage, latestMessage: latestMessagePreview, latestMessagePreview: latestMessagePreview, messageDeliveryStatus: messageDeliveryStatus, setActiveChannel: setActiveChannel, unread: unread }));
84
95
  };
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
2
  import ReactMarkdown from 'react-markdown';
3
- import { htmlToTextPlugin } from '../Message';
3
+ import { htmlToTextPlugin, imageToLink, plusPlusToEmphasis } from '../Message';
4
4
  import remarkGfm from 'remark-gfm';
5
5
  const remarkPlugins = [
6
6
  htmlToTextPlugin,
7
7
  [remarkGfm, { singleTilde: false }],
8
+ plusPlusToEmphasis,
9
+ imageToLink,
8
10
  ];
9
11
  export const renderPreviewText = (text) => (React.createElement(ReactMarkdown, { remarkPlugins: remarkPlugins, skipHtml: true }, text));
10
12
  const getLatestPollVote = (latestVotesByOption) => {
@@ -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.6.6";
27
+ const version = "13.8.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'
@@ -17,7 +17,7 @@ import { useMessageReminder } from './hooks';
17
17
  import { areMessageUIPropsEqual, isMessageBlocked, isMessageBounced, isMessageEdited, messageHasAttachments, messageHasReactions, } from './utils';
18
18
  import { Avatar as DefaultAvatar } from '../Avatar';
19
19
  import { Attachment as DefaultAttachment } from '../Attachment';
20
- import { EditMessageModal } from '../MessageInput';
20
+ import { EditMessageModal as DefaultEditMessageModal } from '../MessageInput';
21
21
  import { Poll } from '../Poll';
22
22
  import { ReactionsList as DefaultReactionList } from '../Reactions';
23
23
  import { MessageBounceModal } from '../MessageBounce/MessageBounceModal';
@@ -32,7 +32,7 @@ const MessageSimpleWithContext = (props) => {
32
32
  const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
33
33
  const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false);
34
34
  const reminder = useMessageReminder(message.id);
35
- const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, MessageOptions = DefaultMessageOptions,
35
+ const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, EditMessageModal = DefaultEditMessageModal, MessageOptions = DefaultMessageOptions,
36
36
  // TODO: remove this "passthrough" in the next
37
37
  // major release and use the new default instead
38
38
  MessageActions = MessageOptions, MessageBlocked = DefaultMessageBlocked, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageDeleted = DefaultMessageDeleted, MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionsList = DefaultReactionList, ReminderNotification = DefaultReminderNotification, StreamedMessageText = DefaultStreamedMessageText, PinIndicator, } = useComponentContext('MessageSimple');
@@ -9,12 +9,14 @@ import { useMessageContext } from '../../context/MessageContext';
9
9
  import { useTranslationContext } from '../../context/TranslationContext';
10
10
  import { useChannelActionContext } from '../../context/ChannelActionContext';
11
11
  import { renderText as defaultRenderText } from './renderText';
12
+ import { useActionHandler } from './';
12
13
  export const QuotedMessage = ({ renderText: propsRenderText }) => {
13
14
  const { Attachment = DefaultAttachment, Avatar: ContextAvatar } = useComponentContext('QuotedMessage');
14
15
  const { client } = useChatContext();
15
16
  const { isMyMessage, message, renderText: contextRenderText, } = useMessageContext('QuotedMessage');
16
17
  const { t, userLanguage } = useTranslationContext('QuotedMessage');
17
18
  const { jumpToMessage } = useChannelActionContext('QuotedMessage');
19
+ const actionHandler = useActionHandler(message);
18
20
  const renderText = propsRenderText ?? contextRenderText ?? defaultRenderText;
19
21
  const Avatar = ContextAvatar || DefaultAvatar;
20
22
  const { quoted_message } = message;
@@ -42,5 +44,5 @@ export const QuotedMessage = ({ renderText: propsRenderText }) => {
42
44
  React.createElement("div", { className: 'str-chat__quoted-message-bubble', "data-testid": 'quoted-message-contents' }, poll ? (React.createElement(Poll, { isQuoted: true, poll: poll })) : (React.createElement(React.Fragment, null,
43
45
  quotedMessageAttachment && (React.createElement(Attachment, { attachments: [quotedMessageAttachment], isQuoted: true })),
44
46
  React.createElement("div", { className: 'str-chat__quoted-message-bubble__text', "data-testid": 'quoted-message-text' }, renderedText))))),
45
- message.attachments?.length ? (React.createElement(Attachment, { attachments: message.attachments })) : null));
47
+ message.attachments?.length ? (React.createElement(Attachment, { actionHandler: actionHandler, attachments: message.attachments })) : null));
46
48
  };
@@ -1,18 +1,19 @@
1
1
  import { useChannelActionContext } from '../../../context/ChannelActionContext';
2
2
  import { useChannelStateContext } from '../../../context/ChannelStateContext';
3
+ import { useStableCallback } from '../../../utils/useStableCallback';
3
4
  export const handleActionWarning = `Action handler was called, but it is missing one of its required arguments.
4
5
  Make sure the ChannelAction and ChannelState contexts are properly set and the hook is initialized with a valid message.`;
5
6
  export function useActionHandler(message) {
6
7
  const { removeMessage, updateMessage } = useChannelActionContext('useActionHandler');
7
8
  const { channel } = useChannelStateContext('useActionHandler');
8
- return async (dataOrName, value, event) => {
9
+ return useStableCallback(async (dataOrName, value, event) => {
9
10
  if (event)
10
11
  event.preventDefault();
11
12
  if (!message || !updateMessage || !removeMessage || !channel) {
12
13
  console.warn(handleActionWarning);
13
14
  return;
14
15
  }
15
- const messageID = message.id;
16
+ const messageId = message.id;
16
17
  let formData = {};
17
18
  // deprecated: value&name should be removed in favor of data obj
18
19
  if (typeof dataOrName === 'string') {
@@ -21,8 +22,8 @@ export function useActionHandler(message) {
21
22
  else {
22
23
  formData = { ...dataOrName };
23
24
  }
24
- if (messageID) {
25
- const data = await channel.sendAction(messageID, formData);
25
+ if (messageId) {
26
+ const data = await channel.sendAction(messageId, formData);
26
27
  if (data?.message) {
27
28
  updateMessage(data.message);
28
29
  }
@@ -30,5 +31,5 @@ export function useActionHandler(message) {
30
31
  removeMessage(message);
31
32
  }
32
33
  }
33
- };
34
+ });
34
35
  }
@@ -0,0 +1,12 @@
1
+ import type { Node } from 'unist';
2
+ export type ImageToLinkPluginOptions = {
3
+ getTextLabelFrom?: 'alt' | 'title' | 'url';
4
+ };
5
+ /**
6
+ * Converts image Markdown links (![Minion](https://octodex.github.com/images/minion.png))
7
+ * to HTML <a href={url}>{url | title | alt}</a>
8
+ *
9
+ * By default, the anchor text content is the image url so that image preview can be generated / enriched on the server.
10
+ * @param getTextLabelFrom
11
+ */
12
+ export declare function imageToLink({ getTextLabelFrom }?: ImageToLinkPluginOptions): (tree: Node) => void;
@@ -0,0 +1,27 @@
1
+ import { SKIP, visit } from 'unist-util-visit';
2
+ const text = (value) => ({ type: 'text', value });
3
+ /**
4
+ * Converts image Markdown links (![Minion](https://octodex.github.com/images/minion.png))
5
+ * to HTML <a href={url}>{url | title | alt}</a>
6
+ *
7
+ * By default, the anchor text content is the image url so that image preview can be generated / enriched on the server.
8
+ * @param getTextLabelFrom
9
+ */
10
+ export function imageToLink({ getTextLabelFrom = 'url' } = {}) {
11
+ return (tree) => {
12
+ const visitor = (node, index, parent) => {
13
+ if (parent == null || index == null)
14
+ return;
15
+ const label = node[getTextLabelFrom] ?? node.url; // node.alt || node.title || node.url;
16
+ const link = {
17
+ children: [text(label)],
18
+ title: node.title ?? node.alt ?? node.url,
19
+ type: 'link',
20
+ url: node.url,
21
+ };
22
+ parent.children.splice(index, 1, link);
23
+ return [SKIP, index + 1];
24
+ };
25
+ visit(tree, 'image', visitor);
26
+ };
27
+ }
@@ -1,2 +1,4 @@
1
1
  export * from './htmlToTextPlugin';
2
+ export * from './imageToLink';
2
3
  export * from './keepLineBreaksPlugin';
4
+ export * from './plusPlusToEmphasis';
@@ -1,2 +1,4 @@
1
1
  export * from './htmlToTextPlugin';
2
+ export * from './imageToLink';
2
3
  export * from './keepLineBreaksPlugin';
4
+ export * from './plusPlusToEmphasis';
@@ -0,0 +1,6 @@
1
+ import type { Plugin } from 'unified';
2
+ /**
3
+ * Converts MD "++Some text++" to inserted text element rendered in HTML as <ins>Some text</ins>
4
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/ins
5
+ */
6
+ export declare const plusPlusToEmphasis: Plugin<[]>;
@@ -0,0 +1,64 @@
1
+ import { SKIP, visit } from 'unist-util-visit';
2
+ /**
3
+ * \S → first char must be non-whitespace
4
+ * (?:...)?→ optional middle+closing when length > 1
5
+ * [\s\S]*?→ anything (including newlines), lazy
6
+ * final \S→ last char non-whitespace (only required when there’s more than 1)
7
+ *
8
+ * Matches:
9
+ * ++a++
10
+ * Does not match:
11
+ * ++++
12
+ * ++ ++
13
+ */
14
+ const INS_REGEX = /\+\+(\S(?:[\s\S]*?\S)?)\+\+/g;
15
+ const IGNORE_NODE_TYPES = new Set([
16
+ 'code',
17
+ 'inlineCode',
18
+ 'link',
19
+ 'linkReference',
20
+ 'definition',
21
+ 'math',
22
+ 'inlineMath',
23
+ ]);
24
+ /**
25
+ * Converts MD "++Some text++" to inserted text element rendered in HTML as <ins>Some text</ins>
26
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/ins
27
+ */
28
+ export const plusPlusToEmphasis = () => {
29
+ const visitor = (node, index, parent) => {
30
+ // 1) Don’t traverse inside ignored nodes
31
+ if (IGNORE_NODE_TYPES.has(node.type))
32
+ return SKIP;
33
+ // 2) Only transform text nodes with a valid parent + index
34
+ if (node.type !== 'text' || parent == null || typeof index !== 'number')
35
+ return;
36
+ const value = node.value;
37
+ // Reset lastIndex to 0 per node so each node is scanned from the beginning
38
+ INS_REGEX.lastIndex = 0;
39
+ let match;
40
+ let last = 0;
41
+ const out = [];
42
+ while ((match = INS_REGEX.exec(value))) {
43
+ const [full, inner] = match;
44
+ const start = match.index;
45
+ if (start > last)
46
+ out.push({ type: 'text', value: value.slice(last, start) });
47
+ // Render as <ins>…</ins> (remark-rehype respects data.hName)
48
+ out.push({
49
+ children: [{ type: 'text', value: inner }],
50
+ data: { hName: 'ins' },
51
+ type: 'emphasis',
52
+ });
53
+ last = start + full.length;
54
+ }
55
+ if (out.length === 0)
56
+ return; // nothing to change
57
+ if (last < value.length)
58
+ out.push({ type: 'text', value: value.slice(last) });
59
+ parent.children.splice(index, 1, ...out);
60
+ // Skip re-visiting the replaced range; continue after inserted nodes
61
+ return [SKIP, index + out.length];
62
+ };
63
+ return (tree) => visit(tree, visitor);
64
+ };
@@ -5,7 +5,7 @@ import remarkGfm from 'remark-gfm';
5
5
  import { Anchor, Emoji, Mention } from './componentRenderers';
6
6
  import { detectHttp, matchMarkdownLinks, messageCodeBlocks } from './regex';
7
7
  import { emojiMarkdownPlugin, mentionsMarkdownPlugin } from './rehypePlugins';
8
- import { htmlToTextPlugin, keepLineBreaksPlugin } from './remarkPlugins';
8
+ import { htmlToTextPlugin, imageToLink, keepLineBreaksPlugin, plusPlusToEmphasis, } from './remarkPlugins';
9
9
  import { ErrorBoundary } from '../../UtilityComponents';
10
10
  export const defaultAllowedTagNames = [
11
11
  'html',
@@ -38,6 +38,7 @@ export const defaultAllowedTagNames = [
38
38
  'h4',
39
39
  'h5',
40
40
  'h6',
41
+ 'ins',
41
42
  ];
42
43
  function formatUrlForDisplay(url) {
43
44
  try {
@@ -124,6 +125,8 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
124
125
  htmlToTextPlugin,
125
126
  keepLineBreaksPlugin,
126
127
  [remarkGfm, { singleTilde: false }],
128
+ plusPlusToEmphasis,
129
+ imageToLink,
127
130
  ];
128
131
  const rehypePlugins = [emojiMarkdownPlugin];
129
132
  if (mentionedUsers?.length) {
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { MessageUIComponentProps } from '../Message';
3
3
  export declare const EditMessageForm: () => React.JSX.Element;
4
- export declare const EditMessageModal: ({ additionalMessageInputProps, }: Pick<MessageUIComponentProps, 'additionalMessageInputProps'>) => React.JSX.Element;
4
+ export type EditMessageModalProps = Pick<MessageUIComponentProps, 'additionalMessageInputProps'>;
5
+ export declare const EditMessageModal: ({ additionalMessageInputProps, }: EditMessageModalProps) => React.JSX.Element;
@@ -30,8 +30,10 @@ export const useMessageComposer = () => {
30
30
  if (editing && cachedEditedMessage) {
31
31
  const tag = MessageComposer.constructTag(cachedEditedMessage);
32
32
  const cachedComposer = queueCache.get(tag);
33
- if (cachedComposer)
33
+ if (cachedComposer) {
34
+ cachedComposer.editedMessage = cachedEditedMessage;
34
35
  return cachedComposer;
36
+ }
35
37
  return new MessageComposer({
36
38
  client,
37
39
  composition: cachedEditedMessage,
@@ -28,6 +28,7 @@ export const PollActions = ({ AddCommentForm = DefaultAddCommentForm, EndPollDia
28
28
  const { poll } = usePollContext();
29
29
  const { allow_answers, allow_user_suggested_options, answers_count, created_by_id, is_closed, options, ownAnswer, } = useStateStore(poll.state, pollStateSelector);
30
30
  const [modalOpen, setModalOpen] = useState();
31
+ const canCastVote = channelCapabilities['cast-poll-vote'] && !is_closed;
31
32
  const closeModal = useCallback(() => setModalOpen(undefined), []);
32
33
  const onUpdateAnswerClick = useCallback(() => setModalOpen('add-comment'), []);
33
34
  return (React.createElement("div", { className: 'str-chat__poll-actions' },
@@ -35,7 +36,7 @@ export const PollActions = ({ AddCommentForm = DefaultAddCommentForm, EndPollDia
35
36
  count: options.length,
36
37
  }), closeModal: closeModal, modalClassName: COMMON_MODAL_CLASS, modalIsOpen: modalOpen === 'view-all-options', openModal: () => setModalOpen('view-all-options') },
37
38
  React.createElement(PollOptionsFullList, { close: closeModal }))),
38
- !is_closed &&
39
+ canCastVote &&
39
40
  allow_user_suggested_options &&
40
41
  options.length < MAX_POLL_OPTIONS && (React.createElement(PollAction, { buttonText: t('Suggest an option'), closeModal: closeModal, modalClassName: clsx(COMMON_MODAL_CLASS, 'str-chat__suggest-poll-option-modal'), modalIsOpen: modalOpen === 'suggest-option', openModal: () => setModalOpen('suggest-option') },
41
42
  React.createElement(SuggestPollOptionForm, { close: closeModal, messageId: message.id }))),
@@ -110,7 +110,9 @@ export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, co
110
110
  });
111
111
  }
112
112
  }
113
- else if (shouldSubmit(event) && textareaRef.current) {
113
+ else if (shouldSubmit(event) &&
114
+ textareaRef.current &&
115
+ messageComposer.hasSendableData) {
114
116
  if (event.key === 'Enter') {
115
117
  // prevent adding newline when submitting a message with
116
118
  event.preventDefault();
@@ -120,6 +122,7 @@ export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, co
120
122
  }, [
121
123
  focusedItemIndex,
122
124
  handleSubmit,
125
+ messageComposer,
123
126
  onKeyDown,
124
127
  shouldSubmit,
125
128
  suggestions,
@@ -1,6 +1,6 @@
1
1
  import type { PropsWithChildren } from 'react';
2
2
  import React from 'react';
3
- import type { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, ChannelPreviewActionButtonsProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, ModalProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListModalProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, ReminderNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, StreamedMessageTextProps, TextareaComposerProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
3
+ import type { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, ChannelPreviewActionButtonsProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EditMessageModalProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, ModalProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListModalProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, ReminderNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, StreamedMessageTextProps, TextareaComposerProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
4
4
  import type { SuggestionItemProps, SuggestionListProps } from '../components/TextareaComposer';
5
5
  import type { SearchProps, SearchResultsPresearchProps, SearchSourceResultListProps } from '../experimental';
6
6
  import type { PropsWithChildrenOnly, UnknownType } from '../types/types';
@@ -35,6 +35,8 @@ export type ComponentContextValue = {
35
35
  DateSeparator?: React.ComponentType<DateSeparatorProps>;
36
36
  /** Custom UI component to override default edit message input, defaults to and accepts same props as: [EditMessageForm](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/EditMessageForm.tsx) */
37
37
  EditMessageInput?: React.ComponentType<MessageInputProps>;
38
+ /** Custom UI component to override default EditMessageModal, defaults to and accepts same props as: [EditMessageModal](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/EditMessageForm.tsx) */
39
+ EditMessageModal?: React.ComponentType<EditMessageModalProps>;
38
40
  /** Custom UI component for rendering button with emoji picker in MessageInput */
39
41
  EmojiPicker?: React.ComponentType;
40
42
  /** Mechanism to be used with autocomplete and text replace features of the `MessageInput` component, see [emoji-mart `SearchIndex`](https://github.com/missive/emoji-mart#%EF%B8%8F%EF%B8%8F-headless-search) */