stream-chat-react 12.4.1 → 12.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/dist/assets/icons/stream-chat-icons.eot +0 -0
  2. package/dist/assets/icons/stream-chat-icons.svg +4 -0
  3. package/dist/assets/icons/stream-chat-icons.ttf +0 -0
  4. package/dist/assets/icons/stream-chat-icons.woff +0 -0
  5. package/dist/assets/icons/stream-chat-icons.woff2 +0 -0
  6. package/dist/components/Attachment/components/ProgressBar.d.ts +2 -2
  7. package/dist/components/Attachment/components/ProgressBar.js +2 -1
  8. package/dist/components/Channel/Channel.d.ts +1 -1
  9. package/dist/components/Channel/Channel.js +36 -15
  10. package/dist/components/Channel/constants.d.ts +1 -0
  11. package/dist/components/Channel/constants.js +1 -0
  12. package/dist/components/Channel/hooks/useChannelContainerClasses.d.ts +2 -0
  13. package/dist/components/Channel/hooks/useChannelContainerClasses.js +10 -5
  14. package/dist/components/ChannelPreview/utils.js +35 -0
  15. package/dist/components/Chat/hooks/useChat.js +2 -0
  16. package/dist/components/Dialog/DialogAnchor.d.ts +1 -2
  17. package/dist/components/Dialog/DialogMenu.d.ts +3 -0
  18. package/dist/components/Dialog/DialogMenu.js +5 -0
  19. package/dist/components/Dialog/DialogPortal.d.ts +1 -1
  20. package/dist/components/Dialog/DialogPortal.js +4 -12
  21. package/dist/components/Dialog/FormDialog.d.ts +23 -0
  22. package/dist/components/Dialog/FormDialog.js +72 -0
  23. package/dist/components/Dialog/PromptDialog.d.ts +8 -0
  24. package/dist/components/Dialog/PromptDialog.js +7 -0
  25. package/dist/components/DragAndDrop/DragAndDropContainer.d.ts +7 -0
  26. package/dist/components/DragAndDrop/DragAndDropContainer.js +93 -0
  27. package/dist/components/Form/FieldError.d.ts +6 -0
  28. package/dist/components/Form/FieldError.js +3 -0
  29. package/dist/components/Form/SwitchField.d.ts +7 -0
  30. package/dist/components/Form/SwitchField.js +21 -0
  31. package/dist/components/InfiniteScrollPaginator/InfiniteScroll.d.ts +10 -0
  32. package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +10 -0
  33. package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.d.ts +10 -0
  34. package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.js +68 -0
  35. package/dist/components/InfiniteScrollPaginator/hooks/useCursorPaginator.d.ts +18 -0
  36. package/dist/components/InfiniteScrollPaginator/hooks/useCursorPaginator.js +41 -0
  37. package/dist/components/Message/MessageSimple.js +5 -1
  38. package/dist/components/Message/QuotedMessage.js +8 -4
  39. package/dist/components/Message/hooks/useUserRole.js +3 -2
  40. package/dist/components/Message/index.d.ts +1 -0
  41. package/dist/components/MessageInput/AttachmentSelector.d.ts +25 -0
  42. package/dist/components/MessageInput/AttachmentSelector.js +125 -0
  43. package/dist/components/MessageInput/EditMessageForm.js +1 -1
  44. package/dist/components/MessageInput/MessageInput.d.ts +2 -0
  45. package/dist/components/MessageInput/MessageInput.js +9 -4
  46. package/dist/components/MessageInput/MessageInputFlat.js +4 -10
  47. package/dist/components/MessageInput/QuotedMessagePreview.js +7 -3
  48. package/dist/components/MessageInput/hooks/useCreateMessageInputContext.js +3 -1
  49. package/dist/components/MessageInput/hooks/usePasteHandler.js +5 -8
  50. package/dist/components/MessageInput/hooks/useSubmitHandler.js +4 -1
  51. package/dist/components/MessageInput/index.d.ts +1 -0
  52. package/dist/components/MessageInput/index.js +1 -0
  53. package/dist/components/Modal/ModalHeader.d.ts +8 -0
  54. package/dist/components/Modal/ModalHeader.js +6 -0
  55. package/dist/components/Poll/Poll.d.ts +7 -0
  56. package/dist/components/Poll/Poll.js +8 -0
  57. package/dist/components/Poll/PollActions/AddCommentForm.d.ts +7 -0
  58. package/dist/components/Poll/PollActions/AddCommentForm.js +24 -0
  59. package/dist/components/Poll/PollActions/EndPollDialog.d.ts +6 -0
  60. package/dist/components/Poll/PollActions/EndPollDialog.js +19 -0
  61. package/dist/components/Poll/PollActions/PollAction.d.ts +9 -0
  62. package/dist/components/Poll/PollActions/PollAction.js +5 -0
  63. package/dist/components/Poll/PollActions/PollActions.d.ts +17 -0
  64. package/dist/components/Poll/PollActions/PollActions.js +46 -0
  65. package/dist/components/Poll/PollActions/PollAnswerList.d.ts +7 -0
  66. package/dist/components/Poll/PollActions/PollAnswerList.js +28 -0
  67. package/dist/components/Poll/PollActions/PollOptionsFullList.d.ts +6 -0
  68. package/dist/components/Poll/PollActions/PollOptionsFullList.js +16 -0
  69. package/dist/components/Poll/PollActions/PollResults/PollOptionVotesList.d.ts +7 -0
  70. package/dist/components/Poll/PollActions/PollResults/PollOptionVotesList.js +18 -0
  71. package/dist/components/Poll/PollActions/PollResults/PollOptionWithLatestVotes.d.ts +9 -0
  72. package/dist/components/Poll/PollActions/PollResults/PollOptionWithLatestVotes.js +19 -0
  73. package/dist/components/Poll/PollActions/PollResults/PollOptionWithVotesHeader.d.ts +11 -0
  74. package/dist/components/Poll/PollActions/PollResults/PollOptionWithVotesHeader.js +18 -0
  75. package/dist/components/Poll/PollActions/PollResults/PollResults.d.ts +6 -0
  76. package/dist/components/Poll/PollActions/PollResults/PollResults.js +33 -0
  77. package/dist/components/Poll/PollActions/PollResults/index.d.ts +1 -0
  78. package/dist/components/Poll/PollActions/PollResults/index.js +1 -0
  79. package/dist/components/Poll/PollActions/SuggestPollOptionForm.d.ts +7 -0
  80. package/dist/components/Poll/PollActions/SuggestPollOptionForm.js +37 -0
  81. package/dist/components/Poll/PollActions/index.d.ts +7 -0
  82. package/dist/components/Poll/PollActions/index.js +7 -0
  83. package/dist/components/Poll/PollContent.d.ts +3 -0
  84. package/dist/components/Poll/PollContent.js +18 -0
  85. package/dist/components/Poll/PollCreationDialog/OptionFieldSet.d.ts +9 -0
  86. package/dist/components/Poll/PollCreationDialog/OptionFieldSet.js +70 -0
  87. package/dist/components/Poll/PollCreationDialog/PollCreationDialog.d.ts +5 -0
  88. package/dist/components/Poll/PollCreationDialog/PollCreationDialog.js +87 -0
  89. package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.d.ts +8 -0
  90. package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.js +44 -0
  91. package/dist/components/Poll/PollCreationDialog/index.d.ts +1 -0
  92. package/dist/components/Poll/PollCreationDialog/index.js +1 -0
  93. package/dist/components/Poll/PollCreationDialog/types.d.ts +21 -0
  94. package/dist/components/Poll/PollCreationDialog/types.js +1 -0
  95. package/dist/components/Poll/PollHeader.d.ts +3 -0
  96. package/dist/components/Poll/PollHeader.js +31 -0
  97. package/dist/components/Poll/PollOptionList.d.ts +6 -0
  98. package/dist/components/Poll/PollOptionList.js +14 -0
  99. package/dist/components/Poll/PollOptionSelector.d.ts +19 -0
  100. package/dist/components/Poll/PollOptionSelector.js +53 -0
  101. package/dist/components/Poll/PollVote.d.ts +12 -0
  102. package/dist/components/Poll/PollVote.js +31 -0
  103. package/dist/components/Poll/QuotedPoll.d.ts +3 -0
  104. package/dist/components/Poll/QuotedPoll.js +17 -0
  105. package/dist/components/Poll/constants.d.ts +3 -0
  106. package/dist/components/Poll/constants.js +3 -0
  107. package/dist/components/Poll/hooks/index.d.ts +2 -0
  108. package/dist/components/Poll/hooks/index.js +2 -0
  109. package/dist/components/Poll/hooks/useManagePollVotesRealtime.d.ts +4 -0
  110. package/dist/components/Poll/hooks/useManagePollVotesRealtime.js +36 -0
  111. package/dist/components/Poll/hooks/usePollAnswerPagination.d.ts +13 -0
  112. package/dist/components/Poll/hooks/usePollAnswerPagination.js +27 -0
  113. package/dist/components/Poll/hooks/usePollOptionVotesPagination.d.ts +13 -0
  114. package/dist/components/Poll/hooks/usePollOptionVotesPagination.js +27 -0
  115. package/dist/components/Poll/index.d.ts +10 -0
  116. package/dist/components/Poll/index.js +10 -0
  117. package/dist/components/Portal/Portal.d.ts +6 -0
  118. package/dist/components/Portal/Portal.js +14 -0
  119. package/dist/components/ReactFileUtilities/UploadButton.d.ts +11 -1
  120. package/dist/components/ReactFileUtilities/UploadButton.js +22 -4
  121. package/dist/components/Thread/Thread.js +1 -1
  122. package/dist/components/index.d.ts +2 -0
  123. package/dist/components/index.js +1 -0
  124. package/dist/context/AttachmentSelectorContext.d.ts +8 -0
  125. package/dist/context/AttachmentSelectorContext.js +6 -0
  126. package/dist/context/ComponentContext.d.ts +21 -5
  127. package/dist/context/PollContext.d.ts +11 -0
  128. package/dist/context/PollContext.js +7 -0
  129. package/dist/context/index.d.ts +1 -0
  130. package/dist/context/index.js +1 -0
  131. package/dist/css/v2/index.css +2 -2
  132. package/dist/css/v2/index.layout.css +2 -2
  133. package/dist/experimental/index.browser.cjs +129 -117
  134. package/dist/experimental/index.browser.cjs.map +4 -4
  135. package/dist/experimental/index.node.cjs +129 -117
  136. package/dist/experimental/index.node.cjs.map +4 -4
  137. package/dist/i18n/Streami18n.d.ts +45 -0
  138. package/dist/i18n/de.json +70 -25
  139. package/dist/i18n/en.json +46 -1
  140. package/dist/i18n/es.json +74 -25
  141. package/dist/i18n/fr.json +83 -34
  142. package/dist/i18n/hi.json +54 -9
  143. package/dist/i18n/it.json +75 -26
  144. package/dist/i18n/ja.json +46 -5
  145. package/dist/i18n/ko.json +46 -5
  146. package/dist/i18n/nl.json +59 -14
  147. package/dist/i18n/pt.json +66 -17
  148. package/dist/i18n/ru.json +66 -13
  149. package/dist/i18n/tr.json +77 -32
  150. package/dist/index.browser.cjs +4229 -1861
  151. package/dist/index.browser.cjs.map +4 -4
  152. package/dist/index.node.cjs +4169 -1774
  153. package/dist/index.node.cjs.map +4 -4
  154. package/dist/scss/v2/AttachmentPreviewList/AttachmentPreviewList-layout.scss +2 -2
  155. package/dist/scss/v2/AudioRecorder/AudioRecorder-layout.scss +64 -14
  156. package/dist/scss/v2/AudioRecorder/AudioRecorder-theme.scss +11 -1
  157. package/dist/scss/v2/Avatar/Avatar-layout.scss +4 -0
  158. package/dist/scss/v2/ChannelList/ChannelList-layout.scss +15 -0
  159. package/dist/scss/v2/Dialog/Dialog-layout.scss +54 -0
  160. package/dist/scss/v2/Dialog/Dialog-theme.scss +103 -0
  161. package/dist/scss/v2/DragAndDropContainer/DragAmdDropContainer-layout.scss +5 -0
  162. package/dist/scss/v2/DragAndDropContainer/DragAndDropContainer-theme.scss +47 -0
  163. package/dist/scss/v2/Form/Form-layout.scss +9 -0
  164. package/dist/scss/v2/Form/Form-theme.scss +17 -0
  165. package/dist/scss/v2/Icon/Icon-layout.scss +6 -1
  166. package/dist/scss/v2/InfiniteScrollPaginator/InfiniteScrollPaginator-layout.scss +4 -0
  167. package/dist/scss/v2/MessageActionsBox/MessageActionsBox-layout.scss +0 -9
  168. package/dist/scss/v2/MessageInput/MessageInput-layout.scss +29 -4
  169. package/dist/scss/v2/MessageInput/MessageInput-theme.scss +61 -0
  170. package/dist/scss/v2/Modal/Modal-layout.scss +31 -0
  171. package/dist/scss/v2/Modal/Modal-theme.scss +6 -0
  172. package/dist/scss/v2/Notification/MessageNotification-layout.scss +1 -1
  173. package/dist/scss/v2/Poll/Poll-layout.scss +488 -0
  174. package/dist/scss/v2/Poll/Poll-theme.scss +206 -0
  175. package/dist/scss/v2/_base.scss +4 -0
  176. package/dist/scss/v2/_global-theme-variables.scss +1 -1
  177. package/dist/scss/v2/_icons.scss +7 -0
  178. package/dist/scss/v2/index.layout.scss +4 -0
  179. package/dist/scss/v2/index.scss +4 -0
  180. package/dist/types/types.d.ts +6 -0
  181. package/package.json +4 -4
