stream-chat-react 12.0.0-rc.9 → 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 +76 -24
- 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 +10817 -10579
- 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 +10704 -10482
- 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,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');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { TimestampFormatterOptions } from '../../i18n/
|
|
2
|
+
import type { TimestampFormatterOptions } from '../../i18n/types';
|
|
3
3
|
export interface TimestampProps extends TimestampFormatterOptions {
|
|
4
4
|
customClass?: string;
|
|
5
5
|
timestamp?: Date | string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { useMessageContext } from '../../context/MessageContext';
|
|
3
|
-
import {
|
|
4
|
-
import { getDateString } from '../../i18n/utils';
|
|
3
|
+
import { useTranslationContext } from '../../context/TranslationContext';
|
|
4
|
+
import { getDateString, isDate } from '../../i18n/utils';
|
|
5
5
|
export function Timestamp(props) {
|
|
6
6
|
const { calendar, calendarFormats, customClass, format, timestamp } = props;
|
|
7
7
|
const { formatDate } = useMessageContext('MessageTimestamp');
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { StreamMessage } from '../../../context/ChannelStateContext';
|
|
3
|
-
import type { ReactEventHandler } from '../types';
|
|
4
3
|
import type { DefaultStreamChatGenerics } from '../../../types/types';
|
|
5
4
|
export declare const reactionHandlerWarning = "Reaction handler was called, but it is missing one of its required arguments.\nMake sure the ChannelAction and ChannelState contexts are properly set and the hook is initialized with a valid message.";
|
|
6
5
|
export declare const useReactionHandler: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(message?: StreamMessage<StreamChatGenerics>) => (reactionType: string, event?: React.BaseSyntheticEvent) => Promise<void>;
|
|
7
|
-
export declare const useReactionClick: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(message?: StreamMessage<StreamChatGenerics>, reactionSelectorRef?: RefObject<HTMLDivElement | null>, messageWrapperRef?: RefObject<HTMLDivElement | null>, closeReactionSelectorOnClick?: boolean) => {
|
|
8
|
-
isReactionEnabled: boolean;
|
|
9
|
-
onReactionListClick: ReactEventHandler;
|
|
10
|
-
showDetailedReactions: boolean;
|
|
11
|
-
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { useCallback
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
2
|
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) => {
|
|
@@ -107,65 +114,3 @@ export const useReactionHandler = (message) => {
|
|
|
107
114
|
}
|
|
108
115
|
};
|
|
109
116
|
};
|
|
110
|
-
export const useReactionClick = (message, reactionSelectorRef, messageWrapperRef, closeReactionSelectorOnClick) => {
|
|
111
|
-
const { channelCapabilities = {} } = useChannelStateContext('useReactionClick');
|
|
112
|
-
const [showDetailedReactions, setShowDetailedReactions] = useState(false);
|
|
113
|
-
const hasListener = useRef(false);
|
|
114
|
-
const isReactionEnabled = channelCapabilities['send-reaction'];
|
|
115
|
-
const messageDeleted = !!message?.deleted_at;
|
|
116
|
-
const closeDetailedReactions = useCallback((event) => {
|
|
117
|
-
if (event.target instanceof HTMLElement &&
|
|
118
|
-
reactionSelectorRef?.current?.contains(event.target) &&
|
|
119
|
-
!closeReactionSelectorOnClick) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
setShowDetailedReactions(false);
|
|
123
|
-
},
|
|
124
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
125
|
-
[setShowDetailedReactions, reactionSelectorRef]);
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
const messageWrapper = messageWrapperRef?.current;
|
|
128
|
-
if (showDetailedReactions && !hasListener.current) {
|
|
129
|
-
hasListener.current = true;
|
|
130
|
-
document.addEventListener('click', closeDetailedReactions);
|
|
131
|
-
if (messageWrapper) {
|
|
132
|
-
messageWrapper.addEventListener('mouseleave', closeDetailedReactions);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (!showDetailedReactions && hasListener.current) {
|
|
136
|
-
document.removeEventListener('click', closeDetailedReactions);
|
|
137
|
-
if (messageWrapper) {
|
|
138
|
-
messageWrapper.removeEventListener('mouseleave', closeDetailedReactions);
|
|
139
|
-
}
|
|
140
|
-
hasListener.current = false;
|
|
141
|
-
}
|
|
142
|
-
return () => {
|
|
143
|
-
if (hasListener.current) {
|
|
144
|
-
document.removeEventListener('click', closeDetailedReactions);
|
|
145
|
-
if (messageWrapper) {
|
|
146
|
-
messageWrapper.removeEventListener('mouseleave', closeDetailedReactions);
|
|
147
|
-
}
|
|
148
|
-
hasListener.current = false;
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
}, [showDetailedReactions, closeDetailedReactions, messageWrapperRef]);
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
const messageWrapper = messageWrapperRef?.current;
|
|
154
|
-
if (messageDeleted && hasListener.current) {
|
|
155
|
-
document.removeEventListener('click', closeDetailedReactions);
|
|
156
|
-
if (messageWrapper) {
|
|
157
|
-
messageWrapper.removeEventListener('mouseleave', closeDetailedReactions);
|
|
158
|
-
}
|
|
159
|
-
hasListener.current = false;
|
|
160
|
-
}
|
|
161
|
-
}, [messageDeleted, closeDetailedReactions, messageWrapperRef]);
|
|
162
|
-
const onReactionListClick = (event) => {
|
|
163
|
-
event?.stopPropagation?.();
|
|
164
|
-
setShowDetailedReactions((prev) => !prev);
|
|
165
|
-
};
|
|
166
|
-
return {
|
|
167
|
-
isReactionEnabled,
|
|
168
|
-
onReactionListClick,
|
|
169
|
-
showDetailedReactions,
|
|
170
|
-
};
|
|
171
|
-
};
|