diff --git a/.changeset/plenty-buses-kneel.md b/.changeset/plenty-buses-kneel.md new file mode 100644 index 00000000000..5c21bdb0bb6 --- /dev/null +++ b/.changeset/plenty-buses-kneel.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Disable "Reply in direct message", "Copy link" and "Forward message" message menu items for encrypted messages as they don't apply to encrypted messages and also disable apps menu items and show a warning. diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 6a3ddd45ca6..c1f9590b98e 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 MessageActionContext = type MessageActionType = 'communication' | 'interaction' | 'duplication' | 'apps' | 'management'; -type MessageActionConditionProps = { +export type MessageActionConditionProps = { message: IMessage; user: IUser | undefined; room: IRoom; @@ -65,6 +65,7 @@ export type MessageActionConfig = { ) => any; condition?: (props: MessageActionConditionProps) => Promise | boolean; type?: MessageActionType; + disabled?: (props: MessageActionConditionProps) => boolean; }; class MessageAction { diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts index 589c387772f..2f2793f7493 100644 --- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts +++ b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts @@ -1,5 +1,5 @@ import type { IMessage } from '@rocket.chat/core-typings'; -import { isRoomFederated } from '@rocket.chat/core-typings'; +import { isE2EEMessage, isRoomFederated } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; import moment from 'moment'; @@ -63,6 +63,9 @@ Meteor.startup(async () => { }, order: 0, group: 'menu', + disabled({ message }) { + return isE2EEMessage(message); + }, }); MessageAction.addButton({ @@ -87,6 +90,9 @@ Meteor.startup(async () => { }, order: 0, group: 'message', + disabled({ message }) { + return isE2EEMessage(message); + }, }); MessageAction.addButton({ @@ -139,6 +145,9 @@ Meteor.startup(async () => { }, order: 5, group: 'menu', + disabled({ message }) { + return isE2EEMessage(message); + }, }); MessageAction.addButton({ diff --git a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx index 9d8367f7ad9..ddde6122b99 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx @@ -41,7 +41,7 @@ const GenericMenu = ({ title, icon = 'menu', disabled, onAction, ...props }: Gen const hasIcon = itemsList.some(({ icon }) => icon); const handleItems = (items: GenericMenuItemProps[]) => - hasIcon ? items.map((item) => ({ ...item, gap: !item.icon && !item.status })) : items; + hasIcon ? items.map((item) => ({ ...item, gap: item.gap ?? (!item.icon && !item.status) })) : items; const isMenuEmpty = !(sections && sections.length > 0) && !(items && items.length > 0); diff --git a/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx index ec987a1ee28..44feedf8611 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx @@ -12,14 +12,15 @@ export type GenericMenuItemProps = { disabled?: boolean; description?: ReactNode; gap?: boolean; + tooltip?: string; }; -const GenericMenuItem = ({ icon, content, addon, status, gap }: GenericMenuItemProps) => ( +const GenericMenuItem = ({ icon, content, addon, status, gap, tooltip }: GenericMenuItemProps) => ( <> {gap && } {icon && } {status && {status}} - {content && {content}} + {content && {content}} {addon && {addon}} ); diff --git a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx b/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx index 8d266fe35d0..6c35f7b73db 100644 --- a/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx @@ -1,8 +1,9 @@ +import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { MouseEvent, ReactElement } from 'react'; import React from 'react'; -import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; +import type { MessageActionConditionProps, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; import GenericMenu from '../../GenericMenu/GenericMenu'; import type { GenericMenuItemProps } from '../../GenericMenu/GenericMenuItem'; @@ -19,11 +20,13 @@ type MessageActionSection = { type MessageActionMenuProps = { onChangeMenuVisibility: (visible: boolean) => void; options: MessageActionConfigOption[]; + context: MessageActionConditionProps; + isMessageEncrypted: boolean; }; -const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMenuProps): ReactElement => { +const MessageActionMenu = ({ options, onChangeMenuVisibility, context, isMessageEncrypted }: MessageActionMenuProps): ReactElement => { const t = useTranslation(); - + const id = useUniqueId(); const groupOptions = options .map((option) => ({ variant: option.color === 'alert' ? 'danger' : '', @@ -32,6 +35,9 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMen 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) }) }), })) .reduce((acc, option) => { const group = option.type ? option.type : ''; @@ -44,7 +50,31 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMen acc.push(newSection); return acc; - }, [] as unknown as MessageActionSection[]); + }, [] 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 ( action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })} key={action.id} icon={action.icon} - title={t(action.label)} + title={ + action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context }) + ? 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 })} /> ))} {actionsQueryResult.isSuccess && actionsQueryResult.data.menu.length > 0 && ( @@ -138,6 +143,8 @@ const MessageToolbar = ({ }))} 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/package.json b/apps/meteor/package.json index 9d02e91c6cb..b060c09a53b 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -242,7 +242,7 @@ "@rocket.chat/favicon": "workspace:^", "@rocket.chat/forked-matrix-appservice-bridge": "^4.0.2", "@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.3", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", "@rocket.chat/fuselage-toastbar": "^0.31.26", diff --git a/apps/meteor/tests/e2e/e2e-encryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption.spec.ts index 58adc3e8654..14d407aaf81 100644 --- a/apps/meteor/tests/e2e/e2e-encryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption.spec.ts @@ -213,6 +213,31 @@ test.describe.serial('e2e-encryption', () => { await expect(poHomeChannel.content.mainThreadMessageText.locator('.rcx-icon--name-key')).toBeVisible(); }); + test('expect create a private encrypted channel and check disabled message menu actions on an encrypted message', async ({ page }) => { + const channelName = faker.string.uuid(); + + await poHomeChannel.sidenav.createEncryptedChannel(channelName); + + await expect(page).toHaveURL(`/group/${channelName}`); + + await poHomeChannel.dismissToast(); + + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + + await poHomeChannel.content.sendMessage('This is an encrypted message.'); + + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.'); + await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + + await page.locator('[data-qa-type="message"]').last().hover(); + await expect(page.locator('role=button[name="Forward message"]')).toBeDisabled(); + + await poHomeChannel.content.openLastMessageMenu(); + + await expect(page.locator('role=menuitem[name="Reply in direct message"]')).toHaveClass(/disabled/); + await expect(page.locator('role=menuitem[name="Copy link"]')).toHaveClass(/disabled/); + }); + test('expect create a private channel, encrypt it and send an encrypted message', async ({ page }) => { const channelName = faker.string.uuid(); diff --git a/ee/packages/ui-theming/package.json b/ee/packages/ui-theming/package.json index 437f1fda92a..dca631587d3 100644 --- a/ee/packages/ui-theming/package.json +++ b/ee/packages/ui-theming/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/icons": "^0.36.0", "@rocket.chat/ui-contexts": "workspace:~", diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json index 119a2375928..6f8b6dfefe1 100644 --- a/packages/fuselage-ui-kit/package.json +++ b/packages/fuselage-ui-kit/package.json @@ -66,7 +66,7 @@ "@rocket.chat/apps-engine": "alpha", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/eslint-config": "workspace:^", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", "@rocket.chat/icons": "^0.36.0", diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json index 0ce09b0aa79..ace746bfb5d 100644 --- a/packages/gazzodown/package.json +++ b/packages/gazzodown/package.json @@ -6,7 +6,7 @@ "@babel/core": "~7.22.20", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-tokens": "^0.33.1", "@rocket.chat/message-parser": "workspace:^", "@rocket.chat/styled": "~0.31.25", diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 863a773d45c..cfaecfe2c92 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -304,6 +304,7 @@ "Action_required": "Action required", "Action_Available_After_Custom_Content_Added": "This action will become available after the custom content has been added", "Action_Available_After_Custom_Content_Added_And_Visible": "This action will become available after the custom content has been added and made visible to everyone", + "Action_not_available_encrypted_content": "{{action}} not available on encrypted content", "Activate": "Activate", "Active": "Active", "Active_users": "Active users", diff --git a/packages/ui-avatar/package.json b/packages/ui-avatar/package.json index a5300bc5648..1db9cc6c1eb 100644 --- a/packages/ui-avatar/package.json +++ b/packages/ui-avatar/package.json @@ -4,7 +4,7 @@ "private": true, "devDependencies": { "@babel/core": "~7.22.20", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/ui-contexts": "workspace:^", "@types/babel__core": "~7.20.3", "@types/react": "~17.0.69", diff --git a/packages/ui-client/package.json b/packages/ui-client/package.json index 6b6429df099..a56e6dcc757 100644 --- a/packages/ui-client/package.json +++ b/packages/ui-client/package.json @@ -6,7 +6,7 @@ "@babel/core": "~7.22.20", "@react-aria/toolbar": "^3.0.0-beta.1", "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/icons": "^0.36.0", "@rocket.chat/mock-providers": "workspace:^", diff --git a/packages/ui-composer/package.json b/packages/ui-composer/package.json index e256334c3eb..a36c0c3df15 100644 --- a/packages/ui-composer/package.json +++ b/packages/ui-composer/package.json @@ -6,7 +6,7 @@ "@babel/core": "~7.22.20", "@react-aria/toolbar": "^3.0.0-beta.1", "@rocket.chat/eslint-config": "workspace:^", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/icons": "^0.36.0", "@storybook/addon-actions": "~6.5.16", "@storybook/addon-docs": "~6.5.16", diff --git a/packages/ui-video-conf/package.json b/packages/ui-video-conf/package.json index 2e92e815811..4ecf8699aec 100644 --- a/packages/ui-video-conf/package.json +++ b/packages/ui-video-conf/package.json @@ -6,7 +6,7 @@ "@babel/core": "~7.22.20", "@rocket.chat/css-in-js": "~0.31.25", "@rocket.chat/eslint-config": "workspace:^", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/icons": "^0.36.0", "@rocket.chat/styled": "~0.31.25", diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json index 133faa8c75e..732b74c0d3d 100644 --- a/packages/uikit-playground/package.json +++ b/packages/uikit-playground/package.json @@ -15,7 +15,7 @@ "@codemirror/tooltip": "^0.19.16", "@lezer/highlight": "^1.1.6", "@rocket.chat/css-in-js": "~0.31.25", - "@rocket.chat/fuselage": "^0.54.2", + "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", "@rocket.chat/fuselage-toastbar": "^0.31.26", diff --git a/yarn.lock b/yarn.lock index 6321b0aff77..ce099ca48a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8915,7 +8915,7 @@ __metadata: "@rocket.chat/apps-engine": alpha "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 "@rocket.chat/gazzodown": "workspace:^" @@ -8977,9 +8977,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/fuselage@npm:^0.54.2": - version: 0.54.2 - resolution: "@rocket.chat/fuselage@npm:0.54.2" +"@rocket.chat/fuselage@npm:^0.54.3": + version: 0.54.3 + resolution: "@rocket.chat/fuselage@npm:0.54.3" dependencies: "@rocket.chat/css-in-js": ^0.31.25 "@rocket.chat/css-supports": ^0.31.25 @@ -8997,7 +8997,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 1e49b3324f50525a002dd450a28dc8c6fca559e51c8e93449e167b50ca88767cc3dd11ac3c89ea794597c6c06a4a681aea1043b8f8bb42c49df0362bb5791019 + checksum: bec4d0b92e919103cda927520040f46004266ec5e1b3964c5bec6c6be59f8f051f2940689785f4e78984a9a18230a175b9f5f8e548f2b8f951387d567570735c languageName: node linkType: hard @@ -9008,7 +9008,7 @@ __metadata: "@babel/core": ~7.22.20 "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-tokens": ^0.33.1 "@rocket.chat/message-parser": "workspace:^" "@rocket.chat/styled": ~0.31.25 @@ -9368,7 +9368,7 @@ __metadata: "@rocket.chat/favicon": "workspace:^" "@rocket.chat/forked-matrix-appservice-bridge": ^4.0.2 "@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.3 - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 "@rocket.chat/fuselage-toastbar": ^0.31.26 @@ -10264,7 +10264,7 @@ __metadata: resolution: "@rocket.chat/ui-avatar@workspace:packages/ui-avatar" dependencies: "@babel/core": ~7.22.20 - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/ui-contexts": "workspace:^" "@types/babel__core": ~7.20.3 "@types/react": ~17.0.69 @@ -10290,7 +10290,7 @@ __metadata: "@babel/core": ~7.22.20 "@react-aria/toolbar": ^3.0.0-beta.1 "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/icons": ^0.36.0 "@rocket.chat/mock-providers": "workspace:^" @@ -10343,7 +10343,7 @@ __metadata: "@babel/core": ~7.22.20 "@react-aria/toolbar": ^3.0.0-beta.1 "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/icons": ^0.36.0 "@storybook/addon-actions": ~6.5.16 "@storybook/addon-docs": ~6.5.16 @@ -10435,7 +10435,7 @@ __metadata: resolution: "@rocket.chat/ui-theming@workspace:ee/packages/ui-theming" dependencies: "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/icons": ^0.36.0 "@rocket.chat/ui-contexts": "workspace:~" @@ -10478,7 +10478,7 @@ __metadata: "@rocket.chat/css-in-js": ~0.31.25 "@rocket.chat/emitter": ~0.31.25 "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/icons": ^0.36.0 "@rocket.chat/styled": ~0.31.25 @@ -10523,7 +10523,7 @@ __metadata: "@codemirror/tooltip": ^0.19.16 "@lezer/highlight": ^1.1.6 "@rocket.chat/css-in-js": ~0.31.25 - "@rocket.chat/fuselage": ^0.54.2 + "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 "@rocket.chat/fuselage-toastbar": ^0.31.26