stream-chat-react 12.3.0 → 12.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/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +2 -0
- package/dist/components/ChatAutoComplete/ChatAutoComplete.d.ts +1 -1
- package/dist/components/Message/MessageOptions.js +1 -1
- package/dist/components/Message/MessageSimple.js +5 -2
- package/dist/components/Message/utils.d.ts +1 -1
- package/dist/components/MessageActions/MessageActions.d.ts +2 -1
- package/dist/components/MessageActions/MessageActions.js +1 -1
- package/dist/components/Reactions/ReactionSelectorWithButton.d.ts +1 -2
- package/dist/components/Reactions/ReactionSelectorWithButton.js +2 -2
- package/dist/components/Threads/hooks/useThreadManagerState.js +1 -1
- package/dist/context/ComponentContext.d.ts +7 -1
- package/dist/experimental/MessageActions/MessageActions.d.ts +17 -0
- package/dist/experimental/MessageActions/MessageActions.js +48 -0
- package/dist/experimental/MessageActions/defaults.d.ts +5 -0
- package/dist/experimental/MessageActions/defaults.js +93 -0
- package/dist/experimental/MessageActions/hooks/index.d.ts +2 -0
- package/dist/experimental/MessageActions/hooks/index.js +2 -0
- package/dist/experimental/MessageActions/hooks/useBaseMessageActionSetFilter.d.ts +8 -0
- package/dist/experimental/MessageActions/hooks/useBaseMessageActionSetFilter.js +57 -0
- package/dist/experimental/MessageActions/hooks/useSplitMessageActionSet.d.ts +5 -0
- package/dist/experimental/MessageActions/hooks/useSplitMessageActionSet.js +12 -0
- package/dist/experimental/MessageActions/index.d.ts +3 -0
- package/dist/experimental/MessageActions/index.js +3 -0
- package/dist/experimental/index.browser.cjs +1091 -0
- package/dist/experimental/index.browser.cjs.map +7 -0
- package/dist/experimental/index.d.ts +1 -0
- package/dist/experimental/index.js +1 -0
- package/dist/experimental/index.node.cjs +1099 -0
- package/dist/experimental/index.node.cjs.map +7 -0
- package/dist/i18n/Streami18n.d.ts +1 -0
- package/dist/i18n/de.json +1 -0
- package/dist/i18n/en.json +1 -0
- package/dist/i18n/es.json +1 -0
- package/dist/i18n/fr.json +1 -0
- package/dist/i18n/hi.json +1 -0
- package/dist/i18n/it.json +1 -0
- package/dist/i18n/ja.json +1 -0
- package/dist/i18n/ko.json +1 -0
- package/dist/i18n/nl.json +1 -0
- package/dist/i18n/pt.json +1 -0
- package/dist/i18n/ru.json +1 -0
- package/dist/i18n/tr.json +1 -0
- package/dist/index.browser.cjs +23 -6
- package/dist/index.browser.cjs.map +3 -3
- package/dist/index.node.cjs +24 -6
- package/dist/index.node.cjs.map +3 -3
- package/package.json +16 -1
|
@@ -6,7 +6,7 @@ import { ComponentContextValue, StreamMessage } from '../../context';
|
|
|
6
6
|
import type { MessageInputProps } from '../MessageInput';
|
|
7
7
|
import type { ChannelUnreadUiState, CustomTrigger, DefaultStreamChatGenerics, GiphyVersions, ImageAttachmentSizeHandler, SendMessageOptions, UpdateMessageOptions, VideoAttachmentSizeHandler } from '../../types/types';
|
|
8
8
|
import type { URLEnrichmentConfig } from '../MessageInput/hooks/useLinkPreviews';
|
|
9
|
-
type ChannelPropsForwardedToComponentContext<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Pick<ComponentContextValue<StreamChatGenerics>, 'Attachment' | 'AttachmentPreviewList' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageBouncePrompt' | 'MessageDeleted' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'QuotedMessage' | 'QuotedMessagePreview' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'SendButton' | 'StartRecordingAudioButton' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TriggerProvider' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage'>;
|
|
9
|
+
type ChannelPropsForwardedToComponentContext<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = Pick<ComponentContextValue<StreamChatGenerics>, 'Attachment' | 'AttachmentPreviewList' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageDeleted' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'QuotedMessage' | 'QuotedMessagePreview' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'SendButton' | 'StartRecordingAudioButton' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TriggerProvider' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage'>;
|
|
10
10
|
export type ChannelProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, V extends CustomTrigger = CustomTrigger> = ChannelPropsForwardedToComponentContext<StreamChatGenerics> & {
|
|
11
11
|
/** List of accepted file types */
|
|
12
12
|
acceptedFiles?: string[];
|
|
@@ -749,6 +749,7 @@ const ChannelInner = (props) => {
|
|
|
749
749
|
LinkPreviewList: props.LinkPreviewList,
|
|
750
750
|
LoadingIndicator: props.LoadingIndicator,
|
|
751
751
|
Message: props.Message,
|
|
752
|
+
MessageActions: props.MessageActions,
|
|
752
753
|
MessageBouncePrompt: props.MessageBouncePrompt,
|
|
753
754
|
MessageDeleted: props.MessageDeleted,
|
|
754
755
|
MessageListNotifications: props.MessageListNotifications,
|
|
@@ -797,6 +798,7 @@ const ChannelInner = (props) => {
|
|
|
797
798
|
props.LinkPreviewList,
|
|
798
799
|
props.LoadingIndicator,
|
|
799
800
|
props.Message,
|
|
801
|
+
props.MessageActions,
|
|
800
802
|
props.MessageBouncePrompt,
|
|
801
803
|
props.MessageDeleted,
|
|
802
804
|
props.MessageListNotifications,
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import type { CommandResponse, UserResponse } from 'stream-chat';
|
|
3
3
|
import type { TriggerSettings } from '../MessageInput/DefaultTriggerProvider';
|
|
4
4
|
import type { CustomTrigger, DefaultStreamChatGenerics, UnknownType } from '../../types/types';
|
|
5
|
-
import { EmojiSearchIndex } from '
|
|
5
|
+
import type { EmojiSearchIndex } from '../MessageInput';
|
|
6
6
|
type ObjectUnion<T> = T[keyof T];
|
|
7
7
|
export type SuggestionCommand<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = CommandResponse<StreamChatGenerics>;
|
|
8
8
|
export type SuggestionUser<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = UserResponse<StreamChatGenerics>;
|
|
@@ -31,6 +31,6 @@ const UnMemoizedMessageOptions = (props) => {
|
|
|
31
31
|
React.createElement(MessageActions, { ActionsIcon: ActionsIcon }),
|
|
32
32
|
shouldShowReplies && (React.createElement("button", { "aria-label": t('aria/Open Thread'), className: `str-chat__message-${theme}__actions__action str-chat__message-${theme}__actions__action--thread str-chat__message-reply-in-thread-button`, "data-testid": 'thread-action', onClick: handleOpenThread },
|
|
33
33
|
React.createElement(ThreadIcon, { className: 'str-chat__message-action-icon' }))),
|
|
34
|
-
shouldShowReactions &&
|
|
34
|
+
shouldShowReactions && React.createElement(ReactionSelectorWithButton, { ReactionIcon: ReactionIcon })));
|
|
35
35
|
};
|
|
36
36
|
export const MessageOptions = React.memo(UnMemoizedMessageOptions);
|
|
@@ -26,7 +26,10 @@ const MessageSimpleWithContext = (props) => {
|
|
|
26
26
|
const { t } = useTranslationContext('MessageSimple');
|
|
27
27
|
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
|
|
28
28
|
const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false);
|
|
29
|
-
const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, EditMessageInput = DefaultEditMessageForm,
|
|
29
|
+
const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, EditMessageInput = DefaultEditMessageForm, MessageOptions = DefaultMessageOptions,
|
|
30
|
+
// TODO: remove this "passthrough" in the next
|
|
31
|
+
// major release and use the new default instead
|
|
32
|
+
MessageActions = MessageOptions, MessageDeleted = DefaultMessageDeleted, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionsList = DefaultReactionList, PinIndicator, } = useComponentContext('MessageSimple');
|
|
30
33
|
const hasAttachment = messageHasAttachments(message);
|
|
31
34
|
const hasReactions = messageHasReactions(message);
|
|
32
35
|
if (message.customType === CUSTOM_MESSAGE_TYPE.date) {
|
|
@@ -73,7 +76,7 @@ const MessageSimpleWithContext = (props) => {
|
|
|
73
76
|
React.createElement("div", { className: clsx('str-chat__message-inner', {
|
|
74
77
|
'str-chat__simple-message--error-failed': allowRetry || isBounced,
|
|
75
78
|
}), "data-testid": 'message-inner', onClick: handleClick, onKeyUp: handleClick },
|
|
76
|
-
React.createElement(
|
|
79
|
+
React.createElement(MessageActions, null),
|
|
77
80
|
React.createElement("div", { className: 'str-chat__message-reactions-host' }, hasReactions && React.createElement(ReactionsList, { reverse: true })),
|
|
78
81
|
React.createElement("div", { className: 'str-chat__message-bubble' },
|
|
79
82
|
message.attachments?.length && !message.quoted_message ? (React.createElement(Attachment, { actionHandler: handleAction, attachments: message.attachments })) : null,
|
|
@@ -24,7 +24,7 @@ export declare const MESSAGE_ACTIONS: {
|
|
|
24
24
|
react: string;
|
|
25
25
|
reply: string;
|
|
26
26
|
};
|
|
27
|
-
export type MessageActionsArray<T extends string = string> = Array<
|
|
27
|
+
export type MessageActionsArray<T extends string = string> = Array<keyof typeof MESSAGE_ACTIONS | T>;
|
|
28
28
|
export declare const defaultPinPermissions: PinPermissions;
|
|
29
29
|
export type Capabilities = {
|
|
30
30
|
canDelete?: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
2
|
import { MessageContextValue } from '../../context/MessageContext';
|
|
3
3
|
import type { DefaultStreamChatGenerics, IconProps } from '../../types/types';
|
|
4
4
|
type MessageContextPropsToPick = 'getMessageActions' | 'handleDelete' | 'handleFlag' | 'handleMarkUnread' | 'handleMute' | 'handlePin' | 'message';
|
|
@@ -14,4 +14,5 @@ export type MessageActionsWrapperProps = {
|
|
|
14
14
|
inline?: boolean;
|
|
15
15
|
toggleOpen?: () => void;
|
|
16
16
|
};
|
|
17
|
+
export declare const MessageActionsWrapper: (props: PropsWithChildren<MessageActionsWrapperProps>) => React.JSX.Element;
|
|
17
18
|
export {};
|
|
@@ -41,7 +41,7 @@ export const MessageActions = (props) => {
|
|
|
41
41
|
React.createElement("button", { "aria-expanded": dialogIsOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Message Actions Menu'), className: 'str-chat__message-actions-box-button', "data-testid": 'message-actions-toggle-button', ref: actionsBoxButtonRef },
|
|
42
42
|
React.createElement(ActionsIcon, { className: 'str-chat__message-action-icon' }))));
|
|
43
43
|
};
|
|
44
|
-
const MessageActionsWrapper = (props) => {
|
|
44
|
+
export const MessageActionsWrapper = (props) => {
|
|
45
45
|
const { children, customWrapperClass, inline, toggleOpen } = props;
|
|
46
46
|
const defaultWrapperClass = clsx('str-chat__message-simple__actions__action', 'str-chat__message-simple__actions__action--options', 'str-chat__message-actions-container');
|
|
47
47
|
const wrapperProps = {
|
|
@@ -3,11 +3,10 @@ import type { DefaultStreamChatGenerics } from '../../types';
|
|
|
3
3
|
import type { IconProps } from '../../types/types';
|
|
4
4
|
type ReactionSelectorWithButtonProps = {
|
|
5
5
|
ReactionIcon: React.ComponentType<IconProps>;
|
|
6
|
-
theme: string;
|
|
7
6
|
};
|
|
8
7
|
/**
|
|
9
8
|
* Internal convenience component - not to be exported. It just groups the button and the dialog anchor and thus prevents
|
|
10
9
|
* cluttering the parent component.
|
|
11
10
|
*/
|
|
12
|
-
export declare const ReactionSelectorWithButton: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ ReactionIcon,
|
|
11
|
+
export declare const ReactionSelectorWithButton: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ ReactionIcon, }: ReactionSelectorWithButtonProps) => React.JSX.Element;
|
|
13
12
|
export {};
|
|
@@ -6,7 +6,7 @@ import { useComponentContext, useMessageContext, useTranslationContext } from '.
|
|
|
6
6
|
* Internal convenience component - not to be exported. It just groups the button and the dialog anchor and thus prevents
|
|
7
7
|
* cluttering the parent component.
|
|
8
8
|
*/
|
|
9
|
-
export const ReactionSelectorWithButton = ({ ReactionIcon,
|
|
9
|
+
export const ReactionSelectorWithButton = ({ ReactionIcon, }) => {
|
|
10
10
|
const { t } = useTranslationContext('ReactionSelectorWithButton');
|
|
11
11
|
const { isMyMessage, message } = useMessageContext('MessageOptions');
|
|
12
12
|
const { ReactionSelector = DefaultReactionSelector } = useComponentContext('MessageOptions');
|
|
@@ -17,6 +17,6 @@ export const ReactionSelectorWithButton = ({ ReactionIcon, theme, }) => {
|
|
|
17
17
|
return (React.createElement(React.Fragment, null,
|
|
18
18
|
React.createElement(DialogAnchor, { id: dialogId, placement: isMyMessage() ? 'top-end' : 'top-start', referenceElement: buttonRef.current, trapFocus: true },
|
|
19
19
|
React.createElement(ReactionSelector, null)),
|
|
20
|
-
React.createElement("button", { "aria-expanded": dialogIsOpen, "aria-label": t('aria/Open Reaction Selector'), className:
|
|
20
|
+
React.createElement("button", { "aria-expanded": dialogIsOpen, "aria-label": t('aria/Open Reaction Selector'), className: 'str-chat__message-reactions-button', "data-testid": 'message-reaction-action', onClick: () => dialog?.toggle(), ref: buttonRef },
|
|
21
21
|
React.createElement(ReactionIcon, { className: 'str-chat__message-action-icon' }))));
|
|
22
22
|
};
|
|
@@ -47,6 +47,8 @@ export type ComponentContextValue<StreamChatGenerics extends DefaultStreamChatGe
|
|
|
47
47
|
LoadingIndicator?: React.ComponentType<LoadingIndicatorProps>;
|
|
48
48
|
/** Custom UI component to display a message in the standard `MessageList`, defaults to and accepts the same props as: [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */
|
|
49
49
|
Message?: React.ComponentType<MessageUIComponentProps<StreamChatGenerics>>;
|
|
50
|
+
/** Custom UI component for message actions popup, accepts no props, all the defaults are set within [MessageActions (unstable)](https://github.com/GetStream/stream-chat-react/blob/master/src/experimental/MessageActions/MessageActions.tsx) */
|
|
51
|
+
MessageActions?: React.ComponentType;
|
|
50
52
|
/** Custom UI component to display the contents of a bounced message modal. Usually it allows to retry, edit, or delete the message. Defaults to and accepts the same props as: [MessageBouncePrompt](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageBounce/MessageBouncePrompt.tsx) */
|
|
51
53
|
MessageBouncePrompt?: React.ComponentType<MessageBouncePromptProps>;
|
|
52
54
|
/** Custom UI component for a deleted message, defaults to and accepts same props as: [MessageDeleted](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageDeleted.tsx) */
|
|
@@ -56,7 +58,11 @@ export type ComponentContextValue<StreamChatGenerics extends DefaultStreamChatGe
|
|
|
56
58
|
MessageListNotifications?: React.ComponentType<MessageListNotificationsProps>;
|
|
57
59
|
/** Custom UI component to display a notification when scrolled up the list and new messages arrive, defaults to and accepts same props as [MessageNotification](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/MessageNotification.tsx) */
|
|
58
60
|
MessageNotification?: React.ComponentType<MessageNotificationProps>;
|
|
59
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Custom UI component for message options popup, defaults to and accepts same props as: [MessageOptions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageOptions.tsx)
|
|
63
|
+
*
|
|
64
|
+
* @deprecated Use MessageActions property instead.
|
|
65
|
+
*/
|
|
60
66
|
MessageOptions?: React.ComponentType<MessageOptionsProps<StreamChatGenerics>>;
|
|
61
67
|
/** Custom UI component to display message replies, defaults to and accepts same props as: [MessageRepliesCountButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageRepliesCountButton.tsx) */
|
|
62
68
|
MessageRepliesCountButton?: React.ComponentType<MessageRepliesCountButtonProps>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MESSAGE_ACTIONS } from '../../components';
|
|
3
|
+
export type MessageActionSetItem = {
|
|
4
|
+
Component: React.ComponentType;
|
|
5
|
+
placement: 'quick' | 'dropdown';
|
|
6
|
+
type: keyof typeof MESSAGE_ACTIONS | (string & {});
|
|
7
|
+
};
|
|
8
|
+
export type MessageActionsProps = {
|
|
9
|
+
disableBaseMessageActionSetFilter?: boolean;
|
|
10
|
+
messageActionSet?: MessageActionSetItem[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A new actions component to replace current `MessageOptions` component.
|
|
14
|
+
* Exports from `stream-chat-react/experimental` __MIGHT__ change - use with caution
|
|
15
|
+
* and follow release notes in case you notice unexpected behavior.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MessageActions: ({ disableBaseMessageActionSetFilter, messageActionSet, }: MessageActionsProps) => React.JSX.Element | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* eslint-disable sort-keys */
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { useChatContext, useMessageContext, useTranslationContext } from '../../context';
|
|
5
|
+
import { ActionsIcon } from '../../components/Message/icons';
|
|
6
|
+
import { DialogAnchor, useDialog, useDialogIsOpen } from '../../components/Dialog';
|
|
7
|
+
import { MessageActionsWrapper } from '../../components/MessageActions/MessageActions';
|
|
8
|
+
import { useBaseMessageActionSetFilter, useSplitMessageActionSet } from './hooks';
|
|
9
|
+
import { defaultMessageActionSet } from './defaults';
|
|
10
|
+
// TODO: allow passing down customWrapperClass
|
|
11
|
+
/**
|
|
12
|
+
* A new actions component to replace current `MessageOptions` component.
|
|
13
|
+
* Exports from `stream-chat-react/experimental` __MIGHT__ change - use with caution
|
|
14
|
+
* and follow release notes in case you notice unexpected behavior.
|
|
15
|
+
*/
|
|
16
|
+
export const MessageActions = ({ disableBaseMessageActionSetFilter = false, messageActionSet = defaultMessageActionSet, }) => {
|
|
17
|
+
const { theme } = useChatContext();
|
|
18
|
+
const { isMyMessage, message } = useMessageContext();
|
|
19
|
+
const { t } = useTranslationContext();
|
|
20
|
+
const [actionsBoxButtonElement, setActionsBoxButtonElement] = useState(null);
|
|
21
|
+
const filteredMessageActionSet = useBaseMessageActionSetFilter(messageActionSet, disableBaseMessageActionSetFilter);
|
|
22
|
+
const { dropdownActionSet, quickActionSet } = useSplitMessageActionSet(filteredMessageActionSet);
|
|
23
|
+
const dropdownDialogId = `message-actions--${message.id}`;
|
|
24
|
+
const reactionSelectorDialogId = `reaction-selector--${message.id}`;
|
|
25
|
+
const dialog = useDialog({ id: dropdownDialogId });
|
|
26
|
+
const dropdownDialogIsOpen = useDialogIsOpen(dropdownDialogId);
|
|
27
|
+
const reactionSelectorDialogIsOpen = useDialogIsOpen(reactionSelectorDialogId);
|
|
28
|
+
// do not render anything if total action count is zero
|
|
29
|
+
if (dropdownActionSet.length + quickActionSet.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return (React.createElement("div", { className: clsx(`str-chat__message-${theme}__actions str-chat__message-options`, {
|
|
33
|
+
'str-chat__message-options--active': dropdownDialogIsOpen || reactionSelectorDialogIsOpen,
|
|
34
|
+
}) },
|
|
35
|
+
dropdownActionSet.length > 0 && (React.createElement(MessageActionsWrapper, { inline: false, toggleOpen: dialog?.toggle },
|
|
36
|
+
React.createElement("button", { "aria-expanded": dropdownDialogIsOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Message Actions Menu'), className: 'str-chat__message-actions-box-button', "data-testid": 'message-actions-toggle-button', ref: setActionsBoxButtonElement },
|
|
37
|
+
React.createElement(ActionsIcon, { className: 'str-chat__message-action-icon' })),
|
|
38
|
+
React.createElement(DialogAnchor, { id: dropdownDialogId, placement: isMyMessage() ? 'top-end' : 'top-start', referenceElement: actionsBoxButtonElement, trapFocus: true },
|
|
39
|
+
React.createElement(DropdownBox, { open: dropdownDialogIsOpen }, dropdownActionSet.map(({ Component: DropdownActionComponent, type }) => (React.createElement(DropdownActionComponent, { key: type }))))))),
|
|
40
|
+
quickActionSet.map(({ Component: QuickActionComponent, type }) => (React.createElement(QuickActionComponent, { key: type })))));
|
|
41
|
+
};
|
|
42
|
+
const DropdownBox = ({ children, open }) => {
|
|
43
|
+
const { t } = useTranslationContext();
|
|
44
|
+
return (React.createElement("div", { className: clsx('str-chat__message-actions-box', {
|
|
45
|
+
'str-chat__message-actions-box--open': open,
|
|
46
|
+
}) },
|
|
47
|
+
React.createElement("div", { "aria-label": t('aria/Message Options'), className: 'str-chat__message-actions-list', role: 'listbox' }, children)));
|
|
48
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import type { MessageActionSetItem } from './MessageActions';
|
|
4
|
+
export declare const DefaultDropdownActionButton: ({ "aria-selected": ariaSelected, children, className, role, ...rest }: ComponentPropsWithoutRef<'button'>) => React.JSX.Element;
|
|
5
|
+
export declare const defaultMessageActionSet: MessageActionSetItem[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* eslint-disable sort-keys */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { isUserMuted } from '../../components';
|
|
4
|
+
import { ReactionIcon as DefaultReactionIcon, ThreadIcon } from '../../components/Message/icons';
|
|
5
|
+
import { ReactionSelectorWithButton } from '../../components/Reactions/ReactionSelectorWithButton';
|
|
6
|
+
import { useChannelActionContext, useChatContext, useMessageContext, useTranslationContext, } from '../../context';
|
|
7
|
+
export const DefaultDropdownActionButton = ({ 'aria-selected': ariaSelected = 'false', children, className = 'str-chat__message-actions-list-item-button', role = 'option', ...rest }) => (React.createElement("button", { "aria-selected": ariaSelected, className: className, role: role, ...rest }, children));
|
|
8
|
+
const DefaultMessageActionComponents = {
|
|
9
|
+
dropdown: {
|
|
10
|
+
Quote() {
|
|
11
|
+
const { setQuotedMessage } = useChannelActionContext();
|
|
12
|
+
const { message } = useMessageContext();
|
|
13
|
+
const { t } = useTranslationContext();
|
|
14
|
+
const handleQuote = () => {
|
|
15
|
+
setQuotedMessage(message);
|
|
16
|
+
const elements = message.parent_id
|
|
17
|
+
? document.querySelectorAll('.str-chat__thread .str-chat__textarea__textarea')
|
|
18
|
+
: document.getElementsByClassName('str-chat__textarea__textarea');
|
|
19
|
+
const textarea = elements.item(0);
|
|
20
|
+
if (textarea instanceof HTMLTextAreaElement) {
|
|
21
|
+
textarea.focus();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return (React.createElement(DefaultDropdownActionButton, { onClick: handleQuote }, t('Quote')));
|
|
25
|
+
},
|
|
26
|
+
Pin() {
|
|
27
|
+
const { handlePin, message } = useMessageContext();
|
|
28
|
+
const { t } = useTranslationContext();
|
|
29
|
+
return (React.createElement(DefaultDropdownActionButton, { onClick: handlePin }, !message.pinned ? t('Pin') : t('Unpin')));
|
|
30
|
+
},
|
|
31
|
+
MarkUnread() {
|
|
32
|
+
const { handleMarkUnread } = useMessageContext();
|
|
33
|
+
const { t } = useTranslationContext();
|
|
34
|
+
return (React.createElement(DefaultDropdownActionButton, { onClick: handleMarkUnread }, t('Mark as unread')));
|
|
35
|
+
},
|
|
36
|
+
Flag() {
|
|
37
|
+
const { handleFlag } = useMessageContext();
|
|
38
|
+
const { t } = useTranslationContext();
|
|
39
|
+
return (React.createElement(DefaultDropdownActionButton, { onClick: handleFlag }, t('Flag')));
|
|
40
|
+
},
|
|
41
|
+
Mute() {
|
|
42
|
+
const { handleMute, message } = useMessageContext();
|
|
43
|
+
const { mutes } = useChatContext();
|
|
44
|
+
const { t } = useTranslationContext();
|
|
45
|
+
return (React.createElement(DefaultDropdownActionButton, { onClick: handleMute }, isUserMuted(message, mutes) ? t('Unmute') : t('Mute')));
|
|
46
|
+
},
|
|
47
|
+
Edit() {
|
|
48
|
+
const { handleEdit } = useMessageContext();
|
|
49
|
+
const { t } = useTranslationContext();
|
|
50
|
+
return (React.createElement(DefaultDropdownActionButton, { onClick: handleEdit }, t('Edit Message')));
|
|
51
|
+
},
|
|
52
|
+
Delete() {
|
|
53
|
+
const { handleDelete } = useMessageContext();
|
|
54
|
+
const { t } = useTranslationContext();
|
|
55
|
+
return (React.createElement(DefaultDropdownActionButton, { onClick: handleDelete }, t('Delete')));
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
quick: {
|
|
59
|
+
React() {
|
|
60
|
+
return React.createElement(ReactionSelectorWithButton, { ReactionIcon: DefaultReactionIcon });
|
|
61
|
+
},
|
|
62
|
+
Reply() {
|
|
63
|
+
const { handleOpenThread } = useMessageContext();
|
|
64
|
+
const { t } = useTranslationContext();
|
|
65
|
+
return (React.createElement("button", { "aria-label": t('aria/Open Thread'), className: 'str-chat__message-reply-in-thread-button', "data-testid": 'thread-action', onClick: handleOpenThread },
|
|
66
|
+
React.createElement(ThreadIcon, { className: 'str-chat__message-action-icon' })));
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
export const defaultMessageActionSet = [
|
|
71
|
+
// { placement: 'dropdown', type: 'block' },
|
|
72
|
+
{ Component: DefaultMessageActionComponents.quick.Reply, placement: 'quick', type: 'reply' },
|
|
73
|
+
{ Component: DefaultMessageActionComponents.quick.React, placement: 'quick', type: 'react' },
|
|
74
|
+
{
|
|
75
|
+
Component: DefaultMessageActionComponents.dropdown.Delete,
|
|
76
|
+
placement: 'dropdown',
|
|
77
|
+
type: 'delete',
|
|
78
|
+
},
|
|
79
|
+
{ Component: DefaultMessageActionComponents.dropdown.Edit, placement: 'dropdown', type: 'edit' },
|
|
80
|
+
{ Component: DefaultMessageActionComponents.dropdown.Mute, placement: 'dropdown', type: 'mute' },
|
|
81
|
+
{ Component: DefaultMessageActionComponents.dropdown.Flag, placement: 'dropdown', type: 'flag' },
|
|
82
|
+
{ Component: DefaultMessageActionComponents.dropdown.Pin, placement: 'dropdown', type: 'pin' },
|
|
83
|
+
{
|
|
84
|
+
Component: DefaultMessageActionComponents.dropdown.Quote,
|
|
85
|
+
placement: 'dropdown',
|
|
86
|
+
type: 'quote',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
Component: DefaultMessageActionComponents.dropdown.MarkUnread,
|
|
90
|
+
placement: 'dropdown',
|
|
91
|
+
type: 'markUnread',
|
|
92
|
+
},
|
|
93
|
+
];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MessageActionSetItem } from '../MessageActions';
|
|
2
|
+
/**
|
|
3
|
+
* Base filter hook which covers actions of type `delete`, `edit`,
|
|
4
|
+
* `flag`, `markUnread`, `mute`, `quote`, `react` and `reply`, whether
|
|
5
|
+
* the rendered message is a reply (replies are limited to certain actions) and
|
|
6
|
+
* whether the message has appropriate type and status.
|
|
7
|
+
*/
|
|
8
|
+
export declare const useBaseMessageActionSetFilter: (messageActionSet: MessageActionSetItem[], disable?: boolean) => MessageActionSetItem[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { ACTIONS_NOT_WORKING_IN_THREAD, useUserRole } from '../../../components';
|
|
3
|
+
import { useMessageContext } from '../../../context';
|
|
4
|
+
/**
|
|
5
|
+
* Base filter hook which covers actions of type `delete`, `edit`,
|
|
6
|
+
* `flag`, `markUnread`, `mute`, `quote`, `react` and `reply`, whether
|
|
7
|
+
* the rendered message is a reply (replies are limited to certain actions) and
|
|
8
|
+
* whether the message has appropriate type and status.
|
|
9
|
+
*/
|
|
10
|
+
export const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
|
|
11
|
+
const { initialMessage: isInitialMessage, message } = useMessageContext();
|
|
12
|
+
const { canDelete, canEdit, canFlag, canMarkUnread, canMute, canQuote, canReact, canReply, } = useUserRole(message);
|
|
13
|
+
const isMessageThreadReply = typeof message.parent_id === 'string';
|
|
14
|
+
return useMemo(() => {
|
|
15
|
+
if (disable)
|
|
16
|
+
return messageActionSet;
|
|
17
|
+
// filter out all actions if any of these are true
|
|
18
|
+
if (isInitialMessage || // not sure whether this thing even works anymore
|
|
19
|
+
!message.type ||
|
|
20
|
+
message.type === 'error' ||
|
|
21
|
+
message.type === 'system' ||
|
|
22
|
+
message.type === 'ephemeral' ||
|
|
23
|
+
message.status === 'failed' ||
|
|
24
|
+
message.status === 'sending')
|
|
25
|
+
return [];
|
|
26
|
+
return messageActionSet.filter(({ type }) => {
|
|
27
|
+
// filter out actions with types that do not work in thread
|
|
28
|
+
if (ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply)
|
|
29
|
+
return false;
|
|
30
|
+
if ((type === 'delete' && !canDelete) ||
|
|
31
|
+
(type === 'edit' && !canEdit) ||
|
|
32
|
+
(type === 'flag' && !canFlag) ||
|
|
33
|
+
(type === 'markUnread' && !canMarkUnread) ||
|
|
34
|
+
(type === 'mute' && !canMute) ||
|
|
35
|
+
(type === 'quote' && !canQuote) ||
|
|
36
|
+
(type === 'react' && !canReact) ||
|
|
37
|
+
(type === 'reply' && !canReply))
|
|
38
|
+
return false;
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
}, [
|
|
42
|
+
canDelete,
|
|
43
|
+
canEdit,
|
|
44
|
+
canFlag,
|
|
45
|
+
canMarkUnread,
|
|
46
|
+
canMute,
|
|
47
|
+
canQuote,
|
|
48
|
+
canReact,
|
|
49
|
+
canReply,
|
|
50
|
+
isInitialMessage,
|
|
51
|
+
isMessageThreadReply,
|
|
52
|
+
message.status,
|
|
53
|
+
message.type,
|
|
54
|
+
disable,
|
|
55
|
+
messageActionSet,
|
|
56
|
+
]);
|
|
57
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
export const useSplitMessageActionSet = (messageActionSet) => useMemo(() => {
|
|
3
|
+
const quickActionSet = [];
|
|
4
|
+
const dropdownActionSet = [];
|
|
5
|
+
for (const action of messageActionSet) {
|
|
6
|
+
if (action.placement === 'quick')
|
|
7
|
+
quickActionSet.push(action);
|
|
8
|
+
if (action.placement === 'dropdown')
|
|
9
|
+
dropdownActionSet.push(action);
|
|
10
|
+
}
|
|
11
|
+
return { dropdownActionSet, quickActionSet };
|
|
12
|
+
}, [messageActionSet]);
|