stream-chat-react 13.2.2 → 13.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Attachment/Attachment.d.ts +5 -3
- package/dist/components/Attachment/Attachment.js +12 -5
- package/dist/components/Attachment/AttachmentContainer.d.ts +4 -3
- package/dist/components/Attachment/AttachmentContainer.js +19 -11
- package/dist/components/Attachment/Geolocation.d.ts +13 -0
- package/dist/components/Attachment/Geolocation.js +34 -0
- package/dist/components/Attachment/icons.d.ts +2 -0
- package/dist/components/Attachment/icons.js +5 -0
- package/dist/components/Attachment/index.d.ts +3 -1
- package/dist/components/Attachment/index.js +3 -1
- package/dist/components/Attachment/utils.d.ts +4 -1
- package/dist/components/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +2 -0
- package/dist/components/ChannelPreview/utils.js +3 -0
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Dialog/DialogAnchor.d.ts +3 -2
- package/dist/components/Dialog/DialogAnchor.js +7 -2
- package/dist/components/Form/Dropdown.d.ts +14 -0
- package/dist/components/Form/Dropdown.js +49 -0
- package/dist/components/Form/SwitchField.js +3 -1
- package/dist/components/Location/ShareLocationDialog.d.ts +18 -0
- package/dist/components/Location/ShareLocationDialog.js +139 -0
- package/dist/components/Location/hooks/useLiveLocationSharingManager.d.ts +18 -0
- package/dist/components/Location/hooks/useLiveLocationSharingManager.js +57 -0
- package/dist/components/Location/index.d.ts +1 -0
- package/dist/components/Location/index.js +1 -0
- package/dist/components/Message/MessageSimple.js +6 -1
- package/dist/components/Message/index.d.ts +4 -1
- package/dist/components/Message/index.js +4 -1
- package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.d.ts +3 -1
- package/dist/components/MessageInput/AttachmentPreviewList/AttachmentPreviewList.js +35 -26
- package/dist/components/MessageInput/AttachmentPreviewList/GeolocationPreview.d.ts +13 -0
- package/dist/components/MessageInput/AttachmentPreviewList/GeolocationPreview.js +25 -0
- package/dist/components/MessageInput/AttachmentSelector.d.ts +2 -1
- package/dist/components/MessageInput/AttachmentSelector.js +34 -12
- package/dist/components/MessageInput/MessageInput.d.ts +3 -1
- package/dist/components/MessageInput/MessageInput.js +7 -3
- package/dist/components/MessageInput/hooks/index.d.ts +1 -0
- package/dist/components/MessageInput/hooks/index.js +1 -0
- package/dist/components/MessageInput/hooks/useAttachmentsForPreview.d.ts +17 -0
- package/dist/components/MessageInput/hooks/useAttachmentsForPreview.js +22 -0
- package/dist/components/Poll/PollActions/AddCommentForm.js +8 -0
- package/dist/components/Poll/PollActions/SuggestPollOptionForm.js +6 -3
- package/dist/components/TextareaComposer/SuggestionList/SuggestionListItem.js +4 -1
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +2 -0
- package/dist/context/ComponentContext.d.ts +3 -0
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/experimental/index.browser.cjs +11 -0
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs +11 -0
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/i18n/Streami18n.d.ts +20 -0
- package/dist/i18n/de.json +21 -1
- package/dist/i18n/en.json +21 -1
- package/dist/i18n/es.json +21 -1
- package/dist/i18n/fr.json +21 -1
- package/dist/i18n/hi.json +21 -1
- package/dist/i18n/it.json +21 -1
- package/dist/i18n/ja.json +21 -1
- package/dist/i18n/ko.json +21 -1
- package/dist/i18n/nl.json +21 -1
- package/dist/i18n/pt.json +21 -1
- package/dist/i18n/ru.json +21 -1
- package/dist/i18n/tr.json +21 -1
- package/dist/index.browser.cjs +1928 -1073
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +1941 -1073
- package/dist/index.node.cjs.map +4 -4
- package/dist/scss/v2/AttachmentList/AttachmentList-layout.scss +50 -0
- package/dist/scss/v2/AttachmentList/AttachmentList-theme.scss +56 -0
- package/dist/scss/v2/AttachmentPreviewList/AttachmentPreviewList-layout.scss +3 -0
- package/dist/scss/v2/AttachmentPreviewList/AttachmentPreviewList-theme.scss +11 -0
- package/dist/scss/v2/Dialog/Dialog-layout.scss +1 -2
- package/dist/scss/v2/Form/Form-layout.scss +40 -0
- package/dist/scss/v2/Form/Form-theme.scss +62 -0
- package/dist/scss/v2/Location/Location-layout.scss +52 -0
- package/dist/scss/v2/Location/Location-theme.scss +32 -0
- package/dist/scss/v2/MessageInput/MessageInput-theme.scss +7 -0
- package/dist/scss/v2/Modal/Modal-layout.scss +2 -0
- package/dist/scss/v2/Poll/Poll-layout.scss +0 -35
- package/dist/scss/v2/Poll/Poll-theme.scss +0 -28
- package/dist/scss/v2/_icons.scss +1 -0
- package/dist/scss/v2/index.layout.scss +1 -0
- package/dist/scss/v2/index.scss +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { LiveLocationManager } from 'stream-chat';
|
|
2
|
+
import { useEffect, useMemo } from 'react';
|
|
3
|
+
const isMobile = () => /Mobi/i.test(navigator.userAgent);
|
|
4
|
+
/**
|
|
5
|
+
* Checks whether the current browser is Safari.
|
|
6
|
+
*/
|
|
7
|
+
export const isSafari = () => {
|
|
8
|
+
if (typeof navigator === 'undefined')
|
|
9
|
+
return false;
|
|
10
|
+
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent || '');
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Checks whether the current browser is Firefox.
|
|
14
|
+
*/
|
|
15
|
+
export const isFirefox = () => {
|
|
16
|
+
if (typeof navigator === 'undefined')
|
|
17
|
+
return false;
|
|
18
|
+
return navigator.userAgent?.includes('Firefox');
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Checks whether the current browser is Google Chrome.
|
|
22
|
+
*/
|
|
23
|
+
export const isChrome = () => {
|
|
24
|
+
if (typeof navigator === 'undefined')
|
|
25
|
+
return false;
|
|
26
|
+
return navigator.userAgent?.includes('Chrome');
|
|
27
|
+
};
|
|
28
|
+
const browser = () => {
|
|
29
|
+
if (isChrome())
|
|
30
|
+
return 'chrome';
|
|
31
|
+
if (isFirefox())
|
|
32
|
+
return 'firefox';
|
|
33
|
+
if (isSafari())
|
|
34
|
+
return 'safari';
|
|
35
|
+
return 'other';
|
|
36
|
+
};
|
|
37
|
+
export const useLiveLocationSharingManager = ({ client, getDeviceId, watchLocation, }) => {
|
|
38
|
+
const manager = useMemo(() => {
|
|
39
|
+
if (!client)
|
|
40
|
+
return null;
|
|
41
|
+
return new LiveLocationManager({
|
|
42
|
+
client,
|
|
43
|
+
getDeviceId: getDeviceId ??
|
|
44
|
+
(() => `web-${isMobile() ? 'mobile' : 'desktop'}-${browser()}-${client.userID}`),
|
|
45
|
+
watchLocation,
|
|
46
|
+
});
|
|
47
|
+
}, [client, getDeviceId, watchLocation]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!manager)
|
|
50
|
+
return;
|
|
51
|
+
manager.init();
|
|
52
|
+
return () => {
|
|
53
|
+
manager.unregisterSubscriptions();
|
|
54
|
+
};
|
|
55
|
+
}, [manager]);
|
|
56
|
+
return manager;
|
|
57
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ShareLocationDialog';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ShareLocationDialog';
|
|
@@ -39,6 +39,11 @@ const MessageSimpleWithContext = (props) => {
|
|
|
39
39
|
const hasAttachment = messageHasAttachments(message);
|
|
40
40
|
const hasReactions = messageHasReactions(message);
|
|
41
41
|
const isAIGenerated = useMemo(() => isMessageAIGenerated?.(message), [isMessageAIGenerated, message]);
|
|
42
|
+
const finalAttachments = useMemo(() => !message.shared_location && !message.attachments
|
|
43
|
+
? []
|
|
44
|
+
: !message.shared_location
|
|
45
|
+
? message.attachments
|
|
46
|
+
: [message.shared_location, ...(message.attachments ?? [])], [message]);
|
|
42
47
|
if (isDateSeparatorMessage(message)) {
|
|
43
48
|
return null;
|
|
44
49
|
}
|
|
@@ -92,7 +97,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
92
97
|
React.createElement("div", { className: 'str-chat__message-reactions-host' }, hasReactions && React.createElement(ReactionsList, { reverse: true })),
|
|
93
98
|
React.createElement("div", { className: 'str-chat__message-bubble' },
|
|
94
99
|
poll && React.createElement(Poll, { poll: poll }),
|
|
95
|
-
|
|
100
|
+
finalAttachments?.length && !message.quoted_message ? (React.createElement(Attachment, { actionHandler: handleAction, attachments: finalAttachments })) : null,
|
|
96
101
|
isAIGenerated ? (React.createElement(StreamedMessageText, { message: message, renderText: renderText })) : (React.createElement(MessageText, { message: message, renderText: renderText })),
|
|
97
102
|
React.createElement(MessageErrorIcon, null))),
|
|
98
103
|
showReplyCountButton && (React.createElement(MessageRepliesCountButton, { onClick: handleOpenThread, reply_count: message.reply_count })),
|
|
@@ -2,7 +2,10 @@ export * from './FixedHeightMessage';
|
|
|
2
2
|
export * from './hooks';
|
|
3
3
|
export * from './icons';
|
|
4
4
|
export * from './Message';
|
|
5
|
+
export * from './MessageBlocked';
|
|
5
6
|
export * from './MessageDeleted';
|
|
7
|
+
export * from './MessageEditedTimestamp';
|
|
8
|
+
export * from './MessageIsThreadReplyInChannelButtonIndicator';
|
|
6
9
|
export * from './MessageOptions';
|
|
7
10
|
export * from './MessageRepliesCountButton';
|
|
8
11
|
export * from './MessageSimple';
|
|
@@ -12,7 +15,7 @@ export * from './MessageTimestamp';
|
|
|
12
15
|
export * from './QuotedMessage';
|
|
13
16
|
export * from './ReminderNotification';
|
|
14
17
|
export * from './renderText';
|
|
18
|
+
export * from './StreamedMessageText';
|
|
15
19
|
export * from './types';
|
|
16
20
|
export * from './utils';
|
|
17
|
-
export * from './StreamedMessageText';
|
|
18
21
|
export type { TimestampProps } from './Timestamp';
|
|
@@ -2,7 +2,10 @@ export * from './FixedHeightMessage';
|
|
|
2
2
|
export * from './hooks';
|
|
3
3
|
export * from './icons';
|
|
4
4
|
export * from './Message';
|
|
5
|
+
export * from './MessageBlocked';
|
|
5
6
|
export * from './MessageDeleted';
|
|
7
|
+
export * from './MessageEditedTimestamp';
|
|
8
|
+
export * from './MessageIsThreadReplyInChannelButtonIndicator';
|
|
6
9
|
export * from './MessageOptions';
|
|
7
10
|
export * from './MessageRepliesCountButton';
|
|
8
11
|
export * from './MessageSimple';
|
|
@@ -12,6 +15,6 @@ export * from './MessageTimestamp';
|
|
|
12
15
|
export * from './QuotedMessage';
|
|
13
16
|
export * from './ReminderNotification';
|
|
14
17
|
export * from './renderText';
|
|
18
|
+
export * from './StreamedMessageText';
|
|
15
19
|
export * from './types';
|
|
16
20
|
export * from './utils';
|
|
17
|
-
export * from './StreamedMessageText';
|
|
@@ -4,12 +4,14 @@ import type { UnsupportedAttachmentPreviewProps } from './UnsupportedAttachmentP
|
|
|
4
4
|
import type { VoiceRecordingPreviewProps } from './VoiceRecordingPreview';
|
|
5
5
|
import type { FileAttachmentPreviewProps } from './FileAttachmentPreview';
|
|
6
6
|
import type { ImageAttachmentPreviewProps } from './ImageAttachmentPreview';
|
|
7
|
+
import { type GeolocationPreviewProps } from './GeolocationPreview';
|
|
7
8
|
export type AttachmentPreviewListProps = {
|
|
8
9
|
AudioAttachmentPreview?: ComponentType<FileAttachmentPreviewProps>;
|
|
9
10
|
FileAttachmentPreview?: ComponentType<FileAttachmentPreviewProps>;
|
|
11
|
+
GeolocationPreview?: ComponentType<GeolocationPreviewProps>;
|
|
10
12
|
ImageAttachmentPreview?: ComponentType<ImageAttachmentPreviewProps>;
|
|
11
13
|
UnsupportedAttachmentPreview?: ComponentType<UnsupportedAttachmentPreviewProps>;
|
|
12
14
|
VideoAttachmentPreview?: ComponentType<FileAttachmentPreviewProps>;
|
|
13
15
|
VoiceRecordingPreview?: ComponentType<VoiceRecordingPreviewProps>;
|
|
14
16
|
};
|
|
15
|
-
export declare const AttachmentPreviewList: ({ AudioAttachmentPreview, FileAttachmentPreview, ImageAttachmentPreview, UnsupportedAttachmentPreview, VideoAttachmentPreview, VoiceRecordingPreview, }: AttachmentPreviewListProps) => React.JSX.Element | null;
|
|
17
|
+
export declare const AttachmentPreviewList: ({ AudioAttachmentPreview, FileAttachmentPreview, GeolocationPreview, ImageAttachmentPreview, UnsupportedAttachmentPreview, VideoAttachmentPreview, VoiceRecordingPreview, }: AttachmentPreviewListProps) => React.JSX.Element | null;
|
|
@@ -4,34 +4,43 @@ import { UnsupportedAttachmentPreview as DefaultUnknownAttachmentPreview } from
|
|
|
4
4
|
import { VoiceRecordingPreview as DefaultVoiceRecordingPreview } from './VoiceRecordingPreview';
|
|
5
5
|
import { FileAttachmentPreview as DefaultFilePreview } from './FileAttachmentPreview';
|
|
6
6
|
import { ImageAttachmentPreview as DefaultImagePreview } from './ImageAttachmentPreview';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import { useAttachmentsForPreview, useMessageComposer } from '../hooks';
|
|
8
|
+
import { GeolocationPreview as DefaultGeolocationPreview, } from './GeolocationPreview';
|
|
9
|
+
export const AttachmentPreviewList = ({ AudioAttachmentPreview = DefaultFilePreview, FileAttachmentPreview = DefaultFilePreview, GeolocationPreview = DefaultGeolocationPreview, ImageAttachmentPreview = DefaultImagePreview, UnsupportedAttachmentPreview = DefaultUnknownAttachmentPreview, VideoAttachmentPreview = DefaultFilePreview, VoiceRecordingPreview = DefaultVoiceRecordingPreview, }) => {
|
|
9
10
|
const messageComposer = useMessageComposer();
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
// todo: we could also allow to attach poll to a message composition
|
|
12
|
+
const { attachments, location } = useAttachmentsForPreview();
|
|
13
|
+
if (!attachments.length && !location)
|
|
12
14
|
return null;
|
|
13
15
|
return (React.createElement("div", { className: 'str-chat__attachment-preview-list' },
|
|
14
|
-
React.createElement("div", { className: 'str-chat__attachment-list-scroll-container', "data-testid": 'attachment-list-scroll-container' },
|
|
15
|
-
|
|
16
|
+
React.createElement("div", { className: 'str-chat__attachment-list-scroll-container', "data-testid": 'attachment-list-scroll-container' },
|
|
17
|
+
location && (React.createElement(GeolocationPreview, { location: location,
|
|
18
|
+
// It is not possible to nullify shared_location field so we do not show a preview when editing
|
|
19
|
+
// to prevent a user from wanting to remove the location
|
|
20
|
+
remove: messageComposer.editedMessage
|
|
21
|
+
? undefined
|
|
22
|
+
: messageComposer.locationComposer.initState })),
|
|
23
|
+
attachments.map((attachment) => {
|
|
24
|
+
if (isScrapedContent(attachment))
|
|
25
|
+
return null;
|
|
26
|
+
if (isLocalVoiceRecordingAttachment(attachment)) {
|
|
27
|
+
return (React.createElement(VoiceRecordingPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
28
|
+
}
|
|
29
|
+
else if (isLocalAudioAttachment(attachment)) {
|
|
30
|
+
return (React.createElement(AudioAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
31
|
+
}
|
|
32
|
+
else if (isLocalVideoAttachment(attachment)) {
|
|
33
|
+
return (React.createElement(VideoAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
34
|
+
}
|
|
35
|
+
else if (isLocalImageAttachment(attachment)) {
|
|
36
|
+
return (React.createElement(ImageAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.image_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
37
|
+
}
|
|
38
|
+
else if (isLocalFileAttachment(attachment)) {
|
|
39
|
+
return (React.createElement(FileAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
40
|
+
}
|
|
41
|
+
else if (isLocalAttachment(attachment)) {
|
|
42
|
+
return (React.createElement(UnsupportedAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
43
|
+
}
|
|
16
44
|
return null;
|
|
17
|
-
|
|
18
|
-
return (React.createElement(VoiceRecordingPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
19
|
-
}
|
|
20
|
-
else if (isLocalAudioAttachment(attachment)) {
|
|
21
|
-
return (React.createElement(AudioAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
22
|
-
}
|
|
23
|
-
else if (isLocalVideoAttachment(attachment)) {
|
|
24
|
-
return (React.createElement(VideoAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
25
|
-
}
|
|
26
|
-
else if (isLocalImageAttachment(attachment)) {
|
|
27
|
-
return (React.createElement(ImageAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.image_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
28
|
-
}
|
|
29
|
-
else if (isLocalFileAttachment(attachment)) {
|
|
30
|
-
return (React.createElement(FileAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id || attachment.asset_url, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
31
|
-
}
|
|
32
|
-
else if (isLocalAttachment(attachment)) {
|
|
33
|
-
return (React.createElement(UnsupportedAttachmentPreview, { attachment: attachment, handleRetry: messageComposer.attachmentManager.uploadAttachment, key: attachment.localMetadata.id, removeAttachments: messageComposer.attachmentManager.removeAttachments }));
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
}))));
|
|
45
|
+
}))));
|
|
37
46
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LiveLocationPreview, StaticLocationPreview } from 'stream-chat';
|
|
2
|
+
import type { ComponentType } from 'react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
type GeolocationPreviewImageProps = {
|
|
5
|
+
location: StaticLocationPreview | LiveLocationPreview;
|
|
6
|
+
};
|
|
7
|
+
export type GeolocationPreviewProps = {
|
|
8
|
+
location: StaticLocationPreview | LiveLocationPreview;
|
|
9
|
+
PreviewImage?: ComponentType<GeolocationPreviewImageProps>;
|
|
10
|
+
remove?: () => void;
|
|
11
|
+
};
|
|
12
|
+
export declare const GeolocationPreview: ({ location, PreviewImage, remove, }: GeolocationPreviewProps) => React.JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CloseIcon } from '../icons';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTranslationContext } from '../../../context';
|
|
4
|
+
import { GeolocationIcon } from '../../Attachment/icons';
|
|
5
|
+
const GeolocationPreviewImage = () => (React.createElement("div", { className: 'str-chat__location-preview-image' },
|
|
6
|
+
React.createElement(GeolocationIcon, null)));
|
|
7
|
+
export const GeolocationPreview = ({ location, PreviewImage = GeolocationPreviewImage, remove, }) => {
|
|
8
|
+
const { t } = useTranslationContext();
|
|
9
|
+
return (React.createElement("div", { className: 'str-chat__location-preview', "data-testid": 'location-preview' },
|
|
10
|
+
React.createElement(PreviewImage, { location: location }),
|
|
11
|
+
remove && (React.createElement("button", { "aria-label": t('aria/Remove location attachment'), className: 'str-chat__attachment-preview-delete', "data-testid": 'location-preview-item-delete-button', onClick: remove },
|
|
12
|
+
React.createElement(CloseIcon, null))),
|
|
13
|
+
React.createElement("div", { className: 'str-chat__attachment-preview-metadata' }, location.durationMs ? (React.createElement(React.Fragment, null,
|
|
14
|
+
React.createElement("div", { className: 'str-chat__attachment-preview-title', title: t('Shared live location') }, t('Live location')),
|
|
15
|
+
React.createElement("div", { className: 'str-chat__attachment-preview-subtitle' }, t('Live for {{duration}}', {
|
|
16
|
+
duration: t('duration/Share Location', {
|
|
17
|
+
milliseconds: location.durationMs,
|
|
18
|
+
}),
|
|
19
|
+
})))) : (React.createElement(React.Fragment, null,
|
|
20
|
+
React.createElement("div", { className: 'str-chat__attachment-preview-title', title: t('Current location') }, t('Current location')),
|
|
21
|
+
React.createElement("div", { className: 'str-chat__attachment-preview-subtitle' },
|
|
22
|
+
location.latitude,
|
|
23
|
+
", ",
|
|
24
|
+
location.longitude))))));
|
|
25
|
+
};
|
|
@@ -9,11 +9,12 @@ export type AttachmentSelectorActionProps = {
|
|
|
9
9
|
};
|
|
10
10
|
export type AttachmentSelectorAction = {
|
|
11
11
|
ActionButton: React.ComponentType<AttachmentSelectorActionProps>;
|
|
12
|
-
type: 'uploadFile' | 'createPoll' | (string & {});
|
|
12
|
+
type: 'uploadFile' | 'createPoll' | 'addLocation' | (string & {});
|
|
13
13
|
ModalContent?: React.ComponentType<AttachmentSelectorModalContentProps>;
|
|
14
14
|
};
|
|
15
15
|
export declare const DefaultAttachmentSelectorComponents: {
|
|
16
16
|
File({ closeMenu }: AttachmentSelectorActionProps): React.JSX.Element;
|
|
17
|
+
Location({ closeMenu, openModalForAction }: AttachmentSelectorActionProps): React.JSX.Element;
|
|
17
18
|
Poll({ closeMenu, openModalForAction }: AttachmentSelectorActionProps): React.JSX.Element;
|
|
18
19
|
};
|
|
19
20
|
export declare const defaultAttachmentSelectorActionSet: AttachmentSelectorAction[];
|
|
@@ -5,12 +5,15 @@ import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
|
|
|
5
5
|
import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
|
|
6
6
|
import { DialogMenuButton } from '../Dialog/DialogMenu';
|
|
7
7
|
import { Modal } from '../Modal';
|
|
8
|
+
import { ShareLocationDialog as DefaultLocationDialog } from '../Location';
|
|
8
9
|
import { PollCreationDialog as DefaultPollCreationDialog } from '../Poll';
|
|
9
10
|
import { Portal } from '../Portal/Portal';
|
|
10
11
|
import { UploadFileInput } from '../ReactFileUtilities';
|
|
11
|
-
import { useChannelStateContext, useComponentContext,
|
|
12
|
+
import { useChannelStateContext, useComponentContext, useTranslationContext, } from '../../context';
|
|
12
13
|
import { AttachmentSelectorContextProvider, useAttachmentSelectorContext, } from '../../context/AttachmentSelectorContext';
|
|
13
14
|
import { useStableId } from '../UtilityComponents/useStableId';
|
|
15
|
+
import clsx from 'clsx';
|
|
16
|
+
import { useMessageComposer } from './hooks';
|
|
14
17
|
export const SimpleAttachmentSelector = () => {
|
|
15
18
|
const { AttachmentSelectorInitiationButtonContents, FileUploadIcon = DefaultUploadIcon, } = useComponentContext();
|
|
16
19
|
const inputRef = useRef(null);
|
|
@@ -55,6 +58,13 @@ export const DefaultAttachmentSelectorComponents = {
|
|
|
55
58
|
closeMenu();
|
|
56
59
|
} }, t('File')));
|
|
57
60
|
},
|
|
61
|
+
Location({ closeMenu, openModalForAction }) {
|
|
62
|
+
const { t } = useTranslationContext();
|
|
63
|
+
return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__add-location-button', onClick: () => {
|
|
64
|
+
openModalForAction('addLocation');
|
|
65
|
+
closeMenu();
|
|
66
|
+
} }, t('Location')));
|
|
67
|
+
},
|
|
58
68
|
Poll({ closeMenu, openModalForAction }) {
|
|
59
69
|
const { t } = useTranslationContext();
|
|
60
70
|
return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__create-poll-button', onClick: () => {
|
|
@@ -69,33 +79,42 @@ export const defaultAttachmentSelectorActionSet = [
|
|
|
69
79
|
ActionButton: DefaultAttachmentSelectorComponents.Poll,
|
|
70
80
|
type: 'createPoll',
|
|
71
81
|
},
|
|
82
|
+
{
|
|
83
|
+
ActionButton: DefaultAttachmentSelectorComponents.Location,
|
|
84
|
+
type: 'addLocation',
|
|
85
|
+
},
|
|
72
86
|
];
|
|
73
87
|
const useAttachmentSelectorActionsFiltered = (original) => {
|
|
74
|
-
const { PollCreationDialog = DefaultPollCreationDialog } = useComponentContext();
|
|
75
|
-
const { channelCapabilities
|
|
76
|
-
const
|
|
88
|
+
const { PollCreationDialog = DefaultPollCreationDialog, ShareLocationDialog = DefaultLocationDialog, } = useComponentContext();
|
|
89
|
+
const { channelCapabilities } = useChannelStateContext();
|
|
90
|
+
const messageComposer = useMessageComposer();
|
|
77
91
|
return original
|
|
78
92
|
.filter((action) => {
|
|
79
|
-
if (action.type === 'uploadFile'
|
|
80
|
-
return
|
|
81
|
-
if (action.type === 'createPoll'
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
if (action.type === 'uploadFile')
|
|
94
|
+
return channelCapabilities['upload-file'];
|
|
95
|
+
if (action.type === 'createPoll')
|
|
96
|
+
return channelCapabilities['send-poll'] && !messageComposer.threadId;
|
|
97
|
+
if (action.type === 'addLocation') {
|
|
98
|
+
return messageComposer.config.location.enabled && !messageComposer.threadId;
|
|
99
|
+
}
|
|
84
100
|
return true;
|
|
85
101
|
})
|
|
86
102
|
.map((action) => {
|
|
87
103
|
if (action.type === 'createPoll' && !action.ModalContent) {
|
|
88
104
|
return { ...action, ModalContent: PollCreationDialog };
|
|
89
105
|
}
|
|
106
|
+
if (action.type === 'addLocation' && !action.ModalContent) {
|
|
107
|
+
return { ...action, ModalContent: ShareLocationDialog };
|
|
108
|
+
}
|
|
90
109
|
return action;
|
|
91
110
|
});
|
|
92
111
|
};
|
|
93
112
|
export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttachmentSelectorActionSet, getModalPortalDestination, }) => {
|
|
94
113
|
const { t } = useTranslationContext();
|
|
95
114
|
const { channelCapabilities } = useChannelStateContext();
|
|
96
|
-
const
|
|
115
|
+
const messageComposer = useMessageComposer();
|
|
97
116
|
const actions = useAttachmentSelectorActionsFiltered(attachmentSelectorActionSet);
|
|
98
|
-
const menuDialogId = `attachment-actions-menu${
|
|
117
|
+
const menuDialogId = `attachment-actions-menu${messageComposer.threadId ? '-thread' : ''}`;
|
|
99
118
|
const menuDialog = useDialog({ id: menuDialogId });
|
|
100
119
|
const menuDialogIsOpen = useDialogIsOpen(menuDialogId);
|
|
101
120
|
const [modalContentAction, setModalContentActionAction] = useState();
|
|
@@ -123,5 +142,8 @@ export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttach
|
|
|
123
142
|
React.createElement(DialogAnchor, { id: menuDialogId, placement: 'top-start', referenceElement: menuButtonRef.current, tabIndex: -1, trapFocus: true },
|
|
124
143
|
React.createElement("div", { className: 'str-chat__attachment-selector-actions-menu str-chat__dialog-menu', "data-testid": 'attachment-selector-actions-menu' }, actions.map(({ ActionButton, type }) => (React.createElement(ActionButton, { closeMenu: menuDialog.close, key: `attachment-selector-item-${type}`, openModalForAction: openModal }))))),
|
|
125
144
|
React.createElement(Portal, { getPortalDestination: getModalPortalDestination ?? getDefaultPortalDestination, isOpen: modalIsOpen },
|
|
126
|
-
React.createElement(Modal, { className:
|
|
145
|
+
React.createElement(Modal, { className: clsx({
|
|
146
|
+
'str-chat__create-poll-modal': modalContentAction?.type === 'createPoll',
|
|
147
|
+
'str-chat__share-location-modal': modalContentAction?.type === 'addLocation',
|
|
148
|
+
}), onClose: closeModal, open: modalIsOpen }, ModalContent && React.createElement(ModalContent, { close: closeModal }))))));
|
|
127
149
|
};
|
|
@@ -40,7 +40,9 @@ export type MessageInputProps = {
|
|
|
40
40
|
hideSendButton?: boolean;
|
|
41
41
|
/** Custom UI component handling how the message input is rendered, defaults to and accepts the same props as [MessageInputFlat](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputFlat.tsx) */
|
|
42
42
|
Input?: React.ComponentType<MessageInputProps>;
|
|
43
|
-
/**
|
|
43
|
+
/** @deprecated use messageComposer.threadId to indicate, whether the message is composed within a thread context
|
|
44
|
+
* Signals that the MessageInput is rendered in a message thread (Thread component)
|
|
45
|
+
*/
|
|
44
46
|
isThreadInput?: boolean;
|
|
45
47
|
/** Max number of rows the underlying `textarea` component is allowed to grow */
|
|
46
48
|
maxRows?: number;
|
|
@@ -31,11 +31,14 @@ const MessageInputProvider = (props) => {
|
|
|
31
31
|
!messageComposer.config.drafts.enabled)
|
|
32
32
|
return;
|
|
33
33
|
// get draft data for legacy thead composer
|
|
34
|
-
messageComposer.channel
|
|
34
|
+
messageComposer.channel
|
|
35
|
+
.getDraft({ parent_id: threadId })
|
|
36
|
+
.then(({ draft }) => {
|
|
35
37
|
if (draft) {
|
|
36
38
|
messageComposer.initState({ composition: draft });
|
|
37
39
|
}
|
|
38
|
-
})
|
|
40
|
+
})
|
|
41
|
+
.catch(console.error);
|
|
39
42
|
}, [messageComposer]);
|
|
40
43
|
useRegisterDropHandlers();
|
|
41
44
|
return (React.createElement(MessageInputContextProvider, { value: messageInputContextValue }, props.children));
|
|
@@ -43,9 +46,10 @@ const MessageInputProvider = (props) => {
|
|
|
43
46
|
const UnMemoizedMessageInput = (props) => {
|
|
44
47
|
const { Input: PropInput } = props;
|
|
45
48
|
const { Input: ContextInput } = useComponentContext('MessageInput');
|
|
49
|
+
const messageComposer = useMessageComposer();
|
|
46
50
|
const id = useStableId();
|
|
47
51
|
const Input = PropInput || ContextInput || MessageInputFlat;
|
|
48
|
-
const dialogManagerId =
|
|
52
|
+
const dialogManagerId = messageComposer.threadId
|
|
49
53
|
? `message-input-dialog-manager-thread-${id}`
|
|
50
54
|
: `message-input-dialog-manager-${id}`;
|
|
51
55
|
return (React.createElement(DialogManagerProvider, { id: dialogManagerId },
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const useAttachmentsForPreview: () => {
|
|
2
|
+
attachments: import("stream-chat").LocalAttachment[];
|
|
3
|
+
location: import("stream-chat").StaticLocationPayload | import("stream-chat").LiveLocationPreview | null;
|
|
4
|
+
poll: {
|
|
5
|
+
id: string;
|
|
6
|
+
max_votes_allowed: string;
|
|
7
|
+
name: string;
|
|
8
|
+
options: import("stream-chat").PollComposerOption[];
|
|
9
|
+
allow_answers?: boolean | undefined;
|
|
10
|
+
allow_user_suggested_options?: boolean | undefined;
|
|
11
|
+
description?: string | undefined;
|
|
12
|
+
enforce_unique_vote?: boolean | undefined;
|
|
13
|
+
is_closed?: boolean | undefined;
|
|
14
|
+
user_id?: string | undefined;
|
|
15
|
+
voting_visibility?: import("stream-chat").VotingVisibility | undefined;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useMessageComposer } from './useMessageComposer';
|
|
2
|
+
import { useStateStore } from '../../../store';
|
|
3
|
+
const attachmentManagerStateSelector = (state) => ({
|
|
4
|
+
attachments: state.attachments,
|
|
5
|
+
});
|
|
6
|
+
const pollComposerStateSelector = (state) => ({
|
|
7
|
+
poll: state.data,
|
|
8
|
+
});
|
|
9
|
+
const locationComposerStateSelector = (state) => ({
|
|
10
|
+
location: state.location,
|
|
11
|
+
});
|
|
12
|
+
export const useAttachmentsForPreview = () => {
|
|
13
|
+
const { attachmentManager, locationComposer, pollComposer } = useMessageComposer();
|
|
14
|
+
const { attachments } = useStateStore(attachmentManager.state, attachmentManagerStateSelector);
|
|
15
|
+
const { poll } = useStateStore(pollComposer.state, pollComposerStateSelector);
|
|
16
|
+
const { location } = useStateStore(locationComposer.state, locationComposerStateSelector);
|
|
17
|
+
return {
|
|
18
|
+
attachments,
|
|
19
|
+
location,
|
|
20
|
+
poll,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -19,6 +19,14 @@ export const AddCommentForm = ({ close, messageId }) => {
|
|
|
19
19
|
type: 'text',
|
|
20
20
|
value: ownAnswer?.answer_text ?? '',
|
|
21
21
|
},
|
|
22
|
+
validator: (value) => {
|
|
23
|
+
const valueString = typeof value !== 'undefined' ? value.toString() : value;
|
|
24
|
+
const trimmedValue = valueString?.trim();
|
|
25
|
+
if (!trimmedValue) {
|
|
26
|
+
return new Error(t('This field cannot be empty or contain only spaces'));
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
},
|
|
22
30
|
},
|
|
23
31
|
}, onSubmit: async (value) => {
|
|
24
32
|
await poll.addAnswer(value.comment, messageId);
|
|
@@ -21,9 +21,12 @@ export const SuggestPollOptionForm = ({ close, messageId, }) => {
|
|
|
21
21
|
value: '',
|
|
22
22
|
},
|
|
23
23
|
validator: (value) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const valueString = typeof value !== 'undefined' ? value.toString() : value;
|
|
25
|
+
const trimmedValue = valueString?.trim();
|
|
26
|
+
if (!trimmedValue) {
|
|
27
|
+
return new Error(t('This field cannot be empty or contain only spaces'));
|
|
28
|
+
}
|
|
29
|
+
const existingOption = options.find((option) => option.text === trimmedValue);
|
|
27
30
|
if (existingOption) {
|
|
28
31
|
return new Error(t('Option already exists'));
|
|
29
32
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import React, { useCallback, useLayoutEffect, useRef } from 'react';
|
|
3
3
|
import { useMessageComposer } from '../../MessageInput';
|
|
4
|
+
import { useMessageInputContext } from '../../../context';
|
|
4
5
|
export const SuggestionListItem = React.forwardRef(function SuggestionListItem({ className, component: Component, focused, item, onMouseEnter }, innerRef) {
|
|
5
6
|
const { textComposer } = useMessageComposer();
|
|
7
|
+
const { textareaRef } = useMessageInputContext();
|
|
6
8
|
const containerRef = useRef(null);
|
|
7
9
|
const handleSelect = useCallback(() => {
|
|
8
10
|
textComposer.handleSelect(item);
|
|
9
|
-
|
|
11
|
+
textareaRef.current?.focus();
|
|
12
|
+
}, [item, textareaRef, textComposer]);
|
|
10
13
|
useLayoutEffect(() => {
|
|
11
14
|
if (!focused)
|
|
12
15
|
return;
|
|
@@ -14,10 +14,11 @@ export * from './Gallery';
|
|
|
14
14
|
export * from './InfiniteScrollPaginator';
|
|
15
15
|
export * from './Loading';
|
|
16
16
|
export * from './LoadMore';
|
|
17
|
+
export * from './Location';
|
|
17
18
|
export * from './MediaRecorder';
|
|
18
19
|
export * from './Message';
|
|
19
20
|
export * from './MessageActions';
|
|
20
|
-
export
|
|
21
|
+
export * from './MessageBounce';
|
|
21
22
|
export * from './MessageInput';
|
|
22
23
|
export * from './MessageList';
|
|
23
24
|
export * from './Modal';
|
package/dist/components/index.js
CHANGED
|
@@ -14,9 +14,11 @@ export * from './Gallery';
|
|
|
14
14
|
export * from './InfiniteScrollPaginator';
|
|
15
15
|
export * from './Loading';
|
|
16
16
|
export * from './LoadMore';
|
|
17
|
+
export * from './Location';
|
|
17
18
|
export * from './MediaRecorder';
|
|
18
19
|
export * from './Message';
|
|
19
20
|
export * from './MessageActions';
|
|
21
|
+
export * from './MessageBounce';
|
|
20
22
|
export * from './MessageInput';
|
|
21
23
|
export * from './MessageList';
|
|
22
24
|
export * from './Modal';
|
|
@@ -5,6 +5,7 @@ import type { SuggestionItemProps, SuggestionListProps } from '../components/Tex
|
|
|
5
5
|
import type { SearchProps, SearchResultsPresearchProps, SearchSourceResultListProps } from '../experimental';
|
|
6
6
|
import type { PropsWithChildrenOnly, UnknownType } from '../types/types';
|
|
7
7
|
import type { StopAIGenerationButtonProps } from '../components/MessageInput/StopAIGenerationButton';
|
|
8
|
+
import type { ShareLocationDialogProps } from '../components/Location';
|
|
8
9
|
export type ComponentContextValue = {
|
|
9
10
|
/** Custom UI component to display a message attachment, defaults to and accepts same props as: [Attachment](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/Attachment.tsx) */
|
|
10
11
|
Attachment?: React.ComponentType<AttachmentProps>;
|
|
@@ -143,6 +144,8 @@ export type ComponentContextValue = {
|
|
|
143
144
|
SendButton?: React.ComponentType<SendButtonProps>;
|
|
144
145
|
/** Custom UI component checkbox that indicates message to be sent to main channel, defaults to and accepts same props as: [SendToChannelCheckbox](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/SendToChannelCheckbox.tsx) */
|
|
145
146
|
SendToChannelCheckbox?: React.ComponentType;
|
|
147
|
+
/** Custom UI component to render the location sharing dialog, defaults to and accepts same props as: [ShareLocationDialog](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Location/ShareLocationDialog.tsx) */
|
|
148
|
+
ShareLocationDialog?: React.ComponentType<ShareLocationDialogProps>;
|
|
146
149
|
/** Custom UI component button for initiating audio recording, defaults to and accepts same props as: [StartRecordingAudioButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MediaRecorder/AudioRecorder/AudioRecordingButtons.tsx) */
|
|
147
150
|
StartRecordingAudioButton?: React.ComponentType<StartRecordingAudioButtonProps>;
|
|
148
151
|
StopAIGenerationButton?: React.ComponentType<StopAIGenerationButtonProps> | null;
|