@@ -10,6 +10,16 @@ const mousewheelListener = (event) => {
10
10
  event.preventDefault();
11
11
  }
12
12
  };
13
+ /**
14
+ * This component serves a single purpose - load more items on scroll inside the MessageList component
15
+ * It is not a general purpose infinite scroll controller, because:
16
+ * 1. It is re-rendered whenever isLoading, hasNext, hasPrev changes. This can lead to scrollListener to have stale data.
17
+ * 2. It pretends to invoke scrollListener on resize event even though this event is emitted only on window resize. It should
18
+ * rather use ResizeObserver. But then again, it ResizeObserver would invoke a stale version of scrollListener.
19
+ *
20
+ * In general, the infinite scroll controller should not aim for checking the loading state and whether there is more data to load.
21
+ * That should be controlled by the loading function.
22
+ */
13
23
  export const InfiniteScroll = (props) => {
14
24
  const { children, element = 'div', hasMore, hasMoreNewer, hasNextPage, hasPreviousPage, head, initialLoad = true, isLoading, listenToScroll, loader, loadMore, loadMoreNewer, loadNextPage, loadPreviousPage, threshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, useCapture = false, ...elementProps } = props;
15
25
  const loadNextPageFn = loadNextPage || loadMoreNewer;
@@ -0,0 +1,10 @@
1
+ import React, { PropsWithChildren } from 'react';
2
+ export type InfiniteScrollPaginatorProps = React.ComponentProps<'div'> & {
3
+ listenToScroll?: (distanceFromBottom: number, distanceFromTop: number, threshold: number) => void;
4
+ loadNextOnScrollToBottom?: () => void;
5
+ loadNextOnScrollToTop?: () => void;
6
+ /** Offset from when to start the loadNextPage call */
7
+ threshold?: number;
8
+ useCapture?: boolean;
9
+ };
10
+ export declare const InfiniteScrollPaginator: (props: PropsWithChildren<InfiniteScrollPaginatorProps>) => React.JSX.Element;
@@ -0,0 +1,68 @@
1
+ import clsx from 'clsx';
2
+ import debounce from 'lodash.debounce';
3
+ import React, { useEffect, useMemo, useRef } from 'react';
4
+ import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD } from '../../constants/limits';
5
+ /**
6
+ * Prevents Chrome hangups
7
+ * See: https://stackoverflow.com/questions/47524205/random-high-content-download-time-in-chrome/47684257#47684257
8
+ */
9
+ const mousewheelListener = (event) => {
10
+ if (event instanceof WheelEvent && event.deltaY === 1) {
11
+ event.preventDefault();
12
+ }
13
+ };
14
+ export const InfiniteScrollPaginator = (props) => {
15
+ const { children, listenToScroll, loadNextOnScrollToBottom, loadNextOnScrollToTop, threshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, useCapture = false, className, ...componentProps } = props;
16
+ const rootRef = useRef(null);
17
+ const childRef = useRef(null);
18
+ const scrollListener = useMemo(() => debounce(() => {
19
+ const root = rootRef.current;
20
+ const child = childRef.current;
21
+ if (!root || root.offsetParent === null || !child) {
22
+ return;
23
+ }
24
+ const distanceFromBottom = child.scrollHeight - root.scrollTop - root.clientHeight;
25
+ const distanceFromTop = root.scrollTop;
26
+ if (listenToScroll) {
27
+ listenToScroll(distanceFromBottom, distanceFromTop, threshold);
28
+ }
29
+ if (distanceFromTop < Number(threshold)) {
30
+ loadNextOnScrollToTop?.();
31
+ }
32
+ if (distanceFromBottom < Number(threshold)) {
33
+ loadNextOnScrollToBottom?.();
34
+ }
35
+ }, 500), [listenToScroll, loadNextOnScrollToBottom, loadNextOnScrollToTop, threshold]);
36
+ useEffect(() => {
37
+ const scrollElement = rootRef.current;
38
+ if (!scrollElement)
39
+ return;
40
+ scrollElement.addEventListener('scroll', scrollListener, useCapture);
41
+ return () => {
42
+ scrollElement.removeEventListener('scroll', scrollListener, useCapture);
43
+ };
44
+ }, [scrollListener, useCapture]);
45
+ useEffect(() => {
46
+ const root = rootRef.current;
47
+ if (!root || typeof ResizeObserver === 'undefined' || !scrollListener)
48
+ return;
49
+ const observer = new ResizeObserver(scrollListener);
50
+ observer.observe(root);
51
+ return () => {
52
+ observer.disconnect();
53
+ };
54
+ }, [scrollListener]);
55
+ useEffect(() => {
56
+ const root = rootRef.current;
57
+ if (root) {
58
+ root.addEventListener('wheel', mousewheelListener, { passive: false });
59
+ }
60
+ return () => {
61
+ if (root) {
62
+ root.removeEventListener('wheel', mousewheelListener, useCapture);
63
+ }
64
+ };
65
+ }, [useCapture]);
66
+ return (React.createElement("div", { ...componentProps, className: clsx('str-chat__infinite-scroll-paginator', className), ref: rootRef },
67
+ React.createElement("div", { className: 'str-chat__infinite-scroll-paginator__content', ref: childRef }, children)));
68
+ };
@@ -0,0 +1,18 @@
1
+ import { StateStore } from 'stream-chat';
2
+ export type CursorPaginatorState<T> = {
3
+ hasNextPage: boolean;
4
+ items: T[];
5
+ latestPageItems: T[];
6
+ loading: boolean;
7
+ error?: Error;
8
+ next?: string | null;
9
+ };
10
+ export type CursorPaginatorStateStore<T> = StateStore<CursorPaginatorState<T>>;
11
+ export type PaginationFn<T> = (next?: string) => Promise<{
12
+ items: T[];
13
+ next?: string;
14
+ }>;
15
+ export declare const useCursorPaginator: <T>(paginationFn: PaginationFn<T>, loadFirstPage?: boolean) => {
16
+ cursorPaginatorState: StateStore<CursorPaginatorState<T>>;
17
+ loadMore: () => Promise<void>;
18
+ };
@@ -0,0 +1,41 @@
1
+ import uniqBy from 'lodash.uniqby';
2
+ import { useCallback, useEffect, useMemo } from 'react';
3
+ import { StateStore } from 'stream-chat';
4
+ export const useCursorPaginator = (paginationFn, loadFirstPage) => {
5
+ const cursorPaginatorState = useMemo(() => new StateStore({
6
+ hasNextPage: true,
7
+ items: [],
8
+ latestPageItems: [],
9
+ loading: false,
10
+ }), []);
11
+ const loadMore = useCallback(async () => {
12
+ const { loading, next: currentNext } = cursorPaginatorState.getLatestValue();
13
+ if (currentNext === null || loading)
14
+ return;
15
+ cursorPaginatorState.partialNext({ loading: true });
16
+ try {
17
+ const { items, next } = await paginationFn(currentNext);
18
+ cursorPaginatorState.next((prev) => ({
19
+ ...prev,
20
+ hasNextPage: !!next,
21
+ items: uniqBy(prev.items.concat(items), 'id'),
22
+ latestPageItems: items,
23
+ next: next || null,
24
+ }));
25
+ }
26
+ catch (error) {
27
+ cursorPaginatorState.partialNext({ error: error });
28
+ }
29
+ cursorPaginatorState.partialNext({ loading: false });
30
+ }, [cursorPaginatorState, paginationFn]);
31
+ useEffect(() => {
32
+ const { items } = cursorPaginatorState.getLatestValue();
33
+ if (!loadFirstPage || items.length)
34
+ return;
35
+ loadMore();
36
+ }, [cursorPaginatorState, loadFirstPage, loadMore]);
37
+ return {
38
+ cursorPaginatorState,
39
+ loadMore,
40
+ };
41
+ };
@@ -15,14 +15,16 @@ import { CUSTOM_MESSAGE_TYPE } from '../../constants/messageTypes';
15
15
  import { EditMessageForm as DefaultEditMessageForm, MessageInput } from '../MessageInput';
