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
|
@@ -5,8 +5,8 @@ const STATUSES_EXCLUDED_FROM_PREPEND = {
|
|
|
5
5
|
};
|
|
6
6
|
export function usePrependedMessagesCount(messages, hasDateSeparator) {
|
|
7
7
|
const firstRealMessageIndex = hasDateSeparator ? 1 : 0;
|
|
8
|
-
const firstMessageOnFirstLoadedPage = useRef();
|
|
9
|
-
const previousFirstMessageOnFirstLoadedPage = useRef();
|
|
8
|
+
const firstMessageOnFirstLoadedPage = useRef(undefined);
|
|
9
|
+
const previousFirstMessageOnFirstLoadedPage = useRef(undefined);
|
|
10
10
|
const previousNumItemsPrepended = useRef(0);
|
|
11
11
|
const numItemsPrepended = useMemo(() => {
|
|
12
12
|
if (!messages || !messages.length) {
|
|
@@ -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
|
}
|
package/dist/components/MessageList/hooks/VirtualizedMessageList/useScrollToBottomOnNewMessage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
export const useScrollToBottomOnNewMessage = ({ messages, scrollToBottom, scrollToLatestMessageOnFocus, }) => {
|
|
3
3
|
const [newMessagesReceivedInBackground, setNewMessagesReceivedInBackground] = useState(false);
|
|
4
|
-
const scrollToBottomIfConfigured = useRef();
|
|
4
|
+
const scrollToBottomIfConfigured = useRef(undefined);
|
|
5
5
|
scrollToBottomIfConfigured.current = (event) => {
|
|
6
6
|
if (!scrollToLatestMessageOnFocus ||
|
|
7
7
|
!newMessagesReceivedInBackground ||
|
|
@@ -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,8 +1,7 @@
|
|
|
1
|
-
import { DefaultStreamChatGenerics } from '../../../types';
|
|
1
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
2
2
|
type UseMarkReadParams = {
|
|
3
3
|
isMessageListScrolledToBottom: boolean;
|
|
4
4
|
messageListIsThread: boolean;
|
|
5
|
-
unreadCount: number;
|
|
6
5
|
wasMarkedUnread?: boolean;
|
|
7
6
|
};
|
|
8
7
|
/**
|
|
@@ -12,8 +11,7 @@ type UseMarkReadParams = {
|
|
|
12
11
|
* 3. the channel was not marked unread by the user
|
|
13
12
|
* @param isMessageListScrolledToBottom
|
|
14
13
|
* @param messageListIsThread
|
|
15
|
-
* @param unreadCount
|
|
16
14
|
* @param wasChannelMarkedUnread
|
|
17
15
|
*/
|
|
18
|
-
export declare const useMarkRead: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ isMessageListScrolledToBottom, messageListIsThread,
|
|
16
|
+
export declare const useMarkRead: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ isMessageListScrolledToBottom, messageListIsThread, wasMarkedUnread, }: UseMarkReadParams) => void;
|
|
19
17
|
export {};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
import { useChannelActionContext, useChannelStateContext, useChatContext, } from '../../../context';
|
|
3
|
+
const hasReadLastMessage = (channel, userId) => {
|
|
4
|
+
const latestMessageIdInChannel = channel.state.latestMessages.slice(-1)[0]?.id;
|
|
5
|
+
const lastReadMessageIdServer = channel.state.read[userId]?.last_read_message_id;
|
|
6
|
+
return latestMessageIdInChannel === lastReadMessageIdServer;
|
|
7
|
+
};
|
|
3
8
|
/**
|
|
4
9
|
* Takes care of marking a channel read. The channel is read only if all the following applies:
|
|
5
10
|
* 1. the message list is not rendered in a thread
|
|
@@ -7,29 +12,25 @@ import { useChannelActionContext, useChannelStateContext, useChatContext, } from
|
|
|
7
12
|
* 3. the channel was not marked unread by the user
|
|
8
13
|
* @param isMessageListScrolledToBottom
|
|
9
14
|
* @param messageListIsThread
|
|
10
|
-
* @param unreadCount
|
|
11
15
|
* @param wasChannelMarkedUnread
|
|
12
16
|
*/
|
|
13
|
-
export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread,
|
|
17
|
+
export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread, wasMarkedUnread, }) => {
|
|
14
18
|
const { client } = useChatContext('useMarkRead');
|
|
15
19
|
const { markRead, setChannelUnreadUiState } = useChannelActionContext('useMarkRead');
|
|
16
20
|
const { channel } = useChannelStateContext('useMarkRead');
|
|
17
|
-
const previousRenderMessageListScrolledToBottom = useRef(isMessageListScrolledToBottom);
|
|
18
21
|
useEffect(() => {
|
|
19
|
-
const shouldMarkRead = (
|
|
22
|
+
const shouldMarkRead = () => !document.hidden &&
|
|
20
23
|
!wasMarkedUnread &&
|
|
21
24
|
!messageListIsThread &&
|
|
22
25
|
isMessageListScrolledToBottom &&
|
|
23
|
-
|
|
26
|
+
client.user?.id &&
|
|
27
|
+
!hasReadLastMessage(channel, client.user.id);
|
|
24
28
|
const onVisibilityChange = () => {
|
|
25
|
-
if (shouldMarkRead(
|
|
29
|
+
if (shouldMarkRead())
|
|
26
30
|
markRead();
|
|
27
31
|
};
|
|
28
32
|
const handleMessageNew = (event) => {
|
|
29
|
-
const isOwnMessage = event.user?.id && event.user.id === client.user?.id;
|
|
30
33
|
const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel;
|
|
31
|
-
if (isOwnMessage)
|
|
32
|
-
return;
|
|
33
34
|
if (!isMessageListScrolledToBottom || wasMarkedUnread || document.hidden) {
|
|
34
35
|
setChannelUnreadUiState((prev) => {
|
|
35
36
|
const previousUnreadCount = prev?.unread_messages ?? 0;
|
|
@@ -44,17 +45,15 @@ export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread
|
|
|
44
45
|
};
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
|
-
else if (mainChannelUpdated && shouldMarkRead(
|
|
48
|
+
else if (mainChannelUpdated && shouldMarkRead()) {
|
|
48
49
|
markRead();
|
|
49
50
|
}
|
|
50
51
|
};
|
|
51
52
|
channel.on('message.new', handleMessageNew);
|
|
52
53
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
53
|
-
|
|
54
|
-
isMessageListScrolledToBottom;
|
|
55
|
-
if (hasScrolledToBottom && shouldMarkRead(channel.countUnread()))
|
|
54
|
+
if (shouldMarkRead()) {
|
|
56
55
|
markRead();
|
|
57
|
-
|
|
56
|
+
}
|
|
58
57
|
return () => {
|
|
59
58
|
channel.off('message.new', handleMessageNew);
|
|
60
59
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
@@ -66,7 +65,6 @@ export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread
|
|
|
66
65
|
markRead,
|
|
67
66
|
messageListIsThread,
|
|
68
67
|
setChannelUnreadUiState,
|
|
69
|
-
unreadCount,
|
|
70
68
|
wasMarkedUnread,
|
|
71
69
|
]);
|
|
72
70
|
};
|
|
@@ -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
|
};
|
|
@@ -64,20 +64,29 @@ export const PollCreationDialog = ({ close }) => {
|
|
|
64
64
|
React.createElement("div", { className: clsx('str-chat__form__input-field__value') },
|
|
65
65
|
React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-max-votes-allowed-input-field-error', text: multipleAnswerCountError }),
|
|
66
66
|
React.createElement("input", { id: 'max_votes_allowed', onChange: (e) => {
|
|
67
|
-
const isValidValue = !e.target.value ||
|
|
67
|
+
const isValidValue = !e.target.value ||
|
|
68
|
+
e.target.value.match(VALID_MAX_VOTES_VALUE_REGEX);
|
|
68
69
|
if (!isValidValue) {
|
|
69
70
|
setMultipleAnswerCountError(t('Type a number from 2 to 10'));
|
|
70
71
|
}
|
|
71
72
|
else if (multipleAnswerCountError) {
|
|
72
73
|
setMultipleAnswerCountError(undefined);
|
|
73
74
|
}
|
|
74
|
-
setState((prev) => ({
|
|
75
|
+
setState((prev) => ({
|
|
76
|
+
...prev,
|
|
77
|
+
max_votes_allowed: e.target.value,
|
|
78
|
+
}));
|
|
75
79
|
}, placeholder: t('Maximum number of votes (from 2 to 10)'), type: 'number', value: state.max_votes_allowed }))))),
|
|
76
80
|
React.createElement(SimpleSwitchField, { checked: state.voting_visibility === 'anonymous', id: 'voting_visibility', labelText: t('Anonymous poll'), onChange: (e) => setState((prev) => ({
|
|
77
81
|
...prev,
|
|
78
|
-
voting_visibility: (e.target.checked
|
|
82
|
+
voting_visibility: (e.target.checked
|
|
83
|
+
? 'anonymous'
|
|
84
|
+
: 'public'),
|
|
85
|
+
})) }),
|
|
86
|
+
React.createElement(SimpleSwitchField, { checked: state.allow_user_suggested_options, id: 'allow_user_suggested_options', labelText: t('Allow option suggestion'), onChange: (e) => setState((prev) => ({
|
|
87
|
+
...prev,
|
|
88
|
+
allow_user_suggested_options: e.target.checked,
|
|
79
89
|
})) }),
|
|
80
|
-
React.createElement(SimpleSwitchField, { checked: state.allow_user_suggested_options, id: 'allow_user_suggested_options', labelText: t('Allow option suggestion'), onChange: (e) => setState((prev) => ({ ...prev, allow_user_suggested_options: e.target.checked })) }),
|
|
81
90
|
React.createElement(SimpleSwitchField, { checked: state.allow_answers, id: 'allow_answers', labelText: t('Allow comments'), onChange: (e) => setState((prev) => ({ ...prev, allow_answers: e.target.checked })) }))),
|
|
82
91
|
React.createElement(PollCreationDialogControls, { close: close, errors: [
|
|
83
92
|
...(nameError ?? []),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { VALID_MAX_VOTES_VALUE_REGEX } from '../constants';
|
|
3
|
-
import { useChatContext, useMessageInputContext, useTranslationContext } from '../../../context';
|
|
3
|
+
import { useChatContext, useMessageInputContext, useTranslationContext, } from '../../../context';
|
|
4
4
|
export const PollCreationDialogControls = ({ close, errors, state, }) => {
|
|
5
5
|
const { client } = useChatContext();
|
|
6
6
|
const { t } = useTranslationContext('PollCreationDialogControls');
|
|
@@ -10,7 +10,8 @@ export const PollCreationDialogControls = ({ close, errors, state, }) => {
|
|
|
10
10
|
const hasName = !!state.name;
|
|
11
11
|
const maxVotesAllowedNumber = parseInt(state.max_votes_allowed?.match(VALID_MAX_VOTES_VALUE_REGEX)?.[0] || '');
|
|
12
12
|
const validMaxVotesAllowed = state.max_votes_allowed === '' ||
|
|
13
|
-
(!!maxVotesAllowedNumber &&
|
|
13
|
+
(!!maxVotesAllowedNumber &&
|
|
14
|
+
(2 <= maxVotesAllowedNumber || maxVotesAllowedNumber <= 10));
|
|
14
15
|
const noErrors = errors.length === 0;
|
|
15
16
|
return hasAtLeastOneOption && hasName && validMaxVotesAllowed && noErrors;
|
|
16
17
|
};
|
|
@@ -24,7 +25,9 @@ export const PollCreationDialogControls = ({ close, errors, state, }) => {
|
|
|
24
25
|
max_votes_allowed: state.max_votes_allowed
|
|
25
26
|
? parseInt(state.max_votes_allowed)
|
|
26
27
|
: undefined,
|
|
27
|
-
options: state.options
|
|
28
|
+
options: state.options
|
|
29
|
+
?.filter((o) => o.text)
|
|
30
|
+
.map((o) => ({ text: o.text })),
|
|
28
31
|
});
|
|
29
32
|
pollId = poll.id;
|
|
30
33
|
}
|
|
@@ -5,7 +5,7 @@ import { useStateStore } from '../../store';
|
|
|
5
5
|
import { useComponentContext, usePollContext } from '../../context';
|
|
6
6
|
const pollStateSelector = (nextValue) => ({ options: nextValue.options });
|
|
7
7
|
export const PollOptionList = ({ optionsDisplayCount, }) => {
|
|
8
|
-
const { PollOptionSelector = DefaultPollOptionSelector
|
|
8
|
+
const { PollOptionSelector = DefaultPollOptionSelector } = useComponentContext();
|
|
9
9
|
const { poll } = usePollContext();
|
|
10
10
|
const { options } = useStateStore(poll.state, pollStateSelector);
|
|
11
11
|
return (React.createElement("div", { className: clsx('str-chat__poll-option-list', {
|
|
@@ -24,7 +24,9 @@ export const PollOptionSelector = ({ displayAvatarCount, option, voteCountVerbos
|
|
|
24
24
|
const { poll } = usePollContext();
|
|
25
25
|
const { is_closed, latest_votes_by_option, maxVotedOptionIds, ownVotesByOptionId, vote_counts_by_option, voting_visibility, } = useStateStore(poll.state, pollStateSelector);
|
|
26
26
|
const canCastVote = channelCapabilities['cast-poll-vote'] && !is_closed;
|
|
27
|
-
const winningOptionCount = maxVotedOptionIds[0]
|
|
27
|
+
const winningOptionCount = maxVotedOptionIds[0]
|
|
28
|
+
? vote_counts_by_option[maxVotedOptionIds[0]]
|
|
29
|
+
: 0;
|
|
28
30
|
const toggleVote = useMemo(() => debounce(() => {
|
|
29
31
|
if (!canCastVote)
|
|
30
32
|
return;
|
|
@@ -45,9 +47,14 @@ export const PollOptionSelector = ({ displayAvatarCount, option, voteCountVerbos
|
|
|
45
47
|
.slice(0, displayAvatarCount)
|
|
46
48
|
.map(({ user }) => (React.createElement(Avatar, { image: user?.image, key: `poll-option-${option.id}-avatar-${user?.id}`, name: user?.name }))))),
|
|
47
49
|
React.createElement("div", { className: 'str-chat__poll-option-vote-count' }, voteCountVerbose
|
|
48
|
-
? t('{{count}} votes', {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
? t('{{count}} votes', {
|
|
51
|
+
count: vote_counts_by_option[option.id] ?? 0,
|
|
52
|
+
})
|
|
53
|
+
: (vote_counts_by_option[option.id] ?? 0))),
|
|
54
|
+
React.createElement(AmountBar, { amount: (winningOptionCount &&
|
|
55
|
+
(vote_counts_by_option[option.id] ?? 0) / winningOptionCount) * 100, className: clsx('str-chat__poll-option__votes-bar', {
|
|
56
|
+
'str-chat__poll-option__votes-bar--winner': is_closed &&
|
|
57
|
+
maxVotedOptionIds.length === 1 &&
|
|
58
|
+
maxVotedOptionIds[0] === option.id,
|
|
52
59
|
}) })));
|
|
53
60
|
};
|
|
@@ -11,16 +11,21 @@ export function useManagePollVotesRealtime(managedVoteType, cursorPaginatorState
|
|
|
11
11
|
return;
|
|
12
12
|
const isAnswer = isVoteAnswer(event.poll_vote);
|
|
13
13
|
if ((managedVoteType === 'answer' && !isAnswer) ||
|
|
14
|
-
(managedVoteType === 'vote' &&
|
|
14
|
+
(managedVoteType === 'vote' &&
|
|
15
|
+
(isAnswer || event.poll_vote.option_id !== optionId)))
|
|
15
16
|
return;
|
|
16
17
|
if (event.type === 'poll.vote_removed') {
|
|
17
|
-
setVotesInRealtime((prev) => event.poll_vote
|
|
18
|
+
setVotesInRealtime((prev) => event.poll_vote
|
|
19
|
+
? prev.filter((vote) => vote.id !== event.poll_vote.id)
|
|
20
|
+
: prev);
|
|
18
21
|
}
|
|
19
22
|
if (event.type === 'poll.vote_changed') {
|
|
20
|
-
setVotesInRealtime((prev) => event.poll_vote
|
|
23
|
+
setVotesInRealtime((prev) => event.poll_vote
|
|
24
|
+
? prev.filter((vote) => vote.id !== event.poll_vote.id)
|
|
25
|
+
: prev);
|
|
21
26
|
}
|
|
22
27
|
if (['poll.vote_casted', 'poll.vote_changed'].includes(event.type)) {
|
|
23
|
-
setVotesInRealtime((prev) =>
|
|
28
|
+
setVotesInRealtime((prev) => event.poll_vote ? [event.poll_vote, ...prev] : prev);
|
|
24
29
|
}
|
|
25
30
|
};
|
|
26
31
|
const voteCastedSubscription = client.on('poll.vote_casted', handleVoteEvent);
|
|
@@ -3,13 +3,19 @@ import { useManagePollVotesRealtime } from './useManagePollVotesRealtime';
|
|
|
3
3
|
import { useCursorPaginator, } from '../../InfiniteScrollPaginator/hooks/useCursorPaginator';
|
|
4
4
|
import { usePollContext } from '../../../context';
|
|
5
5
|
import { useStateStore } from '../../../store';
|
|
6
|
-
const paginationStateSelector = (state) => [
|
|
6
|
+
const paginationStateSelector = (state) => [
|
|
7
|
+
state.error,
|
|
8
|
+
state.hasNextPage,
|
|
9
|
+
state.loading,
|
|
10
|
+
];
|
|
7
11
|
export const usePollAnswerPagination = ({ paginationParams } = {}) => {
|
|
8
12
|
const { poll } = usePollContext();
|
|
9
13
|
const paginationFn = useCallback(async (next) => {
|
|
10
14
|
const { next: newNext, votes } = await poll.queryAnswers({
|
|
11
15
|
filter: paginationParams?.filter,
|
|
12
|
-
options: !next
|
|
16
|
+
options: !next
|
|
17
|
+
? paginationParams?.options
|
|
18
|
+
: { ...paginationParams?.options, next },
|
|
13
19
|
sort: { created_at: -1, ...paginationParams?.sort },
|
|
14
20
|
});
|
|
15
21
|
return { items: votes, next: newNext };
|
|
@@ -3,13 +3,19 @@ import { useManagePollVotesRealtime } from './useManagePollVotesRealtime';
|
|
|
3
3
|
import { useCursorPaginator, } from '../../InfiniteScrollPaginator/hooks/useCursorPaginator';
|
|
4
4
|
import { useStateStore } from '../../../store';
|
|
5
5
|
import { usePollContext } from '../../../context';
|
|
6
|
-
const paginationStateSelector = (state) => [
|
|
6
|
+
const paginationStateSelector = (state) => [
|
|
7
|
+
state.error,
|
|
8
|
+
state.hasNextPage,
|
|
9
|
+
state.loading,
|
|
10
|
+
];
|
|
7
11
|
export const usePollOptionVotesPagination = ({ paginationParams, }) => {
|
|
8
12
|
const { poll } = usePollContext();
|
|
9
13
|
const paginationFn = useCallback(async (next) => {
|
|
10
14
|
const { next: newNext, votes } = await poll.queryOptionVotes({
|
|
11
15
|
filter: paginationParams.filter,
|
|
12
|
-
options: !next
|
|
16
|
+
options: !next
|
|
17
|
+
? paginationParams?.options
|
|
18
|
+
: { ...paginationParams?.options, next },
|
|
13
19
|
sort: { created_at: -1, ...paginationParams?.sort },
|
|
14
20
|
});
|
|
15
21
|
return { items: votes, next: newNext };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type GeneralType = 'audio/' | 'video/' | 'image/' | 'text/';
|
|
2
|
-
export type SupportedMimeType = typeof wordMimeTypes[number] | typeof excelMimeTypes[number] | typeof powerpointMimeTypes[number] | typeof archiveFileTypes[number] | typeof codeFileTypes[number];
|
|
2
|
+
export type SupportedMimeType = (typeof wordMimeTypes)[number] | (typeof excelMimeTypes)[number] | (typeof powerpointMimeTypes)[number] | (typeof archiveFileTypes)[number] | (typeof codeFileTypes)[number];
|
|
3
3
|
export declare const wordMimeTypes: string[];
|
|
4
4
|
export declare const excelMimeTypes: string[];
|
|
5
5
|
export declare const powerpointMimeTypes: string[];
|
|
@@ -12,7 +12,9 @@ export const ImageDropzone = ({ accept: acceptedFiles = [], children, disabled,
|
|
|
12
12
|
handleFiles(accepted);
|
|
13
13
|
}
|
|
14
14
|
}, [handleFiles]);
|
|
15
|
-
const accept = useMemo(() => (typeof acceptedFiles === 'string'
|
|
15
|
+
const accept = useMemo(() => (typeof acceptedFiles === 'string'
|
|
16
|
+
? acceptedFiles.split(',')
|
|
17
|
+
: acceptedFiles).reduce((mediaTypeMap, mediaType) => {
|
|
16
18
|
mediaTypeMap[mediaType] ?? (mediaTypeMap[mediaType] = []);
|
|
17
19
|
return mediaTypeMap;
|
|
18
20
|
}, {}), [acceptedFiles]);
|