stream-chat-react 12.15.0 → 12.15.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.js +2 -1
- package/dist/components/MessageInput/AttachmentSelector.js +3 -3
- package/dist/components/MessageInput/MessageInput.js +4 -2
- package/dist/components/MessageInput/hooks/useSubmitHandler.js +12 -6
- package/dist/components/MessageList/MessageList.js +4 -2
- package/dist/components/MessageList/VirtualizedMessageList.js +4 -2
- package/dist/components/Poll/PollActions/PollResults/PollResults.js +1 -1
- package/dist/components/Poll/PollCreationDialog/PollCreationDialog.js +3 -2
- package/dist/components/UtilityComponents/useStableId.d.ts +5 -0
- package/dist/components/UtilityComponents/useStableId.js +11 -0
- package/dist/experimental/index.browser.cjs.map +1 -1
- package/dist/experimental/index.node.cjs.map +1 -1
- package/dist/index.browser.cjs +699 -687
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +588 -576
- package/dist/index.node.cjs.map +4 -4
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
24
24
|
useEffect(() => {
|
|
25
25
|
if (!client)
|
|
26
26
|
return;
|
|
27
|
-
const version = "12.15.
|
|
27
|
+
const version = "12.15.2";
|
|
28
28
|
const userAgent = client.getUserAgent();
|
|
29
29
|
if (!userAgent.includes('stream-chat-react')) {
|
|
30
30
|
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
1
2
|
import { StateStore } from 'stream-chat';
|
|
2
3
|
/**
|
|
3
4
|
* Keeps a map of Dialog objects.
|
|
@@ -14,7 +15,7 @@ export class DialogManager {
|
|
|
14
15
|
this.state = new StateStore({
|
|
15
16
|
dialogsById: {},
|
|
16
17
|
});
|
|
17
|
-
this.id = id ??
|
|
18
|
+
this.id = id ?? nanoid();
|
|
18
19
|
}
|
|
19
20
|
get openDialogCount() {
|
|
20
21
|
return Object.values(this.state.getLatestValue().dialogsById).reduce((count, dialog) => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
2
|
import { UploadIcon as DefaultUploadIcon } from './icons';
|
|
4
3
|
import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
|
|
5
4
|
import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
|
|
@@ -10,11 +9,12 @@ import { Portal } from '../Portal/Portal';
|
|
|
10
9
|
import { UploadFileInput } from '../ReactFileUtilities';
|
|
11
10
|
import { useChannelStateContext, useComponentContext, useMessageInputContext, useTranslationContext, } from '../../context';
|
|
12
11
|
import { AttachmentSelectorContextProvider, useAttachmentSelectorContext, } from '../../context/AttachmentSelectorContext';
|
|
12
|
+
import { useStableId } from '../UtilityComponents/useStableId';
|
|
13
13
|
export const SimpleAttachmentSelector = () => {
|
|
14
14
|
const { AttachmentSelectorInitiationButtonContents, FileUploadIcon = DefaultUploadIcon, } = useComponentContext();
|
|
15
15
|
const inputRef = useRef(null);
|
|
16
16
|
const [labelElement, setLabelElement] = useState(null);
|
|
17
|
-
const id =
|
|
17
|
+
const id = useStableId();
|
|
18
18
|
useEffect(() => {
|
|
19
19
|
if (!labelElement)
|
|
20
20
|
return;
|
|
@@ -9,6 +9,7 @@ import { useComponentContext, } from '../../context/ComponentContext';
|
|
|
9
9
|
import { MessageInputContextProvider } from '../../context/MessageInputContext';
|
|
10
10
|
import { DialogManagerProvider } from '../../context';
|
|
11
11
|
import { useRegisterDropHandlers } from './WithDragAndDropUpload';
|
|
12
|
+
import { useStableId } from '../UtilityComponents/useStableId';
|
|
12
13
|
const MessageInputProvider = (props) => {
|
|
13
14
|
const cooldownTimerState = useCooldownTimer();
|
|
14
15
|
const messageInputState = useMessageInputState(props);
|
|
@@ -27,10 +28,11 @@ const UnMemoizedMessageInput = (props) => {
|
|
|
27
28
|
const { Input: PropInput } = props;
|
|
28
29
|
const { dragAndDropWindow } = useChannelStateContext();
|
|
29
30
|
const { Input: ContextInput, TriggerProvider = DefaultTriggerProvider } = useComponentContext('MessageInput');
|
|
31
|
+
const id = useStableId();
|
|
30
32
|
const Input = PropInput || ContextInput || MessageInputFlat;
|
|
31
33
|
const dialogManagerId = props.isThreadInput
|
|
32
|
-
?
|
|
33
|
-
:
|
|
34
|
+
? `message-input-dialog-manager-thread-${id}`
|
|
35
|
+
: `message-input-dialog-manager-${id}`;
|
|
34
36
|
if (dragAndDropWindow)
|
|
35
37
|
return (React.createElement(DialogManagerProvider, { id: dialogManagerId },
|
|
36
38
|
React.createElement(TriggerProvider, null,
|
|
@@ -38,9 +38,13 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
38
38
|
if (someAttachmentsUploading) {
|
|
39
39
|
return addNotification(t('Wait until all attachments have uploaded'), 'error');
|
|
40
40
|
}
|
|
41
|
-
const
|
|
42
|
-
.filter((att) =>
|
|
43
|
-
|
|
41
|
+
const attachmentsWithoutLinkPreviews = attachments
|
|
42
|
+
.filter((att) => {
|
|
43
|
+
const isSuccessfulUpload = att.localMetadata?.uploadState === 'finished';
|
|
44
|
+
const isNotUpload = !att.localMetadata?.uploadState;
|
|
45
|
+
const isNotLinkPreview = !att.og_scrape_url;
|
|
46
|
+
return isNotLinkPreview && (isSuccessfulUpload || isNotUpload);
|
|
47
|
+
})
|
|
44
48
|
.map((localAttachment) => {
|
|
45
49
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
46
50
|
const { localMetadata: _, ...attachment } = localAttachment;
|
|
@@ -56,8 +60,7 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
56
60
|
attachmentsFromLinkPreviews = someLinkPreviewsLoading
|
|
57
61
|
? []
|
|
58
62
|
: Array.from(linkPreviews.values())
|
|
59
|
-
.filter((linkPreview) => linkPreview.state === LinkPreviewState.LOADED
|
|
60
|
-
!attachmentsFromUploads.find((attFromUpload) => attFromUpload.og_scrape_url === linkPreview.og_scrape_url))
|
|
63
|
+
.filter((linkPreview) => linkPreview.state === LinkPreviewState.LOADED)
|
|
61
64
|
.map(
|
|
62
65
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
63
66
|
({ state: linkPreviewState, ...ogAttachment }) => ogAttachment);
|
|
@@ -66,7 +69,10 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
66
69
|
(!someLinkPreviewsLoading && attachmentsFromLinkPreviews.length > 0) ||
|
|
67
70
|
someLinkPreviewsDismissed;
|
|
68
71
|
}
|
|
69
|
-
const newAttachments = [
|
|
72
|
+
const newAttachments = [
|
|
73
|
+
...attachmentsWithoutLinkPreviews,
|
|
74
|
+
...attachmentsFromLinkPreviews,
|
|
75
|
+
];
|
|
70
76
|
// Instead of checking if a user is still mentioned every time the text changes,
|
|
71
77
|
// just filter out non-mentioned users before submit, which is cheaper
|
|
72
78
|
// and allows users to easily undo any accidental deletion
|
|
@@ -17,6 +17,7 @@ import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
|
|
|
17
17
|
import { defaultPinPermissions, MESSAGE_ACTIONS } from '../Message/utils';
|
|
18
18
|
import { TypingIndicator as DefaultTypingIndicator } from '../TypingIndicator';
|
|
19
19
|
import { MessageListMainPanel as DefaultMessageListMainPanel } from './MessageListMainPanel';
|
|
20
|
+
import { useStableId } from '../UtilityComponents/useStableId';
|
|
20
21
|
import { defaultRenderMessages } from './renderMessages';
|
|
21
22
|
import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, } from '../../constants/limits';
|
|
22
23
|
const MessageListWithContext = (props) => {
|
|
@@ -123,10 +124,11 @@ const MessageListWithContext = (props) => {
|
|
|
123
124
|
}
|
|
124
125
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
125
126
|
}, [highlightedMessageId]);
|
|
127
|
+
const id = useStableId();
|
|
126
128
|
const showEmptyStateIndicator = elements.length === 0 && !threadList;
|
|
127
129
|
const dialogManagerId = threadList
|
|
128
|
-
?
|
|
129
|
-
:
|
|
130
|
+
? `message-list-dialog-manager-thread-${id}`
|
|
131
|
+
: `message-list-dialog-manager-${id}`;
|
|
130
132
|
return (React.createElement(MessageListContextProvider, { value: { listElement, scrollToBottom } },
|
|
131
133
|
React.createElement(MessageListMainPanel, null,
|
|
132
134
|
React.createElement(DialogManagerProvider, { id: dialogManagerId },
|
|
@@ -21,6 +21,7 @@ import { useChatContext } from '../../context/ChatContext';
|
|
|
21
21
|
import { useComponentContext, } from '../../context/ComponentContext';
|
|
22
22
|
import { VirtualizedMessageListContextProvider } from '../../context/VirtualizedMessageListContext';
|
|
23
23
|
import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
|
|
24
|
+
import { useStableId } from '../UtilityComponents/useStableId';
|
|
24
25
|
function captureResizeObserverExceededError(e) {
|
|
25
26
|
if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
|
|
26
27
|
e.message === 'ResizeObserver loop limit exceeded') {
|
|
@@ -194,11 +195,12 @@ const VirtualizedMessageListWithContext = (props) => {
|
|
|
194
195
|
clearTimeout(scrollTimeout);
|
|
195
196
|
};
|
|
196
197
|
}, [highlightedMessageId, processedMessages]);
|
|
198
|
+
const id = useStableId();
|
|
197
199
|
if (!processedMessages)
|
|
198
200
|
return null;
|
|
199
201
|
const dialogManagerId = threadList
|
|
200
|
-
?
|
|
201
|
-
:
|
|
202
|
+
? `virtualized-message-list-dialog-manager-thread-${id}`
|
|
203
|
+
: `virtualized-message-list-dialog-manager-${id}`;
|
|
202
204
|
return (React.createElement(VirtualizedMessageListContextProvider, { value: { scrollToBottom } },
|
|
203
205
|
React.createElement(MessageListMainPanel, null,
|
|
204
206
|
React.createElement(DialogManagerProvider, { id: dialogManagerId },
|
|
@@ -7,7 +7,7 @@ import { useStateStore } from '../../../../store';
|
|
|
7
7
|
import { usePollContext, useTranslationContext } from '../../../../context';
|
|
8
8
|
const pollStateSelector = (nextValue) => ({
|
|
9
9
|
name: nextValue.name,
|
|
10
|
-
options: nextValue.options,
|
|
10
|
+
options: [...nextValue.options],
|
|
11
11
|
vote_counts_by_option: nextValue.vote_counts_by_option,
|
|
12
12
|
});
|
|
13
13
|
export const PollResults = ({ close, }) => {
|
|
@@ -64,8 +64,9 @@ export const PollCreationDialog = ({ close }) => {
|
|
|
64
64
|
React.createElement("div", { className: clsx('str-chat__form__input-field__value') },
|
|
65
65
|
React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-max-votes-allowed-input-field-error', text: multipleAnswerCountError }),
|
|
66
66
|
React.createElement("input", { id: 'max_votes_allowed', onChange: (e) => {
|
|
67
|
-
const isValidValue =
|
|
68
|
-
e.target.value
|
|
67
|
+
const isValidValue = e.target.validity.valid &&
|
|
68
|
+
(!e.target.value ||
|
|
69
|
+
e.target.value.match(VALID_MAX_VOTES_VALUE_REGEX));
|
|
69
70
|
if (!isValidValue) {
|
|
70
71
|
setMultipleAnswerCountError(t('Type a number from 2 to 10'));
|
|
71
72
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* The ID is generated using the `nanoid` library and is memoized to ensure
|
|
5
|
+
* that it remains the same across renders unless the key changes.
|
|
6
|
+
*/
|
|
7
|
+
export const useStableId = (key) => {
|
|
8
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
9
|
+
const id = useMemo(() => nanoid(), [key]);
|
|
10
|
+
return id;
|
|
11
|
+
};
|