16
16
  import { MML } from '../MML';
17
17
  import { Modal } from '../Modal';
18
+ import { Poll } from '../Poll';
18
19
  import { ReactionsList as DefaultReactionList } from '../Reactions';
19
20
  import { MessageBounceModal } from '../MessageBounce/MessageBounceModal';
20
21
  import { useComponentContext } from '../../context/ComponentContext';
21
22
  import { useMessageContext } from '../../context/MessageContext';
22
- import { useTranslationContext } from '../../context';
23
+ import { useChatContext, useTranslationContext } from '../../context';
23
24
  import { MessageEditedTimestamp } from './MessageEditedTimestamp';
24
25
  const MessageSimpleWithContext = (props) => {
25
26
  const { additionalMessageInputProps, clearEditingState, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMyMessage, message, onUserClick, onUserHover, renderText, threadList, } = props;
27
+ const { client } = useChatContext('MessageSimple');
26
28
  const { t } = useTranslationContext('MessageSimple');
27
29
  const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
28
30
  const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false);
@@ -66,6 +68,7 @@ const MessageSimpleWithContext = (props) => {
66
68
  'str-chat__virtual-message__wrapper--first': firstOfGroup,
67
69
  'str-chat__virtual-message__wrapper--group': groupedByUser,
68
70
  });
