stream-chat-react 12.10.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/hooks/useAudioController.js +3 -1
- 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 -3
- package/dist/components/AutoCompleteTextarea/utils.js +2 -2
- package/dist/components/Avatar/ChannelAvatar.js +1 -1
- package/dist/components/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +27 -12
- package/dist/components/Channel/channelState.js +9 -3
- package/dist/components/Channel/hooks/useCreateChannelStateContext.js +5 -3
- 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 +44 -17
- package/dist/components/ChannelList/hooks/useChannelUpdatedListener.js +2 -1
- package/dist/components/ChannelList/hooks/useMessageNewListener.js +3 -1
- package/dist/components/ChannelList/hooks/usePaginatedChannels.js +4 -2
- 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/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/Image.js +1 -1
- package/dist/components/Gallery/ModalGallery.js +1 -2
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +5 -2
- 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/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.js +3 -1
- package/dist/components/MessageInput/hooks/useSubmitHandler.js +5 -3
- 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 -5
- package/dist/components/MessageList/MessageListNotifications.js +3 -1
- package/dist/components/MessageList/VirtualizedMessageList.js +17 -10
- 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 +2 -1
- 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/usePrependMessagesCount.js +2 -1
- package/dist/components/MessageList/hooks/VirtualizedMessageList/useUnreadMessagesNotificationVirtualized.js +7 -3
- 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/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 +1771 -981
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +1772 -981
- 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 +21 -33
|
@@ -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) ||
|
|
@@ -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,
|
|
@@ -146,7 +146,7 @@ const MessageListWithContext = (props) => {
|
|
|
146
146
|
* - [TypingContext](https://getstream.io/chat/docs/sdk/react/contexts/typing_context/)
|
|
147
147
|
*/
|
|
148
148
|
export const MessageList = (props) => {
|
|
149
|
-
const { jumpToLatestMessage, loadMore, loadMoreNewer
|
|
149
|
+
const { jumpToLatestMessage, loadMore, loadMoreNewer } = useChannelActionContext('MessageList');
|
|
150
150
|
const { members: membersPropToNotPass, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
151
151
|
mutes: mutesPropToNotPass, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
152
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
|
};
|
|
@@ -18,7 +18,7 @@ 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
22
|
import { VirtualizedMessageListContextProvider } from '../../context/VirtualizedMessageListContext';
|
|
23
23
|
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
|
|
24
24
|
function captureResizeObserverExceededError(e) {
|
|
@@ -53,17 +53,17 @@ function calculateInitialTopMostItemIndex(messages, highlightedMessageId) {
|
|
|
53
53
|
const VirtualizedMessageListWithContext = (props) => {
|
|
54
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,
|
|
55
55
|
// TODO: refactor to scrollSeekPlaceHolderConfiguration and components.ScrollSeekPlaceholder, like the Virtuoso Component
|
|
56
|
-
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;
|
|
57
57
|
const { components: virtuosoComponentsFromProps, ...overridingVirtuosoProps } = additionalVirtuosoProps;
|
|
58
58
|
// Stops errors generated from react-virtuoso to bubble up
|
|
59
59
|
// to Sentry or other tracking tools.
|
|
60
60
|
useCaptureResizeObserverExceededError();
|
|
61
|
-
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');
|
|
62
62
|
const MessageUIComponent = MessageUIComponentFromProps || MessageUIComponentFromContext;
|
|
63
63
|
const { client, customClasses } = useChatContext('VirtualizedMessageList');
|
|
64
64
|
const virtuoso = useRef(null);
|
|
65
65
|
const lastRead = useMemo(() => channel.lastRead?.(), [channel]);
|
|
66
|
-
const { show: showUnreadMessagesNotification, toggleShowUnreadMessagesNotification
|
|
66
|
+
const { show: showUnreadMessagesNotification, toggleShowUnreadMessagesNotification } = useUnreadMessagesNotificationVirtualized({
|
|
67
67
|
lastRead: channelUnreadUiState?.last_read,
|
|
68
68
|
showAlways: !!showUnreadNotificationAlways,
|
|
69
69
|
unreadCount: channelUnreadUiState?.unread_messages ?? 0,
|
|
@@ -106,9 +106,7 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
106
106
|
returnAllReadData,
|
|
107
107
|
userID: client.userID,
|
|
108
108
|
});
|
|
109
|
-
const lastReceivedMessageId = useMemo(() => getLastReceived(processedMessages), [
|
|
110
|
-
processedMessages,
|
|
111
|
-
]);
|
|
109
|
+
const lastReceivedMessageId = useMemo(() => getLastReceived(processedMessages), [processedMessages]);
|
|
112
110
|
const groupStylesFn = groupStyles || getGroupStyles;
|
|
113
111
|
const messageGroupStyles = useMemo(() => processedMessages.reduce((acc, message, i) => {
|
|
114
112
|
const style = groupStylesFn(message, processedMessages[i - 1], processedMessages[i + 1], !shouldGroupByUser, maxTimeBetweenGroupedMessages);
|
|
@@ -118,7 +116,12 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
118
116
|
}, {}),
|
|
119
117
|
// processedMessages were incorrectly rebuilt with a new object identity at some point, hence the .length usage
|
|
120
118
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
-
[
|
|
119
|
+
[
|
|
120
|
+
maxTimeBetweenGroupedMessages,
|
|
121
|
+
processedMessages.length,
|
|
122
|
+
shouldGroupByUser,
|
|
123
|
+
groupStylesFn,
|
|
124
|
+
]);
|
|
122
125
|
const { atBottom, isMessageListScrolledToBottom, newMessagesNotification, setIsMessageListScrolledToBottom, setNewMessagesNotification, } = useNewMessageNotification(processedMessages, client.userID, hasMoreNewer);
|
|
123
126
|
useMarkRead({
|
|
124
127
|
isMessageListScrolledToBottom,
|
|
@@ -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);
|
|
@@ -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,
|
|
@@ -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
|
}
|
|
@@ -26,7 +26,8 @@ export function usePrependedMessagesCount(messages, hasDateSeparator) {
|
|
|
26
26
|
// That in turn leads to incorrect index calculation in VirtualizedMessageList trying to access a message
|
|
27
27
|
// at non-existent index. Therefore, we ignore messages of status "sending" / "failed" in order they are
|
|
28
28
|
// not considered as prepended messages.
|
|
29
|
-
const firstMsgMovedAfterMessagesInExcludedStatus = !!(currentFirstMessage?.status &&
|
|
29
|
+
const firstMsgMovedAfterMessagesInExcludedStatus = !!(currentFirstMessage?.status &&
|
|
30
|
+
STATUSES_EXCLUDED_FROM_PREPEND[currentFirstMessage.status]);
|
|
30
31
|
if (noNewMessages || firstMsgMovedAfterMessagesInExcludedStatus) {
|
|
31
32
|
return previousNumItemsPrepended.current;
|
|
32
33
|
}
|
|
@@ -21,10 +21,14 @@ export const useUnreadMessagesNotificationVirtualized = ({ lastRead, showAlways,
|
|
|
21
21
|
if (!(firstRenderedMessage && lastRenderedMessage))
|
|
22
22
|
return;
|
|
23
23
|
const scrolledBelowSeparator = !!lastRead &&
|
|
24
|
-
new Date(firstRenderedMessage.created_at).getTime() >
|
|
24
|
+
new Date(firstRenderedMessage.created_at).getTime() >
|
|
25
|
+
lastRead.getTime();
|
|
25
26
|
const scrolledAboveSeparator = !!lastRead &&
|
|
26
|
-
new Date(lastRenderedMessage.created_at).getTime() <
|
|
27
|
-
|
|
27
|
+
new Date(lastRenderedMessage.created_at).getTime() <
|
|
28
|
+
lastRead.getTime();
|
|
29
|
+
setShow(showAlways
|
|
30
|
+
? scrolledBelowSeparator || scrolledAboveSeparator
|
|
31
|
+
: scrolledBelowSeparator);
|
|
28
32
|
}, [lastRead, showAlways, unreadCount]);
|
|
29
33
|
useEffect(() => {
|
|
30
34
|
if (!unreadCount)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-continue */
|
|
2
1
|
import { nanoid } from 'nanoid';
|
|
3
2
|
import { CUSTOM_MESSAGE_TYPE } from '../../constants/messageTypes';
|
|
4
3
|
import { isMessageEdited } from '../Message/utils';
|
|
@@ -31,20 +30,29 @@ export const processMessages = (params) => {
|
|
|
31
30
|
if (hideDeletedMessages && message.type === 'deleted') {
|
|
32
31
|
continue;
|
|
33
32
|
}
|
|
34
|
-
if (setGiphyPreviewMessage &&
|
|
33
|
+
if (setGiphyPreviewMessage &&
|
|
34
|
+
message.type === 'ephemeral' &&
|
|
35
|
+
message.command === 'giphy') {
|
|
35
36
|
ephemeralMessagePresent = true;
|
|
36
37
|
setGiphyPreviewMessage(message);
|
|
37
38
|
continue;
|
|
38
39
|
}
|
|
39
40
|
const changes = [];
|
|
40
|
-
const messageDate = (message.created_at &&
|
|
41
|
+
const messageDate = (message.created_at &&
|
|
42
|
+
isDate(message.created_at) &&
|
|
43
|
+
message.created_at.toDateString()) ||
|
|
44
|
+
'';
|
|
41
45
|
const previousMessage = messages[i - 1];
|
|
42
46
|
let prevMessageDate = messageDate;
|
|
43
|
-
if (enableDateSeparator &&
|
|
47
|
+
if (enableDateSeparator &&
|
|
48
|
+
previousMessage?.created_at &&
|
|
49
|
+
isDate(previousMessage.created_at)) {
|
|
44
50
|
prevMessageDate = previousMessage.created_at.toDateString();
|
|
45
51
|
}
|
|
46
52
|
if (!unread && !hideNewMessageSeparator) {
|
|
47
|
-
unread =
|
|
53
|
+
unread =
|
|
54
|
+
(lastRead && message.created_at && new Date(lastRead) < message.created_at) ||
|
|
55
|
+
false;
|
|
48
56
|
// do not show date separator for current user's messages
|
|
49
57
|
if (enableDateSeparator && unread && message.user?.id !== userId) {
|
|
50
58
|
changes.push({
|
|
@@ -155,7 +163,9 @@ export const insertIntro = (messages, headerPosition) => {
|
|
|
155
163
|
// else loop over the messages
|
|
156
164
|
for (let i = 0; i < messages.length; i += 1) {
|
|
157
165
|
const message = messages[i];
|
|
158
|
-
const messageTime = message.created_at && isDate(message.created_at)
|
|
166
|
+
const messageTime = message.created_at && isDate(message.created_at)
|
|
167
|
+
? message.created_at.getTime()
|
|
168
|
+
: null;
|
|
159
169
|
const nextMessage = messages[i + 1];
|
|
160
170
|
const nextMessageTime = nextMessage.created_at && isDate(nextMessage.created_at)
|
|
161
171
|
? nextMessage.created_at.getTime()
|
|
@@ -199,7 +209,8 @@ export const getGroupStyles = (message, previousMessage, nextMessage, noGroupByU
|
|
|
199
209
|
(maxTimeBetweenGroupedMessages !== undefined &&
|
|
200
210
|
previousMessage.created_at &&
|
|
201
211
|
message.created_at &&
|
|
202
|
-
new Date(message.created_at).getTime() -
|
|
212
|
+
new Date(message.created_at).getTime() -
|
|
213
|
+
new Date(previousMessage.created_at).getTime() >
|
|
203
214
|
maxTimeBetweenGroupedMessages);
|
|
204
215
|
const isBottomMessage = !nextMessage ||
|
|
205
216
|
nextMessage.customType === CUSTOM_MESSAGE_TYPE.intro ||
|
|
@@ -209,12 +220,14 @@ export const getGroupStyles = (message, previousMessage, nextMessage, noGroupByU
|
|
|
209
220
|
nextMessage.attachments?.length !== 0 ||
|
|
210
221
|
message.user?.id !== nextMessage.user?.id ||
|
|
211
222
|
nextMessage.deleted_at ||
|
|
212
|
-
(nextMessage.reaction_groups &&
|
|
223
|
+
(nextMessage.reaction_groups &&
|
|
224
|
+
Object.keys(nextMessage.reaction_groups).length > 0) ||
|
|
213
225
|
isMessageEdited(message) ||
|
|
214
226
|
(maxTimeBetweenGroupedMessages !== undefined &&
|
|
215
227
|
nextMessage.created_at &&
|
|
216
228
|
message.created_at &&
|
|
217
|
-
new Date(nextMessage.created_at).getTime() -
|
|
229
|
+
new Date(nextMessage.created_at).getTime() -
|
|
230
|
+
new Date(message.created_at).getTime() >
|
|
218
231
|
maxTimeBetweenGroupedMessages);
|
|
219
232
|
if (!isTopMessage && !isBottomMessage) {
|
|
220
233
|
if (message.deleted_at || message.type === 'error')
|
|
@@ -239,7 +252,9 @@ export const hasMoreMessagesProbably = (returnedCountMessages, limit) => returne
|
|
|
239
252
|
// @deprecated
|
|
240
253
|
export const hasNotMoreMessages = (returnedCountMessages, limit) => returnedCountMessages < limit;
|
|
241
254
|
export function isDateSeparatorMessage(message) {
|
|
242
|
-
return message.customType === CUSTOM_MESSAGE_TYPE.date &&
|
|
255
|
+
return (message.customType === CUSTOM_MESSAGE_TYPE.date &&
|
|
256
|
+
!!message.date &&
|
|
257
|
+
isDate(message.date));
|
|
243
258
|
}
|
|
244
259
|
export const getIsFirstUnreadMessage = ({ firstUnreadMessageId, isFirstMessage, lastReadDate, lastReadMessageId, message, previousMessage, unreadMessageCount = 0, }) => {
|
|
245
260
|
const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
|
|
@@ -247,5 +262,7 @@ export const getIsFirstUnreadMessage = ({ firstUnreadMessageId, isFirstMessage,
|
|
|
247
262
|
const messageIsUnread = !!createdAtTimestamp && !!lastReadTimestamp && createdAtTimestamp > lastReadTimestamp;
|
|
248
263
|
const previousMessageIsLastRead = !!lastReadMessageId && lastReadMessageId === previousMessage?.id;
|
|
249
264
|
return (firstUnreadMessageId === message.id ||
|
|
250
|
-
(!!unreadMessageCount &&
|
|
265
|
+
(!!unreadMessageCount &&
|
|
266
|
+
messageIsUnread &&
|
|
267
|
+
(isFirstMessage || previousMessageIsLastRead)));
|
|
251
268
|
};
|
|
@@ -7,4 +7,4 @@ export type ModalProps = {
|
|
|
7
7
|
/** Callback handler for closing of modal. */
|
|
8
8
|
onClose?: (event: React.KeyboardEvent | React.MouseEvent<HTMLButtonElement | HTMLDivElement>) => void;
|
|
9
9
|
};
|
|
10
|
-
export declare const Modal: ({ children, className, onClose, open }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
|
|
10
|
+
export declare const Modal: ({ children, className, onClose, open, }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
|
|
@@ -3,7 +3,7 @@ import React, { useEffect, useRef } from 'react';
|
|
|
3
3
|
import { FocusScope } from '@react-aria/focus';
|
|
4
4
|
import { CloseIconRound } from './icons';
|
|
5
5
|
import { useTranslationContext } from '../../context';
|
|
6
|
-
export const Modal = ({ children, className, onClose, open }) => {
|
|
6
|
+
export const Modal = ({ children, className, onClose, open, }) => {
|
|
7
7
|
const { t } = useTranslationContext('Modal');
|
|
8
8
|
const innerRef = useRef(null);
|
|
9
9
|
const closeRef = useRef(null);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
export const ModalHeader = ({ className, close, goBack, title }) => (React.createElement("div", { className: clsx('str-chat__modal-header', className) },
|
|
4
|
-
goBack && React.createElement("button", { className: 'str-chat__modal-header__go-back-button', onClick: goBack }),
|
|
4
|
+
goBack && (React.createElement("button", { className: 'str-chat__modal-header__go-back-button', onClick: goBack })),
|
|
5
5
|
React.createElement("div", { className: 'str-chat__modal-header__title' }, title),
|
|
6
6
|
close && React.createElement("button", { className: 'str-chat__modal-header__close-button', onClick: close })));
|
|
@@ -3,6 +3,6 @@ import { PollContent as DefaultPollContent } from './PollContent';
|
|
|
3
3
|
import { QuotedPoll as DefaultQuotedPoll } from './QuotedPoll';
|
|
4
4
|
import { PollProvider, useComponentContext } from '../../context';
|
|
5
5
|
export const Poll = ({ isQuoted, poll, }) => {
|
|
6
|
-
const { PollContent = DefaultPollContent, QuotedPoll = DefaultQuotedPoll
|
|
6
|
+
const { PollContent = DefaultPollContent, QuotedPoll = DefaultQuotedPoll } = useComponentContext();
|
|
7
7
|
return poll ? (React.createElement(PollProvider, { poll: poll }, isQuoted ? React.createElement(QuotedPoll, null) : React.createElement(PollContent, null))) : null;
|
|
8
8
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useCallback, useState } from 'react';
|
|
2
2
|
import { PollAction } from './PollAction';
|
|
3
|
-
import { AddCommentForm as DefaultAddCommentForm } from './AddCommentForm';
|
|
3
|
+
import { AddCommentForm as DefaultAddCommentForm, } from './AddCommentForm';
|
|
4
4
|
import { SuggestPollOptionForm as DefaultSuggestPollOptionForm, } from './SuggestPollOptionForm';
|
|
5
|
-
import { EndPollDialog as DefaultEndPollDialog } from './EndPollDialog';
|
|
6
|
-
import { PollAnswerList as DefaultPollAnswerList } from './PollAnswerList';
|
|
5
|
+
import { EndPollDialog as DefaultEndPollDialog, } from './EndPollDialog';
|
|
6
|
+
import { PollAnswerList as DefaultPollAnswerList, } from './PollAnswerList';
|
|
7
7
|
import { PollOptionsFullList as DefaultPollOptionsFullList, } from './PollOptionsFullList';
|
|
8
8
|
import { PollResults as DefaultPollResults } from './PollResults';
|
|
9
9
|
import { MAX_OPTIONS_DISPLAYED, MAX_POLL_OPTIONS } from '../constants';
|
|
@@ -33,7 +33,9 @@ export const PollActions = ({ AddCommentForm = DefaultAddCommentForm, EndPollDia
|
|
|
33
33
|
count: options.length,
|
|
34
34
|
}), closeModal: closeModal, modalIsOpen: modalOpen === 'view-all-options', openModal: () => setModalOpen('view-all-options') },
|
|
35
35
|
React.createElement(PollOptionsFullList, { close: closeModal }))),
|
|
36
|
-
!is_closed &&
|
|
36
|
+
!is_closed &&
|
|
37
|
+
allow_user_suggested_options &&
|
|
38
|
+
options.length < MAX_POLL_OPTIONS && (React.createElement(PollAction, { buttonText: t('Suggest an option'), closeModal: closeModal, modalClassName: 'str-chat__suggest-poll-option-modal', modalIsOpen: modalOpen === 'suggest-option', openModal: () => setModalOpen('suggest-option') },
|
|
37
39
|
React.createElement(SuggestPollOptionForm, { close: closeModal, messageId: message.id }))),
|
|
38
40
|
!is_closed && allow_answers && (React.createElement(PollAction, { buttonText: ownAnswer ? t('Update your comment') : t('Add a comment'), closeModal: closeModal, modalClassName: 'str-chat__add-poll-answer-modal', modalIsOpen: modalOpen === 'add-comment', openModal: () => setModalOpen('add-comment') },
|
|
39
41
|
React.createElement(AddCommentForm, { close: closeModal, messageId: message.id }))),
|
|
@@ -14,7 +14,7 @@ export const PollAnswerList = ({ close, onUpdateOwnAnswerClick, }) => {
|
|
|
14
14
|
const { t } = useTranslationContext();
|
|
15
15
|
const { poll } = usePollContext();
|
|
16
16
|
const { is_closed, ownAnswer } = useStateStore(poll.state, pollStateSelector);
|
|
17
|
-
const { answers, error, hasNextPage, loading, loadMore
|
|
17
|
+
const { answers, error, hasNextPage, loading, loadMore } = usePollAnswerPagination();
|
|
18
18
|
return (React.createElement("div", { className: 'str-chat__modal__poll-answer-list' },
|
|
19
19
|
React.createElement(ModalHeader, { close: close, title: t('Poll comments') }),
|
|
20
20
|
React.createElement("div", { className: 'str-chat__modal__poll-answer-list__body' },
|
|
@@ -6,7 +6,7 @@ import { InfiniteScrollPaginator } from '../../../InfiniteScrollPaginator/Infini
|
|
|
6
6
|
import { PollOptionWithVotesHeader } from './PollOptionWithVotesHeader';
|
|
7
7
|
export const PollOptionVotesList = ({ option, }) => {
|
|
8
8
|
const paginationParams = useMemo(() => ({ filter: { option_id: option.id } }), [option.id]);
|
|
9
|
-
const { error, hasNextPage, loading, loadMore, votes
|
|
9
|
+
const { error, hasNextPage, loading, loadMore, votes } = usePollOptionVotesPagination({
|
|
10
10
|
paginationParams,
|
|
11
11
|
});
|
|
12
12
|
return (React.createElement("div", { className: 'str-chat__poll-option str-chat__poll-option--full-vote-list' },
|
|
@@ -2,8 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import { PollOptionWithVotesHeader } from './PollOptionWithVotesHeader';
|
|
3
3
|
import { PollVoteListing } from '../../PollVote';
|
|
4
4
|
import { useStateStore } from '../../../../store';
|
|
5
|
-
import { useChannelStateContext, usePollContext, useTranslationContext } from '../../../../context';
|
|
6
|
-
const pollStateSelector = (nextValue) => ({
|
|
5
|
+
import { useChannelStateContext, usePollContext, useTranslationContext, } from '../../../../context';
|
|
6
|
+
const pollStateSelector = (nextValue) => ({
|
|
7
|
+
latest_votes_by_option: nextValue.latest_votes_by_option,
|
|
8
|
+
});
|
|
7
9
|
export const PollOptionWithLatestVotes = ({ countVotesPreview = 5, option, showAllVotes, }) => {
|
|
8
10
|
const { t } = useTranslationContext();
|
|
9
11
|
const { channelCapabilities = {} } = useChannelStateContext('PollOptionWithLatestVotes');
|
|
@@ -26,7 +26,8 @@ export const PollResults = ({ close, }) => {
|
|
|
26
26
|
React.createElement("div", { className: 'str-chat__modal__poll-results__body' },
|
|
27
27
|
React.createElement("div", { className: 'str-chat__modal__poll-results__title' }, name),
|
|
28
28
|
React.createElement("div", { className: 'str-chat__modal__poll-results__option-list' }, options
|
|
29
|
-
.sort((next, current) => (vote_counts_by_option[current.id] ?? 0) >=
|
|
29
|
+
.sort((next, current) => (vote_counts_by_option[current.id] ?? 0) >=
|
|
30
|
+
(vote_counts_by_option[next.id] ?? 0)
|
|
30
31
|
? 1
|
|
31
32
|
: -1)
|
|
32
33
|
.map((option) => (React.createElement(PollOptionWithLatestVotes, { key: `poll-option-${option.id}`, option: option, showAllVotes: () => setOptionToView(option) })))))))));
|
|
@@ -8,7 +8,7 @@ import { useComponentContext, usePollContext } from '../../context';
|
|
|
8
8
|
import { useStateStore } from '../../store';
|
|
9
9
|
const pollStateSelectorPollContent = (nextValue) => ({ is_closed: nextValue.is_closed });
|
|
10
10
|
export const PollContent = () => {
|
|
11
|
-
const {
|
|
11
|
+
const { PollActions = DefaultPollActions, PollHeader = DefaultPollHeader } = useComponentContext();
|
|
12
12
|
const { poll } = usePollContext();
|
|
13
13
|
const { is_closed } = useStateStore(poll.state, pollStateSelectorPollContent);
|
|
14
14
|
return (React.createElement("div", { className: clsx('str-chat__poll', { 'str-chat__poll--closed': is_closed }) },
|
|
@@ -6,4 +6,4 @@ export type OptionFieldSetProps = {
|
|
|
6
6
|
setErrors: (fn: (prev: OptionErrors) => OptionErrors) => void;
|
|
7
7
|
setState: (fn: (prev: PollFormState) => PollFormState) => void;
|
|
8
8
|
};
|
|
9
|
-
export declare const OptionFieldSet: ({ errors, options, setErrors, setState }: OptionFieldSetProps) => React.JSX.Element;
|
|
9
|
+
export declare const OptionFieldSet: ({ errors, options, setErrors, setState, }: OptionFieldSetProps) => React.JSX.Element;
|
|
@@ -6,7 +6,7 @@ import { FieldError } from '../../Form/FieldError';
|
|
|
6
6
|
import { DragAndDropContainer } from '../../DragAndDrop/DragAndDropContainer';
|
|
7
7
|
import { useTranslationContext } from '../../../context';
|
|
8
8
|
const VALIDATION_ERRORS = { 'Option already exists': true };
|
|
9
|
-
export const OptionFieldSet = ({ errors, options, setErrors, setState }) => {
|
|
9
|
+
export const OptionFieldSet = ({ errors, options, setErrors, setState, }) => {
|
|
10
10
|
const { t } = useTranslationContext('OptionFieldSet');
|
|
11
11
|
const findOptionDuplicate = (sourceOption) => {
|
|
12
12
|
const isDuplicateFilter = (option) => !!sourceOption.text.trim() && // do not include empty options into consideration
|
|
@@ -15,7 +15,10 @@ export const OptionFieldSet = ({ errors, options, setErrors, setState }) => {
|
|
|
15
15
|
return options.find(isDuplicateFilter);
|
|
16
16
|
};
|
|
17
17
|
const onSetNewOrder = useCallback((newOrder) => {
|
|
18
|
-
setState((prev) => ({
|
|
18
|
+
setState((prev) => ({
|
|
19
|
+
...prev,
|
|
20
|
+
options: newOrder.map((index) => prev.options[index]),
|
|
21
|
+
}));
|
|
19
22
|
}, [setState]);
|
|
20
23
|
const draggable = options.length > 1;
|
|
21
24
|
return (React.createElement("fieldset", { className: 'str-chat__form__field str-chat__form__input-fieldset' },
|
|
@@ -38,7 +41,9 @@ export const OptionFieldSet = ({ errors, options, setErrors, setState }) => {
|
|
|
38
41
|
const shouldAddEmptyOption = prev.options.length < MAX_POLL_OPTIONS &&
|
|
39
42
|
(!prev.options ||
|
|
40
43
|
(prev.options.slice(i + 1).length === 0 && !!e.target.value));
|
|
41
|
-
const shouldRemoveOption = prev.options &&
|
|
44
|
+
const shouldRemoveOption = prev.options &&
|
|
45
|
+
prev.options.slice(i + 1).length > 0 &&
|
|
46
|
+
!e.target.value;
|
|
42
47
|
const optionListHead = prev.options ? prev.options.slice(0, i) : [];
|
|
43
48
|
const optionListTail = shouldAddEmptyOption
|
|
44
49
|
? [{ id: nanoid(), text: '' }]
|
|
@@ -55,7 +60,9 @@ export const OptionFieldSet = ({ errors, options, setErrors, setState }) => {
|
|
|
55
60
|
...prev,
|
|
56
61
|
options: [
|
|
57
62
|
...optionListHead,
|
|
58
|
-
...(shouldRemoveOption
|
|
63
|
+
...(shouldRemoveOption
|
|
64
|
+
? []
|
|
65
|
+
: [{ ...option, text: e.target.value }]),
|
|
59
66
|
...optionListTail,
|
|
60
67
|
],
|
|
61
68
|
};
|