stream-chat-react 12.0.0-rc.8 → 12.0.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/Avatar/Avatar.js +5 -1
- package/dist/components/Channel/Channel.d.ts +3 -4
- package/dist/components/Channel/Channel.js +95 -56
- package/dist/components/Channel/hooks/useCreateChannelStateContext.js +2 -1
- package/dist/components/ChannelHeader/ChannelHeader.js +4 -5
- package/dist/components/ChannelPreview/hooks/useChannelPreviewInfo.js +14 -16
- package/dist/components/ChannelPreview/utils.js +9 -20
- package/dist/components/ChannelSearch/hooks/useChannelSearch.js +2 -3
- package/dist/components/Chat/Chat.d.ts +1 -1
- package/dist/components/Chat/hooks/useChat.d.ts +2 -2
- package/dist/components/Chat/hooks/useChat.js +11 -8
- package/dist/components/ChatView/ChatView.d.ts +18 -0
- package/dist/components/ChatView/ChatView.js +103 -0
- package/dist/components/ChatView/index.d.ts +1 -0
- package/dist/components/ChatView/index.js +1 -0
- package/dist/components/DateSeparator/DateSeparator.d.ts +1 -1
- package/dist/components/Dialog/DialogAnchor.d.ts +25 -0
- package/dist/components/Dialog/DialogAnchor.js +68 -0
- package/dist/components/Dialog/DialogManager.d.ts +43 -0
- package/dist/components/Dialog/DialogManager.js +98 -0
- package/dist/components/Dialog/DialogPortal.d.ts +7 -0
- package/dist/components/Dialog/DialogPortal.js +25 -0
- package/dist/components/Dialog/hooks/index.d.ts +1 -0
- package/dist/components/Dialog/hooks/index.js +1 -0
- package/dist/components/Dialog/hooks/useDialog.d.ts +4 -0
- package/dist/components/Dialog/hooks/useDialog.js +26 -0
- package/dist/components/Dialog/index.d.ts +4 -0
- package/dist/components/Dialog/index.js +4 -0
- package/dist/components/EventComponent/EventComponent.d.ts +1 -1
- package/dist/components/Message/Message.js +5 -6
- package/dist/components/Message/MessageOptions.d.ts +1 -2
- package/dist/components/Message/MessageOptions.js +14 -11
- package/dist/components/Message/MessageSimple.js +6 -14
- package/dist/components/Message/MessageTimestamp.d.ts +1 -1
- package/dist/components/Message/QuotedMessage.js +2 -1
- package/dist/components/Message/Timestamp.d.ts +1 -1
- package/dist/components/Message/Timestamp.js +2 -2
- package/dist/components/Message/hooks/useReactionHandler.d.ts +1 -7
- package/dist/components/Message/hooks/useReactionHandler.js +8 -63
- package/dist/components/Message/utils.d.ts +10 -1
- package/dist/components/Message/utils.js +19 -7
- package/dist/components/MessageActions/MessageActions.d.ts +1 -2
- package/dist/components/MessageActions/MessageActions.js +26 -55
- package/dist/components/MessageActions/MessageActionsBox.d.ts +1 -1
- package/dist/components/MessageActions/MessageActionsBox.js +6 -6
- package/dist/components/MessageInput/MessageInputFlat.js +2 -2
- package/dist/components/MessageInput/QuotedMessagePreview.js +2 -1
- package/dist/components/MessageInput/hooks/useUserTrigger.js +0 -1
- package/dist/components/MessageList/MessageList.js +7 -7
- package/dist/components/MessageList/VirtualizedMessageList.d.ts +2 -1
- package/dist/components/MessageList/VirtualizedMessageList.js +44 -39
- 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/MessageList/utils.js +1 -1
- package/dist/components/Reactions/ReactionSelector.d.ts +6 -3
- package/dist/components/Reactions/ReactionSelector.js +34 -24
- package/dist/components/Reactions/ReactionSelectorWithButton.d.ts +13 -0
- package/dist/components/Reactions/ReactionSelectorWithButton.js +22 -0
- package/dist/components/Reactions/ReactionsList.d.ts +4 -4
- package/dist/components/Reactions/hooks/useProcessReactions.js +2 -1
- package/dist/components/Thread/Thread.js +38 -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 +15 -0
- package/dist/components/Threads/ThreadList/ThreadListItemUI.js +75 -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/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 +2 -0
- package/dist/components/Threads/index.js +2 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +3 -0
- package/dist/context/ComponentContext.d.ts +15 -40
- package/dist/context/ComponentContext.js +7 -9
- package/dist/context/DialogManagerContext.d.ts +10 -0
- package/dist/context/DialogManagerContext.js +14 -0
- package/dist/context/MessageContext.d.ts +3 -11
- package/dist/context/MessageContext.js +3 -2
- package/dist/context/TranslationContext.d.ts +1 -11
- package/dist/context/TranslationContext.js +1 -9
- package/dist/context/WithComponents.d.ts +5 -0
- package/dist/context/WithComponents.js +7 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/css/v2/index.css +2 -2
- package/dist/css/v2/index.layout.css +2 -2
- package/dist/i18n/Streami18n.d.ts +1 -3
- package/dist/i18n/Streami18n.js +1 -2
- package/dist/i18n/index.d.ts +2 -1
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/types.d.ts +26 -0
- package/dist/i18n/types.js +1 -0
- package/dist/i18n/utils.d.ts +9 -20
- package/dist/i18n/utils.js +10 -1
- package/dist/index.browser.cjs +10827 -10601
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.node.cjs +10720 -10510
- package/dist/index.node.cjs.map +4 -4
- package/dist/plugins/Emojis/index.browser.cjs +7 -169
- package/dist/plugins/Emojis/index.browser.cjs.map +4 -4
- package/dist/plugins/Emojis/index.node.cjs +7 -169
- package/dist/plugins/Emojis/index.node.cjs.map +4 -4
- package/dist/plugins/encoders/mp3.browser.cjs +2 -4
- package/dist/plugins/encoders/mp3.browser.cjs.map +1 -1
- package/dist/plugins/encoders/mp3.node.cjs +2 -4
- package/dist/plugins/encoders/mp3.node.cjs.map +1 -1
- 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 +32 -0
- package/dist/scss/v2/Dialog/Dialog-layout.scss +8 -0
- package/dist/scss/v2/LoadingIndicator/LoadingIndicator-layout.scss +16 -0
- package/dist/scss/v2/Message/Message-layout.scss +8 -0
- package/dist/scss/v2/MessageActionsBox/MessageActionsBox-theme.scss +8 -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/MessageReactions/MessageReactionsSelector-layout.scss +16 -0
- package/dist/scss/v2/MessageReactions/MessageReactionsSelector-theme.scss +6 -0
- package/dist/scss/v2/Thread/Thread-layout.scss +15 -1
- package/dist/scss/v2/ThreadList/ThreadList-layout.scss +152 -0
- package/dist/scss/v2/ThreadList/ThreadList-theme.scss +75 -0
- package/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-layout.scss +49 -0
- package/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-theme.scss +11 -0
- package/dist/scss/v2/_base.scss +31 -0
- package/dist/scss/v2/index.layout.scss +4 -0
- package/dist/scss/v2/index.scss +3 -0
- package/dist/store/hooks/index.d.ts +1 -0
- package/dist/store/hooks/index.js +1 -0
- package/dist/store/hooks/useStateStore.d.ts +3 -0
- package/dist/store/hooks/useStateStore.js +15 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/package.json +7 -6
- package/dist/assets/Poweredby_100px-White_VertText.png +0 -0
- package/dist/assets/str-chat__reaction-list-sprite@1x.png +0 -0
- package/dist/assets/str-chat__reaction-list-sprite@2x.png +0 -0
- package/dist/assets/str-chat__reaction-list-sprite@3x.png +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { ThreadProvider } from '../Threads';
|
|
3
|
+
import { Icon } from '../Threads/icons';
|
|
4
|
+
import { UnreadCountBadge } from '../Threads/UnreadCountBadge';
|
|
5
|
+
import { useChatContext } from '../../context';
|
|
6
|
+
import { useStateStore } from '../../store';
|
|
7
|
+
import clsx from 'clsx';
|
|
8
|
+
const availableChatViews = ['channels', 'threads'];
|
|
9
|
+
const ChatViewContext = createContext({
|
|
10
|
+
activeChatView: 'channels',
|
|
11
|
+
setActiveChatView: () => undefined,
|
|
12
|
+
});
|
|
13
|
+
export const ChatView = ({ children }) => {
|
|
14
|
+
const [activeChatView, setActiveChatView] = useState('channels');
|
|
15
|
+
const { theme } = useChatContext();
|
|
16
|
+
const value = useMemo(() => ({ activeChatView, setActiveChatView }), [activeChatView]);
|
|
17
|
+
return (React.createElement(ChatViewContext.Provider, { value: value },
|
|
18
|
+
React.createElement("div", { className: clsx('str-chat', theme, 'str-chat__chat-view') }, children)));
|
|
19
|
+
};
|
|
20
|
+
const ChannelsView = ({ children }) => {
|
|
21
|
+
const { activeChatView } = useContext(ChatViewContext);
|
|
22
|
+
if (activeChatView !== 'channels')
|
|
23
|
+
return null;
|
|
24
|
+
return React.createElement("div", { className: 'str-chat__chat-view__channels' }, children);
|
|
25
|
+
};
|
|
26
|
+
const ThreadsViewContext = createContext({
|
|
27
|
+
activeThread: undefined,
|
|
28
|
+
setActiveThread: () => undefined,
|
|
29
|
+
});
|
|
30
|
+
export const useThreadsViewContext = () => useContext(ThreadsViewContext);
|
|
31
|
+
const ThreadsView = ({ children }) => {
|
|
32
|
+
const { activeChatView } = useContext(ChatViewContext);
|
|
33
|
+
const [activeThread, setActiveThread] = useState(undefined);
|
|
34
|
+
const value = useMemo(() => ({ activeThread, setActiveThread }), [activeThread]);
|
|
35
|
+
if (activeChatView !== 'threads')
|
|
36
|
+
return null;
|
|
37
|
+
return (React.createElement(ThreadsViewContext.Provider, { value: value },
|
|
38
|
+
React.createElement("div", { className: 'str-chat__chat-view__threads' }, children)));
|
|
39
|
+
};
|
|
40
|
+
// thread business logic that's impossible to keep within client but encapsulated for ease of use
|
|
41
|
+
export const useActiveThread = ({ activeThread }) => {
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!activeThread)
|
|
44
|
+
return;
|
|
45
|
+
const handleVisibilityChange = () => {
|
|
46
|
+
if (document.visibilityState === 'visible' && document.hasFocus()) {
|
|
47
|
+
activeThread.activate();
|
|
48
|
+
}
|
|
49
|
+
if (document.visibilityState === 'hidden' || !document.hasFocus()) {
|
|
50
|
+
activeThread.deactivate();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
handleVisibilityChange();
|
|
54
|
+
window.addEventListener('focus', handleVisibilityChange);
|
|
55
|
+
window.addEventListener('blur', handleVisibilityChange);
|
|
56
|
+
return () => {
|
|
57
|
+
activeThread.deactivate();
|
|
58
|
+
window.addEventListener('blur', handleVisibilityChange);
|
|
59
|
+
window.removeEventListener('focus', handleVisibilityChange);
|
|
60
|
+
};
|
|
61
|
+
}, [activeThread]);
|
|
62
|
+
};
|
|
63
|
+
// ThreadList under View.Threads context, will access setting function and on item click will set activeThread
|
|
64
|
+
// which can be accessed for the ease of use by ThreadAdapter which forwards it to required ThreadProvider
|
|
65
|
+
// ThreadList can easily live without this context and click handler can be overriden, ThreadAdapter is then no longer needed
|
|
66
|
+
/**
|
|
67
|
+
* // this setup still works
|
|
68
|
+
* const MyCustomComponent = () => {
|
|
69
|
+
* const [activeThread, setActiveThread] = useState();
|
|
70
|
+
*
|
|
71
|
+
* return <>
|
|
72
|
+
* // simplified
|
|
73
|
+
* <ThreadList onItemPointerDown={setActiveThread} />
|
|
74
|
+
* <ThreadProvider thread={activeThread}>
|
|
75
|
+
* <Thread />
|
|
76
|
+
* </ThreadProvider>
|
|
77
|
+
* </>
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
*/
|
|
81
|
+
const ThreadAdapter = ({ children }) => {
|
|
82
|
+
const { activeThread } = useThreadsViewContext();
|
|
83
|
+
useActiveThread({ activeThread });
|
|
84
|
+
return React.createElement(ThreadProvider, { thread: activeThread }, children);
|
|
85
|
+
};
|
|
86
|
+
const selector = (nextValue) => [nextValue.unreadThreadCount];
|
|
87
|
+
const ChatViewSelector = () => {
|
|
88
|
+
const { client } = useChatContext();
|
|
89
|
+
const [unreadThreadCount] = useStateStore(client.threads.state, selector);
|
|
90
|
+
const { activeChatView, setActiveChatView } = useContext(ChatViewContext);
|
|
91
|
+
return (React.createElement("div", { className: 'str-chat__chat-view__selector' },
|
|
92
|
+
React.createElement("button", { "aria-selected": activeChatView === 'channels', className: 'str-chat__chat-view__selector-button', onPointerDown: () => setActiveChatView('channels'), role: 'tab' },
|
|
93
|
+
React.createElement(Icon.MessageBubbleEmpty, null),
|
|
94
|
+
React.createElement("div", { className: 'str-chat__chat-view__selector-button-text' }, "Channels")),
|
|
95
|
+
React.createElement("button", { "aria-selected": activeChatView === 'threads', className: 'str-chat__chat-view__selector-button', onPointerDown: () => setActiveChatView('threads'), role: 'tab' },
|
|
96
|
+
React.createElement(UnreadCountBadge, { count: unreadThreadCount, position: 'top-right' },
|
|
97
|
+
React.createElement(Icon.MessageBubble, null)),
|
|
98
|
+
React.createElement("div", { className: 'str-chat__chat-view__selector-button-text' }, "Threads"))));
|
|
99
|
+
};
|
|
100
|
+
ChatView.Channels = ChannelsView;
|
|
101
|
+
ChatView.Threads = ThreadsView;
|
|
102
|
+
ChatView.ThreadAdapter = ThreadAdapter;
|
|
103
|
+
ChatView.Selector = ChatViewSelector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ChatView';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ChatView';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Placement } from '@popperjs/core';
|
|
2
|
+
import React, { ComponentProps, PropsWithChildren } from 'react';
|
|
3
|
+
export interface DialogAnchorOptions {
|
|
4
|
+
open: boolean;
|
|
5
|
+
placement: Placement;
|
|
6
|
+
referenceElement: HTMLElement | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function useDialogAnchor<T extends HTMLElement>({ open, placement, referenceElement, }: DialogAnchorOptions): {
|
|
9
|
+
attributes: {
|
|
10
|
+
[key: string]: {
|
|
11
|
+
[key: string]: string;
|
|
12
|
+
} | undefined;
|
|
13
|
+
};
|
|
14
|
+
setPopperElement: React.Dispatch<React.SetStateAction<T | null>>;
|
|
15
|
+
styles: {
|
|
16
|
+
[key: string]: React.CSSProperties;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type DialogAnchorProps = PropsWithChildren<Partial<DialogAnchorOptions>> & {
|
|
20
|
+
id: string;
|
|
21
|
+
focus?: boolean;
|
|
22
|
+
trapFocus?: boolean;
|
|
23
|
+
} & ComponentProps<'div'>;
|
|
24
|
+
export declare const DialogAnchor: ({ children, className, focus, id, placement, referenceElement, trapFocus, ...restDivProps }: DialogAnchorProps) => React.JSX.Element | null;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { FocusScope } from '@react-aria/focus';
|
|
4
|
+
import { usePopper } from 'react-popper';
|
|
5
|
+
import { DialogPortalEntry } from './DialogPortal';
|
|
6
|
+
import { useDialog, useDialogIsOpen } from './hooks';
|
|
7
|
+
export function useDialogAnchor({ open, placement, referenceElement, }) {
|
|
8
|
+
const [popperElement, setPopperElement] = useState(null);
|
|
9
|
+
const { attributes, styles, update } = usePopper(referenceElement, popperElement, {
|
|
10
|
+
modifiers: [
|
|
11
|
+
{
|
|
12
|
+
name: 'eventListeners',
|
|
13
|
+
options: {
|
|
14
|
+
// It's not safe to update popper position on resize and scroll, since popper's
|
|
15
|
+
// reference element might not be visible at the time.
|
|
16
|
+
resize: false,
|
|
17
|
+
scroll: false,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
placement,
|
|
22
|
+
});
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (open && popperElement) {
|
|
25
|
+
// Since the popper's reference element might not be (and usually is not) visible
|
|
26
|
+
// all the time, it's safer to force popper update before showing it.
|
|
27
|
+
// update is non-null only if popperElement is non-null
|
|
28
|
+
update?.();
|
|
29
|
+
}
|
|
30
|
+
}, [open, popperElement, update]);
|
|
31
|
+
if (popperElement && !open) {
|
|
32
|
+
setPopperElement(null);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
attributes,
|
|
36
|
+
setPopperElement,
|
|
37
|
+
styles,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export const DialogAnchor = ({ children, className, focus = true, id, placement = 'auto', referenceElement = null, trapFocus, ...restDivProps }) => {
|
|
41
|
+
const dialog = useDialog({ id });
|
|
42
|
+
const open = useDialogIsOpen(id);
|
|
43
|
+
const { attributes, setPopperElement, styles } = useDialogAnchor({
|
|
44
|
+
open,
|
|
45
|
+
placement,
|
|
46
|
+
referenceElement,
|
|
47
|
+
});
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!open)
|
|
50
|
+
return;
|
|
51
|
+
const hideOnEscape = (event) => {
|
|
52
|
+
if (event.key !== 'Escape')
|
|
53
|
+
return;
|
|
54
|
+
dialog?.close();
|
|
55
|
+
};
|
|
56
|
+
document.addEventListener('keyup', hideOnEscape);
|
|
57
|
+
return () => {
|
|
58
|
+
document.removeEventListener('keyup', hideOnEscape);
|
|
59
|
+
};
|
|
60
|
+
}, [dialog, open]);
|
|
61
|
+
// prevent rendering the dialog contents if the dialog should not be open / shown
|
|
62
|
+
if (!open) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return (React.createElement(DialogPortalEntry, { dialogId: id },
|
|
66
|
+
React.createElement(FocusScope, { autoFocus: focus, contain: trapFocus, restoreFocus: true },
|
|
67
|
+
React.createElement("div", { ...restDivProps, ...attributes.popper, className: clsx('str-chat__dialog-contents', className), "data-testid": 'str-chat__dialog-contents', ref: setPopperElement, style: styles.popper, tabIndex: 0 }, children))));
|
|
68
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { StateStore } from 'stream-chat';
|
|
2
|
+
export type GetOrCreateDialogParams = {
|
|
3
|
+
id: DialogId;
|
|
4
|
+
};
|
|
5
|
+
type DialogId = string;
|
|
6
|
+
export type Dialog = {
|
|
7
|
+
close: () => void;
|
|
8
|
+
id: DialogId;
|
|
9
|
+
isOpen: boolean | undefined;
|
|
10
|
+
open: (zIndex?: number) => void;
|
|
11
|
+
remove: () => void;
|
|
12
|
+
toggle: (closeAll?: boolean) => void;
|
|
13
|
+
};
|
|
14
|
+
export type DialogManagerOptions = {
|
|
15
|
+
id?: string;
|
|
16
|
+
};
|
|
17
|
+
type Dialogs = Record<DialogId, Dialog>;
|
|
18
|
+
export type DialogManagerState = {
|
|
19
|
+
dialogsById: Dialogs;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Keeps a map of Dialog objects.
|
|
23
|
+
* Dialog can be controlled via `Dialog` object retrieved using `useDialog()` hook.
|
|
24
|
+
* The hook returns an object with the following API:
|
|
25
|
+
*
|
|
26
|
+
* - `dialog.open()` - opens the dialog
|
|
27
|
+
* - `dialog.close()` - closes the dialog
|
|
28
|
+
* - `dialog.toggle()` - toggles the dialog open state. Accepts boolean argument closeAll. If enabled closes any other dialog that would be open.
|
|
29
|
+
* - `dialog.remove()` - removes the dialog object reference from the state (primarily for cleanup purposes)
|
|
30
|
+
*/
|
|
31
|
+
export declare class DialogManager {
|
|
32
|
+
id: string;
|
|
33
|
+
state: StateStore<DialogManagerState>;
|
|
34
|
+
constructor({ id }?: DialogManagerOptions);
|
|
35
|
+
get openDialogCount(): number;
|
|
36
|
+
getOrCreate({ id }: GetOrCreateDialogParams): Dialog;
|
|
37
|
+
open(params: GetOrCreateDialogParams, closeRest?: boolean): void;
|
|
38
|
+
close(id: DialogId): void;
|
|
39
|
+
closeAll(): void;
|
|
40
|
+
toggle(params: GetOrCreateDialogParams, closeAll?: boolean): void;
|
|
41
|
+
remove(id: DialogId): void;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { StateStore } from 'stream-chat';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps a map of Dialog objects.
|
|
4
|
+
* Dialog can be controlled via `Dialog` object retrieved using `useDialog()` hook.
|
|
5
|
+
* The hook returns an object with the following API:
|
|
6
|
+
*
|
|
7
|
+
* - `dialog.open()` - opens the dialog
|
|
8
|
+
* - `dialog.close()` - closes the dialog
|
|
9
|
+
* - `dialog.toggle()` - toggles the dialog open state. Accepts boolean argument closeAll. If enabled closes any other dialog that would be open.
|
|
10
|
+
* - `dialog.remove()` - removes the dialog object reference from the state (primarily for cleanup purposes)
|
|
11
|
+
*/
|
|
12
|
+
export class DialogManager {
|
|
13
|
+
constructor({ id } = {}) {
|
|
14
|
+
this.state = new StateStore({
|
|
15
|
+
dialogsById: {},
|
|
16
|
+
});
|
|
17
|
+
this.id = id ?? new Date().getTime().toString();
|
|
18
|
+
}
|
|
19
|
+
get openDialogCount() {
|
|
20
|
+
return Object.values(this.state.getLatestValue().dialogsById).reduce((count, dialog) => {
|
|
21
|
+
if (dialog.isOpen)
|
|
22
|
+
return count + 1;
|
|
23
|
+
return count;
|
|
24
|
+
}, 0);
|
|
25
|
+
}
|
|
26
|
+
getOrCreate({ id }) {
|
|
27
|
+
let dialog = this.state.getLatestValue().dialogsById[id];
|
|
28
|
+
if (!dialog) {
|
|
29
|
+
dialog = {
|
|
30
|
+
close: () => {
|
|
31
|
+
this.close(id);
|
|
32
|
+
},
|
|
33
|
+
id,
|
|
34
|
+
isOpen: false,
|
|
35
|
+
open: () => {
|
|
36
|
+
this.open({ id });
|
|
37
|
+
},
|
|
38
|
+
remove: () => {
|
|
39
|
+
this.remove(id);
|
|
40
|
+
},
|
|
41
|
+
toggle: (closeAll = false) => {
|
|
42
|
+
this.toggle({ id }, closeAll);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
this.state.next((current) => ({
|
|
46
|
+
...current,
|
|
47
|
+
...{ dialogsById: { ...current.dialogsById, [id]: dialog } },
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
return dialog;
|
|
51
|
+
}
|
|
52
|
+
open(params, closeRest) {
|
|
53
|
+
const dialog = this.getOrCreate(params);
|
|
54
|
+
if (dialog.isOpen)
|
|
55
|
+
return;
|
|
56
|
+
if (closeRest) {
|
|
57
|
+
this.closeAll();
|
|
58
|
+
}
|
|
59
|
+
this.state.next((current) => ({
|
|
60
|
+
...current,
|
|
61
|
+
dialogsById: { ...current.dialogsById, [dialog.id]: { ...dialog, isOpen: true } },
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
close(id) {
|
|
65
|
+
const dialog = this.state.getLatestValue().dialogsById[id];
|
|
66
|
+
if (!dialog?.isOpen)
|
|
67
|
+
return;
|
|
68
|
+
this.state.next((current) => ({
|
|
69
|
+
...current,
|
|
70
|
+
dialogsById: { ...current.dialogsById, [dialog.id]: { ...dialog, isOpen: false } },
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
closeAll() {
|
|
74
|
+
Object.values(this.state.getLatestValue().dialogsById).forEach((dialog) => dialog.close());
|
|
75
|
+
}
|
|
76
|
+
toggle(params, closeAll = false) {
|
|
77
|
+
if (this.state.getLatestValue().dialogsById[params.id]?.isOpen) {
|
|
78
|
+
this.close(params.id);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.open(params, closeAll);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
remove(id) {
|
|
85
|
+
const state = this.state.getLatestValue();
|
|
86
|
+
const dialog = state.dialogsById[id];
|
|
87
|
+
if (!dialog)
|
|
88
|
+
return;
|
|
89
|
+
this.state.next((current) => {
|
|
90
|
+
const newDialogs = { ...current.dialogsById };
|
|
91
|
+
delete newDialogs[id];
|
|
92
|
+
return {
|
|
93
|
+
...current,
|
|
94
|
+
dialogsById: newDialogs,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
export declare const DialogPortalDestination: () => React.JSX.Element;
|
|
3
|
+
type DialogPortalEntryProps = {
|
|
4
|
+
dialogId: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const DialogPortalEntry: ({ children, dialogId, }: PropsWithChildren<DialogPortalEntryProps>) => React.ReactPortal | null;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useLayoutEffect, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { useDialogIsOpen, useOpenedDialogCount } from './hooks';
|
|
4
|
+
import { useDialogManager } from '../../context';
|
|
5
|
+
export const DialogPortalDestination = () => {
|
|
6
|
+
const { dialogManager } = useDialogManager();
|
|
7
|
+
const openedDialogCount = useOpenedDialogCount();
|
|
8
|
+
return (React.createElement("div", { className: 'str-chat__dialog-overlay', "data-str-chat__portal-id": dialogManager.id, "data-testid": 'str-chat__dialog-overlay', onClick: () => dialogManager.closeAll(), style: {
|
|
9
|
+
'--str-chat__dialog-overlay-height': openedDialogCount > 0 ? '100%' : '0',
|
|
10
|
+
} }));
|
|
11
|
+
};
|
|
12
|
+
export const DialogPortalEntry = ({ children, dialogId, }) => {
|
|
13
|
+
const { dialogManager } = useDialogManager();
|
|
14
|
+
const dialogIsOpen = useDialogIsOpen(dialogId);
|
|
15
|
+
const [portalDestination, setPortalDestination] = useState(null);
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
const destination = document.querySelector(`div[data-str-chat__portal-id="${dialogManager.id}"]`);
|
|
18
|
+
if (!destination)
|
|
19
|
+
return;
|
|
20
|
+
setPortalDestination(destination);
|
|
21
|
+
}, [dialogManager, dialogIsOpen]);
|
|
22
|
+
if (!portalDestination)
|
|
23
|
+
return null;
|
|
24
|
+
return createPortal(children, portalDestination);
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useDialog';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useDialog';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GetOrCreateDialogParams } from '../DialogManager';
|
|
2
|
+
export declare const useDialog: ({ id }: GetOrCreateDialogParams) => import("../DialogManager").Dialog;
|
|
3
|
+
export declare const useDialogIsOpen: (id: string) => boolean;
|
|
4
|
+
export declare const useOpenedDialogCount: () => number;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import { useDialogManager } from '../../../context';
|
|
3
|
+
import { useStateStore } from '../../../store';
|
|
4
|
+
export const useDialog = ({ id }) => {
|
|
5
|
+
const { dialogManager } = useDialogManager();
|
|
6
|
+
useEffect(() => () => {
|
|
7
|
+
dialogManager.remove(id);
|
|
8
|
+
}, [dialogManager, id]);
|
|
9
|
+
return dialogManager.getOrCreate({ id });
|
|
10
|
+
};
|
|
11
|
+
export const useDialogIsOpen = (id) => {
|
|
12
|
+
const { dialogManager } = useDialogManager();
|
|
13
|
+
const dialogIsOpenSelector = useCallback(({ dialogsById }) => [!!dialogsById[id]?.isOpen], [id]);
|
|
14
|
+
return useStateStore(dialogManager.state, dialogIsOpenSelector)[0];
|
|
15
|
+
};
|
|
16
|
+
const openedDialogCountSelector = (nextValue) => [
|
|
17
|
+
Object.values(nextValue.dialogsById).reduce((count, dialog) => {
|
|
18
|
+
if (dialog.isOpen)
|
|
19
|
+
return count + 1;
|
|
20
|
+
return count;
|
|
21
|
+
}, 0),
|
|
22
|
+
];
|
|
23
|
+
export const useOpenedDialogCount = () => {
|
|
24
|
+
const { dialogManager } = useDialogManager();
|
|
25
|
+
return useStateStore(dialogManager.state, openedDialogCountSelector)[0];
|
|
26
|
+
};
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { AvatarProps } from '../Avatar';
|
|
3
3
|
import type { StreamMessage } from '../../context/ChannelStateContext';
|
|
4
4
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
5
|
-
import { TimestampFormatterOptions } from '../../i18n/
|
|
5
|
+
import type { TimestampFormatterOptions } from '../../i18n/types';
|
|
6
6
|
export type EventComponentProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = TimestampFormatterOptions & {
|
|
7
7
|
/** Message object */
|
|
8
8
|
message: StreamMessage<StreamChatGenerics>;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import React, { useCallback, useMemo
|
|
2
|
-
import { useActionHandler, useDeleteHandler, useEditHandler, useFlagHandler, useMarkUnreadHandler, useMentionsHandler, useMuteHandler, useOpenThreadHandler, usePinHandler,
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
|
+
import { useActionHandler, useDeleteHandler, useEditHandler, useFlagHandler, useMarkUnreadHandler, useMentionsHandler, useMuteHandler, useOpenThreadHandler, usePinHandler, 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,
|
|
@@ -75,7 +76,6 @@ export const Message = (props) => {
|
|
|
75
76
|
const { closeReactionSelectorOnClick, disableQuotedMessages, getDeleteMessageErrorNotification, getFetchReactionsErrorNotification, getFlagMessageErrorNotification, getFlagMessageSuccessNotification, getMarkMessageUnreadErrorNotification, getMarkMessageUnreadSuccessNotification, getMuteUserErrorNotification, getMuteUserSuccessNotification, getPinMessageErrorNotification, message, onlySenderCanEdit = false, onMentionsClick: propOnMentionsClick, onMentionsHover: propOnMentionsHover, openThread: propOpenThread, pinPermissions, reactionDetailsSort, retrySendMessage: propRetrySendMessage, sortReactionDetails, sortReactions, } = props;
|
|
76
77
|
const { addNotification } = useChannelActionContext('Message');
|
|
77
78
|
const { highlightedMessageId, mutes } = useChannelStateContext('Message');
|
|
78
|
-
const reactionSelectorRef = useRef(null);
|
|
79
79
|
const handleAction = useActionHandler(message);
|
|
80
80
|
const handleOpenThread = useOpenThreadHandler(message, propOpenThread);
|
|
81
81
|
const handleReaction = useReactionHandler(message);
|
|
@@ -112,7 +112,6 @@ export const Message = (props) => {
|
|
|
112
112
|
getErrorNotification: getPinMessageErrorNotification,
|
|
113
113
|
notify: addNotification,
|
|
114
114
|
});
|
|
115
|
-
const { isReactionEnabled, onReactionListClick, showDetailedReactions } = useReactionClick(message, reactionSelectorRef, undefined, closeReactionSelectorOnClick);
|
|
116
115
|
const highlighted = highlightedMessageId === message.id;
|
|
117
|
-
return (React.createElement(MemoizedMessage, { additionalMessageInputProps: props.additionalMessageInputProps, autoscrollToBottom: props.autoscrollToBottom, canPin: canPin, customMessageActions: props.customMessageActions, disableQuotedMessages: props.disableQuotedMessages, endOfGroup: props.endOfGroup, firstOfGroup: props.firstOfGroup, formatDate: props.formatDate, groupedByUser: props.groupedByUser, groupStyles: props.groupStyles, handleAction: handleAction, handleDelete: handleDelete, handleFetchReactions: handleFetchReactions, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handleOpenThread: handleOpenThread, handlePin: handlePin, handleReaction: handleReaction, handleRetry: handleRetry, highlighted: highlighted, initialMessage: props.initialMessage,
|
|
116
|
+
return (React.createElement(MemoizedMessage, { additionalMessageInputProps: props.additionalMessageInputProps, autoscrollToBottom: props.autoscrollToBottom, canPin: canPin, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: props.customMessageActions, disableQuotedMessages: props.disableQuotedMessages, endOfGroup: props.endOfGroup, firstOfGroup: props.firstOfGroup, formatDate: props.formatDate, groupedByUser: props.groupedByUser, groupStyles: props.groupStyles, handleAction: handleAction, handleDelete: handleDelete, handleFetchReactions: handleFetchReactions, handleFlag: handleFlag, handleMarkUnread: handleMarkUnread, handleMute: handleMute, handleOpenThread: handleOpenThread, handlePin: handlePin, handleReaction: handleReaction, handleRetry: handleRetry, highlighted: highlighted, initialMessage: props.initialMessage, lastReceivedId: props.lastReceivedId, message: message, Message: props.Message, messageActions: props.messageActions, messageListRect: props.messageListRect, mutes: mutes, onMentionsClickMessage: onMentionsClick, onMentionsHoverMessage: onMentionsHover, onUserClick: props.onUserClick, onUserHover: props.onUserHover, pinPermissions: props.pinPermissions, reactionDetailsSort: reactionDetailsSort, readBy: props.readBy, renderText: props.renderText, sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: props.threadList, unsafeHTML: props.unsafeHTML, userRoles: userRoles }));
|
|
118
117
|
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { MessageContextValue } from '../../context/MessageContext';
|
|
3
2
|
import type { DefaultStreamChatGenerics, IconProps } from '../../types/types';
|
|
3
|
+
import type { MessageContextValue } from '../../context/MessageContext';
|
|
4
4
|
export type MessageOptionsProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Partial<Pick<MessageContextValue<StreamChatGenerics>, 'handleOpenThread'>> & {
|
|
5
5
|
ActionsIcon?: React.ComponentType<IconProps>;
|
|
6
6
|
displayReplies?: boolean;
|
|
7
|
-
messageWrapperRef?: React.RefObject<HTMLDivElement>;
|
|
8
7
|
ReactionIcon?: React.ComponentType<IconProps>;
|
|
9
8
|
theme?: string;
|
|
10
9
|
ThreadIcon?: React.ComponentType<IconProps>;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import { ActionsIcon as DefaultActionsIcon, ReactionIcon as DefaultReactionIcon, ThreadIcon as DefaultThreadIcon, } from './icons';
|
|
3
|
-
import { MESSAGE_ACTIONS
|
|
4
|
+
import { MESSAGE_ACTIONS } from './utils';
|
|
4
5
|
import { MessageActions } from '../MessageActions';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { useDialogIsOpen } from '../Dialog';
|
|
7
|
+
import { ReactionSelectorWithButton } from '../Reactions/ReactionSelectorWithButton';
|
|
8
|
+
import { useMessageContext, useTranslationContext } from '../../context';
|
|
7
9
|
const UnMemoizedMessageOptions = (props) => {
|
|
8
|
-
const { ActionsIcon = DefaultActionsIcon, displayReplies = true, handleOpenThread: propHandleOpenThread,
|
|
9
|
-
const {
|
|
10
|
+
const { ActionsIcon = DefaultActionsIcon, displayReplies = true, handleOpenThread: propHandleOpenThread, ReactionIcon = DefaultReactionIcon, theme = 'simple', ThreadIcon = DefaultThreadIcon, } = props;
|
|
11
|
+
const { getMessageActions, handleOpenThread: contextHandleOpenThread, initialMessage, message, threadList, } = useMessageContext('MessageOptions');
|
|
10
12
|
const { t } = useTranslationContext('MessageOptions');
|
|
13
|
+
const messageActionsDialogIsOpen = useDialogIsOpen(`message-actions--${message.id}`);
|
|
14
|
+
const reactionSelectorDialogIsOpen = useDialogIsOpen(`reaction-selector--${message.id}`);
|
|
11
15
|
const handleOpenThread = propHandleOpenThread || contextHandleOpenThread;
|
|
12
16
|
const messageActions = getMessageActions();
|
|
13
|
-
const showActionsBox = showMessageActionsBox(messageActions, threadList) || !!customMessageActions;
|
|
14
17
|
const shouldShowReactions = messageActions.indexOf(MESSAGE_ACTIONS.react) > -1;
|
|
15
18
|
const shouldShowReplies = messageActions.indexOf(MESSAGE_ACTIONS.reply) > -1 && displayReplies && !threadList;
|
|
16
19
|
if (!message.type ||
|
|
@@ -22,12 +25,12 @@ const UnMemoizedMessageOptions = (props) => {
|
|
|
22
25
|
initialMessage) {
|
|
23
26
|
return null;
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
return (React.createElement("div", { className: clsx(`str-chat__message-${theme}__actions str-chat__message-options`, {
|
|
29
|
+
'str-chat__message-options--active': messageActionsDialogIsOpen || reactionSelectorDialogIsOpen,
|
|
30
|
+
}), "data-testid": 'message-options' },
|
|
31
|
+
React.createElement(MessageActions, { ActionsIcon: ActionsIcon }),
|
|
28
32
|
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
33
|
React.createElement(ThreadIcon, { className: 'str-chat__message-action-icon' }))),
|
|
30
|
-
shouldShowReactions && (React.createElement(
|
|
31
|
-
React.createElement(ReactionIcon, { className: 'str-chat__message-action-icon' })))));
|
|
34
|
+
shouldShowReactions && (React.createElement(ReactionSelectorWithButton, { ReactionIcon: ReactionIcon, theme: theme }))));
|
|
32
35
|
};
|
|
33
36
|
export const MessageOptions = React.memo(UnMemoizedMessageOptions);
|
|
@@ -10,22 +10,23 @@ 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';
|
|
16
17
|
import { Modal } from '../Modal';
|
|
17
|
-
import { ReactionsList as DefaultReactionList
|
|
18
|
+
import { ReactionsList as DefaultReactionList } from '../Reactions';
|
|
18
19
|
import { MessageBounceModal } from '../MessageBounce/MessageBounceModal';
|
|
19
20
|
import { useComponentContext } from '../../context/ComponentContext';
|
|
20
21
|
import { useMessageContext } from '../../context/MessageContext';
|
|
21
22
|
import { useTranslationContext } from '../../context';
|
|
22
23
|
import { MessageEditedTimestamp } from './MessageEditedTimestamp';
|
|
23
24
|
const MessageSimpleWithContext = (props) => {
|
|
24
|
-
const { additionalMessageInputProps, clearEditingState, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMyMessage,
|
|
25
|
+
const { additionalMessageInputProps, clearEditingState, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMyMessage, message, onUserClick, onUserHover, renderText, threadList, } = 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,
|
|
29
|
+
const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, EditMessageInput = DefaultEditMessageForm, MessageDeleted = DefaultMessageDeleted, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageOptions = DefaultMessageOptions, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, 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) {
|
|
@@ -34,13 +35,6 @@ const MessageSimpleWithContext = (props) => {
|
|
|
34
35
|
if (message.deleted_at || message.type === 'deleted') {
|
|
35
36
|
return React.createElement(MessageDeleted, { message: message });
|
|
36
37
|
}
|
|
37
|
-
/** FIXME: isReactionEnabled should be removed with next major version and a proper centralized permissions logic should be put in place
|
|
38
|
-
* With the current permissions implementation it would be sth like:
|
|
39
|
-
* const messageActions = getMessageActions();
|
|
40
|
-
* const canReact = messageActions.includes(MESSAGE_ACTIONS.react);
|
|
41
|
-
*/
|
|
42
|
-
const canReact = isReactionEnabled;
|
|
43
|
-
const canShowReactions = hasReactions;
|
|
44
38
|
const showMetadata = !groupedByUser || endOfGroup;
|
|
45
39
|
const showReplyCountButton = !threadList && !!message.reply_count;
|
|
46
40
|
const allowRetry = message.status === 'failed' && message.errorStatusCode !== 403;
|
|
@@ -62,7 +56,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
62
56
|
'str-chat__message--has-attachment': hasAttachment,
|
|
63
57
|
'str-chat__message--highlighted': highlighted,
|
|
64
58
|
'str-chat__message--pinned pinned-message': message.pinned,
|
|
65
|
-
'str-chat__message--with-reactions':
|
|
59
|
+
'str-chat__message--with-reactions': hasReactions,
|
|
66
60
|
'str-chat__message-send-can-be-retried': message?.status === 'failed' && message?.errorStatusCode !== 403,
|
|
67
61
|
'str-chat__message-with-thread-link': showReplyCountButton,
|
|
68
62
|
'str-chat__virtual-message__wrapper--end': endOfGroup,
|
|
@@ -80,9 +74,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
80
74
|
'str-chat__simple-message--error-failed': allowRetry || isBounced,
|
|
81
75
|
}), "data-testid": 'message-inner', onClick: handleClick, onKeyUp: handleClick },
|
|
82
76
|
React.createElement(MessageOptions, null),
|
|
83
|
-
React.createElement("div", { className: 'str-chat__message-reactions-host' },
|
|
84
|
-
canShowReactions && React.createElement(ReactionsList, { reverse: true }),
|
|
85
|
-
showDetailedReactions && canReact && React.createElement(ReactionSelector, { ref: reactionSelectorRef })),
|
|
77
|
+
React.createElement("div", { className: 'str-chat__message-reactions-host' }, hasReactions && React.createElement(ReactionsList, { reverse: true })),
|
|
86
78
|
React.createElement("div", { className: 'str-chat__message-bubble' },
|
|
87
79
|
message.attachments?.length && !message.quoted_message ? (React.createElement(Attachment, { actionHandler: handleAction, attachments: message.attachments })) : null,
|
|
88
80
|
React.createElement(MessageText, { message: message, renderText: renderText }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { StreamMessage } from '../../context/ChannelStateContext';
|
|
3
|
+
import type { TimestampFormatterOptions } from '../../i18n/types';
|
|
3
4
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
4
|
-
import type { TimestampFormatterOptions } from '../../i18n/utils';
|
|
5
5
|
export type MessageTimestampProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = TimestampFormatterOptions & {
|
|
6
6
|
customClass?: string;
|
|
7
7
|
message?: StreamMessage<StreamChatGenerics>;
|
|
@@ -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');
|