From 0f2c84193801f9502b944ad5d213baa77941d497 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista <tasso.evangelista@rocket.chat> Date: Mon, 31 Jul 2023 10:01:04 -0300 Subject: [PATCH] refactor: Room toolbox quirks (#29959) --- .../app/reactions/client/{init.js => init.ts} | 6 +- .../app/ui-utils/client/lib/MessageAction.ts | 152 ++++-------------- .../client/lib/messageActionDefault.ts | 5 +- .../app/lib/CommonRoomTemplateInstance.ts | 12 -- ...boxHolder.tsx => MessageToolboxHolder.tsx} | 25 +-- .../{Toolbox.tsx => MessageToolbox.tsx} | 36 ++--- .../message/variants/RoomMessage.tsx | 4 +- .../message/variants/ThreadMessage.tsx | 4 +- apps/meteor/client/lib/getPermaLink.ts | 39 +++++ .../client/lib/utils/legacyJumpToMessage.ts | 4 - .../meteor/client/lib/utils/prependReplies.ts | 4 +- .../startup/actionButtons/permalinkPinned.ts | 3 +- .../startup/actionButtons/permalinkStar.ts | 3 +- .../moderation/ModerationConsolePage.tsx | 4 +- .../contactHistory/ContactHistory.tsx | 8 +- .../contextualBar/CallsContextualBarRoom.tsx | 7 +- .../contextualBar/ChatsContextualBar.tsx | 2 +- .../contextualBar/ContactsContextualBar.tsx | 2 +- .../outlookCalendar/OutlookEventsRoute.tsx | 2 +- .../Omnichannel/OmnichannelRoomHeader.tsx | 17 +- .../Header/Omnichannel/VoipRoomHeader.tsx | 17 +- .../room/Header/RoomToolbox/RoomToolbox.tsx | 81 +++++----- .../client/views/room/MemberListRouter.js | 5 +- apps/meteor/client/views/room/Room/Room.tsx | 11 +- .../views/room/components/body/RoomBody.tsx | 9 +- .../contextualBar/MessageListTab.tsx | 2 +- .../views/room/contexts/RoomAPIContext.ts | 5 - .../views/room/contexts/RoomToolboxContext.ts | 25 +++ .../views/room/contexts/ToolboxContext.ts | 35 ---- .../room/contextualBar/Apps/AppsWithData.tsx | 2 +- .../AutoTranslate/AutoTranslateWithData.tsx | 2 +- .../Discussions/DiscussionsListContextBar.tsx | 2 +- .../ExportMessages/ExportMessages.tsx | 2 +- .../Info/EditRoomInfo/EditChannelWithData.js | 2 +- .../contextualBar/Info/RoomInfoRouter.tsx | 2 +- .../KeyboardShortcuts.stories.tsx | 2 +- .../KeyboardShortcutsWithData.tsx | 9 +- .../MessageSearchTab/MessageSearchTab.tsx | 11 +- .../NotificationPreferencesWithData.tsx | 2 +- .../room/contextualBar/OTR/OTRWithData.tsx | 2 +- .../PruneMessages/PruneMessagesWithData.tsx | 19 +-- .../RoomFiles/RoomFilesWithData.js | 2 +- .../RoomMembers/AddUsers/AddUsersWithData.tsx | 2 +- .../InviteUsers/InviteUsersWithData.tsx | 2 +- .../RoomMembers/RoomMembersWithData.tsx | 2 +- .../room/contextualBar/Threads/Thread.tsx | 2 +- .../room/contextualBar/Threads/ThreadList.tsx | 2 +- .../room/contextualBar/Threads/Threads.tsx | 4 +- .../Threads/components/ThreadChat.tsx | 2 +- .../VideoConfList/VideoConfListWithData.tsx | 2 +- .../client/views/room/lib/Toolbox/index.tsx | 46 ++---- .../views/room/providers/RoomProvider.tsx | 15 +- .../room/providers/RoomToolboxProvider.tsx | 101 ++++++------ .../views/room/providers/VirtualAction.tsx | 56 ------- .../providers/hooks/useCoreRoomActions.ts | 6 +- .../views/room/providers/hooks/useUserCard.ts | 2 +- .../contextualBar/channels/TeamsChannels.tsx | 2 +- .../contextualBar/info/TeamsInfoWithData.js | 2 +- .../ee/client/apps/gameCenter/GameCenter.tsx | 2 +- .../CannedResponse/CannedResponseList.tsx | 4 +- .../contextualBar/CannedResponse/index.tsx | 8 +- .../views/room/MessageList/Message.spec.tsx | 2 +- 62 files changed, 320 insertions(+), 532 deletions(-) rename apps/meteor/app/reactions/client/{init.js => init.ts} (83%) delete mode 100644 apps/meteor/app/ui/client/views/app/lib/CommonRoomTemplateInstance.ts rename apps/meteor/client/components/message/{ToolboxHolder.tsx => MessageToolboxHolder.tsx} (64%) rename apps/meteor/client/components/message/toolbox/{Toolbox.tsx => MessageToolbox.tsx} (82%) create mode 100644 apps/meteor/client/lib/getPermaLink.ts delete mode 100644 apps/meteor/client/views/room/contexts/RoomAPIContext.ts create mode 100644 apps/meteor/client/views/room/contexts/RoomToolboxContext.ts delete mode 100644 apps/meteor/client/views/room/contexts/ToolboxContext.ts delete mode 100644 apps/meteor/client/views/room/providers/VirtualAction.tsx diff --git a/apps/meteor/app/reactions/client/init.js b/apps/meteor/app/reactions/client/init.ts similarity index 83% rename from apps/meteor/app/reactions/client/init.js rename to apps/meteor/app/reactions/client/init.ts index de690b66810..b670b5c6d41 100644 --- a/apps/meteor/app/reactions/client/init.js +++ b/apps/meteor/app/reactions/client/init.ts @@ -12,9 +12,9 @@ Meteor.startup(() => { label: 'Add_Reaction', context: ['message', 'message-mobile', 'threads', 'federated'], action(event, props) { - event.stopPropagation(); const { message = messageArgs(this).msg, chat } = props; - chat?.emojiPicker.open(event.currentTarget, (emoji) => sdk.call('setReaction', `:${emoji}:`, message._id)); + event.stopPropagation(); + chat?.emojiPicker.open(event.currentTarget! as Element, (emoji) => sdk.call('setReaction', `:${emoji}:`, message._id)); }, condition({ message, user, room, subscription }) { if (!room) { @@ -29,7 +29,7 @@ Meteor.startup(() => { return false; } - if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) { + if (roomCoordinator.readOnly(room._id, user!) && !room.reactWhenReadOnly) { return false; } const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 18687dae44c..a0a350adc77 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -1,29 +1,15 @@ -import type { IMessage, IUser, ISubscription, IRoom, SettingValue, Serialized, ITranslatedMessage } from '@rocket.chat/core-typings'; +import type { IMessage, IUser, ISubscription, IRoom, SettingValue, ITranslatedMessage } from '@rocket.chat/core-typings'; import type { Keys as IconName } from '@rocket.chat/icons'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import mem from 'mem'; -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Tracker } from 'meteor/tracker'; import type { ContextType } from 'react'; -import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; import type { AutoTranslateOptions } from '../../../../client/views/room/MessageList/hooks/useAutoTranslate'; import type { ChatContext } from '../../../../client/views/room/contexts/ChatContext'; -import type { ToolboxContextValue } from '../../../../client/views/room/contexts/ToolboxContext'; -import { Messages, ChatRoom, Subscriptions } from '../../../models/client'; -import { sdk } from '../../../utils/client/lib/SDKClient'; - -const getMessage = async (msgId: string): Promise<Serialized<IMessage> | null> => { - try { - const { message } = await sdk.rest.get('/v1/chat.getMessage', { msgId }); - return message; - } catch { - return null; - } -}; +import type { RoomToolboxContextValue } from '../../../../client/views/room/contexts/RoomToolboxContext'; type MessageActionGroup = 'message' | 'menu'; + export type MessageActionContext = | 'message' | 'threads' @@ -58,7 +44,8 @@ export type MessageActionConfig = { group?: MessageActionGroup | MessageActionGroup[]; context?: MessageActionContext[]; action: ( - e: Pick<Event, 'preventDefault' | 'stopPropagation'>, + this: any, + e: Pick<Event, 'preventDefault' | 'stopPropagation' | 'currentTarget'>, { message, tabbar, @@ -67,7 +54,7 @@ export type MessageActionConfig = { autoTranslateOptions, }: { message: IMessage & Partial<ITranslatedMessage>; - tabbar: ToolboxContextValue; + tabbar: RoomToolboxContextValue; room?: IRoom; chat: ContextType<typeof ChatContext>; autoTranslateOptions?: AutoTranslateOptions; @@ -76,23 +63,10 @@ export type MessageActionConfig = { condition?: (props: MessageActionConditionProps) => Promise<boolean> | boolean; }; -type MessageActionConfigList = MessageActionConfig[]; - -export const MessageAction = new (class { - /* - config expects the following keys (only id is mandatory): - id (mandatory) - icon: string - label: string - action: function(event, instance) - condition: function(message) - order: integer - group: string (message or menu) - */ +class MessageAction { + public buttons: Record<MessageActionConfig['id'], MessageActionConfig> = {}; - buttons = new ReactiveVar<Record<string, MessageActionConfig>>({}); - - addButton(config: MessageActionConfig): void { + public addButton(config: MessageActionConfig): void { if (!config?.id) { return; } @@ -105,106 +79,34 @@ export const MessageAction = new (class { config.condition = mem(config.condition, { maxAge: 1000, cacheKey: JSON.stringify }); } - return Tracker.nonreactive(() => { - const btns = this.buttons.get(); - btns[config.id] = config; - mem.clear(this._getButtons); - mem.clear(this.getButtonsByGroup); - return this.buttons.set(btns); - }); - } - - removeButton(id: MessageActionConfig['id']): void { - return Tracker.nonreactive(() => { - const btns = this.buttons.get(); - delete btns[id]; - return this.buttons.set(btns); - }); + this.buttons[config.id] = config; } - updateButton(id: MessageActionConfig['id'], config: MessageActionConfig): void { - return Tracker.nonreactive(() => { - const btns = this.buttons.get(); - if (btns[id]) { - btns[id] = Object.assign(btns[id], config); - return this.buttons.set(btns); - } - }); + public removeButton(id: MessageActionConfig['id']): void { + delete this.buttons[id]; } - getButtonById(id: MessageActionConfig['id']): MessageActionConfig | undefined { - const allButtons = this.buttons.get(); - return allButtons[id]; - } - - _getButtons = mem((): MessageActionConfigList => Object.values(this.buttons.get()).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), { - maxAge: 1000, - }); - - async getButtonsByCondition( - prop: MessageActionConditionProps, - arr: MessageActionConfigList = MessageAction._getButtons(), - ): Promise<MessageActionConfigList> { + public async getAll( + props: MessageActionConditionProps, + context: MessageActionContext, + group: MessageActionGroup, + ): Promise<MessageActionConfig[]> { return ( await Promise.all( - arr.map(async (button) => { - return [button, !button.condition || (await button.condition(prop))] as const; - }), + Object.values(this.buttons) + .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) + .filter((button) => !button.group || (Array.isArray(button.group) ? button.group.includes(group) : button.group === group)) + .filter((button) => !button.context || button.context.includes(context)) + .map(async (button) => { + return [button, !button.condition || (await button.condition({ ...props, context }))] as const; + }), ) ) .filter(([, condition]) => condition) .map(([button]) => button); } +} - getButtonsByGroup = mem( - (group: MessageActionGroup, arr: MessageActionConfigList = MessageAction._getButtons()): MessageActionConfigList => { - return arr.filter((button) => !button.group || (Array.isArray(button.group) ? button.group.includes(group) : button.group === group)); - }, - { maxAge: 1000 }, - ); - - getButtonsByContext(context: MessageActionContext, arr: MessageActionConfigList): MessageActionConfigList { - return !context ? arr : arr.filter((button) => !button.context || button.context.includes(context)); - } +const instance = new MessageAction(); - async getButtons( - props: MessageActionConditionProps, - context: MessageActionContext, - group: MessageActionGroup, - ): Promise<MessageActionConfigList> { - const allButtons = group ? this.getButtonsByGroup(group) : MessageAction._getButtons(); - - if (props.message) { - return this.getButtonsByCondition({ ...props, context }, this.getButtonsByContext(context, allButtons)); - } - return allButtons; - } - - resetButtons(): void { - mem.clear(this._getButtons); - mem.clear(this.getButtonsByGroup); - return this.buttons.set({}); - } - - async getPermaLink(msgId: string): Promise<string> { - if (!msgId) { - throw new Error('invalid-parameter'); - } - - const msg = Messages.findOne(msgId) || (await getMessage(msgId)); - if (!msg) { - throw new Error('message-not-found'); - } - const roomData = ChatRoom.findOne({ - _id: msg.rid, - }); - - if (!roomData) { - throw new Error('room-not-found'); - } - - const subData = Subscriptions.findOne({ 'rid': roomData._id, 'u._id': Meteor.userId() }); - const roomURL = roomCoordinator.getURL(roomData.t, { ...(subData || roomData), tab: '' }); - return `${roomURL}?msg=${msgId}`; - } -})(); +export { instance as MessageAction }; diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts index f7cd1c482e3..e2c216bc5f3 100644 --- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts +++ b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts @@ -3,6 +3,7 @@ import { isRoomFederated } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; import moment from 'moment'; +import { getPermaLink } from '../../../../client/lib/getPermaLink'; import { imperativeModal } from '../../../../client/lib/imperativeModal'; import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; import { dispatchToastMessage } from '../../../../client/lib/toast'; @@ -70,7 +71,7 @@ Meteor.startup(async () => { context: ['message', 'message-mobile', 'threads'], async action(_, props) { const { message = messageArgs(this).msg } = props; - const permalink = await MessageAction.getPermaLink(message._id); + const permalink = await getPermaLink(message._id); imperativeModal.open({ component: ShareMessageModal, props: { @@ -123,7 +124,7 @@ Meteor.startup(async () => { async action(_, props) { try { const { message = messageArgs(this).msg } = props; - const permalink = await MessageAction.getPermaLink(message._id); + const permalink = await getPermaLink(message._id); await navigator.clipboard.writeText(permalink); dispatchToastMessage({ type: 'success', message: t('Copied') }); } catch (e) { diff --git a/apps/meteor/app/ui/client/views/app/lib/CommonRoomTemplateInstance.ts b/apps/meteor/app/ui/client/views/app/lib/CommonRoomTemplateInstance.ts deleted file mode 100644 index 2dddb429935..00000000000 --- a/apps/meteor/app/ui/client/views/app/lib/CommonRoomTemplateInstance.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ContextType } from 'react'; - -import type { ChatContext } from '../../../../../../client/views/room/contexts/ChatContext'; -import type { ToolboxContextValue } from '../../../../../../client/views/room/contexts/ToolboxContext'; - -export type CommonRoomTemplateInstance = { - data: { - rid: string; - tabBar: ToolboxContextValue; - chatContext: ContextType<typeof ChatContext>; - }; -}; diff --git a/apps/meteor/client/components/message/ToolboxHolder.tsx b/apps/meteor/client/components/message/MessageToolboxHolder.tsx similarity index 64% rename from apps/meteor/client/components/message/ToolboxHolder.tsx rename to apps/meteor/client/components/message/MessageToolboxHolder.tsx index 0b5199d29eb..54f4ebab823 100644 --- a/apps/meteor/client/components/message/ToolboxHolder.tsx +++ b/apps/meteor/client/components/message/MessageToolboxHolder.tsx @@ -2,19 +2,20 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { MessageToolboxWrapper } from '@rocket.chat/fuselage'; import { useQuery } from '@tanstack/react-query'; import type { ReactElement } from 'react'; -import React, { memo, useRef } from 'react'; +import React, { Suspense, lazy, memo, useRef } from 'react'; import type { MessageActionContext } from '../../../app/ui-utils/client/lib/MessageAction'; import { useChat } from '../../views/room/contexts/ChatContext'; import { useIsVisible } from '../../views/room/hooks/useIsVisible'; -import Toolbox from './toolbox/Toolbox'; -type ToolboxHolderProps = { +type MessageToolboxHolderProps = { message: IMessage; context?: MessageActionContext; }; -const ToolboxHolder = ({ message, context }: ToolboxHolderProps): ReactElement => { +const MessageToolbox = lazy(() => import('./toolbox/MessageToolbox')); + +const MessageToolboxHolder = ({ message, context }: MessageToolboxHolderProps): ReactElement => { const ref = useRef(null); const [visible] = useIsVisible(ref); @@ -33,15 +34,17 @@ const ToolboxHolder = ({ message, context }: ToolboxHolderProps): ReactElement = return ( <MessageToolboxWrapper ref={ref}> {visible && depsQueryResult.isSuccess && depsQueryResult.data.room && ( - <Toolbox - message={message} - messageContext={context} - room={depsQueryResult.data.room} - subscription={depsQueryResult.data.subscription} - /> + <Suspense fallback={null}> + <MessageToolbox + message={message} + messageContext={context} + room={depsQueryResult.data.room} + subscription={depsQueryResult.data.subscription} + /> + </Suspense> )} </MessageToolboxWrapper> ); }; -export default memo(ToolboxHolder); +export default memo(MessageToolboxHolder); diff --git a/apps/meteor/client/components/message/toolbox/Toolbox.tsx b/apps/meteor/client/components/message/toolbox/MessageToolbox.tsx similarity index 82% rename from apps/meteor/client/components/message/toolbox/Toolbox.tsx rename to apps/meteor/client/components/message/toolbox/MessageToolbox.tsx index f957c75a81c..efdfc5b0b49 100644 --- a/apps/meteor/client/components/message/toolbox/Toolbox.tsx +++ b/apps/meteor/client/components/message/toolbox/MessageToolbox.tsx @@ -1,6 +1,6 @@ import type { IMessage, IRoom, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings'; import { isThreadMessage, isRoomFederated } from '@rocket.chat/core-typings'; -import { MessageToolbox, MessageToolboxItem } from '@rocket.chat/fuselage'; +import { MessageToolbox as FuselageMessageToolbox, MessageToolboxItem } from '@rocket.chat/fuselage'; import { useFeaturePreview } from '@rocket.chat/ui-client'; import { useUser, useSettings, useTranslation, useMethod } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; @@ -15,7 +15,7 @@ import EmojiElement from '../../../views/composer/EmojiPicker/EmojiElement'; import { useIsSelecting } from '../../../views/room/MessageList/contexts/SelectedMessagesContext'; import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate'; import { useChat } from '../../../views/room/contexts/ChatContext'; -import { useToolboxContext } from '../../../views/room/contexts/ToolboxContext'; +import { useRoomToolbox } from '../../../views/room/contexts/RoomToolboxContext'; import MessageActionMenu from './MessageActionMenu'; const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => { @@ -38,16 +38,16 @@ const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActi return 'message'; }; -type ToolboxProps = { +type MessageToolboxProps = { message: IMessage & Partial<ITranslatedMessage>; messageContext?: MessageActionContext; room: IRoom; subscription?: ISubscription; }; -const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): ReactElement | null => { +const MessageToolbox = ({ message, messageContext, room, subscription }: MessageToolboxProps): ReactElement | null => { const t = useTranslation(); - const user = useUser(); + const user = useUser() ?? undefined; const settings = useSettings(); const quickReactionsEnabled = useFeaturePreview('quickReactions'); @@ -64,21 +64,15 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): const actionButtonApps = useMessageActionAppsActionButtons(context); const actionsQueryResult = useQuery(['rooms', room._id, 'messages', message._id, 'actions'] as const, async () => { - const messageActions = await MessageAction.getButtons( - { message, room, user: user ?? undefined, subscription, settings: mapSettings, chat }, - context, - 'message', - ); - const menuActions = await MessageAction.getButtons( - { message, room, user: user ?? undefined, subscription, settings: mapSettings, chat }, - context, - 'menu', - ); - - return { message: messageActions, menu: menuActions }; + const props = { message, room, user, subscription, settings: mapSettings, chat }; + + const toolboxItems = await MessageAction.getAll(props, context, 'message'); + const menuItems = await MessageAction.getAll(props, context, 'menu'); + + return { message: toolboxItems, menu: menuItems }; }); - const toolbox = useToolboxContext(); + const toolbox = useRoomToolbox(); const selecting = useIsSelecting(); @@ -96,7 +90,7 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): }; return ( - <MessageToolbox> + <FuselageMessageToolbox> {quickReactionsEnabled && isReactionAllowed && quickReactions.slice(0, 3).map(({ emoji, image }) => { @@ -122,8 +116,8 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): data-qa-type='message-action-menu-options' /> )} - </MessageToolbox> + </FuselageMessageToolbox> ); }; -export default memo(Toolbox); +export default memo(MessageToolbox); diff --git a/apps/meteor/client/components/message/variants/RoomMessage.tsx b/apps/meteor/client/components/message/variants/RoomMessage.tsx index 895cc1ec7df..2e82753051a 100644 --- a/apps/meteor/client/components/message/variants/RoomMessage.tsx +++ b/apps/meteor/client/components/message/variants/RoomMessage.tsx @@ -17,8 +17,8 @@ import { useJumpToMessage } from '../../../views/room/MessageList/hooks/useJumpT import { useChat } from '../../../views/room/contexts/ChatContext'; import IgnoredContent from '../IgnoredContent'; import MessageHeader from '../MessageHeader'; +import MessageToolboxHolder from '../MessageToolboxHolder'; import StatusIndicators from '../StatusIndicators'; -import ToolboxHolder from '../ToolboxHolder'; import MessageAvatar from '../header/MessageAvatar'; import RoomMessageContent from './room/RoomMessageContent'; @@ -104,7 +104,7 @@ const RoomMessage = ({ <RoomMessageContent message={message} unread={unread} mention={mention} all={all} searchText={searchText} /> )} </MessageContainer> - {!message.private && <ToolboxHolder message={message} context={context} />} + {!message.private && <MessageToolboxHolder message={message} context={context} />} </Message> ); }; diff --git a/apps/meteor/client/components/message/variants/ThreadMessage.tsx b/apps/meteor/client/components/message/variants/ThreadMessage.tsx index 565e90cf5a8..804b70ba355 100644 --- a/apps/meteor/client/components/message/variants/ThreadMessage.tsx +++ b/apps/meteor/client/components/message/variants/ThreadMessage.tsx @@ -10,8 +10,8 @@ import { useJumpToMessage } from '../../../views/room/MessageList/hooks/useJumpT import { useChat } from '../../../views/room/contexts/ChatContext'; import IgnoredContent from '../IgnoredContent'; import MessageHeader from '../MessageHeader'; +import MessageToolboxHolder from '../MessageToolboxHolder'; import StatusIndicators from '../StatusIndicators'; -import ToolboxHolder from '../ToolboxHolder'; import MessageAvatar from '../header/MessageAvatar'; import ThreadMessageContent from './thread/ThreadMessageContent'; @@ -68,7 +68,7 @@ const ThreadMessage = ({ message, sequential, unread, showUserAvatar }: ThreadMe {ignored ? <IgnoredContent onShowMessageIgnored={toggleIgnoring} /> : <ThreadMessageContent message={message} />} </MessageContainer> - {!message.private && <ToolboxHolder message={message} context='threads' />} + {!message.private && <MessageToolboxHolder message={message} context='threads' />} </Message> ); }; diff --git a/apps/meteor/client/lib/getPermaLink.ts b/apps/meteor/client/lib/getPermaLink.ts new file mode 100644 index 00000000000..8c74bc7933a --- /dev/null +++ b/apps/meteor/client/lib/getPermaLink.ts @@ -0,0 +1,39 @@ +import type { IMessage, Serialized } from '@rocket.chat/core-typings'; +import { Meteor } from 'meteor/meteor'; + +const getMessage = async (msgId: string): Promise<Serialized<IMessage> | null> => { + try { + const { sdk } = await import('../../app/utils/client/lib/SDKClient'); + const { message } = await sdk.rest.get('/v1/chat.getMessage', { msgId }); + return message; + } catch { + return null; + } +}; + +export const getPermaLink = async (msgId: string): Promise<string> => { + if (!msgId) { + throw new Error('invalid-parameter'); + } + + const { ChatMessage, ChatRoom, ChatSubscription } = await import('../../app/models/client'); + + const msg = ChatMessage.findOne(msgId) || (await getMessage(msgId)); + if (!msg) { + throw new Error('message-not-found'); + } + const roomData = ChatRoom.findOne({ + _id: msg.rid, + }); + + if (!roomData) { + throw new Error('room-not-found'); + } + + const subData = ChatSubscription.findOne({ 'rid': roomData._id, 'u._id': Meteor.userId() }); + + const { roomCoordinator } = await import('./rooms/roomCoordinator'); + + const roomURL = roomCoordinator.getURL(roomData.t, { ...(subData || roomData), tab: '' }); + return `${roomURL}?msg=${msgId}`; +}; diff --git a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts index 3888e672bdc..c1fcbbbb8f3 100644 --- a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts +++ b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts @@ -9,10 +9,6 @@ import { goToRoomById } from './goToRoomById'; /** @deprecated */ export const legacyJumpToMessage = async (message: IMessage) => { - if (matchMedia('(max-width: 500px)').matches) { - (Template.instance() as any)?.tabBar?.close(); - } - if (isThreadMessage(message) || message.tcount) { const { tab, context } = router.getRouteParameters(); diff --git a/apps/meteor/client/lib/utils/prependReplies.ts b/apps/meteor/client/lib/utils/prependReplies.ts index b4a38d4d63b..47f93435124 100644 --- a/apps/meteor/client/lib/utils/prependReplies.ts +++ b/apps/meteor/client/lib/utils/prependReplies.ts @@ -1,11 +1,11 @@ import type { IMessage } from '@rocket.chat/core-typings'; -import { MessageAction } from '../../../app/ui-utils/client/lib/MessageAction'; +import { getPermaLink } from '../getPermaLink'; export const prependReplies = async (msg: string, replies: IMessage[] = []): Promise<string> => { const chunks = await Promise.all( replies.map(async ({ _id }) => { - const permalink = await MessageAction.getPermaLink(_id); + const permalink = await getPermaLink(_id); return `[ ](${permalink})`; }), diff --git a/apps/meteor/client/startup/actionButtons/permalinkPinned.ts b/apps/meteor/client/startup/actionButtons/permalinkPinned.ts index f3d67786270..118f9ff4780 100644 --- a/apps/meteor/client/startup/actionButtons/permalinkPinned.ts +++ b/apps/meteor/client/startup/actionButtons/permalinkPinned.ts @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { MessageAction } from '../../../app/ui-utils/client'; import { t } from '../../../app/utils/lib/i18n'; +import { getPermaLink } from '../../lib/getPermaLink'; import { dispatchToastMessage } from '../../lib/toast'; import { messageArgs } from '../../lib/utils/messageArgs'; @@ -14,7 +15,7 @@ Meteor.startup(() => { async action(_, props) { try { const { message = messageArgs(this).msg } = props; - const permalink = await MessageAction.getPermaLink(message._id); + const permalink = await getPermaLink(message._id); navigator.clipboard.writeText(permalink); dispatchToastMessage({ type: 'success', message: t('Copied') }); } catch (e) { diff --git a/apps/meteor/client/startup/actionButtons/permalinkStar.ts b/apps/meteor/client/startup/actionButtons/permalinkStar.ts index 303cfd5d0f9..60204ffd4e6 100644 --- a/apps/meteor/client/startup/actionButtons/permalinkStar.ts +++ b/apps/meteor/client/startup/actionButtons/permalinkStar.ts @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { MessageAction } from '../../../app/ui-utils/client'; import { t } from '../../../app/utils/lib/i18n'; +import { getPermaLink } from '../../lib/getPermaLink'; import { dispatchToastMessage } from '../../lib/toast'; import { messageArgs } from '../../lib/utils/messageArgs'; @@ -15,7 +16,7 @@ Meteor.startup(() => { async action(_, props) { try { const { message = messageArgs(this).msg } = props; - const permalink = await MessageAction.getPermaLink(message._id); + const permalink = await getPermaLink(message._id); navigator.clipboard.writeText(permalink); dispatchToastMessage({ type: 'success', message: t('Copied') }); } catch (e) { diff --git a/apps/meteor/client/views/admin/moderation/ModerationConsolePage.tsx b/apps/meteor/client/views/admin/moderation/ModerationConsolePage.tsx index 1d6433d96d9..16445c291f6 100644 --- a/apps/meteor/client/views/admin/moderation/ModerationConsolePage.tsx +++ b/apps/meteor/client/views/admin/moderation/ModerationConsolePage.tsx @@ -1,9 +1,9 @@ import { useTranslation, useRouteParameter, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client'; import { Contextualbar } from '../../../components/Contextualbar'; import Page from '../../../components/Page'; +import { getPermaLink } from '../../../lib/getPermaLink'; import MessageReportInfo from './MessageReportInfo'; import ModerationConsoleTable from './ModerationConsoleTable'; import UserMessages from './UserMessages'; @@ -16,7 +16,7 @@ const ModerationConsolePage = () => { const handleRedirect = async (mid: string) => { try { - const permalink = await MessageAction.getPermaLink(mid); + const permalink = await getPermaLink(mid); // open the permalink in same tab window.open(permalink, '_self'); } catch (error) { diff --git a/apps/meteor/client/views/omnichannel/contactHistory/ContactHistory.tsx b/apps/meteor/client/views/omnichannel/contactHistory/ContactHistory.tsx index c9a5b55e4e4..effb96cf91e 100644 --- a/apps/meteor/client/views/omnichannel/contactHistory/ContactHistory.tsx +++ b/apps/meteor/client/views/omnichannel/contactHistory/ContactHistory.tsx @@ -1,17 +1,19 @@ -import type { ReactElement } from 'react'; import React, { useState } from 'react'; +import { useRoomToolbox } from '../../room/contexts/RoomToolboxContext'; import ContactHistoryList from './ContactHistoryList'; import ContactHistoryMessagesList from './MessageList/ContactHistoryMessagesList'; -const ContactHistory = ({ tabBar: { close } }: any): ReactElement => { +const ContactHistory = () => { const [chatId, setChatId] = useState<string>(''); + const { close: closeTab } = useRoomToolbox(); + return ( <> {chatId && chatId !== '' ? ( <ContactHistoryMessagesList chatId={chatId} setChatId={setChatId} close={close} /> ) : ( - <ContactHistoryList setChatId={setChatId} close={close} /> + <ContactHistoryList setChatId={setChatId} close={closeTab} /> )} </> ); diff --git a/apps/meteor/client/views/omnichannel/directory/calls/contextualBar/CallsContextualBarRoom.tsx b/apps/meteor/client/views/omnichannel/directory/calls/contextualBar/CallsContextualBarRoom.tsx index 8da33f5424c..5cbbb1199d5 100644 --- a/apps/meteor/client/views/omnichannel/directory/calls/contextualBar/CallsContextualBarRoom.tsx +++ b/apps/meteor/client/views/omnichannel/directory/calls/contextualBar/CallsContextualBarRoom.tsx @@ -1,14 +1,15 @@ -import type { ReactElement } from 'react'; import React from 'react'; import { useVoipRoom } from '../../../../room/contexts/RoomContext'; +import { useRoomToolbox } from '../../../../room/contexts/RoomToolboxContext'; import { VoipInfo } from './VoipInfo'; // Contextual Bar for room view -const VoipInfoWithData = ({ tabBar: { close } }: any): ReactElement => { +const VoipInfoWithData = () => { const room = useVoipRoom(); + const { close: closeTab } = useRoomToolbox(); - return <VoipInfo room={room} onClickClose={close} />; + return <VoipInfo room={room} onClickClose={closeTab} />; }; export default VoipInfoWithData; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatsContextualBar.tsx b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatsContextualBar.tsx index f5d48fd39e2..e0d31371a71 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatsContextualBar.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatsContextualBar.tsx @@ -2,7 +2,7 @@ import { useRoute, useRouteParameter, useTranslation } from '@rocket.chat/ui-con import React, { useMemo } from 'react'; import { ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose } from '../../../../../components/Contextualbar'; -import { useTabBarClose } from '../../../../room/contexts/ToolboxContext'; +import { useTabBarClose } from '../../../../room/contexts/RoomToolboxContext'; import ChatInfo from './ChatInfo'; import { RoomEditWithData } from './RoomEdit'; diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactsContextualBar.tsx b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactsContextualBar.tsx index 4b7e73386df..e72ad2b5af9 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactsContextualBar.tsx +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactsContextualBar.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose } from '../../../../../components/Contextualbar'; import { useOmnichannelRoom } from '../../../../room/contexts/RoomContext'; -import { useTabBarClose } from '../../../../room/contexts/ToolboxContext'; +import { useTabBarClose } from '../../../../room/contexts/RoomToolboxContext'; import ContactEditWithData from './ContactEditWithData'; import ContactInfo from './ContactInfo'; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx b/apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx index ce73e326f02..d5a2e16e963 100644 --- a/apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx +++ b/apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { useTabBarClose } from '../room/contexts/ToolboxContext'; +import { useTabBarClose } from '../room/contexts/RoomToolboxContext'; import OutlookEventsList from './OutlookEventsList'; import OutlookSettingsList from './OutlookSettingsList'; diff --git a/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx b/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx index cc7df975051..8405ef43bf5 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx @@ -6,8 +6,6 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; import BurgerMenu from '../../../../components/BurgerMenu'; import { useOmnichannelRoom } from '../../contexts/RoomContext'; -import { ToolboxContext, useToolboxContext } from '../../contexts/ToolboxContext'; -import type { ToolboxActionConfig } from '../../lib/Toolbox'; import RoomHeader from '../RoomHeader'; import { BackButton } from './BackButton'; import QuickActions from './QuickActions'; @@ -37,7 +35,6 @@ const OmnichannelRoomHeader: FC<OmnichannelRoomHeaderProps> = ({ slots: parentSl const { isMobile } = useLayout(); const room = useOmnichannelRoom(); - const toolbox = useToolboxContext(); const slots = useMemo( () => ({ @@ -53,19 +50,7 @@ const OmnichannelRoomHeader: FC<OmnichannelRoomHeaderProps> = ({ slots: parentSl [isMobile, currentRouteName, parentSlot], ); - return ( - <ToolboxContext.Provider - value={useMemo( - () => ({ - ...toolbox, - actions: new Map([...(Array.from(toolbox.actions.entries()) as [string, ToolboxActionConfig][])]), - }), - [toolbox], - )} - > - <RoomHeader slots={slots} room={room} /> - </ToolboxContext.Provider> - ); + return <RoomHeader slots={slots} room={room} />; }; export default OmnichannelRoomHeader; diff --git a/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx b/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx index ec7beb37dda..ea0a1575d30 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx @@ -7,8 +7,6 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { parseOutboundPhoneNumber } from '../../../../../ee/client/lib/voip/parseOutboundPhoneNumber'; import BurgerMenu from '../../../../components/BurgerMenu'; -import { ToolboxContext, useToolboxContext } from '../../contexts/ToolboxContext'; -import type { ToolboxActionConfig } from '../../lib/Toolbox'; import type { RoomHeaderProps } from '../RoomHeader'; import RoomHeader from '../RoomHeader'; import { BackButton } from './BackButton'; @@ -26,7 +24,6 @@ const VoipRoomHeader: FC<VoipRoomHeaderProps> = ({ slots: parentSlot, room }) => ); const { isMobile } = useLayout(); - const toolbox = useToolboxContext(); const slots = useMemo( () => ({ @@ -40,19 +37,7 @@ const VoipRoomHeader: FC<VoipRoomHeaderProps> = ({ slots: parentSlot, room }) => }), [isMobile, currentRouteName, parentSlot], ); - return ( - <ToolboxContext.Provider - value={useMemo( - () => ({ - ...toolbox, - actions: new Map([...(Array.from(toolbox.actions.entries()) as [string, ToolboxActionConfig][])]), - }), - [toolbox], - )} - > - <RoomHeader slots={slots} room={{ ...room, name: parseOutboundPhoneNumber(room.fname) }} /> - </ToolboxContext.Provider> - ); + return <RoomHeader slots={slots} room={{ ...room, name: parseOutboundPhoneNumber(room.fname) }} />; }; export default VoipRoomHeader; diff --git a/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx index 4a26c608dba..47e715503d3 100644 --- a/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx +++ b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx @@ -2,13 +2,12 @@ import type { Box } from '@rocket.chat/fuselage'; import { Menu, Option } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { HeaderToolboxAction, HeaderToolboxDivider } from '@rocket.chat/ui-client'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useLayout, useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps, ReactElement } from 'react'; import React, { memo, useRef } from 'react'; // used to open the menu option by keyboard -import { useToolboxContext, useTab, useTabBarOpen } from '../../contexts/ToolboxContext'; +import { useRoomToolbox, useTabBarOpen } from '../../contexts/RoomToolboxContext'; import type { ToolboxActionConfig, OptionRenderer } from '../../lib/Toolbox'; const renderMenuOption: OptionRenderer = ({ label: { title, icon }, ...props }: any) => ( @@ -21,14 +20,12 @@ type RoomToolboxProps = { const RoomToolbox = ({ className }: RoomToolboxProps) => { const t = useTranslation(); - const tab = useTab(); const openTabBar = useTabBarOpen(); const { isMobile } = useLayout(); const hiddenActionRenderers = useRef<{ [key: string]: OptionRenderer }>({}); - const { actions: mapActions } = useToolboxContext(); + const { actions, tab } = useRoomToolbox(); - const actions = (Array.from(mapActions.values()) as ToolboxActionConfig[]).sort((a, b) => (a.order || 0) - (b.order || 0)); const featuredActions = actions.filter((action) => action.featured); const filteredActions = actions.filter((action) => !action.featured); const visibleActions = isMobile ? [] : filteredActions.slice(0, 6); @@ -60,47 +57,41 @@ const RoomToolbox = ({ className }: RoomToolboxProps) => { return ( <> - {featuredActions.map( - ({ renderAction, id, icon, title, action = actionDefault, disabled, 'data-tooltip': dataTooltip, tooltip }, index) => { - const props = { - id, - icon, - title: t(title), - className, - index, - pressed: id === tab?.id, - action, - disabled, - ...(dataTooltip ? { 'data-tooltip': t(dataTooltip as TranslationKey) } : {}), - ...(tooltip ? { tooltip } : {}), - }; - if (renderAction) { - return renderAction(props); - } - return <HeaderToolboxAction {...props} key={id} />; - }, - )} + {featuredActions.map(({ renderAction, id, icon, title, action = actionDefault, disabled, tooltip }, index) => { + const props = { + id, + icon, + title: t(title), + className, + index, + pressed: id === tab?.id, + action, + disabled, + ...(tooltip ? { tooltip } : {}), + }; + if (renderAction) { + return renderAction(props); + } + return <HeaderToolboxAction {...props} key={id} />; + })} {featuredActions.length > 0 && <HeaderToolboxDivider />} - {visibleActions.map( - ({ renderAction, id, icon, title, action = actionDefault, disabled, 'data-tooltip': dataTooltip, tooltip }, index) => { - const props = { - id, - icon, - title: t(title), - className, - index, - pressed: id === tab?.id, - action, - disabled, - ...(dataTooltip ? { 'data-tooltip': t(dataTooltip as TranslationKey) } : {}), - ...(tooltip ? { tooltip } : {}), - }; - if (renderAction) { - return renderAction(props); - } - return <HeaderToolboxAction {...props} key={id} />; - }, - )} + {visibleActions.map(({ renderAction, id, icon, title, action = actionDefault, disabled, tooltip }, index) => { + const props = { + id, + icon, + title: t(title), + className, + index, + pressed: id === tab?.id, + action, + disabled, + ...(tooltip ? { tooltip } : {}), + }; + if (renderAction) { + return renderAction(props); + } + return <HeaderToolboxAction {...props} key={id} />; + })} {(filteredActions.length > 6 || isMobile) && ( <Menu data-qa-id='ToolBox-Menu' diff --git a/apps/meteor/client/views/room/MemberListRouter.js b/apps/meteor/client/views/room/MemberListRouter.js index 78651b0f88d..372d7269461 100644 --- a/apps/meteor/client/views/room/MemberListRouter.js +++ b/apps/meteor/client/views/room/MemberListRouter.js @@ -2,7 +2,7 @@ import { useUserId } from '@rocket.chat/ui-contexts'; import React from 'react'; import { useRoom } from './contexts/RoomContext'; -import { useTab, useTabBarClose, useTabContext } from './contexts/ToolboxContext'; +import { useRoomToolbox, useTabBarClose } from './contexts/RoomToolboxContext'; import RoomMembers from './contextualBar/RoomMembers'; import UserInfo from './contextualBar/UserInfo'; @@ -19,11 +19,10 @@ const getUid = (room, ownUserId) => { }; const MemberListRouter = ({ rid }) => { - const username = useTabContext(); + const { tab, context: username } = useRoomToolbox(); const room = useRoom(); const onClickClose = useTabBarClose(); const ownUserId = useUserId(); - const tab = useTab(); const isMembersList = tab.id === 'members-list' || tab.id === 'user-info-group'; diff --git a/apps/meteor/client/views/room/Room/Room.tsx b/apps/meteor/client/views/room/Room/Room.tsx index aef502a16a0..ecf97659f4c 100644 --- a/apps/meteor/client/views/room/Room/Room.tsx +++ b/apps/meteor/client/views/room/Room/Room.tsx @@ -8,7 +8,7 @@ import Header from '../Header'; import MessageHighlightProvider from '../MessageList/providers/MessageHighlightProvider'; import RoomBody from '../components/body/RoomBody'; import { useRoom } from '../contexts/RoomContext'; -import { useTab, useToolboxContext } from '../contexts/ToolboxContext'; +import { useRoomToolbox } from '../contexts/RoomToolboxContext'; import AppsContextualBar from '../contextualBar/Apps'; import { useAppsContextualBar } from '../hooks/useAppsContextualBar'; import RoomLayout from '../layout/RoomLayout'; @@ -20,9 +20,8 @@ const Room = (): ReactElement => { const room = useRoom(); - const toolbox = useToolboxContext(); + const toolbox = useRoomToolbox(); - const tab = useTab(); const appsContextualBarContext = useAppsContextualBar(); return ( @@ -34,12 +33,12 @@ const Room = (): ReactElement => { header={<Header room={room} />} body={<RoomBody />} aside={ - (tab && ( + (toolbox.tab && ( <ErrorBoundary fallback={null}> <SelectedMessagesProvider> - {typeof tab.template !== 'string' && typeof tab.template !== 'undefined' && ( + {typeof toolbox.tab.template !== 'string' && typeof toolbox.tab.template !== 'undefined' && ( <Suspense fallback={<ContextualbarSkeleton />}> - {createElement(tab.template, { tabBar: toolbox, _id: room._id, rid: room._id, teamId: room.teamId })} + {createElement(toolbox.tab.template, { _id: room._id, rid: room._id, teamId: room.teamId })} </Suspense> )} </SelectedMessagesProvider> diff --git a/apps/meteor/client/views/room/components/body/RoomBody.tsx b/apps/meteor/client/views/room/components/body/RoomBody.tsx index aaa0d8a6318..b21b0743b63 100644 --- a/apps/meteor/client/views/room/components/body/RoomBody.tsx +++ b/apps/meteor/client/views/room/components/body/RoomBody.tsx @@ -32,7 +32,7 @@ import { MessageList } from '../../MessageList/MessageList'; import MessageListErrorBoundary from '../../MessageList/MessageListErrorBoundary'; import { useChat } from '../../contexts/ChatContext'; import { useRoom, useRoomSubscription, useRoomMessages } from '../../contexts/RoomContext'; -import { useToolboxContext } from '../../contexts/ToolboxContext'; +import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import { useScrollMessageList } from '../../hooks/useScrollMessageList'; import DropTargetOverlay from './DropTargetOverlay'; import JumpToRecentMessageButton from './JumpToRecentMessageButton'; @@ -55,7 +55,7 @@ const RoomBody = (): ReactElement => { const isLayoutEmbedded = useEmbeddedLayout(); const room = useRoom(); const user = useUser(); - const toolbox = useToolboxContext(); + const toolbox = useRoomToolbox(); const admin = useRole('admin'); const subscription = useRoomSubscription(); @@ -260,11 +260,6 @@ const RoomBody = (): ReactElement => { const router = useRouter(); - const roomRef = useRef(room); - roomRef.current = room; - const tabBarRef = useRef(toolbox); - tabBarRef.current = toolbox; - const debouncedReadMessageRead = useMemo( () => withDebouncing({ wait: 500 })(() => { diff --git a/apps/meteor/client/views/room/components/contextualBar/MessageListTab.tsx b/apps/meteor/client/views/room/components/contextualBar/MessageListTab.tsx index e30dd3f3cd9..d1a11f69f65 100644 --- a/apps/meteor/client/views/room/components/contextualBar/MessageListTab.tsx +++ b/apps/meteor/client/views/room/components/contextualBar/MessageListTab.tsx @@ -26,7 +26,7 @@ import { isMessageFirstUnread } from '../../MessageList/lib/isMessageFirstUnread import { isMessageNewDay } from '../../MessageList/lib/isMessageNewDay'; import MessageListProvider from '../../MessageList/providers/MessageListProvider'; import { useRoomSubscription } from '../../contexts/RoomContext'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; type MessageListTabProps = { iconName: IconName; diff --git a/apps/meteor/client/views/room/contexts/RoomAPIContext.ts b/apps/meteor/client/views/room/contexts/RoomAPIContext.ts deleted file mode 100644 index 2c2b14cac50..00000000000 --- a/apps/meteor/client/views/room/contexts/RoomAPIContext.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -type RoomAPIContextValue = {}; - -export const RoomAPIContext = createContext<RoomAPIContextValue | undefined>(undefined); diff --git a/apps/meteor/client/views/room/contexts/RoomToolboxContext.ts b/apps/meteor/client/views/room/contexts/RoomToolboxContext.ts new file mode 100644 index 00000000000..570d72e7caa --- /dev/null +++ b/apps/meteor/client/views/room/contexts/RoomToolboxContext.ts @@ -0,0 +1,25 @@ +import { createContext, useContext } from 'react'; + +import type { ToolboxActionConfig } from '../lib/Toolbox'; + +export type RoomToolboxContextValue = { + actions: ToolboxActionConfig[]; + tab?: ToolboxActionConfig; + context?: string; + open: (actionId: string, context?: string) => void; + openRoomInfo: (username?: string) => void; + close: () => void; +}; + +export const RoomToolboxContext = createContext<RoomToolboxContextValue>({ + actions: [], + open: () => undefined, + openRoomInfo: () => undefined, + close: () => undefined, +}); + +export const useRoomToolbox = () => useContext(RoomToolboxContext); + +export const useTabBarOpen = (): ((actionId: string, context?: string) => void) => useContext(RoomToolboxContext).open; +export const useTabBarClose = (): (() => void) => useContext(RoomToolboxContext).close; +export const useTabBarOpenUserInfo = (): ((username: string) => void) => useContext(RoomToolboxContext).openRoomInfo; diff --git a/apps/meteor/client/views/room/contexts/ToolboxContext.ts b/apps/meteor/client/views/room/contexts/ToolboxContext.ts deleted file mode 100644 index 21fac8bda5a..00000000000 --- a/apps/meteor/client/views/room/contexts/ToolboxContext.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { EventHandlerOf } from '@rocket.chat/emitter'; -import { createContext, useContext } from 'react'; - -import type { ToolboxActionConfig, ToolboxAction, Events } from '../lib/Toolbox'; -import { actions, listen } from '../lib/Toolbox'; - -export type ToolboxEventHandler = (handler: EventHandlerOf<Events, 'change'>) => () => void; - -export type ToolboxContextValue = { - actions: Map<ToolboxActionConfig['id'], ToolboxAction>; - listen: ToolboxEventHandler; - tabBar?: any; - context?: any; - open: (actionId: string, context?: string) => void; - openRoomInfo: (username?: string) => void; - close: () => void; - activeTabBar?: ToolboxActionConfig; - setData?: (data: Record<string, unknown>) => void; -}; - -export const ToolboxContext = createContext<ToolboxContextValue>({ - actions, - listen, - open: () => undefined, - openRoomInfo: () => undefined, - close: () => undefined, -}); - -export const useToolboxContext = (): ToolboxContextValue => useContext(ToolboxContext); - -export const useTabContext = (): unknown | undefined => useContext(ToolboxContext).context; -export const useTab = (): ToolboxActionConfig | undefined => useContext(ToolboxContext).activeTabBar; -export const useTabBarOpen = (): ((actionId: string, context?: string) => void) => useContext(ToolboxContext).open; -export const useTabBarClose = (): (() => void) => useContext(ToolboxContext).close; -export const useTabBarOpenUserInfo = (): ((username: string) => void) => useContext(ToolboxContext).openRoomInfo; diff --git a/apps/meteor/client/views/room/contextualBar/Apps/AppsWithData.tsx b/apps/meteor/client/views/room/contextualBar/Apps/AppsWithData.tsx index b50817dae53..5a6c0102d86 100644 --- a/apps/meteor/client/views/room/contextualBar/Apps/AppsWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/Apps/AppsWithData.tsx @@ -17,7 +17,7 @@ import type { Dispatch, SyntheticEvent, ContextType } from 'react'; import React, { memo, useState, useEffect, useReducer } from 'react'; import { useUiKitActionManager } from '../../../../hooks/useUiKitActionManager'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import Apps from './Apps'; type FieldStateValue = string | Array<string> | undefined; diff --git a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx index 0d18e1eeeb8..2c6a2f6d9ac 100644 --- a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx @@ -6,7 +6,7 @@ import React, { useMemo, useEffect, useState, memo } from 'react'; import { useEndpointAction } from '../../../../hooks/useEndpointAction'; import { useEndpointData } from '../../../../hooks/useEndpointData'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import AutoTranslate from './AutoTranslate'; const AutoTranslateWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => { diff --git a/apps/meteor/client/views/room/contextualBar/Discussions/DiscussionsListContextBar.tsx b/apps/meteor/client/views/room/contextualBar/Discussions/DiscussionsListContextBar.tsx index 3a9c22ec390..6501aecf5b5 100644 --- a/apps/meteor/client/views/room/contextualBar/Discussions/DiscussionsListContextBar.tsx +++ b/apps/meteor/client/views/room/contextualBar/Discussions/DiscussionsListContextBar.tsx @@ -6,7 +6,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import DiscussionsList from './DiscussionsList'; import { useDiscussionsList } from './useDiscussionsList'; diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx index 9e81e084791..152bd51acff 100644 --- a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx +++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx @@ -12,7 +12,7 @@ import { ContextualbarClose, ContextualbarScrollableContent, } from '../../../../components/Contextualbar'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import FileExport from './FileExport'; import MailExportForm from './MailExportForm'; diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannelWithData.js b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannelWithData.js index 24f9772cdb5..1222ce27188 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannelWithData.js +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannelWithData.js @@ -1,7 +1,7 @@ import { useUserRoom } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useTabBarClose } from '../../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../../contexts/RoomToolboxContext'; import EditChannel from './EditChannel'; function EditChannelWithData({ rid, onClickBack }) { diff --git a/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx b/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx index b17b0260ec7..c57b64ee474 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx @@ -3,7 +3,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useState } from 'react'; import { useRoom } from '../../contexts/RoomContext'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import EditRoomInfoWithData from './EditRoomInfo'; import RoomInfo from './RoomInfo'; import { useCanEditRoom } from './hooks/useCanEditRoom'; diff --git a/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcuts.stories.tsx b/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcuts.stories.tsx index 58f4e65e5de..8bd3494d115 100644 --- a/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcuts.stories.tsx +++ b/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcuts.stories.tsx @@ -13,5 +13,5 @@ export default { decorators: [(fn) => <Contextualbar height='100vh'>{fn()}</Contextualbar>], } as ComponentMeta<typeof KeyboardShortcutsWithData>; -export const Default: ComponentStory<typeof KeyboardShortcutsWithData> = (args) => <KeyboardShortcutsWithData {...args} />; +export const Default: ComponentStory<typeof KeyboardShortcutsWithData> = () => <KeyboardShortcutsWithData />; Default.storyName = 'KeyboardShortcuts'; diff --git a/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcutsWithData.tsx b/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcutsWithData.tsx index c35baf4933b..e2a202556c6 100644 --- a/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcutsWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/KeyboardShortcuts/KeyboardShortcutsWithData.tsx @@ -1,13 +1,12 @@ -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import type { ReactElement } from 'react'; import React from 'react'; -import type { ToolboxContextValue } from '../../contexts/ToolboxContext'; +import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import KeyboardShortcuts from './KeyboardShortcuts'; -const KeyboardShortcutsWithData = ({ tabBar }: { tabBar: ToolboxContextValue['tabBar'] }): ReactElement => { - const handleClose = useMutableCallback(() => tabBar?.close()); - return <KeyboardShortcuts handleClose={handleClose} />; +const KeyboardShortcutsWithData = (): ReactElement => { + const { close: closeTab } = useRoomToolbox(); + return <KeyboardShortcuts handleClose={closeTab} />; }; export default KeyboardShortcutsWithData; diff --git a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/MessageSearchTab.tsx b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/MessageSearchTab.tsx index b67ff96c34e..8c157d43560 100644 --- a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/MessageSearchTab.tsx +++ b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/MessageSearchTab.tsx @@ -1,6 +1,6 @@ import { Callout } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useCallback, useState } from 'react'; +import React, { useState } from 'react'; import { ContextualbarClose, @@ -9,7 +9,7 @@ import { ContextualbarTitle, ContextualbarIcon, } from '../../../../components/Contextualbar'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import MessageSearch from './components/MessageSearch'; import MessageSearchForm from './components/MessageSearchForm'; import { useMessageSearchProviderQuery } from './hooks/useMessageSearchProviderQuery'; @@ -17,10 +17,7 @@ import { useMessageSearchProviderQuery } from './hooks/useMessageSearchProviderQ const MessageSearchTab = () => { const providerQuery = useMessageSearchProviderQuery(); - const tabBarClose = useTabBarClose(); - const handleCloseButtonClick = useCallback(() => { - tabBarClose(); - }, [tabBarClose]); + const { close: closeTab } = useRoomToolbox(); const [{ searchText, globalSearch }, handleSearch] = useState({ searchText: '', globalSearch: false }); @@ -31,7 +28,7 @@ const MessageSearchTab = () => { <ContextualbarHeader> <ContextualbarIcon name='magnifier' /> <ContextualbarTitle>{t('Search_Messages')}</ContextualbarTitle> - <ContextualbarClose onClick={handleCloseButtonClick} /> + <ContextualbarClose onClick={closeTab} /> </ContextualbarHeader> <ContextualbarContent flexShrink={1} flexGrow={1} paddingInline={0}> {providerQuery.isSuccess && ( diff --git a/apps/meteor/client/views/room/contextualBar/NotificationPreferences/NotificationPreferencesWithData.tsx b/apps/meteor/client/views/room/contextualBar/NotificationPreferences/NotificationPreferencesWithData.tsx index 62bbdf032e6..79ca02e5d9f 100644 --- a/apps/meteor/client/views/room/contextualBar/NotificationPreferences/NotificationPreferencesWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/NotificationPreferences/NotificationPreferencesWithData.tsx @@ -5,7 +5,7 @@ import React, { memo } from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import { useEndpointAction } from '../../../../hooks/useEndpointAction'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import NotificationPreferences from './NotificationPreferences'; export type NotificationFormValues = { diff --git a/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx b/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx index b5763d77af8..323e2cf7240 100644 --- a/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/OTR/OTRWithData.tsx @@ -6,7 +6,7 @@ import ORTInstance from '../../../../../app/otr/client/OTR'; import { OtrRoomState } from '../../../../../app/otr/lib/OtrRoomState'; import { usePresence } from '../../../../hooks/usePresence'; import { useReactiveValue } from '../../../../hooks/useReactiveValue'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import OTR from './OTR'; const OTRWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => { diff --git a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx index b294df0af5d..13395d1929c 100644 --- a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx @@ -1,14 +1,15 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { isDirectMessageRoom } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useSetModal, useToastMessageDispatch, useUserRoom, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; +import { useSetModal, useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import type { ReactElement } from 'react'; import React, { useCallback, useMemo, useState } from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import GenericModal from '../../../../components/GenericModal'; -import type { ToolboxContextValue } from '../../contexts/ToolboxContext'; +import { useRoom } from '../../contexts/RoomContext'; +import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import PruneMessages from './PruneMessages'; const getTimeZoneOffset = (): string => { @@ -36,11 +37,11 @@ export const initialValues = { const DEFAULT_PRUNE_LIMIT = 2000; -const PruneMessagesWithData = ({ rid, tabBar }: { rid: IRoom['_id']; tabBar: ToolboxContextValue['tabBar'] }): ReactElement => { +const PruneMessagesWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => { const t = useTranslation(); - const room = useUserRoom(rid); + const room = useRoom(); const setModal = useSetModal(); - const onClickClose = useMutableCallback(() => tabBar?.close()); + const { close } = useRoomToolbox(); const closeModal = useCallback(() => setModal(null), [setModal]); const dispatchToastMessage = useToastMessageDispatch(); const pruneMessagesAction = useEndpoint('POST', '/v1/rooms.cleanHistory'); @@ -191,13 +192,7 @@ const PruneMessagesWithData = ({ rid, tabBar }: { rid: IRoom['_id']; tabBar: Too return ( <FormProvider {...methods}> - <PruneMessages - callOutText={callOutText} - validateText={validateText} - users={users} - onClickClose={onClickClose} - onClickPrune={handlePrune} - /> + <PruneMessages callOutText={callOutText} validateText={validateText} users={users} onClickClose={close} onClickPrune={handlePrune} /> </FormProvider> ); }; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js index 4f3d4ccd263..1892e5a17f1 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js @@ -5,7 +5,7 @@ import React, { useState, useCallback, useMemo } from 'react'; import GenericModal from '../../../../components/GenericModal'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import RoomFiles from './RoomFiles'; import { useFilesList } from './hooks/useFilesList'; import { useMessageDeletionIsAllowed } from './hooks/useMessageDeletionIsAllowed'; diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsersWithData.tsx index 3233fc7e196..672bea49aa4 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsersWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsersWithData.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { useForm } from '../../../../../hooks/useForm'; import { useRoom } from '../../../contexts/RoomContext'; -import { useTabBarClose } from '../../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../../contexts/RoomToolboxContext'; import AddUsers from './AddUsers'; type AddUsersWithDataProps = { diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx index 6c04b60ec86..f6a80b19645 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx @@ -5,7 +5,7 @@ import type { ReactElement } from 'react'; import React, { useState, useEffect } from 'react'; import { useFormatDateAndTime } from '../../../../../hooks/useFormatDateAndTime'; -import { useTabBarClose } from '../../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../../contexts/RoomToolboxContext'; import InviteUsers from './InviteUsers'; type InviteUsersWithDataProps = { diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx index b7484d68f8e..e18c61da776 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import * as Federation from '../../../../lib/federation/Federation'; import { useMembersList } from '../../../hooks/useMembersList'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import UserInfoWithData from '../UserInfo'; import AddUsers from './AddUsers'; import InviteUsers from './InviteUsers'; diff --git a/apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx b/apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx index 3c551603f65..10aadf11636 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx +++ b/apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx @@ -15,7 +15,7 @@ import { ContextualbarBack, ContextualbarInnerContent, } from '../../../../components/Contextualbar'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import { useGoToThreadList } from '../../hooks/useGoToThreadList'; import ChatProvider from '../../providers/ChatProvider'; import ThreadChat from './components/ThreadChat'; diff --git a/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx b/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx index 58c7341cc88..a7c7148c2e3 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx +++ b/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx @@ -19,7 +19,7 @@ import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../lib/asyncState'; import type { ThreadsListOptions } from '../../../../lib/lists/ThreadsList'; import { useRoom, useRoomSubscription } from '../../contexts/RoomContext'; -import { useTabBarClose } from '../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../contexts/RoomToolboxContext'; import { useGoToThread } from '../../hooks/useGoToThread'; import ThreadListItem from './components/ThreadListItem'; import { useThreadsList } from './hooks/useThreadsList'; diff --git a/apps/meteor/client/views/room/contextualBar/Threads/Threads.tsx b/apps/meteor/client/views/room/contextualBar/Threads/Threads.tsx index fe33cfa89cc..2c916ccb1d5 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/Threads.tsx +++ b/apps/meteor/client/views/room/contextualBar/Threads/Threads.tsx @@ -1,12 +1,12 @@ import type { ReactElement } from 'react'; import React from 'react'; -import { useTabContext } from '../../contexts/ToolboxContext'; +import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; import Thread from './Thread'; import ThreadList from './ThreadList'; const Threads = (): ReactElement => { - const tmid = useTabContext() as string | undefined; + const { context: tmid } = useRoomToolbox(); if (tmid) { return <Thread tmid={tmid} />; diff --git a/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsx b/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsx index 434db3aed0d..7782c04a6b1 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsx +++ b/apps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsx @@ -13,7 +13,7 @@ import ComposerContainer from '../../../components/body/composer/ComposerContain import { useFileUploadDropTarget } from '../../../components/body/hooks/useFileUploadDropTarget'; import { useChat } from '../../../contexts/ChatContext'; import { useRoom, useRoomSubscription } from '../../../contexts/RoomContext'; -import { useTabBarClose } from '../../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../../contexts/RoomToolboxContext'; import ThreadMessageList from './ThreadMessageList'; type ThreadChatProps = { diff --git a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListWithData.tsx b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListWithData.tsx index fbabc97f0e0..c934951cff5 100644 --- a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListWithData.tsx @@ -4,7 +4,7 @@ import React, { useMemo } from 'react'; import { useRecordList } from '../../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../../hooks/useAsyncState'; -import { useTabBarClose } from '../../../contexts/ToolboxContext'; +import { useTabBarClose } from '../../../contexts/RoomToolboxContext'; import VideoConfList from './VideoConfList'; import { useVideoConfList } from './useVideoConfList'; diff --git a/apps/meteor/client/views/room/lib/Toolbox/index.tsx b/apps/meteor/client/views/room/lib/Toolbox/index.tsx index a805172d63e..9c9a83b21fc 100644 --- a/apps/meteor/client/views/room/lib/Toolbox/index.tsx +++ b/apps/meteor/client/views/room/lib/Toolbox/index.tsx @@ -4,12 +4,6 @@ import type { Keys as IconName } from '@rocket.chat/icons'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import type { ReactNode, MouseEvent, ComponentProps, ComponentType } from 'react'; -import type { ToolboxContextValue } from '../../contexts/ToolboxContext'; -import type { Events as GeneratorEvents } from './generator'; -import { generator } from './generator'; - -type ToolboxHook = ({ room }: { room: IRoom }) => ToolboxActionConfig | null; - type ActionRendererProps = Omit<ToolboxActionConfig, 'renderAction' | 'groups' | 'title'> & { className: ComponentProps<typeof Box>['className']; index: number; @@ -21,33 +15,23 @@ type OptionRendererProps = ComponentProps<typeof Option>; export type OptionRenderer = (props: OptionRendererProps) => ReactNode; export type ToolboxActionConfig = { - 'id': string; - 'icon'?: IconName; - 'title': TranslationKey; - 'anonymous'?: boolean; - 'tooltip'?: string; - 'data-tooltip'?: string; - 'disabled'?: boolean; - 'renderAction'?: (props: ActionRendererProps) => ReactNode; - 'full'?: true; - 'renderOption'?: OptionRenderer; - 'order'?: number; - 'groups': Array<'group' | 'channel' | 'live' | 'direct' | 'direct_multiple' | 'team' | 'voip'>; - 'hotkey'?: string; - 'action'?: (e?: MouseEvent<HTMLElement>) => void; - 'template'?: ComponentType<{ - tabBar: ToolboxContextValue; + id: string; + icon?: IconName; + title: TranslationKey; + anonymous?: boolean; + tooltip?: string; + disabled?: boolean; + renderAction?: (props: ActionRendererProps) => ReactNode; + full?: true; + renderOption?: OptionRenderer; + order?: number; + groups: Array<'group' | 'channel' | 'live' | 'direct' | 'direct_multiple' | 'team' | 'voip'>; + hotkey?: string; + action?: (e?: MouseEvent<HTMLElement>) => void; + template?: ComponentType<{ _id: IRoom['_id']; rid: IRoom['_id']; teamId: IRoom['teamId']; }>; - 'featured'?: boolean; + featured?: boolean; }; - -export type ToolboxAction = ToolboxHook | ToolboxActionConfig; - -const { listen, store: actions } = generator<ToolboxAction>(); - -export type Events = GeneratorEvents<ToolboxAction>; - -export { listen, actions }; diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index 1e5a8fd3c7f..8dde725b013 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -15,7 +15,6 @@ import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import RoomNotFound from '../RoomNotFound'; import RoomSkeleton from '../RoomSkeleton'; import { useRoomRolesManagement } from '../components/body/hooks/useRoomRolesManagement'; -import { RoomAPIContext } from '../contexts/RoomAPIContext'; import { RoomContext } from '../contexts/RoomContext'; import ComposerPopupProvider from './ComposerPopupProvider'; import RoomToolboxProvider from './RoomToolboxProvider'; @@ -136,20 +135,16 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }; }, [rid, subscribed]); - const api = useMemo(() => ({}), []); - if (!pseudoRoom) { return isSuccess && !room ? <RoomNotFound /> : <RoomSkeleton />; } return ( - <RoomAPIContext.Provider value={api}> - <RoomContext.Provider value={context}> - <RoomToolboxProvider> - <ComposerPopupProvider room={pseudoRoom}>{children}</ComposerPopupProvider> - </RoomToolboxProvider> - </RoomContext.Provider> - </RoomAPIContext.Provider> + <RoomContext.Provider value={context}> + <RoomToolboxProvider> + <ComposerPopupProvider room={pseudoRoom}>{children}</ComposerPopupProvider> + </RoomToolboxProvider> + </RoomContext.Provider> ); }; diff --git a/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx b/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx index 6fa16dfa61b..851c3111cc2 100644 --- a/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx @@ -1,33 +1,43 @@ -import { useDebouncedState, useMutableCallback, useSafely } from '@rocket.chat/fuselage-hooks'; +import type { RoomType, IRoom } from '@rocket.chat/core-typings'; +import { useMutableCallback, useStableArray } from '@rocket.chat/fuselage-hooks'; import { useUserId, useSetting, useRouter, useRouteParameter } from '@rocket.chat/ui-contexts'; import type { ReactNode } from 'react'; -import React, { useContext, useLayoutEffect, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { useRoom } from '../contexts/RoomContext'; -import type { ToolboxContextValue } from '../contexts/ToolboxContext'; -import { ToolboxContext } from '../contexts/ToolboxContext'; +import type { RoomToolboxContextValue } from '../contexts/RoomToolboxContext'; +import { RoomToolboxContext } from '../contexts/RoomToolboxContext'; import type { ToolboxActionConfig } from '../lib/Toolbox/index'; -import VirtualAction from './VirtualAction'; import { useAppsRoomActions } from './hooks/useAppsRoomActions'; import { useCoreRoomActions } from './hooks/useCoreRoomActions'; +const groupsDict = { + l: 'live', + v: 'voip', + d: 'direct', + p: 'group', + c: 'channel', +} as const satisfies Record<RoomType, ToolboxActionConfig['groups'][number]>; + +const getGroup = (room: IRoom) => { + if (room.teamMain) { + return 'team'; + } + + if (room.t === 'd' && (room.uids?.length ?? 0) > 2) { + return 'direct_multiple'; + } + + return groupsDict[room.t]; +}; + type RoomToolboxProviderProps = { children: ReactNode }; const RoomToolboxProvider = ({ children }: RoomToolboxProviderProps) => { const room = useRoom(); - const [list, setList] = useSafely(useDebouncedState(() => new Map<string, ToolboxActionConfig>(), 5)); - const handleChange = useMutableCallback((fn) => { - fn(list); - setList((list) => new Map(list)); - }); - const router = useRouter(); - const tab = useRouteParameter('tab'); - - const activeTabBar = useMemo(() => (tab ? list.get(tab) : undefined), [tab, list]); - const close = useMutableCallback(() => { const routeName = router.getRouteName(); @@ -47,7 +57,7 @@ const RoomToolboxProvider = ({ children }: RoomToolboxProviderProps) => { }); const open = useMutableCallback((actionId: string, context?: string) => { - if (actionId === activeTabBar?.id && context === undefined) { + if (actionId === tab?.id && context === undefined) { return close(); } @@ -90,52 +100,43 @@ const RoomToolboxProvider = ({ children }: RoomToolboxProviderProps) => { } }); - const { listen, actions } = useContext(ToolboxContext); - const [legacyCoreRoomActions, updateLegacyCoreRoomActions] = useSafely(useState(() => Array.from(actions.entries()))); + const context = useRouteParameter('context'); + + const coreRoomActions = useCoreRoomActions(); + const appsRoomActions = useAppsRoomActions(); + + const allowAnonymousRead = useSetting<boolean>('Accounts_AllowAnonymousRead', false); + const uid = useUserId(); - useLayoutEffect( - () => - listen((actions) => { - updateLegacyCoreRoomActions(Array.from(actions.entries())); - }), - [listen, updateLegacyCoreRoomActions], + const actions = useStableArray( + [...coreRoomActions, ...appsRoomActions] + .filter((action) => uid || (allowAnonymousRead && 'anonymous' in action && action.anonymous)) + .filter((action) => !action.groups || action.groups.includes(getGroup(room))) + .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), ); - const context = useRouteParameter('context'); + const tabActionId = useRouteParameter('tab'); + const tab = useMemo(() => { + if (!tabActionId) { + return undefined; + } + + return actions.find((action) => action.id === tabActionId); + }, [actions, tabActionId]); const contextValue = useMemo( - (): ToolboxContextValue => ({ - listen, - actions: new Map(list), - activeTabBar, + (): RoomToolboxContextValue => ({ + actions, + tab, context, open, close, openRoomInfo, }), - [listen, list, activeTabBar, context, open, close, openRoomInfo], + [actions, tab, context, open, close, openRoomInfo], ); - const coreRoomActions = useCoreRoomActions(); - const appsRoomActions = useAppsRoomActions(); - - const allowAnonymousRead = useSetting<boolean>('Accounts_AllowAnonymousRead', false); - const uid = useUserId(); - - const roomActions = [ - ...legacyCoreRoomActions, - ...coreRoomActions.map((action) => [action.id, action] as [ToolboxActionConfig['id'], ToolboxActionConfig]), - ...appsRoomActions.map((action) => [action.id, action] as [ToolboxActionConfig['id'], ToolboxActionConfig]), - ].filter(([, action]) => uid || (allowAnonymousRead && 'anonymous' in action && action.anonymous)); - - return ( - <ToolboxContext.Provider value={contextValue}> - {roomActions.map(([id, roomAction]) => ( - <VirtualAction key={id + room._id} action={roomAction} handleChange={handleChange} /> - ))} - {children} - </ToolboxContext.Provider> - ); + return <RoomToolboxContext.Provider value={contextValue}>{children}</RoomToolboxContext.Provider>; }; export default RoomToolboxProvider; diff --git a/apps/meteor/client/views/room/providers/VirtualAction.tsx b/apps/meteor/client/views/room/providers/VirtualAction.tsx deleted file mode 100644 index ad1cb6c3b14..00000000000 --- a/apps/meteor/client/views/room/providers/VirtualAction.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type { IRoom, RoomType } from '@rocket.chat/core-typings'; -import { useLayoutEffect, memo } from 'react'; - -import { useRoom } from '../contexts/RoomContext'; -import type { ToolboxAction, ToolboxActionConfig } from '../lib/Toolbox/index'; - -const groupsDict = { - l: 'live', - v: 'voip', - d: 'direct', - p: 'group', - c: 'channel', -} as const satisfies Record<RoomType, string>; - -const getGroup = (room: IRoom) => { - if (room.teamMain) { - return 'team'; - } - - if (room.t === 'd' && (room.uids?.length ?? 0) > 2) { - return 'direct_multiple'; - } - - return groupsDict[room.t]; -}; - -type VirtualActionProps = { - action: ToolboxAction; - handleChange: (callback: (list: Map<ToolboxActionConfig['id'], ToolboxAction>) => void) => void; -}; - -const VirtualAction = ({ action, handleChange }: VirtualActionProps) => { - const room = useRoom(); - - const config = typeof action === 'function' ? action({ room }) : action; - - const group = getGroup(room); - - const visible = !!config && (!config.groups || (groupsDict[room.t] && config.groups.includes(group))); - - useLayoutEffect(() => { - if (!visible) { - return; - } - - handleChange((list) => list.set(config.id, config)); - - return () => { - handleChange((list) => list.delete(config.id)); - }; - }, [config, visible, handleChange]); - - return null; -}; - -export default memo(VirtualAction); diff --git a/apps/meteor/client/views/room/providers/hooks/useCoreRoomActions.ts b/apps/meteor/client/views/room/providers/hooks/useCoreRoomActions.ts index 244d5ac2f09..4c926942d3c 100644 --- a/apps/meteor/client/views/room/providers/hooks/useCoreRoomActions.ts +++ b/apps/meteor/client/views/room/providers/hooks/useCoreRoomActions.ts @@ -1,6 +1,10 @@ +import { useStableArray } from '@rocket.chat/fuselage-hooks'; + import { roomActionHooks } from '../../../../ui'; import type { ToolboxActionConfig } from '../../lib/Toolbox/index'; export const useCoreRoomActions = () => { - return roomActionHooks.map((roomActionHook) => roomActionHook()).filter((roomAction): roomAction is ToolboxActionConfig => !!roomAction); + return useStableArray( + roomActionHooks.map((roomActionHook) => roomActionHook()).filter((roomAction): roomAction is ToolboxActionConfig => !!roomAction), + ); }; diff --git a/apps/meteor/client/views/room/providers/hooks/useUserCard.ts b/apps/meteor/client/views/room/providers/hooks/useUserCard.ts index c4a10886450..c4fcf1b70d2 100644 --- a/apps/meteor/client/views/room/providers/hooks/useUserCard.ts +++ b/apps/meteor/client/views/room/providers/hooks/useUserCard.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect } from 'react'; import { openUserCard, closeUserCard } from '../../../../../app/ui/client/lib/userCard'; import { useRoom } from '../../contexts/RoomContext'; -import { useTabBarOpenUserInfo } from '../../contexts/ToolboxContext'; +import { useTabBarOpenUserInfo } from '../../contexts/RoomToolboxContext'; export const useUserCard = () => { useEffect(() => { diff --git a/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx index 1ebb457979e..6764d615c48 100644 --- a/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx +++ b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx @@ -8,7 +8,7 @@ import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../lib/asyncState'; import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; import CreateChannelWithData from '../../../../sidebar/header/CreateChannel'; -import { useTabBarClose } from '../../../room/contexts/ToolboxContext'; +import { useTabBarClose } from '../../../room/contexts/RoomToolboxContext'; import RoomInfo from '../../../room/contextualBar/Info'; import AddExistingModal from './AddExistingModal'; import BaseTeamsChannels from './BaseTeamsChannels'; diff --git a/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js b/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js index 8044d15d938..7518c754efa 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js +++ b/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js @@ -16,7 +16,7 @@ import { GenericModalDoNotAskAgain } from '../../../../components/GenericModal'; import { useDontAskAgain } from '../../../../hooks/useDontAskAgain'; import { useEndpointAction } from '../../../../hooks/useEndpointAction'; import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; -import { useTabBarClose, useTabBarOpen } from '../../../room/contexts/ToolboxContext'; +import { useTabBarClose, useTabBarOpen } from '../../../room/contexts/RoomToolboxContext'; import ConvertToChannelModal from '../../ConvertToChannelModal'; import DeleteTeamModal from './Delete'; import LeaveTeam from './LeaveTeam'; diff --git a/apps/meteor/ee/client/apps/gameCenter/GameCenter.tsx b/apps/meteor/ee/client/apps/gameCenter/GameCenter.tsx index 1d192e7b14e..b1254602a3c 100644 --- a/apps/meteor/ee/client/apps/gameCenter/GameCenter.tsx +++ b/apps/meteor/ee/client/apps/gameCenter/GameCenter.tsx @@ -3,7 +3,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useState } from 'react'; import type { ReactElement, SyntheticEvent } from 'react'; -import { useTabBarClose } from '../../../../client/views/room/contexts/ToolboxContext'; +import { useTabBarClose } from '../../../../client/views/room/contexts/RoomToolboxContext'; import GameCenterContainer from './GameCenterContainer'; import GameCenterList from './GameCenterList'; import { useExternalComponentsQuery } from './hooks/useExternalComponentsQuery'; diff --git a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx index 67f13856f40..33b55c66562 100644 --- a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx +++ b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/CannedResponseList.tsx @@ -15,7 +15,7 @@ import { ContextualbarFooter, } from '../../../../../../client/components/Contextualbar'; import ScrollableContentWrapper from '../../../../../../client/components/ScrollableContentWrapper'; -import { useTabContext } from '../../../../../../client/views/room/contexts/ToolboxContext'; +import { useRoomToolbox } from '../../../../../../client/views/room/contexts/RoomToolboxContext'; import Item from './Item'; import WrapCannedResponse from './WrapCannedResponse'; @@ -53,7 +53,7 @@ const CannedResponseList: FC<{ const t = useTranslation(); const inputRef = useAutoFocus<HTMLInputElement>(true); - const cannedId = useTabContext(); + const { context: cannedId } = useRoomToolbox(); const { ref, contentBoxSize: { inlineSize = 378 } = {} } = useResizeObserver<HTMLElement>({ debounceDelay: 200, diff --git a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx index f862a63fdf6..73b368095f5 100644 --- a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx +++ b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx @@ -1,19 +1,21 @@ import { useDebouncedValue, useLocalStorage, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useSetModal, useRouter } from '@rocket.chat/ui-contexts'; -import type { FC, MouseEvent } from 'react'; +import type { MouseEvent } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react'; import { useRecordList } from '../../../../../../client/hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../../../client/lib/asyncState'; import { useChat } from '../../../../../../client/views/room/contexts/ChatContext'; import { useRoom } from '../../../../../../client/views/room/contexts/RoomContext'; +import { useRoomToolbox } from '../../../../../../client/views/room/contexts/RoomToolboxContext'; import { useCannedResponseFilterOptions } from '../../../hooks/useCannedResponseFilterOptions'; import { useCannedResponseList } from '../../../hooks/useCannedResponseList'; import CreateCannedResponse from '../../CannedResponse/modals'; import CannedResponseList from './CannedResponseList'; -export const WrapCannedResponseList: FC<{ tabBar: any }> = ({ tabBar }) => { +export const WrapCannedResponseList = () => { const room = useRoom(); + const { close: closeTab } = useRoomToolbox(); const router = useRouter(); const setModal = useSetModal(); @@ -65,7 +67,7 @@ export const WrapCannedResponseList: FC<{ tabBar: any }> = ({ tabBar }) => { loadMoreItems={loadMoreItems} cannedItems={items} itemCount={itemCount} - onClose={tabBar.close} + onClose={closeTab} loading={phase === AsyncStatePhase.LOADING} options={options} text={text} diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/Message.spec.tsx b/apps/meteor/tests/unit/client/views/room/MessageList/Message.spec.tsx index c5242f0d540..b6a0f9ecd95 100644 --- a/apps/meteor/tests/unit/client/views/room/MessageList/Message.spec.tsx +++ b/apps/meteor/tests/unit/client/views/room/MessageList/Message.spec.tsx @@ -47,7 +47,7 @@ const RoomMessage = proxyquire.noCallThru().load('../../../../../../client/compo './room/RoomMessageContent': () => baseMessage.msg, '../MessageHeader': () => <p>message header</p>, '../StatusIndicators': { MessageIndicators: () => <p>message indicators</p> }, - '../ToolboxHolder': () => <p>toolbox</p>, + '../MessageToolboxHolder': () => <p>toolbox</p>, }).default as typeof _RoomMessage; describe('Message', () => { -- GitLab