diff --git a/.vscode/settings.json b/.vscode/settings.json index a32489e34a64..47310bec0703 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,10 +14,5 @@ } ], "typescript.tsdk": "./node_modules/typescript/lib", - "cSpell.words": [ - "livechat", - "omnichannel", - "photoswipe", - "tmid" - ] + "cSpell.words": ["katex", "livechat", "omnichannel", "photoswipe", "tmid"] } diff --git a/apps/meteor/app/autotranslate/client/lib/actionButton.ts b/apps/meteor/app/autotranslate/client/lib/actionButton.ts index eb8cba74c34b..78bbabd0f37a 100644 --- a/apps/meteor/app/autotranslate/client/lib/actionButton.ts +++ b/apps/meteor/app/autotranslate/client/lib/actionButton.ts @@ -1,6 +1,5 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { isTranslatedMessage } from '@rocket.chat/core-typings'; import { AutoTranslate } from './autotranslate'; import { settings } from '../../../settings/client'; @@ -8,6 +7,10 @@ import { hasAtLeastOnePermission } from '../../../authorization/client'; import { MessageAction } from '../../../ui-utils/client/lib/MessageAction'; import { messageArgs } from '../../../../client/lib/utils/messageArgs'; import { Messages } from '../../../models/client'; +import { + hasTranslationLanguageInAttachments, + hasTranslationLanguageInMessage, +} from '../../../../client/views/room/MessageList/lib/autoTranslate'; Meteor.startup(() => { AutoTranslate.init(); @@ -22,8 +25,7 @@ Meteor.startup(() => { action(_, props) { const { message = messageArgs(this).msg } = props; const language = AutoTranslate.getLanguage(message.rid); - if (!isTranslatedMessage(message) || !message.translations[language]) { - // } && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; })) { + if (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)) { (AutoTranslate.messageIdsToWait as any)[message._id] = true; Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); Meteor.call('autoTranslate.translateMessage', message, language); @@ -31,12 +33,19 @@ Meteor.startup(() => { const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); }, - condition({ message, user }) { + condition({ message, subscription, user }) { if (!user) { return false; } + const language = subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid) || ''; - return Boolean(message?.u && message.u._id !== user._id && isTranslatedMessage(message) && message.autoTranslateShowInverse); + return Boolean( + (message?.u && + message.u._id !== user._id && + subscription?.autoTranslate && + (message as { autoTranslateShowInverse?: boolean }).autoTranslateShowInverse) || + (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)), + ); }, order: 90, }); @@ -48,8 +57,7 @@ Meteor.startup(() => { action(_, props) { const { message = messageArgs(this).msg } = props; const language = AutoTranslate.getLanguage(message.rid); - if (!isTranslatedMessage(message) || !message.translations[language]) { - // } && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; })) { + if (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language)) { (AutoTranslate.messageIdsToWait as any)[message._id] = true; Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); Meteor.call('autoTranslate.translateMessage', message, language); @@ -57,12 +65,19 @@ Meteor.startup(() => { const action = 'autoTranslateShowInverse' in message ? '$unset' : '$set'; Messages.update({ _id: message._id }, { [action]: { autoTranslateShowInverse: true } }); }, - condition({ message, user }) { + condition({ message, subscription, user }) { + const language = subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid) || ''; if (!user) { return false; } - return Boolean(message?.u && message.u._id !== user._id && isTranslatedMessage(message) && !message.autoTranslateShowInverse); + return Boolean( + message?.u && + message.u._id !== user._id && + subscription?.autoTranslate && + !(message as { autoTranslateShowInverse?: boolean }).autoTranslateShowInverse && + (hasTranslationLanguageInMessage(message, language) || hasTranslationLanguageInAttachments(message.attachments, language)), + ); }, order: 90, }); diff --git a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts index 71ff7cc92c5a..199df2e5db71 100644 --- a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts +++ b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts @@ -9,10 +9,15 @@ import type { IUser, MessageAttachmentDefault, } from '@rocket.chat/core-typings'; +import { isTranslatedMessageAttachment } from '@rocket.chat/core-typings'; import { Subscriptions, Messages } from '../../../models/client'; import { hasPermission } from '../../../authorization/client'; import { call } from '../../../../client/lib/utils/call'; +import { + hasTranslationLanguageInAttachments, + hasTranslationLanguageInMessage, +} from '../../../../client/views/room/MessageList/lib/autoTranslate'; let userLanguage = 'en'; let username = ''; @@ -55,6 +60,9 @@ export const AutoTranslate = { language: string, autoTranslateShowInverse: boolean, ): MessageAttachmentDefault[] { + if (!isTranslatedMessageAttachment(attachments)) { + return attachments; + } for (const attachment of attachments) { if (attachment.author_name !== username) { if (attachment.text && attachment.translations && attachment.translations[language]) { @@ -134,16 +142,11 @@ export const createAutoTranslateMessageRenderer = (): ((message: ITranslatedMess message.translations = {}; } if (!!subscription?.autoTranslate !== !!message.autoTranslateShowInverse) { - const hasAttachmentsTranslate = - message.attachments?.some( - (attachment) => - 'translations' in attachment && - typeof attachment.translations === 'object' && - autoTranslateLanguage in attachment.translations, - ) ?? false; - message.translations.original = message.html; - if (message.translations[autoTranslateLanguage] && !hasAttachmentsTranslate) { + if ( + message.translations[autoTranslateLanguage] && + !hasTranslationLanguageInAttachments(message.attachments, autoTranslateLanguage) + ) { message.html = message.translations[autoTranslateLanguage]; } @@ -155,12 +158,6 @@ export const createAutoTranslateMessageRenderer = (): ((message: ITranslatedMess ); } } - } else if (message.attachments && message.attachments.length > 0) { - message.attachments = AutoTranslate.translateAttachments( - message.attachments, - autoTranslateLanguage, - !!message.autoTranslateShowInverse, - ); } return message; }; @@ -177,7 +174,8 @@ export const createAutoTranslateMessageStreamHandler = (): ((message: ITranslate subscription && subscription.autoTranslate === true && message.msg && - (!message.translations || !message.translations[language]) + (!message.translations || + (!hasTranslationLanguageInMessage(message, language) && !hasTranslationLanguageInAttachments(message.attachments, language))) ) { // || (message.attachments && !_.find(message.attachments, attachment => { return attachment.translations && attachment.translations[language]; })) Messages.update({ _id: message._id }, { $set: { autoTranslateFetching: true } }); diff --git a/apps/meteor/app/autotranslate/server/autotranslate.ts b/apps/meteor/app/autotranslate/server/autotranslate.ts index edb674f5a187..1d926fe5bb77 100644 --- a/apps/meteor/app/autotranslate/server/autotranslate.ts +++ b/apps/meteor/app/autotranslate/server/autotranslate.ts @@ -305,10 +305,13 @@ export abstract class AutoTranslate { Meteor.defer(() => { for (const [index, attachment] of message.attachments?.entries() ?? []) { if (attachment.description || attachment.text) { - const translations = this._translateAttachmentDescriptions(attachment, targetLanguages); + // Removes the initial link `[ ](quoterl)` from quote message before translation + const translatedText = attachment?.text?.replace(/\[(.*?)\]\(.*?\)/g, '$1') || attachment?.text; + const attachmentMessage = { ...attachment, text: translatedText }; + const translations = this._translateAttachmentDescriptions(attachmentMessage, targetLanguages); + if (!_.isEmpty(translations)) { Messages.addAttachmentTranslations(message._id, index, translations); - Messages.addTranslations(message._id, translations, TranslationProviderRegistry[Provider]); } } } diff --git a/apps/meteor/app/autotranslate/server/googleTranslate.ts b/apps/meteor/app/autotranslate/server/googleTranslate.ts index 91a068b880aa..135601d48efc 100644 --- a/apps/meteor/app/autotranslate/server/googleTranslate.ts +++ b/apps/meteor/app/autotranslate/server/googleTranslate.ts @@ -146,6 +146,7 @@ class GoogleAutoTranslate extends AutoTranslate { params: { key: this.apiKey, target: language, + format: 'text', }, query, }); @@ -190,6 +191,7 @@ class GoogleAutoTranslate extends AutoTranslate { params: { key: this.apiKey, target: language, + format: 'text', }, query, }); diff --git a/apps/meteor/app/ui-message/client/message.html b/apps/meteor/app/ui-message/client/message.html index 6a571fa87c52..e3d9df72cb4b 100644 --- a/apps/meteor/app/ui-message/client/message.html +++ b/apps/meteor/app/ui-message/client/message.html @@ -55,7 +55,6 @@ {{#if showTranslated}} - {{ translationProvider }} {{/if}} {{#if msg.sentByEmail}} diff --git a/apps/meteor/app/ui-message/client/message.js b/apps/meteor/app/ui-message/client/message.js index c32d94b75ad6..f0bffebb9a52 100644 --- a/apps/meteor/app/ui-message/client/message.js +++ b/apps/meteor/app/ui-message/client/message.js @@ -21,6 +21,7 @@ import { renderMessageBody } from '../../../client/lib/utils/renderMessageBody'; import { settings } from '../../settings/client'; import { formatTime } from '../../../client/lib/utils/formatTime'; import { formatDate } from '../../../client/lib/utils/formatDate'; +import { hasTranslationLanguageInAttachments } from '../../../client/views/room/MessageList/lib/autoTranslate'; import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; import './messageThread'; import './message.html'; @@ -258,7 +259,9 @@ Template.message.helpers({ const autoTranslate = subscription && subscription.autoTranslate; return ( msg.autoTranslateFetching || - (!!autoTranslate !== !!msg.autoTranslateShowInverse && msg.translations && msg.translations[settings.translateLanguage]) + (!!autoTranslate !== !!msg.autoTranslateShowInverse && msg.translations && msg.translations[settings.translateLanguage]) || + (!!autoTranslate !== !!msg.autoTranslateShowInverse && + hasTranslationLanguageInAttachments(msg.attachments, settings.translateLanguage)) ); } }, diff --git a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts index 9f5e79c71125..1ed1b5253281 100644 --- a/apps/meteor/app/ui-utils/client/lib/MessageAction.ts +++ b/apps/meteor/app/ui-utils/client/lib/MessageAction.ts @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import type { Icon } from '@rocket.chat/fuselage'; -import type { IMessage, IUser, ISubscription, IRoom, SettingValue, Serialized } from '@rocket.chat/core-typings'; +import type { IMessage, IUser, ISubscription, IRoom, SettingValue, Serialized, ITranslatedMessage } from '@rocket.chat/core-typings'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { Messages, Rooms, Subscriptions } from '../../../models/client'; @@ -13,6 +13,7 @@ import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; import type { ToolboxContextValue } from '../../../../client/views/room/contexts/ToolboxContext'; import type { ChatContext } from '../../../../client/views/room/contexts/ChatContext'; import { APIClient } from '../../../utils/client'; +import type { AutoTranslateOptions } from '../../../../client/views/room/MessageList/hooks/useAutoTranslate'; const getMessage = async (msgId: string): Promise | null> => { try { @@ -71,7 +72,14 @@ export type MessageActionConfig = { tabbar, room, chat, - }: { message?: IMessage; tabbar: ToolboxContextValue; room?: IRoom; chat: ContextType }, + autoTranslateOptions, + }: { + message?: IMessage & Partial; + tabbar: ToolboxContextValue; + room?: IRoom; + chat: ContextType; + autoTranslateOptions?: AutoTranslateOptions; + }, ) => any; condition?: (props: MessageActionConditionProps) => Promise | boolean; }; diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts index 4ee0ced859c2..662da4ac343b 100644 --- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts +++ b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts @@ -70,7 +70,14 @@ Meteor.startup(async function () { label: 'Quote', context: ['message', 'message-mobile', 'threads', 'federated'], action(_, props) { - const { message = messageArgs(this).msg, chat } = props; + const { message = messageArgs(this).msg, chat, autoTranslateOptions } = props; + + if (message && autoTranslateOptions?.autoTranslateEnabled && autoTranslateOptions.showAutoTranslate(message)) { + message.msg = + message.translations && autoTranslateOptions.autoTranslateLanguage + ? message.translations[autoTranslateOptions.autoTranslateLanguage] + : message.msg; + } chat?.composer?.quoteMessage(message); }, diff --git a/apps/meteor/client/components/avatar/AppAvatar.tsx b/apps/meteor/client/components/avatar/AppAvatar.tsx index dc7b1025760a..c146eb4b10fc 100644 --- a/apps/meteor/client/components/avatar/AppAvatar.tsx +++ b/apps/meteor/client/components/avatar/AppAvatar.tsx @@ -1,22 +1,14 @@ import { Box } from '@rocket.chat/fuselage'; -import type { ReactElement } from 'react'; +import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; import BaseAvatar from './BaseAvatar'; -// TODO: frontend chapter day - Remove inline Styling - type AppAvatarProps = { - /* @deprecated */ - size: 'x36' | 'x28' | 'x16' | 'x40' | 'x124'; - /* @deprecated */ - mie?: 'x80' | 'x20' | 'x16' | 'x8'; - /* @deprecated */ - alignSelf?: 'center'; - iconFileContent: string; iconFileData: string; -}; + size: ComponentProps['size']; +} & ComponentProps; export default function AppAvatar({ iconFileContent, size, iconFileData, ...props }: AppAvatarProps): ReactElement { return ( diff --git a/apps/meteor/client/components/message/StatusIndicators.tsx b/apps/meteor/client/components/message/StatusIndicators.tsx index d3bb297da4aa..f6eff61bef6e 100644 --- a/apps/meteor/client/components/message/StatusIndicators.tsx +++ b/apps/meteor/client/components/message/StatusIndicators.tsx @@ -1,6 +1,6 @@ import type { IMessage, ITranslatedMessage } from '@rocket.chat/core-typings'; import { isEditedMessage, isE2EEMessage, isOTRMessage } from '@rocket.chat/core-typings'; -import { MessageStatusIndicator, MessageStatusIndicatorItem, MessageStatusIndicatorText } from '@rocket.chat/fuselage'; +import { MessageStatusIndicator, MessageStatusIndicatorItem } from '@rocket.chat/fuselage'; import { useUserId, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -10,7 +10,6 @@ import { useShowStarred, useShowTranslated, useShowFollowing, - useTranslateProvider, } from '../../views/room/MessageList/contexts/MessageListContext'; type StatusIndicatorsProps = { @@ -19,8 +18,7 @@ type StatusIndicatorsProps = { const StatusIndicators = ({ message }: StatusIndicatorsProps): ReactElement => { const t = useTranslation(); - const translated = useShowTranslated({ message }); - const translateProvider = useTranslateProvider({ message }); + const translated = useShowTranslated(message); const starred = useShowStarred({ message }); const following = useShowFollowing({ message }); @@ -33,11 +31,7 @@ const StatusIndicators = ({ message }: StatusIndicatorsProps): ReactElement => { return ( - {translated && ( - - {translateProvider} - - )} + {translated && } {following && } diff --git a/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx index 8df0ddb34424..8aa541b6f1d8 100644 --- a/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx @@ -4,6 +4,7 @@ import type { FC } from 'react'; import React from 'react'; import MarkdownText from '../../../../MarkdownText'; +import MessageContentBody from '../../../MessageContentBody'; import { useCollapse } from '../../../hooks/useCollapse'; import Attachment from '../structure/Attachment'; import AttachmentContent from '../structure/AttachmentContent'; @@ -22,13 +23,14 @@ export const AudioAttachment: FC = ({ description, title_link: link, title_link_download: hasDownload, + md, }) => { const [collapsed, collapse] = useCollapse(collapsedDefault); const getURL = useMediaUrl(); return ( - + {md ? : } {title} diff --git a/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx index 2f2b12d8e031..4bf59ec20603 100644 --- a/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx @@ -4,6 +4,7 @@ import type { FC } from 'react'; import React from 'react'; import MarkdownText from '../../../../MarkdownText'; +import MessageContentBody from '../../../MessageContentBody'; import Attachment from '../structure/Attachment'; import AttachmentDescription from '../structure/AttachmentDescription'; import AttachmentDownload from '../structure/AttachmentDownload'; @@ -27,6 +28,7 @@ export const GenericFileAttachment: FC = ({ // format, // name, } = {}, + md, }) => { // const [collapsed, collapse] = useCollapse(collapsedDefault); const getURL = useMediaUrl(); @@ -34,7 +36,7 @@ export const GenericFileAttachment: FC = ({ {description && ( - + {md ? : } )} diff --git a/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx index 90fc80487f7a..a5061c9a97dd 100644 --- a/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx @@ -4,6 +4,7 @@ import type { FC } from 'react'; import React from 'react'; import MarkdownText from '../../../../MarkdownText'; +import MessageContentBody from '../../../MessageContentBody'; import { useCollapse } from '../../../hooks/useCollapse'; import Attachment from '../structure/Attachment'; import AttachmentContent from '../structure/AttachmentContent'; @@ -28,6 +29,7 @@ export const ImageAttachment: FC = ({ description, title_link: link, title_link_download: hasDownload, + md, }) => { const [loadImage, setLoadImage] = useLoadImage(); const [collapsed, collapse] = useCollapse(collapsedDefault); @@ -36,7 +38,7 @@ export const ImageAttachment: FC = ({ {description && ( - + {md ? : } )} diff --git a/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx index f8525d8d9d9d..fa1296cfe048 100644 --- a/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { userAgentMIMETypeFallback } from '../../../../../lib/utils/userAgentMIMETypeFallback'; import MarkdownText from '../../../../MarkdownText'; +import MessageContentBody from '../../../MessageContentBody'; import { useCollapse } from '../../../hooks/useCollapse'; import Attachment from '../structure/Attachment'; import AttachmentContent from '../structure/AttachmentContent'; @@ -32,6 +33,7 @@ export const VideoAttachment: FC = ({ description, title_link: link, title_link_download: hasDownload, + md, }) => { const [collapsed, collapse] = useCollapse(collapsedDefault); const getURL = useMediaUrl(); @@ -51,7 +53,7 @@ export const VideoAttachment: FC = ({ {description && ( - + {md ? : } )} diff --git a/apps/meteor/client/components/message/toolbox/Toolbox.tsx b/apps/meteor/client/components/message/toolbox/Toolbox.tsx index 80420d4e1709..cf6ea04b157f 100644 --- a/apps/meteor/client/components/message/toolbox/Toolbox.tsx +++ b/apps/meteor/client/components/message/toolbox/Toolbox.tsx @@ -1,4 +1,4 @@ -import type { IMessage, IUser, IRoom } from '@rocket.chat/core-typings'; +import type { IMessage, IUser, IRoom, ITranslatedMessage } from '@rocket.chat/core-typings'; import { isThreadMessage, isRoomFederated } from '@rocket.chat/core-typings'; import { MessageToolbox, MessageToolboxItem } from '@rocket.chat/fuselage'; import { useUser, useUserSubscription, useSettings, useTranslation } from '@rocket.chat/ui-contexts'; @@ -9,6 +9,7 @@ import React, { memo, useMemo } from 'react'; import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/MessageAction'; import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; import { useIsSelecting } from '../../../views/room/MessageList/contexts/SelectedMessagesContext'; +import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate'; import { useChat } from '../../../views/room/contexts/ChatContext'; import { useRoom } from '../../../views/room/contexts/RoomContext'; import { useToolboxContext } from '../../../views/room/contexts/ToolboxContext'; @@ -28,7 +29,7 @@ const getMessageContext = (message: IMessage, room: IRoom): MessageActionContext }; type ToolboxProps = { - message: IMessage; + message: IMessage & Partial; }; const Toolbox = ({ message }: ToolboxProps): ReactElement | null => { @@ -61,6 +62,8 @@ const Toolbox = ({ message }: ToolboxProps): ReactElement | null => { const selecting = useIsSelecting(); + const autoTranslateOptions = useAutoTranslate(subscription); + if (selecting) { return null; } @@ -69,10 +72,10 @@ const Toolbox = ({ message }: ToolboxProps): ReactElement | null => { {actionsQueryResult.data?.message.map((action) => ( action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })} key={action.id} icon={action.icon} title={t(action.label)} - onClick={(e): void => action.action(e, { message, tabbar: toolbox, room, chat })} data-qa-id={action.label} data-qa-type='message-action-menu' /> @@ -82,7 +85,7 @@ const Toolbox = ({ message }: ToolboxProps): ReactElement | null => { options={ actionsQueryResult.data?.menu.map((action) => ({ ...action, - action: (e): void => action.action(e, { message, tabbar: toolbox, room, chat }), + action: (e): void => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions }), })) ?? [] } data-qa-type='message-action-menu-options' diff --git a/apps/meteor/client/components/message/variants/ThreadMessage.tsx b/apps/meteor/client/components/message/variants/ThreadMessage.tsx index 7d7eb8118670..4c364098c8be 100644 --- a/apps/meteor/client/components/message/variants/ThreadMessage.tsx +++ b/apps/meteor/client/components/message/variants/ThreadMessage.tsx @@ -1,12 +1,13 @@ -import type { ISubscription, IThreadMessage, IThreadMainMessage } from '@rocket.chat/core-typings'; +import type { IThreadMessage, IThreadMainMessage } from '@rocket.chat/core-typings'; import { Message, MessageLeftContainer, MessageContainer } from '@rocket.chat/fuselage'; import { useToggle } from '@rocket.chat/fuselage-hooks'; -import { useUserId } from '@rocket.chat/ui-contexts'; +import { useUserId, useUserSubscription } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useMemo, memo } from 'react'; import { useIsMessageHighlight } from '../../../views/room/MessageList/contexts/MessageHighlightContext'; import { useMessageListContext } from '../../../views/room/MessageList/contexts/MessageListContext'; +import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate'; import { parseMessageTextToAstMarkdown, removePossibleNullMessageValues, @@ -21,7 +22,6 @@ import ThreadMessageContent from './thread/ThreadMessageContent'; type ThreadMessageProps = { message: IThreadMessage | IThreadMainMessage; - subscription?: ISubscription; unread: boolean; sequential: boolean; }; @@ -34,7 +34,9 @@ const ThreadMessage = ({ message, sequential, unread }: ThreadMessageProps): Rea actions: { openUserCard }, } = useMessageActions(); - const { autoTranslateLanguage, katex, showColors, useShowTranslated } = useMessageListContext(); + const { katex, showColors } = useMessageListContext(); + const subscription = useUserSubscription(message.rid); + const autoTranslateOptions = useAutoTranslate(subscription); const normalizeMessage = useMemo(() => { const parseOptions = { @@ -48,8 +50,8 @@ const ThreadMessage = ({ message, sequential, unread }: ThreadMessageProps): Rea }), }; return (message: TMessage) => - parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateLanguage, useShowTranslated); - }, [autoTranslateLanguage, katex, showColors, useShowTranslated]); + parseMessageTextToAstMarkdown(removePossibleNullMessageValues(message), parseOptions, autoTranslateOptions); + }, [katex, showColors, autoTranslateOptions]); const normalizedMessage = useMemo(() => normalizeMessage(message), [message, normalizeMessage]); diff --git a/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx b/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx index 34e9033dad4e..4e5c1539838f 100644 --- a/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx +++ b/apps/meteor/client/components/message/variants/ThreadMessagePreview.tsx @@ -10,12 +10,14 @@ import { ThreadMessageBody, ThreadMessageUnfollow, CheckBox, + MessageStatusIndicatorItem, } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; import { MessageTypes } from '../../../../app/ui-utils/client'; +import { useShowTranslated } from '../../../views/room/MessageList/contexts/MessageListContext'; import { useIsSelecting, useToggleSelect, @@ -24,6 +26,7 @@ import { } from '../../../views/room/MessageList/contexts/SelectedMessagesContext'; import { useMessageBody } from '../../../views/room/MessageList/hooks/useMessageBody'; import { useParentMessage } from '../../../views/room/MessageList/hooks/useParentMessage'; +import { isParsedMessage } from '../../../views/room/MessageList/lib/isParsedMessage'; import { useMessageActions } from '../../../views/room/contexts/MessageContext'; import UserAvatar from '../../avatar/UserAvatar'; import ThreadMessagePreviewBody from './threadPreview/ThreadMessagePreviewBody'; @@ -38,7 +41,8 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr actions: { openThread }, } = useMessageActions(); const parentMessage = useParentMessage(message.tmid); - const body = useMessageBody(parentMessage.data); + + const translated = useShowTranslated(message); const t = useTranslation(); const isSelecting = useIsSelecting(); @@ -47,6 +51,9 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr useCountSelected(); const messageType = parentMessage.isSuccess ? MessageTypes.getType(parentMessage.data) : null; + const messageBody = useMessageBody(parentMessage.data, message.rid); + + const previewMessage = isParsedMessage(messageBody) ? { md: messageBody } : { msg: messageBody }; return ( @@ -62,7 +69,13 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr {(parentMessage.data as { ignored?: boolean })?.ignored ? ( t('Message_Ignored') ) : ( - + + )} + {translated && ( + <> + {' '} + + )} )} @@ -80,7 +93,19 @@ const ThreadMessagePreview = ({ message, sequential, ...props }: ThreadMessagePr - {(message as { ignored?: boolean }).ignored ? t('Message_Ignored') : } + {(message as { ignored?: boolean }).ignored ? ( + t('Message_Ignored') + ) : ( + <> + + {translated && ( + <> + {' '} + + + )} + + )} diff --git a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx index 779475a6e585..9117e4a12778 100644 --- a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx +++ b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; import { useUserData } from '../../../../hooks/useUserData'; import type { UserPresence } from '../../../../lib/presence'; -import { useTranslateAttachments, useMessageListShowReadReceipt } from '../../../../views/room/MessageList/contexts/MessageListContext'; +import { useMessageListShowReadReceipt } from '../../../../views/room/MessageList/contexts/MessageListContext'; import type { MessageWithMdEnforced } from '../../../../views/room/MessageList/lib/parseMessageTextToAstMarkdown'; import { useMessageActions, useMessageOembedIsEnabled, useMessageRunActionLink } from '../../../../views/room/contexts/MessageContext'; import MessageContentBody from '../../MessageContentBody'; @@ -47,8 +47,6 @@ const RoomMessageContent = ({ message, unread, all, mention }: RoomMessageConten const isEncryptedMessage = isE2EEMessage(message); - const messageAttachments = useTranslateAttachments({ message }); - return ( <> {!message.blocks?.length && !!message.md?.length && ( @@ -62,7 +60,7 @@ const RoomMessageContent = ({ message, unread, all, mention }: RoomMessageConten {message.blocks && } - {!!messageAttachments.length && } + {!!message?.attachments?.length && } {oembedIsEnabled && !!message.urls?.length && } diff --git a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx index cdd6f12674a4..fb1eb021cdb9 100644 --- a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx +++ b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; import { useUserData } from '../../../../hooks/useUserData'; import type { UserPresence } from '../../../../lib/presence'; -import { useMessageListShowReadReceipt, useTranslateAttachments } from '../../../../views/room/MessageList/contexts/MessageListContext'; +import { useMessageListShowReadReceipt } from '../../../../views/room/MessageList/contexts/MessageListContext'; import type { MessageWithMdEnforced } from '../../../../views/room/MessageList/lib/parseMessageTextToAstMarkdown'; import { useMessageActions, useMessageOembedIsEnabled, useMessageRunActionLink } from '../../../../views/room/contexts/MessageContext'; import MessageContentBody from '../../MessageContentBody'; @@ -40,8 +40,6 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem const isEncryptedMessage = isE2EEMessage(message); - const messageAttachments = useTranslateAttachments({ message }); - return ( <> {!message.blocks?.length && !!message.md?.length && ( @@ -55,7 +53,7 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem {message.blocks && } - {messageAttachments && } + {message.attachments && } {oembedIsEnabled && !!message.urls?.length && } diff --git a/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx b/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx index c4f036631492..dcf4ffc5421e 100644 --- a/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx +++ b/apps/meteor/client/components/message/variants/threadPreview/ThreadMessagePreviewBody.tsx @@ -5,8 +5,6 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; -import { parseMessageTextToAstMarkdown } from '../../../../views/room/MessageList/lib/parseMessageTextToAstMarkdown'; - type ThreadMessagePreviewBodyProps = { message: IMessage; }; @@ -15,18 +13,16 @@ const ThreadMessagePreviewBody = ({ message }: ThreadMessagePreviewBodyProps): R const t = useTranslation(); const isEncryptedMessage = isE2EEMessage(message); - const parsedMessage = parseMessageTextToAstMarkdown(message, { colors: true, emoticons: true }); - const getMessage = () => { if (!isEncryptedMessage || message.e2e === 'done') { - return parsedMessage.md ? : <>{parsedMessage.msg}; + return message.md ? : <>{message.msg}; } if (isEncryptedMessage && message.e2e === 'pending') { return <>{t('E2E_message_encrypted_placeholder')}; } - return <>{parsedMessage.msg}; + return <>{message.msg}; }; return getMessage(); diff --git a/apps/meteor/client/lib/utils/messageArgs.ts b/apps/meteor/client/lib/utils/messageArgs.ts index 83248369c99a..0a92a6fa8adc 100644 --- a/apps/meteor/client/lib/utils/messageArgs.ts +++ b/apps/meteor/client/lib/utils/messageArgs.ts @@ -1,10 +1,10 @@ -import type { IMessage, IRoom, ISubscription, IUser, SettingValue } from '@rocket.chat/core-typings'; +import type { IRoom, ISubscription, ITranslatedMessage, IUser, SettingValue } from '@rocket.chat/core-typings'; export const messageArgs = ( context: any, ): { context?: 'threads' | 'mentions'; - msg: IMessage; + msg: ITranslatedMessage; u: IUser; room: IRoom; settings: Record; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomList.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomList.tsx new file mode 100644 index 000000000000..290ec03e8b4f --- /dev/null +++ b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomList.tsx @@ -0,0 +1,70 @@ +// import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { Throbber, Box } from '@rocket.chat/fuselage'; +import { useSetModal } from '@rocket.chat/ui-contexts'; +// import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import type { VFC } from 'react'; +import React from 'react'; + +import FederatedRoomListItem from './FederatedRoomListItem'; + +type FederatedRoomListProps = { + serverName: string; + roomName?: string; + pageToken?: string; + count?: number; +}; + +const fetchRoomList = ({ serverName, roomName, count }: FederatedRoomListProps) => { + return { + rooms: Array.from({ length: count || 100 }).map(() => ({ + id: `${Math.random()}:${serverName}`, + name: roomName || 'Matrix', + canJoin: true, + canonicalAlias: `#${serverName}:matrix.org`, + joinedMembers: 44461, + topic: + 'The Official Matrix HQ - chat about Matrix here! | https://matrix.org | https://spec.matrix.org | To support Matrix.org development: https://patreon.com/matrixdotorg | Code of Conduct: https://matrix.org/legal/code-of-conduct/ | This is an English speaking room', + })), + count: 1, + total: 73080, + nextPageToken: 'g6FtzZa3oXK+IUpkemFiTlVQUFh6bENKQWhFbDpmYWJyaWMucHVioWTD', + prevPageToken: 'g6FtzYqIoXK+IWNOd2pkUXdWcFJNc0lNa1VweDptYXRyaXgub3JnoWTC', + success: true, + }; +}; +const joinExternalPublicRoom = (id: string) => console.log(id); + +const FederatedRoomList: VFC = ({ serverName, roomName, pageToken, count }) => { + // const fetchRoomList = useEndpoint('GET', '/v1/federation/searchPublicRooms'); + // const joinExternalPublicRoom = useEndpoint('GET', '/v1/federation/joinExternalPublicRoom'); + + const setModal = useSetModal(); + const { data, isLoading } = useQuery( + ['federation/searchPublicRooms', serverName, roomName, pageToken, count], + async () => fetchRoomList({ serverName, roomName, pageToken, count }), + { keepPreviousData: true }, + ); + + const { mutate: onClickJoin, isLoading: isLoadingMutation } = useMutation( + ['federation/joinExternalPublicRoom'], + async (id: string) => { + return joinExternalPublicRoom(id); + }, + { onSuccess: () => setModal(null) }, + ); + + if (isLoading) { + return ; + } + + return ( + + {data?.rooms.map(({ id, ...props }) => ( + onClickJoin(id)} disabled={isLoadingMutation} {...props} key={id} /> + ))} + + ); +}; + +export default FederatedRoomList; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListItem.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListItem.tsx new file mode 100644 index 000000000000..76b85f603295 --- /dev/null +++ b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListItem.tsx @@ -0,0 +1,45 @@ +import { Box, Button, Icon } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { VFC } from 'react'; +import React from 'react'; + +type FederatedRoomListItemProps = { + name: string; + canJoin: boolean; + canonicalAlias: string; + joinedMembers: number; + topic: string; + disabled: boolean; + onClickJoin: () => void; +}; + +const FederatedRoomListItem: VFC = ({ name, topic, canonicalAlias, joinedMembers, onClickJoin, disabled }) => { + const t = useTranslation(); + + return ( + + + + {name} + + + + + + {topic} + + + + {canonicalAlias}{' '} + + + {joinedMembers} + + + + ); +}; + +export default FederatedRoomListItem; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationAddServerModal.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationAddServerModal.tsx new file mode 100644 index 000000000000..20948a44de18 --- /dev/null +++ b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationAddServerModal.tsx @@ -0,0 +1,61 @@ +import { Box, Modal, ButtonGroup, Button, Field, TextInput } from '@rocket.chat/fuselage'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import { useMutation } from '@tanstack/react-query'; +import type { VFC, FormEvent } from 'react'; +import React, { useState } from 'react'; + +import MatrixFederationSearch from './MatrixFederationSearch'; + +type MatrixFederationAddServerModalProps = { + onClickClose: () => void; +}; + +const addMatrixServer = async (_: { serverName: string }) => ({ success: true }); + +const MatrixFederationAddServerModal: VFC = ({ onClickClose }) => { + const t = useTranslation(); + // const addMatrixServer = useEndpoint('POST', 'v1/federation/addServerByUser'); + const [serverName, setServerName] = useState(''); + const setModal = useSetModal(); + + const { mutate: addServer, isLoading } = useMutation( + ['v1/federation/addServerByUser', serverName], + () => addMatrixServer({ serverName }), + { + onSuccess: () => { + setModal(); + }, + }, + ); + + return ( + + + {t('Add_new_federated_server')} + + + + + {t('Server_name')} + + ) => setServerName(e.currentTarget.value)} /> + + {t('Federation_Example_matrix_server')} + + + + + {t('This_server_will_be_available_while_your_session_is_active')} + + + + + + + + ); +}; + +export default MatrixFederationAddServerModal; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearch.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearch.tsx new file mode 100644 index 000000000000..91a6294a34d9 --- /dev/null +++ b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearch.tsx @@ -0,0 +1,37 @@ +// import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { Modal, Skeleton } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import React from 'react'; +import type { VFC } from 'react'; + +import MatrixFederationSearchModalContent from './MatrixFederationSearchModalContent'; + +type MatrixFederationSearchProps = { + onClose: () => void; +}; + +const fetchServerList = () => ({ + servers: Array.from({ length: 5 }).map((_, index) => ({ name: `Server ${index}`, default: true, local: false })), +}); + +const MatrixFederationSearch: VFC = ({ onClose }) => { + // const fetchRoomList = useEndpoint('GET', '/v1/federation/searchPublicRooms'); + const t = useTranslation(); + const { data, isLoading } = useQuery(['federation/listServersByUsers'], async () => fetchServerList()); + + return ( + + + {t('Federation_Federated_room_search')} + + + + {isLoading && } + {!isLoading && data?.servers && } + + + ); +}; + +export default MatrixFederationSearch; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx new file mode 100644 index 000000000000..a2dacfc7da1e --- /dev/null +++ b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx @@ -0,0 +1,67 @@ +import type { SelectOption } from '@rocket.chat/fuselage'; +import { Box, Select, TextInput } from '@rocket.chat/fuselage'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import type { VFC, FormEvent } from 'react'; +import React, { useCallback, useState, useMemo } from 'react'; + +import FederatedRoomList from './FederatedRoomList'; +import MatrixFederationAddServerModal from './MatrixFederationAddServerModal'; +import MatrixFederationSearch from './MatrixFederationSearch'; + +type MatrixFederationSearchModalContentProps = { + servers: Array<{ + name: string; + default: boolean; + local: boolean; + }>; +}; + +const MatrixFederationSearchModalContent: VFC = ({ servers }) => { + const [serverName, setServerName] = useState(servers[0].name); + const [roomName, setRoomName] = useState(''); + + const setModal = useSetModal(); + + const debouncedRoomName = useDebouncedValue(roomName, 400); + + const t = useTranslation(); + + const serverOptions = useMemo>( + () => servers.map((server): SelectOption => [server.name, server.name]).concat([['addServer', t('Add_server')]]), + [servers, t], + ); + + const handleSelectServer = useCallback( + (value) => { + if (value === 'addServer') { + setModal( + setModal( setModal(null)} />)} />, + ); + return; + } + setServerName(value); + }, + [setModal], + ); + + return ( + <> + +