stream-chat-react 13.2.3 → 13.4.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 +5 -1
- 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 +3 -1
- package/dist/components/Message/index.js +3 -1
- package/dist/components/Message/renderText/renderText.d.ts +1 -1
- package/dist/components/Message/renderText/renderText.js +1 -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/AttachmentPreviewList/index.d.ts +1 -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/MessageInput/index.d.ts +1 -1
- package/dist/components/Modal/Modal.d.ts +8 -3
- package/dist/components/Modal/Modal.js +19 -8
- package/dist/components/Poll/PollActions/AddCommentForm.js +8 -0
- package/dist/components/Poll/PollActions/SuggestPollOptionForm.js +6 -3
- package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.js +4 -1
- 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 +21 -0
- package/dist/i18n/TranslationBuilder/notifications/NotificationTranslationTopic.js +2 -0
- package/dist/i18n/TranslationBuilder/notifications/pollVoteCountTrespass.d.ts +3 -0
- package/dist/i18n/TranslationBuilder/notifications/pollVoteCountTrespass.js +1 -0
- package/dist/i18n/de.json +22 -1
- package/dist/i18n/en.json +22 -1
- package/dist/i18n/es.json +22 -1
- package/dist/i18n/fr.json +22 -1
- package/dist/i18n/hi.json +22 -1
- package/dist/i18n/it.json +22 -1
- package/dist/i18n/ja.json +22 -1
- package/dist/i18n/ko.json +22 -1
- package/dist/i18n/nl.json +22 -1
- package/dist/i18n/pt.json +22 -1
- package/dist/i18n/ru.json +22 -1
- package/dist/i18n/tr.json +22 -1
- package/dist/index.browser.cjs +2610 -1727
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +2622 -1727
- 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 +6 -6
|
@@ -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,9 @@ 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';
|
|
6
8
|
export * from './MessageIsThreadReplyInChannelButtonIndicator';
|
|
7
9
|
export * from './MessageOptions';
|
|
8
10
|
export * from './MessageRepliesCountButton';
|
|
@@ -13,7 +15,7 @@ export * from './MessageTimestamp';
|
|
|
13
15
|
export * from './QuotedMessage';
|
|
14
16
|
export * from './ReminderNotification';
|
|
15
17
|
export * from './renderText';
|
|
18
|
+
export * from './StreamedMessageText';
|
|
16
19
|
export * from './types';
|
|
17
20
|
export * from './utils';
|
|
18
|
-
export * from './StreamedMessageText';
|
|
19
21
|
export type { TimestampProps } from './Timestamp';
|
|
@@ -2,7 +2,9 @@ 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';
|
|
6
8
|
export * from './MessageIsThreadReplyInChannelButtonIndicator';
|
|
7
9
|
export * from './MessageOptions';
|
|
8
10
|
export * from './MessageRepliesCountButton';
|
|
@@ -13,6 +15,6 @@ export * from './MessageTimestamp';
|
|
|
13
15
|
export * from './QuotedMessage';
|
|
14
16
|
export * from './ReminderNotification';
|
|
15
17
|
export * from './renderText';
|
|
18
|
+
export * from './StreamedMessageText';
|
|
16
19
|
export * from './types';
|
|
17
20
|
export * from './utils';
|
|
18
|
-
export * from './StreamedMessageText';
|
|
@@ -16,4 +16,4 @@ export type RenderTextOptions = {
|
|
|
16
16
|
getRehypePlugins?: RenderTextPluginConfigurator;
|
|
17
17
|
getRemarkPlugins?: RenderTextPluginConfigurator;
|
|
18
18
|
};
|
|
19
|
-
export declare const renderText: (text?: string, mentionedUsers?: UserResponse[], { allowedTagNames, customMarkDownRenderers, getRehypePlugins, getRemarkPlugins, }?: RenderTextOptions) => React.JSX.Element | null
|
|
19
|
+
export declare const renderText: (text?: string, mentionedUsers?: UserResponse[], { allowedTagNames, customMarkDownRenderers, getRehypePlugins, getRemarkPlugins, }?: RenderTextOptions) => React.JSX.Element | null;
|
|
@@ -83,7 +83,7 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
|
|
|
83
83
|
return strippedHref.includes(strippedText) || strippedText.includes(strippedHref);
|
|
84
84
|
});
|
|
85
85
|
if (noParsingNeeded.length > 0 || linkIsInBlock)
|
|
86
|
-
|
|
86
|
+
continue;
|
|
87
87
|
try {
|
|
88
88
|
// special case for mentions:
|
|
89
89
|
// it could happen that a user's name matches with an e-mail format pattern.
|
|
@@ -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
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './AttachmentPreviewList';
|
|
2
2
|
export type { FileAttachmentPreviewProps } from './FileAttachmentPreview';
|
|
3
|
+
export type { GeolocationPreviewProps } from './GeolocationPreview';
|
|
3
4
|
export type { ImageAttachmentPreviewProps } from './ImageAttachmentPreview';
|
|
4
5
|
export type { UploadAttachmentPreviewProps as AttachmentPreviewProps } from './types';
|
|
5
6
|
export type { UnsupportedAttachmentPreviewProps } from './UnsupportedAttachmentPreview';
|
|
@@ -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
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './AttachmentSelector';
|
|
2
2
|
export { AttachmentPreviewList } from './AttachmentPreviewList';
|
|
3
|
-
export type { AttachmentPreviewListProps, FileAttachmentPreviewProps, ImageAttachmentPreviewProps, AttachmentPreviewProps, UnsupportedAttachmentPreviewProps, VoiceRecordingPreviewProps, } from './AttachmentPreviewList';
|
|
3
|
+
export type { AttachmentPreviewListProps, FileAttachmentPreviewProps, GeolocationPreviewProps, ImageAttachmentPreviewProps, AttachmentPreviewProps, UnsupportedAttachmentPreviewProps, VoiceRecordingPreviewProps, } from './AttachmentPreviewList';
|
|
4
4
|
export * from './CooldownTimer';
|
|
5
5
|
export * from './EditMessageForm';
|
|
6
6
|
export * from './hooks';
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type PropsWithChildren } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
type CloseEvent = KeyboardEvent | React.KeyboardEvent | React.MouseEvent<HTMLButtonElement | HTMLDivElement>;
|
|
4
|
+
export type ModalCloseSource = 'overlay' | 'button' | 'escape';
|
|
3
5
|
export type ModalProps = {
|
|
4
6
|
/** If true, modal is opened or visible. */
|
|
5
7
|
open: boolean;
|
|
6
8
|
/** Custom class to be applied to the modal root div */
|
|
7
9
|
className?: string;
|
|
8
10
|
/** Callback handler for closing of modal. */
|
|
9
|
-
onClose?: (event:
|
|
11
|
+
onClose?: (event: CloseEvent) => void;
|
|
12
|
+
/** Optional handler to intercept closing logic. Return false to prevent onClose. */
|
|
13
|
+
onCloseAttempt?: (source: ModalCloseSource, event: CloseEvent) => boolean;
|
|
10
14
|
};
|
|
11
|
-
export declare const Modal: ({ children, className, onClose, open, }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
|
|
15
|
+
export declare const Modal: ({ children, className, onClose, onCloseAttempt, open, }: PropsWithChildren<ModalProps>) => React.JSX.Element | null;
|
|
16
|
+
export {};
|
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
+
import { useCallback } from 'react';
|
|
2
3
|
import React, { useEffect, useRef } from 'react';
|
|
3
4
|
import { FocusScope } from '@react-aria/focus';
|
|
4
5
|
import { CloseIconRound } from './icons';
|
|
5
6
|
import { useTranslationContext } from '../../context';
|
|
6
|
-
export const Modal = ({ children, className, onClose, open, }) => {
|
|
7
|
+
export const Modal = ({ children, className, onClose, onCloseAttempt, open, }) => {
|
|
7
8
|
const { t } = useTranslationContext('Modal');
|
|
8
9
|
const innerRef = useRef(null);
|
|
9
|
-
const
|
|
10
|
+
const closeButtonRef = useRef(null);
|
|
11
|
+
const maybeClose = useCallback((source, event) => {
|
|
12
|
+
const allow = onCloseAttempt?.(source, event);
|
|
13
|
+
if (allow !== false) {
|
|
14
|
+
onClose?.(event);
|
|
15
|
+
}
|
|
16
|
+
}, [onClose, onCloseAttempt]);
|
|
10
17
|
const handleClick = (event) => {
|
|
11
18
|
const target = event.target;
|
|
12
|
-
if (!innerRef.current || !
|
|
19
|
+
if (!innerRef.current || !closeButtonRef.current)
|
|
13
20
|
return;
|
|
14
|
-
if (
|
|
15
|
-
|
|
21
|
+
if (closeButtonRef.current.contains(target)) {
|
|
22
|
+
maybeClose('button', event);
|
|
23
|
+
}
|
|
24
|
+
else if (!innerRef.current.contains(target)) {
|
|
25
|
+
maybeClose('overlay', event);
|
|
26
|
+
}
|
|
16
27
|
};
|
|
17
28
|
useEffect(() => {
|
|
18
29
|
if (!open)
|
|
19
30
|
return;
|
|
20
31
|
const handleKeyDown = (event) => {
|
|
21
32
|
if (event.key === 'Escape')
|
|
22
|
-
|
|
33
|
+
maybeClose('escape', event);
|
|
23
34
|
};
|
|
24
35
|
document.addEventListener('keydown', handleKeyDown);
|
|
25
36
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
26
|
-
}, [
|
|
37
|
+
}, [maybeClose, open]);
|
|
27
38
|
if (!open)
|
|
28
39
|
return null;
|
|
29
40
|
return (React.createElement("div", { className: clsx('str-chat__modal str-chat__modal--open', className), onClick: handleClick },
|
|
30
41
|
React.createElement(FocusScope, { autoFocus: true, contain: true },
|
|
31
|
-
React.createElement("button", { className: 'str-chat__modal__close-button', ref:
|
|
42
|
+
React.createElement("button", { className: 'str-chat__modal__close-button', ref: closeButtonRef, title: t('Close') },
|
|
32
43
|
React.createElement(CloseIconRound, null)),
|
|
33
44
|
React.createElement("div", { className: 'str-chat__modal__inner str-chat-react__modal__inner', ref: innerRef }, children))));
|
|
34
45
|
};
|
|
@@ -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
|
}
|
|
@@ -15,7 +15,10 @@ export const PollCreationDialogControls = ({ close, }) => {
|
|
|
15
15
|
messageComposer
|
|
16
16
|
.createPoll()
|
|
17
17
|
.then(() => handleSubmitMessage())
|
|
18
|
-
.then(
|
|
18
|
+
.then(() => {
|
|
19
|
+
messageComposer.pollComposer.initState();
|
|
20
|
+
close();
|
|
21
|
+
})
|
|
19
22
|
.catch(console.error);
|
|
20
23
|
}, type: 'submit' }, t('Create'))));
|
|
21
24
|
};
|