stream-chat-react 12.0.0-rc.10 → 12.0.0-rc.12
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/Avatar/Avatar.js +5 -1
- package/dist/components/Channel/Channel.d.ts +3 -4
- package/dist/components/Channel/Channel.js +76 -24
- package/dist/components/Chat/hooks/useChat.js +10 -6
- package/dist/components/ChatView/ChatView.d.ts +18 -0
- package/dist/components/ChatView/ChatView.js +100 -0
- package/dist/components/ChatView/index.d.ts +1 -0
- package/dist/components/ChatView/index.js +1 -0
- package/dist/components/Message/Message.js +2 -1
- package/dist/components/Message/MessageOptions.js +3 -4
- package/dist/components/Message/MessageSimple.js +2 -1
- package/dist/components/Message/QuotedMessage.js +2 -1
- package/dist/components/Message/hooks/useReactionHandler.js +7 -0
- package/dist/components/Message/utils.d.ts +10 -1
- package/dist/components/Message/utils.js +16 -7
- package/dist/components/MessageActions/MessageActions.js +14 -9
- package/dist/components/MessageInput/MessageInputFlat.js +2 -2
- package/dist/components/MessageInput/QuotedMessagePreview.js +2 -1
- package/dist/components/MessageList/MessageList.js +1 -3
- package/dist/components/MessageList/VirtualizedMessageList.d.ts +2 -1
- package/dist/components/MessageList/VirtualizedMessageList.js +5 -2
- package/dist/components/MessageList/VirtualizedMessageListComponents.d.ts +1 -1
- package/dist/components/MessageList/VirtualizedMessageListComponents.js +6 -6
- package/dist/components/MessageList/renderMessages.d.ts +2 -2
- package/dist/components/MessageList/renderMessages.js +4 -1
- package/dist/components/Reactions/ReactionSelector.d.ts +5 -2
- package/dist/components/Reactions/ReactionSelector.js +2 -1
- package/dist/components/Reactions/ReactionsList.d.ts +4 -1
- package/dist/components/Reactions/hooks/useProcessReactions.js +2 -1
- package/dist/components/Thread/Thread.js +37 -10
- package/dist/components/Threads/ThreadContext.d.ts +9 -0
- package/dist/components/Threads/ThreadContext.js +9 -0
- package/dist/components/Threads/ThreadList/ThreadList.d.ts +9 -0
- package/dist/components/Threads/ThreadList/ThreadList.js +41 -0
- package/dist/components/Threads/ThreadList/ThreadListEmptyPlaceholder.d.ts +2 -0
- package/dist/components/Threads/ThreadList/ThreadListEmptyPlaceholder.js +5 -0
- package/dist/components/Threads/ThreadList/ThreadListItem.d.ts +9 -0
- package/dist/components/Threads/ThreadList/ThreadListItem.js +52 -0
- package/dist/components/Threads/ThreadList/ThreadListItemUI.d.ts +18 -0
- package/dist/components/Threads/ThreadList/ThreadListItemUI.js +76 -0
- package/dist/components/Threads/ThreadList/ThreadListLoadingIndicator.d.ts +2 -0
- package/dist/components/Threads/ThreadList/ThreadListLoadingIndicator.js +14 -0
- package/dist/components/Threads/ThreadList/ThreadListUnseenThreadsBanner.d.ts +2 -0
- package/dist/components/Threads/ThreadList/ThreadListUnseenThreadsBanner.js +16 -0
- package/dist/components/Threads/ThreadList/index.d.ts +3 -0
- package/dist/components/Threads/ThreadList/index.js +3 -0
- package/dist/components/Threads/UnreadCountBadge.d.ts +6 -0
- package/dist/components/Threads/UnreadCountBadge.js +5 -0
- package/dist/components/Threads/hooks/useStateStore.d.ts +3 -0
- package/dist/components/Threads/hooks/useStateStore.js +15 -0
- package/dist/components/Threads/hooks/useThreadManagerState.d.ts +2 -0
- package/dist/components/Threads/hooks/useThreadManagerState.js +6 -0
- package/dist/components/Threads/hooks/useThreadState.d.ts +5 -0
- package/dist/components/Threads/hooks/useThreadState.js +11 -0
- package/dist/components/Threads/icons.d.ts +8 -0
- package/dist/components/Threads/icons.js +13 -0
- package/dist/components/Threads/index.d.ts +3 -0
- package/dist/components/Threads/index.js +3 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/context/ComponentContext.d.ts +15 -40
- package/dist/context/ComponentContext.js +7 -9
- package/dist/context/MessageContext.d.ts +1 -1
- package/dist/context/MessageContext.js +3 -2
- package/dist/context/WithComponents.d.ts +5 -0
- package/dist/context/WithComponents.js +7 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +1 -0
- package/dist/css/v2/index.css +2 -2
- package/dist/css/v2/index.layout.css +2 -2
- package/dist/index.browser.cjs +6456 -5951
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +6390 -5870
- package/dist/index.node.cjs.map +4 -4
- package/dist/scss/v2/Avatar/Avatar-layout.scss +10 -2
- package/dist/scss/v2/Avatar/Avatar-theme.scss +5 -0
- package/dist/scss/v2/ChatView/ChatView-layout.scss +43 -0
- package/dist/scss/v2/ChatView/ChatView-theme.scss +31 -0
- package/dist/scss/v2/LoadingIndicator/LoadingIndicator-layout.scss +16 -0
- package/dist/scss/v2/MessageList/MessageList-layout.scss +0 -6
- package/dist/scss/v2/MessageList/VirtualizedMessageList-layout.scss +0 -12
- package/dist/scss/v2/Thread/Thread-layout.scss +15 -1
- package/dist/scss/v2/ThreadList/ThreadList-layout.scss +149 -0
- package/dist/scss/v2/ThreadList/ThreadList-theme.scss +74 -0
- package/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-layout.scss +49 -0
- package/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-theme.scss +10 -0
- package/dist/scss/v2/index.layout.scss +3 -0
- package/dist/scss/v2/index.scss +3 -0
- package/package.json +4 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { Icon } from '../Threads/icons';
|
|
3
4
|
import { getWholeChar } from '../../utils';
|
|
4
5
|
/**
|
|
5
6
|
* A round avatar image with fallback to username's first letter
|
|
@@ -15,6 +16,9 @@ export const Avatar = (props) => {
|
|
|
15
16
|
const showImage = image && !error;
|
|
16
17
|
return (React.createElement("div", { className: clsx(`str-chat__avatar str-chat__message-sender-avatar`, className, {
|
|
17
18
|
['str-chat__avatar--multiple-letters']: initials.length > 1,
|
|
19
|
+
['str-chat__avatar--no-letters']: !initials.length,
|
|
18
20
|
['str-chat__avatar--one-letter']: initials.length === 1,
|
|
19
|
-
}), "data-testid": 'avatar', onClick: onClick, onMouseOver: onMouseOver, title: name }, showImage ? (React.createElement("img", { alt: initials, className:
|
|
21
|
+
}), "data-testid": 'avatar', onClick: onClick, onMouseOver: onMouseOver, role: 'button', title: name }, showImage ? (React.createElement("img", { alt: initials, className: 'str-chat__avatar-image', "data-testid": 'avatar-img', onError: () => setError(true), src: image })) : (React.createElement(React.Fragment, null,
|
|
22
|
+
!!initials.length && (React.createElement("div", { className: clsx('str-chat__avatar-fallback'), "data-testid": 'avatar-fallback' }, initials)),
|
|
23
|
+
!initials.length && React.createElement(Icon.User, null)))));
|
|
20
24
|
};
|
|
@@ -2,11 +2,10 @@ import React, { PropsWithChildren } from 'react';
|
|
|
2
2
|
import { ChannelQueryOptions, EventAPIResponse, Message, MessageResponse, Channel as StreamChannel, StreamChat, UpdatedMessage } from 'stream-chat';
|
|
3
3
|
import { OnMentionAction } from './hooks/useMentionsHandlers';
|
|
4
4
|
import { LoadingErrorIndicatorProps } from '../Loading';
|
|
5
|
-
import { StreamMessage } from '../../context
|
|
6
|
-
import { ComponentContextValue } from '../../context/ComponentContext';
|
|
5
|
+
import { ComponentContextValue, StreamMessage } from '../../context';
|
|
7
6
|
import type { UnreadMessagesNotificationProps } from '../MessageList';
|
|
8
|
-
import type { MessageProps } from '../Message
|
|
9
|
-
import type { MessageInputProps } from '../MessageInput
|
|
7
|
+
import type { MessageProps } from '../Message';
|
|
8
|
+
import type { MessageInputProps } from '../MessageInput';
|
|
10
9
|
import type { ChannelUnreadUiState, CustomTrigger, DefaultStreamChatGenerics, GiphyVersions, ImageAttachmentSizeHandler, SendMessageOptions, UpdateMessageOptions, VideoAttachmentSizeHandler } from '../../types/types';
|
|
11
10
|
import type { URLEnrichmentConfig } from '../MessageInput/hooks/useLinkPreviews';
|
|
12
11
|
import { ReactionOptions } from '../Reactions';
|
|
@@ -10,26 +10,17 @@ import { useCreateTypingContext } from './hooks/useCreateTypingContext';
|
|
|
10
10
|
import { useEditMessageHandler } from './hooks/useEditMessageHandler';
|
|
11
11
|
import { useIsMounted } from './hooks/useIsMounted';
|
|
12
12
|
import { useMentionsHandlers } from './hooks/useMentionsHandlers';
|
|
13
|
-
import { Attachment as DefaultAttachment } from '../Attachment/Attachment';
|
|
14
13
|
import { LoadingErrorIndicator as DefaultLoadingErrorIndicator, } from '../Loading';
|
|
15
14
|
import { LoadingChannel as DefaultLoadingIndicator } from './LoadingChannel';
|
|
16
|
-
import { MessageSimple } from '../Message/MessageSimple';
|
|
17
15
|
import { DropzoneProvider } from '../MessageInput/DropzoneProvider';
|
|
18
|
-
import { ChannelActionProvider, } from '../../context
|
|
19
|
-
import { ChannelStateProvider, } from '../../context/ChannelStateContext';
|
|
20
|
-
import { ComponentProvider } from '../../context/ComponentContext';
|
|
21
|
-
import { useChatContext } from '../../context/ChatContext';
|
|
22
|
-
import { useTranslationContext } from '../../context/TranslationContext';
|
|
23
|
-
import { TypingProvider } from '../../context/TypingContext';
|
|
16
|
+
import { ChannelActionProvider, ChannelStateProvider, TypingProvider, useChatContext, useTranslationContext, WithComponents, } from '../../context';
|
|
24
17
|
import { DEFAULT_HIGHLIGHT_DURATION, DEFAULT_INITIAL_CHANNEL_PAGE_SIZE, DEFAULT_JUMP_TO_PAGE_SIZE, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, DEFAULT_THREAD_PAGE_SIZE, } from '../../constants/limits';
|
|
25
|
-
import { hasMoreMessagesProbably
|
|
18
|
+
import { hasMoreMessagesProbably } from '../MessageList';
|
|
26
19
|
import { useChannelContainerClasses } from './hooks/useChannelContainerClasses';
|
|
27
20
|
import { findInMsgSetByDate, findInMsgSetById, makeAddNotifications } from './utils';
|
|
28
21
|
import { getChannel } from '../../utils';
|
|
29
22
|
import { getImageAttachmentConfiguration, getVideoAttachmentConfiguration, } from '../Attachment/attachment-sizing';
|
|
30
|
-
import {
|
|
31
|
-
import { EventComponent } from '../EventComponent';
|
|
32
|
-
import { DateSeparator } from '../DateSeparator';
|
|
23
|
+
import { useThreadContext } from '../Threads';
|
|
33
24
|
const isUserResponseArray = (output) => output[0]?.id != null;
|
|
34
25
|
const UnMemoizedChannel = (props) => {
|
|
35
26
|
const { channel: propsChannel, EmptyPlaceholder = null, LoadingErrorIndicator, LoadingIndicator = DefaultLoadingIndicator, } = props;
|
|
@@ -60,6 +51,7 @@ const ChannelInner = (props) => {
|
|
|
60
51
|
const { client, customClasses, latestMessageDatesByChannels, mutes, theme, } = useChatContext('Channel');
|
|
61
52
|
const { t } = useTranslationContext('Channel');
|
|
62
53
|
const { channelClass, chatClass, chatContainerClass, windowsEmojiClass, } = useChannelContainerClasses({ customClasses });
|
|
54
|
+
const thread = useThreadContext();
|
|
63
55
|
const [channelConfig, setChannelConfig] = useState(channel.getConfig());
|
|
64
56
|
const [notifications, setNotifications] = useState([]);
|
|
65
57
|
const [quotedMessage, setQuotedMessage] = useState();
|
|
@@ -565,25 +557,37 @@ const ChannelInner = (props) => {
|
|
|
565
557
|
errorStatusCode: parsedError.status || undefined,
|
|
566
558
|
status: 'failed',
|
|
567
559
|
});
|
|
560
|
+
thread?.upsertReplyLocally({
|
|
561
|
+
// @ts-expect-error
|
|
562
|
+
message: {
|
|
563
|
+
...message,
|
|
564
|
+
error: parsedError,
|
|
565
|
+
errorStatusCode: parsedError.status || undefined,
|
|
566
|
+
status: 'failed',
|
|
567
|
+
},
|
|
568
|
+
});
|
|
568
569
|
}
|
|
569
570
|
}
|
|
570
571
|
};
|
|
571
572
|
const sendMessage = async ({ attachments = [], mentioned_users = [], parent, text = '', }, customMessageData, options) => {
|
|
572
573
|
channel.state.filterErrorMessages();
|
|
573
574
|
const messagePreview = {
|
|
574
|
-
__html: text,
|
|
575
575
|
attachments,
|
|
576
576
|
created_at: new Date(),
|
|
577
577
|
html: text,
|
|
578
578
|
id: customMessageData?.id ?? `${client.userID}-${nanoid()}`,
|
|
579
579
|
mentioned_users,
|
|
580
|
+
parent_id: parent?.id,
|
|
580
581
|
reactions: [],
|
|
581
582
|
status: 'sending',
|
|
582
583
|
text,
|
|
583
584
|
type: 'regular',
|
|
584
585
|
user: client.user,
|
|
585
|
-
...(parent?.id ? { parent_id: parent.id } : null),
|
|
586
586
|
};
|
|
587
|
+
thread?.upsertReplyLocally({
|
|
588
|
+
// @ts-expect-error
|
|
589
|
+
message: messagePreview,
|
|
590
|
+
});
|
|
587
591
|
updateMessage(messagePreview);
|
|
588
592
|
await doSendMessage(messagePreview, customMessageData, options);
|
|
589
593
|
};
|
|
@@ -722,8 +726,9 @@ const ChannelInner = (props) => {
|
|
|
722
726
|
jumpToLatestMessage,
|
|
723
727
|
setChannelUnreadUiState,
|
|
724
728
|
]);
|
|
729
|
+
// @ts-expect-error
|
|
725
730
|
const componentContextValue = useMemo(() => ({
|
|
726
|
-
Attachment: props.Attachment
|
|
731
|
+
Attachment: props.Attachment,
|
|
727
732
|
AttachmentPreviewList: props.AttachmentPreviewList,
|
|
728
733
|
AudioRecorder: props.AudioRecorder,
|
|
729
734
|
AutocompleteSuggestionItem: props.AutocompleteSuggestionItem,
|
|
@@ -732,7 +737,7 @@ const ChannelInner = (props) => {
|
|
|
732
737
|
BaseImage: props.BaseImage,
|
|
733
738
|
CooldownTimer: props.CooldownTimer,
|
|
734
739
|
CustomMessageActionsList: props.CustomMessageActionsList,
|
|
735
|
-
DateSeparator: props.DateSeparator
|
|
740
|
+
DateSeparator: props.DateSeparator,
|
|
736
741
|
EditMessageInput: props.EditMessageInput,
|
|
737
742
|
EmojiPicker: props.EmojiPicker,
|
|
738
743
|
emojiSearchIndex: props.emojiSearchIndex,
|
|
@@ -743,7 +748,7 @@ const ChannelInner = (props) => {
|
|
|
743
748
|
Input: props.Input,
|
|
744
749
|
LinkPreviewList: props.LinkPreviewList,
|
|
745
750
|
LoadingIndicator: props.LoadingIndicator,
|
|
746
|
-
Message: props.Message
|
|
751
|
+
Message: props.Message,
|
|
747
752
|
MessageBouncePrompt: props.MessageBouncePrompt,
|
|
748
753
|
MessageDeleted: props.MessageDeleted,
|
|
749
754
|
MessageListNotifications: props.MessageListNotifications,
|
|
@@ -751,13 +756,13 @@ const ChannelInner = (props) => {
|
|
|
751
756
|
MessageOptions: props.MessageOptions,
|
|
752
757
|
MessageRepliesCountButton: props.MessageRepliesCountButton,
|
|
753
758
|
MessageStatus: props.MessageStatus,
|
|
754
|
-
MessageSystem: props.MessageSystem
|
|
759
|
+
MessageSystem: props.MessageSystem,
|
|
755
760
|
MessageTimestamp: props.MessageTimestamp,
|
|
756
761
|
ModalGallery: props.ModalGallery,
|
|
757
762
|
PinIndicator: props.PinIndicator,
|
|
758
763
|
QuotedMessage: props.QuotedMessage,
|
|
759
764
|
QuotedMessagePreview: props.QuotedMessagePreview,
|
|
760
|
-
reactionOptions: props.reactionOptions
|
|
765
|
+
reactionOptions: props.reactionOptions,
|
|
761
766
|
ReactionSelector: props.ReactionSelector,
|
|
762
767
|
ReactionsList: props.ReactionsList,
|
|
763
768
|
SendButton: props.SendButton,
|
|
@@ -769,11 +774,58 @@ const ChannelInner = (props) => {
|
|
|
769
774
|
TriggerProvider: props.TriggerProvider,
|
|
770
775
|
TypingIndicator: props.TypingIndicator,
|
|
771
776
|
UnreadMessagesNotification: props.UnreadMessagesNotification,
|
|
772
|
-
UnreadMessagesSeparator: props.UnreadMessagesSeparator
|
|
777
|
+
UnreadMessagesSeparator: props.UnreadMessagesSeparator,
|
|
773
778
|
VirtualMessage: props.VirtualMessage,
|
|
774
|
-
}),
|
|
775
|
-
|
|
776
|
-
|
|
779
|
+
}), [
|
|
780
|
+
props.Attachment,
|
|
781
|
+
props.AttachmentPreviewList,
|
|
782
|
+
props.AudioRecorder,
|
|
783
|
+
props.AutocompleteSuggestionItem,
|
|
784
|
+
props.AutocompleteSuggestionList,
|
|
785
|
+
props.Avatar,
|
|
786
|
+
props.BaseImage,
|
|
787
|
+
props.CooldownTimer,
|
|
788
|
+
props.CustomMessageActionsList,
|
|
789
|
+
props.DateSeparator,
|
|
790
|
+
props.EditMessageInput,
|
|
791
|
+
props.EmojiPicker,
|
|
792
|
+
props.EmptyStateIndicator,
|
|
793
|
+
props.FileUploadIcon,
|
|
794
|
+
props.GiphyPreviewMessage,
|
|
795
|
+
props.HeaderComponent,
|
|
796
|
+
props.Input,
|
|
797
|
+
props.LinkPreviewList,
|
|
798
|
+
props.LoadingIndicator,
|
|
799
|
+
props.Message,
|
|
800
|
+
props.MessageBouncePrompt,
|
|
801
|
+
props.MessageDeleted,
|
|
802
|
+
props.MessageListNotifications,
|
|
803
|
+
props.MessageNotification,
|
|
804
|
+
props.MessageOptions,
|
|
805
|
+
props.MessageRepliesCountButton,
|
|
806
|
+
props.MessageStatus,
|
|
807
|
+
props.MessageSystem,
|
|
808
|
+
props.MessageTimestamp,
|
|
809
|
+
props.ModalGallery,
|
|
810
|
+
props.PinIndicator,
|
|
811
|
+
props.QuotedMessage,
|
|
812
|
+
props.QuotedMessagePreview,
|
|
813
|
+
props.ReactionSelector,
|
|
814
|
+
props.ReactionsList,
|
|
815
|
+
props.SendButton,
|
|
816
|
+
props.StartRecordingAudioButton,
|
|
817
|
+
props.ThreadHead,
|
|
818
|
+
props.ThreadHeader,
|
|
819
|
+
props.ThreadStart,
|
|
820
|
+
props.Timestamp,
|
|
821
|
+
props.TriggerProvider,
|
|
822
|
+
props.TypingIndicator,
|
|
823
|
+
props.UnreadMessagesNotification,
|
|
824
|
+
props.UnreadMessagesSeparator,
|
|
825
|
+
props.VirtualMessage,
|
|
826
|
+
props.emojiSearchIndex,
|
|
827
|
+
props.reactionOptions,
|
|
828
|
+
]);
|
|
777
829
|
const typingContextValue = useCreateTypingContext({
|
|
778
830
|
typing,
|
|
779
831
|
});
|
|
@@ -793,7 +845,7 @@ const ChannelInner = (props) => {
|
|
|
793
845
|
return (React.createElement("div", { className: clsx(className, windowsEmojiClass) },
|
|
794
846
|
React.createElement(ChannelStateProvider, { value: channelStateContextValue },
|
|
795
847
|
React.createElement(ChannelActionProvider, { value: channelActionContextValue },
|
|
796
|
-
React.createElement(
|
|
848
|
+
React.createElement(WithComponents, { overrides: componentContextValue },
|
|
797
849
|
React.createElement(TypingProvider, { value: typingContextValue },
|
|
798
850
|
React.createElement("div", { className: `${chatContainerClass}` },
|
|
799
851
|
dragAndDropWindow && (React.createElement(DropzoneProvider, { ...optionalMessageInputProps }, children)),
|
|
@@ -23,13 +23,17 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
23
23
|
return appSettings.current;
|
|
24
24
|
};
|
|
25
25
|
useEffect(() => {
|
|
26
|
-
if (client)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
26
|
+
if (!client)
|
|
27
|
+
return;
|
|
28
|
+
const userAgent = client.getUserAgent();
|
|
29
|
+
if (!userAgent.includes('stream-chat-react')) {
|
|
30
|
+
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
31
|
+
client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
|
|
32
32
|
}
|
|
33
|
+
client.threads.registerSubscriptions();
|
|
34
|
+
return () => {
|
|
35
|
+
client.threads.unregisterSubscriptions();
|
|
36
|
+
};
|
|
33
37
|
}, [client]);
|
|
34
38
|
useEffect(() => {
|
|
35
39
|
setMutes(clientMutes);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PropsWithChildren } from 'react';
|
|
3
|
+
import type { Thread } from 'stream-chat';
|
|
4
|
+
export declare const ChatView: {
|
|
5
|
+
({ children }: PropsWithChildren): React.JSX.Element;
|
|
6
|
+
Channels: ({ children }: PropsWithChildren) => React.JSX.Element | null;
|
|
7
|
+
Threads: ({ children }: PropsWithChildren) => React.JSX.Element | null;
|
|
8
|
+
ThreadAdapter: ({ children }: PropsWithChildren) => React.JSX.Element;
|
|
9
|
+
Selector: () => React.JSX.Element;
|
|
10
|
+
};
|
|
11
|
+
export type ThreadsViewContextValue = {
|
|
12
|
+
activeThread: Thread | undefined;
|
|
13
|
+
setActiveThread: (cv: ThreadsViewContextValue['activeThread']) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare const useThreadsViewContext: () => ThreadsViewContextValue;
|
|
16
|
+
export declare const useActiveThread: ({ activeThread }: {
|
|
17
|
+
activeThread?: Thread;
|
|
18
|
+
}) => void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { ThreadProvider, useStateStore } from '../Threads';
|
|
3
|
+
import { Icon } from '../Threads/icons';
|
|
4
|
+
import { UnreadCountBadge } from '../Threads/UnreadCountBadge';
|
|
5
|
+
import { useChatContext } from '../../context';
|
|
6
|
+
const availableChatViews = ['channels', 'threads'];
|
|
7
|
+
const ChatViewContext = createContext({
|
|
8
|
+
activeChatView: 'channels',
|
|
9
|
+
setActiveChatView: () => undefined,
|
|
10
|
+
});
|
|
11
|
+
export const ChatView = ({ children }) => {
|
|
12
|
+
const [activeChatView, setActiveChatView] = useState('channels');
|
|
13
|
+
const value = useMemo(() => ({ activeChatView, setActiveChatView }), [activeChatView]);
|
|
14
|
+
return (React.createElement(ChatViewContext.Provider, { value: value },
|
|
15
|
+
React.createElement("div", { className: 'str-chat str-chat__chat-view' }, children)));
|
|
16
|
+
};
|
|
17
|
+
const ChannelsView = ({ children }) => {
|
|
18
|
+
const { activeChatView } = useContext(ChatViewContext);
|
|
19
|
+
if (activeChatView !== 'channels')
|
|
20
|
+
return null;
|
|
21
|
+
return React.createElement("div", { className: 'str-chat__chat-view__channels' }, children);
|
|
22
|
+
};
|
|
23
|
+
const ThreadsViewContext = createContext({
|
|
24
|
+
activeThread: undefined,
|
|
25
|
+
setActiveThread: () => undefined,
|
|
26
|
+
});
|
|
27
|
+
export const useThreadsViewContext = () => useContext(ThreadsViewContext);
|
|
28
|
+
const ThreadsView = ({ children }) => {
|
|
29
|
+
const { activeChatView } = useContext(ChatViewContext);
|
|
30
|
+
const [activeThread, setActiveThread] = useState(undefined);
|
|
31
|
+
const value = useMemo(() => ({ activeThread, setActiveThread }), [activeThread]);
|
|
32
|
+
if (activeChatView !== 'threads')
|
|
33
|
+
return null;
|
|
34
|
+
return (React.createElement(ThreadsViewContext.Provider, { value: value },
|
|
35
|
+
React.createElement("div", { className: 'str-chat__chat-view__threads' }, children)));
|
|
36
|
+
};
|
|
37
|
+
// thread business logic that's impossible to keep within client but encapsulated for ease of use
|
|
38
|
+
export const useActiveThread = ({ activeThread }) => {
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!activeThread)
|
|
41
|
+
return;
|
|
42
|
+
const handleVisibilityChange = () => {
|
|
43
|
+
if (document.visibilityState === 'visible' && document.hasFocus()) {
|
|
44
|
+
activeThread.activate();
|
|
45
|
+
}
|
|
46
|
+
if (document.visibilityState === 'hidden' || !document.hasFocus()) {
|
|
47
|
+
activeThread.deactivate();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
handleVisibilityChange();
|
|
51
|
+
window.addEventListener('focus', handleVisibilityChange);
|
|
52
|
+
window.addEventListener('blur', handleVisibilityChange);
|
|
53
|
+
return () => {
|
|
54
|
+
activeThread.deactivate();
|
|
55
|
+
window.addEventListener('blur', handleVisibilityChange);
|
|
56
|
+
window.removeEventListener('focus', handleVisibilityChange);
|
|
57
|
+
};
|
|
58
|
+
}, [activeThread]);
|
|
59
|
+
};
|
|
60
|
+
// ThreadList under View.Threads context, will access setting function and on item click will set activeThread
|
|
61
|
+
// which can be accessed for the ease of use by ThreadAdapter which forwards it to required ThreadProvider
|
|
62
|
+
// ThreadList can easily live without this context and click handler can be overriden, ThreadAdapter is then no longer needed
|
|
63
|
+
/**
|
|
64
|
+
* // this setup still works
|
|
65
|
+
* const MyCustomComponent = () => {
|
|
66
|
+
* const [activeThread, setActiveThread] = useState();
|
|
67
|
+
*
|
|
68
|
+
* return <>
|
|
69
|
+
* // simplified
|
|
70
|
+
* <ThreadList onItemPointerDown={setActiveThread} />
|
|
71
|
+
* <ThreadProvider thread={activeThread}>
|
|
72
|
+
* <Thread />
|
|
73
|
+
* </ThreadProvider>
|
|
74
|
+
* </>
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
*/
|
|
78
|
+
const ThreadAdapter = ({ children }) => {
|
|
79
|
+
const { activeThread } = useThreadsViewContext();
|
|
80
|
+
useActiveThread({ activeThread });
|
|
81
|
+
return React.createElement(ThreadProvider, { thread: activeThread }, children);
|
|
82
|
+
};
|
|
83
|
+
const selector = (nextValue) => [nextValue.unreadThreadCount];
|
|
84
|
+
const ChatViewSelector = () => {
|
|
85
|
+
const { client } = useChatContext();
|
|
86
|
+
const [unreadThreadCount] = useStateStore(client.threads.state, selector);
|
|
87
|
+
const { activeChatView, setActiveChatView } = useContext(ChatViewContext);
|
|
88
|
+
return (React.createElement("div", { className: 'str-chat__chat-view__selector' },
|
|
89
|
+
React.createElement("button", { "aria-selected": activeChatView === 'channels', className: 'str-chat__chat-view__selector-button', onPointerDown: () => setActiveChatView('channels'), role: 'tab' },
|
|
90
|
+
React.createElement(Icon.MessageBubbleEmpty, null),
|
|
91
|
+
React.createElement("div", { className: 'str-chat__chat-view__selector-button-text' }, "Channels")),
|
|
92
|
+
React.createElement("button", { "aria-selected": activeChatView === 'threads', className: 'str-chat__chat-view__selector-button', onPointerDown: () => setActiveChatView('threads'), role: 'tab' },
|
|
93
|
+
React.createElement(UnreadCountBadge, { count: unreadThreadCount, position: 'top-right' },
|
|
94
|
+
React.createElement(Icon.MessageBubble, null)),
|
|
95
|
+
React.createElement("div", { className: 'str-chat__chat-view__selector-button-text' }, "Threads"))));
|
|
96
|
+
};
|
|
97
|
+
ChatView.Channels = ChannelsView;
|
|
98
|
+
ChatView.Threads = ThreadsView;
|
|
99
|
+
ChatView.ThreadAdapter = ThreadAdapter;
|
|
100
|
+
ChatView.Selector = ChatViewSelector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ChatView';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ChatView';
|
|
@@ -2,13 +2,14 @@ import React, { useCallback, useMemo, useRef } from 'react';
|
|
|
2
2
|
import { useActionHandler, useDeleteHandler, useEditHandler, useFlagHandler, useMarkUnreadHandler, useMentionsHandler, useMuteHandler, useOpenThreadHandler, usePinHandler, useReactionClick, useReactionHandler, useReactionsFetcher, useRetryHandler, useUserHandler, useUserRole, } from './hooks';
|
|
3
3
|
import { areMessagePropsEqual, getMessageActions, MESSAGE_ACTIONS } from './utils';
|
|
4
4
|
import { MessageProvider, useChannelActionContext, useChannelStateContext, useChatContext, useComponentContext, } from '../../context';
|
|
5
|
+
import { MessageSimple as DefaultMessage } from './MessageSimple';
|
|
5
6
|
const MessageWithContext = (props) => {
|
|
6
7
|
const { canPin, groupedByUser, Message: propMessage, message, messageActions = Object.keys(MESSAGE_ACTIONS), onUserClick: propOnUserClick, onUserHover: propOnUserHover, userRoles, } = props;
|
|
7
8
|
const { client } = useChatContext('Message');
|
|
8
9
|
const { read } = useChannelStateContext('Message');
|
|
9
10
|
const { Message: contextMessage } = useComponentContext('Message');
|
|
10
11
|
const actionsEnabled = message.type === 'regular' && message.status === 'received';
|
|
11
|
-
const MessageUIComponent = propMessage
|
|
12
|
+
const MessageUIComponent = propMessage ?? contextMessage ?? DefaultMessage;
|
|
12
13
|
const { clearEdit, editing, setEdit } = useEditHandler();
|
|
13
14
|
const { onUserClick, onUserHover } = useUserHandler(message, {
|
|
14
15
|
onUserClickHandler: propOnUserClick,
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ActionsIcon as DefaultActionsIcon, ReactionIcon as DefaultReactionIcon, ThreadIcon as DefaultThreadIcon, } from './icons';
|
|
3
|
-
import { MESSAGE_ACTIONS
|
|
3
|
+
import { MESSAGE_ACTIONS } from './utils';
|
|
4
4
|
import { MessageActions } from '../MessageActions';
|
|
5
5
|
import { useMessageContext } from '../../context/MessageContext';
|
|
6
6
|
import { useTranslationContext } from '../../context';
|
|
7
7
|
const UnMemoizedMessageOptions = (props) => {
|
|
8
8
|
const { ActionsIcon = DefaultActionsIcon, displayReplies = true, handleOpenThread: propHandleOpenThread, messageWrapperRef, ReactionIcon = DefaultReactionIcon, theme = 'simple', ThreadIcon = DefaultThreadIcon, } = props;
|
|
9
|
-
const {
|
|
9
|
+
const { getMessageActions, handleOpenThread: contextHandleOpenThread, initialMessage, message, onReactionListClick, showDetailedReactions, threadList, } = useMessageContext('MessageOptions');
|
|
10
10
|
const { t } = useTranslationContext('MessageOptions');
|
|
11
11
|
const handleOpenThread = propHandleOpenThread || contextHandleOpenThread;
|
|
12
12
|
const messageActions = getMessageActions();
|
|
13
|
-
const showActionsBox = showMessageActionsBox(messageActions, threadList) || !!customMessageActions;
|
|
14
13
|
const shouldShowReactions = messageActions.indexOf(MESSAGE_ACTIONS.react) > -1;
|
|
15
14
|
const shouldShowReplies = messageActions.indexOf(MESSAGE_ACTIONS.reply) > -1 && displayReplies && !threadList;
|
|
16
15
|
if (!message.type ||
|
|
@@ -24,7 +23,7 @@ const UnMemoizedMessageOptions = (props) => {
|
|
|
24
23
|
}
|
|
25
24
|
const rootClassName = `str-chat__message-${theme}__actions str-chat__message-options`;
|
|
26
25
|
return (React.createElement("div", { className: rootClassName, "data-testid": 'message-options' },
|
|
27
|
-
|
|
26
|
+
React.createElement(MessageActions, { ActionsIcon: ActionsIcon, messageWrapperRef: messageWrapperRef }),
|
|
28
27
|
shouldShowReplies && (React.createElement("button", { "aria-label": t('aria/Open Thread'), className: `str-chat__message-${theme}__actions__action str-chat__message-${theme}__actions__action--thread str-chat__message-reply-in-thread-button`, "data-testid": 'thread-action', onClick: handleOpenThread },
|
|
29
28
|
React.createElement(ThreadIcon, { className: 'str-chat__message-action-icon' }))),
|
|
30
29
|
shouldShowReactions && (React.createElement("button", { "aria-expanded": showDetailedReactions, "aria-label": t('aria/Open Reaction Selector'), className: `str-chat__message-${theme}__actions__action str-chat__message-${theme}__actions__action--reactions str-chat__message-reactions-button`, "data-testid": 'message-reaction-action', onClick: onReactionListClick },
|
|
@@ -10,6 +10,7 @@ import { MessageText } from './MessageText';
|
|
|
10
10
|
import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp';
|
|
11
11
|
import { areMessageUIPropsEqual, isMessageBounced, isMessageEdited, messageHasAttachments, messageHasReactions, } from './utils';
|
|
12
12
|
import { Avatar as DefaultAvatar } from '../Avatar';
|
|
13
|
+
import { Attachment as DefaultAttachment } from '../Attachment';
|
|
13
14
|
import { CUSTOM_MESSAGE_TYPE } from '../../constants/messageTypes';
|
|
14
15
|
import { EditMessageForm as DefaultEditMessageForm, MessageInput } from '../MessageInput';
|
|
15
16
|
import { MML } from '../MML';
|
|
@@ -25,7 +26,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
25
26
|
const { t } = useTranslationContext('MessageSimple');
|
|
26
27
|
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
|
|
27
28
|
const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false);
|
|
28
|
-
const { Attachment, Avatar = DefaultAvatar, EditMessageInput = DefaultEditMessageForm, MessageDeleted = DefaultMessageDeleted, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageOptions = DefaultMessageOptions, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionSelector = DefaultReactionSelector, ReactionsList = DefaultReactionList, PinIndicator, } = useComponentContext('MessageSimple');
|
|
29
|
+
const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, EditMessageInput = DefaultEditMessageForm, MessageDeleted = DefaultMessageDeleted, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageOptions = DefaultMessageOptions, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionSelector = DefaultReactionSelector, ReactionsList = DefaultReactionList, PinIndicator, } = useComponentContext('MessageSimple');
|
|
29
30
|
const hasAttachment = messageHasAttachments(message);
|
|
30
31
|
const hasReactions = messageHasReactions(message);
|
|
31
32
|
if (message.customType === CUSTOM_MESSAGE_TYPE.date) {
|
|
@@ -5,8 +5,9 @@ import { useComponentContext } from '../../context/ComponentContext';
|
|
|
5
5
|
import { useMessageContext } from '../../context/MessageContext';
|
|
6
6
|
import { useTranslationContext } from '../../context/TranslationContext';
|
|
7
7
|
import { useChannelActionContext } from '../../context/ChannelActionContext';
|
|
8
|
+
import { Attachment as DefaultAttachment } from '../Attachment';
|
|
8
9
|
export const QuotedMessage = () => {
|
|
9
|
-
const { Attachment, Avatar: ContextAvatar } = useComponentContext('QuotedMessage');
|
|
10
|
+
const { Attachment = DefaultAttachment, Avatar: ContextAvatar, } = useComponentContext('QuotedMessage');
|
|
10
11
|
const { isMyMessage, message } = useMessageContext('QuotedMessage');
|
|
11
12
|
const { t, userLanguage } = useTranslationContext('QuotedMessage');
|
|
12
13
|
const { jumpToMessage } = useChannelActionContext('QuotedMessage');
|
|
@@ -3,9 +3,11 @@ import throttle from 'lodash.throttle';
|
|
|
3
3
|
import { useChannelActionContext } from '../../../context/ChannelActionContext';
|
|
4
4
|
import { useChannelStateContext } from '../../../context/ChannelStateContext';
|
|
5
5
|
import { useChatContext } from '../../../context/ChatContext';
|
|
6
|
+
import { useThreadContext } from '../../Threads';
|
|
6
7
|
export const reactionHandlerWarning = `Reaction handler was called, but it is missing one of its required arguments.
|
|
7
8
|
Make sure the ChannelAction and ChannelState contexts are properly set and the hook is initialized with a valid message.`;
|
|
8
9
|
export const useReactionHandler = (message) => {
|
|
10
|
+
const thread = useThreadContext();
|
|
9
11
|
const { updateMessage } = useChannelActionContext('useReactionHandler');
|
|
10
12
|
const { channel, channelCapabilities } = useChannelStateContext('useReactionHandler');
|
|
11
13
|
const { client } = useChatContext('useReactionHandler');
|
|
@@ -64,14 +66,19 @@ export const useReactionHandler = (message) => {
|
|
|
64
66
|
const tempMessage = createMessagePreview(add, newReaction, message);
|
|
65
67
|
try {
|
|
66
68
|
updateMessage(tempMessage);
|
|
69
|
+
// @ts-expect-error
|
|
70
|
+
thread?.upsertReplyLocally({ message: tempMessage });
|
|
67
71
|
const messageResponse = add
|
|
68
72
|
? await channel.sendReaction(id, { type })
|
|
69
73
|
: await channel.deleteReaction(id, type);
|
|
74
|
+
// seems useless as we're expecting WS event to come in and replace this anyway
|
|
70
75
|
updateMessage(messageResponse.message);
|
|
71
76
|
}
|
|
72
77
|
catch (error) {
|
|
73
78
|
// revert to the original message if the API call fails
|
|
74
79
|
updateMessage(message);
|
|
80
|
+
// @ts-expect-error
|
|
81
|
+
thread?.upsertReplyLocally({ message });
|
|
75
82
|
}
|
|
76
83
|
}, 1000);
|
|
77
84
|
return async (reactionType, event) => {
|
|
@@ -2,7 +2,7 @@ import type { TFunction } from 'i18next';
|
|
|
2
2
|
import type { MessageResponse, Mute, StreamChat, UserResponse } from 'stream-chat';
|
|
3
3
|
import type { PinPermissions } from './hooks';
|
|
4
4
|
import type { MessageProps } from './types';
|
|
5
|
-
import type { MessageContextValue, StreamMessage } from '../../context';
|
|
5
|
+
import type { ComponentContextValue, CustomMessageActions, MessageContextValue, StreamMessage } from '../../context';
|
|
6
6
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
7
7
|
/**
|
|
8
8
|
* Following function validates a function which returns notification message.
|
|
@@ -39,7 +39,16 @@ export type Capabilities = {
|
|
|
39
39
|
};
|
|
40
40
|
export declare const getMessageActions: (actions: MessageActionsArray | boolean, { canDelete, canEdit, canFlag, canMarkUnread, canMute, canPin, canQuote, canReact, canReply, }: Capabilities) => MessageActionsArray<string>;
|
|
41
41
|
export declare const ACTIONS_NOT_WORKING_IN_THREAD: string[];
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated use `shouldRenderMessageActions` instead
|
|
44
|
+
*/
|
|
42
45
|
export declare const showMessageActionsBox: (actions: MessageActionsArray<string>, inThread?: boolean | undefined) => boolean;
|
|
46
|
+
export declare const shouldRenderMessageActions: <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ customMessageActions, CustomMessageActionsList, inThread, messageActions, }: {
|
|
47
|
+
messageActions: MessageActionsArray;
|
|
48
|
+
customMessageActions?: CustomMessageActions<SCG>;
|
|
49
|
+
CustomMessageActionsList?: ComponentContextValue<SCG>['CustomMessageActionsList'];
|
|
50
|
+
inThread?: boolean;
|
|
51
|
+
}) => boolean;
|
|
43
52
|
export declare const areMessagePropsEqual: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(prevProps: MessageProps<StreamChatGenerics> & {
|
|
44
53
|
mutes?: Mute<StreamChatGenerics>[];
|
|
45
54
|
showDetailedReactions?: boolean;
|
|
@@ -140,22 +140,31 @@ export const getMessageActions = (actions, { canDelete, canEdit, canFlag, canMar
|
|
|
140
140
|
};
|
|
141
141
|
export const ACTIONS_NOT_WORKING_IN_THREAD = [
|
|
142
142
|
MESSAGE_ACTIONS.pin,
|
|
143
|
-
MESSAGE_ACTIONS.react,
|
|
144
143
|
MESSAGE_ACTIONS.reply,
|
|
145
144
|
MESSAGE_ACTIONS.markUnread,
|
|
146
145
|
];
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
/**
|
|
147
|
+
* @deprecated use `shouldRenderMessageActions` instead
|
|
148
|
+
*/
|
|
149
|
+
export const showMessageActionsBox = (actions, inThread) => shouldRenderMessageActions({ inThread, messageActions: actions });
|
|
150
|
+
export const shouldRenderMessageActions = ({ customMessageActions, CustomMessageActionsList, inThread, messageActions, }) => {
|
|
151
|
+
if (typeof CustomMessageActionsList !== 'undefined' ||
|
|
152
|
+
typeof customMessageActions !== 'undefined')
|
|
153
|
+
return true;
|
|
154
|
+
if (!messageActions.length)
|
|
149
155
|
return false;
|
|
150
|
-
}
|
|
151
156
|
if (inThread &&
|
|
152
|
-
|
|
157
|
+
messageActions.filter((action) => !ACTIONS_NOT_WORKING_IN_THREAD.includes(action)).length === 0) {
|
|
153
158
|
return false;
|
|
154
159
|
}
|
|
155
|
-
if (
|
|
160
|
+
if (messageActions.length === 1 &&
|
|
161
|
+
(messageActions.includes(MESSAGE_ACTIONS.react) ||
|
|
162
|
+
messageActions.includes(MESSAGE_ACTIONS.reply))) {
|
|
156
163
|
return false;
|
|
157
164
|
}
|
|
158
|
-
if (
|
|
165
|
+
if (messageActions.length === 2 &&
|
|
166
|
+
messageActions.includes(MESSAGE_ACTIONS.react) &&
|
|
167
|
+
messageActions.includes(MESSAGE_ACTIONS.reply)) {
|
|
159
168
|
return false;
|
|
160
169
|
}
|
|
161
170
|
return true;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState, } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
2
3
|
import { MessageActionsBox } from './MessageActionsBox';
|
|
3
4
|
import { ActionsIcon as DefaultActionsIcon } from '../Message/icons';
|
|
4
|
-
import { isUserMuted } from '../Message/utils';
|
|
5
|
+
import { isUserMuted, shouldRenderMessageActions } from '../Message/utils';
|
|
5
6
|
import { useChatContext } from '../../context/ChatContext';
|
|
6
7
|
import { useMessageContext } from '../../context/MessageContext';
|
|
7
8
|
import { useMessageActionsBoxPopper } from './hooks';
|
|
8
|
-
import { useTranslationContext } from '../../context';
|
|
9
|
+
import { useComponentContext, useTranslationContext } from '../../context';
|
|
9
10
|
export const MessageActions = (props) => {
|
|
10
11
|
const { ActionsIcon = DefaultActionsIcon, customWrapperClass = '', getMessageActions: propGetMessageActions, handleDelete: propHandleDelete, handleFlag: propHandleFlag, handleMarkUnread: propHandleMarkUnread, handleMute: propHandleMute, handlePin: propHandlePin, inline, message: propMessage, messageWrapperRef, mine, } = props;
|
|
11
12
|
const { mutes } = useChatContext('MessageActions');
|
|
12
|
-
const { customMessageActions, getMessageActions: contextGetMessageActions, handleDelete: contextHandleDelete, handleFlag: contextHandleFlag, handleMarkUnread: contextHandleMarkUnread, handleMute: contextHandleMute, handlePin: contextHandlePin, isMyMessage, message: contextMessage, setEditingState, } = useMessageContext('MessageActions');
|
|
13
|
+
const { customMessageActions, getMessageActions: contextGetMessageActions, handleDelete: contextHandleDelete, handleFlag: contextHandleFlag, handleMarkUnread: contextHandleMarkUnread, handleMute: contextHandleMute, handlePin: contextHandlePin, isMyMessage, message: contextMessage, setEditingState, threadList, } = useMessageContext('MessageActions');
|
|
14
|
+
const { CustomMessageActionsList } = useComponentContext('MessageActions');
|
|
13
15
|
const { t } = useTranslationContext('MessageActions');
|
|
14
16
|
const getMessageActions = propGetMessageActions || contextGetMessageActions;
|
|
15
17
|
const handleDelete = propHandleDelete || contextHandleDelete;
|
|
@@ -21,13 +23,19 @@ export const MessageActions = (props) => {
|
|
|
21
23
|
const isMine = mine ? mine() : isMyMessage();
|
|
22
24
|
const [actionsBoxOpen, setActionsBoxOpen] = useState(false);
|
|
23
25
|
const isMuted = useCallback(() => isUserMuted(message, mutes), [message, mutes]);
|
|
26
|
+
const messageActions = getMessageActions();
|
|
27
|
+
const renderMessageActions = shouldRenderMessageActions({
|
|
28
|
+
customMessageActions,
|
|
29
|
+
CustomMessageActionsList,
|
|
30
|
+
inThread: threadList,
|
|
31
|
+
messageActions,
|
|
32
|
+
});
|
|
24
33
|
const hideOptions = useCallback((event) => {
|
|
25
34
|
if (event instanceof KeyboardEvent && event.key !== 'Escape') {
|
|
26
35
|
return;
|
|
27
36
|
}
|
|
28
37
|
setActionsBoxOpen(false);
|
|
29
38
|
}, []);
|
|
30
|
-
const messageActions = getMessageActions();
|
|
31
39
|
const messageDeletedAt = !!message?.deleted_at;
|
|
32
40
|
useEffect(() => {
|
|
33
41
|
if (messageWrapperRef?.current) {
|
|
@@ -55,7 +63,7 @@ export const MessageActions = (props) => {
|
|
|
55
63
|
placement: isMine ? 'top-end' : 'top-start',
|
|
56
64
|
referenceElement: actionsBoxButtonRef.current,
|
|
57
65
|
});
|
|
58
|
-
if (!
|
|
66
|
+
if (!renderMessageActions)
|
|
59
67
|
return null;
|
|
60
68
|
return (React.createElement(MessageActionsWrapper, { customWrapperClass: customWrapperClass, inline: inline, setActionsBoxOpen: setActionsBoxOpen },
|
|
61
69
|
React.createElement(MessageActionsBox, { ...attributes.popper, getMessageActions: getMessageActions, handleDelete: handleDelete, handleEdit: setEditingState, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handlePin: handlePin, isUserMuted: isMuted, mine: isMine, open: actionsBoxOpen, ref: popperElementRef, style: styles.popper }),
|
|
@@ -64,10 +72,7 @@ export const MessageActions = (props) => {
|
|
|
64
72
|
};
|
|
65
73
|
const MessageActionsWrapper = (props) => {
|
|
66
74
|
const { children, customWrapperClass, inline, setActionsBoxOpen } = props;
|
|
67
|
-
const defaultWrapperClass =
|
|
68
|
-
str-chat__message-simple__actions__action
|
|
69
|
-
str-chat__message-simple__actions__action--options
|
|
70
|
-
str-chat__message-actions-container`;
|
|
75
|
+
const defaultWrapperClass = clsx('str-chat__message-simple__actions__action', 'str-chat__message-simple__actions__action--options', 'str-chat__message-actions-container');
|
|
71
76
|
const wrapperClass = customWrapperClass || defaultWrapperClass;
|
|
72
77
|
const onClickOptionsAction = (event) => {
|
|
73
78
|
event.stopPropagation();
|