stream-chat-react 12.0.0-rc.1 → 12.0.0-rc.10
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 +11 -1
- package/dist/components/Attachment/components/WaveProgressBar.d.ts +3 -1
- package/dist/components/Attachment/components/WaveProgressBar.js +44 -9
- package/dist/components/Channel/Channel.js +19 -32
- package/dist/components/Channel/channelState.js +1 -0
- package/dist/components/Channel/hooks/useCreateChannelStateContext.js +1 -1
- package/dist/components/ChannelList/ChannelList.js +1 -1
- 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 +1 -2
- package/dist/components/Chat/hooks/useCreateChatClient.d.ts +4 -2
- package/dist/components/Chat/hooks/useCreateChatClient.js +5 -4
- package/dist/components/DateSeparator/DateSeparator.d.ts +1 -1
- package/dist/components/DateSeparator/DateSeparator.js +1 -1
- package/dist/components/EventComponent/EventComponent.d.ts +1 -1
- package/dist/components/EventComponent/EventComponent.js +1 -1
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +9 -3
- package/dist/components/MediaRecorder/AudioRecorder/AudioRecordingInProgress.js +3 -0
- 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/MessageStatus.js +3 -2
- package/dist/components/Message/MessageTimestamp.d.ts +1 -2
- package/dist/components/Message/MessageTimestamp.js +0 -1
- package/dist/components/Message/Timestamp.d.ts +1 -2
- package/dist/components/Message/Timestamp.js +4 -5
- package/dist/components/Message/renderText/rehypePlugins/mentionsMarkdownPlugin.d.ts +1 -1
- package/dist/components/Message/renderText/remarkPlugins/htmlToTextPlugin.d.ts +1 -1
- package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.d.ts +1 -1
- package/dist/components/Message/renderText/remarkPlugins/keepLineBreaksPlugin.js +2 -6
- package/dist/components/Message/renderText/renderText.d.ts +2 -2
- package/dist/components/Message/renderText/renderText.js +8 -6
- 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/useTimeElapsed.js +5 -4
- 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 +17 -7
- 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/components/Thread/Thread.d.ts +0 -2
- package/dist/components/Thread/Thread.js +1 -2
- package/dist/components/UtilityComponents/ErrorBoundary.d.ts +16 -0
- package/dist/components/UtilityComponents/ErrorBoundary.js +19 -0
- package/dist/components/UtilityComponents/index.d.ts +1 -0
- package/dist/components/UtilityComponents/index.js +1 -0
- package/dist/components/Window/Window.d.ts +1 -3
- package/dist/components/Window/Window.js +2 -2
- package/dist/context/ChannelActionContext.d.ts +2 -2
- package/dist/context/MessageInputContext.d.ts +1 -5
- package/dist/context/TranslationContext.d.ts +1 -11
- package/dist/context/TranslationContext.js +1 -9
- 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 +3 -3
- package/dist/i18n/Streami18n.js +1 -2
- 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/index.d.ts +2 -1
- package/dist/i18n/index.js +2 -0
- 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/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 +47221 -0
- package/dist/index.browser.cjs.map +7 -0
- package/dist/{index.cjs.js → index.node.cjs} +17604 -29018
- package/dist/index.node.cjs.map +7 -0
- 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/plugins/Emojis/index.browser.cjs +167 -0
- package/dist/plugins/Emojis/index.browser.cjs.map +7 -0
- package/dist/plugins/Emojis/index.d.ts +2 -0
- package/dist/plugins/Emojis/index.js +2 -0
- package/dist/{components/Emojis/index.cjs.js → plugins/Emojis/index.node.cjs} +31 -192
- package/dist/plugins/Emojis/index.node.cjs.map +7 -0
- package/dist/plugins/encoders/mp3.browser.cjs +105 -0
- package/dist/plugins/encoders/mp3.browser.cjs.map +7 -0
- package/dist/{components/MediaRecorder/transcode → plugins/encoders}/mp3.js +3 -3
- package/dist/plugins/encoders/mp3.node.cjs +109 -0
- package/dist/plugins/encoders/mp3.node.cjs.map +7 -0
- 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/Channel/Channel-layout.scss +0 -4
- 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 +39 -6
- package/dist/scss/v2/MessageReactions/MessageReactionsSelector-layout.scss +18 -0
- package/dist/scss/v2/MessageReactions/MessageReactionsSelector-theme.scss +5 -0
- package/dist/scss/v2/Thread/Thread-layout.scss +0 -5
- package/dist/scss/v2/_base.scss +1 -0
- package/dist/scss/v2/_emoji-replacement.scss +4 -2
- package/package.json +50 -18
- 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/index.cjs.js.map +0 -7
- /package/dist/{components → plugins}/Emojis/EmojiPicker.d.ts +0 -0
- /package/dist/{components/MediaRecorder/transcode → plugins/encoders}/mp3.d.ts +0 -0
|
@@ -1,8 +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
|
-
export declare const defaultTimestampFormat = "h:mmA";
|
|
6
5
|
export type MessageTimestampProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = TimestampFormatterOptions & {
|
|
7
6
|
customClass?: string;
|
|
8
7
|
message?: StreamMessage<StreamChatGenerics>;
|
|
@@ -2,7 +2,6 @@ import React from 'react';
|
|
|
2
2
|
import { useMessageContext } from '../../context/MessageContext';
|
|
3
3
|
import { Timestamp as DefaultTimestamp } from './Timestamp';
|
|
4
4
|
import { useComponentContext } from '../../context';
|
|
5
|
-
export const defaultTimestampFormat = 'h:mmA';
|
|
6
5
|
const UnMemoizedMessageTimestamp = (props) => {
|
|
7
6
|
const { message: propMessage, ...timestampProps } = props;
|
|
8
7
|
const { message: contextMessage } = useMessageContext('MessageTimestamp');
|
|
@@ -1,8 +1,7 @@
|
|
|
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;
|
|
6
6
|
}
|
|
7
|
-
export declare const defaultTimestampFormat = "h:mmA";
|
|
8
7
|
export declare function Timestamp(props: TimestampProps): React.JSX.Element | null;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { useMessageContext } from '../../context/MessageContext';
|
|
3
|
-
import {
|
|
4
|
-
import { getDateString } from '../../i18n/utils';
|
|
5
|
-
export const defaultTimestampFormat = 'h:mmA';
|
|
3
|
+
import { useTranslationContext } from '../../context/TranslationContext';
|
|
4
|
+
import { getDateString, isDate } from '../../i18n/utils';
|
|
6
5
|
export function Timestamp(props) {
|
|
7
|
-
const { calendar, calendarFormats, customClass, format
|
|
6
|
+
const { calendar, calendarFormats, customClass, format, timestamp } = props;
|
|
8
7
|
const { formatDate } = useMessageContext('MessageTimestamp');
|
|
9
8
|
const { t, tDateTimeParser } = useTranslationContext('MessageTimestamp');
|
|
10
9
|
const normalizedTimestamp = timestamp && isDate(timestamp) ? timestamp.toISOString() : timestamp;
|
|
@@ -16,7 +15,7 @@ export function Timestamp(props) {
|
|
|
16
15
|
messageCreatedAt: normalizedTimestamp,
|
|
17
16
|
t,
|
|
18
17
|
tDateTimeParser,
|
|
19
|
-
timestampTranslationKey: 'timestamp/
|
|
18
|
+
timestampTranslationKey: 'timestamp/MessageTimestamp',
|
|
20
19
|
}), [calendar, calendarFormats, format, formatDate, normalizedTimestamp, t, tDateTimeParser]);
|
|
21
20
|
if (!when) {
|
|
22
21
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Nodes } from 'hast-util-find-and-replace/lib';
|
|
2
2
|
import type { UserResponse } from 'stream-chat';
|
|
3
|
-
import type { DefaultStreamChatGenerics } from '../../../../types
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../../../types';
|
|
4
4
|
export declare const mentionsMarkdownPlugin: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(mentioned_users: UserResponse<StreamChatGenerics>[]) => () => (tree: Nodes) => void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Nodes } from '
|
|
1
|
+
import type { Nodes } from 'hast-util-find-and-replace/lib';
|
|
2
2
|
export declare const htmlToTextPlugin: () => (tree: Nodes) => void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Nodes } from '
|
|
1
|
+
import type { Nodes } from 'hast-util-find-and-replace/lib';
|
|
2
2
|
export declare const keepLineBreaksPlugin: () => (tree: Nodes) => void;
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { visit } from 'unist-util-visit';
|
|
2
2
|
import { u } from 'unist-builder';
|
|
3
3
|
const visitor = (node, index, parent) => {
|
|
4
|
-
if (
|
|
5
|
-
return;
|
|
6
|
-
if (typeof parent === 'undefined')
|
|
7
|
-
return;
|
|
8
|
-
if (!node.position)
|
|
4
|
+
if (!(index && parent && node.position))
|
|
9
5
|
return;
|
|
10
6
|
const prevSibling = parent.children.at(index - 1);
|
|
11
7
|
if (!prevSibling?.position)
|
|
12
8
|
return;
|
|
13
9
|
if (node.position.start.line === prevSibling.position.start.line)
|
|
14
|
-
return
|
|
10
|
+
return;
|
|
15
11
|
const ownStartLine = node.position.start.line;
|
|
16
12
|
const prevEndLine = prevSibling.position.end.line;
|
|
17
13
|
// the -1 is adjustment for the single line break into which multiple line breaks are converted
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { ComponentType } from 'react';
|
|
2
2
|
import { Options } from 'react-markdown';
|
|
3
|
-
import {
|
|
4
|
-
import type { PluggableList } from 'react-markdown/lib';
|
|
3
|
+
import type { PluggableList } from 'react-markdown/lib/react-markdown';
|
|
5
4
|
import type { UserResponse } from 'stream-chat';
|
|
5
|
+
import { MentionProps } from './componentRenderers';
|
|
6
6
|
import type { DefaultStreamChatGenerics } from '../../../types/types';
|
|
7
7
|
export type RenderTextPluginConfigurator = (defaultPlugins: PluggableList) => PluggableList;
|
|
8
8
|
export declare const defaultAllowedTagNames: Array<keyof JSX.IntrinsicElements | 'emoji' | 'mention'>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import ReactMarkdown, {
|
|
2
|
+
import ReactMarkdown, { uriTransformer } from 'react-markdown';
|
|
3
3
|
import { find } from 'linkifyjs';
|
|
4
4
|
import uniqBy from 'lodash.uniqby';
|
|
5
5
|
import remarkGfm from 'remark-gfm';
|
|
@@ -7,6 +7,7 @@ import { Anchor, Emoji, Mention } from './componentRenderers';
|
|
|
7
7
|
import { detectHttp, escapeRegExp, matchMarkdownLinks, messageCodeBlocks } from './regex';
|
|
8
8
|
import { emojiMarkdownPlugin, mentionsMarkdownPlugin } from './rehypePlugins';
|
|
9
9
|
import { htmlToTextPlugin, keepLineBreaksPlugin } from './remarkPlugins';
|
|
10
|
+
import { ErrorBoundary } from '../../UtilityComponents';
|
|
10
11
|
export const defaultAllowedTagNames = [
|
|
11
12
|
'html',
|
|
12
13
|
'text',
|
|
@@ -42,7 +43,7 @@ function encodeDecode(url) {
|
|
|
42
43
|
return url;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
const urlTransform = (uri) => (uri.startsWith('app://') ? uri :
|
|
46
|
+
const urlTransform = (uri) => (uri.startsWith('app://') ? uri : uriTransformer(uri));
|
|
46
47
|
const getPluginsForward = (plugins) => plugins;
|
|
47
48
|
export const markDownRenderers = {
|
|
48
49
|
a: Anchor,
|
|
@@ -106,8 +107,9 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
|
|
|
106
107
|
if (mentionedUsers?.length) {
|
|
107
108
|
rehypePlugins.push(mentionsMarkdownPlugin(mentionedUsers));
|
|
108
109
|
}
|
|
109
|
-
return (React.createElement(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
return (React.createElement(ErrorBoundary, { fallback: React.createElement(React.Fragment, null, text) },
|
|
111
|
+
React.createElement(ReactMarkdown, { allowedElements: allowedTagNames, components: {
|
|
112
|
+
...markDownRenderers,
|
|
113
|
+
...customMarkDownRenderers,
|
|
114
|
+
}, rehypePlugins: getRehypePlugins(rehypePlugins), remarkPlugins: getRemarkPlugins(remarkPlugins), skipHtml: true, transformLinkUri: urlTransform, unwrapDisallowed: true }, newText)));
|
|
113
115
|
};
|
|
@@ -212,6 +212,8 @@ export const areMessageUIPropsEqual = (prevProps, nextProps) => {
|
|
|
212
212
|
return false;
|
|
213
213
|
if (prevProps.readBy?.length !== nextProps.readBy?.length)
|
|
214
214
|
return false;
|
|
215
|
+
if (prevProps.groupStyles !== nextProps.groupStyles)
|
|
216
|
+
return false;
|
|
215
217
|
if (prevProps.showDetailedReactions !== nextProps.showDetailedReactions) {
|
|
216
218
|
return false;
|
|
217
219
|
}
|
|
@@ -2,37 +2,33 @@ import React from 'react';
|
|
|
2
2
|
import { UnsupportedAttachmentPreview as DefaultUnknownAttachmentPreview, } from './UnsupportedAttachmentPreview';
|
|
3
3
|
import { VoiceRecordingPreview as DefaultVoiceRecordingPreview, } from './VoiceRecordingPreview';
|
|
4
4
|
import { FileAttachmentPreview as DefaultFilePreview, } from './FileAttachmentPreview';
|
|
5
|
-
import { FileUploadPreviewAdapter, ImageUploadPreviewAdapter } from './UploadPreviewItem';
|
|
6
5
|
import { ImageAttachmentPreview as DefaultImagePreview, } from './ImageAttachmentPreview';
|
|
7
6
|
import { isLocalAttachment, isLocalAudioAttachment, isLocalFileAttachment, isLocalImageAttachment, isLocalMediaAttachment, isLocalVoiceRecordingAttachment, isScrapedContent, } from '../../Attachment';
|
|
8
7
|
import { useMessageInputContext } from '../../../context';
|
|
9
8
|
export const AttachmentPreviewList = ({ AudioAttachmentPreview = DefaultFilePreview, FileAttachmentPreview = DefaultFilePreview, ImageAttachmentPreview = DefaultImagePreview, UnsupportedAttachmentPreview = DefaultUnknownAttachmentPreview, VideoAttachmentPreview = DefaultFilePreview, VoiceRecordingPreview = DefaultVoiceRecordingPreview, }) => {
|
|
10
|
-
const { attachments,
|
|
9
|
+
const { attachments, removeAttachments, uploadAttachment, } = useMessageInputContext('AttachmentPreviewList');
|
|
11
10
|
return (React.createElement("div", { className: 'str-chat__attachment-preview-list' },
|
|
12
|
-
React.createElement("div", { className: 'str-chat__attachment-list-scroll-container', "data-testid": 'attachment-list-scroll-container' },
|
|
13
|
-
|
|
14
|
-
if (isScrapedContent(attachment))
|
|
15
|
-
return null;
|
|
16
|
-
if (isLocalVoiceRecordingAttachment(attachment)) {
|
|
17
|
-
return (React.createElement(VoiceRecordingPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
18
|
-
}
|
|
19
|
-
else if (isLocalAudioAttachment(attachment)) {
|
|
20
|
-
return (React.createElement(AudioAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
21
|
-
}
|
|
22
|
-
else if (isLocalMediaAttachment(attachment)) {
|
|
23
|
-
return (React.createElement(VideoAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
24
|
-
}
|
|
25
|
-
else if (isLocalImageAttachment(attachment)) {
|
|
26
|
-
return (React.createElement(ImageAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.image_url, removeAttachments: removeAttachments }));
|
|
27
|
-
}
|
|
28
|
-
else if (isLocalFileAttachment(attachment)) {
|
|
29
|
-
return (React.createElement(FileAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
30
|
-
}
|
|
31
|
-
else if (isLocalAttachment(attachment)) {
|
|
32
|
-
return (React.createElement(UnsupportedAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id, removeAttachments: removeAttachments }));
|
|
33
|
-
}
|
|
11
|
+
React.createElement("div", { className: 'str-chat__attachment-list-scroll-container', "data-testid": 'attachment-list-scroll-container' }, attachments.map((attachment) => {
|
|
12
|
+
if (isScrapedContent(attachment))
|
|
34
13
|
return null;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
if (isLocalVoiceRecordingAttachment(attachment)) {
|
|
15
|
+
return (React.createElement(VoiceRecordingPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
16
|
+
}
|
|
17
|
+
else if (isLocalAudioAttachment(attachment)) {
|
|
18
|
+
return (React.createElement(AudioAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
19
|
+
}
|
|
20
|
+
else if (isLocalMediaAttachment(attachment)) {
|
|
21
|
+
return (React.createElement(VideoAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
22
|
+
}
|
|
23
|
+
else if (isLocalImageAttachment(attachment)) {
|
|
24
|
+
return (React.createElement(ImageAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.image_url, removeAttachments: removeAttachments }));
|
|
25
|
+
}
|
|
26
|
+
else if (isLocalFileAttachment(attachment)) {
|
|
27
|
+
return (React.createElement(FileAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: removeAttachments }));
|
|
28
|
+
}
|
|
29
|
+
else if (isLocalAttachment(attachment)) {
|
|
30
|
+
return (React.createElement(UnsupportedAttachmentPreview, { attachment: attachment, handleRetry: uploadAttachment, key: attachment.localMetadata.id, removeAttachments: removeAttachments }));
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}))));
|
|
38
34
|
};
|
|
@@ -13,7 +13,7 @@ export const FileAttachmentPreview = ({ attachment, handleRetry, removeAttachmen
|
|
|
13
13
|
React.createElement(RetryIcon, null))),
|
|
14
14
|
React.createElement("div", { className: 'str-chat__attachment-preview-file-end' },
|
|
15
15
|
React.createElement("div", { className: 'str-chat__attachment-preview-file-name', title: attachment.title }, attachment.title),
|
|
16
|
-
attachment.localMetadata?.uploadState === 'finished' && !!attachment.asset_url && (React.createElement("a", { className: 'str-chat__attachment-preview-file-download', download: true, href: attachment.asset_url, rel: 'noreferrer', target: '_blank' },
|
|
16
|
+
attachment.localMetadata?.uploadState === 'finished' && !!attachment.asset_url && (React.createElement("a", { "aria-label": t('aria/Download attachment'), className: 'str-chat__attachment-preview-file-download', download: true, href: attachment.asset_url, rel: 'noreferrer', target: '_blank', title: t('Download attachment {{ name }}', { name: attachment.title }) },
|
|
17
17
|
React.createElement(DownloadIcon, null))),
|
|
18
18
|
attachment.localMetadata?.uploadState === 'uploading' && (React.createElement(LoadingIndicatorIcon, { size: 17 })))));
|
|
19
19
|
};
|
|
@@ -9,6 +9,7 @@ export const ImageAttachmentPreview = ({ attachment, handleRetry, removeAttachme
|
|
|
9
9
|
const [previewError, setPreviewError] = useState(false);
|
|
10
10
|
const { id, uploadState } = attachment.localMetadata ?? {};
|
|
11
11
|
const handleLoadError = useCallback(() => setPreviewError(true), []);
|
|
12
|
+
const assetUrl = attachment.image_url || attachment.localMetadata.previewUri;
|
|
12
13
|
return (React.createElement("div", { className: clsx('str-chat__attachment-preview-image', {
|
|
13
14
|
'str-chat__attachment-preview-image--error': previewError,
|
|
14
15
|
}), "data-testid": 'attachment-preview-image' },
|
|
@@ -18,5 +19,5 @@ export const ImageAttachmentPreview = ({ attachment, handleRetry, removeAttachme
|
|
|
18
19
|
React.createElement(RetryIcon, null))),
|
|
19
20
|
uploadState === 'uploading' && (React.createElement("div", { className: 'str-chat__attachment-preview-image-loading' },
|
|
20
21
|
React.createElement(LoadingIndicatorIcon, { size: 17 }))),
|
|
21
|
-
|
|
22
|
+
assetUrl && (React.createElement(BaseImage, { alt: attachment.fallback, className: 'str-chat__attachment-preview-thumbnail', onError: handleLoadError, src: assetUrl, title: attachment.fallback }))));
|
|
22
23
|
};
|
|
@@ -2,11 +2,11 @@ import React from 'react';
|
|
|
2
2
|
import { StreamMessage } from '../../context/ChannelStateContext';
|
|
3
3
|
import { ComponentContextValue } from '../../context/ComponentContext';
|
|
4
4
|
import type { Channel, Message, SendFileAPIResponse } from 'stream-chat';
|
|
5
|
+
import type { BaseLocalAttachmentMetadata, LocalAttachmentUploadMetadata } from './types';
|
|
5
6
|
import type { SearchQueryParams } from '../ChannelSearch/hooks/useChannelSearch';
|
|
6
7
|
import type { MessageToSend } from '../../context/ChannelActionContext';
|
|
7
8
|
import type { CustomTrigger, DefaultStreamChatGenerics, SendMessageOptions, UnknownType } from '../../types/types';
|
|
8
9
|
import type { URLEnrichmentConfig } from './hooks/useLinkPreviews';
|
|
9
|
-
import type { FileUpload, ImageUpload } from './types';
|
|
10
10
|
import type { CustomAudioRecordingConfig } from '../MediaRecorder';
|
|
11
11
|
export type EmojiSearchIndexResult = {
|
|
12
12
|
id: string;
|
|
@@ -39,15 +39,13 @@ export type MessageInputProps<StreamChatGenerics extends DefaultStreamChatGeneri
|
|
|
39
39
|
/** If true, the suggestion list will not display and autocomplete @mentions. Default: false. */
|
|
40
40
|
disableMentions?: boolean;
|
|
41
41
|
/** Function to override the default file upload request */
|
|
42
|
-
doFileUploadRequest?: (file:
|
|
42
|
+
doFileUploadRequest?: (file: LocalAttachmentUploadMetadata['file'], channel: Channel<StreamChatGenerics>) => Promise<SendFileAPIResponse>;
|
|
43
43
|
/** Function to override the default image upload request */
|
|
44
|
-
doImageUploadRequest?: (file:
|
|
44
|
+
doImageUploadRequest?: (file: LocalAttachmentUploadMetadata['file'], channel: Channel<StreamChatGenerics>) => Promise<SendFileAPIResponse>;
|
|
45
45
|
/** Mechanism to be used with autocomplete and text replace features of the `MessageInput` component, see [emoji-mart `SearchIndex`](https://github.com/missive/emoji-mart#%EF%B8%8F%EF%B8%8F-headless-search) */
|
|
46
46
|
emojiSearchIndex?: ComponentContextValue['emojiSearchIndex'];
|
|
47
47
|
/** Custom error handler function to be called with a file/image upload fails */
|
|
48
|
-
errorHandler?: (error: Error, type: string, file:
|
|
49
|
-
id?: string;
|
|
50
|
-
}) => void;
|
|
48
|
+
errorHandler?: (error: Error, type: string, file: LocalAttachmentUploadMetadata['file'] & BaseLocalAttachmentMetadata) => void;
|
|
51
49
|
/** If true, focuses the text input on component mount */
|
|
52
50
|
focus?: boolean;
|
|
53
51
|
/** Generates the default value for the underlying textarea element. The function's return value takes precedence before additionalTextareaProps.defaultValue. */
|
|
@@ -20,7 +20,7 @@ import { useMessageInputContext } from '../../context/MessageInputContext';
|
|
|
20
20
|
import { useComponentContext } from '../../context/ComponentContext';
|
|
21
21
|
export const MessageInputFlat = () => {
|
|
22
22
|
const { t } = useTranslationContext('MessageInputFlat');
|
|
23
|
-
const { asyncMessagesMultiSendEnabled, attachments, cooldownRemaining,
|
|
23
|
+
const { asyncMessagesMultiSendEnabled, attachments, cooldownRemaining, findAndEnqueueURLsToEnrich, handleSubmit, hideSendButton, isUploadEnabled, linkPreviews, maxFilesLeft, message, numberOfUploads, recordingController, setCooldownRemaining, text, uploadNewFiles, } = useMessageInputContext('MessageInputFlat');
|
|
24
24
|
const { AudioRecorder = DefaultAudioRecorder, AttachmentPreviewList = DefaultAttachmentPreviewList, CooldownTimer = DefaultCooldownTimer, FileUploadIcon = DefaultUploadIcon, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, EmojiPicker, } = useComponentContext('MessageInputFlat');
|
|
25
25
|
const { acceptedFiles = [], multipleUploads, quotedMessage, } = useChannelStateContext('MessageInputFlat');
|
|
26
26
|
const { setQuotedMessage } = useChannelActionContext('MessageInputFlat');
|
|
@@ -30,9 +30,7 @@ export const MessageInputFlat = () => {
|
|
|
30
30
|
setShowRecordingPermissionDeniedNotification(false);
|
|
31
31
|
}, []);
|
|
32
32
|
const id = useMemo(() => nanoid(), []);
|
|
33
|
-
const
|
|
34
|
-
const failedUploadsCount = useMemo(() => Object.values(fileUploads).filter((upload) => upload.state === 'failed').length +
|
|
35
|
-
Object.values(imageUploads).filter((upload) => upload.state === 'failed').length, [fileUploads, imageUploads]);
|
|
33
|
+
const failedUploadsCount = useMemo(() => attachments.filter((a) => a.localMetadata?.uploadState === 'failed').length, [attachments]);
|
|
36
34
|
const accept = useMemo(() => acceptedFiles.reduce((mediaTypeMap, mediaType) => {
|
|
37
35
|
mediaTypeMap[mediaType] ?? (mediaTypeMap[mediaType] = []);
|
|
38
36
|
return mediaTypeMap;
|
|
@@ -89,15 +87,14 @@ export const MessageInputFlat = () => {
|
|
|
89
87
|
React.createElement("div", { className: 'str-chat__message-textarea-container' },
|
|
90
88
|
displayQuotedMessage && React.createElement(QuotedMessagePreview, { quotedMessage: quotedMessage }),
|
|
91
89
|
isUploadEnabled &&
|
|
92
|
-
!!(numberOfUploads + failedUploadsCount ||
|
|
93
|
-
(attachments.length && attachments.length !== linkPreviews.size)) && React.createElement(AttachmentPreviewList, null),
|
|
90
|
+
!!(numberOfUploads + failedUploadsCount || attachments.length > 0) && (React.createElement(AttachmentPreviewList, null)),
|
|
94
91
|
React.createElement("div", { className: 'str-chat__message-textarea-with-emoji-picker' },
|
|
95
92
|
React.createElement(ChatAutoComplete, null),
|
|
96
93
|
EmojiPicker && React.createElement(EmojiPicker, null))),
|
|
97
94
|
!hideSendButton && (React.createElement(React.Fragment, null, cooldownRemaining ? (React.createElement(CooldownTimer, { cooldownInterval: cooldownRemaining, setCooldownRemaining: setCooldownRemaining })) : (React.createElement(React.Fragment, null,
|
|
98
95
|
React.createElement(SendButton, { disabled: !numberOfUploads &&
|
|
99
96
|
!text.length &&
|
|
100
|
-
attachments.length -
|
|
97
|
+
attachments.length - failedUploadsCount === 0, sendMessage: handleSubmit }),
|
|
101
98
|
recordingEnabled && (React.createElement(StartRecordingAudioButton, { disabled: isRecording ||
|
|
102
99
|
(!asyncMessagesMultiSendEnabled &&
|
|
103
100
|
attachments.some((a) => a.type === RecordingAttachmentType.VOICE_RECORDING)), onClick: () => {
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
+
import type { FileLike } from '../../ReactFileUtilities';
|
|
1
2
|
import type { Attachment } from 'stream-chat';
|
|
2
3
|
import type { MessageInputReducerAction, MessageInputState } from './useMessageInputState';
|
|
3
4
|
import type { MessageInputProps } from '../MessageInput';
|
|
4
5
|
import type { LocalAttachment } from '../types';
|
|
5
|
-
import type { FileLike } from '../../ReactFileUtilities';
|
|
6
6
|
import type { CustomTrigger, DefaultStreamChatGenerics } from '../../../types/types';
|
|
7
7
|
export declare const useAttachments: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, V extends CustomTrigger = CustomTrigger>(props: MessageInputProps<StreamChatGenerics, V>, state: MessageInputState<StreamChatGenerics>, dispatch: React.Dispatch<MessageInputReducerAction<StreamChatGenerics>>, textareaRef: React.MutableRefObject<HTMLTextAreaElement | undefined>) => {
|
|
8
8
|
maxFilesLeft: number;
|
|
9
9
|
numberOfUploads: number;
|
|
10
10
|
removeAttachments: (ids: string[]) => void;
|
|
11
|
-
removeFile: (id: string) => void;
|
|
12
|
-
removeImage: (id: string) => void;
|
|
13
11
|
uploadAttachment: (att: LocalAttachment<StreamChatGenerics>) => Promise<LocalAttachment<StreamChatGenerics> | undefined>;
|
|
14
|
-
uploadFile: (id: string) => void;
|
|
15
|
-
uploadImage: (id: string) => Promise<void>;
|
|
16
12
|
uploadNewFiles: (files: FileList | File[] | FileLike[]) => void;
|
|
17
13
|
upsertAttachments: (attachments: (Attachment<StreamChatGenerics> | LocalAttachment<StreamChatGenerics>)[]) => void;
|
|
18
14
|
};
|
|
@@ -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,
|