71
+ const poll = message.poll_id && client.polls.fromState(message.poll_id);
69
72
  return (React.createElement(React.Fragment, null,
70
73
  editing && (React.createElement(Modal, { className: 'str-chat__edit-message-modal', onClose: clearEditingState, open: editing },
71
74
  React.createElement(MessageInput, { clearEditingState: clearEditingState, grow: true, hideSendButton: true, Input: EditMessageInput, message: message, ...additionalMessageInputProps }))),
@@ -79,6 +82,7 @@ const MessageSimpleWithContext = (props) => {
79
82
  React.createElement(MessageActions, null),
80
83
  React.createElement("div", { className: 'str-chat__message-reactions-host' }, hasReactions && React.createElement(ReactionsList, { reverse: true })),
81
84
  React.createElement("div", { className: 'str-chat__message-bubble' },
85
+ poll && React.createElement(Poll, { poll: poll }),
82
86
  message.attachments?.length && !message.quoted_message ? (React.createElement(Attachment, { actionHandler: handleAction, attachments: message.attachments })) : null,
83
87
  React.createElement(MessageText, { message: message, renderText: renderText }),
84
88
  message.mml && (React.createElement(MML, { actionHandler: handleAction, align: isMyMessage() ? 'right' : 'left', source: message.mml })),
@@ -1,13 +1,16 @@
1
1
  import React from 'react';
2
2
  import clsx from 'clsx';
3
+ import { Attachment as DefaultAttachment } from '../Attachment';
3
4
  import { Avatar as DefaultAvatar } from '../Avatar';
5
+ import { Poll } from '../Poll';
6
+ import { useChatContext } from '../../context/ChatContext';
4
7
  import { useComponentContext } from '../../context/ComponentContext';
5
8
  import { useMessageContext } from '../../context/MessageContext';
6
9
  import { useTranslationContext } from '../../context/TranslationContext';
7
10
  import { useChannelActionContext } from '../../context/ChannelActionContext';
8
- import { Attachment as DefaultAttachment } from '../Attachment';
9
11
  export const QuotedMessage = () => {
10
12
  const { Attachment = DefaultAttachment, Avatar: ContextAvatar, } = useComponentContext('QuotedMessage');
13
+ const { client } = useChatContext();
11
14
  const { isMyMessage, message } = useMessageContext('QuotedMessage');
12
15
  const { t, userLanguage } = useTranslationContext('QuotedMessage');
13
16
  const { jumpToMessage } = useChannelActionContext('QuotedMessage');
@@ -15,6 +18,7 @@ export const QuotedMessage = () => {
15
18
  const { quoted_message } = message;
16
19
  if (!quoted_message)
17
20
  return null;
21
+ const poll = quoted_message.poll_id && client.polls.fromState(quoted_message.poll_id);
18
22
  const quotedMessageDeleted = quoted_message.deleted_at || quoted_message.type === 'deleted';
19
23
  const quotedMessageText = quotedMessageDeleted
20
24
  ? t('This message was deleted...')
@@ -23,7 +27,7 @@ export const QuotedMessage = () => {
23
27
  const quotedMessageAttachment = quoted_message.attachments?.length && !quotedMessageDeleted
24
28
  ? quoted_message.attachments[0]
25
29
  : null;
26
- if (!quotedMessageText && !quotedMessageAttachment)
30
+ if (!quoted_message.poll && !quotedMessageText && !quotedMessageAttachment)
27
31
  return null;
28
32
  return (React.createElement(React.Fragment, null,
29
33
  React.createElement("div", { className: clsx('str-chat__quoted-message-preview', { mine: isMyMessage() }), "data-testid": 'quoted-message', onClickCapture: (e) => {
@@ -32,8 +36,8 @@ export const QuotedMessage = () => {
32
36
  jumpToMessage(quoted_message.id);
33
37
  } },
34
38
  quoted_message.user && (React.createElement(Avatar, { className: 'str-chat__avatar--quoted-message-sender', image: quoted_message.user.image, name: quoted_message.user.name || quoted_message.user.id, user: quoted_message.user })),
35
- React.createElement("div", { className: 'str-chat__quoted-message-bubble', "data-testid": 'quoted-message-contents' },
39
+ 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,
36
40
  quotedMessageAttachment && (React.createElement(Attachment, { attachments: [quotedMessageAttachment], isQuoted: true })),
37
- React.createElement("div", { className: 'str-chat__quoted-message-bubble__text', "data-testid": 'quoted-message-text' }, quotedMessageText))),
41
+ React.createElement("div", { className: 'str-chat__quoted-message-bubble__text', "data-testid": 'quoted-message-text' }, quotedMessageText))))),
38
42
  message.attachments?.length ? React.createElement(Attachment, { attachments: message.attachments }) : null));
39
43
  };
@@ -23,8 +23,9 @@ export const useUserRole = (message, onlySenderCanEdit, disableQuotedMessages) =
23
23
  channel.state.membership.is_moderator === true ||
24
24
  channel.state.membership.channel_role === 'channel_moderator';
25
25
  const isMyMessage = client.userID === message.user?.id;
26
- const canEdit = (!onlySenderCanEdit && channelCapabilities['update-any-message']) ||
27
- (isMyMessage && channelCapabilities['update-own-message']);
26
+ const canEdit = !message.poll &&
27
+ ((!onlySenderCanEdit && channelCapabilities['update-any-message']) ||
28
+ (isMyMessage && channelCapabilities['update-own-message']));
28
29
  const canDelete = channelCapabilities['delete-any-message'] ||
29
30
  (isMyMessage && channelCapabilities['delete-own-message']);
30
31
  const canFlag = !isMyMessage && channelCapabilities['flag-message'];
@@ -13,3 +13,4 @@ export * from './QuotedMessage';
13
13
  export * from './renderText';
14
14
  export * from './types';
15
15
  export * from './utils';
16
+ export type { TimestampProps } from './Timestamp';
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import type { DefaultStreamChatGenerics } from '../../types';
3
+ export declare const SimpleAttachmentSelector: () => React.JSX.Element;
4
+ export type AttachmentSelectorModalContentProps = {
5
+ close: () => void;
6
+ };
7
+ export type AttachmentSelectorActionProps = {
8
+ closeMenu: () => void;
9
+ openModalForAction: (actionType: AttachmentSelectorAction['type']) => void;
10
+ };
11
+ export type AttachmentSelectorAction = {
12
+ ActionButton: React.ComponentType<AttachmentSelectorActionProps>;
13
+ type: 'uploadFile' | 'createPoll' | (string & {});
14
+ ModalContent?: React.ComponentType<AttachmentSelectorModalContentProps>;
15
+ };
16
+ export declare const DefaultAttachmentSelectorComponents: {
17
+ File({ closeMenu }: AttachmentSelectorActionProps): React.JSX.Element;
18
+ Poll({ closeMenu, openModalForAction }: AttachmentSelectorActionProps): React.JSX.Element;
19
+ };
20
+ export declare const defaultAttachmentSelectorActionSet: AttachmentSelectorAction[];
21
+ export type AttachmentSelectorProps = {
22
+ attachmentSelectorActionSet?: AttachmentSelectorAction[];
23
+ getModalPortalDestination?: () => HTMLElement | null;
24
+ };
25
+ export declare const AttachmentSelector: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ attachmentSelectorActionSet, getModalPortalDestination, }: AttachmentSelectorProps) => React.JSX.Element | null;
@@ -0,0 +1,125 @@
1
+ import { nanoid } from 'nanoid';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { UploadIcon as DefaultUploadIcon } from './icons';
4
+ import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
5
+ import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
6
+ import { DialogMenuButton } from '../Dialog/DialogMenu';
7
+ import { Modal } from '../Modal';
8
+ import { PollCreationDialog as DefaultPollCreationDialog } from '../Poll';
9
+ import { Portal } from '../Portal/Portal';
10
+ import { UploadFileInput } from '../ReactFileUtilities';
11
+ import { useChannelStateContext, useComponentContext, useMessageInputContext, useTranslationContext, } from '../../context';
12
+ import { AttachmentSelectorContextProvider, useAttachmentSelectorContext, } from '../../context/AttachmentSelectorContext';
13
+ export const SimpleAttachmentSelector = () => {
14
+ const { AttachmentSelectorInitiationButtonContents, FileUploadIcon = DefaultUploadIcon, } = useComponentContext();
15
+ const inputRef = useRef(null);
16
+ const [labelElement, setLabelElement] = useState(null);
17
+ const id = useMemo(() => nanoid(), []);
18
+ useEffect(() => {
19
+ if (!labelElement)
20
+ return;
21
+ const handleKeyUp = (event) => {
22
+ if (![' ', 'Enter'].includes(event.key) || !inputRef.current)
23
+ return;
24
+ event.preventDefault();
25
+ inputRef.current.click();
26
+ };
27
+ labelElement.addEventListener('keyup', handleKeyUp);
28
+ return () => {
29
+ labelElement.removeEventListener('keyup', handleKeyUp);
30
+ };
31
+ }, [labelElement]);
32
+ return (React.createElement("div", { className: 'str-chat__file-input-container', "data-testid": 'file-upload-button' },
33
+ React.createElement(UploadFileInput, { id: id, ref: inputRef }),
34
+ React.createElement("label", { className: 'str-chat__file-input-label', htmlFor: id, ref: setLabelElement, tabIndex: 0 }, AttachmentSelectorInitiationButtonContents ? (React.createElement(AttachmentSelectorInitiationButtonContents, null)) : (React.createElement(FileUploadIcon, null)))));
35
+ };
36
+ const AttachmentSelectorMenuInitButtonIcon = () => {
37
+ const { AttachmentSelectorInitiationButtonContents, FileUploadIcon } = useComponentContext('SimpleAttachmentSelector');
38
+ if (AttachmentSelectorInitiationButtonContents) {
39
+ return React.createElement(AttachmentSelectorInitiationButtonContents, null);
40
+ }
41
+ if (FileUploadIcon) {
42
+ return React.createElement(FileUploadIcon, null);
43
+ }
44
+ return React.createElement("div", { className: 'str-chat__attachment-selector__menu-button__icon' });
45
+ };
46
+ export const DefaultAttachmentSelectorComponents = {
47
+ File({ closeMenu }) {
48
+ const { t } = useTranslationContext();
49
+ const { fileInput } = useAttachmentSelectorContext();
50
+ return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__upload-file-button', onClick: () => {
51
+ if (fileInput)
52
+ fileInput.click();
53
+ closeMenu();
54
+ } }, t('File')));
55
+ },
56
+ Poll({ closeMenu, openModalForAction }) {
57
+ const { t } = useTranslationContext();
58
+ return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__create-poll-button', onClick: () => {
59
+ openModalForAction('createPoll');
60
+ closeMenu();
61
+ } }, t('Poll')));
62
+ },
63
+ };
64
+ export const defaultAttachmentSelectorActionSet = [
65
+ { ActionButton: DefaultAttachmentSelectorComponents.File, type: 'uploadFile' },
66
+ {
67
+ ActionButton: DefaultAttachmentSelectorComponents.Poll,
68
+ type: 'createPoll',
69
+ },
70
+ ];
71
+ const useAttachmentSelectorActionsFiltered = (original) => {
72
+ const { PollCreationDialog = DefaultPollCreationDialog } = useComponentContext();
73
+ const { channelCapabilities, channelConfig } = useChannelStateContext();
74
+ const { isThreadInput } = useMessageInputContext();
75
+ return original
76
+ .filter((action) => {
77
+ if (action.type === 'uploadFile' && !channelCapabilities['upload-file'])
78
+ return false;
79
+ if (action.type === 'createPoll' &&
80
+ (!channelConfig?.polls || isThreadInput || !channelCapabilities['send-poll']))
81
+ return false;
82
+ return true;
83
+ })
84
+ .map((action) => {
85
+ if (action.type === 'createPoll' && !action.ModalContent) {
86
+ return { ...action, ModalContent: PollCreationDialog };
87
+ }
88
+ return action;
89
+ });
90
+ };
91
+ export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttachmentSelectorActionSet, getModalPortalDestination, }) => {
92
+ const { t } = useTranslationContext();
93
+ const { channelCapabilities } = useChannelStateContext();
94
+ const { isThreadInput } = useMessageInputContext();
95
+ const actions = useAttachmentSelectorActionsFiltered(attachmentSelectorActionSet);
96
+ const menuDialogId = `attachment-actions-menu${isThreadInput ? '-thread' : ''}`;
97
+ const menuDialog = useDialog({ id: menuDialogId });
98
+ const menuDialogIsOpen = useDialogIsOpen(menuDialogId);
99
+ const [modalContentAction, setModalContentActionAction] = useState();
100
+ const openModal = useCallback((actionType) => {
101
+ const action = actions.find((a) => a.type === actionType);
102
+ if (!action?.ModalContent)
103
+ return;
104
+ setModalContentActionAction(action);
105
+ }, [actions]);
106
+ const closeModal = useCallback(() => setModalContentActionAction(undefined), []);
107
+ const [fileInput, setFileInput] = useState(null);
108
+ const menuButtonRef = useRef(null);
109
+ const getDefaultPortalDestination = useCallback(() => document.getElementById(CHANNEL_CONTAINER_ID), []);
110
+ if (actions.length === 0)
111
+ return null;
112
+ if (actions.length === 1 && actions[0].type === 'uploadFile')
113
+ return React.createElement(SimpleAttachmentSelector, null);
114
+ const ModalContent = modalContentAction?.ModalContent;
115
+ const modalIsOpen = !!ModalContent;
116
+ return (React.createElement(AttachmentSelectorContextProvider, { value: { fileInput } },
117
+ React.createElement("div", { className: 'str-chat__attachment-selector' },
118
+ channelCapabilities['upload-file'] && React.createElement(UploadFileInput, { ref: setFileInput }),
119
+ 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 },
120
+ React.createElement(AttachmentSelectorMenuInitButtonIcon, null)),
121
+ React.createElement(DialogAnchor, { id: menuDialogId, placement: 'top-start', referenceElement: menuButtonRef.current, trapFocus: true },
122
+ 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 }))))),
123
+ React.createElement(Portal, { getPortalDestination: getModalPortalDestination ?? getDefaultPortalDestination, isOpen: modalIsOpen },
124
+ React.createElement(Modal, { className: 'str-chat__create-poll-modal', onClose: closeModal, open: modalIsOpen }, ModalContent && React.createElement(ModalContent, { close: closeModal }))))));
125
+ };
@@ -12,7 +12,7 @@ export const EditMessageForm = () => {
12
12
  document.addEventListener('keydown', onKeyDown);
13
13
  return () => document.removeEventListener('keydown', onKeyDown);
14
14
  }, [clearEditingState]);
