From 2248fdfc0b48d4779688802c329b7976c8172b41 Mon Sep 17 00:00:00 2001 From: Tasso Date: Thu, 5 Dec 2024 19:57:31 -0300 Subject: [PATCH 01/10] Move actions of Auto Translate --- .../autotranslate/client/lib/actionButton.ts | 89 ------------------- .../autotranslate/client/lib/autotranslate.ts | 2 +- .../message/toolbar/MessageToolbar.tsx | 4 + .../message/toolbar/useTranslateAction.ts | 73 +++++++++++++++ .../useViewOriginalTranslationAction.ts | 73 +++++++++++++++ 5 files changed, 151 insertions(+), 90 deletions(-) create mode 100644 apps/meteor/client/components/message/toolbar/useTranslateAction.ts create mode 100644 apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts diff --git a/apps/meteor/app/autotranslate/client/lib/actionButton.ts b/apps/meteor/app/autotranslate/client/lib/actionButton.ts index 3901ef2df8e7..742ea4c9a5b7 100644 --- a/apps/meteor/app/autotranslate/client/lib/actionButton.ts +++ b/apps/meteor/app/autotranslate/client/lib/actionButton.ts @@ -1,96 +1,7 @@ import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; import { AutoTranslate } from './autotranslate'; -import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; -import { - hasTranslationLanguageInAttachments, - hasTranslationLanguageInMessage, -} from '../../../../client/views/room/MessageList/lib/autoTranslate'; -import { hasAtLeastOnePermission } from '../../../authorization/client'; -import { Messages } from '../../../models/client'; -import { settings } from '../../../settings/client'; -import { MessageAction } from '../../../ui-utils/client/lib/MessageAction'; -import { sdk } from '../../../utils/client/lib/SDKClient'; Meteor.startup(() => { AutoTranslate.init(); - - Tracker.autorun(() => { - if (settings.get('AutoTranslate_Enabled') && hasAtLeastOnePermission(['auto-translate'])) { - MessageAction.addButton({ - id: 'translate', - icon: 'language', - label: 'Translate', - context: ['message', 'message-mobile', 'threads'], - type: 'interaction', - action(_, { message }) { - const language = AutoTranslate.getLanguage(message.rid); - if (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)) { - (AutoTranslate.messageIdsToWait as any)[message._id] = true; - Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); - void sdk.call('autoTranslate.translateMessage', message, language); - } - const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; - Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); - }, - condition({ message, subscription, user, room }) { - if (!user) { - return false; - } - const language = subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid) || ''; - const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); - const isDifferentUser = message?.u && message.u._id !== user._id; - const autoTranslateEnabled = subscription?.autoTranslate || isLivechatRoom; - const hasLanguage = - hasTranslationLanguageInMessage(message, language) || hasTranslationLanguageInAttachments(message.attachments, language); - - return Boolean( - (message as { autoTranslateShowInverse?: boolean }).autoTranslateShowInverse || - (isDifferentUser && autoTranslateEnabled && !hasLanguage), - ); - }, - order: 90, - }); - MessageAction.addButton({ - id: 'view-original', - icon: 'language', - label: 'View_original', - context: ['message', 'message-mobile', 'threads'], - type: 'interaction', - action(_, props) { - const { message } = props; - const language = AutoTranslate.getLanguage(message.rid); - if (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)) { - (AutoTranslate.messageIdsToWait as any)[message._id] = true; - Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); - void sdk.call('autoTranslate.translateMessage', message, language); - } - const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; - Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); - }, - condition({ message, subscription, user, room }) { - const language = subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid) || ''; - const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); - if (!user) { - return false; - } - const isDifferentUser = message?.u && message.u._id !== user._id; - const autoTranslateEnabled = subscription?.autoTranslate || isLivechatRoom; - const hasLanguage = - hasTranslationLanguageInMessage(message, language) || hasTranslationLanguageInAttachments(message.attachments, language); - - return Boolean( - !(message as { autoTranslateShowInverse?: boolean }).autoTranslateShowInverse && - isDifferentUser && - autoTranslateEnabled && - hasLanguage, - ); - }, - order: 90, - }); - } else { - MessageAction.removeButton('toggle-language'); - } - }); }); diff --git a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts index 1cf02277878a..0309b406feb7 100644 --- a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts +++ b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts @@ -37,7 +37,7 @@ Meteor.startup(() => { export const AutoTranslate = { initialized: false, providersMetadata: {} as { [providerNamer: string]: { name: string; displayName: string } }, - messageIdsToWait: {} as { [messageId: string]: string }, + messageIdsToWait: {} as { [messageId: string]: boolean }, supportedLanguages: [] as ISupportedLanguage[] | undefined, findSubscriptionByRid: mem((rid) => Subscriptions.findOne({ rid })), diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index 9e5a0f10f85f..de2625c2b996 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -19,9 +19,11 @@ import { usePinMessageAction } from './usePinMessageAction'; import { useReactionMessageAction } from './useReactionMessageAction'; import { useReplyInThreadMessageAction } from './useReplyInThreadMessageAction'; import { useStarMessageAction } from './useStarMessageAction'; +import { useTranslateAction } from './useTranslateAction'; import { useUnFollowMessageAction } from './useUnFollowMessageAction'; import { useUnpinMessageAction } from './useUnpinMessageAction'; import { useUnstarMessageAction } from './useUnstarMessageAction'; +import { useViewOriginalTranslationAction } from './useViewOriginalTranslationAction'; import { useWebDAVMessageAction } from './useWebDAVMessageAction'; import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; @@ -135,6 +137,8 @@ const MessageToolbar = ({ }); useReactionMessageAction(message, { user, room, subscription }); useMarkAsUnreadMessageAction(message, { user, room, subscription }); + useTranslateAction(message, { user, room, subscription }); + useViewOriginalTranslationAction(message, { user, room, subscription }); const actionsQueryResult = useQuery({ queryKey: roomsQueryKeys.messageActionsWithParameters(room._id, message), diff --git a/apps/meteor/client/components/message/toolbar/useTranslateAction.ts b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts new file mode 100644 index 000000000000..4b43a683ea1f --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts @@ -0,0 +1,73 @@ +import type { IUser, IMessage, ISubscription, IRoom } from '@rocket.chat/core-typings'; +import { useMethod, usePermission, useSetting } from '@rocket.chat/ui-contexts'; +import { useEffect, useMemo } from 'react'; + +import { AutoTranslate } from '../../../../app/autotranslate/client'; +import { Messages } from '../../../../app/models/client'; +import { MessageAction } from '../../../../app/ui-utils/client'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import { hasTranslationLanguageInAttachments, hasTranslationLanguageInMessage } from '../../../views/room/MessageList/lib/autoTranslate'; + +export const useTranslateAction = ( + message: IMessage & { autoTranslateShowInverse?: boolean }, + { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, +) => { + const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false); + const canAutoTranslate = usePermission('auto-translate'); + const translateMessage = useMethod('autoTranslate.translateMessage'); + + const language = useMemo( + () => subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid), + [message.rid, subscription?.autoTranslateLanguage], + ); + const hasTranslations = useMemo( + () => hasTranslationLanguageInMessage(message, language) || hasTranslationLanguageInAttachments(message.attachments, language), + [message, language], + ); + + useEffect(() => { + if (!autoTranslateEnabled || !canAutoTranslate || !user) { + return; + } + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); + const isDifferentUser = message?.u && message.u._id !== user._id; + const autoTranslationActive = subscription?.autoTranslate || isLivechatRoom; + + if (!message.autoTranslateShowInverse && (!isDifferentUser || !autoTranslationActive || hasTranslations)) { + return; + } + + MessageAction.addButton({ + id: 'translate', + icon: 'language', + label: 'Translate', + context: ['message', 'message-mobile', 'threads'], + type: 'interaction', + action() { + if (!hasTranslations) { + AutoTranslate.messageIdsToWait[message._id] = true; + Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); + void translateMessage(message, language); + } + const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; + Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); + }, + order: 90, + }); + + return () => { + MessageAction.removeButton('translate'); + }; + }, [ + autoTranslateEnabled, + canAutoTranslate, + hasTranslations, + language, + message, + room?.t, + subscription?.autoTranslate, + translateMessage, + user, + ]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts new file mode 100644 index 000000000000..519074a2dfeb --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts @@ -0,0 +1,73 @@ +import type { IMessage, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; +import { useMethod, usePermission, useSetting } from '@rocket.chat/ui-contexts'; +import { useEffect, useMemo } from 'react'; + +import { AutoTranslate } from '../../../../app/autotranslate/client'; +import { Messages } from '../../../../app/models/client'; +import { MessageAction } from '../../../../app/ui-utils/client'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import { hasTranslationLanguageInAttachments, hasTranslationLanguageInMessage } from '../../../views/room/MessageList/lib/autoTranslate'; + +export const useViewOriginalTranslationAction = ( + message: IMessage & { autoTranslateShowInverse?: boolean }, + { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, +) => { + const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false); + const canAutoTranslate = usePermission('auto-translate'); + const translateMessage = useMethod('autoTranslate.translateMessage'); + + const language = useMemo( + () => subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid), + [message.rid, subscription?.autoTranslateLanguage], + ); + const hasTranslations = useMemo( + () => hasTranslationLanguageInMessage(message, language) || hasTranslationLanguageInAttachments(message.attachments, language), + [message, language], + ); + + useEffect(() => { + if (!autoTranslateEnabled || !canAutoTranslate || !user) { + return; + } + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); + const isDifferentUser = message?.u && message.u._id !== user._id; + const autoTranslationActive = subscription?.autoTranslate || isLivechatRoom; + + if (message.autoTranslateShowInverse || !isDifferentUser || !autoTranslationActive || !hasTranslations) { + return; + } + + MessageAction.addButton({ + id: 'view-original', + icon: 'language', + label: 'View_original', + context: ['message', 'message-mobile', 'threads'], + type: 'interaction', + action() { + if (!hasTranslations) { + AutoTranslate.messageIdsToWait[message._id] = true; + Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); + void translateMessage(message, language); + } + const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; + Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); + }, + order: 90, + }); + + return () => { + MessageAction.removeButton('view-original'); + }; + }, [ + autoTranslateEnabled, + canAutoTranslate, + hasTranslations, + language, + message, + room?.t, + subscription?.autoTranslate, + translateMessage, + user, + ]); +}; From 3d7fc28039a11d581701e15ec873e04b93b07cc0 Mon Sep 17 00:00:00 2001 From: Tasso Date: Thu, 5 Dec 2024 22:21:27 -0300 Subject: [PATCH 02/10] Move more actions to hooks --- apps/meteor/app/ui-utils/client/index.ts | 2 - .../client/lib/messageActionDefault.ts | 256 ------------------ .../message/toolbar/MessageToolbar.tsx | 18 ++ .../message/toolbar/useCopyAction.ts | 43 +++ .../message/toolbar/useDeleteMessageAction.ts | 50 ++++ .../message/toolbar/useEditMessageAction.ts | 70 +++++ .../toolbar/useForwardMessageAction.tsx | 41 +++ .../message/toolbar/useQuoteMessageAction.ts | 43 +++ .../toolbar/useReadReceiptsDetailsAction.tsx | 43 +++ .../message/toolbar/useReplyInDMAction.ts | 59 ++++ .../toolbar/useReportMessageAction.tsx | 58 ++++ .../toolbar/useShowMessageReactionsAction.tsx | 40 +++ apps/meteor/client/startup/index.ts | 1 - apps/meteor/client/startup/readReceipt.ts | 33 --- 14 files changed, 465 insertions(+), 292 deletions(-) delete mode 100644 apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts create mode 100644 apps/meteor/client/components/message/toolbar/useCopyAction.ts create mode 100644 apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts create mode 100644 apps/meteor/client/components/message/toolbar/useEditMessageAction.ts create mode 100644 apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx create mode 100644 apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts create mode 100644 apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx create mode 100644 apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts create mode 100644 apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx create mode 100644 apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx delete mode 100644 apps/meteor/client/startup/readReceipt.ts diff --git a/apps/meteor/app/ui-utils/client/index.ts b/apps/meteor/app/ui-utils/client/index.ts index 6409db5a3592..b2f9e4264162 100644 --- a/apps/meteor/app/ui-utils/client/index.ts +++ b/apps/meteor/app/ui-utils/client/index.ts @@ -1,5 +1,3 @@ -import './lib/messageActionDefault'; - export { MessageAction } from './lib/MessageAction'; export { messageBox } from './lib/messageBox'; export { LegacyRoomManager } from './lib/LegacyRoomManager'; diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts deleted file mode 100644 index 1301863b7e53..000000000000 --- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts +++ /dev/null @@ -1,256 +0,0 @@ -import type { IMessage } from '@rocket.chat/core-typings'; -import { isE2EEMessage, isRoomFederated } from '@rocket.chat/core-typings'; -import { Meteor } from 'meteor/meteor'; -import moment from 'moment'; - -import { MessageAction } from './MessageAction'; -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'; -import { router } from '../../../../client/providers/RouterProvider'; -import ForwardMessageModal from '../../../../client/views/room/modals/ForwardMessageModal/ForwardMessageModal'; -import ReactionListModal from '../../../../client/views/room/modals/ReactionListModal'; -import ReportMessageModal from '../../../../client/views/room/modals/ReportMessageModal'; -import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/client'; -import { Rooms, Subscriptions } from '../../../models/client'; -import { t } from '../../../utils/lib/i18n'; - -const getMainMessageText = (message: IMessage): IMessage => { - const newMessage = { ...message }; - newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || ''; - newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined; - return { ...newMessage }; -}; - -Meteor.startup(async () => { - MessageAction.addButton({ - id: 'reply-directly', - icon: 'reply-directly', - label: 'Reply_in_direct_message', - context: ['message', 'message-mobile', 'threads', 'federated'], - role: 'link', - type: 'communication', - action(_, { message }) { - roomCoordinator.openRouteLink( - 'd', - { name: message.u.username }, - { - ...router.getSearchParameters(), - reply: message._id, - }, - ); - }, - condition({ subscription, room, message, user }) { - if (subscription == null) { - return false; - } - if (room.t === 'd' || room.t === 'l') { - return false; - } - - // Check if we already have a DM started with the message user (not ourselves) or we can start one - if (!!user && user._id !== message.u._id && !hasPermission('create-d')) { - const dmRoom = Rooms.findOne({ _id: [user._id, message.u._id].sort().join('') }); - if (!dmRoom || !Subscriptions.findOne({ 'rid': dmRoom._id, 'u._id': user._id })) { - return false; - } - } - - return true; - }, - order: 0, - group: 'menu', - disabled({ message }) { - return isE2EEMessage(message); - }, - }); - - MessageAction.addButton({ - id: 'forward-message', - icon: 'arrow-forward', - label: 'Forward_message', - context: ['message', 'message-mobile', 'threads'], - type: 'communication', - async action(_, { message }) { - const permalink = await getPermaLink(message._id); - imperativeModal.open({ - component: ForwardMessageModal, - props: { - message, - permalink, - onClose: (): void => { - imperativeModal.close(); - }, - }, - }); - }, - order: 0, - group: 'message', - disabled({ message }) { - return isE2EEMessage(message); - }, - }); - - MessageAction.addButton({ - id: 'quote-message', - icon: 'quote', - label: 'Quote', - context: ['message', 'message-mobile', 'threads', 'federated'], - async action(_, { message, chat, autoTranslateOptions }) { - if (message && autoTranslateOptions?.autoTranslateEnabled && autoTranslateOptions.showAutoTranslate(message)) { - message.msg = - message.translations && autoTranslateOptions.autoTranslateLanguage - ? message.translations[autoTranslateOptions.autoTranslateLanguage] - : message.msg; - } - - await chat?.composer?.quoteMessage(message); - }, - condition({ subscription }) { - if (subscription == null) { - return false; - } - - return true; - }, - order: -2, - group: 'message', - }); - - MessageAction.addButton({ - id: 'copy', - icon: 'copy', - label: 'Copy_text', - // classes: 'clipboard', - context: ['message', 'message-mobile', 'threads', 'federated'], - type: 'duplication', - async action(_, { message }) { - const msgText = getMainMessageText(message).msg; - await navigator.clipboard.writeText(msgText); - dispatchToastMessage({ type: 'success', message: t('Copied') }); - }, - condition({ subscription }) { - return !!subscription; - }, - order: 6, - group: 'menu', - }); - - MessageAction.addButton({ - id: 'edit-message', - icon: 'edit', - label: 'Edit', - context: ['message', 'message-mobile', 'threads', 'federated'], - type: 'management', - async action(_, { message, chat }) { - await chat?.messageEditing.editMessage(message); - }, - condition({ message, subscription, settings, room, user }) { - if (subscription == null) { - return false; - } - if (isRoomFederated(room)) { - return message.u._id === user?._id; - } - const canEditMessage = hasAtLeastOnePermission('edit-message', message.rid); - const isEditAllowed = settings.Message_AllowEditing; - const editOwn = message.u && message.u._id === user?._id; - if (!(canEditMessage || (isEditAllowed && editOwn))) { - return false; - } - const blockEditInMinutes = settings.Message_AllowEditing_BlockEditInMinutes as number; - const bypassBlockTimeLimit = hasPermission('bypass-time-limit-edit-and-delete', message.rid); - - if (!bypassBlockTimeLimit && blockEditInMinutes) { - let msgTs; - if (message.ts != null) { - msgTs = moment(message.ts); - } - let currentTsDiff; - if (msgTs != null) { - currentTsDiff = moment().diff(msgTs, 'minutes'); - } - return (!!currentTsDiff || currentTsDiff === 0) && currentTsDiff < blockEditInMinutes; - } - return true; - }, - order: 8, - group: 'menu', - }); - - MessageAction.addButton({ - id: 'delete-message', - icon: 'trash', - label: 'Delete', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - color: 'alert', - type: 'management', - async action(_, { message, chat }) { - await chat?.flows.requestMessageDeletion(message); - }, - condition({ message, subscription, room, chat, user }) { - if (!subscription) { - return false; - } - if (isRoomFederated(room)) { - return message.u._id === user?._id; - } - const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); - if (isLivechatRoom) { - return false; - } - - return chat?.data.canDeleteMessage(message) ?? false; - }, - order: 10, - group: 'menu', - }); - - MessageAction.addButton({ - id: 'report-message', - icon: 'report', - label: 'Report', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - color: 'alert', - type: 'management', - action(_, { message }) { - imperativeModal.open({ - component: ReportMessageModal, - props: { - message: getMainMessageText(message), - onClose: imperativeModal.close, - }, - }); - }, - condition({ subscription, room, message, user }) { - const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); - if (isLivechatRoom || message.u._id === user?._id) { - return false; - } - - return Boolean(subscription); - }, - order: 9, - group: 'menu', - }); - - MessageAction.addButton({ - id: 'reaction-list', - icon: 'emoji', - label: 'Reactions', - context: ['message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], - type: 'interaction', - action(_, { message: { reactions = {} } }) { - imperativeModal.open({ - component: ReactionListModal, - props: { reactions, onClose: imperativeModal.close }, - }); - }, - condition({ message: { reactions } }) { - return !!reactions; - }, - order: 9, - group: 'menu', - }); -}); diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index de2625c2b996..5111c597b93b 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -10,14 +10,23 @@ import React, { memo, useMemo, useRef } from 'react'; import MessageActionMenu from './MessageActionMenu'; import MessageToolbarStarsActionMenu from './MessageToolbarStarsActionMenu'; +import { useCopyAction } from './useCopyAction'; +import { useDeleteMessageAction } from './useDeleteMessageAction'; +import { useEditMessageAction } from './useEditMessageAction'; import { useFollowMessageAction } from './useFollowMessageAction'; +import { useForwardMessageAction } from './useForwardMessageAction'; import { useJumpToMessageContextAction } from './useJumpToMessageContextAction'; import { useMarkAsUnreadMessageAction } from './useMarkAsUnreadMessageAction'; import { useNewDiscussionMessageAction } from './useNewDiscussionMessageAction'; import { usePermalinkAction } from './usePermalinkAction'; import { usePinMessageAction } from './usePinMessageAction'; +import { useQuoteMessageAction } from './useQuoteMessageAction'; import { useReactionMessageAction } from './useReactionMessageAction'; +import { useReadReceiptsDetailsAction } from './useReadReceiptsDetailsAction'; +import { useReplyInDMAction } from './useReplyInDMAction'; import { useReplyInThreadMessageAction } from './useReplyInThreadMessageAction'; +import { useReportMessageAction } from './useReportMessageAction'; +import { useShowMessageReactionsAction } from './useShowMessageReactionsAction'; import { useStarMessageAction } from './useStarMessageAction'; import { useTranslateAction } from './useTranslateAction'; import { useUnFollowMessageAction } from './useUnFollowMessageAction'; @@ -139,6 +148,15 @@ const MessageToolbar = ({ useMarkAsUnreadMessageAction(message, { user, room, subscription }); useTranslateAction(message, { user, room, subscription }); useViewOriginalTranslationAction(message, { user, room, subscription }); + useReplyInDMAction(message, { user, room, subscription }); + useForwardMessageAction(message); + useQuoteMessageAction(message, { subscription }); + useCopyAction(message, { subscription }); + useEditMessageAction(message, { user, room, subscription }); + useDeleteMessageAction(message, { user, room, subscription }); + useReportMessageAction(message, { user, room, subscription }); + useShowMessageReactionsAction(message); + useReadReceiptsDetailsAction(message); const actionsQueryResult = useQuery({ queryKey: roomsQueryKeys.messageActionsWithParameters(room._id, message), diff --git a/apps/meteor/client/components/message/toolbar/useCopyAction.ts b/apps/meteor/client/components/message/toolbar/useCopyAction.ts new file mode 100644 index 000000000000..47c817ab68a7 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useCopyAction.ts @@ -0,0 +1,43 @@ +import type { IMessage, ISubscription } from '@rocket.chat/core-typings'; +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { MessageAction } from '../../../../app/ui-utils/client'; + +const getMainMessageText = (message: IMessage): IMessage => { + const newMessage = { ...message }; + newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || ''; + newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined; + return { ...newMessage }; +}; + +export const useCopyAction = (message: IMessage, { subscription }: { subscription: ISubscription | undefined }) => { + const { t } = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + useEffect(() => { + if (!subscription) { + return; + } + + MessageAction.addButton({ + id: 'copy', + icon: 'copy', + label: 'Copy_text', + context: ['message', 'message-mobile', 'threads', 'federated'], + type: 'duplication', + async action(_, { message }) { + const msgText = getMainMessageText(message).msg; + await navigator.clipboard.writeText(msgText); + dispatchToastMessage({ type: 'success', message: t('Copied') }); + }, + order: 6, + group: 'menu', + }); + + return () => { + MessageAction.removeButton('copy'); + }; + }, [dispatchToastMessage, message, subscription, t]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts b/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts new file mode 100644 index 000000000000..f6e120428b82 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts @@ -0,0 +1,50 @@ +import { isRoomFederated } from '@rocket.chat/core-typings'; +import type { ISubscription, IUser, IRoom, IMessage } from '@rocket.chat/core-typings'; +import { useEffect } from 'react'; + +import { MessageAction } from '../../../../app/ui-utils/client'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import { useChat } from '../../../views/room/contexts/ChatContext'; + +export const useDeleteMessageAction = ( + message: IMessage, + { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, +) => { + const chat = useChat(); + + useEffect(() => { + if (!subscription) { + return; + } + + MessageAction.addButton({ + id: 'delete-message', + icon: 'trash', + label: 'Delete', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + color: 'alert', + type: 'management', + async action() { + await chat?.flows.requestMessageDeletion(message); + }, + condition() { + if (isRoomFederated(room)) { + return message.u._id === user?._id; + } + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); + if (isLivechatRoom) { + return false; + } + + return chat?.data.canDeleteMessage(message) ?? false; + }, + order: 10, + group: 'menu', + }); + + return () => { + MessageAction.removeButton('delete-message'); + }; + }, [chat?.data, chat?.flows, message, room, subscription, user?._id]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts b/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts new file mode 100644 index 000000000000..8889f33d8509 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts @@ -0,0 +1,70 @@ +import { isRoomFederated } from '@rocket.chat/core-typings'; +import type { IRoom, IUser, IMessage, ISubscription } from '@rocket.chat/core-typings'; +import { usePermission, useSetting } from '@rocket.chat/ui-contexts'; +import moment from 'moment'; +import { useEffect } from 'react'; + +import { MessageAction } from '../../../../app/ui-utils/client'; +import { useChat } from '../../../views/room/contexts/ChatContext'; + +export const useEditMessageAction = ( + message: IMessage, + { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, +) => { + const chat = useChat(); + const isEditAllowed = useSetting('Message_AllowEditing', true); + const canEditMessage = usePermission('edit-message', message.rid); + const blockEditInMinutes = useSetting('Message_AllowEditing_BlockEditInMinutes', 0); + const canBypassBlockTimeLimit = usePermission('bypass-time-limit-edit-and-delete', message.rid); + + useEffect(() => { + if (!subscription) { + return; + } + + MessageAction.addButton({ + id: 'edit-message', + icon: 'edit', + label: 'Edit', + context: ['message', 'message-mobile', 'threads', 'federated'], + type: 'management', + async action() { + await chat?.messageEditing.editMessage(message); + }, + condition() { + if (isRoomFederated(room)) { + return message.u._id === user?._id; + } + + const editOwn = message.u && message.u._id === user?._id; + if (!canEditMessage && (!isEditAllowed || !editOwn)) { + return false; + } + + if (!canBypassBlockTimeLimit && blockEditInMinutes) { + const msgTs = message.ts ? moment(message.ts) : undefined; + const currentTsDiff = msgTs ? moment().diff(msgTs, 'minutes') : undefined; + return typeof currentTsDiff === 'number' && currentTsDiff < blockEditInMinutes; + } + + return true; + }, + order: 8, + group: 'menu', + }); + + return () => { + MessageAction.removeButton('edit-message'); + }; + }, [ + blockEditInMinutes, + canBypassBlockTimeLimit, + canEditMessage, + chat?.messageEditing, + isEditAllowed, + message, + room, + subscription, + user?._id, + ]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx new file mode 100644 index 000000000000..eb51efdef2a0 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx @@ -0,0 +1,41 @@ +import { type IMessage, isE2EEMessage } from '@rocket.chat/core-typings'; +import { useSetModal } from '@rocket.chat/ui-contexts'; +import React, { useEffect } from 'react'; + +import { MessageAction } from '../../../../app/ui-utils/client'; +import { getPermaLink } from '../../../lib/getPermaLink'; +import ForwardMessageModal from '../../../views/room/modals/ForwardMessageModal'; + +export const useForwardMessageAction = (message: IMessage) => { + const encrypted = isE2EEMessage(message); + const setModal = useSetModal(); + + useEffect(() => { + MessageAction.addButton({ + id: 'forward-message', + icon: 'arrow-forward', + label: 'Forward_message', + context: ['message', 'message-mobile', 'threads'], + type: 'communication', + async action() { + const permalink = await getPermaLink(message._id); + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 0, + group: 'message', + disabled: () => encrypted, + }); + + return () => { + MessageAction.removeButton('forward-message'); + }; + }, [encrypted, message, setModal]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts b/apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts new file mode 100644 index 000000000000..8638e731dce1 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts @@ -0,0 +1,43 @@ +import type { IMessage, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings'; +import { useEffect } from 'react'; + +import { MessageAction } from '../../../../app/ui-utils/client'; +import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate'; +import { useChat } from '../../../views/room/contexts/ChatContext'; + +export const useQuoteMessageAction = ( + message: IMessage & Partial, + { subscription }: { subscription: ISubscription | undefined }, +) => { + const chat = useChat(); + const autoTranslateOptions = useAutoTranslate(subscription); + + useEffect(() => { + if (!subscription) { + return; + } + + MessageAction.addButton({ + id: 'quote-message', + icon: 'quote', + label: 'Quote', + context: ['message', 'message-mobile', 'threads', 'federated'], + async action() { + if (message && autoTranslateOptions?.autoTranslateEnabled && autoTranslateOptions.showAutoTranslate(message)) { + message.msg = + message.translations && autoTranslateOptions.autoTranslateLanguage + ? message.translations[autoTranslateOptions.autoTranslateLanguage] + : message.msg; + } + + await chat?.composer?.quoteMessage(message); + }, + order: -2, + group: 'message', + }); + + return () => { + MessageAction.removeButton('quote-message'); + }; + }, [autoTranslateOptions, chat?.composer, message, subscription]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx b/apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx new file mode 100644 index 000000000000..e71bd169359f --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx @@ -0,0 +1,43 @@ +import type { IMessage } from '@rocket.chat/core-typings'; +import { useSetModal, useSetting } from '@rocket.chat/ui-contexts'; +import React, { useEffect } from 'react'; + +import { MessageAction } from '../../../../app/ui-utils/client'; +import ReadReceiptsModal from '../../../views/room/modals/ReadReceiptsModal'; + +export const useReadReceiptsDetailsAction = (message: IMessage) => { + const setModal = useSetModal(); + + const readReceiptsEnabled = useSetting('Message_Read_Receipt_Enabled', false); + const readReceiptsStoreUsers = useSetting('Message_Read_Receipt_Store_Users', false); + + useEffect(() => { + if (!readReceiptsEnabled || !readReceiptsStoreUsers) { + return; + } + + MessageAction.addButton({ + id: 'receipt-detail', + icon: 'check-double', + label: 'Read_Receipts', + context: ['starred', 'message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], + type: 'duplication', + action() { + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 10, + group: 'menu', + }); + + return () => { + MessageAction.removeButton('receipt-detail'); + }; + }, [message._id, readReceiptsEnabled, readReceiptsStoreUsers, setModal]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts new file mode 100644 index 000000000000..5f3b8781dcb7 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts @@ -0,0 +1,59 @@ +import { type IUser, type IMessage, type ISubscription, type IRoom, isE2EEMessage } from '@rocket.chat/core-typings'; +import { usePermission, useRouter } from '@rocket.chat/ui-contexts'; +import { useEffect } from 'react'; + +import { Rooms, Subscriptions } from '../../../../app/models/client'; +import { MessageAction } from '../../../../app/ui-utils/client'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; + +export const useReplyInDMAction = ( + message: IMessage, + { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, +) => { + const router = useRouter(); + const encrypted = isE2EEMessage(message); + const canCreateDM = usePermission('create-d'); + + useEffect(() => { + if (!subscription || room.t === 'd' || room.t === 'l') { + return; + } + + MessageAction.addButton({ + id: 'reply-directly', + icon: 'reply-directly', + label: 'Reply_in_direct_message', + context: ['message', 'message-mobile', 'threads', 'federated'], + role: 'link', + type: 'communication', + action() { + roomCoordinator.openRouteLink( + 'd', + { name: message.u.username }, + { + ...router.getSearchParameters(), + reply: message._id, + }, + ); + }, + condition() { + // Check if we already have a DM started with the message user (not ourselves) or we can start one + if (!!user && user._id !== message.u._id && !canCreateDM) { + const dmRoom = Rooms.findOne({ _id: [user._id, message.u._id].sort().join('') }); + if (!dmRoom || !Subscriptions.findOne({ 'rid': dmRoom._id, 'u._id': user._id })) { + return false; + } + } + + return true; + }, + order: 0, + group: 'menu', + disabled: () => encrypted, + }); + + return () => { + MessageAction.removeButton('reply-directly'); + }; + }, [canCreateDM, encrypted, message._id, message.u._id, message.u.username, room.t, router, subscription, user]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx new file mode 100644 index 000000000000..5a09c436d051 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx @@ -0,0 +1,58 @@ +import type { ISubscription, IUser, IRoom, IMessage } from '@rocket.chat/core-typings'; +import { useSetModal } from '@rocket.chat/ui-contexts'; +import React, { useEffect } from 'react'; + +import { MessageAction } from '../../../../app/ui-utils/client'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import ReportMessageModal from '../../../views/room/modals/ReportMessageModal'; + +const getMainMessageText = (message: IMessage): IMessage => { + const newMessage = { ...message }; + newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || ''; + newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined; + return { ...newMessage }; +}; + +export const useReportMessageAction = ( + message: IMessage, + { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, +) => { + const setModal = useSetModal(); + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); + + useEffect(() => { + if (!subscription) { + return; + } + + if (isLivechatRoom || message.u._id === user?._id) { + return; + } + + MessageAction.addButton({ + id: 'report-message', + icon: 'report', + label: 'Report', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + color: 'alert', + type: 'management', + action() { + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 9, + group: 'menu', + }); + + return () => { + MessageAction.removeButton('report-message'); + }; + }, [isLivechatRoom, message, setModal, subscription, user?._id]); +}; diff --git a/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx b/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx new file mode 100644 index 000000000000..a97f3be6d8f2 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx @@ -0,0 +1,40 @@ +import type { IMessage } from '@rocket.chat/core-typings'; +import { useSetModal } from '@rocket.chat/ui-contexts'; +import React, { useEffect } from 'react'; + +import { MessageAction } from '../../../../app/ui-utils/client'; +import ReactionListModal from '../../../views/room/modals/ReactionListModal'; + +export const useShowMessageReactionsAction = ({ reactions = {} }: IMessage) => { + const setModal = useSetModal(); + + useEffect(() => { + if (!reactions) { + return; + } + + MessageAction.addButton({ + id: 'reaction-list', + icon: 'emoji', + label: 'Reactions', + context: ['message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], + type: 'interaction', + action() { + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 9, + group: 'menu', + }); + + return () => { + MessageAction.removeButton('reaction-list'); + }; + }, [reactions, setModal]); +}; diff --git a/apps/meteor/client/startup/index.ts b/apps/meteor/client/startup/index.ts index 3edff17dc427..7810fc23fc5f 100644 --- a/apps/meteor/client/startup/index.ts +++ b/apps/meteor/client/startup/index.ts @@ -16,7 +16,6 @@ import './messageObserve'; import './messageTypes'; import './notifications'; import './otr'; -import './readReceipt'; import './reloadRoomAfterLogin'; import './roles'; import './rootUrlChange'; diff --git a/apps/meteor/client/startup/readReceipt.ts b/apps/meteor/client/startup/readReceipt.ts deleted file mode 100644 index 5998ea3e6c52..000000000000 --- a/apps/meteor/client/startup/readReceipt.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; - -import { settings } from '../../app/settings/client'; -import { MessageAction } from '../../app/ui-utils/client'; -import { imperativeModal } from '../lib/imperativeModal'; -import ReadReceiptsModal from '../views/room/modals/ReadReceiptsModal'; - -Meteor.startup(() => { - Tracker.autorun(() => { - const enabled = settings.get('Message_Read_Receipt_Enabled') && settings.get('Message_Read_Receipt_Store_Users'); - - if (!enabled) { - return MessageAction.removeButton('receipt-detail'); - } - - MessageAction.addButton({ - id: 'receipt-detail', - icon: 'check-double', - label: 'Read_Receipts', - context: ['starred', 'message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], - type: 'duplication', - action(_, { message }) { - imperativeModal.open({ - component: ReadReceiptsModal, - props: { messageId: message._id, onClose: imperativeModal.close }, - }); - }, - order: 10, - group: 'menu', - }); - }); -}); From 293fcb93e905825ac76867307fc6512cb32cc597 Mon Sep 17 00:00:00 2001 From: Tasso Date: Fri, 6 Dec 2024 11:55:38 -0300 Subject: [PATCH 03/10] Shrink `MessageAction` API --- .../app/ui-utils/client/lib/MessageAction.ts | 60 +++------------ .../message/toolbar/MessageActionMenu.tsx | 11 ++- .../message/toolbar/MessageToolbar.tsx | 59 +++++---------- .../toolbar/MessageToolbarStarsActionMenu.tsx | 16 ++-- .../message/toolbar/useCopyAction.ts | 2 +- .../toolbar/useForwardMessageAction.tsx | 2 +- .../toolbar/useNewDiscussionMessageAction.tsx | 73 ++++++++++--------- .../message/toolbar/usePermalinkAction.ts | 2 +- .../toolbar/useReactionMessageAction.ts | 7 +- .../message/toolbar/useReplyInDMAction.ts | 3 +- .../toolbar/useShowMessageReactionsAction.tsx | 8 +- .../message/toolbar/useTranslateAction.ts | 1 + .../useViewOriginalTranslationAction.ts | 1 + .../toolbar/useWebDAVMessageAction.tsx | 21 ++++-- .../client/hooks/useAppActionButtons.ts | 24 ++++-- 15 files changed, 126 insertions(+), 164 deletions(-) diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 0a5483c4acaa..426caea0ecf9 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -1,12 +1,6 @@ -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 type { ContextType } from 'react'; - -import type { AutoTranslateOptions } from '../../../../client/views/room/MessageList/hooks/useAutoTranslate'; -import type { ChatContext } from '../../../../client/views/room/contexts/ChatContext'; -import type { RoomToolboxContextValue } from '../../../../client/views/room/contexts/RoomToolboxContext'; type MessageActionGroup = 'message' | 'menu'; @@ -25,60 +19,30 @@ export type MessageActionContext = type MessageActionType = 'communication' | 'interaction' | 'duplication' | 'apps' | 'management'; -export type MessageActionConditionProps = { - message: IMessage; - user: IUser | undefined; - room: IRoom; - subscription?: ISubscription; - context?: MessageActionContext; - settings: { [key: string]: SettingValue }; - chat: ContextType; -}; - export type MessageActionConfig = { id: string; icon: IconName; variant?: 'danger' | 'success' | 'warning'; label: TranslationKey; - order?: number; + order: number; /* @deprecated */ - color?: string; - role?: string; - group?: MessageActionGroup | MessageActionGroup[]; + color?: 'alert'; + group: MessageActionGroup; context?: MessageActionContext[]; - action: ( - e: Pick | undefined, - { - message, - tabbar, - room, - chat, - autoTranslateOptions, - }: { - message: IMessage & Partial; - tabbar: RoomToolboxContextValue; - room?: IRoom; - chat: ContextType; - autoTranslateOptions?: AutoTranslateOptions; - }, - ) => any; - condition?: (props: MessageActionConditionProps) => Promise | boolean; + action: (e: Pick | undefined) => any; + condition?: () => Promise | boolean; type?: MessageActionType; - disabled?: (props: MessageActionConditionProps) => boolean; + disabled?: boolean; }; class MessageAction { - public buttons: Record = {}; + private buttons: Record = {}; public addButton(config: MessageActionConfig): void { if (!config?.id) { return; } - if (!config.group) { - config.group = 'menu'; - } - if (config.condition) { config.condition = mem(config.condition, { maxAge: 1000, cacheKey: JSON.stringify }); } @@ -90,19 +54,15 @@ class MessageAction { delete this.buttons[id]; } - public async getAll( - props: MessageActionConditionProps, - context: MessageActionContext, - group: MessageActionGroup, - ): Promise { + public async getAll(context: MessageActionContext, group: MessageActionGroup): Promise { return ( await Promise.all( 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.group === group) .filter((button) => !button.context || button.context.includes(context)) .map(async (button) => { - return [button, !button.condition || (await button.condition({ ...props, context }))] as const; + return [button, !button.condition || (await button.condition())] as const; }), ) ) diff --git a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx index 143c0a3fe46a..dcb2fcaf4699 100644 --- a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx @@ -4,7 +4,7 @@ import type { MouseEvent, ReactElement } from 'react'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import type { MessageActionConditionProps, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; type MessageActionConfigOption = Omit & { action: (e?: MouseEvent) => void; @@ -19,11 +19,10 @@ type MessageActionSection = { type MessageActionMenuProps = { onChangeMenuVisibility: (visible: boolean) => void; options: MessageActionConfigOption[]; - context: MessageActionConditionProps; isMessageEncrypted: boolean; }; -const MessageActionMenu = ({ options, onChangeMenuVisibility, context, isMessageEncrypted }: MessageActionMenuProps): ReactElement => { +const MessageActionMenu = ({ options, onChangeMenuVisibility, isMessageEncrypted }: MessageActionMenuProps): ReactElement => { const { t } = useTranslation(); const id = useUniqueId(); const groupOptions = options @@ -34,9 +33,9 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility, context, isMessage content: t(option.label), onClick: option.action, type: option.type, - ...(option.disabled && { disabled: option?.disabled?.(context) }), - ...(option.disabled && - option?.disabled?.(context) && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), + ...(typeof option.disabled === 'boolean' && { disabled: option.disabled }), + ...(typeof option.disabled === 'boolean' && + option.disabled && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), })) .reduce( (acc, option) => { diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index 5111c597b93b..dcc9d1332b07 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -3,10 +3,10 @@ import type { IMessage, IRoom, ISubscription, ITranslatedMessage } from '@rocket import { isThreadMessage, isRoomFederated, isVideoConfMessage, isE2EEMessage } from '@rocket.chat/core-typings'; import { MessageToolbar as FuselageMessageToolbar, MessageToolbarItem } from '@rocket.chat/fuselage'; import { useFeaturePreview } from '@rocket.chat/ui-client'; -import { useUser, useSettings, useTranslation, useMethod, useLayoutHiddenActions, useSetting } from '@rocket.chat/ui-contexts'; +import { useUser, useTranslation, useMethod, useLayoutHiddenActions, useSetting } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import type { ComponentProps, ReactElement } from 'react'; -import React, { memo, useMemo, useRef } from 'react'; +import React, { memo, useRef } from 'react'; import MessageActionMenu from './MessageActionMenu'; import MessageToolbarStarsActionMenu from './MessageToolbarStarsActionMenu'; @@ -42,9 +42,6 @@ import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; import { roomsQueryKeys } from '../../../lib/queryKeys'; 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 { useRoomToolbox } from '../../../views/room/contexts/RoomToolboxContext'; const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => { if (context) { @@ -84,7 +81,6 @@ const MessageToolbar = ({ }: MessageToolbarProps): ReactElement | null => { const t = useTranslation(); const user = useUser() ?? undefined; - const settings = useSettings(); const isLayoutEmbedded = useEmbeddedLayout(); const toolbarRef = useRef(null); @@ -96,21 +92,18 @@ const MessageToolbar = ({ const context = getMessageContext(message, room, messageContext); - const mapSettings = useMemo(() => Object.fromEntries(settings.map((setting) => [setting._id, setting.value])), [settings]); - - const chat = useChat(); const { quickReactions, addRecentEmoji } = useEmojiPickerData(); - const actionButtonApps = useMessageActionAppsActionButtons(context); + const actionButtonApps = useMessageActionAppsActionButtons(message, context); - const starsAction = useMessageActionAppsActionButtons(context, 'ai'); + const starsAction = useMessageActionAppsActionButtons(message, context, 'ai'); const { messageToolbox: hiddenActions } = useLayoutHiddenActions(); const allowStarring = useSetting('Message_AllowStarring'); // TODO: move this to another place - useWebDAVMessageAction(); - useNewDiscussionMessageAction(); + useWebDAVMessageAction(message, { subscription }); + useNewDiscussionMessageAction(message, { user, room, subscription }); useUnpinMessageAction(message, { room, subscription }); usePinMessageAction(message, { room, subscription }); useStarMessageAction(message, { room, user }); @@ -158,13 +151,11 @@ const MessageToolbar = ({ useShowMessageReactionsAction(message); useReadReceiptsDetailsAction(message); - const actionsQueryResult = useQuery({ + const { isSuccess, data } = useQuery({ queryKey: roomsQueryKeys.messageActionsWithParameters(room._id, message), queryFn: async () => { - 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'); + const toolboxItems = await MessageAction.getAll(context, 'message'); + const menuItems = await MessageAction.getAll(context, 'menu'); return { message: toolboxItems.filter((action) => !hiddenActions.includes(action.id)), @@ -174,17 +165,13 @@ const MessageToolbar = ({ keepPreviousData: true, }); - const toolbox = useRoomToolbox(); - const selecting = useIsSelecting(); - const autoTranslateOptions = useAutoTranslate(subscription); - - if (selecting || (!actionsQueryResult.data?.message.length && !actionsQueryResult.data?.menu.length)) { + if (selecting || (!data?.message.length && !data?.menu.length)) { return null; } - const isReactionAllowed = actionsQueryResult.data?.message.find(({ id }) => id === 'reaction-message'); + const isReactionAllowed = data?.message.find(({ id }) => id === 'reaction-message'); const handleSetReaction = (emoji: string) => { setReaction(`:${emoji}:`, message._id); @@ -198,44 +185,38 @@ const MessageToolbar = ({ quickReactions.slice(0, 3).map(({ emoji, image }) => { return handleSetReaction(emoji)} />; })} - {actionsQueryResult.isSuccess && - actionsQueryResult.data.message.map((action) => ( + {isSuccess && + data.message.map((action) => ( action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })} + onClick={(e): void => action.action(e)} key={action.id} icon={action.icon} - title={ - action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context }) - ? t('Action_not_available_encrypted_content', { action: t(action.label) }) - : t(action.label) - } + title={action?.disabled ? t('Action_not_available_encrypted_content', { action: t(action.label) }) : t(action.label)} data-qa-id={action.label} data-qa-type='message-action-menu' - disabled={action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context })} + disabled={action?.disabled} /> ))} {starsAction.data && starsAction.data.length > 0 && ( ({ ...action, - action: (e) => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions }), + action: (e) => action.action(e), }))} onChangeMenuVisibility={onChangeMenuVisibility} data-qa-type='message-action-stars-menu-options' - context={{ message, room, user, subscription, settings: mapSettings, chat, context }} isMessageEncrypted={isE2EEMessage(message)} /> )} - {actionsQueryResult.isSuccess && actionsQueryResult.data.menu.length > 0 && ( + {isSuccess && data.menu.length > 0 && ( ({ + options={[...data?.menu, ...(actionButtonApps.data ?? [])].filter(Boolean).map((action) => ({ ...action, - action: (e) => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions }), + action: (e) => action.action(e), }))} onChangeMenuVisibility={onChangeMenuVisibility} data-qa-type='message-action-menu-options' - context={{ message, room, user, subscription, settings: mapSettings, chat, context }} isMessageEncrypted={isE2EEMessage(message)} /> )} diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx index 44f9bc558ddd..198e84ec8d97 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx @@ -4,7 +4,7 @@ import type { MouseEvent, ReactElement } from 'react'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import type { MessageActionConditionProps, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; type MessageActionConfigOption = Omit & { action: (e?: MouseEvent) => void; @@ -19,16 +19,10 @@ type MessageActionSection = { type MessageActionMenuProps = { onChangeMenuVisibility: (visible: boolean) => void; options: MessageActionConfigOption[]; - context: MessageActionConditionProps; isMessageEncrypted: boolean; }; -const MessageToolbarStarsActionMenu = ({ - options, - onChangeMenuVisibility, - context, - isMessageEncrypted, -}: MessageActionMenuProps): ReactElement => { +const MessageToolbarStarsActionMenu = ({ options, onChangeMenuVisibility, isMessageEncrypted }: MessageActionMenuProps): ReactElement => { const { t } = useTranslation(); const id = useUniqueId(); @@ -40,9 +34,9 @@ const MessageToolbarStarsActionMenu = ({ content: t(option.label), onClick: option.action, type: option.type, - ...(option.disabled && { disabled: option?.disabled?.(context) }), - ...(option.disabled && - option?.disabled?.(context) && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), + ...(typeof option.disabled === 'boolean' && { disabled: option.disabled }), + ...(typeof option.disabled === 'boolean' && + option.disabled && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), }; const group = option.type || ''; diff --git a/apps/meteor/client/components/message/toolbar/useCopyAction.ts b/apps/meteor/client/components/message/toolbar/useCopyAction.ts index 47c817ab68a7..1604eb0308bd 100644 --- a/apps/meteor/client/components/message/toolbar/useCopyAction.ts +++ b/apps/meteor/client/components/message/toolbar/useCopyAction.ts @@ -27,7 +27,7 @@ export const useCopyAction = (message: IMessage, { subscription }: { subscriptio label: 'Copy_text', context: ['message', 'message-mobile', 'threads', 'federated'], type: 'duplication', - async action(_, { message }) { + async action() { const msgText = getMainMessageText(message).msg; await navigator.clipboard.writeText(msgText); dispatchToastMessage({ type: 'success', message: t('Copied') }); diff --git a/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx index eb51efdef2a0..ca7fc4a32976 100644 --- a/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx @@ -31,7 +31,7 @@ export const useForwardMessageAction = (message: IMessage) => { }, order: 0, group: 'message', - disabled: () => encrypted, + disabled: encrypted, }); return () => { diff --git a/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx index 2812e1c06ba8..802f7ec56af9 100644 --- a/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx @@ -1,27 +1,60 @@ -import { useSetModal, useSetting } from '@rocket.chat/ui-contexts'; +import type { IMessage, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; +import { usePermission, useSetModal, useSetting } from '@rocket.chat/ui-contexts'; import React, { useEffect } from 'react'; -import { hasPermission } from '../../../../app/authorization/client'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import CreateDiscussion from '../../CreateDiscussion'; -export const useNewDiscussionMessageAction = () => { +export const useNewDiscussionMessageAction = ( + message: IMessage, + { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, +) => { const enabled = useSetting('Discussion_enabled', false); const setModal = useSetModal(); + const canStartDiscussion = usePermission('start-discussion', room._id); + const canStartDiscussionOtherUser = usePermission('start-discussion-other-user', room._id); + useEffect(() => { if (!enabled) { - return MessageAction.removeButton('start-discussion'); + return; + } + + const { + u: { _id: uid }, + drid, + dcount, + } = message; + if (drid || !Number.isNaN(Number(dcount))) { + return; + } + + if (!subscription) { + return; + } + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); + if (isLivechatRoom) { + return; + } + + if (!user) { + return; + } + + if (!(uid !== user._id ? canStartDiscussionOtherUser : canStartDiscussion)) { + return; } + MessageAction.addButton({ id: 'start-discussion', icon: 'discussion', label: 'Discussion_start', type: 'communication', context: ['message', 'message-mobile', 'videoconf'], - async action(_, { message, room }) { + async action() { setModal( { />, ); }, - condition({ - message: { - u: { _id: uid }, - drid, - dcount, - }, - room, - subscription, - user, - }) { - if (drid || !Number.isNaN(Number(dcount))) { - return false; - } - if (!subscription) { - return false; - } - const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); - if (isLivechatRoom) { - return false; - } - - if (!user) { - return false; - } - - return uid !== user._id ? hasPermission('start-discussion-other-user', room._id) : hasPermission('start-discussion', room._id); - }, order: 1, group: 'menu', }); + return () => { MessageAction.removeButton('start-discussion'); }; - }, [enabled, setModal]); + }, [canStartDiscussion, canStartDiscussionOtherUser, enabled, message, room?._id, room?.prid, room.t, setModal, subscription, user]); }; diff --git a/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts b/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts index 78a197d5c5d7..a1335e958a7c 100644 --- a/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts +++ b/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts @@ -42,7 +42,7 @@ export const usePermalinkAction = ( }, order, group: 'menu', - disabled: () => encrypted, + disabled: encrypted, }); return () => { diff --git a/apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts b/apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts index 3456a01204a4..6f6c764c99ab 100644 --- a/apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts @@ -5,11 +5,14 @@ import { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client'; import { sdk } from '../../../../app/utils/client/lib/SDKClient'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import { useChat } from '../../../views/room/contexts/ChatContext'; export const useReactionMessageAction = ( message: IMessage, { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, ) => { + const chat = useChat(); + useEffect(() => { if (!room || isOmnichannelRoom(room) || !subscription || message.private || !user) { return; @@ -24,7 +27,7 @@ export const useReactionMessageAction = ( icon: 'add-reaction', label: 'Add_Reaction', context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - action(event, { message, chat }) { + action(event) { event?.stopPropagation(); chat?.emojiPicker.open(event?.currentTarget as Element, (emoji) => sdk.call('setReaction', `:${emoji}:`, message._id)); }, @@ -35,5 +38,5 @@ export const useReactionMessageAction = ( return () => { MessageAction.removeButton('reaction-message'); }; - }, [message.private, room, subscription, user]); + }, [chat?.emojiPicker, message._id, message.private, room, subscription, user]); }; diff --git a/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts index 5f3b8781dcb7..cec1de1b0ca6 100644 --- a/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts +++ b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts @@ -24,7 +24,6 @@ export const useReplyInDMAction = ( icon: 'reply-directly', label: 'Reply_in_direct_message', context: ['message', 'message-mobile', 'threads', 'federated'], - role: 'link', type: 'communication', action() { roomCoordinator.openRouteLink( @@ -49,7 +48,7 @@ export const useReplyInDMAction = ( }, order: 0, group: 'menu', - disabled: () => encrypted, + disabled: encrypted, }); return () => { diff --git a/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx b/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx index a97f3be6d8f2..4f4b006fea5e 100644 --- a/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx @@ -5,11 +5,11 @@ import React, { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client'; import ReactionListModal from '../../../views/room/modals/ReactionListModal'; -export const useShowMessageReactionsAction = ({ reactions = {} }: IMessage) => { +export const useShowMessageReactionsAction = (message: IMessage) => { const setModal = useSetModal(); useEffect(() => { - if (!reactions) { + if (!message.reactions) { return; } @@ -22,7 +22,7 @@ export const useShowMessageReactionsAction = ({ reactions = {} }: IMessage) => { action() { setModal( { setModal(null); }} @@ -36,5 +36,5 @@ export const useShowMessageReactionsAction = ({ reactions = {} }: IMessage) => { return () => { MessageAction.removeButton('reaction-list'); }; - }, [reactions, setModal]); + }, [message.reactions, setModal]); }; diff --git a/apps/meteor/client/components/message/toolbar/useTranslateAction.ts b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts index 4b43a683ea1f..ba81ae9de82a 100644 --- a/apps/meteor/client/components/message/toolbar/useTranslateAction.ts +++ b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts @@ -44,6 +44,7 @@ export const useTranslateAction = ( label: 'Translate', context: ['message', 'message-mobile', 'threads'], type: 'interaction', + group: 'menu', action() { if (!hasTranslations) { AutoTranslate.messageIdsToWait[message._id] = true; diff --git a/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts index 519074a2dfeb..25f32857de45 100644 --- a/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts +++ b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts @@ -44,6 +44,7 @@ export const useViewOriginalTranslationAction = ( label: 'View_original', context: ['message', 'message-mobile', 'threads'], type: 'interaction', + group: 'menu', action() { if (!hasTranslations) { AutoTranslate.messageIdsToWait[message._id] = true; diff --git a/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx index 166872acaa42..082985710fc9 100644 --- a/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx @@ -1,3 +1,4 @@ +import type { IMessage, ISubscription } from '@rocket.chat/core-typings'; import { useSetModal, useSetting } from '@rocket.chat/ui-contexts'; import React, { useEffect } from 'react'; @@ -6,7 +7,7 @@ import { getURL } from '../../../../app/utils/client'; import { useWebDAVAccountIntegrationsQuery } from '../../../hooks/webdav/useWebDAVAccountIntegrationsQuery'; import SaveToWebdavModal from '../../../views/room/webdav/SaveToWebdavModal'; -export const useWebDAVMessageAction = () => { +export const useWebDAVMessageAction = (message: IMessage, { subscription }: { subscription: ISubscription | undefined }) => { const enabled = useSetting('Webdav_Integration_Enabled', false); const { data } = useWebDAVAccountIntegrationsQuery({ enabled }); @@ -14,7 +15,7 @@ export const useWebDAVMessageAction = () => { const setModal = useSetModal(); useEffect(() => { - if (!enabled) { + if (!enabled || !subscription || !data?.length || !message.file) { return; } @@ -22,14 +23,18 @@ export const useWebDAVMessageAction = () => { id: 'webdav-upload', icon: 'upload', label: 'Save_To_Webdav', - condition: ({ message, subscription }) => { - return !!subscription && !!data?.length && !!message.file; - }, - action(_, { message }) { + action() { const [attachment] = message.attachments || []; const url = getURL(attachment.title_link as string, { full: true }); - setModal( setModal(undefined)} />); + setModal( + { + setModal(null); + }} + />, + ); }, order: 100, group: 'menu', @@ -38,5 +43,5 @@ export const useWebDAVMessageAction = () => { return () => { MessageAction.removeButton('webdav-upload'); }; - }, [data?.length, enabled, setModal]); + }, [data?.length, enabled, message.attachments, message.file, setModal, subscription]); }; diff --git a/apps/meteor/client/hooks/useAppActionButtons.ts b/apps/meteor/client/hooks/useAppActionButtons.ts index 0dcb2d380d46..692df862470f 100644 --- a/apps/meteor/client/hooks/useAppActionButtons.ts +++ b/apps/meteor/client/hooks/useAppActionButtons.ts @@ -1,4 +1,5 @@ import { type IUIActionButton, type UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui'; +import type { IMessage } from '@rocket.chat/core-typings'; import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks'; import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useEndpoint, useStream, useToastMessageDispatch, useUserId } from '@rocket.chat/ui-contexts'; @@ -161,7 +162,7 @@ export const useUserDropdownAppsActionButtons = () => { } as UseQueryResult; }; -export const useMessageActionAppsActionButtons = (context?: MessageActionContext, category?: string) => { +export const useMessageActionAppsActionButtons = (message: IMessage, context?: MessageActionContext, category?: string) => { const result = useAppActionButtons('messageAction'); const actionManager = useUiKitActionManager(); const applyButtonFilters = useApplyButtonFilters(category); @@ -185,13 +186,14 @@ export const useMessageActionAppsActionButtons = (context?: MessageActionContext order: 7, type: 'apps', variant: action.variant, - action: (_, params) => { + group: 'menu', + action: () => { void actionManager .emitInteraction(action.appId, { type: 'actionButton', - rid: params.message.rid, - tmid: params.message.tmid, - mid: params.message._id, + rid: message.rid, + tmid: message.tmid, + mid: message._id, actionId: action.actionId, payload: { context: action.context }, }) @@ -211,7 +213,17 @@ export const useMessageActionAppsActionButtons = (context?: MessageActionContext return item; }), - [actionManager, applyButtonFilters, dispatchToastMessage, filterActionsByContext, result.data, t], + [ + actionManager, + applyButtonFilters, + dispatchToastMessage, + filterActionsByContext, + message._id, + message.rid, + message.tmid, + result.data, + t, + ], ); return { ...result, From a65a3c9d366f5d8d8a4c059267d248559901c234 Mon Sep 17 00:00:00 2001 From: Tasso Date: Sun, 8 Dec 2024 11:45:09 -0300 Subject: [PATCH 04/10] Fix router bug --- apps/meteor/client/providers/RouterProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/providers/RouterProvider.tsx b/apps/meteor/client/providers/RouterProvider.tsx index 590c5f20da57..1fab7713a5b2 100644 --- a/apps/meteor/client/providers/RouterProvider.tsx +++ b/apps/meteor/client/providers/RouterProvider.tsx @@ -46,7 +46,7 @@ const subscribeToRouteChange = (onRouteChange: () => void): (() => void) => { }; }; -const getLocationPathname = () => FlowRouter.current().path as LocationPathname; +const getLocationPathname = () => FlowRouter.current().path.replace(/\?.*/, '') as LocationPathname; const getLocationSearch = () => location.search as LocationSearch; From ea559add0f0f06d458ccf3038c9ffd7bb3f2a416 Mon Sep 17 00:00:00 2001 From: Tasso Date: Mon, 9 Dec 2024 11:58:25 -0300 Subject: [PATCH 05/10] Minor issue --- .../message/toolbar/MessageToolbar.tsx | 45 ++++++++++--------- .../toolbar/useReplyInThreadMessageAction.ts | 4 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index dcc9d1332b07..874bdd645f0f 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -98,28 +98,14 @@ const MessageToolbar = ({ const starsAction = useMessageActionAppsActionButtons(message, context, 'ai'); - const { messageToolbox: hiddenActions } = useLayoutHiddenActions(); - const allowStarring = useSetting('Message_AllowStarring'); + const hiddenActions = useLayoutHiddenActions().messageToolbox; + const allowStarring = useSetting('Message_AllowStarring', true); // TODO: move this to another place - useWebDAVMessageAction(message, { subscription }); - useNewDiscussionMessageAction(message, { user, room, subscription }); - useUnpinMessageAction(message, { room, subscription }); - usePinMessageAction(message, { room, subscription }); - useStarMessageAction(message, { room, user }); - useUnstarMessageAction(message, { room, user }); - usePermalinkAction(message, { subscription, id: 'permalink-star', context: ['starred'], order: 10 }); - usePermalinkAction(message, { subscription, id: 'permalink-pinned', context: ['pinned'], order: 5 }); - usePermalinkAction(message, { - subscription, - id: 'permalink', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - type: 'duplication', - order: 5, - }); - useFollowMessageAction(message, { room, user, context }); - useUnFollowMessageAction(message, { room, user, context }); + useReactionMessageAction(message, { user, room, subscription }); + useQuoteMessageAction(message, { subscription }); useReplyInThreadMessageAction(message, { room, subscription }); + useForwardMessageAction(message); useJumpToMessageContextAction(message, { id: 'jump-to-message', order: 100, @@ -137,13 +123,28 @@ const MessageToolbar = ({ order: 100, context: ['starred'], }); - useReactionMessageAction(message, { user, room, subscription }); + + useWebDAVMessageAction(message, { subscription }); + useNewDiscussionMessageAction(message, { user, room, subscription }); + useUnpinMessageAction(message, { room, subscription }); + usePinMessageAction(message, { room, subscription }); + useStarMessageAction(message, { room, user }); + useUnstarMessageAction(message, { room, user }); + usePermalinkAction(message, { subscription, id: 'permalink-star', context: ['starred'], order: 10 }); + usePermalinkAction(message, { subscription, id: 'permalink-pinned', context: ['pinned'], order: 5 }); + usePermalinkAction(message, { + subscription, + id: 'permalink', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + type: 'duplication', + order: 5, + }); + useFollowMessageAction(message, { room, user, context }); + useUnFollowMessageAction(message, { room, user, context }); useMarkAsUnreadMessageAction(message, { user, room, subscription }); useTranslateAction(message, { user, room, subscription }); useViewOriginalTranslationAction(message, { user, room, subscription }); useReplyInDMAction(message, { user, room, subscription }); - useForwardMessageAction(message); - useQuoteMessageAction(message, { subscription }); useCopyAction(message, { subscription }); useEditMessageAction(message, { user, room, subscription }); useDeleteMessageAction(message, { user, room, subscription }); diff --git a/apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts b/apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts index 1920ae68dd36..0fae8e99029b 100644 --- a/apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts @@ -41,6 +41,8 @@ export const useReplyInThreadMessageAction = ( group: 'message', }); - return () => MessageAction.removeButton('unfollow-message'); + return () => { + MessageAction.removeButton('reply-in-thread'); + }; }, [message._id, message.tmid, room, route, subscription, threadsEnabled]); }; From ec79e226225831ac4aee04cc9c5c3508c4ce25cf Mon Sep 17 00:00:00 2001 From: Tasso Date: Tue, 10 Dec 2024 01:04:28 -0300 Subject: [PATCH 06/10] Lift message selecting logic --- .../client/components/message/toolbar/MessageToolbar.tsx | 8 +------- .../message/toolbar/useJumpToMessageContextAction.tsx | 6 +++--- .../client/components/message/variants/RoomMessage.tsx | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index 874bdd645f0f..e64acfc40c5a 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -41,7 +41,6 @@ import { useMessageActionAppsActionButtons } from '../../../hooks/useAppActionBu import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; import { roomsQueryKeys } from '../../../lib/queryKeys'; import EmojiElement from '../../../views/composer/EmojiPicker/EmojiElement'; -import { useIsSelecting } from '../../../views/room/MessageList/contexts/SelectedMessagesContext'; const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => { if (context) { @@ -108,19 +107,16 @@ const MessageToolbar = ({ useForwardMessageAction(message); useJumpToMessageContextAction(message, { id: 'jump-to-message', - order: 100, context: ['mentions', 'threads', 'videoconf-threads', 'message-mobile', 'search'], }); useJumpToMessageContextAction(message, { id: 'jump-to-pin-message', - order: 100, hidden: !subscription, context: ['pinned', 'direct'], }); useJumpToMessageContextAction(message, { id: 'jump-to-star-message', hidden: !allowStarring || !subscription, - order: 100, context: ['starred'], }); @@ -166,9 +162,7 @@ const MessageToolbar = ({ keepPreviousData: true, }); - const selecting = useIsSelecting(); - - if (selecting || (!data?.message.length && !data?.menu.length)) { + if (!data?.message.length && !data?.menu.length) { return null; } diff --git a/apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx b/apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx index f225e8fc81f7..d8f9dd912e48 100644 --- a/apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx @@ -7,7 +7,7 @@ import { setMessageJumpQueryStringParameter } from '../../../lib/utils/setMessag export const useJumpToMessageContextAction = ( message: IMessage, - { id, order, hidden, context }: { id: string; order: number; hidden?: boolean; context: MessageActionContext[] }, + { id, hidden, context }: { id: string; hidden?: boolean; context: MessageActionContext[] }, ) => { useEffect(() => { if (hidden) { @@ -22,12 +22,12 @@ export const useJumpToMessageContextAction = ( async action() { setMessageJumpQueryStringParameter(message._id); }, - order, + order: 100, group: 'message', }); return () => { MessageAction.removeButton(id); }; - }, [hidden, context, id, message._id, order]); + }, [hidden, context, id, message._id]); }; diff --git a/apps/meteor/client/components/message/variants/RoomMessage.tsx b/apps/meteor/client/components/message/variants/RoomMessage.tsx index bf5c12a9a6dd..8dec6c9abbaa 100644 --- a/apps/meteor/client/components/message/variants/RoomMessage.tsx +++ b/apps/meteor/client/components/message/variants/RoomMessage.tsx @@ -110,7 +110,7 @@ const RoomMessage = ({ )} - {!message.private && message?.e2e !== 'pending' && } + {!message.private && message?.e2e !== 'pending' && !selecting && } ); }; From b7b06a6c2b3bd42d1bc05d2dbea7559eacf59367 Mon Sep 17 00:00:00 2001 From: Tasso Date: Tue, 10 Dec 2024 14:44:28 -0300 Subject: [PATCH 07/10] Segregate items as components --- .../app/ui-utils/client/lib/MessageAction.ts | 2 +- .../message/toolbar/MessageToolbar.tsx | 97 +++++++------------ .../message/toolbar/MessageToolbarItem.tsx | 35 +++++++ .../message/toolbar/items/DefaultItems.tsx | 26 +++++ .../message/toolbar/items/DirectItems.tsx | 16 +++ .../message/toolbar/items/FederatedItems.tsx | 24 +++++ .../message/toolbar/items/MentionsItems.tsx | 20 ++++ .../message/toolbar/items/MobileItems.tsx | 28 ++++++ .../message/toolbar/items/PinnedItems.tsx | 20 ++++ .../message/toolbar/items/SearchItems.tsx | 20 ++++ .../message/toolbar/items/StarredItems.tsx | 20 ++++ .../message/toolbar/items/ThreadsItems.tsx | 26 +++++ .../message/toolbar/items/VideoconfItems.tsx | 22 +++++ .../toolbar/items/VideoconfThreadsItems.tsx | 22 +++++ .../items/actions/ForwardMessageAction.tsx | 43 ++++++++ .../items/actions/JumpToMessageAction.tsx | 29 ++++++ .../items/actions/QuoteMessageAction.tsx | 43 ++++++++ .../items/actions/ReactionMessageAction.tsx | 62 ++++++++++++ .../actions/ReplyInThreadMessageAction.tsx | 48 +++++++++ .../toolbar/useForwardMessageAction.tsx | 41 -------- .../toolbar/useJumpToMessageContextAction.tsx | 33 ------- .../message/toolbar/useQuoteMessageAction.ts | 43 -------- .../toolbar/useReactionMessageAction.ts | 42 -------- .../toolbar/useReplyInThreadMessageAction.ts | 48 --------- 24 files changed, 539 insertions(+), 271 deletions(-) create mode 100644 apps/meteor/client/components/message/toolbar/MessageToolbarItem.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/DefaultItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/DirectItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/FederatedItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/MentionsItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/MobileItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/PinnedItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/SearchItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/StarredItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/ThreadsItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/VideoconfItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/VideoconfThreadsItems.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/actions/ForwardMessageAction.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/actions/JumpToMessageAction.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/actions/QuoteMessageAction.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx create mode 100644 apps/meteor/client/components/message/toolbar/items/actions/ReplyInThreadMessageAction.tsx delete mode 100644 apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx delete mode 100644 apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx delete mode 100644 apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts delete mode 100644 apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts delete mode 100644 apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 426caea0ecf9..c7b2836a6a3a 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -2,7 +2,7 @@ import type { Keys as IconName } from '@rocket.chat/icons'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import mem from 'mem'; -type MessageActionGroup = 'message' | 'menu'; +type MessageActionGroup = 'menu'; export type MessageActionContext = | 'message' diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index e64acfc40c5a..79c939811e6a 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -1,30 +1,35 @@ import { useToolbar } from '@react-aria/toolbar'; import type { IMessage, IRoom, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings'; import { isThreadMessage, isRoomFederated, isVideoConfMessage, isE2EEMessage } from '@rocket.chat/core-typings'; -import { MessageToolbar as FuselageMessageToolbar, MessageToolbarItem } from '@rocket.chat/fuselage'; -import { useFeaturePreview } from '@rocket.chat/ui-client'; -import { useUser, useTranslation, useMethod, useLayoutHiddenActions, useSetting } from '@rocket.chat/ui-contexts'; +import { MessageToolbar as FuselageMessageToolbar } from '@rocket.chat/fuselage'; +import { useUser, useTranslation, useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -import type { ComponentProps, ReactElement } from 'react'; +import type { ComponentProps, ElementType, ReactElement } from 'react'; import React, { memo, useRef } from 'react'; import MessageActionMenu from './MessageActionMenu'; import MessageToolbarStarsActionMenu from './MessageToolbarStarsActionMenu'; +import DefaultItems from './items/DefaultItems'; +import DirectItems from './items/DirectItems'; +import FederatedItems from './items/FederatedItems'; +import MentionsItems from './items/MentionsItems'; +import MobileItems from './items/MobileItems'; +import PinnedItems from './items/PinnedItems'; +import SearchItems from './items/SearchItems'; +import StarredItems from './items/StarredItems'; +import ThreadsItems from './items/ThreadsItems'; +import VideoconfItems from './items/VideoconfItems'; +import VideoconfThreadsItems from './items/VideoconfThreadsItems'; import { useCopyAction } from './useCopyAction'; import { useDeleteMessageAction } from './useDeleteMessageAction'; import { useEditMessageAction } from './useEditMessageAction'; import { useFollowMessageAction } from './useFollowMessageAction'; -import { useForwardMessageAction } from './useForwardMessageAction'; -import { useJumpToMessageContextAction } from './useJumpToMessageContextAction'; import { useMarkAsUnreadMessageAction } from './useMarkAsUnreadMessageAction'; import { useNewDiscussionMessageAction } from './useNewDiscussionMessageAction'; import { usePermalinkAction } from './usePermalinkAction'; import { usePinMessageAction } from './usePinMessageAction'; -import { useQuoteMessageAction } from './useQuoteMessageAction'; -import { useReactionMessageAction } from './useReactionMessageAction'; import { useReadReceiptsDetailsAction } from './useReadReceiptsDetailsAction'; import { useReplyInDMAction } from './useReplyInDMAction'; -import { useReplyInThreadMessageAction } from './useReplyInThreadMessageAction'; import { useReportMessageAction } from './useReportMessageAction'; import { useShowMessageReactionsAction } from './useShowMessageReactionsAction'; import { useStarMessageAction } from './useStarMessageAction'; @@ -36,11 +41,9 @@ import { useViewOriginalTranslationAction } from './useViewOriginalTranslationAc import { useWebDAVMessageAction } from './useWebDAVMessageAction'; import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { useEmojiPickerData } from '../../../contexts/EmojiPickerContext'; import { useMessageActionAppsActionButtons } from '../../../hooks/useAppActionButtons'; import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; import { roomsQueryKeys } from '../../../lib/queryKeys'; -import EmojiElement from '../../../views/composer/EmojiPicker/EmojiElement'; const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => { if (context) { @@ -62,6 +65,23 @@ const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActi return 'message'; }; +const itemsByContext: Record< + MessageActionContext, + ElementType<{ message: IMessage; room: IRoom; subscription: ISubscription | undefined }> +> = { + 'message': DefaultItems, + 'message-mobile': MobileItems, + 'threads': ThreadsItems, + 'videoconf': VideoconfItems, + 'videoconf-threads': VideoconfThreadsItems, + 'pinned': PinnedItems, + 'direct': DirectItems, + 'starred': StarredItems, + 'mentions': MentionsItems, + 'federated': FederatedItems, + 'search': SearchItems, +}; + type MessageToolbarProps = { message: IMessage & Partial; messageContext?: MessageActionContext; @@ -85,41 +105,15 @@ const MessageToolbar = ({ const toolbarRef = useRef(null); const { toolbarProps } = useToolbar(props, toolbarRef); - const quickReactionsEnabled = useFeaturePreview('quickReactions'); - - const setReaction = useMethod('setReaction'); - const context = getMessageContext(message, room, messageContext); - const { quickReactions, addRecentEmoji } = useEmojiPickerData(); - const actionButtonApps = useMessageActionAppsActionButtons(message, context); const starsAction = useMessageActionAppsActionButtons(message, context, 'ai'); const hiddenActions = useLayoutHiddenActions().messageToolbox; - const allowStarring = useSetting('Message_AllowStarring', true); // TODO: move this to another place - useReactionMessageAction(message, { user, room, subscription }); - useQuoteMessageAction(message, { subscription }); - useReplyInThreadMessageAction(message, { room, subscription }); - useForwardMessageAction(message); - useJumpToMessageContextAction(message, { - id: 'jump-to-message', - context: ['mentions', 'threads', 'videoconf-threads', 'message-mobile', 'search'], - }); - useJumpToMessageContextAction(message, { - id: 'jump-to-pin-message', - hidden: !subscription, - context: ['pinned', 'direct'], - }); - useJumpToMessageContextAction(message, { - id: 'jump-to-star-message', - hidden: !allowStarring || !subscription, - context: ['starred'], - }); - useWebDAVMessageAction(message, { subscription }); useNewDiscussionMessageAction(message, { user, room, subscription }); useUnpinMessageAction(message, { room, subscription }); @@ -151,47 +145,24 @@ const MessageToolbar = ({ const { isSuccess, data } = useQuery({ queryKey: roomsQueryKeys.messageActionsWithParameters(room._id, message), queryFn: async () => { - const toolboxItems = await MessageAction.getAll(context, 'message'); const menuItems = await MessageAction.getAll(context, 'menu'); return { - message: toolboxItems.filter((action) => !hiddenActions.includes(action.id)), menu: menuItems.filter((action) => !(isLayoutEmbedded && action.id === 'reply-directly') && !hiddenActions.includes(action.id)), }; }, keepPreviousData: true, }); - if (!data?.message.length && !data?.menu.length) { + if (!data?.menu.length) { return null; } - const isReactionAllowed = data?.message.find(({ id }) => id === 'reaction-message'); - - const handleSetReaction = (emoji: string) => { - setReaction(`:${emoji}:`, message._id); - addRecentEmoji(emoji); - }; + const Items = itemsByContext[context]; return ( - {quickReactionsEnabled && - isReactionAllowed && - quickReactions.slice(0, 3).map(({ emoji, image }) => { - return handleSetReaction(emoji)} />; - })} - {isSuccess && - data.message.map((action) => ( - action.action(e)} - key={action.id} - icon={action.icon} - title={action?.disabled ? t('Action_not_available_encrypted_content', { action: t(action.label) }) : t(action.label)} - data-qa-id={action.label} - data-qa-type='message-action-menu' - disabled={action?.disabled} - /> - ))} + {starsAction.data && starsAction.data.length > 0 && ( ({ diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbarItem.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbarItem.tsx new file mode 100644 index 000000000000..368a3b6d6acd --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/MessageToolbarItem.tsx @@ -0,0 +1,35 @@ +import { MessageToolbarItem as FuselageMessageToolbarItem } from '@rocket.chat/fuselage'; +import type { Keys as IconName } from '@rocket.chat/icons'; +import { useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; +import type { MouseEventHandler } from 'react'; +import React from 'react'; + +type MessageToolbarItemProps = { + id: string; + icon: IconName; + title: string; + disabled?: boolean; + qa: string; + onClick: MouseEventHandler; +}; + +const MessageToolbarItem = ({ id, icon, title, disabled, qa, onClick }: MessageToolbarItemProps) => { + const hiddenActions = useLayoutHiddenActions().messageToolbox; + + if (hiddenActions.includes(id)) { + return null; + } + + return ( + + ); +}; + +export default MessageToolbarItem; diff --git a/apps/meteor/client/components/message/toolbar/items/DefaultItems.tsx b/apps/meteor/client/components/message/toolbar/items/DefaultItems.tsx new file mode 100644 index 000000000000..d7c4a9f9baa2 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/DefaultItems.tsx @@ -0,0 +1,26 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import ForwardMessageAction from './actions/ForwardMessageAction'; +import QuoteMessageAction from './actions/QuoteMessageAction'; +import ReactionMessageAction from './actions/ReactionMessageAction'; +import ReplyInThreadMessageAction from './actions/ReplyInThreadMessageAction'; + +type DefaultItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const DefaultItems = ({ message, room, subscription }: DefaultItemsProps) => { + return ( + <> + + + + + + ); +}; + +export default DefaultItems; diff --git a/apps/meteor/client/components/message/toolbar/items/DirectItems.tsx b/apps/meteor/client/components/message/toolbar/items/DirectItems.tsx new file mode 100644 index 000000000000..0b729e5ac28e --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/DirectItems.tsx @@ -0,0 +1,16 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import JumpToMessageAction from './actions/JumpToMessageAction'; + +type DirectItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const DirectItems = ({ message, subscription }: DirectItemsProps) => { + return <>{!!subscription && }; +}; + +export default DirectItems; diff --git a/apps/meteor/client/components/message/toolbar/items/FederatedItems.tsx b/apps/meteor/client/components/message/toolbar/items/FederatedItems.tsx new file mode 100644 index 000000000000..536dc114b698 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/FederatedItems.tsx @@ -0,0 +1,24 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import QuoteMessageAction from './actions/QuoteMessageAction'; +import ReactionMessageAction from './actions/ReactionMessageAction'; +import ReplyInThreadMessageAction from './actions/ReplyInThreadMessageAction'; + +type FederatedItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const FederatedItems = ({ message, room, subscription }: FederatedItemsProps) => { + return ( + <> + + + + + ); +}; + +export default FederatedItems; diff --git a/apps/meteor/client/components/message/toolbar/items/MentionsItems.tsx b/apps/meteor/client/components/message/toolbar/items/MentionsItems.tsx new file mode 100644 index 000000000000..03e363787d58 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/MentionsItems.tsx @@ -0,0 +1,20 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import JumpToMessageAction from './actions/JumpToMessageAction'; + +type MentionsItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const MentionsItems = ({ message }: MentionsItemsProps) => { + return ( + <> + + + ); +}; + +export default MentionsItems; diff --git a/apps/meteor/client/components/message/toolbar/items/MobileItems.tsx b/apps/meteor/client/components/message/toolbar/items/MobileItems.tsx new file mode 100644 index 000000000000..fde71116b3b5 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/MobileItems.tsx @@ -0,0 +1,28 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import ForwardMessageAction from './actions/ForwardMessageAction'; +import JumpToMessageAction from './actions/JumpToMessageAction'; +import QuoteMessageAction from './actions/QuoteMessageAction'; +import ReactionMessageAction from './actions/ReactionMessageAction'; +import ReplyInThreadMessageAction from './actions/ReplyInThreadMessageAction'; + +type MobileItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const MobileItems = ({ message, room, subscription }: MobileItemsProps) => { + return ( + <> + + + + + + + ); +}; + +export default MobileItems; diff --git a/apps/meteor/client/components/message/toolbar/items/PinnedItems.tsx b/apps/meteor/client/components/message/toolbar/items/PinnedItems.tsx new file mode 100644 index 000000000000..f854952add04 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/PinnedItems.tsx @@ -0,0 +1,20 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import JumpToMessageAction from './actions/JumpToMessageAction'; + +type PinnedItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const PinnedItems = ({ message }: PinnedItemsProps) => { + return ( + <> + + + ); +}; + +export default PinnedItems; diff --git a/apps/meteor/client/components/message/toolbar/items/SearchItems.tsx b/apps/meteor/client/components/message/toolbar/items/SearchItems.tsx new file mode 100644 index 000000000000..d3bf6cfca4f1 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/SearchItems.tsx @@ -0,0 +1,20 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import JumpToMessageAction from './actions/JumpToMessageAction'; + +type SearchItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const SearchItems = ({ message }: SearchItemsProps) => { + return ( + <> + + + ); +}; + +export default SearchItems; diff --git a/apps/meteor/client/components/message/toolbar/items/StarredItems.tsx b/apps/meteor/client/components/message/toolbar/items/StarredItems.tsx new file mode 100644 index 000000000000..35f56aba73bc --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/StarredItems.tsx @@ -0,0 +1,20 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import JumpToMessageAction from './actions/JumpToMessageAction'; + +type StarredItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const StarredItems = ({ message }: StarredItemsProps) => { + return ( + <> + + + ); +}; + +export default StarredItems; diff --git a/apps/meteor/client/components/message/toolbar/items/ThreadsItems.tsx b/apps/meteor/client/components/message/toolbar/items/ThreadsItems.tsx new file mode 100644 index 000000000000..43dff5224a7e --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/ThreadsItems.tsx @@ -0,0 +1,26 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import ForwardMessageAction from './actions/ForwardMessageAction'; +import JumpToMessageAction from './actions/JumpToMessageAction'; +import QuoteMessageAction from './actions/QuoteMessageAction'; +import ReactionMessageAction from './actions/ReactionMessageAction'; + +type ThreadsItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const ThreadsItems = ({ message, room, subscription }: ThreadsItemsProps) => { + return ( + <> + + + + + + ); +}; + +export default ThreadsItems; diff --git a/apps/meteor/client/components/message/toolbar/items/VideoconfItems.tsx b/apps/meteor/client/components/message/toolbar/items/VideoconfItems.tsx new file mode 100644 index 000000000000..392d78820f2c --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/VideoconfItems.tsx @@ -0,0 +1,22 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import ReactionMessageAction from './actions/ReactionMessageAction'; +import ReplyInThreadMessageAction from './actions/ReplyInThreadMessageAction'; + +type VideoconfItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const VideoconfItems = ({ message, room, subscription }: VideoconfItemsProps) => { + return ( + <> + + + + ); +}; + +export default VideoconfItems; diff --git a/apps/meteor/client/components/message/toolbar/items/VideoconfThreadsItems.tsx b/apps/meteor/client/components/message/toolbar/items/VideoconfThreadsItems.tsx new file mode 100644 index 000000000000..819bc2f04705 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/VideoconfThreadsItems.tsx @@ -0,0 +1,22 @@ +import type { IRoom, ISubscription, IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; + +import JumpToMessageAction from './actions/JumpToMessageAction'; +import ReactionMessageAction from './actions/ReactionMessageAction'; + +type VideoconfThreadsItemsProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const VideoconfThreadsItems = ({ message, room, subscription }: VideoconfThreadsItemsProps) => { + return ( + <> + + + + ); +}; + +export default VideoconfThreadsItems; diff --git a/apps/meteor/client/components/message/toolbar/items/actions/ForwardMessageAction.tsx b/apps/meteor/client/components/message/toolbar/items/actions/ForwardMessageAction.tsx new file mode 100644 index 000000000000..88bb222d0641 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/actions/ForwardMessageAction.tsx @@ -0,0 +1,43 @@ +import { type IMessage, isE2EEMessage } from '@rocket.chat/core-typings'; +import { useSetModal } from '@rocket.chat/ui-contexts'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { getPermaLink } from '../../../../../lib/getPermaLink'; +import ForwardMessageModal from '../../../../../views/room/modals/ForwardMessageModal'; +import MessageToolbarItem from '../../MessageToolbarItem'; + +type ForwardMessageActionProps = { + message: IMessage; +}; + +const ForwardMessageAction = ({ message }: ForwardMessageActionProps) => { + const setModal = useSetModal(); + const { t } = useTranslation(); + + const encrypted = isE2EEMessage(message); + + return ( + { + const permalink = await getPermaLink(message._id); + setModal( + { + setModal(null); + }} + />, + ); + }} + /> + ); +}; + +export default ForwardMessageAction; diff --git a/apps/meteor/client/components/message/toolbar/items/actions/JumpToMessageAction.tsx b/apps/meteor/client/components/message/toolbar/items/actions/JumpToMessageAction.tsx new file mode 100644 index 000000000000..f8c72add56a6 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/actions/JumpToMessageAction.tsx @@ -0,0 +1,29 @@ +import type { IMessage } from '@rocket.chat/core-typings'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { setMessageJumpQueryStringParameter } from '../../../../../lib/utils/setMessageJumpQueryStringParameter'; +import MessageToolbarItem from '../../MessageToolbarItem'; + +type JumpToMessageActionProps = { + id: 'jump-to-message' | 'jump-to-pin-message' | 'jump-to-star-message'; + message: IMessage; +}; + +const JumpToMessageAction = ({ id, message }: JumpToMessageActionProps) => { + const { t } = useTranslation(); + + return ( + { + setMessageJumpQueryStringParameter(message._id); + }} + /> + ); +}; + +export default JumpToMessageAction; diff --git a/apps/meteor/client/components/message/toolbar/items/actions/QuoteMessageAction.tsx b/apps/meteor/client/components/message/toolbar/items/actions/QuoteMessageAction.tsx new file mode 100644 index 000000000000..f7a3d1d8cd99 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/actions/QuoteMessageAction.tsx @@ -0,0 +1,43 @@ +import type { ITranslatedMessage, IMessage, ISubscription } from '@rocket.chat/core-typings'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAutoTranslate } from '../../../../../views/room/MessageList/hooks/useAutoTranslate'; +import { useChat } from '../../../../../views/room/contexts/ChatContext'; +import MessageToolbarItem from '../../MessageToolbarItem'; + +type QuoteMessageActionProps = { + message: IMessage & Partial; + subscription: ISubscription | undefined; +}; + +const QuoteMessageAction = ({ message, subscription }: QuoteMessageActionProps) => { + const chat = useChat(); + const autoTranslateOptions = useAutoTranslate(subscription); + const { t } = useTranslation(); + + if (!chat || !subscription) { + return null; + } + + return ( + { + if (message && autoTranslateOptions?.autoTranslateEnabled && autoTranslateOptions.showAutoTranslate(message)) { + message.msg = + message.translations && autoTranslateOptions.autoTranslateLanguage + ? message.translations[autoTranslateOptions.autoTranslateLanguage] + : message.msg; + } + + chat?.composer?.quoteMessage(message); + }} + /> + ); +}; + +export default QuoteMessageAction; diff --git a/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx b/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx new file mode 100644 index 000000000000..5099cb13c969 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx @@ -0,0 +1,62 @@ +import { isOmnichannelRoom, type IMessage, type IRoom, type ISubscription } from '@rocket.chat/core-typings'; +import { useFeaturePreview } from '@rocket.chat/ui-client'; +import { useUser, useMethod } from '@rocket.chat/ui-contexts'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useEmojiPickerData } from '../../../../../contexts/EmojiPickerContext'; +import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; +import EmojiElement from '../../../../../views/composer/EmojiPicker/EmojiElement'; +import { useChat } from '../../../../../views/room/contexts/ChatContext'; +import MessageToolbarItem from '../../MessageToolbarItem'; + +type ReactionMessageActionProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const ReactionMessageAction = ({ message, room, subscription }: ReactionMessageActionProps) => { + const chat = useChat(); + const user = useUser(); + const setReaction = useMethod('setReaction'); + const quickReactionsEnabled = useFeaturePreview('quickReactions'); + const { quickReactions, addRecentEmoji } = useEmojiPickerData(); + const { t } = useTranslation(); + + if (!chat || !room || isOmnichannelRoom(room) || !subscription || message.private || !user) { + return null; + } + + if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) { + return null; + } + + const toggleReaction = (emoji: string) => { + setReaction(`:${emoji}:`, message._id); + addRecentEmoji(emoji); + }; + + return ( + <> + {quickReactionsEnabled && + quickReactions.slice(0, 3).map(({ emoji, image }) => { + return toggleReaction(emoji)} />; + })} + { + event.stopPropagation(); + chat.emojiPicker.open(event.currentTarget, (emoji) => { + toggleReaction(emoji); + }); + }} + /> + + ); +}; + +export default ReactionMessageAction; diff --git a/apps/meteor/client/components/message/toolbar/items/actions/ReplyInThreadMessageAction.tsx b/apps/meteor/client/components/message/toolbar/items/actions/ReplyInThreadMessageAction.tsx new file mode 100644 index 000000000000..55f4967625ad --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/items/actions/ReplyInThreadMessageAction.tsx @@ -0,0 +1,48 @@ +import { type IMessage, type ISubscription, type IRoom, isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { useRouter, useSetting } from '@rocket.chat/ui-contexts'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import MessageToolbarItem from '../../MessageToolbarItem'; + +type ReplyInThreadMessageActionProps = { + message: IMessage; + room: IRoom; + subscription: ISubscription | undefined; +}; + +const ReplyInThreadMessageAction = ({ message, room, subscription }: ReplyInThreadMessageActionProps) => { + const router = useRouter(); + const threadsEnabled = useSetting('Threads_enabled', true); + const { t } = useTranslation(); + + if (!threadsEnabled || isOmnichannelRoom(room) || !subscription) { + return null; + } + + return ( + { + event.stopPropagation(); + const routeName = router.getRouteName(); + + if (routeName) { + router.navigate({ + name: routeName, + params: { + ...router.getRouteParameters(), + tab: 'thread', + context: message.tmid || message._id, + }, + }); + } + }} + /> + ); +}; + +export default ReplyInThreadMessageAction; diff --git a/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx deleted file mode 100644 index ca7fc4a32976..000000000000 --- a/apps/meteor/client/components/message/toolbar/useForwardMessageAction.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { type IMessage, isE2EEMessage } from '@rocket.chat/core-typings'; -import { useSetModal } from '@rocket.chat/ui-contexts'; -import React, { useEffect } from 'react'; - -import { MessageAction } from '../../../../app/ui-utils/client'; -import { getPermaLink } from '../../../lib/getPermaLink'; -import ForwardMessageModal from '../../../views/room/modals/ForwardMessageModal'; - -export const useForwardMessageAction = (message: IMessage) => { - const encrypted = isE2EEMessage(message); - const setModal = useSetModal(); - - useEffect(() => { - MessageAction.addButton({ - id: 'forward-message', - icon: 'arrow-forward', - label: 'Forward_message', - context: ['message', 'message-mobile', 'threads'], - type: 'communication', - async action() { - const permalink = await getPermaLink(message._id); - setModal( - { - setModal(null); - }} - />, - ); - }, - order: 0, - group: 'message', - disabled: encrypted, - }); - - return () => { - MessageAction.removeButton('forward-message'); - }; - }, [encrypted, message, setModal]); -}; diff --git a/apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx b/apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx deleted file mode 100644 index d8f9dd912e48..000000000000 --- a/apps/meteor/client/components/message/toolbar/useJumpToMessageContextAction.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { IMessage } from '@rocket.chat/core-typings'; -import { useEffect } from 'react'; - -import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { setMessageJumpQueryStringParameter } from '../../../lib/utils/setMessageJumpQueryStringParameter'; - -export const useJumpToMessageContextAction = ( - message: IMessage, - { id, hidden, context }: { id: string; hidden?: boolean; context: MessageActionContext[] }, -) => { - useEffect(() => { - if (hidden) { - return; - } - - MessageAction.addButton({ - id, - icon: 'jump', - label: 'Jump_to_message', - context, - async action() { - setMessageJumpQueryStringParameter(message._id); - }, - order: 100, - group: 'message', - }); - - return () => { - MessageAction.removeButton(id); - }; - }, [hidden, context, id, message._id]); -}; diff --git a/apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts b/apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts deleted file mode 100644 index 8638e731dce1..000000000000 --- a/apps/meteor/client/components/message/toolbar/useQuoteMessageAction.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { IMessage, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings'; -import { useEffect } from 'react'; - -import { MessageAction } from '../../../../app/ui-utils/client'; -import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate'; -import { useChat } from '../../../views/room/contexts/ChatContext'; - -export const useQuoteMessageAction = ( - message: IMessage & Partial, - { subscription }: { subscription: ISubscription | undefined }, -) => { - const chat = useChat(); - const autoTranslateOptions = useAutoTranslate(subscription); - - useEffect(() => { - if (!subscription) { - return; - } - - MessageAction.addButton({ - id: 'quote-message', - icon: 'quote', - label: 'Quote', - context: ['message', 'message-mobile', 'threads', 'federated'], - async action() { - if (message && autoTranslateOptions?.autoTranslateEnabled && autoTranslateOptions.showAutoTranslate(message)) { - message.msg = - message.translations && autoTranslateOptions.autoTranslateLanguage - ? message.translations[autoTranslateOptions.autoTranslateLanguage] - : message.msg; - } - - await chat?.composer?.quoteMessage(message); - }, - order: -2, - group: 'message', - }); - - return () => { - MessageAction.removeButton('quote-message'); - }; - }, [autoTranslateOptions, chat?.composer, message, subscription]); -}; diff --git a/apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts b/apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts deleted file mode 100644 index 6f6c764c99ab..000000000000 --- a/apps/meteor/client/components/message/toolbar/useReactionMessageAction.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import type { IRoom, ISubscription, IUser, IMessage } from '@rocket.chat/core-typings'; -import { useEffect } from 'react'; - -import { MessageAction } from '../../../../app/ui-utils/client'; -import { sdk } from '../../../../app/utils/client/lib/SDKClient'; -import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; -import { useChat } from '../../../views/room/contexts/ChatContext'; - -export const useReactionMessageAction = ( - message: IMessage, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, -) => { - const chat = useChat(); - - useEffect(() => { - if (!room || isOmnichannelRoom(room) || !subscription || message.private || !user) { - return; - } - - if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) { - return; - } - - MessageAction.addButton({ - id: 'reaction-message', - icon: 'add-reaction', - label: 'Add_Reaction', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - action(event) { - event?.stopPropagation(); - chat?.emojiPicker.open(event?.currentTarget as Element, (emoji) => sdk.call('setReaction', `:${emoji}:`, message._id)); - }, - order: -3, - group: 'message', - }); - - return () => { - MessageAction.removeButton('reaction-message'); - }; - }, [chat?.emojiPicker, message._id, message.private, room, subscription, user]); -}; diff --git a/apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts b/apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts deleted file mode 100644 index 0fae8e99029b..000000000000 --- a/apps/meteor/client/components/message/toolbar/useReplyInThreadMessageAction.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; -import { useSetting, useRouter } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; - -import { MessageAction } from '../../../../app/ui-utils/client'; - -export const useReplyInThreadMessageAction = ( - message: IMessage, - { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { - const threadsEnabled = useSetting('Threads_enabled'); - - const route = useRouter(); - - useEffect(() => { - if (!threadsEnabled || isOmnichannelRoom(room) || !subscription) { - return; - } - - MessageAction.addButton({ - id: 'reply-in-thread', - icon: 'thread', - label: 'Reply_in_thread', - context: ['message', 'message-mobile', 'federated', 'videoconf'], - action(e) { - e?.stopPropagation(); - const routeName = route.getRouteName(); - if (routeName) { - route.navigate({ - name: routeName, - params: { - ...route.getRouteParameters(), - tab: 'thread', - context: message.tmid || message._id, - }, - }); - } - }, - order: -1, - group: 'message', - }); - - return () => { - MessageAction.removeButton('reply-in-thread'); - }; - }, [message._id, message.tmid, room, route, subscription, threadsEnabled]); -}; From 458235691203f9f10712b06ed13db4baa6707348 Mon Sep 17 00:00:00 2001 From: Tasso Date: Tue, 10 Dec 2024 17:54:26 -0300 Subject: [PATCH 08/10] Split hooks from module --- .../hooks/useMarketPlaceMenu.tsx | 2 +- .../message/toolbar/MessageToolbar.tsx | 2 +- .../client/hooks/useAppActionButtons.ts | 186 +----------------- apps/meteor/client/hooks/useFilterActions.ts | 19 -- .../useMessageActionAppsActionButtons.ts | 78 ++++++++ .../hooks/useMessageboxAppsActionButtons.ts | 62 ++++++ .../hooks/useUserDropdownAppsActionButtons.ts | 56 ++++++ .../header/actions/hooks/useAppsItems.tsx | 2 +- .../MessageBoxActionsToolbar.tsx | 2 +- 9 files changed, 203 insertions(+), 206 deletions(-) delete mode 100644 apps/meteor/client/hooks/useFilterActions.ts create mode 100644 apps/meteor/client/hooks/useMessageActionAppsActionButtons.ts create mode 100644 apps/meteor/client/hooks/useMessageboxAppsActionButtons.ts create mode 100644 apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts diff --git a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx index 034ab0367e81..80e61896bed3 100644 --- a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx +++ b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.tsx @@ -3,7 +3,7 @@ import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useTranslation, usePermission, useRouter } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useUserDropdownAppsActionButtons } from '../../../hooks/useAppActionButtons'; +import { useUserDropdownAppsActionButtons } from '../../../hooks/useUserDropdownAppsActionButtons'; import { useAppRequestStats } from '../../../views/marketplace/hooks/useAppRequestStats'; export const useMarketPlaceMenu = () => { diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index 79c939811e6a..813b19ad27c4 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -41,8 +41,8 @@ import { useViewOriginalTranslationAction } from './useViewOriginalTranslationAc import { useWebDAVMessageAction } from './useWebDAVMessageAction'; import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { useMessageActionAppsActionButtons } from '../../../hooks/useAppActionButtons'; import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; +import { useMessageActionAppsActionButtons } from '../../../hooks/useMessageActionAppsActionButtons'; import { roomsQueryKeys } from '../../../lib/queryKeys'; const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => { diff --git a/apps/meteor/client/hooks/useAppActionButtons.ts b/apps/meteor/client/hooks/useAppActionButtons.ts index 692df862470f..b5796965b062 100644 --- a/apps/meteor/client/hooks/useAppActionButtons.ts +++ b/apps/meteor/client/hooks/useAppActionButtons.ts @@ -1,22 +1,10 @@ import { type IUIActionButton, type UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui'; -import type { IMessage } from '@rocket.chat/core-typings'; import { useDebouncedCallback } from '@rocket.chat/fuselage-hooks'; -import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; -import { useEndpoint, useStream, useToastMessageDispatch, useUserId } from '@rocket.chat/ui-contexts'; -import type { UseQueryResult } from '@tanstack/react-query'; +import { useEndpoint, useStream, useUserId } from '@rocket.chat/ui-contexts'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { useEffect, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useEffect } from 'react'; -import { useApplyButtonFilters, useApplyButtonAuthFilter } from './useApplyButtonFilters'; -import { useFilterActionsByContext } from './useFilterActions'; -import { UiKitTriggerTimeoutError } from '../../app/ui-message/client/UiKitTriggerTimeoutError'; -import type { MessageActionConfig, MessageActionContext } from '../../app/ui-utils/client/lib/MessageAction'; -import type { MessageBoxAction } from '../../app/ui-utils/client/lib/messageBox'; -import { Utilities } from '../../ee/lib/misc/Utilities'; -import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager'; - -const getIdForActionButton = ({ appId, actionId }: IUIActionButton): string => `${appId}/${actionId}`; +export const getIdForActionButton = ({ appId, actionId }: IUIActionButton): string => `${appId}/${actionId}`; export const useAppActionButtons = (context?: TContext) => { const queryClient = useQueryClient(); @@ -62,171 +50,3 @@ export const useAppActionButtons = return result; }; - -export const useMessageboxAppsActionButtons = () => { - const result = useAppActionButtons('messageBoxAction'); - const actionManager = useUiKitActionManager(); - const dispatchToastMessage = useToastMessageDispatch(); - const { t } = useTranslation(); - - const applyButtonFilters = useApplyButtonFilters(); - - const data = useMemo( - () => - result.data - ?.filter((action) => { - return applyButtonFilters(action); - }) - .map((action) => { - const item: Omit = { - id: getIdForActionButton(action), - label: Utilities.getI18nKeyForApp(action.labelI18n, action.appId), - action: (params) => { - void actionManager - .emitInteraction(action.appId, { - type: 'actionButton', - rid: params.rid, - tmid: params.tmid, - actionId: action.actionId, - payload: { context: action.context, message: params.chat.composer?.text ?? '' }, - }) - .catch(async (reason) => { - if (reason instanceof UiKitTriggerTimeoutError) { - dispatchToastMessage({ - type: 'error', - message: t('UIKit_Interaction_Timeout'), - }); - return; - } - - return reason; - }); - }, - }; - - return item; - }), - [actionManager, applyButtonFilters, dispatchToastMessage, result.data, t], - ); - return { - ...result, - data, - } as UseQueryResult; -}; - -export const useUserDropdownAppsActionButtons = () => { - const result = useAppActionButtons('userDropdownAction'); - const actionManager = useUiKitActionManager(); - const dispatchToastMessage = useToastMessageDispatch(); - const { t } = useTranslation(); - - const applyButtonFilters = useApplyButtonAuthFilter(); - - const data = useMemo( - () => - result.data - ?.filter((action) => { - return applyButtonFilters(action); - }) - .map((action) => { - return { - id: `${action.appId}_${action.actionId}`, - // icon: action.icon as GenericMenuItemProps['icon'], - content: action.labelI18n, - onClick: () => { - void actionManager - .emitInteraction(action.appId, { - type: 'actionButton', - actionId: action.actionId, - payload: { context: action.context }, - }) - .catch(async (reason) => { - if (reason instanceof UiKitTriggerTimeoutError) { - dispatchToastMessage({ - type: 'error', - message: t('UIKit_Interaction_Timeout'), - }); - return; - } - - return reason; - }); - }, - }; - }), - [actionManager, applyButtonFilters, dispatchToastMessage, result.data, t], - ); - return { - ...result, - data, - } as UseQueryResult; -}; - -export const useMessageActionAppsActionButtons = (message: IMessage, context?: MessageActionContext, category?: string) => { - const result = useAppActionButtons('messageAction'); - const actionManager = useUiKitActionManager(); - const applyButtonFilters = useApplyButtonFilters(category); - const dispatchToastMessage = useToastMessageDispatch(); - const { t } = useTranslation(); - const filterActionsByContext = useFilterActionsByContext(context); - const data = useMemo( - () => - result.data - ?.filter((action) => { - if (!filterActionsByContext(action)) { - return false; - } - return applyButtonFilters(action); - }) - .map((action) => { - const item: MessageActionConfig = { - icon: undefined as any, - id: getIdForActionButton(action), - label: Utilities.getI18nKeyForApp(action.labelI18n, action.appId), - order: 7, - type: 'apps', - variant: action.variant, - group: 'menu', - action: () => { - void actionManager - .emitInteraction(action.appId, { - type: 'actionButton', - rid: message.rid, - tmid: message.tmid, - mid: message._id, - actionId: action.actionId, - payload: { context: action.context }, - }) - .catch(async (reason) => { - if (reason instanceof UiKitTriggerTimeoutError) { - dispatchToastMessage({ - type: 'error', - message: t('UIKit_Interaction_Timeout'), - }); - return; - } - - return reason; - }); - }, - }; - - return item; - }), - [ - actionManager, - applyButtonFilters, - dispatchToastMessage, - filterActionsByContext, - message._id, - message.rid, - message.tmid, - result.data, - t, - ], - ); - return { - ...result, - data, - } as UseQueryResult; -}; diff --git a/apps/meteor/client/hooks/useFilterActions.ts b/apps/meteor/client/hooks/useFilterActions.ts deleted file mode 100644 index 4dda122c3bb3..000000000000 --- a/apps/meteor/client/hooks/useFilterActions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MessageActionContext } from '@rocket.chat/apps-engine/definition/ui'; -import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; -import { useCallback } from 'react'; - -export const useFilterActionsByContext = (context: string | undefined) => { - return useCallback( - (action: IUIActionButton) => { - if (!context) { - return true; - } - - const messageActionContext = action.when?.messageActionContext || Object.values(MessageActionContext); - const isContextMatch = messageActionContext.includes(context as MessageActionContext); - - return isContextMatch; - }, - [context], - ); -}; diff --git a/apps/meteor/client/hooks/useMessageActionAppsActionButtons.ts b/apps/meteor/client/hooks/useMessageActionAppsActionButtons.ts new file mode 100644 index 000000000000..1098f8f52422 --- /dev/null +++ b/apps/meteor/client/hooks/useMessageActionAppsActionButtons.ts @@ -0,0 +1,78 @@ +import { type IUIActionButton, MessageActionContext as AppsEngineMessageActionContext } from '@rocket.chat/apps-engine/definition/ui'; +import type { IMessage } from '@rocket.chat/core-typings'; +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAppActionButtons, getIdForActionButton } from './useAppActionButtons'; +import { useApplyButtonFilters } from './useApplyButtonFilters'; +import { UiKitTriggerTimeoutError } from '../../app/ui-message/client/UiKitTriggerTimeoutError'; +import type { MessageActionContext, MessageActionConfig } from '../../app/ui-utils/client/lib/MessageAction'; +import { Utilities } from '../../ee/lib/misc/Utilities'; +import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager'; + +const filterActionsByContext = (context: string | undefined, action: IUIActionButton) => { + if (!context) { + return true; + } + + const messageActionContext = action.when?.messageActionContext || Object.values(AppsEngineMessageActionContext); + const isContextMatch = messageActionContext.includes(context as AppsEngineMessageActionContext); + + return isContextMatch; +}; + +export const useMessageActionAppsActionButtons = (message: IMessage, context?: MessageActionContext, category?: string) => { + const result = useAppActionButtons('messageAction'); + const actionManager = useUiKitActionManager(); + const applyButtonFilters = useApplyButtonFilters(category); + const dispatchToastMessage = useToastMessageDispatch(); + const { t } = useTranslation(); + const data = useMemo( + () => + result.data + ?.filter((action) => filterActionsByContext(context, action)) + .filter((action) => applyButtonFilters(action)) + .map((action) => { + const item: MessageActionConfig = { + icon: undefined as any, + id: getIdForActionButton(action), + label: Utilities.getI18nKeyForApp(action.labelI18n, action.appId), + order: 7, + type: 'apps', + variant: action.variant, + group: 'menu', + action: () => { + void actionManager + .emitInteraction(action.appId, { + type: 'actionButton', + rid: message.rid, + tmid: message.tmid, + mid: message._id, + actionId: action.actionId, + payload: { context: action.context }, + }) + .catch(async (reason) => { + if (reason instanceof UiKitTriggerTimeoutError) { + dispatchToastMessage({ + type: 'error', + message: t('UIKit_Interaction_Timeout'), + }); + return; + } + + return reason; + }); + }, + }; + + return item; + }), + [actionManager, applyButtonFilters, context, dispatchToastMessage, message._id, message.rid, message.tmid, result.data, t], + ); + return { + ...result, + data, + } as UseQueryResult; +}; diff --git a/apps/meteor/client/hooks/useMessageboxAppsActionButtons.ts b/apps/meteor/client/hooks/useMessageboxAppsActionButtons.ts new file mode 100644 index 000000000000..10c6f4f58ef4 --- /dev/null +++ b/apps/meteor/client/hooks/useMessageboxAppsActionButtons.ts @@ -0,0 +1,62 @@ +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAppActionButtons, getIdForActionButton } from './useAppActionButtons'; +import { useApplyButtonFilters } from './useApplyButtonFilters'; +import { UiKitTriggerTimeoutError } from '../../app/ui-message/client/UiKitTriggerTimeoutError'; +import type { MessageBoxAction } from '../../app/ui-utils/client/lib/messageBox'; +import { Utilities } from '../../ee/lib/misc/Utilities'; +import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager'; + +export const useMessageboxAppsActionButtons = (): UseQueryResult => { + const result = useAppActionButtons('messageBoxAction'); + const actionManager = useUiKitActionManager(); + const dispatchToastMessage = useToastMessageDispatch(); + const { t } = useTranslation(); + + const applyButtonFilters = useApplyButtonFilters(); + + const data = useMemo( + () => + result.data + ?.filter((action) => { + return applyButtonFilters(action); + }) + .map((action) => { + const item: Omit = { + id: getIdForActionButton(action), + label: Utilities.getI18nKeyForApp(action.labelI18n, action.appId), + action: (params) => { + void actionManager + .emitInteraction(action.appId, { + type: 'actionButton', + rid: params.rid, + tmid: params.tmid, + actionId: action.actionId, + payload: { context: action.context, message: params.chat.composer?.text ?? '' }, + }) + .catch(async (reason) => { + if (reason instanceof UiKitTriggerTimeoutError) { + dispatchToastMessage({ + type: 'error', + message: t('UIKit_Interaction_Timeout'), + }); + return; + } + + return reason; + }); + }, + }; + + return item; + }), + [actionManager, applyButtonFilters, dispatchToastMessage, result.data, t], + ); + return { + ...result, + data, + } as UseQueryResult; +}; diff --git a/apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts b/apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts new file mode 100644 index 000000000000..69355590fe90 --- /dev/null +++ b/apps/meteor/client/hooks/useUserDropdownAppsActionButtons.ts @@ -0,0 +1,56 @@ +import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAppActionButtons } from './useAppActionButtons'; +import { useApplyButtonAuthFilter } from './useApplyButtonFilters'; +import { UiKitTriggerTimeoutError } from '../../app/ui-message/client/UiKitTriggerTimeoutError'; +import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager'; + +export const useUserDropdownAppsActionButtons = () => { + const result = useAppActionButtons('userDropdownAction'); + const actionManager = useUiKitActionManager(); + const dispatchToastMessage = useToastMessageDispatch(); + const { t } = useTranslation(); + + const applyButtonFilters = useApplyButtonAuthFilter(); + + const data = useMemo( + () => + result.data + ?.filter((action) => applyButtonFilters(action)) + .map((action) => { + return { + id: `${action.appId}_${action.actionId}`, + // icon: action.icon as GenericMenuItemProps['icon'], + content: action.labelI18n, + onClick: () => { + void actionManager + .emitInteraction(action.appId, { + type: 'actionButton', + actionId: action.actionId, + payload: { context: action.context }, + }) + .catch(async (reason) => { + if (reason instanceof UiKitTriggerTimeoutError) { + dispatchToastMessage({ + type: 'error', + message: t('UIKit_Interaction_Timeout'), + }); + return; + } + + return reason; + }); + }, + }; + }), + [actionManager, applyButtonFilters, dispatchToastMessage, result.data, t], + ); + return { + ...result, + data, + } as UseQueryResult; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx index f7b9cea0d56a..2fa95d1606e1 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx @@ -3,7 +3,7 @@ import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useTranslation, useRoute, usePermission } from '@rocket.chat/ui-contexts'; import React from 'react'; -import { useUserDropdownAppsActionButtons } from '../../../../hooks/useAppActionButtons'; +import { useUserDropdownAppsActionButtons } from '../../../../hooks/useUserDropdownAppsActionButtons'; import { useAppRequestStats } from '../../../../views/marketplace/hooks/useAppRequestStats'; /** diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx index b4ec012f3337..64eed975e9cc 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx @@ -16,7 +16,7 @@ import { useVideoMessageAction } from './hooks/useVideoMessageAction'; import { useWebdavActions } from './hooks/useWebdavActions'; import { messageBox } from '../../../../../../app/ui-utils/client'; import { isTruthy } from '../../../../../../lib/isTruthy'; -import { useMessageboxAppsActionButtons } from '../../../../../hooks/useAppActionButtons'; +import { useMessageboxAppsActionButtons } from '../../../../../hooks/useMessageboxAppsActionButtons'; import { useChat } from '../../../contexts/ChatContext'; import { useRoom } from '../../../contexts/RoomContext'; From b3ed7b92850e9d783e37989c02063a3dad97d396 Mon Sep 17 00:00:00 2001 From: Tasso Date: Wed, 11 Dec 2024 00:29:30 -0300 Subject: [PATCH 09/10] Segregate menus' logic --- .../app/ui-utils/client/lib/MessageAction.ts | 2 +- .../message/toolbar/MessageActionMenu.tsx | 93 ---------- .../message/toolbar/MessageToolbar.tsx | 118 ++----------- .../toolbar/MessageToolbarActionMenu.tsx | 164 ++++++++++++++++++ .../toolbar/MessageToolbarStarsActionMenu.tsx | 30 ++-- .../message/toolbar/useDeleteMessageAction.ts | 6 +- .../message/toolbar/useEditMessageAction.ts | 7 +- .../message/toolbar/useFollowMessageAction.ts | 10 +- .../toolbar/useMarkAsUnreadMessageAction.ts | 7 +- .../useMessageActionAppsActionButtons.ts | 12 +- .../toolbar/useNewDiscussionMessageAction.tsx | 7 +- .../message/toolbar/useReplyInDMAction.ts | 16 +- .../toolbar/useReportMessageAction.tsx | 7 +- .../message/toolbar/useStarMessageAction.ts | 7 +- .../message/toolbar/useTranslateAction.ts | 7 +- .../toolbar/useUnFollowMessageAction.ts | 10 +- .../message/toolbar/useUnstarMessageAction.ts | 7 +- .../useViewOriginalTranslationAction.ts | 7 +- 18 files changed, 253 insertions(+), 264 deletions(-) delete mode 100644 apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx create mode 100644 apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx rename apps/meteor/client/{hooks => components/message/toolbar}/useMessageActionAppsActionButtons.ts (84%) diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index c7b2836a6a3a..91b17c390863 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -25,7 +25,7 @@ export type MessageActionConfig = { variant?: 'danger' | 'success' | 'warning'; label: TranslationKey; order: number; - /* @deprecated */ + /** @deprecated */ color?: 'alert'; group: MessageActionGroup; context?: MessageActionContext[]; diff --git a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx deleted file mode 100644 index dcb2fcaf4699..000000000000 --- a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useUniqueId } from '@rocket.chat/fuselage-hooks'; -import { GenericMenu, type GenericMenuItemProps } from '@rocket.chat/ui-client'; -import type { MouseEvent, ReactElement } from 'react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; - -type MessageActionConfigOption = Omit & { - action: (e?: MouseEvent) => void; -}; - -type MessageActionSection = { - id: string; - title: string; - items: GenericMenuItemProps[]; -}; - -type MessageActionMenuProps = { - onChangeMenuVisibility: (visible: boolean) => void; - options: MessageActionConfigOption[]; - isMessageEncrypted: boolean; -}; - -const MessageActionMenu = ({ options, onChangeMenuVisibility, isMessageEncrypted }: MessageActionMenuProps): ReactElement => { - const { t } = useTranslation(); - const id = useUniqueId(); - const groupOptions = options - .map((option) => ({ - variant: option.color === 'alert' ? 'danger' : '', - id: option.id, - icon: option.icon, - content: t(option.label), - onClick: option.action, - type: option.type, - ...(typeof option.disabled === 'boolean' && { disabled: option.disabled }), - ...(typeof option.disabled === 'boolean' && - option.disabled && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), - })) - .reduce( - (acc, option) => { - const group = option.type ? option.type : ''; - const section = acc.find((section: { id: string }) => section.id === group); - if (section) { - section.items.push(option); - return acc; - } - const newSection = { id: group, title: group === 'apps' ? t('Apps') : '', items: [option] }; - acc.push(newSection); - - return acc; - }, - [] as unknown as MessageActionSection[], - ) - .map((section) => { - if (section.id !== 'apps') { - return section; - } - - if (!isMessageEncrypted) { - return section; - } - - return { - id: 'apps', - title: t('Apps'), - items: [ - { - content: t('Unavailable'), - type: 'apps', - id, - disabled: true, - gap: false, - tooltip: t('Action_not_available_encrypted_content', { action: t('Apps') }), - }, - ], - }; - }); - - return ( - - ); -}; - -export default MessageActionMenu; diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx index 813b19ad27c4..113b5278cdff 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbar.tsx @@ -1,13 +1,12 @@ import { useToolbar } from '@react-aria/toolbar'; import type { IMessage, IRoom, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings'; -import { isThreadMessage, isRoomFederated, isVideoConfMessage, isE2EEMessage } from '@rocket.chat/core-typings'; +import { isThreadMessage, isRoomFederated, isVideoConfMessage } from '@rocket.chat/core-typings'; import { MessageToolbar as FuselageMessageToolbar } from '@rocket.chat/fuselage'; -import { useUser, useTranslation, useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps, ElementType, ReactElement } from 'react'; import React, { memo, useRef } from 'react'; -import MessageActionMenu from './MessageActionMenu'; +import MessageToolbarActionMenu from './MessageToolbarActionMenu'; import MessageToolbarStarsActionMenu from './MessageToolbarStarsActionMenu'; import DefaultItems from './items/DefaultItems'; import DirectItems from './items/DirectItems'; @@ -20,30 +19,7 @@ import StarredItems from './items/StarredItems'; import ThreadsItems from './items/ThreadsItems'; import VideoconfItems from './items/VideoconfItems'; import VideoconfThreadsItems from './items/VideoconfThreadsItems'; -import { useCopyAction } from './useCopyAction'; -import { useDeleteMessageAction } from './useDeleteMessageAction'; -import { useEditMessageAction } from './useEditMessageAction'; -import { useFollowMessageAction } from './useFollowMessageAction'; -import { useMarkAsUnreadMessageAction } from './useMarkAsUnreadMessageAction'; -import { useNewDiscussionMessageAction } from './useNewDiscussionMessageAction'; -import { usePermalinkAction } from './usePermalinkAction'; -import { usePinMessageAction } from './usePinMessageAction'; -import { useReadReceiptsDetailsAction } from './useReadReceiptsDetailsAction'; -import { useReplyInDMAction } from './useReplyInDMAction'; -import { useReportMessageAction } from './useReportMessageAction'; -import { useShowMessageReactionsAction } from './useShowMessageReactionsAction'; -import { useStarMessageAction } from './useStarMessageAction'; -import { useTranslateAction } from './useTranslateAction'; -import { useUnFollowMessageAction } from './useUnFollowMessageAction'; -import { useUnpinMessageAction } from './useUnpinMessageAction'; -import { useUnstarMessageAction } from './useUnstarMessageAction'; -import { useViewOriginalTranslationAction } from './useViewOriginalTranslationAction'; -import { useWebDAVMessageAction } from './useWebDAVMessageAction'; import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; -import { useMessageActionAppsActionButtons } from '../../../hooks/useMessageActionAppsActionButtons'; -import { roomsQueryKeys } from '../../../lib/queryKeys'; const getMessageContext = (message: IMessage, room: IRoom, context?: MessageActionContext): MessageActionContext => { if (context) { @@ -99,93 +75,25 @@ const MessageToolbar = ({ ...props }: MessageToolbarProps): ReactElement | null => { const t = useTranslation(); - const user = useUser() ?? undefined; - const isLayoutEmbedded = useEmbeddedLayout(); const toolbarRef = useRef(null); const { toolbarProps } = useToolbar(props, toolbarRef); const context = getMessageContext(message, room, messageContext); - const actionButtonApps = useMessageActionAppsActionButtons(message, context); - - const starsAction = useMessageActionAppsActionButtons(message, context, 'ai'); - - const hiddenActions = useLayoutHiddenActions().messageToolbox; - - // TODO: move this to another place - useWebDAVMessageAction(message, { subscription }); - useNewDiscussionMessageAction(message, { user, room, subscription }); - useUnpinMessageAction(message, { room, subscription }); - usePinMessageAction(message, { room, subscription }); - useStarMessageAction(message, { room, user }); - useUnstarMessageAction(message, { room, user }); - usePermalinkAction(message, { subscription, id: 'permalink-star', context: ['starred'], order: 10 }); - usePermalinkAction(message, { subscription, id: 'permalink-pinned', context: ['pinned'], order: 5 }); - usePermalinkAction(message, { - subscription, - id: 'permalink', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - type: 'duplication', - order: 5, - }); - useFollowMessageAction(message, { room, user, context }); - useUnFollowMessageAction(message, { room, user, context }); - useMarkAsUnreadMessageAction(message, { user, room, subscription }); - useTranslateAction(message, { user, room, subscription }); - useViewOriginalTranslationAction(message, { user, room, subscription }); - useReplyInDMAction(message, { user, room, subscription }); - useCopyAction(message, { subscription }); - useEditMessageAction(message, { user, room, subscription }); - useDeleteMessageAction(message, { user, room, subscription }); - useReportMessageAction(message, { user, room, subscription }); - useShowMessageReactionsAction(message); - useReadReceiptsDetailsAction(message); - - const { isSuccess, data } = useQuery({ - queryKey: roomsQueryKeys.messageActionsWithParameters(room._id, message), - queryFn: async () => { - const menuItems = await MessageAction.getAll(context, 'menu'); - - return { - menu: menuItems.filter((action) => !(isLayoutEmbedded && action.id === 'reply-directly') && !hiddenActions.includes(action.id)), - }; - }, - keepPreviousData: true, - }); - - if (!data?.menu.length) { - return null; - } - - const Items = itemsByContext[context]; + const MessageToolbarItems = itemsByContext[context]; return ( - - {starsAction.data && starsAction.data.length > 0 && ( - ({ - ...action, - action: (e) => action.action(e), - }))} - onChangeMenuVisibility={onChangeMenuVisibility} - data-qa-type='message-action-stars-menu-options' - isMessageEncrypted={isE2EEMessage(message)} - /> - )} - - {isSuccess && data.menu.length > 0 && ( - ({ - ...action, - action: (e) => action.action(e), - }))} - onChangeMenuVisibility={onChangeMenuVisibility} - data-qa-type='message-action-menu-options' - isMessageEncrypted={isE2EEMessage(message)} - /> - )} + + + ); }; diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx new file mode 100644 index 000000000000..f8c84cc8ffa4 --- /dev/null +++ b/apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx @@ -0,0 +1,164 @@ +import { isE2EEMessage, type IMessage, type IRoom, type ISubscription } from '@rocket.chat/core-typings'; +import { useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { GenericMenu, type GenericMenuItemProps } from '@rocket.chat/ui-client'; +import { useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useCopyAction } from './useCopyAction'; +import { useDeleteMessageAction } from './useDeleteMessageAction'; +import { useEditMessageAction } from './useEditMessageAction'; +import { useFollowMessageAction } from './useFollowMessageAction'; +import { useMarkAsUnreadMessageAction } from './useMarkAsUnreadMessageAction'; +import { useMessageActionAppsActionButtons } from './useMessageActionAppsActionButtons'; +import { useNewDiscussionMessageAction } from './useNewDiscussionMessageAction'; +import { usePermalinkAction } from './usePermalinkAction'; +import { usePinMessageAction } from './usePinMessageAction'; +import { useReadReceiptsDetailsAction } from './useReadReceiptsDetailsAction'; +import { useReplyInDMAction } from './useReplyInDMAction'; +import { useReportMessageAction } from './useReportMessageAction'; +import { useShowMessageReactionsAction } from './useShowMessageReactionsAction'; +import { useStarMessageAction } from './useStarMessageAction'; +import { useTranslateAction } from './useTranslateAction'; +import { useUnFollowMessageAction } from './useUnFollowMessageAction'; +import { useUnpinMessageAction } from './useUnpinMessageAction'; +import { useUnstarMessageAction } from './useUnstarMessageAction'; +import { useViewOriginalTranslationAction } from './useViewOriginalTranslationAction'; +import { useWebDAVMessageAction } from './useWebDAVMessageAction'; +import { MessageAction, type MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; +import { roomsQueryKeys } from '../../../lib/queryKeys'; + +type MessageActionSection = { + id: string; + title: string; + items: GenericMenuItemProps[]; +}; + +type MessageToolbarActionMenuProps = { + message: IMessage; + context: MessageActionContext; + room: IRoom; + subscription: ISubscription | undefined; + onChangeMenuVisibility: (visible: boolean) => void; +}; + +const MessageToolbarActionMenu = ({ message, context, room, subscription, onChangeMenuVisibility }: MessageToolbarActionMenuProps) => { + // TODO: move this to another place + useWebDAVMessageAction(message, { subscription }); + useNewDiscussionMessageAction(message, { room, subscription }); + useUnpinMessageAction(message, { room, subscription }); + usePinMessageAction(message, { room, subscription }); + useStarMessageAction(message, { room }); + useUnstarMessageAction(message, { room }); + usePermalinkAction(message, { id: 'permalink-star', subscription, context: ['starred'], order: 10 }); + usePermalinkAction(message, { id: 'permalink-pinned', subscription, context: ['pinned'], order: 5 }); + usePermalinkAction(message, { + id: 'permalink', + subscription, + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + type: 'duplication', + order: 5, + }); + useFollowMessageAction(message, { room, context }); + useUnFollowMessageAction(message, { room, context }); + useMarkAsUnreadMessageAction(message, { room, subscription }); + useTranslateAction(message, { room, subscription }); + useViewOriginalTranslationAction(message, { room, subscription }); + useReplyInDMAction(message, { room, subscription }); + useCopyAction(message, { subscription }); + useEditMessageAction(message, { room, subscription }); + useDeleteMessageAction(message, { room, subscription }); + useReportMessageAction(message, { room, subscription }); + useShowMessageReactionsAction(message); + useReadReceiptsDetailsAction(message); + + const hiddenActions = useLayoutHiddenActions().messageToolbox; + const { isSuccess, data } = useQuery({ + queryKey: roomsQueryKeys.messageActionsWithParameters(room._id, message), + queryFn: async () => { + const menuItems = await MessageAction.getAll(context, 'menu'); + return menuItems.filter((action) => !hiddenActions.includes(action.id)); + }, + keepPreviousData: true, + }); + + const actionButtonApps = useMessageActionAppsActionButtons(message, context); + + const id = useUniqueId(); + const { t } = useTranslation(); + + if (!isSuccess || data.length === 0) { + return null; + } + + const options = [...data, ...(actionButtonApps.data ?? [])]; + + const isMessageEncrypted = isE2EEMessage(message); + + const groupOptions = options + .map((option) => ({ + variant: option.color === 'alert' ? 'danger' : '', + id: option.id, + icon: option.icon, + content: t(option.label), + onClick: option.action, + type: option.type, + ...(typeof option.disabled === 'boolean' && { disabled: option.disabled }), + ...(typeof option.disabled === 'boolean' && + option.disabled && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), + })) + .reduce( + (acc, option) => { + const group = option.type ? option.type : ''; + const section = acc.find((section: { id: string }) => section.id === group); + if (section) { + section.items.push(option); + return acc; + } + const newSection = { id: group, title: group === 'apps' ? t('Apps') : '', items: [option] }; + acc.push(newSection); + + return acc; + }, + [] as unknown as MessageActionSection[], + ) + .map((section) => { + if (section.id !== 'apps') { + return section; + } + + if (!isMessageEncrypted) { + return section; + } + + return { + id: 'apps', + title: t('Apps'), + items: [ + { + content: t('Unavailable'), + type: 'apps', + id, + disabled: true, + gap: false, + tooltip: t('Action_not_available_encrypted_content', { action: t('Apps') }), + }, + ], + }; + }); + + return ( + + ); +}; + +export default MessageToolbarActionMenu; diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx index 198e84ec8d97..4c5699c58f7a 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbarStarsActionMenu.tsx @@ -1,14 +1,11 @@ +import { isE2EEMessage, type IMessage } from '@rocket.chat/core-typings'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import { GenericMenu, type GenericMenuItemProps } from '@rocket.chat/ui-client'; -import type { MouseEvent, ReactElement } from 'react'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; - -type MessageActionConfigOption = Omit & { - action: (e?: MouseEvent) => void; -}; +import { useMessageActionAppsActionButtons } from './useMessageActionAppsActionButtons'; +import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; type MessageActionSection = { id: string; @@ -17,16 +14,23 @@ type MessageActionSection = { }; type MessageActionMenuProps = { + message: IMessage; + context: MessageActionContext; onChangeMenuVisibility: (visible: boolean) => void; - options: MessageActionConfigOption[]; - isMessageEncrypted: boolean; }; -const MessageToolbarStarsActionMenu = ({ options, onChangeMenuVisibility, isMessageEncrypted }: MessageActionMenuProps): ReactElement => { +const MessageToolbarStarsActionMenu = ({ message, context, onChangeMenuVisibility }: MessageActionMenuProps) => { + const starsAction = useMessageActionAppsActionButtons(message, context, 'ai'); const { t } = useTranslation(); const id = useUniqueId(); - const groupOptions = options.reduce((acc, option) => { + if (!starsAction.data?.length) { + return null; + } + + const isMessageEncrypted = isE2EEMessage(message); + + const groupOptions = starsAction.data.reduce((acc, option) => { const transformedOption = { variant: option.color === 'alert' ? 'danger' : '', id: option.id, @@ -68,14 +72,14 @@ const MessageToolbarStarsActionMenu = ({ options, onChangeMenuVisibility, isMess return ( ); }; diff --git a/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts b/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts index f6e120428b82..afedb2fa2698 100644 --- a/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts @@ -1,5 +1,6 @@ import { isRoomFederated } from '@rocket.chat/core-typings'; -import type { ISubscription, IUser, IRoom, IMessage } from '@rocket.chat/core-typings'; +import type { ISubscription, IRoom, IMessage } from '@rocket.chat/core-typings'; +import { useUser } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client'; @@ -8,8 +9,9 @@ import { useChat } from '../../../views/room/contexts/ChatContext'; export const useDeleteMessageAction = ( message: IMessage, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, ) => { + const user = useUser(); const chat = useChat(); useEffect(() => { diff --git a/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts b/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts index 8889f33d8509..1333b5a40c61 100644 --- a/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts @@ -1,6 +1,6 @@ import { isRoomFederated } from '@rocket.chat/core-typings'; -import type { IRoom, IUser, IMessage, ISubscription } from '@rocket.chat/core-typings'; -import { usePermission, useSetting } from '@rocket.chat/ui-contexts'; +import type { IRoom, IMessage, ISubscription } from '@rocket.chat/core-typings'; +import { usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import { useEffect } from 'react'; @@ -9,8 +9,9 @@ import { useChat } from '../../../views/room/contexts/ChatContext'; export const useEditMessageAction = ( message: IMessage, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, ) => { + const user = useUser(); const chat = useChat(); const isEditAllowed = useSetting('Message_AllowEditing', true); const canEditMessage = usePermission('edit-message', message.rid); diff --git a/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts b/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts index 7fdde7c97b5e..76049e347f1b 100644 --- a/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts @@ -1,6 +1,6 @@ -import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { useSetting, useToastMessageDispatch, useUser } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; @@ -12,10 +12,8 @@ import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { roomsQueryKeys } from '../../../lib/queryKeys'; import { useToggleFollowingThreadMutation } from '../../../views/room/contextualBar/Threads/hooks/useToggleFollowingThreadMutation'; -export const useFollowMessageAction = ( - message: IMessage, - { room, user, context }: { room: IRoom; user: IUser | undefined; context: MessageActionContext }, -) => { +export const useFollowMessageAction = (message: IMessage, { room, context }: { room: IRoom; context: MessageActionContext }) => { + const user = useUser(); const threadsEnabled = useSetting('Threads_enabled'); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts b/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts index 208a83679f7e..b894f32cdb12 100644 --- a/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts @@ -1,6 +1,6 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import type { ISubscription, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; -import { useRouter } from '@rocket.chat/ui-contexts'; +import type { ISubscription, IMessage, IRoom } from '@rocket.chat/core-typings'; +import { useRouter, useUser } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client'; @@ -8,8 +8,9 @@ import { useMarkAsUnreadMutation } from '../hooks/useMarkAsUnreadMutation'; export const useMarkAsUnreadMessageAction = ( message: IMessage, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, ) => { + const user = useUser(); const { mutateAsync: markAsUnread } = useMarkAsUnreadMutation(); const router = useRouter(); diff --git a/apps/meteor/client/hooks/useMessageActionAppsActionButtons.ts b/apps/meteor/client/components/message/toolbar/useMessageActionAppsActionButtons.ts similarity index 84% rename from apps/meteor/client/hooks/useMessageActionAppsActionButtons.ts rename to apps/meteor/client/components/message/toolbar/useMessageActionAppsActionButtons.ts index 1098f8f52422..cecb79c183af 100644 --- a/apps/meteor/client/hooks/useMessageActionAppsActionButtons.ts +++ b/apps/meteor/client/components/message/toolbar/useMessageActionAppsActionButtons.ts @@ -5,12 +5,12 @@ import type { UseQueryResult } from '@tanstack/react-query'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useAppActionButtons, getIdForActionButton } from './useAppActionButtons'; -import { useApplyButtonFilters } from './useApplyButtonFilters'; -import { UiKitTriggerTimeoutError } from '../../app/ui-message/client/UiKitTriggerTimeoutError'; -import type { MessageActionContext, MessageActionConfig } from '../../app/ui-utils/client/lib/MessageAction'; -import { Utilities } from '../../ee/lib/misc/Utilities'; -import { useUiKitActionManager } from '../uikit/hooks/useUiKitActionManager'; +import { UiKitTriggerTimeoutError } from '../../../../app/ui-message/client/UiKitTriggerTimeoutError'; +import type { MessageActionContext, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; +import { Utilities } from '../../../../ee/lib/misc/Utilities'; +import { useAppActionButtons, getIdForActionButton } from '../../../hooks/useAppActionButtons'; +import { useApplyButtonFilters } from '../../../hooks/useApplyButtonFilters'; +import { useUiKitActionManager } from '../../../uikit/hooks/useUiKitActionManager'; const filterActionsByContext = (context: string | undefined, action: IUIActionButton) => { if (!context) { diff --git a/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx index 802f7ec56af9..c7ac70d3aee8 100644 --- a/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx @@ -1,5 +1,5 @@ -import type { IMessage, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; -import { usePermission, useSetModal, useSetting } from '@rocket.chat/ui-contexts'; +import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; +import { usePermission, useSetModal, useSetting, useUser } from '@rocket.chat/ui-contexts'; import React, { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; @@ -8,8 +8,9 @@ import CreateDiscussion from '../../CreateDiscussion'; export const useNewDiscussionMessageAction = ( message: IMessage, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, ) => { + const user = useUser(); const enabled = useSetting('Discussion_enabled', false); const setModal = useSetModal(); diff --git a/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts index cec1de1b0ca6..5bb75025177b 100644 --- a/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts +++ b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts @@ -1,21 +1,21 @@ -import { type IUser, type IMessage, type ISubscription, type IRoom, isE2EEMessage } from '@rocket.chat/core-typings'; -import { usePermission, useRouter } from '@rocket.chat/ui-contexts'; +import { type IMessage, type ISubscription, type IRoom, isE2EEMessage } from '@rocket.chat/core-typings'; +import { usePermission, useRouter, useUser } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; import { Rooms, Subscriptions } from '../../../../app/models/client'; import { MessageAction } from '../../../../app/ui-utils/client'; +import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; -export const useReplyInDMAction = ( - message: IMessage, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, -) => { +export const useReplyInDMAction = (message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }) => { + const user = useUser(); const router = useRouter(); const encrypted = isE2EEMessage(message); const canCreateDM = usePermission('create-d'); + const isLayoutEmbedded = useEmbeddedLayout(); useEffect(() => { - if (!subscription || room.t === 'd' || room.t === 'l') { + if (!subscription || room.t === 'd' || room.t === 'l' || isLayoutEmbedded) { return; } @@ -54,5 +54,5 @@ export const useReplyInDMAction = ( return () => { MessageAction.removeButton('reply-directly'); }; - }, [canCreateDM, encrypted, message._id, message.u._id, message.u.username, room.t, router, subscription, user]); + }, [canCreateDM, encrypted, isLayoutEmbedded, message._id, message.u._id, message.u.username, room.t, router, subscription, user]); }; diff --git a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx index 5a09c436d051..0154fcf95806 100644 --- a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx @@ -1,5 +1,5 @@ -import type { ISubscription, IUser, IRoom, IMessage } from '@rocket.chat/core-typings'; -import { useSetModal } from '@rocket.chat/ui-contexts'; +import type { ISubscription, IRoom, IMessage } from '@rocket.chat/core-typings'; +import { useSetModal, useUser } from '@rocket.chat/ui-contexts'; import React, { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client'; @@ -15,8 +15,9 @@ const getMainMessageText = (message: IMessage): IMessage => { export const useReportMessageAction = ( message: IMessage, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, ) => { + const user = useUser(); const setModal = useSetModal(); const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); diff --git a/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts b/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts index 829a94db9aa8..4f8fd6abd1ca 100644 --- a/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts @@ -1,12 +1,13 @@ -import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { useSetting } from '@rocket.chat/ui-contexts'; +import { useSetting, useUser } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useStarMessageMutation } from '../hooks/useStarMessageMutation'; -export const useStarMessageAction = (message: IMessage, { room, user }: { room: IRoom; user: IUser | undefined }) => { +export const useStarMessageAction = (message: IMessage, { room }: { room: IRoom }) => { + const user = useUser(); const allowStarring = useSetting('Message_AllowStarring', true); const { mutateAsync: starMessage } = useStarMessageMutation(); diff --git a/apps/meteor/client/components/message/toolbar/useTranslateAction.ts b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts index ba81ae9de82a..27e8e2afa0b8 100644 --- a/apps/meteor/client/components/message/toolbar/useTranslateAction.ts +++ b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts @@ -1,5 +1,5 @@ -import type { IUser, IMessage, ISubscription, IRoom } from '@rocket.chat/core-typings'; -import { useMethod, usePermission, useSetting } from '@rocket.chat/ui-contexts'; +import type { IMessage, ISubscription, IRoom } from '@rocket.chat/core-typings'; +import { useMethod, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts'; import { useEffect, useMemo } from 'react'; import { AutoTranslate } from '../../../../app/autotranslate/client'; @@ -10,8 +10,9 @@ import { hasTranslationLanguageInAttachments, hasTranslationLanguageInMessage } export const useTranslateAction = ( message: IMessage & { autoTranslateShowInverse?: boolean }, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, ) => { + const user = useUser(); const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false); const canAutoTranslate = usePermission('auto-translate'); const translateMessage = useMethod('autoTranslate.translateMessage'); diff --git a/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts b/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts index f54f25a1d00b..78128a7e9355 100644 --- a/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts @@ -1,6 +1,6 @@ -import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { useSetting, useToastMessageDispatch, useUser } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; @@ -12,10 +12,8 @@ import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { roomsQueryKeys } from '../../../lib/queryKeys'; import { useToggleFollowingThreadMutation } from '../../../views/room/contextualBar/Threads/hooks/useToggleFollowingThreadMutation'; -export const useUnFollowMessageAction = ( - message: IMessage, - { room, user, context }: { room: IRoom; user: IUser | undefined; context: MessageActionContext }, -) => { +export const useUnFollowMessageAction = (message: IMessage, { room, context }: { room: IRoom; context: MessageActionContext }) => { + const user = useUser(); const threadsEnabled = useSetting('Threads_enabled'); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts b/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts index 851ce1ae4115..7487937db8d7 100644 --- a/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts @@ -1,12 +1,13 @@ -import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { useSetting } from '@rocket.chat/ui-contexts'; +import { useSetting, useUser } from '@rocket.chat/ui-contexts'; import { useEffect } from 'react'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useUnstarMessageMutation } from '../hooks/useUnstarMessageMutation'; -export const useUnstarMessageAction = (message: IMessage, { room, user }: { room: IRoom; user: IUser | undefined }) => { +export const useUnstarMessageAction = (message: IMessage, { room }: { room: IRoom }) => { + const user = useUser(); const allowStarring = useSetting('Message_AllowStarring'); const { mutateAsync: unstarMessage } = useUnstarMessageMutation(); diff --git a/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts index 25f32857de45..0db063ac4444 100644 --- a/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts +++ b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts @@ -1,5 +1,5 @@ -import type { IMessage, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; -import { useMethod, usePermission, useSetting } from '@rocket.chat/ui-contexts'; +import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; +import { useMethod, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts'; import { useEffect, useMemo } from 'react'; import { AutoTranslate } from '../../../../app/autotranslate/client'; @@ -10,8 +10,9 @@ import { hasTranslationLanguageInAttachments, hasTranslationLanguageInMessage } export const useViewOriginalTranslationAction = ( message: IMessage & { autoTranslateShowInverse?: boolean }, - { user, room, subscription }: { user: IUser | undefined; room: IRoom; subscription: ISubscription | undefined }, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, ) => { + const user = useUser(); const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false); const canAutoTranslate = usePermission('auto-translate'); const translateMessage = useMethod('autoTranslate.translateMessage'); From 59cbfc44f37bdeca961895bad8507bf9e920036b Mon Sep 17 00:00:00 2001 From: Tasso Date: Wed, 11 Dec 2024 21:06:10 -0300 Subject: [PATCH 10/10] Replace `MessageAction` with direct returned values --- apps/meteor/app/ui-utils/client/index.ts | 1 - .../app/ui-utils/client/lib/MessageAction.ts | 42 ------- .../message/hooks/usePinMessageMutation.ts | 1 - .../message/hooks/useStarMessageMutation.ts | 1 - .../message/hooks/useUnpinMessageMutation.ts | 1 - .../message/hooks/useUnstarMessageMutation.ts | 1 - .../toolbar/MessageToolbarActionMenu.tsx | 105 ++++++++---------- .../message/toolbar/useCopyAction.ts | 48 ++++---- .../message/toolbar/useDeleteMessageAction.ts | 78 ++++++------- .../message/toolbar/useEditMessageAction.ts | 86 ++++++-------- .../message/toolbar/useFollowMessageAction.ts | 70 ++++++------ .../toolbar/useMarkAsUnreadMessageAction.ts | 54 ++++----- .../toolbar/useNewDiscussionMessageAction.tsx | 98 ++++++++-------- .../message/toolbar/usePermalinkAction.ts | 58 ++++------ .../message/toolbar/usePinMessageAction.tsx | 50 ++++----- .../toolbar/useReadReceiptsDetailsAction.tsx | 56 +++++----- .../message/toolbar/useReplyInDMAction.ts | 85 +++++++------- .../toolbar/useReportMessageAction.tsx | 68 ++++++------ .../toolbar/useShowMessageReactionsAction.tsx | 56 +++++----- .../message/toolbar/useStarMessageAction.ts | 47 ++++---- .../message/toolbar/useTranslateAction.ts | 82 ++++++-------- .../toolbar/useUnFollowMessageAction.ts | 70 ++++++------ .../message/toolbar/useUnpinMessageAction.tsx | 41 +++---- .../message/toolbar/useUnstarMessageAction.ts | 47 ++++---- .../useViewOriginalTranslationAction.ts | 82 ++++++-------- .../toolbar/useWebDAVMessageAction.tsx | 63 +++++------ apps/meteor/client/lib/queryKeys.ts | 5 +- 27 files changed, 606 insertions(+), 790 deletions(-) diff --git a/apps/meteor/app/ui-utils/client/index.ts b/apps/meteor/app/ui-utils/client/index.ts index b2f9e4264162..7fdb76e73d4e 100644 --- a/apps/meteor/app/ui-utils/client/index.ts +++ b/apps/meteor/app/ui-utils/client/index.ts @@ -1,4 +1,3 @@ -export { MessageAction } from './lib/MessageAction'; export { messageBox } from './lib/messageBox'; export { LegacyRoomManager } from './lib/LegacyRoomManager'; export { upsertMessage, RoomHistoryManager } from './lib/RoomHistoryManager'; diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 91b17c390863..5a1710dc4830 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -1,6 +1,5 @@ import type { Keys as IconName } from '@rocket.chat/icons'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import mem from 'mem'; type MessageActionGroup = 'menu'; @@ -30,47 +29,6 @@ export type MessageActionConfig = { group: MessageActionGroup; context?: MessageActionContext[]; action: (e: Pick | undefined) => any; - condition?: () => Promise | boolean; type?: MessageActionType; disabled?: boolean; }; - -class MessageAction { - private buttons: Record = {}; - - public addButton(config: MessageActionConfig): void { - if (!config?.id) { - return; - } - - if (config.condition) { - config.condition = mem(config.condition, { maxAge: 1000, cacheKey: JSON.stringify }); - } - - this.buttons[config.id] = config; - } - - public removeButton(id: MessageActionConfig['id']): void { - delete this.buttons[id]; - } - - public async getAll(context: MessageActionContext, group: MessageActionGroup): Promise { - return ( - await Promise.all( - Object.values(this.buttons) - .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) - .filter((button) => button.group === group) - .filter((button) => !button.context || button.context.includes(context)) - .map(async (button) => { - return [button, !button.condition || (await button.condition())] as const; - }), - ) - ) - .filter(([, condition]) => condition) - .map(([button]) => button); - } -} - -const instance = new MessageAction(); - -export { instance as MessageAction }; diff --git a/apps/meteor/client/components/message/hooks/usePinMessageMutation.ts b/apps/meteor/client/components/message/hooks/usePinMessageMutation.ts index ce8ec3f9cd81..3405512dbe8b 100644 --- a/apps/meteor/client/components/message/hooks/usePinMessageMutation.ts +++ b/apps/meteor/client/components/message/hooks/usePinMessageMutation.ts @@ -27,7 +27,6 @@ export const usePinMessageMutation = () => { }, onSettled: (_data, _error, message) => { queryClient.invalidateQueries(roomsQueryKeys.pinnedMessages(message.rid)); - queryClient.invalidateQueries(roomsQueryKeys.messageActions(message.rid, message._id)); }, }); }; diff --git a/apps/meteor/client/components/message/hooks/useStarMessageMutation.ts b/apps/meteor/client/components/message/hooks/useStarMessageMutation.ts index da73b73eacd6..eabfc8692643 100644 --- a/apps/meteor/client/components/message/hooks/useStarMessageMutation.ts +++ b/apps/meteor/client/components/message/hooks/useStarMessageMutation.ts @@ -27,7 +27,6 @@ export const useStarMessageMutation = () => { }, onSettled: (_data, _error, message) => { queryClient.invalidateQueries(roomsQueryKeys.starredMessages(message.rid)); - queryClient.invalidateQueries(roomsQueryKeys.messageActions(message.rid, message._id)); }, }); }; diff --git a/apps/meteor/client/components/message/hooks/useUnpinMessageMutation.ts b/apps/meteor/client/components/message/hooks/useUnpinMessageMutation.ts index a3c4c2882b0b..f777929d5689 100644 --- a/apps/meteor/client/components/message/hooks/useUnpinMessageMutation.ts +++ b/apps/meteor/client/components/message/hooks/useUnpinMessageMutation.ts @@ -27,7 +27,6 @@ export const useUnpinMessageMutation = () => { }, onSettled: (_data, _error, message) => { queryClient.invalidateQueries(roomsQueryKeys.pinnedMessages(message.rid)); - queryClient.invalidateQueries(roomsQueryKeys.messageActions(message.rid, message._id)); }, }); }; diff --git a/apps/meteor/client/components/message/hooks/useUnstarMessageMutation.ts b/apps/meteor/client/components/message/hooks/useUnstarMessageMutation.ts index 7cb29fd0bc3f..329e931fe116 100644 --- a/apps/meteor/client/components/message/hooks/useUnstarMessageMutation.ts +++ b/apps/meteor/client/components/message/hooks/useUnstarMessageMutation.ts @@ -27,7 +27,6 @@ export const useUnstarMessageMutation = () => { }, onSettled: (_data, _error, message) => { queryClient.invalidateQueries(roomsQueryKeys.starredMessages(message.rid)); - queryClient.invalidateQueries(roomsQueryKeys.messageActions(message.rid, message._id)); }, }); }; diff --git a/apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx index f8c84cc8ffa4..7c0e0e509eaa 100644 --- a/apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageToolbarActionMenu.tsx @@ -2,7 +2,6 @@ import { isE2EEMessage, type IMessage, type IRoom, type ISubscription } from '@r import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import { GenericMenu, type GenericMenuItemProps } from '@rocket.chat/ui-client'; import { useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -26,8 +25,8 @@ import { useUnpinMessageAction } from './useUnpinMessageAction'; import { useUnstarMessageAction } from './useUnstarMessageAction'; import { useViewOriginalTranslationAction } from './useViewOriginalTranslationAction'; import { useWebDAVMessageAction } from './useWebDAVMessageAction'; -import { MessageAction, type MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { roomsQueryKeys } from '../../../lib/queryKeys'; +import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; +import { isTruthy } from '../../../../lib/isTruthy'; type MessageActionSection = { id: string; @@ -45,58 +44,55 @@ type MessageToolbarActionMenuProps = { const MessageToolbarActionMenu = ({ message, context, room, subscription, onChangeMenuVisibility }: MessageToolbarActionMenuProps) => { // TODO: move this to another place - useWebDAVMessageAction(message, { subscription }); - useNewDiscussionMessageAction(message, { room, subscription }); - useUnpinMessageAction(message, { room, subscription }); - usePinMessageAction(message, { room, subscription }); - useStarMessageAction(message, { room }); - useUnstarMessageAction(message, { room }); - usePermalinkAction(message, { id: 'permalink-star', subscription, context: ['starred'], order: 10 }); - usePermalinkAction(message, { id: 'permalink-pinned', subscription, context: ['pinned'], order: 5 }); - usePermalinkAction(message, { - id: 'permalink', - subscription, - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - type: 'duplication', - order: 5, - }); - useFollowMessageAction(message, { room, context }); - useUnFollowMessageAction(message, { room, context }); - useMarkAsUnreadMessageAction(message, { room, subscription }); - useTranslateAction(message, { room, subscription }); - useViewOriginalTranslationAction(message, { room, subscription }); - useReplyInDMAction(message, { room, subscription }); - useCopyAction(message, { subscription }); - useEditMessageAction(message, { room, subscription }); - useDeleteMessageAction(message, { room, subscription }); - useReportMessageAction(message, { room, subscription }); - useShowMessageReactionsAction(message); - useReadReceiptsDetailsAction(message); + const menuItems = [ + useWebDAVMessageAction(message, { subscription }), + useNewDiscussionMessageAction(message, { room, subscription }), + useUnpinMessageAction(message, { room, subscription }), + usePinMessageAction(message, { room, subscription }), + useStarMessageAction(message, { room }), + useUnstarMessageAction(message, { room }), + usePermalinkAction(message, { id: 'permalink-star', context: ['starred'], order: 10 }), + usePermalinkAction(message, { id: 'permalink-pinned', context: ['pinned'], order: 5 }), + usePermalinkAction(message, { + id: 'permalink', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + type: 'duplication', + order: 5, + }), + useFollowMessageAction(message, { room, context }), + useUnFollowMessageAction(message, { room, context }), + useMarkAsUnreadMessageAction(message, { room, subscription }), + useTranslateAction(message, { room, subscription }), + useViewOriginalTranslationAction(message, { room, subscription }), + useReplyInDMAction(message, { room, subscription }), + useCopyAction(message, { subscription }), + useEditMessageAction(message, { room, subscription }), + useDeleteMessageAction(message, { room, subscription }), + useReportMessageAction(message, { room, subscription }), + useShowMessageReactionsAction(message), + useReadReceiptsDetailsAction(message), + ]; const hiddenActions = useLayoutHiddenActions().messageToolbox; - const { isSuccess, data } = useQuery({ - queryKey: roomsQueryKeys.messageActionsWithParameters(room._id, message), - queryFn: async () => { - const menuItems = await MessageAction.getAll(context, 'menu'); - return menuItems.filter((action) => !hiddenActions.includes(action.id)); - }, - keepPreviousData: true, - }); + const data = menuItems + .filter(isTruthy) + .filter((button) => button.group === 'menu') + .filter((button) => !button.context || button.context.includes(context)) + .filter((action) => !hiddenActions.includes(action.id)) + .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); const actionButtonApps = useMessageActionAppsActionButtons(message, context); const id = useUniqueId(); const { t } = useTranslation(); - if (!isSuccess || data.length === 0) { + if (data.length === 0) { return null; } - const options = [...data, ...(actionButtonApps.data ?? [])]; - const isMessageEncrypted = isE2EEMessage(message); - const groupOptions = options + const groupOptions = [...data, ...(actionButtonApps.data ?? [])] .map((option) => ({ variant: option.color === 'alert' ? 'danger' : '', id: option.id, @@ -108,21 +104,18 @@ const MessageToolbarActionMenu = ({ message, context, room, subscription, onChan ...(typeof option.disabled === 'boolean' && option.disabled && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), })) - .reduce( - (acc, option) => { - const group = option.type ? option.type : ''; - const section = acc.find((section: { id: string }) => section.id === group); - if (section) { - section.items.push(option); - return acc; - } - const newSection = { id: group, title: group === 'apps' ? t('Apps') : '', items: [option] }; - acc.push(newSection); - + .reduce((acc, option) => { + const group = option.type ? option.type : ''; + const section = acc.find((section: { id: string }) => section.id === group); + if (section) { + section.items.push(option); return acc; - }, - [] as unknown as MessageActionSection[], - ) + } + const newSection = { id: group, title: group === 'apps' ? t('Apps') : '', items: [option] }; + acc.push(newSection); + + return acc; + }, [] as MessageActionSection[]) .map((section) => { if (section.id !== 'apps') { return section; diff --git a/apps/meteor/client/components/message/toolbar/useCopyAction.ts b/apps/meteor/client/components/message/toolbar/useCopyAction.ts index 1604eb0308bd..1a03dac99936 100644 --- a/apps/meteor/client/components/message/toolbar/useCopyAction.ts +++ b/apps/meteor/client/components/message/toolbar/useCopyAction.ts @@ -1,9 +1,8 @@ import type { IMessage, ISubscription } from '@rocket.chat/core-typings'; import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; const getMainMessageText = (message: IMessage): IMessage => { const newMessage = { ...message }; @@ -12,32 +11,29 @@ const getMainMessageText = (message: IMessage): IMessage => { return { ...newMessage }; }; -export const useCopyAction = (message: IMessage, { subscription }: { subscription: ISubscription | undefined }) => { +export const useCopyAction = ( + message: IMessage, + { subscription }: { subscription: ISubscription | undefined }, +): MessageActionConfig | null => { const { t } = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); - useEffect(() => { - if (!subscription) { - return; - } + if (!subscription) { + return null; + } - MessageAction.addButton({ - id: 'copy', - icon: 'copy', - label: 'Copy_text', - context: ['message', 'message-mobile', 'threads', 'federated'], - type: 'duplication', - async action() { - const msgText = getMainMessageText(message).msg; - await navigator.clipboard.writeText(msgText); - dispatchToastMessage({ type: 'success', message: t('Copied') }); - }, - order: 6, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('copy'); - }; - }, [dispatchToastMessage, message, subscription, t]); + return { + id: 'copy', + icon: 'copy', + label: 'Copy_text', + context: ['message', 'message-mobile', 'threads', 'federated'], + type: 'duplication', + async action() { + const msgText = getMainMessageText(message).msg; + await navigator.clipboard.writeText(msgText); + dispatchToastMessage({ type: 'success', message: t('Copied') }); + }, + order: 6, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts b/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts index afedb2fa2698..ff8a24d12b84 100644 --- a/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useDeleteMessageAction.ts @@ -1,52 +1,54 @@ import { isRoomFederated } from '@rocket.chat/core-typings'; import type { ISubscription, IRoom, IMessage } from '@rocket.chat/core-typings'; import { useUser } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import { useChat } from '../../../views/room/contexts/ChatContext'; export const useDeleteMessageAction = ( message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const user = useUser(); const chat = useChat(); - useEffect(() => { - if (!subscription) { - return; - } - - MessageAction.addButton({ - id: 'delete-message', - icon: 'trash', - label: 'Delete', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - color: 'alert', - type: 'management', - async action() { - await chat?.flows.requestMessageDeletion(message); - }, - condition() { - if (isRoomFederated(room)) { - return message.u._id === user?._id; - } - - const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); - if (isLivechatRoom) { - return false; - } - - return chat?.data.canDeleteMessage(message) ?? false; - }, - order: 10, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('delete-message'); - }; - }, [chat?.data, chat?.flows, message, room, subscription, user?._id]); + const { data: condition = false } = useQuery({ + queryKey: ['delete-message', message] as const, + queryFn: async () => { + if (!subscription) { + return false; + } + + if (isRoomFederated(room)) { + return message.u._id === user?._id; + } + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); + if (isLivechatRoom) { + return false; + } + + return chat?.data.canDeleteMessage(message) ?? false; + }, + }); + + if (!condition) { + return null; + } + + return { + id: 'delete-message', + icon: 'trash', + label: 'Delete', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + color: 'alert', + type: 'management', + async action() { + await chat?.flows.requestMessageDeletion(message); + }, + order: 10, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts b/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts index 1333b5a40c61..92df4f7cea35 100644 --- a/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useEditMessageAction.ts @@ -2,15 +2,14 @@ import { isRoomFederated } from '@rocket.chat/core-typings'; import type { IRoom, IMessage, ISubscription } from '@rocket.chat/core-typings'; import { usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts'; import moment from 'moment'; -import { useEffect } from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useChat } from '../../../views/room/contexts/ChatContext'; export const useEditMessageAction = ( message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const user = useUser(); const chat = useChat(); const isEditAllowed = useSetting('Message_AllowEditing', true); @@ -18,54 +17,43 @@ export const useEditMessageAction = ( const blockEditInMinutes = useSetting('Message_AllowEditing_BlockEditInMinutes', 0); const canBypassBlockTimeLimit = usePermission('bypass-time-limit-edit-and-delete', message.rid); - useEffect(() => { - if (!subscription) { - return; - } - - MessageAction.addButton({ - id: 'edit-message', - icon: 'edit', - label: 'Edit', - context: ['message', 'message-mobile', 'threads', 'federated'], - type: 'management', - async action() { - await chat?.messageEditing.editMessage(message); - }, - condition() { - if (isRoomFederated(room)) { - return message.u._id === user?._id; - } + if (!subscription) { + return null; + } - const editOwn = message.u && message.u._id === user?._id; - if (!canEditMessage && (!isEditAllowed || !editOwn)) { - return false; - } + const condition = (() => { + if (isRoomFederated(room)) { + return message.u._id === user?._id; + } - if (!canBypassBlockTimeLimit && blockEditInMinutes) { - const msgTs = message.ts ? moment(message.ts) : undefined; - const currentTsDiff = msgTs ? moment().diff(msgTs, 'minutes') : undefined; - return typeof currentTsDiff === 'number' && currentTsDiff < blockEditInMinutes; - } + const editOwn = message.u && message.u._id === user?._id; + if (!canEditMessage && (!isEditAllowed || !editOwn)) { + return false; + } - return true; - }, - order: 8, - group: 'menu', - }); + if (!canBypassBlockTimeLimit && blockEditInMinutes) { + const msgTs = message.ts ? moment(message.ts) : undefined; + const currentTsDiff = msgTs ? moment().diff(msgTs, 'minutes') : undefined; + return typeof currentTsDiff === 'number' && currentTsDiff < blockEditInMinutes; + } - return () => { - MessageAction.removeButton('edit-message'); - }; - }, [ - blockEditInMinutes, - canBypassBlockTimeLimit, - canEditMessage, - chat?.messageEditing, - isEditAllowed, - message, - room, - subscription, - user?._id, - ]); + return true; + })(); + + if (!condition) { + return null; + } + + return { + id: 'edit-message', + icon: 'edit', + label: 'Edit', + context: ['message', 'message-mobile', 'threads', 'federated'], + type: 'management', + async action() { + await chat?.messageEditing.editMessage(message); + }, + order: 8, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts b/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts index 76049e347f1b..3a9409260035 100644 --- a/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useFollowMessageAction.ts @@ -1,25 +1,23 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { useSetting, useToastMessageDispatch, useUser } from '@rocket.chat/ui-contexts'; -import { useQueryClient } from '@tanstack/react-query'; -import { useEffect } from 'react'; import { Messages } from '../../../../app/models/client'; -import { MessageAction } from '../../../../app/ui-utils/client'; -import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionContext, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { t } from '../../../../app/utils/lib/i18n'; import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { roomsQueryKeys } from '../../../lib/queryKeys'; import { useToggleFollowingThreadMutation } from '../../../views/room/contextualBar/Threads/hooks/useToggleFollowingThreadMutation'; -export const useFollowMessageAction = (message: IMessage, { room, context }: { room: IRoom; context: MessageActionContext }) => { +export const useFollowMessageAction = ( + message: IMessage, + { room, context }: { room: IRoom; context: MessageActionContext }, +): MessageActionConfig | null => { const user = useUser(); const threadsEnabled = useSetting('Threads_enabled'); const dispatchToastMessage = useToastMessageDispatch(); - const queryClient = useQueryClient(); - const { mutate: toggleFollowingThread } = useToggleFollowingThreadMutation({ onSuccess: () => { dispatchToastMessage({ @@ -34,40 +32,36 @@ export const useFollowMessageAction = (message: IMessage, { room, context }: { r Messages.findOne({ _id: tmid || _id }, { fields: { replies: 1 } }), ); - useEffect(() => { - if (!message || !threadsEnabled || isOmnichannelRoom(room)) { - return; - } - - let { replies = [] } = message; - if (tmid || context) { - const parentMessage = messageQuery.data; - if (parentMessage) { - replies = parentMessage.replies || []; - } - } + if (!message || !threadsEnabled || isOmnichannelRoom(room)) { + return null; + } - if (!user?._id) { - return; + let { replies = [] } = message; + if (tmid || context) { + const parentMessage = messageQuery.data; + if (parentMessage) { + replies = parentMessage.replies || []; } + } - if ((replies as string[]).includes(user._id)) { - return; - } + if (!user?._id) { + return null; + } - MessageAction.addButton({ - id: 'follow-message', - icon: 'bell', - label: 'Follow_message', - type: 'interaction', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - action() { - toggleFollowingThread({ tmid: tmid || _id, follow: true, rid: room._id }); - }, - order: 1, - group: 'menu', - }); + if (replies.includes(user._id)) { + return null; + } - return () => MessageAction.removeButton('follow-message'); - }, [_id, context, message, messageQuery, messageQuery.data, queryClient, room, threadsEnabled, tmid, toggleFollowingThread, user]); + return { + id: 'follow-message', + icon: 'bell', + label: 'Follow_message', + type: 'interaction', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + action() { + toggleFollowingThread({ tmid: tmid || _id, follow: true, rid: room._id }); + }, + order: 1, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts b/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts index b894f32cdb12..54f70cb08d41 100644 --- a/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useMarkAsUnreadMessageAction.ts @@ -1,48 +1,42 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import type { ISubscription, IMessage, IRoom } from '@rocket.chat/core-typings'; import { useRouter, useUser } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useMarkAsUnreadMutation } from '../hooks/useMarkAsUnreadMutation'; export const useMarkAsUnreadMessageAction = ( message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const user = useUser(); const { mutateAsync: markAsUnread } = useMarkAsUnreadMutation(); const router = useRouter(); - useEffect(() => { - if (isOmnichannelRoom(room) || !user) { - return; - } + if (isOmnichannelRoom(room) || !user) { + return null; + } - if (!subscription) { - return; - } + if (!subscription) { + return null; + } - if (message.u._id === user._id) { - return; - } + if (message.u._id === user._id) { + return null; + } - MessageAction.addButton({ - id: 'mark-message-as-unread', - icon: 'flag', - label: 'Mark_unread', - context: ['message', 'message-mobile', 'threads'], - type: 'interaction', - async action() { - router.navigate('/home'); - await markAsUnread({ message, subscription }); - }, - order: 4, - group: 'menu', - }); - return () => { - MessageAction.removeButton('mark-message-as-unread'); - }; - }, [markAsUnread, message, room, router, subscription, user]); + return { + id: 'mark-message-as-unread', + icon: 'flag', + label: 'Mark_unread', + context: ['message', 'message-mobile', 'threads'], + type: 'interaction', + async action() { + router.navigate('/home'); + await markAsUnread({ message, subscription }); + }, + order: 4, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx index c7ac70d3aee8..18a6c267da4e 100644 --- a/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useNewDiscussionMessageAction.tsx @@ -1,15 +1,15 @@ import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { usePermission, useSetModal, useSetting, useUser } from '@rocket.chat/ui-contexts'; -import React, { useEffect } from 'react'; +import React from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import CreateDiscussion from '../../CreateDiscussion'; export const useNewDiscussionMessageAction = ( message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const user = useUser(); const enabled = useSetting('Discussion_enabled', false); @@ -18,59 +18,53 @@ export const useNewDiscussionMessageAction = ( const canStartDiscussion = usePermission('start-discussion', room._id); const canStartDiscussionOtherUser = usePermission('start-discussion-other-user', room._id); - useEffect(() => { - if (!enabled) { - return; - } + if (!enabled) { + return null; + } - const { - u: { _id: uid }, - drid, - dcount, - } = message; - if (drid || !Number.isNaN(Number(dcount))) { - return; - } + const { + u: { _id: uid }, + drid, + dcount, + } = message; + if (drid || !Number.isNaN(Number(dcount))) { + return null; + } - if (!subscription) { - return; - } + if (!subscription) { + return null; + } - const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); - if (isLivechatRoom) { - return; - } + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); + if (isLivechatRoom) { + return null; + } - if (!user) { - return; - } + if (!user) { + return null; + } - if (!(uid !== user._id ? canStartDiscussionOtherUser : canStartDiscussion)) { - return; - } + if (!(uid !== user._id ? canStartDiscussionOtherUser : canStartDiscussion)) { + return null; + } - MessageAction.addButton({ - id: 'start-discussion', - icon: 'discussion', - label: 'Discussion_start', - type: 'communication', - context: ['message', 'message-mobile', 'videoconf'], - async action() { - setModal( - setModal(undefined)} - parentMessageId={message._id} - nameSuggestion={message?.msg?.substr(0, 140)} - />, - ); - }, - order: 1, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('start-discussion'); - }; - }, [canStartDiscussion, canStartDiscussionOtherUser, enabled, message, room?._id, room?.prid, room.t, setModal, subscription, user]); + return { + id: 'start-discussion', + icon: 'discussion', + label: 'Discussion_start', + type: 'communication', + context: ['message', 'message-mobile', 'videoconf'], + async action() { + setModal( + setModal(undefined)} + parentMessageId={message._id} + nameSuggestion={message?.msg?.substr(0, 140)} + />, + ); + }, + order: 1, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts b/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts index a1335e958a7c..d3d0ea975dc2 100644 --- a/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts +++ b/apps/meteor/client/components/message/toolbar/usePermalinkAction.ts @@ -1,52 +1,38 @@ -import type { IMessage, ISubscription } from '@rocket.chat/core-typings'; +import type { IMessage } from '@rocket.chat/core-typings'; import { isE2EEMessage } from '@rocket.chat/core-typings'; import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import type { MessageActionConfig, MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; import { getPermaLink } from '../../../lib/getPermaLink'; export const usePermalinkAction = ( message: IMessage, - { - subscription, - id, - context, - type, - order, - }: { subscription: ISubscription | undefined; context: MessageActionContext[]; order: number } & Pick, -) => { + { id, context, type, order }: { context: MessageActionContext[]; order: number } & Pick, +): MessageActionConfig | null => { const { t } = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); const encrypted = isE2EEMessage(message); - useEffect(() => { - MessageAction.addButton({ - id, - icon: 'permalink', - label: 'Copy_link', - context, - type, - async action() { - try { - const permalink = await getPermaLink(message._id); - navigator.clipboard.writeText(permalink); - dispatchToastMessage({ type: 'success', message: t('Copied') }); - } catch (e) { - dispatchToastMessage({ type: 'error', message: e }); - } - }, - order, - group: 'menu', - disabled: encrypted, - }); - - return () => { - MessageAction.removeButton(id); - }; - }, [context, dispatchToastMessage, encrypted, id, message._id, order, subscription, t, type]); + return { + id, + icon: 'permalink', + label: 'Copy_link', + context, + type, + async action() { + try { + const permalink = await getPermaLink(message._id); + navigator.clipboard.writeText(permalink); + dispatchToastMessage({ type: 'success', message: t('Copied') }); + } catch (e) { + dispatchToastMessage({ type: 'error', message: e }); + } + }, + order, + group: 'menu', + disabled: encrypted, + }; }; diff --git a/apps/meteor/client/components/message/toolbar/usePinMessageAction.tsx b/apps/meteor/client/components/message/toolbar/usePinMessageAction.tsx index 53b5764de475..7012d1c7e4a3 100644 --- a/apps/meteor/client/components/message/toolbar/usePinMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/usePinMessageAction.tsx @@ -1,47 +1,41 @@ import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { useSetting, useSetModal, usePermission } from '@rocket.chat/ui-contexts'; -import React, { useEffect } from 'react'; +import React from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import PinMessageModal from '../../../views/room/modals/PinMessageModal'; import { usePinMessageMutation } from '../hooks/usePinMessageMutation'; export const usePinMessageAction = ( message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const setModal = useSetModal(); const allowPinning = useSetting('Message_AllowPinning'); const hasPermission = usePermission('pin-message', room._id); const { mutateAsync: pinMessage } = usePinMessageMutation(); - useEffect(() => { - if (!allowPinning || isOmnichannelRoom(room) || !hasPermission || message.pinned || !subscription) { - return; - } + if (!allowPinning || isOmnichannelRoom(room) || !hasPermission || message.pinned || !subscription) { + return null; + } - const onConfirm = async () => { - pinMessage(message); - setModal(null); - }; + const onConfirm = async () => { + pinMessage(message); + setModal(null); + }; - MessageAction.addButton({ - id: 'pin-message', - icon: 'pin', - label: 'Pin', - type: 'interaction', - context: ['pinned', 'message', 'message-mobile', 'threads', 'direct', 'videoconf', 'videoconf-threads'], - async action() { - setModal( setModal(null)} />); - }, - order: 2, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('pin-message'); - }; - }, [allowPinning, hasPermission, message, pinMessage, room, setModal, subscription]); + return { + id: 'pin-message', + icon: 'pin', + label: 'Pin', + type: 'interaction', + context: ['pinned', 'message', 'message-mobile', 'threads', 'direct', 'videoconf', 'videoconf-threads'], + async action() { + setModal( setModal(null)} />); + }, + order: 2, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx b/apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx index e71bd169359f..7cd87b2fd564 100644 --- a/apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx @@ -1,43 +1,37 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { useSetModal, useSetting } from '@rocket.chat/ui-contexts'; -import React, { useEffect } from 'react'; +import React from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import ReadReceiptsModal from '../../../views/room/modals/ReadReceiptsModal'; -export const useReadReceiptsDetailsAction = (message: IMessage) => { +export const useReadReceiptsDetailsAction = (message: IMessage): MessageActionConfig | null => { const setModal = useSetModal(); const readReceiptsEnabled = useSetting('Message_Read_Receipt_Enabled', false); const readReceiptsStoreUsers = useSetting('Message_Read_Receipt_Store_Users', false); - useEffect(() => { - if (!readReceiptsEnabled || !readReceiptsStoreUsers) { - return; - } + if (!readReceiptsEnabled || !readReceiptsStoreUsers) { + return null; + } - MessageAction.addButton({ - id: 'receipt-detail', - icon: 'check-double', - label: 'Read_Receipts', - context: ['starred', 'message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], - type: 'duplication', - action() { - setModal( - { - setModal(null); - }} - />, - ); - }, - order: 10, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('receipt-detail'); - }; - }, [message._id, readReceiptsEnabled, readReceiptsStoreUsers, setModal]); + return { + id: 'receipt-detail', + icon: 'check-double', + label: 'Read_Receipts', + context: ['starred', 'message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], + type: 'duplication', + action() { + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 10, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts index 5bb75025177b..abf4babf1577 100644 --- a/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts +++ b/apps/meteor/client/components/message/toolbar/useReplyInDMAction.ts @@ -1,58 +1,63 @@ import { type IMessage, type ISubscription, type IRoom, isE2EEMessage } from '@rocket.chat/core-typings'; import { usePermission, useRouter, useUser } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; +import { useCallback } from 'react'; import { Rooms, Subscriptions } from '../../../../app/models/client'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; +import { useReactiveValue } from '../../../hooks/useReactiveValue'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; -export const useReplyInDMAction = (message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }) => { +export const useReplyInDMAction = ( + message: IMessage, + { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, +): MessageActionConfig | null => { const user = useUser(); const router = useRouter(); const encrypted = isE2EEMessage(message); const canCreateDM = usePermission('create-d'); const isLayoutEmbedded = useEmbeddedLayout(); - useEffect(() => { - if (!subscription || room.t === 'd' || room.t === 'l' || isLayoutEmbedded) { - return; - } + const condition = useReactiveValue( + useCallback(() => { + if (!subscription || room.t === 'd' || room.t === 'l' || isLayoutEmbedded) { + return false; + } - MessageAction.addButton({ - id: 'reply-directly', - icon: 'reply-directly', - label: 'Reply_in_direct_message', - context: ['message', 'message-mobile', 'threads', 'federated'], - type: 'communication', - action() { - roomCoordinator.openRouteLink( - 'd', - { name: message.u.username }, - { - ...router.getSearchParameters(), - reply: message._id, - }, - ); - }, - condition() { - // Check if we already have a DM started with the message user (not ourselves) or we can start one - if (!!user && user._id !== message.u._id && !canCreateDM) { - const dmRoom = Rooms.findOne({ _id: [user._id, message.u._id].sort().join('') }); - if (!dmRoom || !Subscriptions.findOne({ 'rid': dmRoom._id, 'u._id': user._id })) { - return false; - } + // Check if we already have a DM started with the message user (not ourselves) or we can start one + if (!!user && user._id !== message.u._id && !canCreateDM) { + const dmRoom = Rooms.findOne({ _id: [user._id, message.u._id].sort().join('') }); + if (!dmRoom || !Subscriptions.findOne({ 'rid': dmRoom._id, 'u._id': user._id })) { + return false; } + } - return true; - }, - order: 0, - group: 'menu', - disabled: encrypted, - }); + return true; + }, [canCreateDM, isLayoutEmbedded, message.u._id, room.t, subscription, user]), + ); - return () => { - MessageAction.removeButton('reply-directly'); - }; - }, [canCreateDM, encrypted, isLayoutEmbedded, message._id, message.u._id, message.u.username, room.t, router, subscription, user]); + if (!condition) { + return null; + } + + return { + id: 'reply-directly', + icon: 'reply-directly', + label: 'Reply_in_direct_message', + context: ['message', 'message-mobile', 'threads', 'federated'], + type: 'communication', + action() { + roomCoordinator.openRouteLink( + 'd', + { name: message.u.username }, + { + ...router.getSearchParameters(), + reply: message._id, + }, + ); + }, + order: 0, + group: 'menu', + disabled: encrypted, + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx index 0154fcf95806..0ba5d653743b 100644 --- a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx @@ -1,8 +1,8 @@ import type { ISubscription, IRoom, IMessage } from '@rocket.chat/core-typings'; import { useSetModal, useUser } from '@rocket.chat/ui-contexts'; -import React, { useEffect } from 'react'; +import React from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import ReportMessageModal from '../../../views/room/modals/ReportMessageModal'; @@ -16,44 +16,38 @@ const getMainMessageText = (message: IMessage): IMessage => { export const useReportMessageAction = ( message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const user = useUser(); const setModal = useSetModal(); const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); - useEffect(() => { - if (!subscription) { - return; - } - - if (isLivechatRoom || message.u._id === user?._id) { - return; - } - - MessageAction.addButton({ - id: 'report-message', - icon: 'report', - label: 'Report', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - color: 'alert', - type: 'management', - action() { - setModal( - { - setModal(null); - }} - />, - ); - }, - order: 9, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('report-message'); - }; - }, [isLivechatRoom, message, setModal, subscription, user?._id]); + if (!subscription) { + return null; + } + + if (isLivechatRoom || message.u._id === user?._id) { + return null; + } + + return { + id: 'report-message', + icon: 'report', + label: 'Report', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + color: 'alert', + type: 'management', + action() { + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 9, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx b/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx index 4f4b006fea5e..e2fe4eb4661e 100644 --- a/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useShowMessageReactionsAction.tsx @@ -1,40 +1,34 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { useSetModal } from '@rocket.chat/ui-contexts'; -import React, { useEffect } from 'react'; +import React from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import ReactionListModal from '../../../views/room/modals/ReactionListModal'; -export const useShowMessageReactionsAction = (message: IMessage) => { +export const useShowMessageReactionsAction = (message: IMessage): MessageActionConfig | null => { const setModal = useSetModal(); - useEffect(() => { - if (!message.reactions) { - return; - } + if (!message.reactions) { + return null; + } - MessageAction.addButton({ - id: 'reaction-list', - icon: 'emoji', - label: 'Reactions', - context: ['message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], - type: 'interaction', - action() { - setModal( - { - setModal(null); - }} - />, - ); - }, - order: 9, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('reaction-list'); - }; - }, [message.reactions, setModal]); + return { + id: 'reaction-list', + icon: 'emoji', + label: 'Reactions', + context: ['message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'], + type: 'interaction', + action() { + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 9, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts b/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts index 4f8fd6abd1ca..df24b8b49f41 100644 --- a/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useStarMessageAction.ts @@ -1,41 +1,34 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { useSetting, useUser } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useStarMessageMutation } from '../hooks/useStarMessageMutation'; -export const useStarMessageAction = (message: IMessage, { room }: { room: IRoom }) => { +export const useStarMessageAction = (message: IMessage, { room }: { room: IRoom }): MessageActionConfig | null => { const user = useUser(); const allowStarring = useSetting('Message_AllowStarring', true); const { mutateAsync: starMessage } = useStarMessageMutation(); - useEffect(() => { - if (!allowStarring || isOmnichannelRoom(room)) { - return; - } + if (!allowStarring || isOmnichannelRoom(room)) { + return null; + } - if (Array.isArray(message.starred) && message.starred.some((star) => star._id === user?._id)) { - return; - } + if (Array.isArray(message.starred) && message.starred.some((star) => star._id === user?._id)) { + return null; + } - MessageAction.addButton({ - id: 'star-message', - icon: 'star', - label: 'Star', - type: 'interaction', - context: ['starred', 'message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - async action() { - await starMessage(message); - }, - order: 3, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('star-message'); - }; - }, [allowStarring, message, room, starMessage, user?._id]); + return { + id: 'star-message', + icon: 'star', + label: 'Star', + type: 'interaction', + context: ['starred', 'message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + async action() { + await starMessage(message); + }, + order: 3, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useTranslateAction.ts b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts index 27e8e2afa0b8..5848b172b978 100644 --- a/apps/meteor/client/components/message/toolbar/useTranslateAction.ts +++ b/apps/meteor/client/components/message/toolbar/useTranslateAction.ts @@ -1,17 +1,17 @@ import type { IMessage, ISubscription, IRoom } from '@rocket.chat/core-typings'; import { useMethod, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { AutoTranslate } from '../../../../app/autotranslate/client'; import { Messages } from '../../../../app/models/client'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import { hasTranslationLanguageInAttachments, hasTranslationLanguageInMessage } from '../../../views/room/MessageList/lib/autoTranslate'; export const useTranslateAction = ( message: IMessage & { autoTranslateShowInverse?: boolean }, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const user = useUser(); const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false); const canAutoTranslate = usePermission('auto-translate'); @@ -26,50 +26,34 @@ export const useTranslateAction = ( [message, language], ); - useEffect(() => { - if (!autoTranslateEnabled || !canAutoTranslate || !user) { - return; - } - - const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); - const isDifferentUser = message?.u && message.u._id !== user._id; - const autoTranslationActive = subscription?.autoTranslate || isLivechatRoom; - - if (!message.autoTranslateShowInverse && (!isDifferentUser || !autoTranslationActive || hasTranslations)) { - return; - } - - MessageAction.addButton({ - id: 'translate', - icon: 'language', - label: 'Translate', - context: ['message', 'message-mobile', 'threads'], - type: 'interaction', - group: 'menu', - action() { - if (!hasTranslations) { - AutoTranslate.messageIdsToWait[message._id] = true; - Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); - void translateMessage(message, language); - } - const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; - Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); - }, - order: 90, - }); - - return () => { - MessageAction.removeButton('translate'); - }; - }, [ - autoTranslateEnabled, - canAutoTranslate, - hasTranslations, - language, - message, - room?.t, - subscription?.autoTranslate, - translateMessage, - user, - ]); + if (!autoTranslateEnabled || !canAutoTranslate || !user) { + return null; + } + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); + const isDifferentUser = message?.u && message.u._id !== user._id; + const autoTranslationActive = subscription?.autoTranslate || isLivechatRoom; + + if (!message.autoTranslateShowInverse && (!isDifferentUser || !autoTranslationActive || hasTranslations)) { + return null; + } + + return { + id: 'translate', + icon: 'language', + label: 'Translate', + context: ['message', 'message-mobile', 'threads'], + type: 'interaction', + group: 'menu', + action() { + if (!hasTranslations) { + AutoTranslate.messageIdsToWait[message._id] = true; + Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); + void translateMessage(message, language); + } + const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; + Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); + }, + order: 90, + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts b/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts index 78128a7e9355..7ebca9e01180 100644 --- a/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useUnFollowMessageAction.ts @@ -1,25 +1,23 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { useSetting, useToastMessageDispatch, useUser } from '@rocket.chat/ui-contexts'; -import { useQueryClient } from '@tanstack/react-query'; -import { useEffect } from 'react'; import { Messages } from '../../../../app/models/client'; -import { MessageAction } from '../../../../app/ui-utils/client'; -import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionContext, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { t } from '../../../../app/utils/lib/i18n'; import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { roomsQueryKeys } from '../../../lib/queryKeys'; import { useToggleFollowingThreadMutation } from '../../../views/room/contextualBar/Threads/hooks/useToggleFollowingThreadMutation'; -export const useUnFollowMessageAction = (message: IMessage, { room, context }: { room: IRoom; context: MessageActionContext }) => { +export const useUnFollowMessageAction = ( + message: IMessage, + { room, context }: { room: IRoom; context: MessageActionContext }, +): MessageActionConfig | null => { const user = useUser(); const threadsEnabled = useSetting('Threads_enabled'); const dispatchToastMessage = useToastMessageDispatch(); - const queryClient = useQueryClient(); - const { mutate: toggleFollowingThread } = useToggleFollowingThreadMutation({ onSuccess: () => { dispatchToastMessage({ @@ -35,41 +33,37 @@ export const useUnFollowMessageAction = (message: IMessage, { room, context }: { () => Messages.findOne({ _id: tmid || _id }, { fields: { replies: 1 } }) ?? null, ); - useEffect(() => { - if (!message || !threadsEnabled || isOmnichannelRoom(room)) { - return; - } - - let { replies } = message; + if (!message || !threadsEnabled || isOmnichannelRoom(room)) { + return null; + } - if (tmid || context) { - const parentMessage = messageQuery.data; - if (parentMessage) { - replies = parentMessage.replies || []; - } - } + let { replies } = message; - if (!user?._id) { - return; + if (tmid || context) { + const parentMessage = messageQuery.data; + if (parentMessage) { + replies = parentMessage.replies || []; } + } - if (!replies?.includes(user._id)) { - return; - } + if (!user?._id) { + return null; + } - MessageAction.addButton({ - id: 'unfollow-message', - icon: 'bell-off', - label: 'Unfollow_message', - type: 'interaction', - context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - action() { - toggleFollowingThread({ tmid: tmid || _id, follow: false, rid: room._id }); - }, - order: 1, - group: 'menu', - }); + if (!replies?.includes(user._id)) { + return null; + } - return () => MessageAction.removeButton('unfollow-message'); - }, [_id, context, message, messageQuery.data, queryClient, room, threadsEnabled, tmid, toggleFollowingThread, user]); + return { + id: 'unfollow-message', + icon: 'bell-off', + label: 'Unfollow_message', + type: 'interaction', + context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + action() { + toggleFollowingThread({ tmid: tmid || _id, follow: false, rid: room._id }); + }, + order: 1, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useUnpinMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useUnpinMessageAction.tsx index 06daaaa45dce..038dd4a662d9 100644 --- a/apps/meteor/client/components/message/toolbar/useUnpinMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useUnpinMessageAction.tsx @@ -1,40 +1,33 @@ import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { useSetting, usePermission } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useUnpinMessageMutation } from '../hooks/useUnpinMessageMutation'; export const useUnpinMessageAction = ( message: IMessage, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const allowPinning = useSetting('Message_AllowPinning'); const hasPermission = usePermission('pin-message', room._id); const { mutate: unpinMessage } = useUnpinMessageMutation(); - useEffect(() => { - if (!allowPinning || isOmnichannelRoom(room) || !hasPermission || !message.pinned || !subscription) { - return; - } + if (!allowPinning || isOmnichannelRoom(room) || !hasPermission || !message.pinned || !subscription) { + return null; + } - MessageAction.addButton({ - id: 'unpin-message', - icon: 'pin', - label: 'Unpin', - type: 'interaction', - context: ['pinned', 'message', 'message-mobile', 'threads', 'direct', 'videoconf', 'videoconf-threads'], - action() { - unpinMessage(message); - }, - order: 2, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('unpin-message'); - }; - }, [allowPinning, hasPermission, message, room, subscription, unpinMessage]); + return { + id: 'unpin-message', + icon: 'pin', + label: 'Unpin', + type: 'interaction', + context: ['pinned', 'message', 'message-mobile', 'threads', 'direct', 'videoconf', 'videoconf-threads'], + action() { + unpinMessage(message); + }, + order: 2, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts b/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts index 7487937db8d7..4ffb090dffc5 100644 --- a/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts +++ b/apps/meteor/client/components/message/toolbar/useUnstarMessageAction.ts @@ -1,41 +1,34 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { useSetting, useUser } from '@rocket.chat/ui-contexts'; -import { useEffect } from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useUnstarMessageMutation } from '../hooks/useUnstarMessageMutation'; -export const useUnstarMessageAction = (message: IMessage, { room }: { room: IRoom }) => { +export const useUnstarMessageAction = (message: IMessage, { room }: { room: IRoom }): MessageActionConfig | null => { const user = useUser(); const allowStarring = useSetting('Message_AllowStarring'); const { mutateAsync: unstarMessage } = useUnstarMessageMutation(); - useEffect(() => { - if (!allowStarring || isOmnichannelRoom(room)) { - return; - } + if (!allowStarring || isOmnichannelRoom(room)) { + return null; + } - if (!Array.isArray(message.starred) || message.starred.every((star) => star._id !== user?._id)) { - return; - } + if (!Array.isArray(message.starred) || message.starred.every((star) => star._id !== user?._id)) { + return null; + } - MessageAction.addButton({ - id: 'unstar-message', - icon: 'star', - label: 'Unstar_Message', - type: 'interaction', - context: ['starred', 'message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], - async action() { - await unstarMessage(message); - }, - order: 3, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('unstar-message'); - }; - }, [allowStarring, message, room, unstarMessage, user?._id]); + return { + id: 'unstar-message', + icon: 'star', + label: 'Unstar_Message', + type: 'interaction', + context: ['starred', 'message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'], + async action() { + await unstarMessage(message); + }, + order: 3, + group: 'menu', + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts index 0db063ac4444..3d6e91e5eb54 100644 --- a/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts +++ b/apps/meteor/client/components/message/toolbar/useViewOriginalTranslationAction.ts @@ -1,17 +1,17 @@ import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { useMethod, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { AutoTranslate } from '../../../../app/autotranslate/client'; import { Messages } from '../../../../app/models/client'; -import { MessageAction } from '../../../../app/ui-utils/client'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; import { hasTranslationLanguageInAttachments, hasTranslationLanguageInMessage } from '../../../views/room/MessageList/lib/autoTranslate'; export const useViewOriginalTranslationAction = ( message: IMessage & { autoTranslateShowInverse?: boolean }, { room, subscription }: { room: IRoom; subscription: ISubscription | undefined }, -) => { +): MessageActionConfig | null => { const user = useUser(); const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false); const canAutoTranslate = usePermission('auto-translate'); @@ -26,50 +26,34 @@ export const useViewOriginalTranslationAction = ( [message, language], ); - useEffect(() => { - if (!autoTranslateEnabled || !canAutoTranslate || !user) { - return; - } - - const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); - const isDifferentUser = message?.u && message.u._id !== user._id; - const autoTranslationActive = subscription?.autoTranslate || isLivechatRoom; - - if (message.autoTranslateShowInverse || !isDifferentUser || !autoTranslationActive || !hasTranslations) { - return; - } - - MessageAction.addButton({ - id: 'view-original', - icon: 'language', - label: 'View_original', - context: ['message', 'message-mobile', 'threads'], - type: 'interaction', - group: 'menu', - action() { - if (!hasTranslations) { - AutoTranslate.messageIdsToWait[message._id] = true; - Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); - void translateMessage(message, language); - } - const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; - Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); - }, - order: 90, - }); - - return () => { - MessageAction.removeButton('view-original'); - }; - }, [ - autoTranslateEnabled, - canAutoTranslate, - hasTranslations, - language, - message, - room?.t, - subscription?.autoTranslate, - translateMessage, - user, - ]); + if (!autoTranslateEnabled || !canAutoTranslate || !user) { + return null; + } + + const isLivechatRoom = roomCoordinator.isLivechatRoom(room?.t); + const isDifferentUser = message?.u && message.u._id !== user._id; + const autoTranslationActive = subscription?.autoTranslate || isLivechatRoom; + + if (message.autoTranslateShowInverse || !isDifferentUser || !autoTranslationActive || !hasTranslations) { + return null; + } + + return { + id: 'view-original', + icon: 'language', + label: 'View_original', + context: ['message', 'message-mobile', 'threads'], + type: 'interaction', + group: 'menu', + action() { + if (!hasTranslations) { + AutoTranslate.messageIdsToWait[message._id] = true; + Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); + void translateMessage(message, language); + } + const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; + Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); + }, + order: 90, + }; }; diff --git a/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx index 082985710fc9..fe2c753a397f 100644 --- a/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/useWebDAVMessageAction.tsx @@ -1,47 +1,44 @@ import type { IMessage, ISubscription } from '@rocket.chat/core-typings'; import { useSetModal, useSetting } from '@rocket.chat/ui-contexts'; -import React, { useEffect } from 'react'; +import React from 'react'; -import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import { getURL } from '../../../../app/utils/client'; import { useWebDAVAccountIntegrationsQuery } from '../../../hooks/webdav/useWebDAVAccountIntegrationsQuery'; import SaveToWebdavModal from '../../../views/room/webdav/SaveToWebdavModal'; -export const useWebDAVMessageAction = (message: IMessage, { subscription }: { subscription: ISubscription | undefined }) => { +export const useWebDAVMessageAction = ( + message: IMessage, + { subscription }: { subscription: ISubscription | undefined }, +): MessageActionConfig | null => { const enabled = useSetting('Webdav_Integration_Enabled', false); const { data } = useWebDAVAccountIntegrationsQuery({ enabled }); const setModal = useSetModal(); - useEffect(() => { - if (!enabled || !subscription || !data?.length || !message.file) { - return; - } - - MessageAction.addButton({ - id: 'webdav-upload', - icon: 'upload', - label: 'Save_To_Webdav', - action() { - const [attachment] = message.attachments || []; - const url = getURL(attachment.title_link as string, { full: true }); - - setModal( - { - setModal(null); - }} - />, - ); - }, - order: 100, - group: 'menu', - }); - - return () => { - MessageAction.removeButton('webdav-upload'); - }; - }, [data?.length, enabled, message.attachments, message.file, setModal, subscription]); + if (!enabled || !subscription || !data?.length || !message.file) { + return null; + } + + return { + id: 'webdav-upload', + icon: 'upload', + label: 'Save_To_Webdav', + action() { + const [attachment] = message.attachments || []; + const url = getURL(attachment.title_link as string, { full: true }); + + setModal( + { + setModal(null); + }} + />, + ); + }, + order: 100, + group: 'menu', + }; }; diff --git a/apps/meteor/client/lib/queryKeys.ts b/apps/meteor/client/lib/queryKeys.ts index 40382fcd6bef..a9217935b821 100644 --- a/apps/meteor/client/lib/queryKeys.ts +++ b/apps/meteor/client/lib/queryKeys.ts @@ -1,4 +1,4 @@ -import type { IMessage, IRoom, Serialized } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; export const roomsQueryKeys = { all: ['rooms'] as const, @@ -7,9 +7,6 @@ export const roomsQueryKeys = { pinnedMessages: (rid: IRoom['_id']) => [...roomsQueryKeys.room(rid), 'pinned-messages'] as const, messages: (rid: IRoom['_id']) => [...roomsQueryKeys.room(rid), 'messages'] as const, message: (rid: IRoom['_id'], mid: IMessage['_id']) => [...roomsQueryKeys.messages(rid), mid] as const, - messageActions: (rid: IRoom['_id'], mid: IMessage['_id']) => [...roomsQueryKeys.message(rid, mid), 'actions'] as const, - messageActionsWithParameters: (rid: IRoom['_id'], message: IMessage | Serialized) => - [...roomsQueryKeys.messageActions(rid, message._id), message] as const, threads: (rid: IRoom['_id']) => [...roomsQueryKeys.room(rid), 'threads'] as const, };