stream-chat-react 12.0.0-rc.1 → 12.0.0-rc.3
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/README.md +10 -0
- package/dist/components/Attachment/components/WaveProgressBar.d.ts +3 -1
- package/dist/components/Attachment/components/WaveProgressBar.js +44 -9
- package/dist/components/Channel/channelState.js +1 -0
- package/dist/components/DateSeparator/DateSeparator.js +1 -1
- package/dist/components/EventComponent/EventComponent.js +1 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +9 -3
- package/dist/components/MediaRecorder/classes/MediaRecorderController.d.ts +6 -7
- package/dist/components/MediaRecorder/classes/MediaRecorderController.js +0 -5
- package/dist/components/MediaRecorder/hooks/index.d.ts +1 -1
- package/dist/components/MediaRecorder/hooks/useMediaRecorder.d.ts +1 -2
- package/dist/components/MediaRecorder/hooks/useMediaRecorder.js +1 -1
- package/dist/components/MediaRecorder/index.d.ts +1 -0
- package/dist/components/MediaRecorder/transcode/index.d.ts +6 -5
- package/dist/components/MediaRecorder/transcode/index.js +5 -15
- package/dist/components/Message/MessageSimple.js +1 -1
- package/dist/components/Message/MessageTimestamp.d.ts +0 -1
- package/dist/components/Message/MessageTimestamp.js +0 -1
- package/dist/components/Message/Timestamp.d.ts +0 -1
- package/dist/components/Message/Timestamp.js +2 -3
- package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.js +1 -1
- package/dist/components/Message/utils.js +2 -0
- package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.js +23 -27
- package/dist/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.d.ts +1 -0
- package/dist/components/MessageInput/AttachmentPreviewList/FileAttachmentPreview.js +1 -1
- package/dist/components/MessageInput/AttachmentPreviewList/ImageAttachmentPreview.js +2 -1
- package/dist/components/MessageInput/MessageInput.d.ts +4 -6
- package/dist/components/MessageInput/MessageInputFlat.js +4 -7
- package/dist/components/MessageInput/hooks/useAttachments.d.ts +1 -5
- package/dist/components/MessageInput/hooks/useAttachments.js +65 -52
- package/dist/components/MessageInput/hooks/useCreateMessageInputContext.js +2 -19
- package/dist/components/MessageInput/hooks/useMessageInputState.d.ts +2 -35
- package/dist/components/MessageInput/hooks/useMessageInputState.js +2 -107
- package/dist/components/MessageInput/hooks/usePasteHandler.js +1 -3
- package/dist/components/MessageInput/hooks/useSubmitHandler.js +19 -71
- package/dist/components/MessageInput/hooks/utils.d.ts +1 -2
- package/dist/components/MessageInput/icons.d.ts +0 -1
- package/dist/components/MessageInput/icons.js +0 -3
- package/dist/components/MessageInput/types.d.ts +3 -30
- package/dist/components/MessageList/MessageList.d.ts +3 -1
- package/dist/components/MessageList/MessageList.js +2 -1
- package/dist/components/MessageList/VirtualizedMessageList.d.ts +3 -1
- package/dist/components/MessageList/VirtualizedMessageList.js +3 -3
- package/dist/components/MessageList/VirtualizedMessageListComponents.js +3 -2
- package/dist/components/MessageList/hooks/MessageList/useEnrichedMessages.d.ts +2 -1
- package/dist/components/MessageList/hooks/MessageList/useEnrichedMessages.js +3 -3
- package/dist/components/MessageList/utils.d.ts +1 -1
- package/dist/components/MessageList/utils.js +16 -6
- package/dist/components/ReactFileUtilities/types.d.ts +0 -29
- package/dist/components/ReactFileUtilities/utils.d.ts +2 -0
- package/dist/components/ReactFileUtilities/utils.js +2 -0
- package/dist/context/ChannelActionContext.d.ts +2 -2
- package/dist/context/MessageInputContext.d.ts +1 -5
- package/dist/css/v2/emoji-replacement.css +1 -1
- package/dist/css/v2/index.css +2 -2
- package/dist/css/v2/index.layout.css +2 -2
- package/dist/i18n/Streami18n.d.ts +2 -0
- package/dist/i18n/de.json +3 -1
- package/dist/i18n/en.json +3 -1
- package/dist/i18n/es.json +3 -1
- package/dist/i18n/fr.json +3 -1
- package/dist/i18n/hi.json +3 -1
- package/dist/i18n/it.json +3 -1
- package/dist/i18n/ja.json +3 -1
- package/dist/i18n/ko.json +3 -1
- package/dist/i18n/nl.json +3 -1
- package/dist/i18n/pt.json +3 -1
- package/dist/i18n/ru.json +3 -1
- package/dist/i18n/tr.json +3 -1
- package/dist/i18n/utils.d.ts +3 -3
- package/dist/index.cjs.js +1987 -12143
- package/dist/index.cjs.js.map +4 -4
- package/dist/{components → plugins}/Emojis/EmojiPicker.js +1 -1
- package/dist/plugins/Emojis/icons.d.ts +2 -0
- package/dist/plugins/Emojis/icons.js +4 -0
- package/dist/{components → plugins}/Emojis/index.cjs.js +23 -22
- package/dist/plugins/Emojis/index.cjs.js.map +7 -0
- package/dist/plugins/Emojis/index.d.ts +2 -0
- package/dist/plugins/Emojis/index.js +2 -0
- package/dist/plugins/encoders/mp3.cjs.js +111 -0
- package/dist/plugins/encoders/mp3.cjs.js.map +7 -0
- package/dist/{components/MediaRecorder/transcode → plugins/encoders}/mp3.js +3 -3
- package/dist/scss/v2/Autocomplete/Autocomplete-layout.scss +1 -1
- package/dist/scss/v2/Autocomplete/Autocomplete-theme.scss +4 -2
- package/dist/scss/v2/Avatar/Avatar-layout.scss +31 -23
- package/dist/scss/v2/ChannelList/ChannelList-layout.scss +0 -5
- package/dist/scss/v2/ChannelSearch/ChannelSearch-layout.scss +1 -0
- package/dist/scss/v2/EditMessageForm/EditMessageForm-theme.scss +9 -9
- package/dist/scss/v2/Message/Message-layout.scss +37 -6
- package/dist/scss/v2/MessageReactions/MessageReactionsSelector-layout.scss +11 -0
- package/dist/scss/v2/MessageReactions/MessageReactionsSelector-theme.scss +5 -0
- package/dist/scss/v2/_emoji-replacement.scss +4 -2
- package/package.json +17 -7
- package/dist/components/Emojis/index.cjs.js.map +0 -7
- package/dist/components/Emojis/index.d.ts +0 -1
- package/dist/components/Emojis/index.js +0 -1
- package/dist/components/MessageInput/AttachmentPreviewList/UploadPreviewItem.d.ts +0 -11
- package/dist/components/MessageInput/AttachmentPreviewList/UploadPreviewItem.js +0 -51
- package/dist/components/MessageInput/hooks/useFileUploads.d.ts +0 -7
- package/dist/components/MessageInput/hooks/useFileUploads.js +0 -85
- package/dist/components/MessageInput/hooks/useImageUploads.d.ts +0 -8
- package/dist/components/MessageInput/hooks/useImageUploads.js +0 -94
- /package/dist/{components → plugins}/Emojis/EmojiPicker.d.ts +0 -0
- /package/dist/{components/MediaRecorder/transcode → plugins/encoders}/mp3.d.ts +0 -0
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
|
-
import { useImageUploads } from './useImageUploads';
|
|
4
|
-
import { useFileUploads } from './useFileUploads';
|
|
5
3
|
import { checkUploadPermissions } from './utils';
|
|
6
|
-
import { isLocalAttachment, isLocalImageAttachment
|
|
4
|
+
import { isLocalAttachment, isLocalImageAttachment } from '../../Attachment';
|
|
5
|
+
import { createFileFromBlobs, generateFileName, isBlobButNotFile } from '../../ReactFileUtilities';
|
|
7
6
|
import { useChannelActionContext, useChannelStateContext, useChatContext, useTranslationContext, } from '../../../context';
|
|
8
7
|
const apiMaxNumberOfFiles = 10;
|
|
8
|
+
// const isAudioFile = (file: FileLike) => file.type.includes('audio/');
|
|
9
|
+
const isImageFile = (file) => file.type.startsWith('image/') && !file.type.endsWith('.photoshop'); // photoshop files begin with 'image/'
|
|
10
|
+
// const isVideoFile = (file: FileLike) => file.type.includes('video/');
|
|
11
|
+
const getAttachmentTypeFromMime = (mimeType) => {
|
|
12
|
+
if (mimeType.startsWith('image/') && !mimeType.endsWith('.photoshop'))
|
|
13
|
+
return 'image';
|
|
14
|
+
if (mimeType.includes('video/'))
|
|
15
|
+
return 'video';
|
|
16
|
+
if (mimeType.includes('audio/'))
|
|
17
|
+
return 'audio';
|
|
18
|
+
return 'file';
|
|
19
|
+
};
|
|
9
20
|
const ensureIsLocalAttachment = (attachment) => {
|
|
10
21
|
if (isLocalAttachment(attachment)) {
|
|
11
22
|
return attachment;
|
|
@@ -21,45 +32,15 @@ const ensureIsLocalAttachment = (attachment) => {
|
|
|
21
32
|
};
|
|
22
33
|
export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
23
34
|
const { doFileUploadRequest, doImageUploadRequest, errorHandler, noFiles } = props;
|
|
24
|
-
const { fileUploads, imageUploads } = state;
|
|
25
35
|
const { getAppSettings } = useChatContext('useAttachments');
|
|
26
36
|
const { t } = useTranslationContext('useAttachments');
|
|
27
37
|
const { addNotification } = useChannelActionContext('useAttachments');
|
|
28
38
|
const { channel, maxNumberOfFiles, multipleUploads } = useChannelStateContext('useAttachments');
|
|
29
|
-
const { removeFile, uploadFile } = useFileUploads(props, state, dispatch);
|
|
30
|
-
const { removeImage, uploadImage } = useImageUploads(props, state, dispatch);
|
|
31
39
|
// Number of files that the user can still add. Should never be more than the amount allowed by the API.
|
|
32
40
|
// If multipleUploads is false, we only want to allow a single upload.
|
|
33
41
|
const maxFilesAllowed = !multipleUploads ? 1 : maxNumberOfFiles || apiMaxNumberOfFiles;
|
|
34
|
-
|
|
35
|
-
const numberOfImages = Object.values(imageUploads).filter(({ og_scrape_url, state }) => state !== 'failed' && !og_scrape_url).length;
|
|
36
|
-
const numberOfFiles = Object.values(fileUploads).filter(({ state }) => state !== 'failed').length;
|
|
37
|
-
const numberOfUploads = numberOfImages + numberOfFiles;
|
|
42
|
+
const numberOfUploads = Object.values(state.attachments).filter(({ localMetadata }) => localMetadata.uploadState && localMetadata.uploadState !== 'failed').length;
|
|
38
43
|
const maxFilesLeft = maxFilesAllowed - numberOfUploads;
|
|
39
|
-
const uploadNewFiles = useCallback((files) => {
|
|
40
|
-
Array.from(files)
|
|
41
|
-
.slice(0, maxFilesLeft)
|
|
42
|
-
.forEach((file) => {
|
|
43
|
-
const id = nanoid();
|
|
44
|
-
if (file.type.startsWith('image/') &&
|
|
45
|
-
!file.type.endsWith('.photoshop') // photoshop files begin with 'image/'
|
|
46
|
-
) {
|
|
47
|
-
dispatch({
|
|
48
|
-
file,
|
|
49
|
-
id,
|
|
50
|
-
previewUri: URL.createObjectURL?.(file),
|
|
51
|
-
state: 'uploading',
|
|
52
|
-
type: 'setImageUpload',
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
else if (file instanceof File && !noFiles) {
|
|
56
|
-
dispatch({ file, id, state: 'uploading', type: 'setFileUpload' });
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
textareaRef?.current?.focus();
|
|
60
|
-
},
|
|
61
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
|
-
[maxFilesLeft, noFiles]);
|
|
63
44
|
const removeAttachments = useCallback((ids) => {
|
|
64
45
|
if (!ids.length)
|
|
65
46
|
return;
|
|
@@ -74,12 +55,13 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
74
55
|
});
|
|
75
56
|
}, [dispatch]);
|
|
76
57
|
const uploadAttachment = useCallback(async (att) => {
|
|
77
|
-
const { localMetadata, ...
|
|
58
|
+
const { localMetadata, ...providedAttachmentData } = att;
|
|
78
59
|
if (!localMetadata?.file)
|
|
79
60
|
return att;
|
|
80
|
-
const isImage = isUploadedImage(attachment);
|
|
81
|
-
const id = localMetadata?.id ?? nanoid();
|
|
82
61
|
const { file } = localMetadata;
|
|
62
|
+
const isImage = isImageFile(file);
|
|
63
|
+
if (noFiles && !isImage)
|
|
64
|
+
return att;
|
|
83
65
|
const canUpload = await checkUploadPermissions({
|
|
84
66
|
addNotification,
|
|
85
67
|
file,
|
|
@@ -87,18 +69,31 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
87
69
|
t,
|
|
88
70
|
uploadType: isImage ? 'image' : 'file',
|
|
89
71
|
});
|
|
90
|
-
if (!canUpload)
|
|
91
|
-
const notificationText = t('Missing permissions to upload the attachment');
|
|
92
|
-
console.error(new Error(notificationText));
|
|
93
|
-
addNotification(notificationText, 'error');
|
|
72
|
+
if (!canUpload)
|
|
94
73
|
return att;
|
|
74
|
+
localMetadata.id = localMetadata?.id ?? nanoid();
|
|
75
|
+
const finalAttachment = {
|
|
76
|
+
type: getAttachmentTypeFromMime(file.type),
|
|
77
|
+
};
|
|
78
|
+
if (isImage) {
|
|
79
|
+
localMetadata.previewUri = URL.createObjectURL?.(file);
|
|
80
|
+
if (file instanceof File) {
|
|
81
|
+
finalAttachment.fallback = file.name;
|
|
82
|
+
}
|
|
95
83
|
}
|
|
84
|
+
else {
|
|
85
|
+
finalAttachment.file_size = file.size;
|
|
86
|
+
finalAttachment.mime_type = file.type;
|
|
87
|
+
if (file instanceof File) {
|
|
88
|
+
finalAttachment.title = file.name;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
Object.assign(finalAttachment, providedAttachmentData);
|
|
96
92
|
upsertAttachments([
|
|
97
93
|
{
|
|
98
|
-
...
|
|
94
|
+
...finalAttachment,
|
|
99
95
|
localMetadata: {
|
|
100
96
|
...localMetadata,
|
|
101
|
-
id,
|
|
102
97
|
uploadState: 'uploading',
|
|
103
98
|
},
|
|
104
99
|
},
|
|
@@ -124,7 +119,7 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
124
119
|
console.error(finalError);
|
|
125
120
|
addNotification(finalError.message, 'error');
|
|
126
121
|
const failedAttachment = {
|
|
127
|
-
...
|
|
122
|
+
...finalAttachment,
|
|
128
123
|
localMetadata: {
|
|
129
124
|
...localMetadata,
|
|
130
125
|
uploadState: 'failed',
|
|
@@ -132,19 +127,19 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
132
127
|
};
|
|
133
128
|
upsertAttachments([failedAttachment]);
|
|
134
129
|
if (errorHandler) {
|
|
135
|
-
errorHandler(finalError, 'upload-attachment', file);
|
|
130
|
+
errorHandler(finalError, 'upload-attachment', { ...file, id: localMetadata.id });
|
|
136
131
|
}
|
|
137
132
|
return failedAttachment;
|
|
138
133
|
}
|
|
139
134
|
if (!response) {
|
|
140
|
-
// Copied this from useImageUpload / useFileUpload.
|
|
135
|
+
// Copied this from useImageUpload / useFileUpload.
|
|
141
136
|
// If doUploadRequest returns any falsy value, then don't create the upload preview.
|
|
142
137
|
// This is for the case if someone wants to handle failure on app level.
|
|
143
|
-
removeAttachments([id]);
|
|
138
|
+
removeAttachments([localMetadata.id]);
|
|
144
139
|
return;
|
|
145
140
|
}
|
|
146
141
|
const uploadedAttachment = {
|
|
147
|
-
...
|
|
142
|
+
...finalAttachment,
|
|
148
143
|
localMetadata: {
|
|
149
144
|
...localMetadata,
|
|
150
145
|
uploadState: 'finished',
|
|
@@ -160,6 +155,9 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
160
155
|
else {
|
|
161
156
|
uploadedAttachment.asset_url = response.file;
|
|
162
157
|
}
|
|
158
|
+
if (response.thumb_url) {
|
|
159
|
+
uploadedAttachment.thumb_url = response.thumb_url;
|
|
160
|
+
}
|
|
163
161
|
upsertAttachments([uploadedAttachment]);
|
|
164
162
|
return uploadedAttachment;
|
|
165
163
|
}, [
|
|
@@ -169,19 +167,34 @@ export const useAttachments = (props, state, dispatch, textareaRef) => {
|
|
|
169
167
|
doImageUploadRequest,
|
|
170
168
|
errorHandler,
|
|
171
169
|
getAppSettings,
|
|
170
|
+
noFiles,
|
|
172
171
|
removeAttachments,
|
|
173
172
|
t,
|
|
174
173
|
upsertAttachments,
|
|
175
174
|
]);
|
|
175
|
+
const uploadNewFiles = useCallback((files) => {
|
|
176
|
+
const filesToBeUploaded = noFiles ? Array.from(files).filter(isImageFile) : Array.from(files);
|
|
177
|
+
filesToBeUploaded.slice(0, maxFilesLeft).forEach((fileLike) => {
|
|
178
|
+
uploadAttachment({
|
|
179
|
+
localMetadata: {
|
|
180
|
+
file: isBlobButNotFile(fileLike)
|
|
181
|
+
? createFileFromBlobs({
|
|
182
|
+
blobsArray: [fileLike],
|
|
183
|
+
fileName: generateFileName(fileLike.type),
|
|
184
|
+
mimeType: fileLike.type,
|
|
185
|
+
})
|
|
186
|
+
: fileLike,
|
|
187
|
+
id: nanoid(),
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
textareaRef.current?.focus();
|
|
192
|
+
}, [maxFilesLeft, noFiles, textareaRef, uploadAttachment]);
|
|
176
193
|
return {
|
|
177
194
|
maxFilesLeft,
|
|
178
195
|
numberOfUploads,
|
|
179
196
|
removeAttachments,
|
|
180
|
-
removeFile,
|
|
181
|
-
removeImage,
|
|
182
197
|
uploadAttachment,
|
|
183
|
-
uploadFile,
|
|
184
|
-
uploadImage,
|
|
185
198
|
uploadNewFiles,
|
|
186
199
|
upsertAttachments,
|
|
187
200
|
};
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
export const useCreateMessageInputContext = (value) => {
|
|
3
|
-
const { additionalTextareaProps, asyncMessagesMultiSendEnabled, attachments, audioRecordingEnabled, autocompleteTriggers, cancelURLEnrichment, clearEditingState, closeCommandsList, closeMentionsList, cooldownInterval, cooldownRemaining, disabled, disableMentions, dismissLinkPreview, doFileUploadRequest, doImageUploadRequest, emojiSearchIndex, errorHandler,
|
|
3
|
+
const { additionalTextareaProps, asyncMessagesMultiSendEnabled, attachments, audioRecordingEnabled, autocompleteTriggers, cancelURLEnrichment, clearEditingState, closeCommandsList, closeMentionsList, cooldownInterval, cooldownRemaining, disabled, disableMentions, dismissLinkPreview, doFileUploadRequest, doImageUploadRequest, emojiSearchIndex, errorHandler, findAndEnqueueURLsToEnrich, focus, grow, handleChange, handleSubmit, hideSendButton, insertText, isUploadEnabled, linkPreviews, maxFilesLeft, maxRows, mentionAllAppUsers, mentioned_users, mentionQueryParams, message, minRows, noFiles, numberOfUploads, onPaste, onSelectUser, openCommandsList, openMentionsList, overrideSubmitHandler, parent, publishTypingEvent, recordingController, removeAttachments, setCooldownRemaining, setText, shouldSubmit, showCommandsList, showMentionsList, text, textareaRef, uploadAttachment, uploadNewFiles, upsertAttachments, useMentionsTransliteration, } = value;
|
|
4
4
|
const editing = message?.editing;
|
|
5
|
-
const fileUploadsValue = Object.entries(fileUploads)
|
|
6
|
-
// eslint-disable-next-line
|
|
7
|
-
.map(([_, value]) => value.state)
|
|
8
|
-
.join();
|
|
9
|
-
const imageUploadsValue = Object.entries(imageUploads)
|
|
10
|
-
// eslint-disable-next-line
|
|
11
|
-
.map(([_, value]) => value.state)
|
|
12
|
-
.join();
|
|
13
5
|
const linkPreviewsValue = Array.from(linkPreviews.values()).join();
|
|
14
6
|
const mentionedUsersLength = mentioned_users.length;
|
|
15
7
|
const parentId = parent?.id;
|
|
@@ -32,16 +24,12 @@ export const useCreateMessageInputContext = (value) => {
|
|
|
32
24
|
doImageUploadRequest,
|
|
33
25
|
emojiSearchIndex,
|
|
34
26
|
errorHandler,
|
|
35
|
-
fileOrder,
|
|
36
|
-
fileUploads,
|
|
37
27
|
findAndEnqueueURLsToEnrich,
|
|
38
28
|
focus,
|
|
39
29
|
grow,
|
|
40
30
|
handleChange,
|
|
41
31
|
handleSubmit,
|
|
42
32
|
hideSendButton,
|
|
43
|
-
imageOrder,
|
|
44
|
-
imageUploads,
|
|
45
33
|
insertText,
|
|
46
34
|
isUploadEnabled,
|
|
47
35
|
linkPreviews,
|
|
@@ -63,8 +51,6 @@ export const useCreateMessageInputContext = (value) => {
|
|
|
63
51
|
publishTypingEvent,
|
|
64
52
|
recordingController,
|
|
65
53
|
removeAttachments,
|
|
66
|
-
removeFile,
|
|
67
|
-
removeImage,
|
|
68
54
|
setCooldownRemaining,
|
|
69
55
|
setText,
|
|
70
56
|
shouldSubmit,
|
|
@@ -73,8 +59,6 @@ export const useCreateMessageInputContext = (value) => {
|
|
|
73
59
|
text,
|
|
74
60
|
textareaRef,
|
|
75
61
|
uploadAttachment,
|
|
76
|
-
uploadFile,
|
|
77
|
-
uploadImage,
|
|
78
62
|
uploadNewFiles,
|
|
79
63
|
upsertAttachments,
|
|
80
64
|
useMentionsTransliteration,
|
|
@@ -82,6 +66,7 @@ export const useCreateMessageInputContext = (value) => {
|
|
|
82
66
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83
67
|
[
|
|
84
68
|
asyncMessagesMultiSendEnabled,
|
|
69
|
+
attachments,
|
|
85
70
|
audioRecordingEnabled,
|
|
86
71
|
cancelURLEnrichment,
|
|
87
72
|
cooldownInterval,
|
|
@@ -89,11 +74,9 @@ export const useCreateMessageInputContext = (value) => {
|
|
|
89
74
|
dismissLinkPreview,
|
|
90
75
|
editing,
|
|
91
76
|
emojiSearchIndex,
|
|
92
|
-
fileUploadsValue,
|
|
93
77
|
findAndEnqueueURLsToEnrich,
|
|
94
78
|
handleSubmit,
|
|
95
79
|
hideSendButton,
|
|
96
|
-
imageUploadsValue,
|
|
97
80
|
isUploadEnabled,
|
|
98
81
|
linkPreviewsValue,
|
|
99
82
|
mentionedUsersLength,
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { EnrichURLsController } from './useLinkPreviews';
|
|
3
3
|
import { RecordingController } from '../../MediaRecorder/hooks/useMediaRecorder';
|
|
4
|
+
import type { LinkPreviewMap, LocalAttachment } from '../types';
|
|
4
5
|
import { SetLinkPreviewMode } from '../types';
|
|
5
|
-
import type { FileUpload, ImageUpload, LinkPreviewMap, LocalAttachment } from '../types';
|
|
6
|
-
import type { FileLike } from '../../ReactFileUtilities';
|
|
7
6
|
import type { Attachment, Message, UserResponse } from 'stream-chat';
|
|
8
7
|
import type { MessageInputProps } from '../MessageInput';
|
|
9
8
|
import type { CustomTrigger, DefaultStreamChatGenerics, SendMessageOptions } from '../../../types/types';
|
|
10
9
|
export type MessageInputState<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
11
10
|
attachments: LocalAttachment<StreamChatGenerics>[];
|
|
12
|
-
fileOrder: string[];
|
|
13
|
-
fileUploads: Record<string, FileUpload>;
|
|
14
|
-
imageOrder: string[];
|
|
15
|
-
imageUploads: Record<string, ImageUpload>;
|
|
16
11
|
linkPreviews: LinkPreviewMap;
|
|
17
12
|
mentioned_users: UserResponse<StreamChatGenerics>[];
|
|
18
13
|
setText: (text: string) => void;
|
|
@@ -33,40 +28,16 @@ type SetTextAction = {
|
|
|
33
28
|
type ClearAction = {
|
|
34
29
|
type: 'clear';
|
|
35
30
|
};
|
|
36
|
-
type SetImageUploadAction = {
|
|
37
|
-
id: string;
|
|
38
|
-
type: 'setImageUpload';
|
|
39
|
-
file?: File | FileLike;
|
|
40
|
-
previewUri?: string;
|
|
41
|
-
state?: string;
|
|
42
|
-
url?: string;
|
|
43
|
-
};
|
|
44
|
-
type SetFileUploadAction = {
|
|
45
|
-
id: string;
|
|
46
|
-
type: 'setFileUpload';
|
|
47
|
-
file?: File;
|
|
48
|
-
state?: string;
|
|
49
|
-
thumb_url?: string;
|
|
50
|
-
url?: string;
|
|
51
|
-
};
|
|
52
31
|
type SetLinkPreviewsAction = {
|
|
53
32
|
linkPreviews: LinkPreviewMap;
|
|
54
33
|
mode: SetLinkPreviewMode;
|
|
55
34
|
type: 'setLinkPreviews';
|
|
56
35
|
};
|
|
57
|
-
type RemoveImageUploadAction = {
|
|
58
|
-
id: string;
|
|
59
|
-
type: 'removeImageUpload';
|
|
60
|
-
};
|
|
61
|
-
type RemoveFileUploadAction = {
|
|
62
|
-
id: string;
|
|
63
|
-
type: 'removeFileUpload';
|
|
64
|
-
};
|
|
65
36
|
type AddMentionedUserAction<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
66
37
|
type: 'addMentionedUser';
|
|
67
38
|
user: UserResponse<StreamChatGenerics>;
|
|
68
39
|
};
|
|
69
|
-
export type MessageInputReducerAction<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = SetTextAction | ClearAction |
|
|
40
|
+
export type MessageInputReducerAction<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = SetTextAction | ClearAction | SetLinkPreviewsAction | AddMentionedUserAction<StreamChatGenerics> | UpsertAttachmentsAction | RemoveAttachmentsAction;
|
|
70
41
|
export type MessageInputHookProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = EnrichURLsController & {
|
|
71
42
|
handleChange: React.ChangeEventHandler<HTMLTextAreaElement>;
|
|
72
43
|
handleSubmit: (event?: React.BaseSyntheticEvent, customMessageData?: Partial<Message<StreamChatGenerics>>, options?: SendMessageOptions) => void;
|
|
@@ -78,12 +49,8 @@ export type MessageInputHookProps<StreamChatGenerics extends DefaultStreamChatGe
|
|
|
78
49
|
onSelectUser: (item: UserResponse<StreamChatGenerics>) => void;
|
|
79
50
|
recordingController: RecordingController<StreamChatGenerics>;
|
|
80
51
|
removeAttachments: (ids: string[]) => void;
|
|
81
|
-
removeFile: (id: string) => void;
|
|
82
|
-
removeImage: (id: string) => void;
|
|
83
52
|
textareaRef: React.MutableRefObject<HTMLTextAreaElement | null | undefined>;
|
|
84
53
|
uploadAttachment: (attachment: LocalAttachment<StreamChatGenerics>) => Promise<LocalAttachment<StreamChatGenerics> | undefined>;
|
|
85
|
-
uploadFile: (id: string) => void;
|
|
86
|
-
uploadImage: (id: string) => void;
|
|
87
54
|
uploadNewFiles: (files: FileList | File[]) => void;
|
|
88
55
|
upsertAttachments: (attachments: (Attachment<StreamChatGenerics> | LocalAttachment<StreamChatGenerics>)[]) => void;
|
|
89
56
|
};
|
|
@@ -11,10 +11,6 @@ import { LinkPreviewState, SetLinkPreviewMode } from '../types';
|
|
|
11
11
|
import { mergeDeep } from '../../../utils/mergeDeep';
|
|
12
12
|
const makeEmptyMessageInputState = () => ({
|
|
13
13
|
attachments: [],
|
|
14
|
-
fileOrder: [],
|
|
15
|
-
fileUploads: {},
|
|
16
|
-
imageOrder: [],
|
|
17
|
-
imageUploads: {},
|
|
18
14
|
linkPreviews: new Map(),
|
|
19
15
|
mentioned_users: [],
|
|
20
16
|
setText: () => null,
|
|
@@ -27,43 +23,6 @@ const initState = (message) => {
|
|
|
27
23
|
if (!message) {
|
|
28
24
|
return makeEmptyMessageInputState();
|
|
29
25
|
}
|
|
30
|
-
// if message prop is defined, get image uploads, file uploads, text, etc.
|
|
31
|
-
const imageUploads = message.attachments
|
|
32
|
-
?.filter(({ type }) => type === 'image')
|
|
33
|
-
.reduce((acc, { author_name, fallback = '', image_url, og_scrape_url, text, title, title_link }) => {
|
|
34
|
-
const id = nanoid();
|
|
35
|
-
acc[id] = {
|
|
36
|
-
author_name,
|
|
37
|
-
file: {
|
|
38
|
-
name: fallback,
|
|
39
|
-
},
|
|
40
|
-
id,
|
|
41
|
-
og_scrape_url, // fixme: why scraped content is mixed with uploaded content?
|
|
42
|
-
state: 'finished',
|
|
43
|
-
text,
|
|
44
|
-
title,
|
|
45
|
-
title_link,
|
|
46
|
-
url: image_url,
|
|
47
|
-
};
|
|
48
|
-
return acc;
|
|
49
|
-
}, {}) ?? {};
|
|
50
|
-
const fileUploads = message.attachments
|
|
51
|
-
?.filter(({ type }) => type === 'file')
|
|
52
|
-
.reduce((acc, { asset_url, file_size, mime_type, thumb_url, title = '' }) => {
|
|
53
|
-
const id = nanoid();
|
|
54
|
-
acc[id] = {
|
|
55
|
-
file: {
|
|
56
|
-
name: title,
|
|
57
|
-
size: file_size,
|
|
58
|
-
type: mime_type,
|
|
59
|
-
},
|
|
60
|
-
id,
|
|
61
|
-
state: 'finished',
|
|
62
|
-
thumb_url,
|
|
63
|
-
url: asset_url,
|
|
64
|
-
};
|
|
65
|
-
return acc;
|
|
66
|
-
}, {}) ?? {};
|
|
67
26
|
const linkPreviews = message.attachments?.reduce((acc, attachment) => {
|
|
68
27
|
if (!attachment.og_scrape_url)
|
|
69
28
|
return acc;
|
|
@@ -73,10 +32,8 @@ const initState = (message) => {
|
|
|
73
32
|
});
|
|
74
33
|
return acc;
|
|
75
34
|
}, new Map()) ?? new Map();
|
|
76
|
-
const imageOrder = Object.keys(imageUploads);
|
|
77
|
-
const fileOrder = Object.keys(fileUploads);
|
|
78
35
|
const attachments = message.attachments
|
|
79
|
-
?.filter(({
|
|
36
|
+
?.filter(({ og_scrape_url }) => !og_scrape_url)
|
|
80
37
|
.map((att) => ({
|
|
81
38
|
...att,
|
|
82
39
|
localMetadata: { id: nanoid() },
|
|
@@ -84,10 +41,6 @@ const initState = (message) => {
|
|
|
84
41
|
const mentioned_users = message.mentioned_users || [];
|
|
85
42
|
return {
|
|
86
43
|
attachments,
|
|
87
|
-
fileOrder,
|
|
88
|
-
fileUploads,
|
|
89
|
-
imageOrder,
|
|
90
|
-
imageUploads,
|
|
91
44
|
linkPreviews,
|
|
92
45
|
mentioned_users,
|
|
93
46
|
setText: () => null,
|
|
@@ -126,38 +79,6 @@ const messageInputReducer = (state, action) => {
|
|
|
126
79
|
attachments: state.attachments.filter((att) => !action.ids.includes(att.localMetadata?.id)),
|
|
127
80
|
};
|
|
128
81
|
}
|
|
129
|
-
case 'setImageUpload': {
|
|
130
|
-
const imageAlreadyExists = state.imageUploads[action.id];
|
|
131
|
-
if (!imageAlreadyExists && !action.file)
|
|
132
|
-
return state;
|
|
133
|
-
const imageOrder = imageAlreadyExists ? state.imageOrder : state.imageOrder.concat(action.id);
|
|
134
|
-
const newUploadFields = { ...action };
|
|
135
|
-
delete newUploadFields.type;
|
|
136
|
-
return {
|
|
137
|
-
...state,
|
|
138
|
-
imageOrder,
|
|
139
|
-
imageUploads: {
|
|
140
|
-
...state.imageUploads,
|
|
141
|
-
[action.id]: { ...state.imageUploads[action.id], ...newUploadFields },
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
case 'setFileUpload': {
|
|
146
|
-
const fileAlreadyExists = state.fileUploads[action.id];
|
|
147
|
-
if (!fileAlreadyExists && !action.file)
|
|
148
|
-
return state;
|
|
149
|
-
const fileOrder = fileAlreadyExists ? state.fileOrder : state.fileOrder.concat(action.id);
|
|
150
|
-
const newUploadFields = { ...action };
|
|
151
|
-
delete newUploadFields.type;
|
|
152
|
-
return {
|
|
153
|
-
...state,
|
|
154
|
-
fileOrder,
|
|
155
|
-
fileUploads: {
|
|
156
|
-
...state.fileUploads,
|
|
157
|
-
[action.id]: { ...state.fileUploads[action.id], ...newUploadFields },
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
82
|
case 'setLinkPreviews': {
|
|
162
83
|
const linkPreviews = new Map(state.linkPreviews);
|
|
163
84
|
if (action.mode === SetLinkPreviewMode.REMOVE) {
|
|
@@ -187,28 +108,6 @@ const messageInputReducer = (state, action) => {
|
|
|
187
108
|
linkPreviews,
|
|
188
109
|
};
|
|
189
110
|
}
|
|
190
|
-
case 'removeImageUpload': {
|
|
191
|
-
if (!state.imageUploads[action.id])
|
|
192
|
-
return state; // cannot remove anything
|
|
193
|
-
const newImageUploads = { ...state.imageUploads };
|
|
194
|
-
delete newImageUploads[action.id];
|
|
195
|
-
return {
|
|
196
|
-
...state,
|
|
197
|
-
imageOrder: state.imageOrder.filter((_id) => _id !== action.id),
|
|
198
|
-
imageUploads: newImageUploads,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
case 'removeFileUpload': {
|
|
202
|
-
if (!state.fileUploads[action.id])
|
|
203
|
-
return state; // cannot remove anything
|
|
204
|
-
const newFileUploads = { ...state.fileUploads };
|
|
205
|
-
delete newFileUploads[action.id];
|
|
206
|
-
return {
|
|
207
|
-
...state,
|
|
208
|
-
fileOrder: state.fileOrder.filter((_id) => _id !== action.id),
|
|
209
|
-
fileUploads: newFileUploads,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
111
|
case 'addMentionedUser':
|
|
213
112
|
return {
|
|
214
113
|
...state,
|
|
@@ -255,7 +154,7 @@ export const useMessageInputState = (props) => {
|
|
|
255
154
|
setShowMentionsList(true);
|
|
256
155
|
};
|
|
257
156
|
const closeMentionsList = () => setShowMentionsList(false);
|
|
258
|
-
const { maxFilesLeft, numberOfUploads, removeAttachments,
|
|
157
|
+
const { maxFilesLeft, numberOfUploads, removeAttachments, uploadAttachment, uploadNewFiles, upsertAttachments, } = useAttachments(props, state, dispatch, textareaRef);
|
|
259
158
|
const { handleSubmit } = useSubmitHandler(props, state, dispatch, numberOfUploads, enrichURLsController);
|
|
260
159
|
const recordingController = useMediaRecorder({
|
|
261
160
|
asyncMessagesMultiSendEnabled,
|
|
@@ -289,15 +188,11 @@ export const useMessageInputState = (props) => {
|
|
|
289
188
|
openMentionsList,
|
|
290
189
|
recordingController,
|
|
291
190
|
removeAttachments,
|
|
292
|
-
removeFile,
|
|
293
|
-
removeImage,
|
|
294
191
|
setText,
|
|
295
192
|
showCommandsList,
|
|
296
193
|
showMentionsList,
|
|
297
194
|
textareaRef,
|
|
298
195
|
uploadAttachment,
|
|
299
|
-
uploadFile,
|
|
300
|
-
uploadImage,
|
|
301
196
|
uploadNewFiles,
|
|
302
197
|
upsertAttachments,
|
|
303
198
|
};
|
|
@@ -36,8 +36,6 @@ export const usePasteHandler = (uploadNewFiles, insertText, isUploadEnabled, fin
|
|
|
36
36
|
findAndEnqueueURLsToEnrich?.flush();
|
|
37
37
|
}
|
|
38
38
|
})(clipboardEvent);
|
|
39
|
-
},
|
|
40
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
-
[insertText, uploadNewFiles]);
|
|
39
|
+
}, [findAndEnqueueURLsToEnrich, insertText, isUploadEnabled, uploadNewFiles]);
|
|
42
40
|
return { onPaste };
|
|
43
41
|
};
|
|
@@ -3,16 +3,9 @@ import { useChannelActionContext } from '../../../context/ChannelActionContext';
|
|
|
3
3
|
import { useChannelStateContext } from '../../../context/ChannelStateContext';
|
|
4
4
|
import { useTranslationContext } from '../../../context/TranslationContext';
|
|
5
5
|
import { LinkPreviewState } from '../types';
|
|
6
|
-
const getAttachmentTypeFromMime = (mime) => {
|
|
7
|
-
if (mime.includes('video/'))
|
|
8
|
-
return 'video';
|
|
9
|
-
if (mime.includes('audio/'))
|
|
10
|
-
return 'audio';
|
|
11
|
-
return 'file';
|
|
12
|
-
};
|
|
13
6
|
export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrichURLsController) => {
|
|
14
7
|
const { clearEditingState, message, overrideSubmitHandler, parent, publishTypingEvent } = props;
|
|
15
|
-
const { attachments,
|
|
8
|
+
const { attachments, linkPreviews, mentioned_users, text } = state;
|
|
16
9
|
const { cancelURLEnrichment, findAndEnqueueURLsToEnrich } = enrichURLsController;
|
|
17
10
|
const { channel } = useChannelStateContext('useSubmitHandler');
|
|
18
11
|
const { addNotification, editMessage, sendMessage } = useChannelActionContext('useSubmitHandler');
|
|
@@ -25,47 +18,6 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
25
18
|
}
|
|
26
19
|
textReference.current.hasChanged = text !== textReference.current.initialText;
|
|
27
20
|
}, [text]);
|
|
28
|
-
const getAttachmentsFromUploads = () => {
|
|
29
|
-
const imageAttachments = imageOrder
|
|
30
|
-
.map((id) => imageUploads[id])
|
|
31
|
-
.filter((upload) => upload.state !== 'failed')
|
|
32
|
-
.filter(({ id, url }, _, self) => self.every((upload) => upload.id === id || upload.url !== url))
|
|
33
|
-
.filter((upload) => {
|
|
34
|
-
// keep the OG attachments in case the text has not changed as the BE
|
|
35
|
-
// won't re-enrich the message when only attachments have changed
|
|
36
|
-
if (!textReference.current.hasChanged)
|
|
37
|
-
return true;
|
|
38
|
-
return !upload.og_scrape_url;
|
|
39
|
-
})
|
|
40
|
-
.map(({ file: { name }, url, ...rest }) => ({
|
|
41
|
-
author_name: rest.author_name,
|
|
42
|
-
fallback: name,
|
|
43
|
-
image_url: url,
|
|
44
|
-
og_scrape_url: rest.og_scrape_url,
|
|
45
|
-
text: rest.text,
|
|
46
|
-
title: rest.title,
|
|
47
|
-
title_link: rest.title_link,
|
|
48
|
-
type: 'image',
|
|
49
|
-
}));
|
|
50
|
-
const fileAttachments = fileOrder
|
|
51
|
-
.map((id) => fileUploads[id])
|
|
52
|
-
.filter((upload) => upload.state !== 'failed')
|
|
53
|
-
.map((upload) => ({
|
|
54
|
-
asset_url: upload.url,
|
|
55
|
-
file_size: upload.file.size,
|
|
56
|
-
mime_type: upload.file.type,
|
|
57
|
-
thumb_url: upload.thumb_url,
|
|
58
|
-
title: upload.file.name,
|
|
59
|
-
type: getAttachmentTypeFromMime(upload.file.type || ''),
|
|
60
|
-
}));
|
|
61
|
-
const otherAttachments = attachments
|
|
62
|
-
.filter((att) => att.localMetadata?.uploadState !== 'failed')
|
|
63
|
-
.map((localAttachment) => {
|
|
64
|
-
const { localMetadata: _, ...attachment } = localAttachment;
|
|
65
|
-
return attachment;
|
|
66
|
-
});
|
|
67
|
-
return [...otherAttachments, ...imageAttachments, ...fileAttachments];
|
|
68
|
-
};
|
|
69
21
|
const handleSubmit = async (event, customMessageData, options) => {
|
|
70
22
|
event?.preventDefault();
|
|
71
23
|
const trimmedMessage = text.trim();
|
|
@@ -79,31 +31,35 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
79
31
|
trimmedMessage === '****';
|
|
80
32
|
if (isEmptyMessage && numberOfUploads === 0 && attachments.length === 0)
|
|
81
33
|
return;
|
|
82
|
-
|
|
83
|
-
const someAttachmentsUploading = Object.values(imageUploads).some((upload) => upload.state === 'uploading') ||
|
|
84
|
-
Object.values(fileUploads).some((upload) => upload.state === 'uploading') ||
|
|
85
|
-
attachments.some((att) => att.localMetadata?.uploadState === 'uploading');
|
|
34
|
+
const someAttachmentsUploading = attachments.some((att) => att.localMetadata?.uploadState === 'uploading');
|
|
86
35
|
if (someAttachmentsUploading) {
|
|
87
36
|
return addNotification(t('Wait until all attachments have uploaded'), 'error');
|
|
88
37
|
}
|
|
89
|
-
|
|
38
|
+
const attachmentsFromUploads = attachments
|
|
39
|
+
.filter((att) => att.localMetadata?.uploadState !== 'failed' ||
|
|
40
|
+
(findAndEnqueueURLsToEnrich && !att.og_scrape_url))
|
|
41
|
+
.map((localAttachment) => {
|
|
42
|
+
const { localMetadata: _, ...attachment } = localAttachment;
|
|
43
|
+
return attachment;
|
|
44
|
+
});
|
|
45
|
+
const sendOptions = { ...options };
|
|
90
46
|
let attachmentsFromLinkPreviews = [];
|
|
91
|
-
let someLinkPreviewsLoading;
|
|
92
|
-
let someLinkPreviewsDismissed;
|
|
93
47
|
if (findAndEnqueueURLsToEnrich) {
|
|
94
|
-
// filter out all the attachments scraped before the message was edited - only if the scr
|
|
95
|
-
attachmentsFromUploads = attachmentsFromUploads.filter((attachment) => !attachment.og_scrape_url);
|
|
96
48
|
// prevent showing link preview in MessageInput after the message has been sent
|
|
97
49
|
cancelURLEnrichment();
|
|
98
|
-
someLinkPreviewsLoading = Array.from(linkPreviews.values()).some((linkPreview) => [LinkPreviewState.QUEUED, LinkPreviewState.LOADING].includes(linkPreview.state));
|
|
99
|
-
someLinkPreviewsDismissed = Array.from(linkPreviews.values()).some((linkPreview) => linkPreview.state === LinkPreviewState.DISMISSED);
|
|
100
|
-
|
|
101
|
-
|
|
50
|
+
const someLinkPreviewsLoading = Array.from(linkPreviews.values()).some((linkPreview) => [LinkPreviewState.QUEUED, LinkPreviewState.LOADING].includes(linkPreview.state));
|
|
51
|
+
const someLinkPreviewsDismissed = Array.from(linkPreviews.values()).some((linkPreview) => linkPreview.state === LinkPreviewState.DISMISSED);
|
|
52
|
+
attachmentsFromLinkPreviews = someLinkPreviewsLoading
|
|
53
|
+
? []
|
|
54
|
+
: Array.from(linkPreviews.values())
|
|
102
55
|
.filter((linkPreview) => linkPreview.state === LinkPreviewState.LOADED &&
|
|
103
56
|
!attachmentsFromUploads.find((attFromUpload) => attFromUpload.og_scrape_url === linkPreview.og_scrape_url))
|
|
104
57
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
105
58
|
.map(({ state: linkPreviewState, ...ogAttachment }) => ogAttachment);
|
|
106
|
-
|
|
59
|
+
// scraped attachments are added only if all enrich queries has completed. Otherwise, the scraping has to be done server-side.
|
|
60
|
+
sendOptions.skip_enrich_url =
|
|
61
|
+
(!someLinkPreviewsLoading && attachmentsFromLinkPreviews.length > 0) ||
|
|
62
|
+
someLinkPreviewsDismissed;
|
|
107
63
|
}
|
|
108
64
|
const newAttachments = [...attachmentsFromUploads, ...attachmentsFromLinkPreviews];
|
|
109
65
|
// Instead of checking if a user is still mentioned every time the text changes,
|
|
@@ -115,14 +71,6 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
|
|
|
115
71
|
mentioned_users: actualMentionedUsers,
|
|
116
72
|
text,
|
|
117
73
|
};
|
|
118
|
-
// scraped attachments are added only if all enrich queries has completed. Otherwise, the scraping has to be done server-side.
|
|
119
|
-
const linkPreviewsEnabled = !!findAndEnqueueURLsToEnrich;
|
|
120
|
-
const skip_enrich_url = linkPreviewsEnabled &&
|
|
121
|
-
((!someLinkPreviewsLoading && attachmentsFromLinkPreviews.length > 0) ||
|
|
122
|
-
someLinkPreviewsDismissed);
|
|
123
|
-
const sendOptions = linkPreviewsEnabled || options
|
|
124
|
-
? Object.assign(linkPreviewsEnabled ? { skip_enrich_url } : {}, options ?? {})
|
|
125
|
-
: undefined;
|
|
126
74
|
if (message && message.type !== 'error') {
|
|
127
75
|
delete message.i18n;
|
|
128
76
|
try {
|