15
- return (React.createElement("form", { className: 'str-chat__edit-message-form', onSubmit: handleSubmit },
15
+ return (React.createElement("form", { autoComplete: 'off', className: 'str-chat__edit-message-form', onSubmit: handleSubmit },
16
16
  React.createElement(MessageInputFlat, null),
17
17
  React.createElement("div", { className: 'str-chat__edit-message-form-options' },
18
18
  React.createElement("button", { className: 'str-chat__edit-message-cancel', "data-testid": 'cancel-button', onClick: clearEditingState }, t('Cancel')),
@@ -56,6 +56,8 @@ export type MessageInputProps<StreamChatGenerics extends DefaultStreamChatGeneri
56
56
  hideSendButton?: boolean;
57
57
  /** Custom UI component handling how the message input is rendered, defaults to and accepts the same props as [MessageInputFlat](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputFlat.tsx) */
58
58
  Input?: React.ComponentType<MessageInputProps<StreamChatGenerics, V>>;
59
+ /** Signals that the MessageInput is rendered in a message thread (Thread component) */
60
+ isThreadInput?: boolean;
59
61
  /** Max number of rows the underlying `textarea` component is allowed to grow */
60
62
  maxRows?: number;
61
63
  /** If true, the suggestion list will search all app users for an @mention, not just current channel members/watchers. Default: false. */
@@ -7,6 +7,7 @@ import { useMessageInputState } from './hooks/useMessageInputState';
7
7
  import { useChannelStateContext } from '../../context/ChannelStateContext';
8
8
  import { useComponentContext } from '../../context/ComponentContext';
9
9
  import { MessageInputContextProvider } from '../../context/MessageInputContext';
10
+ import { DialogManagerProvider } from '../../context';
10
11
  const MessageInputProvider = (props) => {
11
12
  const cooldownTimerState = useCooldownTimer();
12
13
  const messageInputState = useMessageInputState(props);
@@ -24,13 +25,17 @@ const UnMemoizedMessageInput = (props) => {
24
25
  const { dragAndDropWindow } = useChannelStateContext();
25
26
  const { Input: ContextInput, TriggerProvider = DefaultTriggerProvider } = useComponentContext('MessageInput');
26
27
  const Input = PropInput || ContextInput || MessageInputFlat;
28
+ const dialogManagerId = props.isThreadInput
29
+ ? 'message-input-dialog-manager-thread'
30
+ : 'message-input-dialog-manager';
27
31
  if (dragAndDropWindow)
28
- return (React.createElement(React.Fragment, null,
32
+ return (React.createElement(DialogManagerProvider, { id: dialogManagerId },
29
33
  React.createElement(TriggerProvider, null,
30
34
  React.createElement(Input, null))));
31
- return (React.createElement(MessageInputProvider, { ...props },
32
- React.createElement(TriggerProvider, null,
33
- React.createElement(Input, null))));
35
+ return (React.createElement(DialogManagerProvider, { id: dialogManagerId },
36
+ React.createElement(MessageInputProvider, { ...props },
37
+ React.createElement(TriggerProvider, null,
38
+ React.createElement(Input, null)))));
34
39
  };
35
40
  /**
36
41
  * A high level component that has provides all functionality to the Input it renders.
@@ -1,14 +1,12 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { UploadButton } from '../ReactFileUtilities';
3
2
  import clsx from 'clsx';
4
3
  import { useDropzone } from 'react-dropzone';
5
- import { nanoid } from 'nanoid';
6
- import { UploadIcon as DefaultUploadIcon } from './icons';
4
+ import { AttachmentSelector as DefaultAttachmentSelector, SimpleAttachmentSelector, } from './AttachmentSelector';
5
+ import { AttachmentPreviewList as DefaultAttachmentPreviewList } from './AttachmentPreviewList';
7
6
  import { CooldownTimer as DefaultCooldownTimer } from './CooldownTimer';
8
7
  import { SendButton as DefaultSendButton } from './SendButton';
9
8
  import { AudioRecorder as DefaultAudioRecorder, RecordingPermissionDeniedNotification as DefaultRecordingPermissionDeniedNotification, StartRecordingAudioButton as DefaultStartRecordingAudioButton, RecordingPermission, } from '../MediaRecorder';
10
9
  import { QuotedMessagePreview as DefaultQuotedMessagePreview, QuotedMessagePreviewHeader, } from './QuotedMessagePreview';
11
- import { AttachmentPreviewList as DefaultAttachmentPreviewList } from './AttachmentPreviewList';
12
10
  import { LinkPreviewList as DefaultLinkPreviewList } from './LinkPreviewList';
13
11
  import { ChatAutoComplete } from '../ChatAutoComplete/ChatAutoComplete';
14
12
  import { RecordingAttachmentType } from '../MediaRecorder/classes';
@@ -21,7 +19,7 @@ import { useComponentContext } from '../../context/ComponentContext';
21
19
  export const MessageInputFlat = () => {
22
20
  const { t } = useTranslationContext('MessageInputFlat');
23
21
  const { asyncMessagesMultiSendEnabled, attachments, cooldownRemaining, findAndEnqueueURLsToEnrich, handleSubmit, hideSendButton, isUploadEnabled, linkPreviews, maxFilesLeft, message, numberOfUploads, parent, recordingController, setCooldownRemaining, text, uploadNewFiles, } = useMessageInputContext('MessageInputFlat');
24
- const { AudioRecorder = DefaultAudioRecorder, AttachmentPreviewList = DefaultAttachmentPreviewList, CooldownTimer = DefaultCooldownTimer, FileUploadIcon = DefaultUploadIcon, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, EmojiPicker, } = useComponentContext('MessageInputFlat');
22
+ const { AudioRecorder = DefaultAudioRecorder, AttachmentPreviewList = DefaultAttachmentPreviewList, AttachmentSelector = message ? SimpleAttachmentSelector : DefaultAttachmentSelector, CooldownTimer = DefaultCooldownTimer, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, EmojiPicker, } = useComponentContext('MessageInputFlat');
25
23
  const { acceptedFiles = [], multipleUploads, quotedMessage, } = useChannelStateContext('MessageInputFlat');
26
24
  const { setQuotedMessage } = useChannelActionContext('MessageInputFlat');
27
25
  const { channel } = useChatContext('MessageInputFlat');
@@ -29,7 +27,6 @@ export const MessageInputFlat = () => {
29
27
  const closePermissionDeniedNotification = useCallback(() => {
30
28
  setShowRecordingPermissionDeniedNotification(false);
31
29
  }, []);
32
- const id = useMemo(() => nanoid(), []);
33
30
  const failedUploadsCount = useMemo(() => attachments.filter((a) => a.localMetadata?.uploadState === 'failed').length, [attachments]);
34
31
  const accept = useMemo(() => acceptedFiles.reduce((mediaTypeMap, mediaType) => {
35
32
  mediaTypeMap[mediaType] ?? (mediaTypeMap[mediaType] = []);
@@ -80,10 +77,7 @@ export const MessageInputFlat = () => {
80
77
  isDragReject && React.createElement("p", null, t('Some of the files will not be accepted')))),
81
78
  displayQuotedMessage && React.createElement(QuotedMessagePreviewHeader, null),
82
79
  React.createElement("div", { className: 'str-chat__message-input-inner' },
83
- React.createElement("div", { className: 'str-chat__file-input-container', "data-testid": 'file-upload-button' },
84
- React.createElement(UploadButton, { accept: acceptedFiles?.join(','), "aria-label": t('aria/File upload'), className: 'str-chat__file-input', "data-testid": 'file-input', disabled: !isUploadEnabled || maxFilesLeft === 0, id: id, multiple: multipleUploads, onFileChange: uploadNewFiles }),
85
- React.createElement("label", { className: 'str-chat__file-input-label', htmlFor: id },
86
- React.createElement(FileUploadIcon, null))),
80
+ React.createElement(AttachmentSelector, null),
87
81
  React.createElement("div", { className: 'str-chat__message-textarea-container' },
88
82
  displayQuotedMessage && React.createElement(QuotedMessagePreview, { quotedMessage: quotedMessage }),
89
83
  isUploadEnabled &&
@@ -1,7 +1,9 @@
1
1
  import React, { useMemo } from 'react';
2
+ import { CloseIcon } from './icons';
2
3
  import { Attachment as DefaultAttachment } from '../Attachment';
3
4
  import { Avatar as DefaultAvatar } from '../Avatar';
4
- import { CloseIcon } from './icons';
5
+ import { Poll } from '../Poll';
6
+ import { useChatContext } from '../../context/ChatContext';
5
7
  import { useChannelActionContext } from '../../context/ChannelActionContext';
6
8
  import { useComponentContext } from '../../context/ComponentContext';
7
9
  import { useTranslationContext } from '../../context/TranslationContext';
@@ -14,6 +16,7 @@ export const QuotedMessagePreviewHeader = () => {
14
16
  React.createElement(CloseIcon, null))));
15
17
  };
16
18
  export const QuotedMessagePreview = ({ quotedMessage, }) => {
19
+ const { client } = useChatContext();
17
20
  const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, } = useComponentContext('QuotedMessagePreview');
18
21
  const { userLanguage } = useTranslationContext('QuotedMessagePreview');
19
22
  const quotedMessageText = quotedMessage.i18n?.[`${userLanguage}_text`] ||
@@ -24,10 +27,11 @@ export const QuotedMessagePreview = ({ quotedMessage, }) => {
24
27
  }, [quotedMessage.attachments]);
25
28
  if (!quotedMessageText && !quotedMessageAttachment)
26
29
  return null;
30
+ const poll = quotedMessage.poll_id && client.polls.fromState(quotedMessage.poll_id);
27
31
  return (React.createElement("div", { className: 'str-chat__quoted-message-preview', "data-testid": 'quoted-message-preview' },
28
32
  quotedMessage.user && (React.createElement(Avatar, { className: 'str-chat__avatar--quoted-message-sender', image: quotedMessage.user.image, name: quotedMessage.user.name || quotedMessage.user.id, user: quotedMessage.user })),
29
- React.createElement("div", { className: 'str-chat__quoted-message-bubble' },
33
+ React.createElement("div", { className: 'str-chat__quoted-message-bubble' }, poll ? (React.createElement(Poll, { isQuoted: true, poll: poll })) : (React.createElement(React.Fragment, null,
30
34
  !!quotedMessageAttachment.length && (React.createElement(Attachment, { attachments: quotedMessageAttachment, isQuoted: true })),
31
35
  React.createElement("div", { className: 'str-chat__quoted-message-text', "data-testid": 'quoted-message-text' },
32
- React.createElement("p", null, quotedMessageText)))));
36
+ React.createElement("p", null, quotedMessageText)))))));
33
37
  };
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
  export const useCreateMessageInputContext = (value) => {
3
- const { additionalTextareaProps, asyncMessagesMultiSendEnabled, attachments, audioRecordingEnabled, autocompleteTriggers, cancelURLEnrichment, clearEditingState, closeCommandsList, closeMentionsList, cooldownInterval, cooldownRemaining, disabled, disableMentions, dismissLinkPreview, doFileUploadRequest, doImageUploadRequest, emojiSearchIndex, errorHandler, findAndEnqueueURLsToEnrich, focus, grow, handleChange, handleSubmit, hideSendButton, insertText, isUploadEnabled, linkPreviews, maxFilesLeft, maxRows, mentionAllAppUsers, mentioned_users, mentionQueryParams, message, minRows, noFiles, numberOfUploads, onPaste, onSelectUser, openCommandsList, openMentionsList, overrideSubmitHandler, parent, publishTypingEvent, recordingController, removeAttachments, setCooldownRemaining, setText, shouldSubmit, showCommandsList, showMentionsList, text, textareaRef, uploadAttachment, uploadNewFiles, upsertAttachments, useMentionsTransliteration, } = value;
3
+ const { additionalTextareaProps, asyncMessagesMultiSendEnabled, attachments, audioRecordingEnabled, autocompleteTriggers, cancelURLEnrichment, clearEditingState, closeCommandsList, closeMentionsList, cooldownInterval, cooldownRemaining, disabled, disableMentions, dismissLinkPreview, doFileUploadRequest, doImageUploadRequest, emojiSearchIndex, errorHandler, findAndEnqueueURLsToEnrich, focus, grow, handleChange, handleSubmit, hideSendButton, insertText, isThreadInput, isUploadEnabled, linkPreviews, maxFilesLeft, maxRows, mentionAllAppUsers, mentioned_users, mentionQueryParams, message, minRows, noFiles, numberOfUploads, onPaste, onSelectUser, openCommandsList, openMentionsList, overrideSubmitHandler, parent, publishTypingEvent, recordingController, removeAttachments, setCooldownRemaining, setText, shouldSubmit, showCommandsList, showMentionsList, text, textareaRef, uploadAttachment, uploadNewFiles, upsertAttachments, useMentionsTransliteration, } = value;
4
4
  const editing = message?.editing;
5
5
  const linkPreviewsValue = Array.from(linkPreviews.values()).join();
6
6
  const mentionedUsersLength = mentioned_users.length;
@@ -31,6 +31,7 @@ export const useCreateMessageInputContext = (value) => {
31
31
  handleSubmit,
32
32
  hideSendButton,
33
33
  insertText,
34
+ isThreadInput,
34
35
  isUploadEnabled,
35
36
  linkPreviews,
36
37
  maxFilesLeft,
@@ -78,6 +79,7 @@ export const useCreateMessageInputContext = (value) => {
78
79
  handleSubmit,
79
80
  hideSendButton,
80
81
  isUploadEnabled,
82
+ isThreadInput,
81
83
  linkPreviewsValue,
82
84
  mentionedUsersLength,
83
85
  minRows,
@@ -1,7 +1,5 @@
1
1
  import { useCallback } from 'react';
2
- import {
3
- // dataTransferItemsHaveFiles,
4
- dataTransferItemsToFiles, } from '../../ReactFileUtilities';
2
+ import { dataTransferItemsToFiles } from '../../ReactFileUtilities';
5
3
  import { SetLinkPreviewMode } from '../types';
6
4
  export const usePasteHandler = (uploadNewFiles, insertText, isUploadEnabled, findAndEnqueueURLsToEnrich) => {
7
5
  const onPaste = useCallback((clipboardEvent) => {
@@ -24,17 +22,16 @@ export const usePasteHandler = (uploadNewFiles, insertText, isUploadEnabled, fin
24
22
  }
25
23
  }
26
24
  const fileLikes = await dataTransferItemsToFiles(Array.from(items));
27
- if (fileLikes.length && isUploadEnabled) {
28
- uploadNewFiles(fileLikes);
29
- return;
30
- }
31
- // fallback to regular text paste
32
25
  if (plainTextPromise) {
33
26
  const pastedText = await plainTextPromise;
34
27
  insertText(pastedText);
35
28
  findAndEnqueueURLsToEnrich?.(pastedText, SetLinkPreviewMode.UPSERT);
36
29
  findAndEnqueueURLsToEnrich?.flush();
37
30
  }
31
+ else if (fileLikes.length && isUploadEnabled) {
32
+ uploadNewFiles(fileLikes);
33
+ return;
34
+ }
38
35
  })(clipboardEvent);
39
36
  }, [findAndEnqueueURLsToEnrich, insertText, isUploadEnabled, uploadNewFiles]);
40
37
  return { onPaste };
@@ -29,7 +29,10 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
29
29
  trimmedMessage === '____' ||
30
30
  trimmedMessage === '__' ||
31
31
  trimmedMessage === '****';
32
- if (isEmptyMessage && numberOfUploads === 0 && attachments.length === 0)
32
+ if (isEmptyMessage &&
33
+ numberOfUploads === 0 &&
34
+ attachments.length === 0 &&
35
+ !customMessageData?.poll_id)
33
36
  return;
34
37
  const someAttachmentsUploading = attachments.some((att) => att.localMetadata?.uploadState === 'uploading');
35
38
  if (someAttachmentsUploading) {
@@ -1,3 +1,4 @@
1
+ export * from './AttachmentSelector';
1
2
  export { AttachmentPreviewList } from './AttachmentPreviewList';
2
3
  export type { AttachmentPreviewListProps, FileAttachmentPreviewProps, ImageAttachmentPreviewProps, AttachmentPreviewProps, UnsupportedAttachmentPreviewProps, VoiceRecordingPreviewProps, } from './AttachmentPreviewList';
3
4
  export * from './CooldownTimer';
@@ -1,3 +1,4 @@
1
+ export * from './AttachmentSelector';
1
2
  export { AttachmentPreviewList } from './AttachmentPreviewList';
2
3
  export * from './CooldownTimer';
3
4
  export * from './DefaultTriggerProvider';