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
package/README.md
CHANGED
|
@@ -110,3 +110,13 @@ We recently closed a [$38 million Series B funding round](https://techcrunch.com
|
|
|
110
110
|
Our APIs are used by more than a billion end-users, and by working at Stream, you have the chance to make a huge impact on a team of very strong engineers.
|
|
111
111
|
|
|
112
112
|
Check out our current openings and apply via [Stream's website](https://getstream.io/team/#jobs).
|
|
113
|
+
|
|
114
|
+
## Acknowledgements
|
|
115
|
+
|
|
116
|
+
### Lamejs
|
|
117
|
+
|
|
118
|
+
This project uses `lamejs` library under the LGPL license to convert the recorded audio to mp3 format.
|
|
119
|
+
The library source code is dynamically imported and used only if audio recording is enabled.
|
|
120
|
+
|
|
121
|
+
You can obtain the source code for `lamejs` from the [lamejs repository](https://github.com/gideonstele/lamejs) that is a fork of [the original JS library](https://github.com/zhuker/lamejs).
|
|
122
|
+
You can find the source code for LAME at https://lame.sourceforge.net and its license at: https://lame.sourceforge.net/license.txt
|
|
@@ -9,6 +9,8 @@ type WaveProgressBarProps = {
|
|
|
9
9
|
amplitudesCount?: number;
|
|
10
10
|
/** Progress expressed in fractional number value btw 0 and 100. */
|
|
11
11
|
progress?: number;
|
|
12
|
+
relativeAmplitudeBarWidth?: number;
|
|
13
|
+
relativeAmplitudeGap?: number;
|
|
12
14
|
};
|
|
13
|
-
export declare const WaveProgressBar: ({ amplitudesCount, progress, seek, waveformData, }: WaveProgressBarProps) => React.JSX.Element | null;
|
|
15
|
+
export declare const WaveProgressBar: ({ amplitudesCount, progress, relativeAmplitudeBarWidth, relativeAmplitudeGap, seek, waveformData, }: WaveProgressBarProps) => React.JSX.Element | null;
|
|
14
16
|
export {};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import throttle from 'lodash.throttle';
|
|
2
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
2
3
|
import clsx from 'clsx';
|
|
3
4
|
import { resampleWaveformData } from '../audioSampling';
|
|
4
|
-
export const WaveProgressBar = ({ amplitudesCount = 40, progress = 0, seek, waveformData, }) => {
|
|
5
|
+
export const WaveProgressBar = ({ amplitudesCount = 40, progress = 0, relativeAmplitudeBarWidth = 2, relativeAmplitudeGap = 1, seek, waveformData, }) => {
|
|
5
6
|
const [progressIndicator, setProgressIndicator] = useState(null);
|
|
6
7
|
const isDragging = useRef(false);
|
|
7
|
-
const
|
|
8
|
+
const [root, setRoot] = useState(null);
|
|
9
|
+
const [trackAxisX, setTrackAxisX] = useState();
|
|
10
|
+
const lastRootWidth = useRef();
|
|
8
11
|
const handleDragStart = (e) => {
|
|
9
12
|
e.preventDefault();
|
|
10
13
|
if (!progressIndicator)
|
|
@@ -25,22 +28,54 @@ export const WaveProgressBar = ({ amplitudesCount = 40, progress = 0, seek, wave
|
|
|
25
28
|
isDragging.current = false;
|
|
26
29
|
progressIndicator.style.removeProperty('cursor');
|
|
27
30
|
}, [progressIndicator]);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const getTrackAxisX = useMemo(() => throttle((rootWidth) => {
|
|
32
|
+
if (rootWidth === lastRootWidth.current)
|
|
33
|
+
return;
|
|
34
|
+
lastRootWidth.current = rootWidth;
|
|
35
|
+
const possibleAmpCount = Math.floor(rootWidth / (relativeAmplitudeGap + relativeAmplitudeBarWidth));
|
|
36
|
+
const tooManyAmplitudesToRender = possibleAmpCount < amplitudesCount;
|
|
37
|
+
const barCount = tooManyAmplitudesToRender ? possibleAmpCount : amplitudesCount;
|
|
38
|
+
const amplitudeBarWidthToGapRatio = relativeAmplitudeBarWidth / (relativeAmplitudeBarWidth + relativeAmplitudeGap);
|
|
39
|
+
const barWidth = barCount && (rootWidth / barCount) * amplitudeBarWidthToGapRatio;
|
|
40
|
+
setTrackAxisX({
|
|
41
|
+
barCount,
|
|
42
|
+
barWidth,
|
|
43
|
+
gap: barWidth * (relativeAmplitudeGap / relativeAmplitudeBarWidth),
|
|
44
|
+
});
|
|
45
|
+
}, 1), [relativeAmplitudeBarWidth, relativeAmplitudeGap, amplitudesCount]);
|
|
46
|
+
const resampledWaveformData = useMemo(() => (trackAxisX ? resampleWaveformData(waveformData, trackAxisX.barCount) : []), [trackAxisX, waveformData]);
|
|
32
47
|
useEffect(() => {
|
|
33
48
|
document.addEventListener('pointerup', handleDragStop);
|
|
34
49
|
return () => {
|
|
35
50
|
document.removeEventListener('pointerup', handleDragStop);
|
|
36
51
|
};
|
|
37
52
|
}, [handleDragStop]);
|
|
38
|
-
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!root || typeof ResizeObserver === 'undefined')
|
|
55
|
+
return;
|
|
56
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
57
|
+
getTrackAxisX(entry.contentRect.width);
|
|
58
|
+
});
|
|
59
|
+
observer.observe(root);
|
|
60
|
+
return () => {
|
|
61
|
+
observer.disconnect();
|
|
62
|
+
};
|
|
63
|
+
}, [getTrackAxisX, root]);
|
|
64
|
+
useLayoutEffect(() => {
|
|
65
|
+
if (!root)
|
|
66
|
+
return;
|
|
67
|
+
const { width: rootWidth } = root.getBoundingClientRect();
|
|
68
|
+
getTrackAxisX(rootWidth);
|
|
69
|
+
}, [getTrackAxisX, root]);
|
|
70
|
+
if (!waveformData.length || trackAxisX?.barCount === 0)
|
|
39
71
|
return null;
|
|
40
|
-
return (React.createElement("div", { className: 'str-chat__wave-progress-bar__track', "data-testid": 'wave-progress-bar-track', onClick: seek, onPointerDown: handleDragStart, onPointerMove: handleDrag, onPointerUp: handleDragStop, ref:
|
|
72
|
+
return (React.createElement("div", { className: 'str-chat__wave-progress-bar__track', "data-testid": 'wave-progress-bar-track', onClick: seek, onPointerDown: handleDragStart, onPointerMove: handleDrag, onPointerUp: handleDragStop, ref: setRoot, role: 'progressbar', style: {
|
|
73
|
+
'--str-chat__voice-recording-amplitude-bar-gap-width': trackAxisX?.gap + 'px',
|
|
74
|
+
} },
|
|
41
75
|
resampledWaveformData.map((amplitude, i) => (React.createElement("div", { className: clsx('str-chat__wave-progress-bar__amplitude-bar', {
|
|
42
76
|
['str-chat__wave-progress-bar__amplitude-bar--active']: progress > (i / resampledWaveformData.length) * 100,
|
|
43
77
|
}), "data-testid": 'amplitude-bar', key: `amplitude-${i}`, style: {
|
|
78
|
+
'--str-chat__voice-recording-amplitude-bar-width': trackAxisX?.barWidth + 'px',
|
|
44
79
|
'--str-chat__wave-progress-bar__amplitude-bar-height': amplitude
|
|
45
80
|
? amplitude * 100 + '%'
|
|
46
81
|
: '0%',
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { useTranslationContext } from '../../context/TranslationContext';
|
|
3
3
|
import { getDateString } from '../../i18n/utils';
|
|
4
4
|
const UnMemoizedDateSeparator = (props) => {
|
|
5
|
-
const { calendar
|
|
5
|
+
const { calendar, date: messageCreatedAt, formatDate, position = 'right', unread, ...restTimestampFormatterOptions } = props;
|
|
6
6
|
const { t, tDateTimeParser } = useTranslationContext('DateSeparator');
|
|
7
7
|
const formattedDate = getDateString({
|
|
8
8
|
calendar,
|
|
@@ -6,7 +6,7 @@ import { getDateString } from '../../i18n/utils';
|
|
|
6
6
|
* Component to display system and channel event messages
|
|
7
7
|
*/
|
|
8
8
|
const UnMemoizedEventComponent = (props) => {
|
|
9
|
-
const { calendar, calendarFormats, format
|
|
9
|
+
const { calendar, calendarFormats, format, Avatar = DefaultAvatar, message } = props;
|
|
10
10
|
const { t, tDateTimeParser } = useTranslationContext('EventComponent');
|
|
11
11
|
const { created_at = '', event, text, type } = message;
|
|
12
12
|
const getDateOptions = { messageCreatedAt: created_at.toString(), tDateTimeParser };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect,
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { deprecationAndReplacementWarning } from '../../utils/deprecationWarning';
|
|
3
3
|
import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD } from '../../constants/limits';
|
|
4
4
|
/**
|
|
@@ -17,6 +17,8 @@ export const InfiniteScroll = (props) => {
|
|
|
17
17
|
const hasNextPageFlag = hasNextPage || hasMoreNewer;
|
|
18
18
|
const hasPreviousPageFlag = hasPreviousPage || hasMore;
|
|
19
19
|
const scrollComponent = useRef();
|
|
20
|
+
const previousOffset = useRef();
|
|
21
|
+
const previousReverseOffset = useRef();
|
|
20
22
|
const scrollListenerRef = useRef();
|
|
21
23
|
scrollListenerRef.current = () => {
|
|
22
24
|
const element = scrollComponent.current;
|
|
@@ -32,7 +34,11 @@ export const InfiniteScroll = (props) => {
|
|
|
32
34
|
}
|
|
33
35
|
if (isLoading)
|
|
34
36
|
return;
|
|
35
|
-
|
|
37
|
+
if (previousOffset.current === offset && previousReverseOffset.current === reverseOffset)
|
|
38
|
+
return;
|
|
39
|
+
previousOffset.current = offset;
|
|
40
|
+
previousReverseOffset.current = reverseOffset;
|
|
41
|
+
// FIXME: this triggers loadMore call when a user types messages in thread and the scroll container expands
|
|
36
42
|
if (reverseOffset < Number(threshold) &&
|
|
37
43
|
typeof loadPreviousPageFn === 'function' &&
|
|
38
44
|
hasPreviousPageFlag) {
|
|
@@ -51,7 +57,7 @@ export const InfiniteScroll = (props) => {
|
|
|
51
57
|
], 'InfiniteScroll');
|
|
52
58
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
53
59
|
}, []);
|
|
54
|
-
|
|
60
|
+
useEffect(() => {
|
|
55
61
|
const scrollElement = scrollComponent.current?.parentNode;
|
|
56
62
|
if (!scrollElement)
|
|
57
63
|
return;
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import { AmplitudeRecorder, AmplitudeRecorderConfig } from './AmplitudeRecorder';
|
|
2
2
|
import { BrowserPermission } from './BrowserPermission';
|
|
3
3
|
import { BehaviorSubject, Subject } from '../observable';
|
|
4
|
+
import { TranscoderConfig } from '../transcode';
|
|
4
5
|
import { RecordedMediaType } from '../../ReactFileUtilities';
|
|
5
6
|
import { TranslationContextValue } from '../../../context';
|
|
6
7
|
import type { LocalVoiceRecordingAttachment } from '../../MessageInput';
|
|
7
8
|
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
8
|
-
export declare const POSSIBLE_TRANSCODING_MIME_TYPES: readonly ["audio/wav", "audio/mp3"];
|
|
9
9
|
export declare const DEFAULT_MEDIA_RECORDER_CONFIG: MediaRecorderConfig;
|
|
10
10
|
export declare const DEFAULT_AUDIO_TRANSCODER_CONFIG: TranscoderConfig;
|
|
11
|
-
type SupportedTranscodeMimeTypes = typeof POSSIBLE_TRANSCODING_MIME_TYPES[number];
|
|
12
|
-
export type TranscoderConfig = {
|
|
13
|
-
sampleRate: number;
|
|
14
|
-
targetMimeType: SupportedTranscodeMimeTypes;
|
|
15
|
-
};
|
|
16
11
|
type MediaRecorderConfig = Omit<MediaRecorderOptions, 'mimeType'> & Required<Pick<MediaRecorderOptions, 'mimeType'>>;
|
|
17
12
|
export type AudioRecorderConfig = {
|
|
18
13
|
amplitudeRecorderConfig: AmplitudeRecorderConfig;
|
|
19
14
|
mediaRecorderConfig: MediaRecorderOptions;
|
|
20
15
|
transcoderConfig: TranscoderConfig;
|
|
21
16
|
};
|
|
17
|
+
type PartialValues<T> = {
|
|
18
|
+
[P in keyof T]?: Partial<T[P]>;
|
|
19
|
+
};
|
|
20
|
+
export type CustomAudioRecordingConfig = PartialValues<AudioRecorderConfig>;
|
|
22
21
|
export type AudioRecorderOptions = {
|
|
23
|
-
config?:
|
|
22
|
+
config?: CustomAudioRecordingConfig;
|
|
24
23
|
generateRecordingTitle?: (mimeType: string) => string;
|
|
25
24
|
t?: TranslationContextValue['t'];
|
|
26
25
|
};
|
|
@@ -15,7 +15,6 @@ const RECORDED_MIME_TYPE_BY_BROWSER = {
|
|
|
15
15
|
safari: 'audio/mp4;codecs=mp4a.40.2',
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
|
-
export const POSSIBLE_TRANSCODING_MIME_TYPES = ['audio/wav', 'audio/mp3'];
|
|
19
18
|
export const DEFAULT_MEDIA_RECORDER_CONFIG = {
|
|
20
19
|
mimeType: isSafari()
|
|
21
20
|
? RECORDED_MIME_TYPE_BY_BROWSER.audio.safari
|
|
@@ -23,7 +22,6 @@ export const DEFAULT_MEDIA_RECORDER_CONFIG = {
|
|
|
23
22
|
};
|
|
24
23
|
export const DEFAULT_AUDIO_TRANSCODER_CONFIG = {
|
|
25
24
|
sampleRate: 16000,
|
|
26
|
-
targetMimeType: 'audio/mp3',
|
|
27
25
|
};
|
|
28
26
|
const disposeOfMediaStream = (stream) => {
|
|
29
27
|
if (!stream?.active)
|
|
@@ -245,9 +243,6 @@ export class MediaRecorderController {
|
|
|
245
243
|
this.amplitudeRecorderConfig = mergeDeepUndefined({ ...config?.amplitudeRecorderConfig }, DEFAULT_AMPLITUDE_RECORDER_CONFIG);
|
|
246
244
|
this.mediaRecorderConfig = mergeDeepUndefined({ ...config?.mediaRecorderConfig }, DEFAULT_MEDIA_RECORDER_CONFIG);
|
|
247
245
|
this.transcoderConfig = mergeDeepUndefined({ ...config?.transcoderConfig }, DEFAULT_AUDIO_TRANSCODER_CONFIG);
|
|
248
|
-
if (!POSSIBLE_TRANSCODING_MIME_TYPES.includes(this.transcoderConfig.targetMimeType)) {
|
|
249
|
-
this.transcoderConfig.targetMimeType = DEFAULT_AUDIO_TRANSCODER_CONFIG.targetMimeType;
|
|
250
|
-
}
|
|
251
246
|
const mediaType = getRecordedMediaTypeFromMimeType(this.mediaRecorderConfig.mimeType);
|
|
252
247
|
if (!mediaType) {
|
|
253
248
|
throw new Error(`Unsupported media type (supported audio or video only). Provided mimeType: ${this.mediaRecorderConfig.mimeType}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { RecordingController } from './useMediaRecorder';
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { MessageInputContextValue } from '../../../context';
|
|
2
|
-
import {
|
|
2
|
+
import { CustomAudioRecordingConfig, MediaRecorderController, MediaRecordingState } from '../classes';
|
|
3
3
|
import type { LocalVoiceRecordingAttachment } from '../../MessageInput';
|
|
4
4
|
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
5
|
-
export type CustomAudioRecordingConfig = Partial<AudioRecorderConfig>;
|
|
6
5
|
export type RecordingController<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
7
6
|
completeRecording: () => void;
|
|
8
7
|
permissionState?: PermissionState;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useTranslationContext } from '../../../context';
|
|
3
|
-
import { MediaRecorderController } from '../classes';
|
|
3
|
+
import { MediaRecorderController, } from '../classes';
|
|
4
4
|
export const useMediaRecorder = ({ asyncMessagesMultiSendEnabled, enabled, generateRecordingTitle, handleSubmit, recordingConfig, uploadAttachment, }) => {
|
|
5
5
|
const { t } = useTranslationContext('useMediaRecorder');
|
|
6
6
|
const [recording, setRecording] = useState();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
type
|
|
2
|
-
blob: Blob;
|
|
1
|
+
export type TranscoderConfig = {
|
|
3
2
|
sampleRate: number;
|
|
4
|
-
|
|
3
|
+
encoder?: (file: File, sampleRate: number) => Promise<Blob>;
|
|
4
|
+
};
|
|
5
|
+
export type TranscodeParams = TranscoderConfig & {
|
|
6
|
+
blob: Blob;
|
|
5
7
|
};
|
|
6
|
-
export declare const transcode: ({ blob, sampleRate,
|
|
7
|
-
export {};
|
|
8
|
+
export declare const transcode: ({ blob, encoder, sampleRate, }: TranscodeParams) => Promise<Blob>;
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { encodeToWaw } from './wav';
|
|
2
|
-
import { encodeToMp3 } from './mp3';
|
|
3
2
|
import { createFileFromBlobs, getExtensionFromMimeType } from '../../ReactFileUtilities';
|
|
4
|
-
export const transcode = ({ blob, sampleRate,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
});
|
|
10
|
-
if (targetMimeType.match('audio/wav')) {
|
|
11
|
-
return encodeToWaw(file, sampleRate);
|
|
12
|
-
}
|
|
13
|
-
if (targetMimeType.match('audio/mp3')) {
|
|
14
|
-
return encodeToMp3(file, sampleRate);
|
|
15
|
-
}
|
|
16
|
-
return Promise.resolve(blob);
|
|
17
|
-
};
|
|
3
|
+
export const transcode = ({ blob, encoder = encodeToWaw, sampleRate, }) => encoder(createFileFromBlobs({
|
|
4
|
+
blobsArray: [blob],
|
|
5
|
+
fileName: `audio_recording_${new Date().toISOString()}.${getExtensionFromMimeType(blob.type)}`,
|
|
6
|
+
mimeType: blob.type,
|
|
7
|
+
}), sampleRate);
|
|
@@ -92,7 +92,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
92
92
|
showMetadata && (React.createElement("div", { className: 'str-chat__message-metadata' },
|
|
93
93
|
React.createElement(MessageStatus, null),
|
|
94
94
|
!isMyMessage() && !!message.user && (React.createElement("span", { className: 'str-chat__message-simple-name' }, message.user.name || message.user.id)),
|
|
95
|
-
React.createElement(MessageTimestamp, {
|
|
95
|
+
React.createElement(MessageTimestamp, { customClass: 'str-chat__message-simple-timestamp' }),
|
|
96
96
|
isEdited && (React.createElement("span", { className: 'str-chat__mesage-simple-edited' }, t('Edited'))),
|
|
97
97
|
isEdited && React.createElement(MessageEditedTimestamp, { calendar: true, open: isEditedTimestampOpen }))))));
|
|
98
98
|
};
|
|
@@ -2,7 +2,6 @@ import React from 'react';
|
|
|
2
2
|
import type { StreamMessage } from '../../context/ChannelStateContext';
|
|
3
3
|
import type { DefaultStreamChatGenerics } from '../../types/types';
|
|
4
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');
|
|
@@ -2,9 +2,8 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
import { useMessageContext } from '../../context/MessageContext';
|
|
3
3
|
import { isDate, useTranslationContext } from '../../context/TranslationContext';
|
|
4
4
|
import { getDateString } from '../../i18n/utils';
|
|
5
|
-
export const defaultTimestampFormat = 'h:mmA';
|
|
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;
|
|
@@ -11,7 +11,7 @@ const visitor = (node, index, parent) => {
|
|
|
11
11
|
if (!prevSibling?.position)
|
|
12
12
|
return;
|
|
13
13
|
if (node.position.start.line === prevSibling.position.start.line)
|
|
14
|
-
return
|
|
14
|
+
return;
|
|
15
15
|
const ownStartLine = node.position.start.line;
|
|
16
16
|
const prevEndLine = prevSibling.position.end.line;
|
|
17
17
|
// the -1 is adjustment for the single line break into which multiple line breaks are converted
|
|
@@ -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
|
};
|