stream-chat-react 12.9.0 → 12.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Attachment/Attachment.js +2 -1
- package/dist/components/Attachment/AttachmentContainer.d.ts +1 -1
- package/dist/components/Attachment/AttachmentContainer.js +2 -2
- package/dist/components/Attachment/Card.js +3 -3
- package/dist/components/Attachment/VoiceRecording.d.ts +1 -1
- package/dist/components/Attachment/VoiceRecording.js +2 -2
- package/dist/components/Attachment/attachment-sizing.js +2 -1
- package/dist/components/Attachment/components/FileSizeIndicator.d.ts +1 -1
- package/dist/components/Attachment/components/FileSizeIndicator.js +1 -1
- package/dist/components/Attachment/components/WaveProgressBar.js +1 -1
- package/dist/components/Attachment/hooks/useAudioController.d.ts +1 -1
- package/dist/components/Attachment/hooks/useAudioController.js +4 -2
- package/dist/components/Attachment/utils.d.ts +1 -1
- package/dist/components/Attachment/utils.js +12 -3
- package/dist/components/AutoCompleteTextarea/List.js +3 -2
- package/dist/components/AutoCompleteTextarea/Textarea.js +3 -7
- package/dist/components/AutoCompleteTextarea/utils.js +3 -7
- package/dist/components/Avatar/ChannelAvatar.js +1 -1
- package/dist/components/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +30 -14
- package/dist/components/Channel/channelState.d.ts +1 -3
- package/dist/components/Channel/channelState.js +10 -4
- package/dist/components/Channel/hooks/useCreateChannelStateContext.js +5 -3
- package/dist/components/Channel/hooks/useIsMounted.d.ts +1 -1
- package/dist/components/Channel/hooks/useMentionsHandlers.js +5 -2
- package/dist/components/Channel/utils.js +2 -1
- package/dist/components/ChannelHeader/ChannelHeader.js +1 -1
- package/dist/components/ChannelList/ChannelList.js +13 -8
- package/dist/components/ChannelList/hooks/useChannelListShape.d.ts +2 -2
- package/dist/components/ChannelList/hooks/useChannelListShape.js +46 -19
- package/dist/components/ChannelList/hooks/useChannelUpdatedListener.js +2 -1
- package/dist/components/ChannelList/hooks/useMessageNewListener.js +3 -1
- package/dist/components/ChannelList/hooks/useMobileNavigation.d.ts +1 -1
- package/dist/components/ChannelList/hooks/usePaginatedChannels.js +5 -3
- package/dist/components/ChannelList/hooks/useSelectedChannelState.js +1 -1
- package/dist/components/ChannelPreview/ChannelPreview.js +2 -2
- package/dist/components/ChannelPreview/ChannelPreviewMessenger.js +1 -1
- package/dist/components/ChannelPreview/icons.js +0 -1
- package/dist/components/ChannelPreview/utils.js +3 -3
- package/dist/components/ChannelSearch/ChannelSearch.js +5 -3
- package/dist/components/ChannelSearch/SearchBar.d.ts +1 -1
- package/dist/components/ChannelSearch/SearchInput.d.ts +1 -1
- package/dist/components/ChannelSearch/SearchResults.js +1 -1
- package/dist/components/ChannelSearch/hooks/useChannelSearch.d.ts +4 -2
- package/dist/components/ChannelSearch/hooks/useChannelSearch.js +61 -36
- package/dist/components/ChannelSearch/index.d.ts +1 -1
- package/dist/components/Chat/hooks/useChat.js +4 -2
- package/dist/components/Chat/hooks/useCreateChatClient.js +3 -1
- package/dist/components/ChatAutoComplete/ChatAutoComplete.js +1 -1
- package/dist/components/ChatView/ChatView.js +0 -1
- package/dist/components/Dialog/PromptDialog.d.ts +1 -1
- package/dist/components/Dialog/PromptDialog.js +1 -1
- package/dist/components/EventComponent/EventComponent.js +1 -1
- package/dist/components/Gallery/BaseImage.d.ts +1 -3
- package/dist/components/Gallery/Gallery.js +10 -2
- package/dist/components/Gallery/Image.js +1 -1
- package/dist/components/Gallery/ModalGallery.js +4 -4
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +9 -6
- package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.js +1 -1
- package/dist/components/LoadMore/LoadMorePaginator.js +1 -1
- package/dist/components/MML/MML.js +1 -0
- package/dist/components/MediaRecorder/transcode/wav.js +6 -3
- package/dist/components/Message/FixedHeightMessage.js +6 -8
- package/dist/components/Message/Message.js +2 -1
- package/dist/components/Message/MessageEditedTimestamp.js +1 -1
- package/dist/components/Message/MessageSimple.js +2 -5
- package/dist/components/Message/MessageStatus.js +1 -1
- package/dist/components/Message/MessageText.js +7 -7
- package/dist/components/Message/QuotedMessage.js +2 -2
- package/dist/components/Message/Timestamp.js +9 -1
- package/dist/components/Message/hooks/useActionHandler.js +1 -1
- package/dist/components/Message/hooks/useFlagHandler.js +2 -1
- package/dist/components/Message/hooks/useMarkUnreadHandler.js +2 -1
- package/dist/components/Message/hooks/useMessageTextStreaming.d.ts +1 -1
- package/dist/components/Message/hooks/useMessageTextStreaming.js +1 -1
- package/dist/components/Message/hooks/useMuteHandler.js +7 -4
- package/dist/components/Message/hooks/usePinHandler.js +1 -1
- package/dist/components/Message/hooks/useReactionHandler.js +10 -5
- package/dist/components/Message/hooks/useRetryHandler.js +1 -1
- package/dist/components/Message/hooks/useUserRole.js +1 -1
- package/dist/components/Message/renderText/renderText.js +2 -2
- package/dist/components/Message/utils.js +3 -3
- package/dist/components/MessageActions/MessageActionsBox.js +7 -3
- package/dist/components/MessageActions/hooks/useMessageActionsBoxPopper.d.ts +1 -1
- package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.js +1 -1
- package/dist/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.js +4 -2
- package/dist/components/MessageInput/AttachmentPreviewList/UnsupportedAttachmentPreview.js +4 -2
- package/dist/components/MessageInput/AttachmentSelector.js +1 -1
- package/dist/components/MessageInput/DefaultTriggerProvider.js +1 -1
- package/dist/components/MessageInput/DropzoneProvider.js +1 -1
- package/dist/components/MessageInput/MessageInput.js +1 -1
- package/dist/components/MessageInput/MessageInputFlat.js +4 -3
- package/dist/components/MessageInput/QuotedMessagePreview.js +1 -1
- package/dist/components/MessageInput/hooks/useAttachments.js +12 -4
- package/dist/components/MessageInput/hooks/useLinkPreviews.js +3 -1
- package/dist/components/MessageInput/hooks/useMessageInputState.js +4 -3
- package/dist/components/MessageInput/hooks/useMessageInputText.d.ts +1 -1
- package/dist/components/MessageInput/hooks/useMessageInputText.js +5 -3
- package/dist/components/MessageInput/hooks/useSubmitHandler.js +5 -3
- package/dist/components/MessageInput/hooks/useTimeElapsed.js +1 -1
- package/dist/components/MessageInput/hooks/useUserTrigger.js +2 -2
- package/dist/components/MessageInput/hooks/utils.js +6 -2
- package/dist/components/MessageList/ConnectionStatus.js +1 -1
- package/dist/components/MessageList/MessageList.js +5 -6
- package/dist/components/MessageList/MessageListNotifications.js +3 -1
- package/dist/components/MessageList/VirtualizedMessageList.d.ts +1 -1
- package/dist/components/MessageList/VirtualizedMessageList.js +19 -12
- package/dist/components/MessageList/VirtualizedMessageListComponents.js +2 -2
- package/dist/components/MessageList/hooks/MessageList/useMessageListElements.js +1 -3
- package/dist/components/MessageList/hooks/MessageList/useMessageListScrollManager.js +3 -2
- package/dist/components/MessageList/hooks/MessageList/useScrollLocationLogic.js +3 -2
- package/dist/components/MessageList/hooks/MessageList/useUnreadMessagesNotification.js +6 -2
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useMessageSetKey.js +1 -1
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useNewMessageNotification.d.ts +1 -1
- package/dist/components/MessageList/hooks/VirtualizedMessageList/usePrependMessagesCount.js +4 -3
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useScrollToBottomOnNewMessage.js +1 -1
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useUnreadMessagesNotificationVirtualized.js +7 -3
- package/dist/components/MessageList/hooks/useMarkRead.d.ts +2 -4
- package/dist/components/MessageList/hooks/useMarkRead.js +14 -16
- package/dist/components/MessageList/utils.js +28 -11
- package/dist/components/Modal/Modal.d.ts +1 -1
- package/dist/components/Modal/Modal.js +1 -1
- package/dist/components/Modal/ModalHeader.js +1 -1
- package/dist/components/Poll/Poll.js +1 -1
- package/dist/components/Poll/PollActions/PollActions.js +6 -4
- package/dist/components/Poll/PollActions/PollAnswerList.js +1 -1
- package/dist/components/Poll/PollActions/PollResults/PollOptionVotesList.js +1 -1
- package/dist/components/Poll/PollActions/PollResults/PollOptionWithLatestVotes.js +4 -2
- package/dist/components/Poll/PollActions/PollResults/PollResults.js +2 -1
- package/dist/components/Poll/PollContent.js +1 -1
- package/dist/components/Poll/PollCreationDialog/OptionFieldSet.d.ts +1 -1
- package/dist/components/Poll/PollCreationDialog/OptionFieldSet.js +11 -4
- package/dist/components/Poll/PollCreationDialog/PollCreationDialog.js +13 -4
- package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.js +6 -3
- package/dist/components/Poll/PollOptionList.js +1 -1
- package/dist/components/Poll/PollOptionSelector.js +12 -5
- package/dist/components/Poll/hooks/useManagePollVotesRealtime.js +9 -4
- package/dist/components/Poll/hooks/usePollAnswerPagination.js +8 -2
- package/dist/components/Poll/hooks/usePollOptionVotesPagination.js +8 -2
- package/dist/components/ReactFileUtilities/FileIcon/mimeTypes.d.ts +1 -1
- package/dist/components/ReactFileUtilities/ImageDropzone.js +3 -1
- package/dist/components/ReactFileUtilities/UploadButton.js +1 -1
- package/dist/components/ReactFileUtilities/utils.js +3 -1
- package/dist/components/Reactions/ReactionSelector.js +3 -1
- package/dist/components/Reactions/ReactionSelectorWithButton.js +1 -1
- package/dist/components/Reactions/ReactionsList.d.ts +2 -2
- package/dist/components/Reactions/ReactionsList.js +7 -4
- package/dist/components/Reactions/ReactionsListModal.d.ts +1 -2
- package/dist/components/Reactions/ReactionsListModal.js +1 -1
- package/dist/components/Reactions/SpriteImage.js +6 -2
- package/dist/components/Reactions/hooks/useFetchReactions.js +1 -1
- package/dist/components/Reactions/hooks/useProcessReactions.js +1 -1
- package/dist/components/Reactions/index.d.ts +1 -0
- package/dist/components/Reactions/index.js +1 -0
- package/dist/components/Reactions/reactionOptions.js +20 -5
- package/dist/components/Thread/Thread.js +4 -3
- package/dist/components/Threads/ThreadContext.d.ts +1 -1
- package/dist/components/Threads/ThreadContext.js +1 -1
- package/dist/components/Threads/ThreadList/ThreadList.js +1 -1
- package/dist/components/Threads/ThreadList/ThreadListItem.d.ts +1 -1
- package/dist/components/Threads/ThreadList/ThreadListItem.js +1 -1
- package/dist/components/Threads/ThreadList/ThreadListItemUI.js +5 -3
- package/dist/components/Threads/icons.js +0 -1
- package/dist/components/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip/Tooltip.js +1 -1
- package/dist/context/ComponentContext.d.ts +3 -1
- package/dist/context/ComponentContext.js +1 -1
- package/dist/context/DialogManagerContext.d.ts +1 -1
- package/dist/context/DialogManagerContext.js +1 -1
- package/dist/context/MessageBounceContext.js +2 -2
- package/dist/context/MessageContext.js +1 -1
- package/dist/context/VirtualizedMessageListContext.d.ts +13 -0
- package/dist/context/VirtualizedMessageListContext.js +7 -0
- package/dist/context/WithComponents.js +1 -1
- package/dist/css/v2/index.css +2 -2
- package/dist/css/v2/index.layout.css +2 -2
- package/dist/experimental/MessageActions/MessageActions.js +0 -1
- package/dist/experimental/MessageActions/defaults.js +31 -7
- package/dist/experimental/index.browser.cjs +103 -37
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs +103 -37
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/i18n/Streami18n.js +11 -2
- package/dist/i18n/utils.js +14 -1
- package/dist/index.browser.cjs +2329 -1145
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +2594 -1164
- package/dist/index.node.cjs.map +4 -4
- package/dist/plugins/Emojis/EmojiPicker.js +0 -1
- package/dist/plugins/Emojis/index.browser.cjs +3 -1
- package/dist/plugins/Emojis/index.browser.cjs.map +2 -2
- package/dist/plugins/Emojis/index.node.cjs +3 -1
- package/dist/plugins/Emojis/index.node.cjs.map +2 -2
- package/dist/plugins/encoders/mp3.browser.cjs +7 -4
- package/dist/plugins/encoders/mp3.browser.cjs.map +2 -2
- package/dist/plugins/encoders/mp3.node.cjs +7 -4
- package/dist/plugins/encoders/mp3.node.cjs.map +2 -2
- package/dist/scss/v2/ChannelSearch/ChannelSearch-layout.scss +1 -0
- package/dist/scss/v2/Message/Message-layout.scss +12 -5
- package/dist/scss/v2/Poll/Poll-layout.scss +1 -1
- package/dist/scss/v2/Search/Search-layout.scss +148 -0
- package/dist/scss/v2/Search/Search-theme.scss +222 -0
- package/dist/scss/v2/_icons.scss +2 -0
- package/dist/scss/v2/index.layout.scss +1 -0
- package/dist/scss/v2/index.scss +1 -0
- package/dist/store/hooks/useStateStore.js +35 -11
- package/package.json +32 -47
|
@@ -8,13 +8,15 @@ export const UnsupportedAttachmentPreview = ({ attachment, handleRetry, removeAt
|
|
|
8
8
|
return (React.createElement("div", { className: 'str-chat__attachment-preview-unsupported', "data-testid": 'attachment-preview-unknown' },
|
|
9
9
|
React.createElement("div", { className: 'str-chat__attachment-preview-file-icon' },
|
|
10
10
|
React.createElement(FileIcon, { filename: title, mimeType: attachment.mime_type })),
|
|
11
|
-
React.createElement("button", { className: 'str-chat__attachment-preview-delete', "data-testid": 'file-preview-item-delete-button', disabled: attachment.localMetadata?.uploadState === 'uploading', onClick: () => attachment.localMetadata?.id &&
|
|
11
|
+
React.createElement("button", { className: 'str-chat__attachment-preview-delete', "data-testid": 'file-preview-item-delete-button', disabled: attachment.localMetadata?.uploadState === 'uploading', onClick: () => attachment.localMetadata?.id &&
|
|
12
|
+
removeAttachments([attachment.localMetadata?.id]) },
|
|
12
13
|
React.createElement(CloseIcon, null)),
|
|
13
14
|
attachment.localMetadata?.uploadState === 'failed' && !!handleRetry && (React.createElement("button", { className: 'str-chat__attachment-preview-error str-chat__attachment-preview-error-file', "data-testid": 'file-preview-item-retry-button', onClick: () => handleRetry(attachment) },
|
|
14
15
|
React.createElement(RetryIcon, null))),
|
|
15
16
|
React.createElement("div", { className: 'str-chat__attachment-preview-metadata' },
|
|
16
17
|
React.createElement("div", { className: 'str-chat__attachment-preview-title', title: title }, title),
|
|
17
|
-
attachment.localMetadata?.uploadState === 'finished' &&
|
|
18
|
+
attachment.localMetadata?.uploadState === 'finished' &&
|
|
19
|
+
!!attachment.asset_url && (React.createElement("a", { className: 'str-chat__attachment-preview-file-download', download: true, href: attachment.asset_url, rel: 'noreferrer', target: '_blank' },
|
|
18
20
|
React.createElement(DownloadIcon, null))),
|
|
19
21
|
attachment.localMetadata?.uploadState === 'uploading' && (React.createElement(LoadingIndicatorIcon, { size: 17 })))));
|
|
20
22
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { UploadIcon as DefaultUploadIcon } from './icons';
|
|
4
4
|
import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
|
|
5
5
|
import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
|
|
@@ -20,5 +20,5 @@ export const DefaultTriggerProvider = ({ children, }) => {
|
|
|
20
20
|
...currentValue,
|
|
21
21
|
autocompleteTriggers: defaultAutocompleteTriggers,
|
|
22
22
|
};
|
|
23
|
-
return React.createElement(MessageInputContextProvider, { value: newValue }, children);
|
|
23
|
+
return (React.createElement(MessageInputContextProvider, { value: newValue }, children));
|
|
24
24
|
};
|
|
@@ -7,7 +7,7 @@ import { useChannelStateContext } from '../../context/ChannelStateContext';
|
|
|
7
7
|
import { MessageInputContextProvider, useMessageInputContext, } from '../../context/MessageInputContext';
|
|
8
8
|
const DropzoneInner = ({ children, }) => {
|
|
9
9
|
const { acceptedFiles, multipleUploads } = useChannelStateContext('DropzoneProvider');
|
|
10
|
-
const { cooldownRemaining, isUploadEnabled, maxFilesLeft, uploadNewFiles
|
|
10
|
+
const { cooldownRemaining, isUploadEnabled, maxFilesLeft, uploadNewFiles } = useMessageInputContext('DropzoneProvider');
|
|
11
11
|
return (React.createElement(ImageDropzone, { accept: acceptedFiles, disabled: !isUploadEnabled || maxFilesLeft === 0 || !!cooldownRemaining, handleFiles: uploadNewFiles, maxNumberOfFiles: maxFilesLeft, multiple: multipleUploads }, children));
|
|
12
12
|
};
|
|
13
13
|
export const DropzoneProvider = (props) => {
|
|
@@ -5,7 +5,7 @@ import { useCooldownTimer } from './hooks/useCooldownTimer';
|
|
|
5
5
|
import { useCreateMessageInputContext } from './hooks/useCreateMessageInputContext';
|
|
6
6
|
import { useMessageInputState } from './hooks/useMessageInputState';
|
|
7
7
|
import { useChannelStateContext } from '../../context/ChannelStateContext';
|
|
8
|
-
import { useComponentContext } from '../../context/ComponentContext';
|
|
8
|
+
import { useComponentContext, } from '../../context/ComponentContext';
|
|
9
9
|
import { MessageInputContextProvider } from '../../context/MessageInputContext';
|
|
10
10
|
import { DialogManagerProvider } from '../../context';
|
|
11
11
|
const MessageInputProvider = (props) => {
|
|
@@ -21,7 +21,7 @@ import { AIStates, useAIState } from '../AIStateIndicator';
|
|
|
21
21
|
export const MessageInputFlat = () => {
|
|
22
22
|
const { t } = useTranslationContext('MessageInputFlat');
|
|
23
23
|
const { asyncMessagesMultiSendEnabled, attachments, cooldownRemaining, findAndEnqueueURLsToEnrich, handleSubmit, hideSendButton, isUploadEnabled, linkPreviews, maxFilesLeft, message, numberOfUploads, parent, recordingController, setCooldownRemaining, text, uploadNewFiles, } = useMessageInputContext('MessageInputFlat');
|
|
24
|
-
const {
|
|
24
|
+
const { AttachmentPreviewList = DefaultAttachmentPreviewList, AttachmentSelector = message ? SimpleAttachmentSelector : DefaultAttachmentSelector, AudioRecorder = DefaultAudioRecorder, CooldownTimer = DefaultCooldownTimer, EmojiPicker, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, StopAIGenerationButton: StopAIGenerationButtonOverride, } = useComponentContext('MessageInputFlat');
|
|
25
25
|
const { acceptedFiles = [], multipleUploads, quotedMessage, } = useChannelStateContext('MessageInputFlat');
|
|
26
26
|
const { setQuotedMessage } = useChannelActionContext('MessageInputFlat');
|
|
27
27
|
const { channel } = useChatContext('MessageInputFlat');
|
|
@@ -76,7 +76,8 @@ export const MessageInputFlat = () => {
|
|
|
76
76
|
const StopAIGenerationButton = StopAIGenerationButtonOverride === undefined
|
|
77
77
|
? DefaultStopAIGenerationButton
|
|
78
78
|
: StopAIGenerationButtonOverride;
|
|
79
|
-
const shouldDisplayStopAIGeneration = [AIStates.Thinking, AIStates.Generating].includes(aiState) &&
|
|
79
|
+
const shouldDisplayStopAIGeneration = [AIStates.Thinking, AIStates.Generating].includes(aiState) &&
|
|
80
|
+
!!StopAIGenerationButton;
|
|
80
81
|
return (React.createElement(React.Fragment, null,
|
|
81
82
|
React.createElement("div", { ...getRootProps({ className: 'str-chat__message-input' }) },
|
|
82
83
|
recordingEnabled &&
|
|
@@ -92,7 +93,7 @@ export const MessageInputFlat = () => {
|
|
|
92
93
|
React.createElement("div", { className: 'str-chat__message-input-inner' },
|
|
93
94
|
React.createElement(AttachmentSelector, null),
|
|
94
95
|
React.createElement("div", { className: 'str-chat__message-textarea-container' },
|
|
95
|
-
displayQuotedMessage && React.createElement(QuotedMessagePreview, { quotedMessage: quotedMessage }),
|
|
96
|
+
displayQuotedMessage && (React.createElement(QuotedMessagePreview, { quotedMessage: quotedMessage })),
|
|
96
97
|
isUploadEnabled &&
|
|
97
98
|
!!(numberOfUploads + failedUploadsCount || attachments.length > 0) && (React.createElement(AttachmentPreviewList, null)),
|
|
98
99
|
React.createElement("div", { className: 'str-chat__message-textarea-with-emoji-picker' },
|
|
@@ -17,7 +17,7 @@ export const QuotedMessagePreviewHeader = () => {
|
|
|
17
17
|
};
|
|
18
18
|
export const QuotedMessagePreview = ({ quotedMessage, }) => {
|
|
19
19
|
const { client } = useChatContext();
|
|
20
|
-
const { Attachment = DefaultAttachment, Avatar = DefaultAvatar
|
|
20
|
+
const { Attachment = DefaultAttachment, Avatar = DefaultAvatar } = useComponentContext('QuotedMessagePreview');
|
|
21
21
|
const { userLanguage } = useTranslationContext('QuotedMessagePreview');
|
|
22
22
|
const quotedMessageText = quotedMessage.i18n?.[`${userLanguage}_text`] ||
|
|
23
23
|
quotedMessage.text;
|
|
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
|
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
3
|
import { checkUploadPermissions } from './utils';
|
|
4
4
|
import { isLocalAttachment, isLocalImageAttachment } from '../../Attachment';
|
|
5
|
-
import { createFileFromBlobs, generateFileName, isBlobButNotFile } from '../../ReactFileUtilities';
|
|
5
|
+
import { createFileFromBlobs, generateFileName, isBlobButNotFile, } from '../../ReactFileUtilities';
|
|
6
6
|
import { useChannelActionContext, useChannelStateContext, useChatContext, useTranslationContext, } from '../../../context';
|
|
7
7
|
const apiMaxNumberOfFiles = 10;
|
|
8
8
|
// const isAudioFile = (file: FileLike) => file.type.includes('audio/');
|
|
@@ -109,7 +109,10 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
catch (error) {
|
|
112
|
-
let finalError = {
|
|
112
|
+
let finalError = {
|
|
113
|
+
message: t('Error uploading attachment'),
|
|
114
|
+
name: 'Error',
|
|
115
|
+
};
|
|
113
116
|
if (typeof error.message === 'string') {
|
|
114
117
|
finalError = error;
|
|
115
118
|
}
|
|
@@ -127,7 +130,10 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
127
130
|
};
|
|
128
131
|
upsertAttachments([failedAttachment]);
|
|
129
132
|
if (errorHandler) {
|
|
130
|
-
errorHandler(finalError, 'upload-attachment', {
|
|
133
|
+
errorHandler(finalError, 'upload-attachment', {
|
|
134
|
+
...file,
|
|
135
|
+
id: localMetadata.id,
|
|
136
|
+
});
|
|
131
137
|
}
|
|
132
138
|
return failedAttachment;
|
|
133
139
|
}
|
|
@@ -173,7 +179,9 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
173
179
|
upsertAttachments,
|
|
174
180
|
]);
|
|
175
181
|
const uploadNewFiles = useCallback((files) => {
|
|
176
|
-
const filesToBeUploaded = noFiles
|
|
182
|
+
const filesToBeUploaded = noFiles
|
|
183
|
+
? Array.from(files).filter(isImageFile)
|
|
184
|
+
: Array.from(files);
|
|
177
185
|
filesToBeUploaded.slice(0, maxFilesLeft).forEach((fileLike) => {
|
|
178
186
|
uploadAttachment({
|
|
179
187
|
localMetadata: {
|
|
@@ -102,6 +102,8 @@ export const useLinkPreviews = ({ debounceURLEnrichmentMs: debounceURLEnrichment
|
|
|
102
102
|
return {
|
|
103
103
|
cancelURLEnrichment,
|
|
104
104
|
dismissLinkPreview,
|
|
105
|
-
findAndEnqueueURLsToEnrich: channelConfig?.url_enrichment && enrichURLForPreview
|
|
105
|
+
findAndEnqueueURLsToEnrich: channelConfig?.url_enrichment && enrichURLForPreview
|
|
106
|
+
? findAndEnqueueURLsToEnrich
|
|
107
|
+
: undefined,
|
|
106
108
|
};
|
|
107
109
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useCallback, useReducer, useState } from 'react';
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
|
-
import { useChannelStateContext } from '../../../context/ChannelStateContext';
|
|
3
|
+
import { useChannelStateContext, } from '../../../context/ChannelStateContext';
|
|
4
4
|
import { useAttachments } from './useAttachments';
|
|
5
5
|
import { useLinkPreviews } from './useLinkPreviews';
|
|
6
6
|
import { useMessageInputText } from './useMessageInputText';
|
|
7
7
|
import { useSubmitHandler } from './useSubmitHandler';
|
|
8
8
|
import { usePasteHandler } from './usePasteHandler';
|
|
9
|
-
import { useMediaRecorder } from '../../MediaRecorder/hooks/useMediaRecorder';
|
|
9
|
+
import { useMediaRecorder, } from '../../MediaRecorder/hooks/useMediaRecorder';
|
|
10
10
|
import { LinkPreviewState, SetLinkPreviewMode } from '../types';
|
|
11
11
|
import { mergeDeep } from '../../../utils/mergeDeep';
|
|
12
12
|
const makeEmptyMessageInputState = () => ({
|
|
@@ -59,7 +59,8 @@ const messageInputReducer = (state, action) => {
|
|
|
59
59
|
case 'upsertAttachments': {
|
|
60
60
|
const attachments = [...state.attachments];
|
|
61
61
|
action.attachments.forEach((actionAttachment) => {
|
|
62
|
-
const attachmentIndex = state.attachments.findIndex((att) => att.localMetadata?.id &&
|
|
62
|
+
const attachmentIndex = state.attachments.findIndex((att) => att.localMetadata?.id &&
|
|
63
|
+
att.localMetadata?.id === actionAttachment.localMetadata?.id);
|
|
63
64
|
if (attachmentIndex === -1) {
|
|
64
65
|
attachments.push(actionAttachment);
|
|
65
66
|
}
|
|
@@ -6,5 +6,5 @@ import type { EnrichURLsController } from './useLinkPreviews';
|
|
|
6
6
|
export declare const useMessageInputText: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, V extends CustomTrigger = CustomTrigger>(props: MessageInputProps<StreamChatGenerics, V>, state: MessageInputState<StreamChatGenerics>, dispatch: React.Dispatch<MessageInputReducerAction<StreamChatGenerics>>, findAndEnqueueURLsToEnrich?: EnrichURLsController['findAndEnqueueURLsToEnrich']) => {
|
|
7
7
|
handleChange: import("react").ChangeEventHandler<HTMLTextAreaElement>;
|
|
8
8
|
insertText: (textToInsert: string) => void;
|
|
9
|
-
textareaRef: import("react").
|
|
9
|
+
textareaRef: import("react").RefObject<HTMLTextAreaElement | undefined>;
|
|
10
10
|
};
|
|
@@ -5,7 +5,7 @@ export const useMessageInputText = (props, state, dispatch, findAndEnqueueURLsTo
|
|
|
5
5
|
const { channel } = useChannelStateContext('useMessageInputText');
|
|
6
6
|
const { additionalTextareaProps, focus, parent, publishTypingEvent = true } = props;
|
|
7
7
|
const { text } = state;
|
|
8
|
-
const textareaRef = useRef();
|
|
8
|
+
const textareaRef = useRef(undefined);
|
|
9
9
|
// Focus
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
if (focus && textareaRef.current) {
|
|
@@ -13,7 +13,7 @@ export const useMessageInputText = (props, state, dispatch, findAndEnqueueURLsTo
|
|
|
13
13
|
}
|
|
14
14
|
}, [focus]);
|
|
15
15
|
// Text + cursor position
|
|
16
|
-
const newCursorPosition = useRef();
|
|
16
|
+
const newCursorPosition = useRef(undefined);
|
|
17
17
|
const insertText = useCallback((textToInsert) => {
|
|
18
18
|
const { maxLength } = additionalTextareaProps || {};
|
|
19
19
|
if (!textareaRef.current) {
|
|
@@ -32,7 +32,9 @@ export const useMessageInputText = (props, state, dispatch, findAndEnqueueURLsTo
|
|
|
32
32
|
newCursorPosition.current = selectionStart + textToInsert.length;
|
|
33
33
|
dispatch({
|
|
34
34
|
getNewText: (prevText) => {
|
|
35
|
-
const updatedText = prevText.slice(0, selectionStart) +
|
|
35
|
+
const updatedText = prevText.slice(0, selectionStart) +
|
|
36
|
+
textToInsert +
|
|
37
|
+
prevText.slice(selectionEnd);
|
|
36
38
|
if (maxLength && updatedText.length > maxLength) {
|
|
37
39
|
return updatedText.slice(0, maxLength);
|
|
38
40
|
}
|
|
@@ -4,7 +4,7 @@ import { useChannelStateContext } from '../../../context/ChannelStateContext';
|
|
|
4
4
|
import { useTranslationContext } from '../../../context/TranslationContext';
|
|
5
5
|
import { LinkPreviewState } from '../types';
|
|
6
6
|
export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrichURLsController) => {
|
|
7
|
-
const { clearEditingState, message, overrideSubmitHandler, parent, publishTypingEvent } = props;
|
|
7
|
+
const { clearEditingState, message, overrideSubmitHandler, parent, publishTypingEvent, } = props;
|
|
8
8
|
const { attachments, linkPreviews, mentioned_users, text } = state;
|
|
9
9
|
const { cancelURLEnrichment, findAndEnqueueURLsToEnrich } = enrichURLsController;
|
|
10
10
|
const { channel } = useChannelStateContext('useSubmitHandler');
|
|
@@ -42,6 +42,7 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
42
42
|
.filter((att) => att.localMetadata?.uploadState !== 'failed' ||
|
|
43
43
|
(findAndEnqueueURLsToEnrich && !att.og_scrape_url))
|
|
44
44
|
.map((localAttachment) => {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
46
|
const { localMetadata: _, ...attachment } = localAttachment;
|
|
46
47
|
return attachment;
|
|
47
48
|
});
|
|
@@ -57,8 +58,9 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
57
58
|
: Array.from(linkPreviews.values())
|
|
58
59
|
.filter((linkPreview) => linkPreview.state === LinkPreviewState.LOADED &&
|
|
59
60
|
!attachmentsFromUploads.find((attFromUpload) => attFromUpload.og_scrape_url === linkPreview.og_scrape_url))
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
.map(
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
63
|
+
({ state: linkPreviewState, ...ogAttachment }) => ogAttachment);
|
|
62
64
|
// scraped attachments are added only if all enrich queries has completed. Otherwise, the scraping has to be done server-side.
|
|
63
65
|
sendOptions.skip_enrich_url =
|
|
64
66
|
(!someLinkPreviewsLoading && attachmentsFromLinkPreviews.length > 0) ||
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
// todo: provide start timestamp
|
|
3
3
|
export const useTimeElapsed = ({ startOnMount } = {}) => {
|
|
4
4
|
const [secondsElapsed, setSecondsElapsed] = useState(0);
|
|
5
|
-
const updateInterval = useRef();
|
|
5
|
+
const updateInterval = useRef(undefined);
|
|
6
6
|
const startCounter = useCallback(() => {
|
|
7
7
|
if (updateInterval.current)
|
|
8
8
|
return;
|
|
@@ -27,7 +27,7 @@ export const useUserTrigger = (params) => {
|
|
|
27
27
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
28
28
|
const queryMembersThrottled = useCallback(throttle(async (query, onReady) => {
|
|
29
29
|
try {
|
|
30
|
-
// @ts-expect-error
|
|
30
|
+
// @ts-expect-error valid query
|
|
31
31
|
const response = await channel.queryMembers({
|
|
32
32
|
name: { $autocomplete: query },
|
|
33
33
|
});
|
|
@@ -49,7 +49,7 @@ export const useUserTrigger = (params) => {
|
|
|
49
49
|
setSearching(true);
|
|
50
50
|
try {
|
|
51
51
|
const { users } = await client.queryUsers(
|
|
52
|
-
// @ts-expect-error
|
|
52
|
+
// @ts-expect-error valid query
|
|
53
53
|
{
|
|
54
54
|
$or: [{ id: { $autocomplete: query } }, { name: { $autocomplete: query } }],
|
|
55
55
|
...(typeof mentionQueryParams.filters === 'function'
|
|
@@ -53,6 +53,7 @@ export const searchLocalUsers = (params) => {
|
|
|
53
53
|
let updatedQuery = removeDiacritics(query).toLowerCase();
|
|
54
54
|
if (useMentionsTransliteration) {
|
|
55
55
|
(async () => {
|
|
56
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
56
57
|
const { default: transliterate } = await import('@stream-io/transliterate');
|
|
57
58
|
updatedName = transliterate(user.name || '').toLowerCase();
|
|
58
59
|
updatedQuery = transliterate(query).toLowerCase();
|
|
@@ -63,7 +64,8 @@ export const searchLocalUsers = (params) => {
|
|
|
63
64
|
const lastDigits = text.slice(-(maxDistance + 1)).includes('@');
|
|
64
65
|
if (updatedName) {
|
|
65
66
|
const levenshtein = calculateLevenshtein(updatedQuery, updatedName);
|
|
66
|
-
if (updatedName.includes(updatedQuery) ||
|
|
67
|
+
if (updatedName.includes(updatedQuery) ||
|
|
68
|
+
(levenshtein <= maxDistance && lastDigits)) {
|
|
67
69
|
return true;
|
|
68
70
|
}
|
|
69
71
|
}
|
|
@@ -79,7 +81,9 @@ export const checkUploadPermissions = async (params) => {
|
|
|
79
81
|
const { allowed_file_extensions, allowed_mime_types, blocked_file_extensions, blocked_mime_types, size_limit, } = (uploadType === 'image'
|
|
80
82
|
? appSettings?.app?.image_upload_config
|
|
81
83
|
: appSettings?.app?.file_upload_config) || {};
|
|
82
|
-
const sendNotAllowedErrorNotification = () => addNotification(t(`Upload type: "{{ type }}" is not allowed`, {
|
|
84
|
+
const sendNotAllowedErrorNotification = () => addNotification(t(`Upload type: "{{ type }}" is not allowed`, {
|
|
85
|
+
type: file.type || 'unknown type',
|
|
86
|
+
}), 'error');
|
|
83
87
|
if (allowed_file_extensions?.length) {
|
|
84
88
|
const allowed = allowed_file_extensions.some((ext) => file.name.toLowerCase().endsWith(ext.toLowerCase()));
|
|
85
89
|
if (!allowed) {
|
|
@@ -6,7 +6,7 @@ const UnMemoizedConnectionStatus = () => {
|
|
|
6
6
|
const { t } = useTranslationContext('ConnectionStatus');
|
|
7
7
|
const [online, setOnline] = useState(true);
|
|
8
8
|
useEffect(() => {
|
|
9
|
-
const connectionChanged = ({ online: onlineStatus = false }) => {
|
|
9
|
+
const connectionChanged = ({ online: onlineStatus = false, }) => {
|
|
10
10
|
if (online !== onlineStatus) {
|
|
11
11
|
setOnline(onlineStatus);
|
|
12
12
|
}
|
|
@@ -12,7 +12,7 @@ import { useChatContext } from '../../context/ChatContext';
|
|
|
12
12
|
import { useComponentContext } from '../../context/ComponentContext';
|
|
13
13
|
import { MessageListContextProvider } from '../../context/MessageListContext';
|
|
14
14
|
import { EmptyStateIndicator as DefaultEmptyStateIndicator } from '../EmptyStateIndicator';
|
|
15
|
-
import { InfiniteScroll } from '../InfiniteScrollPaginator/InfiniteScroll';
|
|
15
|
+
import { InfiniteScroll, } from '../InfiniteScrollPaginator/InfiniteScroll';
|
|
16
16
|
import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
|
|
17
17
|
import { defaultPinPermissions, MESSAGE_ACTIONS } from '../Message/utils';
|
|
18
18
|
import { TypingIndicator as DefaultTypingIndicator } from '../TypingIndicator';
|
|
@@ -20,12 +20,12 @@ import { MessageListMainPanel as DefaultMessageListMainPanel } from './MessageLi
|
|
|
20
20
|
import { defaultRenderMessages } from './renderMessages';
|
|
21
21
|
import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, } from '../../constants/limits';
|
|
22
22
|
const MessageListWithContext = (props) => {
|
|
23
|
-
const { channel, channelUnreadUiState, disableDateSeparator = false, groupStyles, hideDeletedMessages = false, hideNewMessageSeparator = false, internalInfiniteScrollProps: { threshold: loadMoreScrollThreshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, ...restInternalInfiniteScrollProps } = {},
|
|
24
|
-
|
|
23
|
+
const { channel, channelUnreadUiState, disableDateSeparator = false, groupStyles, hasMoreNewer = false, headerPosition, hideDeletedMessages = false, hideNewMessageSeparator = false, highlightedMessageId, internalInfiniteScrollProps: { threshold: loadMoreScrollThreshold = DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, ...restInternalInfiniteScrollProps } = {}, jumpToLatestMessage = () => Promise.resolve(), loadMore: loadMoreCallback, loadMoreNewer: loadMoreNewerCallback, // @deprecated in favor of `channelCapabilities` - TODO: remove in next major release
|
|
24
|
+
maxTimeBetweenGroupedMessages, messageActions = Object.keys(MESSAGE_ACTIONS), messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE, messages = [], noGroupByUser = false, notifications, pinPermissions = defaultPinPermissions, reactionDetailsSort, read, renderMessages = defaultRenderMessages, returnAllReadData = false, reviewProcessedMessage, showUnreadNotificationAlways, sortReactionDetails, sortReactions, suppressAutoscroll, threadList = false, unsafeHTML = false, } = props;
|
|
25
25
|
const [listElement, setListElement] = React.useState(null);
|
|
26
26
|
const [ulElement, setUlElement] = React.useState(null);
|
|
27
27
|
const { customClasses } = useChatContext('MessageList');
|
|
28
|
-
const { EmptyStateIndicator = DefaultEmptyStateIndicator, LoadingIndicator = DefaultLoadingIndicator, MessageListNotifications = DefaultMessageListNotifications, MessageNotification = DefaultMessageNotification, TypingIndicator = DefaultTypingIndicator, UnreadMessagesNotification = DefaultUnreadMessagesNotification,
|
|
28
|
+
const { EmptyStateIndicator = DefaultEmptyStateIndicator, LoadingIndicator = DefaultLoadingIndicator, MessageListMainPanel = DefaultMessageListMainPanel, MessageListNotifications = DefaultMessageListNotifications, MessageNotification = DefaultMessageNotification, TypingIndicator = DefaultTypingIndicator, UnreadMessagesNotification = DefaultUnreadMessagesNotification, } = useComponentContext('MessageList');
|
|
29
29
|
const { hasNewMessages, isMessageListScrolledToBottom, onScroll, scrollToBottom, wrapperRect, } = useScrollLocationLogic({
|
|
30
30
|
hasMoreNewer,
|
|
31
31
|
listElement,
|
|
@@ -42,7 +42,6 @@ const MessageListWithContext = (props) => {
|
|
|
42
42
|
useMarkRead({
|
|
43
43
|
isMessageListScrolledToBottom,
|
|
44
44
|
messageListIsThread: threadList,
|
|
45
|
-
unreadCount: channelUnreadUiState?.unread_messages ?? 0,
|
|
46
45
|
wasMarkedUnread: !!channelUnreadUiState?.first_unread_message_id,
|
|
47
46
|
});
|
|
48
47
|
const { messageGroupStyles, messages: enrichedMessages } = useEnrichedMessages({
|
|
@@ -147,7 +146,7 @@ const MessageListWithContext = (props) => {
|
|
|
147
146
|
* - [TypingContext](https://getstream.io/chat/docs/sdk/react/contexts/typing_context/)
|
|
148
147
|
*/
|
|
149
148
|
export const MessageList = (props) => {
|
|
150
|
-
const { jumpToLatestMessage, loadMore, loadMoreNewer
|
|
149
|
+
const { jumpToLatestMessage, loadMore, loadMoreNewer } = useChannelActionContext('MessageList');
|
|
151
150
|
const { members: membersPropToNotPass, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
152
151
|
mutes: mutesPropToNotPass, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
153
152
|
watchers: watchersPropToNotPass, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
@@ -8,5 +8,7 @@ export const MessageListNotifications = (props) => {
|
|
|
8
8
|
return (React.createElement("div", { className: 'str-chat__list-notifications' },
|
|
9
9
|
notifications.map((notification) => (React.createElement(CustomNotification, { active: true, key: notification.id, type: notification.type }, notification.text))),
|
|
10
10
|
React.createElement(ConnectionStatus, null),
|
|
11
|
-
React.createElement(MessageNotification, { isMessageListScrolledToBottom: isMessageListScrolledToBottom, onClick: scrollToBottom, showNotification: hasNewMessages || isNotAtLatestMessageSet, threadList: threadList, unreadCount: unreadCount }, isNotAtLatestMessageSet
|
|
11
|
+
React.createElement(MessageNotification, { isMessageListScrolledToBottom: isMessageListScrolledToBottom, onClick: scrollToBottom, showNotification: hasNewMessages || isNotAtLatestMessageSet, threadList: threadList, unreadCount: unreadCount }, isNotAtLatestMessageSet
|
|
12
|
+
? t('Latest Messages')
|
|
13
|
+
: t('New Messages!'))));
|
|
12
14
|
};
|
|
@@ -25,7 +25,7 @@ export type VirtuosoContext<StreamChatGenerics extends DefaultStreamChatGenerics
|
|
|
25
25
|
/** The original message list enriched with date separators, omitted deleted messages or giphy previews. */
|
|
26
26
|
processedMessages: StreamMessage<StreamChatGenerics>[];
|
|
27
27
|
/** Instance of VirtuosoHandle object providing the API to navigate in the virtualized list by various scroll actions. */
|
|
28
|
-
virtuosoRef: RefObject<VirtuosoHandle>;
|
|
28
|
+
virtuosoRef: RefObject<VirtuosoHandle | null>;
|
|
29
29
|
/** Message id which was marked as unread. ALl the messages following this message are considered unrea. */
|
|
30
30
|
firstUnreadMessageId?: string;
|
|
31
31
|
lastReadDate?: Date;
|
|
@@ -18,7 +18,8 @@ import { DialogManagerProvider } from '../../context';
|
|
|
18
18
|
import { useChannelActionContext, } from '../../context/ChannelActionContext';
|
|
19
19
|
import { useChannelStateContext, } from '../../context/ChannelStateContext';
|
|
20
20
|
import { useChatContext } from '../../context/ChatContext';
|
|
21
|
-
import { useComponentContext } from '../../context/ComponentContext';
|
|
21
|
+
import { useComponentContext, } from '../../context/ComponentContext';
|
|
22
|
+
import { VirtualizedMessageListContextProvider } from '../../context/VirtualizedMessageListContext';
|
|
22
23
|
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
|
|
23
24
|
function captureResizeObserverExceededError(e) {
|
|
24
25
|
if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
|
|
@@ -52,17 +53,17 @@ function calculateInitialTopMostItemIndex(messages, highlightedMessageId) {
|
|
|
52
53
|
const VirtualizedMessageListWithContext = (props) => {
|
|
53
54
|
const { additionalMessageInputProps, additionalVirtuosoProps = {}, channel, channelUnreadUiState, closeReactionSelectorOnClick, customMessageActions, customMessageRenderer, defaultItemHeight, disableDateSeparator = true, formatDate, groupStyles, hasMoreNewer, head, hideDeletedMessages = false, hideNewMessageSeparator = false, highlightedMessageId, jumpToLatestMessage, loadingMore, loadMore, loadMoreNewer, maxTimeBetweenGroupedMessages, Message: MessageUIComponentFromProps, messageActions, messageLimit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE, messages, notifications, openThread,
|
|
54
55
|
// TODO: refactor to scrollSeekPlaceHolderConfiguration and components.ScrollSeekPlaceholder, like the Virtuoso Component
|
|
55
|
-
overscan = 0, read, returnAllReadData = false, reviewProcessedMessage, scrollSeekPlaceHolder, scrollToLatestMessageOnFocus = false, separateGiphyPreview = false, shouldGroupByUser = false, showUnreadNotificationAlways,
|
|
56
|
+
overscan = 0, reactionDetailsSort, read, returnAllReadData = false, reviewProcessedMessage, scrollSeekPlaceHolder, scrollToLatestMessageOnFocus = false, separateGiphyPreview = false, shouldGroupByUser = false, showUnreadNotificationAlways, sortReactionDetails, sortReactions, stickToBottomScrollBehavior = 'smooth', suppressAutoscroll, threadList, } = props;
|
|
56
57
|
const { components: virtuosoComponentsFromProps, ...overridingVirtuosoProps } = additionalVirtuosoProps;
|
|
57
58
|
// Stops errors generated from react-virtuoso to bubble up
|
|
58
59
|
// to Sentry or other tracking tools.
|
|
59
60
|
useCaptureResizeObserverExceededError();
|
|
60
|
-
const { DateSeparator = DefaultDateSeparator, GiphyPreviewMessage = DefaultGiphyPreviewMessage, MessageListNotifications = DefaultMessageListNotifications, MessageNotification = DefaultMessageNotification, MessageSystem = DefaultMessageSystem,
|
|
61
|
+
const { DateSeparator = DefaultDateSeparator, GiphyPreviewMessage = DefaultGiphyPreviewMessage, MessageListMainPanel = DefaultMessageListMainPanel, MessageListNotifications = DefaultMessageListNotifications, MessageNotification = DefaultMessageNotification, MessageSystem = DefaultMessageSystem, TypingIndicator, UnreadMessagesNotification = DefaultUnreadMessagesNotification, UnreadMessagesSeparator = DefaultUnreadMessagesSeparator, VirtualMessage: MessageUIComponentFromContext = MessageSimple, } = useComponentContext('VirtualizedMessageList');
|
|
61
62
|
const MessageUIComponent = MessageUIComponentFromProps || MessageUIComponentFromContext;
|
|
62
63
|
const { client, customClasses } = useChatContext('VirtualizedMessageList');
|
|
63
64
|
const virtuoso = useRef(null);
|
|
64
65
|
const lastRead = useMemo(() => channel.lastRead?.(), [channel]);
|
|
65
|
-
const { show: showUnreadMessagesNotification, toggleShowUnreadMessagesNotification
|
|
66
|
+
const { show: showUnreadMessagesNotification, toggleShowUnreadMessagesNotification } = useUnreadMessagesNotificationVirtualized({
|
|
66
67
|
lastRead: channelUnreadUiState?.last_read,
|
|
67
68
|
showAlways: !!showUnreadNotificationAlways,
|
|
68
69
|
unreadCount: channelUnreadUiState?.unread_messages ?? 0,
|
|
@@ -105,9 +106,7 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
105
106
|
returnAllReadData,
|
|
106
107
|
userID: client.userID,
|
|
107
108
|
});
|
|
108
|
-
const lastReceivedMessageId = useMemo(() => getLastReceived(processedMessages), [
|
|
109
|
-
processedMessages,
|
|
110
|
-
]);
|
|
109
|
+
const lastReceivedMessageId = useMemo(() => getLastReceived(processedMessages), [processedMessages]);
|
|
111
110
|
const groupStylesFn = groupStyles || getGroupStyles;
|
|
112
111
|
const messageGroupStyles = useMemo(() => processedMessages.reduce((acc, message, i) => {
|
|
113
112
|
const style = groupStylesFn(message, processedMessages[i - 1], processedMessages[i + 1], !shouldGroupByUser, maxTimeBetweenGroupedMessages);
|
|
@@ -117,12 +116,16 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
117
116
|
}, {}),
|
|
118
117
|
// processedMessages were incorrectly rebuilt with a new object identity at some point, hence the .length usage
|
|
119
118
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
120
|
-
[
|
|
119
|
+
[
|
|
120
|
+
maxTimeBetweenGroupedMessages,
|
|
121
|
+
processedMessages.length,
|
|
122
|
+
shouldGroupByUser,
|
|
123
|
+
groupStylesFn,
|
|
124
|
+
]);
|
|
121
125
|
const { atBottom, isMessageListScrolledToBottom, newMessagesNotification, setIsMessageListScrolledToBottom, setNewMessagesNotification, } = useNewMessageNotification(processedMessages, client.userID, hasMoreNewer);
|
|
122
126
|
useMarkRead({
|
|
123
127
|
isMessageListScrolledToBottom,
|
|
124
128
|
messageListIsThread: !!threadList,
|
|
125
|
-
unreadCount: channelUnreadUiState?.unread_messages ?? 0,
|
|
126
129
|
wasMarkedUnread: !!channelUnreadUiState?.first_unread_message_id,
|
|
127
130
|
});
|
|
128
131
|
const scrollToBottom = useCallback(async () => {
|
|
@@ -144,7 +147,11 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
144
147
|
hasMoreNewer,
|
|
145
148
|
jumpToLatestMessage,
|
|
146
149
|
]);
|
|
147
|
-
useScrollToBottomOnNewMessage({
|
|
150
|
+
useScrollToBottomOnNewMessage({
|
|
151
|
+
messages,
|
|
152
|
+
scrollToBottom,
|
|
153
|
+
scrollToLatestMessageOnFocus,
|
|
154
|
+
});
|
|
148
155
|
const numItemsPrepended = usePrependedMessagesCount(processedMessages, !disableDateSeparator);
|
|
149
156
|
const { messageSetKey } = useMessageSetKey({ messages });
|
|
150
157
|
const shouldForceScrollToBottom = useShouldForceScrollToBottom(processedMessages, client.userID);
|
|
@@ -192,7 +199,7 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
192
199
|
const dialogManagerId = threadList
|
|
193
200
|
? 'virtualized-message-list-dialog-manager-thread'
|
|
194
201
|
: 'virtualized-message-list-dialog-manager';
|
|
195
|
-
return (React.createElement(
|
|
202
|
+
return (React.createElement(VirtualizedMessageListContextProvider, { value: { scrollToBottom } },
|
|
196
203
|
React.createElement(MessageListMainPanel, null,
|
|
197
204
|
React.createElement(DialogManagerProvider, { id: dialogManagerId },
|
|
198
205
|
!threadList && showUnreadMessagesNotification && (React.createElement(UnreadMessagesNotification, { unreadCount: channelUnreadUiState?.unread_messages })),
|
|
@@ -242,7 +249,7 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
242
249
|
* It is a consumer of the React contexts set in [Channel](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Channel/Channel.tsx).
|
|
243
250
|
*/
|
|
244
251
|
export function VirtualizedMessageList(props) {
|
|
245
|
-
const { jumpToLatestMessage, loadMore, loadMoreNewer
|
|
252
|
+
const { jumpToLatestMessage, loadMore, loadMoreNewer } = useChannelActionContext('VirtualizedMessageList');
|
|
246
253
|
const { channel, channelUnreadUiState, hasMore, hasMoreNewer, highlightedMessageId, loadingMore, loadingMoreNewer, messages: contextMessages, notifications, read, suppressAutoscroll, } = useChannelStateContext('VirtualizedMessageList');
|
|
247
254
|
const messages = props.messages || contextMessages;
|
|
248
255
|
return (React.createElement(VirtualizedMessageListWithContext, { channel: channel, channelUnreadUiState: props.channelUnreadUiState ?? channelUnreadUiState, hasMore: !!hasMore, hasMoreNewer: !!hasMoreNewer, highlightedMessageId: highlightedMessageId, jumpToLatestMessage: jumpToLatestMessage, loadingMore: !!loadingMore, loadingMoreNewer: !!loadingMoreNewer, loadMore: loadMore, loadMoreNewer: loadMoreNewer, messages: messages, notifications: notifications, read: read, suppressAutoscroll: suppressAutoscroll, ...props }));
|
|
@@ -43,7 +43,7 @@ export const Header = ({ context, }) => {
|
|
|
43
43
|
React.createElement(LoadingIndicator, { size: 20 })))));
|
|
44
44
|
};
|
|
45
45
|
export const EmptyPlaceholder = ({ context, }) => {
|
|
46
|
-
const { EmptyStateIndicator = DefaultEmptyStateIndicator
|
|
46
|
+
const { EmptyStateIndicator = DefaultEmptyStateIndicator } = useComponentContext('VirtualizedMessageList');
|
|
47
47
|
return (React.createElement(React.Fragment, null, EmptyStateIndicator && (React.createElement(EmptyStateIndicator, { listType: context?.threadList ? 'thread' : 'message' }))));
|
|
48
48
|
};
|
|
49
49
|
export const messageRenderer = (virtuosoIndex, _data, virtuosoContext) => {
|
|
@@ -56,7 +56,7 @@ export const messageRenderer = (virtuosoIndex, _data, virtuosoContext) => {
|
|
|
56
56
|
if (!message)
|
|
57
57
|
return React.createElement("div", { style: { height: '1px' } }); // returning null or zero height breaks the virtuoso
|
|
58
58
|
if (isDateSeparatorMessage(message)) {
|
|
59
|
-
return DateSeparator ? React.createElement(DateSeparator, { date: message.date, unread: message.unread }) : null;
|
|
59
|
+
return DateSeparator ? (React.createElement(DateSeparator, { date: message.date, unread: message.unread })) : null;
|
|
60
60
|
}
|
|
61
61
|
if (message.type === 'system') {
|
|
62
62
|
return MessageSystem ? React.createElement(MessageSystem, { message: message }) : null;
|
|
@@ -14,9 +14,7 @@ export const useMessageListElements = (props) => {
|
|
|
14
14
|
returnAllReadData,
|
|
15
15
|
userID: client.userID,
|
|
16
16
|
});
|
|
17
|
-
const lastReceivedMessageId = useMemo(() => getLastReceived(enrichedMessages), [
|
|
18
|
-
enrichedMessages,
|
|
19
|
-
]);
|
|
17
|
+
const lastReceivedMessageId = useMemo(() => getLastReceived(enrichedMessages), [enrichedMessages]);
|
|
20
18
|
const elements = useMemo(() => renderMessages({
|
|
21
19
|
channelUnreadUiState,
|
|
22
20
|
components,
|
|
@@ -8,7 +8,7 @@ export function useMessageListScrollManager(params) {
|
|
|
8
8
|
offsetHeight: 0,
|
|
9
9
|
scrollHeight: 0,
|
|
10
10
|
});
|
|
11
|
-
const messages = useRef();
|
|
11
|
+
const messages = useRef(undefined);
|
|
12
12
|
const scrollTop = useRef(0);
|
|
13
13
|
useLayoutEffect(() => {
|
|
14
14
|
const prevMeasures = measures.current;
|
|
@@ -41,7 +41,8 @@ export function useMessageListScrollManager(params) {
|
|
|
41
41
|
}
|
|
42
42
|
// message list length didn't change, but check if last message had reaction/reply update
|
|
43
43
|
else {
|
|
44
|
-
const hasNewReactions = lastPrevMessage?.latest_reactions?.length !==
|
|
44
|
+
const hasNewReactions = lastPrevMessage?.latest_reactions?.length !==
|
|
45
|
+
lastNewMessage.latest_reactions?.length;
|
|
45
46
|
const hasNewReplies = lastPrevMessage?.reply_count !== lastNewMessage.reply_count;
|
|
46
47
|
if ((hasNewReactions || hasNewReplies) && wasAtBottom) {
|
|
47
48
|
scrollToBottom();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useMessageListScrollManager } from './useMessageListScrollManager';
|
|
3
3
|
export const useScrollLocationLogic = (params) => {
|
|
4
|
-
const { loadMoreScrollThreshold, messages = [], scrolledUpThreshold = 200,
|
|
4
|
+
const { hasMoreNewer, listElement, loadMoreScrollThreshold, messages = [], scrolledUpThreshold = 200, suppressAutoscroll, } = params;
|
|
5
5
|
const [hasNewMessages, setHasNewMessages] = useState(false);
|
|
6
6
|
const [wrapperRect, setWrapperRect] = useState();
|
|
7
7
|
const [isMessageListScrolledToBottom, setIsMessageListScrolledToBottom] = useState(true);
|
|
@@ -44,7 +44,8 @@ export const useScrollLocationLogic = (params) => {
|
|
|
44
44
|
const offsetHeight = element.offsetHeight;
|
|
45
45
|
const scrollHeight = element.scrollHeight;
|
|
46
46
|
const prevCloseToBottom = closeToBottom.current;
|
|
47
|
-
closeToBottom.current =
|
|
47
|
+
closeToBottom.current =
|
|
48
|
+
scrollHeight - (scrollTop + offsetHeight) < scrolledUpThreshold;
|
|
48
49
|
closeToTop.current = scrollTop < scrolledUpThreshold;
|
|
49
50
|
if (closeToBottom.current) {
|
|
50
51
|
setHasNewMessages(false);
|
|
@@ -31,7 +31,9 @@ export const useUnreadMessagesNotification = ({ isMessageListScrolledToBottom, s
|
|
|
31
31
|
}
|
|
32
32
|
const scrolledBelowSeparator = targetScrolledAboveVisibleContainerArea(observedTarget);
|
|
33
33
|
const scrolledAboveSeparator = targetScrolledBelowVisibleContainerArea(observedTarget, msgListPanel);
|
|
34
|
-
setShow(showAlways
|
|
34
|
+
setShow(showAlways
|
|
35
|
+
? scrolledBelowSeparator || scrolledAboveSeparator
|
|
36
|
+
: scrolledBelowSeparator);
|
|
35
37
|
const observer = new IntersectionObserver((elements) => {
|
|
36
38
|
if (!elements.length)
|
|
37
39
|
return;
|
|
@@ -61,7 +63,9 @@ export const useUnreadMessagesNotification = ({ isMessageListScrolledToBottom, s
|
|
|
61
63
|
* The intersection observer is not triggered when Element.scrollTo() is called. So we end up in a situation when we are scrolled to the bottom
|
|
62
64
|
* and at the same time scrolled above the observed target.
|
|
63
65
|
*/
|
|
64
|
-
if (unreadCount &&
|
|
66
|
+
if (unreadCount &&
|
|
67
|
+
isMessageListScrolledToBottom &&
|
|
68
|
+
isScrolledAboveTargetTop.current) {
|
|
65
69
|
setShow(true);
|
|
66
70
|
isScrolledAboveTargetTop.current = false;
|
|
67
71
|
}
|
|
@@ -4,7 +4,7 @@ export const useMessageSetKey = ({ messages, }) => {
|
|
|
4
4
|
* Logic to update the key of the virtuoso component when the list jumps to a new location.
|
|
5
5
|
*/
|
|
6
6
|
const [messageSetKey, setMessageSetKey] = useState(+new Date());
|
|
7
|
-
const firstMessageId = useRef();
|
|
7
|
+
const firstMessageId = useRef(undefined);
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
const continuousSet = messages?.find((message) => message.id === firstMessageId.current);
|
|
10
10
|
if (!continuousSet) {
|
package/dist/components/MessageList/hooks/VirtualizedMessageList/useNewMessageNotification.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { StreamMessage } from '../../../../context/ChannelStateContext';
|
|
3
3
|
import type { DefaultStreamChatGenerics } from '../../../../types/types';
|
|
4
4
|
export declare function useNewMessageNotification<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(messages: StreamMessage<StreamChatGenerics>[], currentUserId: string | undefined, hasMoreNewer?: boolean): {
|
|
5
|
-
atBottom: import("react").
|
|
5
|
+
atBottom: import("react").RefObject<boolean>;
|
|
6
6
|
isMessageListScrolledToBottom: boolean;
|
|
7
7
|
newMessagesNotification: boolean;
|
|
8
8
|
setIsMessageListScrolledToBottom: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|