stream-chat-react 13.0.4 → 13.1.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/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +7 -0
- package/dist/components/ChannelList/hooks/useChannelListShape.js +3 -3
- package/dist/components/Chat/hooks/useChat.js +7 -3
- package/dist/components/Dialog/ButtonWithSubmenu.d.ts +11 -0
- package/dist/components/Dialog/ButtonWithSubmenu.js +88 -0
- package/dist/components/Dialog/index.d.ts +1 -0
- package/dist/components/Dialog/index.js +1 -0
- package/dist/components/Loading/LoadingErrorIndicator.js +1 -1
- package/dist/components/Message/Message.js +3 -2
- package/dist/components/Message/MessageSimple.js +11 -4
- package/dist/components/Message/MessageThreadReplyInChannelButtonIndicator.d.ts +2 -0
- package/dist/components/Message/MessageThreadReplyInChannelButtonIndicator.js +63 -0
- package/dist/components/Message/ReminderNotification.d.ts +6 -0
- package/dist/components/Message/ReminderNotification.js +30 -0
- package/dist/components/Message/hooks/index.d.ts +1 -0
- package/dist/components/Message/hooks/index.js +1 -0
- package/dist/components/Message/hooks/useMessageReminder.d.ts +1 -0
- package/dist/components/Message/hooks/useMessageReminder.js +11 -0
- package/dist/components/Message/index.d.ts +1 -0
- package/dist/components/Message/index.js +1 -0
- package/dist/components/Message/utils.d.ts +4 -2
- package/dist/components/Message/utils.js +11 -1
- package/dist/components/MessageActions/MessageActionsBox.js +12 -6
- package/dist/components/MessageActions/RemindMeSubmenu.d.ts +6 -0
- package/dist/components/MessageActions/RemindMeSubmenu.js +18 -0
- package/dist/components/MessageInput/MessageInputFlat.js +5 -3
- package/dist/components/MessageInput/SendToChannelCheckbox.d.ts +2 -0
- package/dist/components/MessageInput/SendToChannelCheckbox.js +20 -0
- package/dist/components/MessageList/MessageListNotifications.js +8 -3
- package/dist/components/MessageList/VirtualizedMessageListComponents.d.ts +1 -1
- package/dist/components/MessageList/VirtualizedMessageListComponents.js +4 -0
- package/dist/components/Notifications/hooks/index.d.ts +1 -0
- package/dist/components/Notifications/hooks/index.js +1 -0
- package/dist/components/Notifications/hooks/useNotifications.d.ts +2 -0
- package/dist/components/Notifications/hooks/useNotifications.js +10 -0
- package/dist/components/Notifications/index.d.ts +1 -0
- package/dist/components/Notifications/index.js +1 -0
- package/dist/components/TextareaComposer/TextareaComposer.js +4 -0
- package/dist/components/Thread/LegacyThreadContext.d.ts +8 -0
- package/dist/components/Thread/LegacyThreadContext.js +3 -0
- package/dist/components/Thread/Thread.d.ts +0 -4
- package/dist/components/Thread/Thread.js +2 -3
- package/dist/components/Thread/index.d.ts +1 -0
- package/dist/components/Thread/index.js +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/context/ComponentContext.d.ts +6 -1
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/experimental/MessageActions/defaults.d.ts +1 -1
- package/dist/experimental/MessageActions/defaults.js +27 -4
- package/dist/experimental/index.browser.cjs +382 -169
- package/dist/experimental/index.browser.cjs.map +4 -4
- package/dist/experimental/index.node.cjs +382 -169
- package/dist/experimental/index.node.cjs.map +4 -4
- package/dist/i18n/Streami18n.d.ts +32 -3
- package/dist/i18n/Streami18n.js +34 -5
- package/dist/i18n/TranslationBuilder/TranslationBuilder.d.ts +31 -0
- package/dist/i18n/TranslationBuilder/TranslationBuilder.js +68 -0
- package/dist/i18n/TranslationBuilder/index.d.ts +2 -0
- package/dist/i18n/TranslationBuilder/index.js +2 -0
- package/dist/i18n/TranslationBuilder/notifications/NotificationTranslationTopic.d.ts +11 -0
- package/dist/i18n/TranslationBuilder/notifications/NotificationTranslationTopic.js +27 -0
- package/dist/i18n/TranslationBuilder/notifications/attachmentUpload.d.ts +4 -0
- package/dist/i18n/TranslationBuilder/notifications/attachmentUpload.js +32 -0
- package/dist/i18n/TranslationBuilder/notifications/index.d.ts +1 -0
- package/dist/i18n/TranslationBuilder/notifications/index.js +1 -0
- package/dist/i18n/TranslationBuilder/notifications/pollComposition.d.ts +3 -0
- package/dist/i18n/TranslationBuilder/notifications/pollComposition.js +9 -0
- package/dist/i18n/TranslationBuilder/notifications/types.d.ts +4 -0
- package/dist/i18n/TranslationBuilder/notifications/types.js +1 -0
- package/dist/i18n/de.json +23 -0
- package/dist/i18n/en.json +23 -0
- package/dist/i18n/es.json +23 -0
- package/dist/i18n/fr.json +23 -0
- package/dist/i18n/hi.json +23 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/it.json +23 -0
- package/dist/i18n/ja.json +23 -0
- package/dist/i18n/ko.json +23 -0
- package/dist/i18n/nl.json +23 -0
- package/dist/i18n/pt.json +23 -0
- package/dist/i18n/ru.json +23 -0
- package/dist/i18n/tr.json +23 -0
- package/dist/i18n/types.d.ts +54 -0
- package/dist/i18n/utils.d.ts +1 -1
- package/dist/i18n/utils.js +8 -2
- package/dist/index.browser.cjs +3589 -2162
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +3645 -2156
- package/dist/index.node.cjs.map +4 -4
- package/dist/plugins/Emojis/index.browser.cjs +1 -2
- package/dist/plugins/Emojis/index.browser.cjs.map +3 -3
- package/dist/plugins/Emojis/index.node.cjs +1 -2
- package/dist/plugins/Emojis/index.node.cjs.map +3 -3
- package/dist/scss/v2/Message/Message-layout.scss +13 -2
- package/dist/scss/v2/Message/Message-theme.scss +31 -1
- package/dist/scss/v2/MessageActionsBox/MessageActionsBox-theme.scss +8 -0
- package/dist/scss/v2/MessageInput/MessageInput-layout.scss +19 -0
- package/dist/scss/v2/MessageInput/MessageInput-theme.scss +11 -0
- package/package.json +6 -8
|
@@ -5,7 +5,7 @@ import type { OnMentionAction } from './hooks/useMentionsHandlers';
|
|
|
5
5
|
import type { LoadingErrorIndicatorProps } from '../Loading';
|
|
6
6
|
import type { ComponentContextValue } from '../../context';
|
|
7
7
|
import type { ChannelUnreadUiState, GiphyVersions, ImageAttachmentSizeHandler, VideoAttachmentSizeHandler } from '../../types/types';
|
|
8
|
-
type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'SendButton' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
|
|
8
|
+
type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageIsThreadReplyInChannelButtonIndicator' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'ReminderNotification' | 'SendButton' | 'SendToChannelCheckbox' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
|
|
9
9
|
export type ChannelProps = ChannelPropsForwardedToComponentContext & {
|
|
10
10
|
/** Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab */
|
|
11
11
|
activeUnreadHandler?: (unread: number, documentTitle: string) => void;
|
|
@@ -69,6 +69,7 @@ const ChannelInner = (props) => {
|
|
|
69
69
|
...initialState,
|
|
70
70
|
hasMore: channel.state.messagePagination.hasPrev,
|
|
71
71
|
loading: !channel.initialized,
|
|
72
|
+
messages: channel.state.messages,
|
|
72
73
|
});
|
|
73
74
|
const jumpToMessageFromSearch = useSearchFocusedMessage();
|
|
74
75
|
const isMounted = useIsMounted();
|
|
@@ -731,6 +732,7 @@ const ChannelInner = (props) => {
|
|
|
731
732
|
MessageBlocked: props.MessageBlocked,
|
|
732
733
|
MessageBouncePrompt: props.MessageBouncePrompt,
|
|
733
734
|
MessageDeleted: props.MessageDeleted,
|
|
735
|
+
MessageIsThreadReplyInChannelButtonIndicator: props.MessageIsThreadReplyInChannelButtonIndicator,
|
|
734
736
|
MessageListNotifications: props.MessageListNotifications,
|
|
735
737
|
MessageNotification: props.MessageNotification,
|
|
736
738
|
MessageOptions: props.MessageOptions,
|
|
@@ -752,7 +754,9 @@ const ChannelInner = (props) => {
|
|
|
752
754
|
ReactionSelector: props.ReactionSelector,
|
|
753
755
|
ReactionsList: props.ReactionsList,
|
|
754
756
|
ReactionsListModal: props.ReactionsListModal,
|
|
757
|
+
ReminderNotification: props.ReminderNotification,
|
|
755
758
|
SendButton: props.SendButton,
|
|
759
|
+
SendToChannelCheckbox: props.SendToChannelCheckbox,
|
|
756
760
|
StartRecordingAudioButton: props.StartRecordingAudioButton,
|
|
757
761
|
StopAIGenerationButton: props.StopAIGenerationButton,
|
|
758
762
|
StreamedMessageText: props.StreamedMessageText,
|
|
@@ -793,6 +797,7 @@ const ChannelInner = (props) => {
|
|
|
793
797
|
props.MessageBlocked,
|
|
794
798
|
props.MessageBouncePrompt,
|
|
795
799
|
props.MessageDeleted,
|
|
800
|
+
props.MessageIsThreadReplyInChannelButtonIndicator,
|
|
796
801
|
props.MessageListNotifications,
|
|
797
802
|
props.MessageNotification,
|
|
798
803
|
props.MessageOptions,
|
|
@@ -814,7 +819,9 @@ const ChannelInner = (props) => {
|
|
|
814
819
|
props.ReactionSelector,
|
|
815
820
|
props.ReactionsList,
|
|
816
821
|
props.ReactionsListModal,
|
|
822
|
+
props.ReminderNotification,
|
|
817
823
|
props.SendButton,
|
|
824
|
+
props.SendToChannelCheckbox,
|
|
818
825
|
props.StartRecordingAudioButton,
|
|
819
826
|
props.StopAIGenerationButton,
|
|
820
827
|
props.StreamedMessageText,
|
|
@@ -162,13 +162,13 @@ export const useChannelListShapeDefaults = () => {
|
|
|
162
162
|
if (typeof customHandler === 'function') {
|
|
163
163
|
return customHandler(setChannels, event);
|
|
164
164
|
}
|
|
165
|
-
if (!event.
|
|
165
|
+
if (!event.channel_id && !event.channel_type) {
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
168
|
const channel = await getChannel({
|
|
169
169
|
client,
|
|
170
|
-
id: event.
|
|
171
|
-
type: event.
|
|
170
|
+
id: event.channel_id,
|
|
171
|
+
type: event.channel_type,
|
|
172
172
|
});
|
|
173
173
|
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
|
|
174
174
|
if (isChannelArchived(channel) && considerArchivedChannels && !filters.archived) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { defaultDateTimeParser, isLanguageSupported, Streami18n } from '../../../i18n';
|
|
2
|
+
import { defaultDateTimeParser, defaultTranslatorFunction, isLanguageSupported, Streami18n, } from '../../../i18n';
|
|
3
3
|
export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialNavOpen, }) => {
|
|
4
4
|
const [translators, setTranslators] = useState({
|
|
5
|
-
t:
|
|
5
|
+
t: defaultTranslatorFunction,
|
|
6
6
|
tDateTimeParser: defaultDateTimeParser,
|
|
7
7
|
userLanguage: 'en',
|
|
8
8
|
});
|
|
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
24
24
|
useEffect(() => {
|
|
25
25
|
if (!client)
|
|
26
26
|
return;
|
|
27
|
-
const version = "13.0
|
|
27
|
+
const version = "13.1.0";
|
|
28
28
|
const userAgent = client.getUserAgent();
|
|
29
29
|
if (!userAgent.includes('stream-chat-react')) {
|
|
30
30
|
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
@@ -33,9 +33,13 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
33
33
|
}
|
|
34
34
|
client.threads.registerSubscriptions();
|
|
35
35
|
client.polls.registerSubscriptions();
|
|
36
|
+
client.reminders.registerSubscriptions();
|
|
37
|
+
client.reminders.initTimers();
|
|
36
38
|
return () => {
|
|
37
39
|
client.threads.unregisterSubscriptions();
|
|
38
40
|
client.polls.unregisterSubscriptions();
|
|
41
|
+
client.reminders.unregisterSubscriptions();
|
|
42
|
+
client.reminders.clearTimers();
|
|
39
43
|
};
|
|
40
44
|
}, [client]);
|
|
41
45
|
useEffect(() => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ComponentProps, ComponentType } from 'react';
|
|
3
|
+
import type { Placement } from '@popperjs/core';
|
|
4
|
+
type ButtonWithSubmenu = ComponentProps<'button'> & {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
placement: Placement;
|
|
7
|
+
Submenu: ComponentType;
|
|
8
|
+
submenuContainerProps?: ComponentProps<'div'>;
|
|
9
|
+
};
|
|
10
|
+
export declare const ButtonWithSubmenu: ({ children, className, placement, Submenu, submenuContainerProps, ...buttonProps }: ButtonWithSubmenu) => React.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useDialog, useDialogIsOpen } from './hooks';
|
|
4
|
+
import { useDialogAnchor } from './DialogAnchor';
|
|
5
|
+
export const ButtonWithSubmenu = ({ children, className, placement, Submenu, submenuContainerProps, ...buttonProps }) => {
|
|
6
|
+
const buttonRef = useRef(null);
|
|
7
|
+
const [dialogContainer, setDialogContainer] = useState(null);
|
|
8
|
+
const keepSubmenuOpen = useRef(false);
|
|
9
|
+
const dialogCloseTimeout = useRef(null);
|
|
10
|
+
const dialogId = useMemo(() => `submenu-${Math.random().toString(36).slice(2)}`, []);
|
|
11
|
+
const dialog = useDialog({ id: dialogId });
|
|
12
|
+
const dialogIsOpen = useDialogIsOpen(dialogId);
|
|
13
|
+
const { attributes, setPopperElement, styles } = useDialogAnchor({
|
|
14
|
+
open: dialogIsOpen,
|
|
15
|
+
placement,
|
|
16
|
+
referenceElement: buttonRef.current,
|
|
17
|
+
});
|
|
18
|
+
const closeDialogLazily = useCallback(() => {
|
|
19
|
+
if (dialogCloseTimeout.current)
|
|
20
|
+
clearTimeout(dialogCloseTimeout.current);
|
|
21
|
+
dialogCloseTimeout.current = setTimeout(() => {
|
|
22
|
+
if (keepSubmenuOpen.current)
|
|
23
|
+
return;
|
|
24
|
+
dialog.close();
|
|
25
|
+
}, 100);
|
|
26
|
+
}, [dialog]);
|
|
27
|
+
const handleClose = useCallback((event) => {
|
|
28
|
+
const parentButton = buttonRef.current;
|
|
29
|
+
if (!dialogIsOpen || !parentButton)
|
|
30
|
+
return;
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
closeDialogLazily();
|
|
33
|
+
parentButton.focus();
|
|
34
|
+
}, [closeDialogLazily, dialogIsOpen, buttonRef]);
|
|
35
|
+
const handleFocusParentButton = () => {
|
|
36
|
+
if (dialogIsOpen)
|
|
37
|
+
return;
|
|
38
|
+
dialog.open();
|
|
39
|
+
keepSubmenuOpen.current = true;
|
|
40
|
+
};
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const parentButton = buttonRef.current;
|
|
43
|
+
if (!dialogIsOpen || !parentButton)
|
|
44
|
+
return;
|
|
45
|
+
const hideOnEscape = (event) => {
|
|
46
|
+
if (event.key !== 'Escape')
|
|
47
|
+
return;
|
|
48
|
+
handleClose(event);
|
|
49
|
+
keepSubmenuOpen.current = false;
|
|
50
|
+
};
|
|
51
|
+
document.addEventListener('keyup', hideOnEscape, { capture: true });
|
|
52
|
+
return () => {
|
|
53
|
+
document.removeEventListener('keyup', hideOnEscape, { capture: true });
|
|
54
|
+
};
|
|
55
|
+
}, [dialogIsOpen, handleClose]);
|
|
56
|
+
return (React.createElement(React.Fragment, null,
|
|
57
|
+
React.createElement("button", { "aria-selected": 'false', className: clsx(className, 'str_chat__button-with-submenu', {
|
|
58
|
+
'str_chat__button-with-submenu--submenu-open': dialogIsOpen,
|
|
59
|
+
}), onBlur: () => {
|
|
60
|
+
keepSubmenuOpen.current = false;
|
|
61
|
+
closeDialogLazily();
|
|
62
|
+
}, onClick: (event) => {
|
|
63
|
+
event.stopPropagation();
|
|
64
|
+
dialog.toggle();
|
|
65
|
+
}, onFocus: handleFocusParentButton, onMouseEnter: handleFocusParentButton, onMouseLeave: () => {
|
|
66
|
+
keepSubmenuOpen.current = false;
|
|
67
|
+
closeDialogLazily();
|
|
68
|
+
}, ref: buttonRef, role: 'option', ...buttonProps }, children),
|
|
69
|
+
dialogIsOpen && (React.createElement("div", { ...attributes.popper, onBlur: (event) => {
|
|
70
|
+
const isBlurredDescendant = event.relatedTarget instanceof Node &&
|
|
71
|
+
dialogContainer?.contains(event.relatedTarget);
|
|
72
|
+
if (isBlurredDescendant)
|
|
73
|
+
return;
|
|
74
|
+
keepSubmenuOpen.current = false;
|
|
75
|
+
closeDialogLazily();
|
|
76
|
+
}, onFocus: () => {
|
|
77
|
+
keepSubmenuOpen.current = true;
|
|
78
|
+
}, onMouseEnter: () => {
|
|
79
|
+
keepSubmenuOpen.current = true;
|
|
80
|
+
}, onMouseLeave: () => {
|
|
81
|
+
keepSubmenuOpen.current = false;
|
|
82
|
+
closeDialogLazily();
|
|
83
|
+
}, ref: (element) => {
|
|
84
|
+
setPopperElement(element);
|
|
85
|
+
setDialogContainer(element);
|
|
86
|
+
}, style: styles.popper, tabIndex: -1, ...submenuContainerProps },
|
|
87
|
+
React.createElement(Submenu, null)))));
|
|
88
|
+
};
|
|
@@ -7,6 +7,6 @@ const UnMemoizedLoadingErrorIndicator = ({ error }) => {
|
|
|
7
7
|
const { t } = useTranslationContext('LoadingErrorIndicator');
|
|
8
8
|
if (!error)
|
|
9
9
|
return null;
|
|
10
|
-
return
|
|
10
|
+
return React.createElement("div", null, t('Error: {{ errorMessage }}', { errorMessage: error.message }));
|
|
11
11
|
};
|
|
12
12
|
export const LoadingErrorIndicator = React.memo(UnMemoizedLoadingErrorIndicator, (prevProps, nextProps) => prevProps.error?.message === nextProps.error?.message);
|
|
@@ -6,7 +6,7 @@ import { MessageSimple as DefaultMessage } from './MessageSimple';
|
|
|
6
6
|
const MessageWithContext = (props) => {
|
|
7
7
|
const { canPin, groupedByUser, Message: propMessage, message, messageActions = Object.keys(MESSAGE_ACTIONS), onUserClick: propOnUserClick, onUserHover: propOnUserHover, userRoles, } = props;
|
|
8
8
|
const { client, isMessageAIGenerated } = useChatContext('Message');
|
|
9
|
-
const { read } = useChannelStateContext('Message');
|
|
9
|
+
const { channelConfig, read } = useChannelStateContext('Message');
|
|
10
10
|
const { Message: contextMessage } = useComponentContext('Message');
|
|
11
11
|
const actionsEnabled = message.type === 'regular' && message.status === 'received';
|
|
12
12
|
const MessageUIComponent = propMessage ?? contextMessage ?? DefaultMessage;
|
|
@@ -33,7 +33,7 @@ const MessageWithContext = (props) => {
|
|
|
33
33
|
canQuote,
|
|
34
34
|
canReact,
|
|
35
35
|
canReply,
|
|
36
|
-
}), [
|
|
36
|
+
}, channelConfig), [
|
|
37
37
|
messageActions,
|
|
38
38
|
canDelete,
|
|
39
39
|
canEdit,
|
|
@@ -44,6 +44,7 @@ const MessageWithContext = (props) => {
|
|
|
44
44
|
canQuote,
|
|
45
45
|
canReact,
|
|
46
46
|
canReply,
|
|
47
|
+
channelConfig,
|
|
47
48
|
]);
|
|
48
49
|
const { canPin: canPinPropToNotPass, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
49
50
|
messageActions: messageActionsPropToNotPass, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
@@ -9,6 +9,11 @@ import { MessageRepliesCountButton as DefaultMessageRepliesCountButton } from '.
|
|
|
9
9
|
import { MessageStatus as DefaultMessageStatus } from './MessageStatus';
|
|
10
10
|
import { MessageText } from './MessageText';
|
|
11
11
|
import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp';
|
|
12
|
+
import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText';
|
|
13
|
+
import { isDateSeparatorMessage } from '../MessageList';
|
|
14
|
+
import { MessageThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageThreadReplyInChannelButtonIndicator';
|
|
15
|
+
import { ReminderNotification as DefaultReminderNotification } from './ReminderNotification';
|
|
16
|
+
import { useMessageReminder } from './hooks';
|
|
12
17
|
import { areMessageUIPropsEqual, isMessageBlocked, isMessageBounced, isMessageEdited, messageHasAttachments, messageHasReactions, } from './utils';
|
|
13
18
|
import { Avatar as DefaultAvatar } from '../Avatar';
|
|
14
19
|
import { Attachment as DefaultAttachment } from '../Attachment';
|
|
@@ -21,18 +26,17 @@ import { useComponentContext } from '../../context/ComponentContext';
|
|
|
21
26
|
import { useMessageContext } from '../../context/MessageContext';
|
|
22
27
|
import { useChatContext, useTranslationContext } from '../../context';
|
|
23
28
|
import { MessageEditedTimestamp } from './MessageEditedTimestamp';
|
|
24
|
-
import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText';
|
|
25
|
-
import { isDateSeparatorMessage } from '../MessageList';
|
|
26
29
|
const MessageSimpleWithContext = (props) => {
|
|
27
30
|
const { additionalMessageInputProps, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMessageAIGenerated, isMyMessage, message, onUserClick, onUserHover, renderText, threadList, } = props;
|
|
28
31
|
const { client } = useChatContext('MessageSimple');
|
|
29
32
|
const { t } = useTranslationContext('MessageSimple');
|
|
30
33
|
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
|
|
31
34
|
const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false);
|
|
35
|
+
const reminder = useMessageReminder(message.id);
|
|
32
36
|
const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, MessageOptions = DefaultMessageOptions,
|
|
33
37
|
// TODO: remove this "passthrough" in the next
|
|
34
38
|
// major release and use the new default instead
|
|
35
|
-
MessageActions = MessageOptions, MessageBlocked = DefaultMessageBlocked, MessageDeleted = DefaultMessageDeleted,
|
|
39
|
+
MessageActions = MessageOptions, MessageBlocked = DefaultMessageBlocked, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageDeleted = DefaultMessageDeleted, MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionsList = DefaultReactionList, ReminderNotification = DefaultReminderNotification, StreamedMessageText = DefaultStreamedMessageText, PinIndicator, } = useComponentContext('MessageSimple');
|
|
36
40
|
const hasAttachment = messageHasAttachments(message);
|
|
37
41
|
const hasReactions = messageHasReactions(message);
|
|
38
42
|
const isAIGenerated = useMemo(() => isMessageAIGenerated?.(message), [isMessageAIGenerated, message]);
|
|
@@ -47,6 +51,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
47
51
|
}
|
|
48
52
|
const showMetadata = !groupedByUser || endOfGroup;
|
|
49
53
|
const showReplyCountButton = !threadList && !!message.reply_count;
|
|
54
|
+
const showIsReplyInChannel = !threadList && message.show_in_channel && message.parent_id;
|
|
50
55
|
const allowRetry = message.status === 'failed' && message.error?.status !== 403;
|
|
51
56
|
const isBounced = isMessageBounced(message);
|
|
52
57
|
const isEdited = isMessageEdited(message) && !isAIGenerated;
|
|
@@ -68,7 +73,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
68
73
|
'str-chat__message--pinned pinned-message': message.pinned,
|
|
69
74
|
'str-chat__message--with-reactions': hasReactions,
|
|
70
75
|
'str-chat__message-send-can-be-retried': message?.status === 'failed' && message?.error?.status !== 403,
|
|
71
|
-
'str-chat__message-with-thread-link': showReplyCountButton,
|
|
76
|
+
'str-chat__message-with-thread-link': showReplyCountButton || showIsReplyInChannel,
|
|
72
77
|
'str-chat__virtual-message__wrapper--end': endOfGroup,
|
|
73
78
|
'str-chat__virtual-message__wrapper--first': firstOfGroup,
|
|
74
79
|
'str-chat__virtual-message__wrapper--group': groupedByUser,
|
|
@@ -79,6 +84,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
79
84
|
isBounceDialogOpen && (React.createElement(MessageBounceModal, { MessageBouncePrompt: MessageBouncePrompt, onClose: () => setIsBounceDialogOpen(false), open: isBounceDialogOpen })),
|
|
80
85
|
React.createElement("div", { className: rootClassName, key: message.id },
|
|
81
86
|
PinIndicator && React.createElement(PinIndicator, null),
|
|
87
|
+
!!reminder && React.createElement(ReminderNotification, { reminder: reminder }),
|
|
82
88
|
message.user && (React.createElement(Avatar, { image: message.user.image, name: message.user.name || message.user.id, onClick: onUserClick, onMouseOver: onUserHover, user: message.user })),
|
|
83
89
|
React.createElement("div", { className: clsx('str-chat__message-inner', {
|
|
84
90
|
'str-chat__simple-message--error-failed': allowRetry || isBounced,
|
|
@@ -92,6 +98,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
92
98
|
message.mml && (React.createElement(MML, { actionHandler: handleAction, align: isMyMessage() ? 'right' : 'left', source: message.mml })),
|
|
93
99
|
React.createElement(MessageErrorIcon, null))),
|
|
94
100
|
showReplyCountButton && (React.createElement(MessageRepliesCountButton, { onClick: handleOpenThread, reply_count: message.reply_count })),
|
|
101
|
+
showIsReplyInChannel && React.createElement(MessageIsThreadReplyInChannelButtonIndicator, null),
|
|
95
102
|
showMetadata && (React.createElement("div", { className: 'str-chat__message-metadata' },
|
|
96
103
|
React.createElement(MessageStatus, null),
|
|
97
104
|
!isMyMessage() && !!message.user && (React.createElement("span", { className: 'str-chat__message-simple-name' }, message.user.name || message.user.id)),
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { formatMessage } from 'stream-chat';
|
|
3
|
+
import { useChannelActionContext, useChannelStateContext, useChatContext, useMessageContext, useTranslationContext, } from '../../context';
|
|
4
|
+
export const MessageThreadReplyInChannelButtonIndicator = () => {
|
|
5
|
+
const { client } = useChatContext();
|
|
6
|
+
const { t } = useTranslationContext();
|
|
7
|
+
const { channel } = useChannelStateContext();
|
|
8
|
+
const { openThread } = useChannelActionContext();
|
|
9
|
+
const { message } = useMessageContext();
|
|
10
|
+
const parentMessageRef = useRef(undefined);
|
|
11
|
+
const querySearchParent = () => channel
|
|
12
|
+
.getClient()
|
|
13
|
+
.search({ cid: channel.cid }, { id: message.parent_id })
|
|
14
|
+
.then(({ results }) => {
|
|
15
|
+
if (!results.length) {
|
|
16
|
+
throw new Error('Thread has not been found');
|
|
17
|
+
}
|
|
18
|
+
parentMessageRef.current = formatMessage(results[0].message);
|
|
19
|
+
})
|
|
20
|
+
.catch((error) => {
|
|
21
|
+
client.notifications.addError({
|
|
22
|
+
message: t('Thread has not been found'),
|
|
23
|
+
options: {
|
|
24
|
+
originalError: error,
|
|
25
|
+
type: 'api:message:search:not-found',
|
|
26
|
+
},
|
|
27
|
+
origin: {
|
|
28
|
+
context: { threadReply: message },
|
|
29
|
+
emitter: 'MessageThreadReplyInChannelButtonIndicator',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (parentMessageRef.current ||
|
|
35
|
+
parentMessageRef.current === null ||
|
|
36
|
+
!message.parent_id)
|
|
37
|
+
return;
|
|
38
|
+
const localMessage = channel.state.findMessage(message.parent_id);
|
|
39
|
+
if (localMessage) {
|
|
40
|
+
parentMessageRef.current = localMessage;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}, [channel, message]);
|
|
44
|
+
if (!message.parent_id)
|
|
45
|
+
return null;
|
|
46
|
+
return (React.createElement("div", { className: 'str-chat__message-is-thread-reply-button-wrapper' },
|
|
47
|
+
React.createElement("button", { className: 'str-chat__message-is-thread-reply-button', "data-testid": 'message-is-thread-reply-button', onClick: async () => {
|
|
48
|
+
if (!parentMessageRef.current) {
|
|
49
|
+
// search query is performed here in order to prevent multiple search queries in useEffect
|
|
50
|
+
// due to the message list 3x remounting its items
|
|
51
|
+
await querySearchParent();
|
|
52
|
+
if (parentMessageRef.current) {
|
|
53
|
+
openThread(parentMessageRef.current);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// prevent further search queries if the message is not found in the DB
|
|
57
|
+
parentMessageRef.current = null;
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
openThread(parentMessageRef.current);
|
|
62
|
+
}, type: 'button' }, t('Thread reply'))));
|
|
63
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslationContext } from '../../context';
|
|
3
|
+
import { useStateStore } from '../../store';
|
|
4
|
+
const reminderStateSelector = (state) => ({
|
|
5
|
+
timeLeftMs: state.timeLeftMs,
|
|
6
|
+
});
|
|
7
|
+
export const ReminderNotification = ({ reminder }) => {
|
|
8
|
+
const { t } = useTranslationContext();
|
|
9
|
+
const { timeLeftMs } = useStateStore(reminder?.state, reminderStateSelector) ?? {};
|
|
10
|
+
const stopRefreshBoundaryMs = reminder?.timer.stopRefreshBoundaryMs;
|
|
11
|
+
const stopRefreshTimeStamp = reminder?.remindAt && stopRefreshBoundaryMs
|
|
12
|
+
? reminder?.remindAt.getTime() + stopRefreshBoundaryMs
|
|
13
|
+
: undefined;
|
|
14
|
+
const isBehindRefreshBoundary = !!stopRefreshTimeStamp && new Date().getTime() > stopRefreshTimeStamp;
|
|
15
|
+
return (React.createElement("p", { className: 'str-chat__message-reminder' },
|
|
16
|
+
React.createElement("span", null, t('Saved for later')),
|
|
17
|
+
reminder?.remindAt && timeLeftMs !== null && (React.createElement(React.Fragment, null,
|
|
18
|
+
React.createElement("span", null, " | "),
|
|
19
|
+
React.createElement("span", null, isBehindRefreshBoundary
|
|
20
|
+
? t('Due since {{ dueSince }}', {
|
|
21
|
+
dueSince: t(`timestamp/ReminderNotification`, {
|
|
22
|
+
timestamp: reminder.remindAt,
|
|
23
|
+
}),
|
|
24
|
+
})
|
|
25
|
+
: t(`Due {{ timeLeft }}`, {
|
|
26
|
+
timeLeft: t('duration/Message reminder', {
|
|
27
|
+
milliseconds: timeLeftMs,
|
|
28
|
+
}),
|
|
29
|
+
}))))));
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useMessageReminder: (messageId: string) => import("stream-chat").Reminder | undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useChatContext } from '../../../context';
|
|
3
|
+
import { useStateStore } from '../../../store';
|
|
4
|
+
export const useMessageReminder = (messageId) => {
|
|
5
|
+
const { client } = useChatContext();
|
|
6
|
+
const reminderSelector = useCallback((state) => ({
|
|
7
|
+
reminder: state.reminders.get(messageId),
|
|
8
|
+
}), [messageId]);
|
|
9
|
+
const { reminder } = useStateStore(client.reminders.state, reminderSelector);
|
|
10
|
+
return reminder;
|
|
11
|
+
};
|
|
@@ -10,6 +10,7 @@ export * from './MessageStatus';
|
|
|
10
10
|
export * from './MessageText';
|
|
11
11
|
export * from './MessageTimestamp';
|
|
12
12
|
export * from './QuotedMessage';
|
|
13
|
+
export * from './ReminderNotification';
|
|
13
14
|
export * from './renderText';
|
|
14
15
|
export * from './types';
|
|
15
16
|
export * from './utils';
|
|
@@ -10,6 +10,7 @@ export * from './MessageStatus';
|
|
|
10
10
|
export * from './MessageText';
|
|
11
11
|
export * from './MessageTimestamp';
|
|
12
12
|
export * from './QuotedMessage';
|
|
13
|
+
export * from './ReminderNotification';
|
|
13
14
|
export * from './renderText';
|
|
14
15
|
export * from './types';
|
|
15
16
|
export * from './utils';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TFunction } from 'i18next';
|
|
2
|
-
import type { LocalMessage, MessageResponse, Mute, StreamChat, UserResponse } from 'stream-chat';
|
|
2
|
+
import type { ChannelConfigWithInfo, LocalMessage, MessageResponse, Mute, StreamChat, UserResponse } from 'stream-chat';
|
|
3
3
|
import type { PinPermissions } from './hooks';
|
|
4
4
|
import type { MessageProps } from './types';
|
|
5
5
|
import type { ComponentContextValue, CustomMessageActions, MessageContextValue } from '../../context';
|
|
@@ -21,7 +21,9 @@ export declare const MESSAGE_ACTIONS: {
|
|
|
21
21
|
pin: string;
|
|
22
22
|
quote: string;
|
|
23
23
|
react: string;
|
|
24
|
+
remindMe: string;
|
|
24
25
|
reply: string;
|
|
26
|
+
saveForLater: string;
|
|
25
27
|
};
|
|
26
28
|
export type MessageActionsArray<T extends string = string> = Array<keyof typeof MESSAGE_ACTIONS | T>;
|
|
27
29
|
export declare const defaultPinPermissions: PinPermissions;
|
|
@@ -36,7 +38,7 @@ export type Capabilities = {
|
|
|
36
38
|
canReact?: boolean;
|
|
37
39
|
canReply?: boolean;
|
|
38
40
|
};
|
|
39
|
-
export declare const getMessageActions: (actions: MessageActionsArray | boolean, { canDelete, canEdit, canFlag, canMarkUnread, canMute, canPin, canQuote, canReact, canReply, }: Capabilities) => MessageActionsArray<string>;
|
|
41
|
+
export declare const getMessageActions: (actions: MessageActionsArray | boolean, { canDelete, canEdit, canFlag, canMarkUnread, canMute, canPin, canQuote, canReact, canReply, }: Capabilities, channelConfig?: ChannelConfigWithInfo) => MessageActionsArray<string>;
|
|
40
42
|
export declare const ACTIONS_NOT_WORKING_IN_THREAD: string[];
|
|
41
43
|
/**
|
|
42
44
|
* @deprecated use `shouldRenderMessageActions` instead
|
|
@@ -35,7 +35,9 @@ export const MESSAGE_ACTIONS = {
|
|
|
35
35
|
pin: 'pin',
|
|
36
36
|
quote: 'quote',
|
|
37
37
|
react: 'react',
|
|
38
|
+
remindMe: 'remindMe',
|
|
38
39
|
reply: 'reply',
|
|
40
|
+
saveForLater: 'saveForLater',
|
|
39
41
|
};
|
|
40
42
|
// @deprecated in favor of `channelCapabilities` - TODO: remove in next major release
|
|
41
43
|
export const defaultPinPermissions = {
|
|
@@ -95,7 +97,7 @@ export const defaultPinPermissions = {
|
|
|
95
97
|
user: false,
|
|
96
98
|
},
|
|
97
99
|
};
|
|
98
|
-
export const getMessageActions = (actions, { canDelete, canEdit, canFlag, canMarkUnread, canMute, canPin, canQuote, canReact, canReply, }) => {
|
|
100
|
+
export const getMessageActions = (actions, { canDelete, canEdit, canFlag, canMarkUnread, canMute, canPin, canQuote, canReact, canReply, }, channelConfig) => {
|
|
99
101
|
const messageActionsAfterPermission = [];
|
|
100
102
|
let messageActions = [];
|
|
101
103
|
if (actions && typeof actions === 'boolean') {
|
|
@@ -132,9 +134,17 @@ export const getMessageActions = (actions, { canDelete, canEdit, canFlag, canMar
|
|
|
132
134
|
if (canReact && messageActions.indexOf(MESSAGE_ACTIONS.react) > -1) {
|
|
133
135
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.react);
|
|
134
136
|
}
|
|
137
|
+
if (channelConfig?.['user_message_reminders'] &&
|
|
138
|
+
messageActions.indexOf(MESSAGE_ACTIONS.remindMe)) {
|
|
139
|
+
messageActionsAfterPermission.push(MESSAGE_ACTIONS.remindMe);
|
|
140
|
+
}
|
|
135
141
|
if (canReply && messageActions.indexOf(MESSAGE_ACTIONS.reply) > -1) {
|
|
136
142
|
messageActionsAfterPermission.push(MESSAGE_ACTIONS.reply);
|
|
137
143
|
}
|
|
144
|
+
if (channelConfig?.['user_message_reminders'] &&
|
|
145
|
+
messageActions.indexOf(MESSAGE_ACTIONS.saveForLater)) {
|
|
146
|
+
messageActionsAfterPermission.push(MESSAGE_ACTIONS.saveForLater);
|
|
147
|
+
}
|
|
138
148
|
return messageActionsAfterPermission;
|
|
139
149
|
};
|
|
140
150
|
export const ACTIONS_NOT_WORKING_IN_THREAD = [
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { MESSAGE_ACTIONS } from '../Message/utils';
|
|
4
|
-
import { useComponentContext, useMessageContext, useTranslationContext, } from '../../context';
|
|
5
3
|
import { CustomMessageActionsList as DefaultCustomMessageActionsList } from './CustomMessageActionsList';
|
|
4
|
+
import { RemindMeActionButton } from './RemindMeSubmenu';
|
|
5
|
+
import { useMessageReminder } from '../Message';
|
|
6
6
|
import { useMessageComposer } from '../MessageInput';
|
|
7
|
+
import { useChatContext, useComponentContext, useMessageContext, useTranslationContext, } from '../../context';
|
|
8
|
+
import { MESSAGE_ACTIONS } from '../Message/utils';
|
|
7
9
|
const UnMemoizedMessageActionsBox = (props) => {
|
|
8
|
-
const { className, getMessageActions, handleDelete, handleEdit, handleFlag, handleMarkUnread, handleMute, handlePin, isUserMuted,
|
|
9
|
-
|
|
10
|
-
mine, open, ...restDivProps } = props;
|
|
10
|
+
const { className, getMessageActions, handleDelete, handleEdit, handleFlag, handleMarkUnread, handleMute, handlePin, isUserMuted, mine, open, ...restDivProps } = props;
|
|
11
|
+
const { client } = useChatContext();
|
|
11
12
|
const { CustomMessageActionsList = DefaultCustomMessageActionsList } = useComponentContext('MessageActionsBox');
|
|
12
13
|
const { customMessageActions, message, threadList } = useMessageContext('MessageActionsBox');
|
|
13
14
|
const { t } = useTranslationContext('MessageActionsBox');
|
|
14
15
|
const messageComposer = useMessageComposer();
|
|
16
|
+
const reminder = useMessageReminder(message.id);
|
|
15
17
|
const messageActions = getMessageActions();
|
|
16
18
|
const handleQuote = () => {
|
|
17
19
|
messageComposer.setQuotedMessage(message);
|
|
@@ -38,7 +40,11 @@ const UnMemoizedMessageActionsBox = (props) => {
|
|
|
38
40
|
messageActions.indexOf(MESSAGE_ACTIONS.flag) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleFlag, role: 'option' }, t('Flag'))),
|
|
39
41
|
messageActions.indexOf(MESSAGE_ACTIONS.mute) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleMute, role: 'option' }, isUserMuted() ? t('Unmute') : t('Mute'))),
|
|
40
42
|
messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleEdit, role: 'option' }, t('Edit Message'))),
|
|
41
|
-
messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleDelete, role: 'option' }, t('Delete')))
|
|
43
|
+
messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: handleDelete, role: 'option' }, t('Delete'))),
|
|
44
|
+
messageActions.indexOf(MESSAGE_ACTIONS.remindMe) > -1 && (React.createElement(RemindMeActionButton, { className: buttonClassName, isMine: mine })),
|
|
45
|
+
messageActions.indexOf(MESSAGE_ACTIONS.saveForLater) > -1 && (React.createElement("button", { "aria-selected": 'false', className: buttonClassName, onClick: () => reminder
|
|
46
|
+
? client.reminders.deleteReminder(reminder.id)
|
|
47
|
+
: client.reminders.createReminder({ messageId: message.id }), role: 'option' }, reminder ? t('Remove reminder') : t('Save for later'))))));
|
|
42
48
|
};
|
|
43
49
|
/**
|
|
44
50
|
* A popup box that displays the available actions on a message, such as edit, delete, pin, etc.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ComponentProps } from 'react';
|
|
3
|
+
export declare const RemindMeActionButton: ({ className, isMine, }: {
|
|
4
|
+
isMine: boolean;
|
|
5
|
+
} & ComponentProps<'button'>) => React.JSX.Element;
|
|
6
|
+
export declare const RemindMeSubmenu: () => React.JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useChatContext, useMessageContext, useTranslationContext } from '../../context';
|
|
3
|
+
import { ButtonWithSubmenu } from '../Dialog';
|
|
4
|
+
export const RemindMeActionButton = ({ className, isMine, }) => {
|
|
5
|
+
const { t } = useTranslationContext();
|
|
6
|
+
return (React.createElement(ButtonWithSubmenu, { "aria-selected": 'false', className: className, placement: isMine ? 'left-start' : 'right-start', Submenu: RemindMeSubmenu }, t('Remind Me')));
|
|
7
|
+
};
|
|
8
|
+
export const RemindMeSubmenu = () => {
|
|
9
|
+
const { t } = useTranslationContext();
|
|
10
|
+
const { client } = useChatContext();
|
|
11
|
+
const { message } = useMessageContext();
|
|
12
|
+
return (React.createElement("div", { "aria-label": t('aria/Remind Me Options'), className: 'str-chat__message-actions-box__submenu', role: 'listbox' }, client.reminders.scheduledOffsetsMs.map((offsetMs) => (React.createElement("button", { className: 'str-chat__message-actions-list-item-button', key: `reminder-offset-option--${offsetMs}`, onClick: () => {
|
|
13
|
+
client.reminders.upsertReminder({
|
|
14
|
+
messageId: message.id,
|
|
15
|
+
remind_at: new Date(new Date().getTime() + offsetMs).toISOString(),
|
|
16
|
+
});
|
|
17
|
+
} }, t('duration/Remind Me', { milliseconds: offsetMs }))))));
|
|
18
|
+
};
|