stream-chat-react 12.8.0 → 12.8.2
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/Chat/hooks/useChat.js +1 -1
- package/dist/components/Dialog/DialogManager.d.ts +8 -0
- package/dist/components/Dialog/DialogManager.js +42 -0
- package/dist/components/Dialog/hooks/useDialog.js +5 -1
- package/dist/components/MessageList/VirtualizedMessageListComponents.js +12 -19
- package/dist/components/MessageList/hooks/useMarkRead.js +3 -6
- package/dist/components/MessageList/renderMessages.js +14 -21
- package/dist/components/MessageList/utils.d.ts +9 -0
- package/dist/components/MessageList/utils.js +8 -0
- package/dist/experimental/index.browser.cjs +1 -1
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs +1 -1
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/index.browser.cjs +87 -26
- package/dist/index.browser.cjs.map +2 -2
- package/dist/index.node.cjs +88 -26
- package/dist/index.node.cjs.map +2 -2
- package/package.json +2 -2
|
@@ -28,7 +28,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
28
28
|
if (!userAgent.includes('stream-chat-react')) {
|
|
29
29
|
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
30
30
|
// the upper-case text between double underscores is replaced with the actual semantic version of the library
|
|
31
|
-
client.setUserAgent(`stream-chat-react-12.8.
|
|
31
|
+
client.setUserAgent(`stream-chat-react-12.8.2-${userAgent}`);
|
|
32
32
|
}
|
|
33
33
|
client.threads.registerSubscriptions();
|
|
34
34
|
client.polls.registerSubscriptions();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import { StateStore } from 'stream-chat';
|
|
2
3
|
export type GetOrCreateDialogParams = {
|
|
3
4
|
id: DialogId;
|
|
@@ -8,6 +9,7 @@ export type Dialog = {
|
|
|
8
9
|
id: DialogId;
|
|
9
10
|
isOpen: boolean | undefined;
|
|
10
11
|
open: (zIndex?: number) => void;
|
|
12
|
+
removalTimeout: NodeJS.Timeout | undefined;
|
|
11
13
|
remove: () => void;
|
|
12
14
|
toggle: (closeAll?: boolean) => void;
|
|
13
15
|
};
|
|
@@ -39,5 +41,11 @@ export declare class DialogManager {
|
|
|
39
41
|
closeAll(): void;
|
|
40
42
|
toggle(params: GetOrCreateDialogParams, closeAll?: boolean): void;
|
|
41
43
|
remove(id: DialogId): void;
|
|
44
|
+
/**
|
|
45
|
+
* Marks the dialog state as unused. If the dialog id is referenced again quickly,
|
|
46
|
+
* the state will not be removed. Otherwise, the state will be removed after
|
|
47
|
+
* a short timeout.
|
|
48
|
+
*/
|
|
49
|
+
markForRemoval(id: DialogId): void;
|
|
42
50
|
}
|
|
43
51
|
export {};
|
|
@@ -35,6 +35,7 @@ export class DialogManager {
|
|
|
35
35
|
open: () => {
|
|
36
36
|
this.open({ id });
|
|
37
37
|
},
|
|
38
|
+
removalTimeout: undefined,
|
|
38
39
|
remove: () => {
|
|
39
40
|
this.remove(id);
|
|
40
41
|
},
|
|
@@ -47,6 +48,21 @@ export class DialogManager {
|
|
|
47
48
|
...{ dialogsById: { ...current.dialogsById, [id]: dialog } },
|
|
48
49
|
}));
|
|
49
50
|
}
|
|
51
|
+
if (dialog.removalTimeout) {
|
|
52
|
+
clearTimeout(dialog.removalTimeout);
|
|
53
|
+
this.state.next((current) => ({
|
|
54
|
+
...current,
|
|
55
|
+
...{
|
|
56
|
+
dialogsById: {
|
|
57
|
+
...current.dialogsById,
|
|
58
|
+
[id]: {
|
|
59
|
+
...dialog,
|
|
60
|
+
removalTimeout: undefined,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
50
66
|
return dialog;
|
|
51
67
|
}
|
|
52
68
|
open(params, closeRest) {
|
|
@@ -86,6 +102,9 @@ export class DialogManager {
|
|
|
86
102
|
const dialog = state.dialogsById[id];
|
|
87
103
|
if (!dialog)
|
|
88
104
|
return;
|
|
105
|
+
if (dialog.removalTimeout) {
|
|
106
|
+
clearTimeout(dialog.removalTimeout);
|
|
107
|
+
}
|
|
89
108
|
this.state.next((current) => {
|
|
90
109
|
const newDialogs = { ...current.dialogsById };
|
|
91
110
|
delete newDialogs[id];
|
|
@@ -95,4 +114,27 @@ export class DialogManager {
|
|
|
95
114
|
};
|
|
96
115
|
});
|
|
97
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Marks the dialog state as unused. If the dialog id is referenced again quickly,
|
|
119
|
+
* the state will not be removed. Otherwise, the state will be removed after
|
|
120
|
+
* a short timeout.
|
|
121
|
+
*/
|
|
122
|
+
markForRemoval(id) {
|
|
123
|
+
const dialog = this.state.getLatestValue().dialogsById[id];
|
|
124
|
+
if (!dialog) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.state.next((current) => ({
|
|
128
|
+
...current,
|
|
129
|
+
dialogsById: {
|
|
130
|
+
...current.dialogsById,
|
|
131
|
+
[id]: {
|
|
132
|
+
...dialog,
|
|
133
|
+
removalTimeout: setTimeout(() => {
|
|
134
|
+
this.remove(id);
|
|
135
|
+
}, 16),
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
98
140
|
}
|
|
@@ -4,7 +4,11 @@ import { useStateStore } from '../../../store';
|
|
|
4
4
|
export const useDialog = ({ id }) => {
|
|
5
5
|
const { dialogManager } = useDialogManager();
|
|
6
6
|
useEffect(() => () => {
|
|
7
|
-
|
|
7
|
+
// Since this cleanup can run even if the component is still mounted
|
|
8
|
+
// and dialog id is unchanged (e.g. in <StrictMode />), it's safer to
|
|
9
|
+
// mark state as unused and only remove it after a timeout, rather than
|
|
10
|
+
// to remove it immediately.
|
|
11
|
+
dialogManager.markForRemoval(id);
|
|
8
12
|
}, [dialogManager, id]);
|
|
9
13
|
return dialogManager.getOrCreate({ id });
|
|
10
14
|
};
|
|
@@ -5,7 +5,7 @@ import { EmptyStateIndicator as DefaultEmptyStateIndicator } from '../EmptyState
|
|
|
5
5
|
import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
|
|
6
6
|
import { isMessageEdited, Message } from '../Message';
|
|
7
7
|
import { useComponentContext } from '../../context';
|
|
8
|
-
import { isDateSeparatorMessage } from './utils';
|
|
8
|
+
import { getIsFirstUnreadMessage, isDateSeparatorMessage } from './utils';
|
|
9
9
|
const PREPEND_OFFSET = 10 ** 7;
|
|
10
10
|
export function calculateItemIndex(virtuosoIndex, numItemsPrepended) {
|
|
11
11
|
return virtuosoIndex + numItemsPrepended - PREPEND_OFFSET;
|
|
@@ -72,24 +72,17 @@ export const messageRenderer = (virtuosoIndex, _data, virtuosoContext) => {
|
|
|
72
72
|
(maybePrevMessage && isMessageEdited(maybePrevMessage)));
|
|
73
73
|
const endOfGroup = shouldGroupByUser &&
|
|
74
74
|
(message.user?.id !== maybeNextMessage?.user?.id || isMessageEdited(message));
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
lastReadTimestamp &&
|
|
85
|
-
createdAtTimestamp > lastReadTimestamp &&
|
|
86
|
-
isFirstMessage);
|
|
87
|
-
const showUnreadSeparatorAbove = !lastReadMessageId && isFirstUnreadMessage;
|
|
88
|
-
const showUnreadSeparatorBelow = isLastReadMessage && !isNewestMessage && (firstUnreadMessageId || !!unreadMessageCount);
|
|
75
|
+
const isFirstUnreadMessage = getIsFirstUnreadMessage({
|
|
76
|
+
firstUnreadMessageId,
|
|
77
|
+
isFirstMessage: streamMessageIndex === 0,
|
|
78
|
+
lastReadDate,
|
|
79
|
+
lastReadMessageId,
|
|
80
|
+
message,
|
|
81
|
+
previousMessage: streamMessageIndex ? messageList[streamMessageIndex - 1] : undefined,
|
|
82
|
+
unreadMessageCount,
|
|
83
|
+
});
|
|
89
84
|
return (React.createElement(React.Fragment, null,
|
|
90
|
-
|
|
85
|
+
isFirstUnreadMessage && (React.createElement("div", { className: 'str-chat__unread-messages-separator-wrapper' },
|
|
91
86
|
React.createElement(UnreadMessagesSeparator, { unreadCount: unreadMessageCount }))),
|
|
92
|
-
React.createElement(Message, { additionalMessageInputProps: additionalMessageInputProps, autoscrollToBottom: virtuosoRef.current?.autoscrollToBottom, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: customMessageActions, endOfGroup: endOfGroup, firstOfGroup: firstOfGroup, formatDate: formatDate, groupedByUser: groupedByUser, groupStyles: [messageGroupStyles[message.id] ?? ''], lastReceivedId: lastReceivedMessageId, message: message, Message: MessageUIComponent, messageActions: messageActions, openThread: openThread, reactionDetailsSort: reactionDetailsSort, readBy: ownMessagesReadByOthers[message.id] || [], sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: threadList })
|
|
93
|
-
showUnreadSeparatorBelow && (React.createElement("div", { className: 'str-chat__unread-messages-separator-wrapper' },
|
|
94
|
-
React.createElement(UnreadMessagesSeparator, { unreadCount: unreadMessageCount })))));
|
|
87
|
+
React.createElement(Message, { additionalMessageInputProps: additionalMessageInputProps, autoscrollToBottom: virtuosoRef.current?.autoscrollToBottom, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: customMessageActions, endOfGroup: endOfGroup, firstOfGroup: firstOfGroup, formatDate: formatDate, groupedByUser: groupedByUser, groupStyles: [messageGroupStyles[message.id] ?? ''], lastReceivedId: lastReceivedMessageId, message: message, Message: MessageUIComponent, messageActions: messageActions, openThread: openThread, reactionDetailsSort: reactionDetailsSort, readBy: ownMessagesReadByOthers[message.id] || [], sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: threadList })));
|
|
95
88
|
};
|
|
@@ -26,7 +26,6 @@ export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread
|
|
|
26
26
|
markRead();
|
|
27
27
|
};
|
|
28
28
|
const handleMessageNew = (event) => {
|
|
29
|
-
const newMessageToCurrentChannel = event.cid === channel.cid;
|
|
30
29
|
const isOwnMessage = event.user?.id && event.user.id === client.user?.id;
|
|
31
30
|
const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel;
|
|
32
31
|
if (isOwnMessage)
|
|
@@ -45,13 +44,11 @@ export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread
|
|
|
45
44
|
};
|
|
46
45
|
});
|
|
47
46
|
}
|
|
48
|
-
else if (
|
|
49
|
-
mainChannelUpdated &&
|
|
50
|
-
shouldMarkRead(channel.countUnread())) {
|
|
47
|
+
else if (mainChannelUpdated && shouldMarkRead(channel.countUnread())) {
|
|
51
48
|
markRead();
|
|
52
49
|
}
|
|
53
50
|
};
|
|
54
|
-
|
|
51
|
+
channel.on('message.new', handleMessageNew);
|
|
55
52
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
56
53
|
const hasScrolledToBottom = previousRenderMessageListScrolledToBottom.current !== isMessageListScrolledToBottom &&
|
|
57
54
|
isMessageListScrolledToBottom;
|
|
@@ -59,7 +56,7 @@ export const useMarkRead = ({ isMessageListScrolledToBottom, messageListIsThread
|
|
|
59
56
|
markRead();
|
|
60
57
|
previousRenderMessageListScrolledToBottom.current = isMessageListScrolledToBottom;
|
|
61
58
|
return () => {
|
|
62
|
-
|
|
59
|
+
channel.off('message.new', handleMessageNew);
|
|
63
60
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
64
61
|
};
|
|
65
62
|
}, [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { Fragment } from 'react';
|
|
2
|
-
import { isDateSeparatorMessage } from './utils';
|
|
2
|
+
import { getIsFirstUnreadMessage, isDateSeparatorMessage } from './utils';
|
|
3
3
|
import { Message } from '../Message';
|
|
4
4
|
import { DateSeparator as DefaultDateSeparator } from '../DateSeparator';
|
|
5
5
|
import { EventComponent as DefaultMessageSystem } from '../EventComponent';
|
|
@@ -9,6 +9,7 @@ export function defaultRenderMessages({ channelUnreadUiState, components, custom
|
|
|
9
9
|
const { DateSeparator = DefaultDateSeparator, HeaderComponent, MessageSystem = DefaultMessageSystem, UnreadMessagesSeparator = DefaultUnreadMessagesSeparator, } = components;
|
|
10
10
|
const renderedMessages = [];
|
|
11
11
|
let firstMessage;
|
|
12
|
+
let previousMessage = undefined;
|
|
12
13
|
for (let index = 0; index < messages.length; index++) {
|
|
13
14
|
const message = messages[index];
|
|
14
15
|
if (isDateSeparatorMessage(message)) {
|
|
@@ -29,29 +30,21 @@ export function defaultRenderMessages({ channelUnreadUiState, components, custom
|
|
|
29
30
|
}
|
|
30
31
|
const groupStyles = messageGroupStyles[message.id] || '';
|
|
31
32
|
const messageClass = customClasses?.message || `str-chat__li str-chat__li--${groupStyles}`;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
!!lastReadTimestamp &&
|
|
42
|
-
createdAtTimestamp > lastReadTimestamp &&
|
|
43
|
-
isFirstMessage);
|
|
44
|
-
const showUnreadSeparatorAbove = !channelUnreadUiState?.last_read_message_id && isFirstUnreadMessage;
|
|
45
|
-
const showUnreadSeparatorBelow = isLastReadMessage &&
|
|
46
|
-
!isNewestMessage &&
|
|
47
|
-
(channelUnreadUiState?.first_unread_message_id || !!channelUnreadUiState?.unread_messages); // this part has to be here as we do not mark channel read when sending a message
|
|
33
|
+
const isFirstUnreadMessage = getIsFirstUnreadMessage({
|
|
34
|
+
firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id,
|
|
35
|
+
isFirstMessage: !!firstMessage?.id && firstMessage.id === message.id,
|
|
36
|
+
lastReadDate: channelUnreadUiState?.last_read,
|
|
37
|
+
lastReadMessageId: channelUnreadUiState?.last_read_message_id,
|
|
38
|
+
message,
|
|
39
|
+
previousMessage,
|
|
40
|
+
unreadMessageCount: channelUnreadUiState?.unread_messages,
|
|
41
|
+
});
|
|
48
42
|
renderedMessages.push(React.createElement(Fragment, { key: message.id || message.created_at },
|
|
49
|
-
|
|
43
|
+
isFirstUnreadMessage && UnreadMessagesSeparator && (React.createElement("li", { className: 'str-chat__li str-chat__unread-messages-separator-wrapper' },
|
|
50
44
|
React.createElement(UnreadMessagesSeparator, { unreadCount: channelUnreadUiState?.unread_messages }))),
|
|
51
45
|
React.createElement("li", { className: messageClass, "data-message-id": message.id, "data-testid": messageClass },
|
|
52
|
-
React.createElement(Message, { groupStyles: [groupStyles], lastReceivedId: lastReceivedId, message: message, readBy: readData[message.id] || [], ...messageProps }))
|
|
53
|
-
|
|
54
|
-
React.createElement(UnreadMessagesSeparator, { unreadCount: channelUnreadUiState?.unread_messages })))));
|
|
46
|
+
React.createElement(Message, { groupStyles: [groupStyles], lastReceivedId: lastReceivedId, message: message, readBy: readData[message.id] || [], ...messageProps }))));
|
|
47
|
+
previousMessage = message;
|
|
55
48
|
}
|
|
56
49
|
}
|
|
57
50
|
return renderedMessages;
|
|
@@ -68,4 +68,13 @@ type DateSeparatorMessage = {
|
|
|
68
68
|
unread: boolean;
|
|
69
69
|
};
|
|
70
70
|
export declare function isDateSeparatorMessage<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(message: StreamMessage<StreamChatGenerics>): message is DateSeparatorMessage;
|
|
71
|
+
export declare const getIsFirstUnreadMessage: ({ firstUnreadMessageId, isFirstMessage, lastReadDate, lastReadMessageId, message, previousMessage, unreadMessageCount, }: {
|
|
72
|
+
isFirstMessage: boolean;
|
|
73
|
+
message: StreamMessage;
|
|
74
|
+
firstUnreadMessageId?: string;
|
|
75
|
+
lastReadDate?: Date;
|
|
76
|
+
lastReadMessageId?: string;
|
|
77
|
+
previousMessage?: StreamMessage;
|
|
78
|
+
unreadMessageCount?: number;
|
|
79
|
+
}) => boolean;
|
|
71
80
|
export {};
|
|
@@ -241,3 +241,11 @@ export const hasNotMoreMessages = (returnedCountMessages, limit) => returnedCoun
|
|
|
241
241
|
export function isDateSeparatorMessage(message) {
|
|
242
242
|
return message.customType === CUSTOM_MESSAGE_TYPE.date && !!message.date && isDate(message.date);
|
|
243
243
|
}
|
|
244
|
+
export const getIsFirstUnreadMessage = ({ firstUnreadMessageId, isFirstMessage, lastReadDate, lastReadMessageId, message, previousMessage, unreadMessageCount = 0, }) => {
|
|
245
|
+
const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
|
|
246
|
+
const lastReadTimestamp = lastReadDate?.getTime();
|
|
247
|
+
const messageIsUnread = !!createdAtTimestamp && !!lastReadTimestamp && createdAtTimestamp > lastReadTimestamp;
|
|
248
|
+
const previousMessageIsLastRead = !!lastReadMessageId && lastReadMessageId === previousMessage?.id;
|
|
249
|
+
return (firstUnreadMessageId === message.id ||
|
|
250
|
+
(!!unreadMessageCount && messageIsUnread && (isFirstMessage || previousMessageIsLastRead)));
|
|
251
|
+
};
|