diff --git a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
index bab1ed08213e1..d3207b9205930 100644
--- a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
+++ b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
@@ -69,6 +69,16 @@ export const AutoTranslate = {
}
}
+ if (attachment.description && attachment.translations && attachment.translations[language]) {
+ attachment.translations.original = attachment.description;
+
+ if (autoTranslateShowInverse) {
+ attachment.description = attachment.translations.original;
+ } else {
+ attachment.description = attachment.translations[language];
+ }
+ }
+
if (attachment.attachments && attachment.attachments.length > 0) {
// @ts-expect-error - not sure what to do with this
attachment.attachments = this.translateAttachments(attachment.attachments, language);
diff --git a/apps/meteor/app/autotranslate/server/autotranslate.ts b/apps/meteor/app/autotranslate/server/autotranslate.ts
index 3e04f6d39eb30..2f91e02463d58 100644
--- a/apps/meteor/app/autotranslate/server/autotranslate.ts
+++ b/apps/meteor/app/autotranslate/server/autotranslate.ts
@@ -320,7 +320,7 @@ export abstract class AutoTranslate {
if (message.attachments && message.attachments.length > 0) {
setImmediate(async () => {
for (const [index, attachment] of message.attachments?.entries() ?? []) {
- if (attachment.text) {
+ if (attachment.description || attachment.text) {
// Removes the initial link `[ ](quoterl)` from quote message before translation
const translatedText = attachment?.text?.replace(/\[(.*?)\]\(.*?\)/g, '$1') || attachment?.text;
const attachmentMessage = { ...attachment, text: translatedText };
diff --git a/apps/meteor/app/autotranslate/server/deeplTranslate.ts b/apps/meteor/app/autotranslate/server/deeplTranslate.ts
index 35f73e1755da6..d76a7ea2e4901 100644
--- a/apps/meteor/app/autotranslate/server/deeplTranslate.ts
+++ b/apps/meteor/app/autotranslate/server/deeplTranslate.ts
@@ -196,7 +196,7 @@ class DeeplAutoTranslate extends AutoTranslate {
params: {
auth_key: this.apiKey,
target_lang: language,
- text: attachment.text || '',
+ text: attachment.description || attachment.text || '',
},
});
if (!result.ok) {
diff --git a/apps/meteor/app/autotranslate/server/googleTranslate.ts b/apps/meteor/app/autotranslate/server/googleTranslate.ts
index 53b9bb7c1d5ea..9667ae53c967a 100644
--- a/apps/meteor/app/autotranslate/server/googleTranslate.ts
+++ b/apps/meteor/app/autotranslate/server/googleTranslate.ts
@@ -195,7 +195,7 @@ class GoogleAutoTranslate extends AutoTranslate {
key: this.apiKey,
target: language,
format: 'text',
- q: attachment.text || '',
+ q: attachment.description || attachment.text || '',
},
});
if (!result.ok) {
diff --git a/apps/meteor/app/autotranslate/server/msTranslate.ts b/apps/meteor/app/autotranslate/server/msTranslate.ts
index 6508734a1c0da..ddb345d3c895a 100644
--- a/apps/meteor/app/autotranslate/server/msTranslate.ts
+++ b/apps/meteor/app/autotranslate/server/msTranslate.ts
@@ -192,7 +192,7 @@ class MsAutoTranslate extends AutoTranslate {
return this._translate(
[
{
- Text: attachment.text || '',
+ Text: attachment.description || attachment.text || '',
},
],
targetLanguages,
diff --git a/apps/meteor/app/lib/server/functions/notifications/email.js b/apps/meteor/app/lib/server/functions/notifications/email.js
index 4de543abb7dd6..c41445fcf55b2 100644
--- a/apps/meteor/app/lib/server/functions/notifications/email.js
+++ b/apps/meteor/app/lib/server/functions/notifications/email.js
@@ -77,8 +77,13 @@ export async function getEmailContent({ message, user, room }) {
}
if (hasFiles) {
- const fileParts = files.map((file) => {
- return escapeHTML(file.name);
+ const attachments = message.attachments || [];
+ const fileParts = files.map((file, index) => {
+ let part = escapeHTML(file.name);
+ if (attachments[index]?.description) {
+ part += `
${escapeHTML(attachments[index].description)}`;
+ }
+ return part;
});
contentParts.push(fileParts.join('
'));
}
diff --git a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
index 498cef1624624..7a089abba0815 100644
--- a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
+++ b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
@@ -195,6 +195,8 @@ export const sendNotification = async ({
const firstAttachment = message.attachments?.length && message.attachments.shift();
if (firstAttachment) {
+ firstAttachment.description =
+ typeof firstAttachment.description === 'string' ? emojione.shortnameToUnicode(firstAttachment.description) : undefined;
firstAttachment.text = typeof firstAttachment.text === 'string' ? emojione.shortnameToUnicode(firstAttachment.text) : undefined;
}
diff --git a/apps/meteor/app/lib/server/methods/updateMessage.ts b/apps/meteor/app/lib/server/methods/updateMessage.ts
index 833b4403c0eca..45ba42f25f000 100644
--- a/apps/meteor/app/lib/server/methods/updateMessage.ts
+++ b/apps/meteor/app/lib/server/methods/updateMessage.ts
@@ -34,7 +34,7 @@ export async function executeUpdateMessage(
// IF the message has custom fields, always update
// Ideally, we'll compare the custom fields to check for change, but since we don't know the shape of
// custom fields, as it's user defined, we're gonna update
- const msgText = originalMessage.msg;
+ const msgText = originalMessage?.attachments?.[0]?.description ?? originalMessage.msg;
if (msgText === message.msg && !previewUrls && !message.customFields) {
return;
}
@@ -86,6 +86,13 @@ export async function executeUpdateMessage(
}
await canSendMessageAsync(message.rid, { uid: user._id, username: user.username ?? undefined, ...user });
+ // It is possible to have an empty array as the attachments property, so ensure both things exist
+ if (originalMessage.attachments && originalMessage.attachments.length > 0 && originalMessage.attachments[0].description !== undefined) {
+ originalMessage.attachments[0].description = message.msg;
+ message.attachments = originalMessage.attachments;
+ message.msg = originalMessage.msg;
+ }
+
message.u = originalMessage.u;
return updateMessage(message, user, originalMessage, previewUrls);
diff --git a/apps/meteor/app/livechat/server/lib/sendTranscript.ts b/apps/meteor/app/livechat/server/lib/sendTranscript.ts
index f52ac3f516710..199275f6a516b 100644
--- a/apps/meteor/app/livechat/server/lib/sendTranscript.ts
+++ b/apps/meteor/app/livechat/server/lib/sendTranscript.ts
@@ -108,7 +108,7 @@ export async function sendTranscript({
const messageType = MessageTypes.getType(message);
- const messageContent = messageType?.system
+ let messageContent = messageType?.system
? DOMPurify.sanitize(`
${messageType.text(i18n.cloneInstance({ interpolation: { escapeValue: false } }).t, message)}}`)
: escapeHtml(message.msg);
@@ -116,6 +116,9 @@ export async function sendTranscript({
let filesHTML = '';
if (message.attachments && message.attachments?.length > 0) {
+ messageContent = message.attachments[0].description || '';
+ escapeHtml(messageContent);
+
for await (const attachment of message.attachments) {
if (!isFileAttachment(attachment)) {
continue;
diff --git a/apps/meteor/app/slackbridge/server/RocketAdapter.ts b/apps/meteor/app/slackbridge/server/RocketAdapter.ts
index 5c46d75368dda..100e6991c3b08 100644
--- a/apps/meteor/app/slackbridge/server/RocketAdapter.ts
+++ b/apps/meteor/app/slackbridge/server/RocketAdapter.ts
@@ -203,11 +203,14 @@ export default class RocketAdapter {
if (rocketMessage.file.name) {
let fileName = rocketMessage.file.name;
- const text = rocketMessage.msg;
+ let text = rocketMessage.msg;
const attachment = this.getMessageAttachment(rocketMessage);
if (attachment) {
fileName = Meteor.absoluteUrl(attachment.title_link);
+ if (!text) {
+ text = attachment.description;
+ }
}
await slack.postMessage(slack.getSlackChannel(rocketMessage.rid), { ...rocketMessage, msg: `${text} ${fileName}` });
diff --git a/apps/meteor/app/ui/client/lib/ChatMessages.ts b/apps/meteor/app/ui/client/lib/ChatMessages.ts
index 70b64201979ba..a6febf3fdfef9 100644
--- a/apps/meteor/app/ui/client/lib/ChatMessages.ts
+++ b/apps/meteor/app/ui/client/lib/ChatMessages.ts
@@ -120,7 +120,7 @@ export class ChatMessages implements ChatAPI {
},
editMessage: async (message: IMessage, { cursorAtStart = false }: { cursorAtStart?: boolean } = {}) => {
this.composer?.uploads.clear();
- const text = (await this.data.getDraft(message._id)) || message.msg;
+ const text = (await this.data.getDraft(message._id)) || message.attachments?.[0]?.description || message.msg;
await this.currentEditingMessage.stop();
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 9fa94126127e8..227874cfb8eaf 100644
--- a/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
@@ -4,13 +4,17 @@ import { useMediaUrl } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { useReloadOnError } from './hooks/useReloadOnError';
+import MarkdownText from '../../../../MarkdownText';
import MessageCollapsible from '../../../MessageCollapsible';
+import MessageContentBody from '../../../MessageContentBody';
const AudioAttachment = ({
title,
audio_url: url,
audio_type: type,
audio_size: size,
+ description,
+ descriptionMd,
title_link: link,
title_link_download: hasDownload,
collapsed,
@@ -21,6 +25,7 @@ const AudioAttachment = ({
return (
<>
+ {descriptionMd ? : }
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 d57325334098e..f46b24d8d5634 100644
--- a/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
@@ -13,7 +13,9 @@ import { useTranslation } from 'react-i18next';
import { getFileExtension } from '../../../../../../lib/utils/getFileExtension';
import { forAttachmentDownload, registerDownloadForUid } from '../../../../../hooks/useDownloadFromServiceWorker';
+import MarkdownText from '../../../../MarkdownText';
import MessageCollapsible from '../../../MessageCollapsible';
+import MessageContentBody from '../../../MessageContentBody';
import AttachmentSize from '../structure/AttachmentSize';
import { useOpenEncryptedPdf } from './hooks/useOpenEncryptedPdf';
@@ -23,6 +25,8 @@ type GenericFileAttachmentProps = MessageAttachmentBase;
const GenericFileAttachment = ({
title,
+ description,
+ descriptionMd,
title_link: link,
title_link_download: hasDownload,
size,
@@ -81,6 +85,7 @@ const GenericFileAttachment = ({
return (
<>
+ {descriptionMd ? : }
+ {descriptionMd ? : }
+ {descriptionMd ? : }
diff --git a/apps/meteor/client/components/message/toolbar/useCopyAction.ts b/apps/meteor/client/components/message/toolbar/useCopyAction.ts
index b8275144abca1..1a03dac99936d 100644
--- a/apps/meteor/client/components/message/toolbar/useCopyAction.ts
+++ b/apps/meteor/client/components/message/toolbar/useCopyAction.ts
@@ -6,8 +6,8 @@ import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/Me
const getMainMessageText = (message: IMessage): IMessage => {
const newMessage = { ...message };
- newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.title || '';
- newMessage.md = newMessage.md || undefined;
+ newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || '';
+ newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined;
return { ...newMessage };
};
diff --git a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx
index dbbb962032907..ba281c5a1f5a2 100644
--- a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx
+++ b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx
@@ -7,8 +7,8 @@ import ReportMessageModal from '../../../views/room/modals/ReportMessageModal';
const getMainMessageText = (message: IMessage): IMessage => {
const newMessage = { ...message };
- newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.title || '';
- newMessage.md = newMessage.md || undefined;
+ newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || '';
+ newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined;
return { ...newMessage };
};
diff --git a/apps/meteor/client/hooks/useDecryptedMessage.spec.ts b/apps/meteor/client/hooks/useDecryptedMessage.spec.ts
index 3103e708910f6..5b35e8d6e3352 100644
--- a/apps/meteor/client/hooks/useDecryptedMessage.spec.ts
+++ b/apps/meteor/client/hooks/useDecryptedMessage.spec.ts
@@ -53,7 +53,7 @@ describe('useDecryptedMessage', () => {
it('should handle E2EE messages with attachments', async () => {
(isE2EEMessage as jest.MockedFunction).mockReturnValue(true);
(e2e.decryptMessage as jest.Mock).mockResolvedValue({
- attachments: [{ title: 'Attachment title' }],
+ attachments: [{ description: 'Attachment description' }],
});
const message = { msg: 'Encrypted message with attachment' };
@@ -63,6 +63,7 @@ describe('useDecryptedMessage', () => {
expect(result.current).toBe('E2E_message_encrypted_placeholder');
});
+ expect(result.current).toBe('Attachment description');
expect(e2e.decryptMessage).toHaveBeenCalledWith(message);
});
diff --git a/apps/meteor/client/hooks/useDecryptedMessage.ts b/apps/meteor/client/hooks/useDecryptedMessage.ts
index 771665dc0b631..e560aacc5b111 100644
--- a/apps/meteor/client/hooks/useDecryptedMessage.ts
+++ b/apps/meteor/client/hooks/useDecryptedMessage.ts
@@ -18,11 +18,14 @@ export const useDecryptedMessage = (message: IMessage): string => {
e2e.decryptMessage(message).then((decryptedMsg) => {
if (decryptedMsg.msg) {
setDecryptedMessage(decryptedMsg.msg);
- return;
}
- if (decryptedMsg.attachments && decryptedMsg.attachments.length > 0) {
- setDecryptedMessage(t('Message_with_attachment'));
+ if (decryptedMsg.attachments && decryptedMsg.attachments?.length > 0) {
+ if (decryptedMsg.attachments[0].description) {
+ setDecryptedMessage(decryptedMsg.attachments[0].description);
+ } else {
+ setDecryptedMessage(t('Message_with_attachment'));
+ }
}
});
}, [message, t, setDecryptedMessage]);
diff --git a/apps/meteor/client/lib/normalizeThreadMessage.tsx b/apps/meteor/client/lib/normalizeThreadMessage.tsx
index 067a23f6aedfe..f2ee47688a59e 100644
--- a/apps/meteor/client/lib/normalizeThreadMessage.tsx
+++ b/apps/meteor/client/lib/normalizeThreadMessage.tsx
@@ -24,7 +24,11 @@ export function normalizeThreadMessage({ ...message }: Readonly attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return <>{attachment.description}>;
+ }
if (attachment?.title) {
return <>{attachment.title}>;
diff --git a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts
index 0105608949b7f..e48eb15f885cf 100644
--- a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts
+++ b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts
@@ -178,6 +178,47 @@ describe('parseMessageTextToAstMarkdown', () => {
});
it('should return correct attachment translated parsed md when translate is active', () => {
+ const attachmentTranslatedMessage = {
+ ...translatedMessage,
+ attachments: [
+ {
+ description: 'description',
+ translations: {
+ en: 'description translated',
+ },
+ },
+ ],
+ };
+ const attachmentTranslatedMessageParsed = {
+ ...translatedMessage,
+ md: translatedMessageParsed,
+ attachments: [
+ {
+ description: 'description',
+ translations: {
+ en: 'description translated',
+ },
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'description translated',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ expect(parseMessageTextToAstMarkdown(attachmentTranslatedMessage, parseOptions, enabledAutoTranslatedOptions)).toStrictEqual(
+ attachmentTranslatedMessageParsed,
+ );
+ });
+
+ it('should return correct attachment quote translated parsed md when translate is active', () => {
const attachmentTranslatedMessage = {
...translatedMessage,
attachments: [
@@ -337,7 +378,7 @@ describe('parseMessageAttachments', () => {
const attachmentMessage = [
{
- text: 'message **bold** _italic_ and ~strike~',
+ description: 'message **bold** _italic_ and ~strike~',
md: messageParserTokenMessage,
},
];
@@ -359,18 +400,46 @@ describe('parseMessageAttachments', () => {
autoTranslateLanguage: 'en',
};
- it('should return correct attachment text parsed md when translate is active and auto translate language is undefined', () => {
- const textAttachment = [
+ it('should return correct attachment description translated parsed md when translate is active', () => {
+ const descriptionAttachment = [
{
...attachmentMessage[0],
- text: 'attachment not translated',
+ description: 'attachment not translated',
translationProvider: 'provider',
translations: {
en: 'attachment translated',
},
},
];
- const textAttachmentParsed: Root = [
+ const descriptionAttachmentParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment translated',
+ },
+ ],
+ },
+ ];
+
+ expect(parseMessageAttachments(descriptionAttachment, parseOptions, enabledAutoTranslatedOptions)[0].md).toStrictEqual(
+ descriptionAttachmentParsed,
+ );
+ });
+
+ it('should return correct attachment description parsed md when translate is active and auto translate language is undefined', () => {
+ const descriptionAttachment = [
+ {
+ ...attachmentMessage[0],
+ description: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ },
+ ];
+ const descriptionAttachmentParsed: Root = [
{
type: 'PARAGRAPH',
value: [
@@ -383,11 +452,39 @@ describe('parseMessageAttachments', () => {
];
expect(
- parseMessageAttachments(textAttachment, parseOptions, {
+ parseMessageAttachments(descriptionAttachment, parseOptions, {
...enabledAutoTranslatedOptions,
autoTranslateLanguage: undefined,
})[0].md,
- ).toStrictEqual(textAttachmentParsed);
+ ).toStrictEqual(descriptionAttachmentParsed);
+ });
+
+ it('should return correct attachment text translated parsed md when translate is active', () => {
+ const textAttachment = [
+ {
+ ...attachmentMessage[0],
+ text: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ },
+ ];
+ const textAttachmentParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment translated',
+ },
+ ],
+ },
+ ];
+
+ expect(parseMessageAttachments(textAttachment, parseOptions, enabledAutoTranslatedOptions)[0].md).toStrictEqual(
+ textAttachmentParsed,
+ );
});
it('should return correct attachment text translated parsed md when translate is active and has multiple texts', () => {
diff --git a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts
index 55d393cee38ed..df84785e26351 100644
--- a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts
+++ b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts
@@ -1,5 +1,12 @@
import type { IMessage, ITranslatedMessage, MessageAttachment } from '@rocket.chat/core-typings';
-import { isE2EEMessage, isQuoteAttachment, isTranslatedAttachment, isTranslatedMessage } from '@rocket.chat/core-typings';
+import {
+ isFileAttachment,
+ isE2EEMessage,
+ isQuoteAttachment,
+ isTranslatedAttachment,
+ isTranslatedMessage,
+ isEncryptedMessageAttachment,
+} from '@rocket.chat/core-typings';
import type { Options, Root } from '@rocket.chat/message-parser';
import { parse } from '@rocket.chat/message-parser';
@@ -51,7 +58,7 @@ export const parseMessageAttachment = (
autoTranslateOptions: { autoTranslateLanguage?: string; translated: boolean },
): T => {
const { translated, autoTranslateLanguage } = autoTranslateOptions;
- if (!attachment.text) {
+ if (!attachment.text && !attachment.description) {
return attachment;
}
@@ -62,8 +69,16 @@ export const parseMessageAttachment = (
const text =
(isTranslatedAttachment(attachment) && autoTranslateLanguage && attachment?.translations?.[autoTranslateLanguage]) ||
attachment.text ||
+ attachment.description ||
'';
+ if (isFileAttachment(attachment) && attachment.description) {
+ attachment.descriptionMd =
+ translated || isEncryptedMessageAttachment(attachment)
+ ? textToMessageToken(text, parseOptions)
+ : (attachment.descriptionMd ?? textToMessageToken(text, parseOptions));
+ }
+
return {
...attachment,
md: translated ? textToMessageToken(text, parseOptions) : (attachment.md ?? textToMessageToken(text, parseOptions)),
diff --git a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts
index 9584c13531439..184145a6c4506 100644
--- a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts
+++ b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts
@@ -48,7 +48,7 @@ describe('normalizeMessagePreview', () => {
});
describe('when message has attachments', () => {
- it('should return attachment title when description is available', () => {
+ it('should return attachment description when available', () => {
const message = createFakeMessageWithAttachment({
msg: '',
attachments: [
@@ -60,10 +60,10 @@ describe('normalizeMessagePreview', () => {
});
const result = normalizeMessagePreview(message, mockT);
- expect(result).toBe('Attachment title');
+ expect(result).toBe('Attachment description');
});
- it('should return attachment title when message is not provided', () => {
+ it('should return attachment title when description is not available', () => {
const message = createFakeMessageWithAttachment({
msg: '',
attachments: [
@@ -112,7 +112,7 @@ describe('normalizeMessagePreview', () => {
expect(result).toBe('Second attachment title');
});
- it('should find first attachment title', () => {
+ it('should find first attachment description', () => {
const message = createFakeMessageWithAttachment({
msg: '',
attachments: [
@@ -129,7 +129,21 @@ describe('normalizeMessagePreview', () => {
});
const result = normalizeMessagePreview(message, mockT);
- expect(result).toBe('Third attachment title');
+ expect(result).toBe('Second attachment description');
+ });
+
+ it('should escape HTML in attachment description', () => {
+ const message = createFakeMessageWithAttachment({
+ msg: '',
+ attachments: [
+ {
+ description: '',
+ },
+ ],
+ });
+ const result = normalizeMessagePreview(message, mockT);
+
+ expect(result).toBe('<script>alert("xss")</script>');
});
it('should escape HTML in attachment title', () => {
diff --git a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts
index 0fbefea5fa48d..53bf5bf2d4056 100644
--- a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts
+++ b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts
@@ -11,7 +11,11 @@ export const normalizeMessagePreview = (message: IMessage, t: TFunction): string
}
if (message.attachments) {
- const attachment = message.attachments.find((attachment) => attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return escapeHTML(attachment.description);
+ }
if (attachment?.title) {
return escapeHTML(attachment.title);
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx b/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
index 94313e6925432..0bcbbccece740 100644
--- a/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
+++ b/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
@@ -31,7 +31,11 @@ export const useMessageBody = (message: IMessage | undefined): string | Root =>
}
if (message.attachments) {
- const attachment = message.attachments.find((attachment) => attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return attachment.description;
+ }
if (attachment?.title) {
return attachment.title;
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts b/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts
index 291475075f4e4..f7b53352df5a1 100644
--- a/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts
@@ -39,6 +39,7 @@ export const useDownloadExportMutation = () => {
...('image_type' in attachment && { image_type: attachment.image_type }),
...('image_size' in attachment && { image_size: attachment.image_size }),
...('type' in attachment && { type: attachment.type }),
+ description: attachment.description,
})) ?? [],
}),
);
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx
index 383388f0c316f..716f7f3c8bd19 100644
--- a/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx
@@ -116,6 +116,7 @@ export const useExportMessagesAsPDFMutation = () => {
{parseMessage(message)}
{message.attachments?.map((attachment: MessageAttachmentDefault, index) => (
+ {attachment.description && {attachment.description}}
{attachment.image_url && }
{attachment.title}
diff --git a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts
index c052942a9567d..4ac708211cf47 100644
--- a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts
+++ b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts
@@ -33,7 +33,11 @@ export const useNormalizedThreadTitleHtml = (mainMessage: IThreadMainMessage) =>
}
if (message.attachments) {
- const attachment = message.attachments.find((attachment) => attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return escapeHTML(attachment.description);
+ }
if (attachment?.title) {
return escapeHTML(attachment.title);
diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts
index d15ac6dbbc909..b2480acff247d 100644
--- a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts
+++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts
@@ -142,7 +142,11 @@ slashCommands.add({
return;
}
- const emailText = message?.msg || '';
+ const emailText =
+ message?.attachments
+ ?.map((a) => a.description)
+ .filter(Boolean)
+ .join('\n\n') || '';
void sendEmail(
inbox,
diff --git a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts
index b2b47ad0b9228..088aff0c8b052 100644
--- a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts
+++ b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts
@@ -38,6 +38,10 @@ export class BeforeSaveMarkdownParser {
if (message.msg) {
message.md = parse(message.msg, config);
}
+
+ if (message.attachments?.[0]?.description) {
+ message.attachments[0].descriptionMd = parse(message.attachments[0].description, config);
+ }
} catch (e) {
console.error(e); // errors logged while the parser is at experimental stage
}
diff --git a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts
index 791404e4f1d9f..0ce7279e89b39 100644
--- a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts
+++ b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts
@@ -500,6 +500,17 @@ describe('Create attachments for message URLs', () => {
image_size: 68016,
type: 'file',
description: 'chained 3 - file',
+ descriptionMd: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'chained 3 - file',
+ },
+ ],
+ },
+ ],
},
],
},
diff --git a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts
index c744883ce9e3c..c511b071ac1a0 100644
--- a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts
+++ b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts
@@ -82,4 +82,29 @@ describe('Markdown parser', () => {
expect(message).to.have.property('md');
});
+
+ it('should parse markdown on the first attachment only', async () => {
+ const markdownParser = new BeforeSaveMarkdownParser(true);
+
+ const message = await markdownParser.parseMarkdown({
+ message: createMessage('hey', {
+ attachments: [
+ {
+ description: 'hey ho',
+ },
+ {
+ description: 'lets go',
+ },
+ ],
+ }),
+ config: {},
+ });
+
+ expect(message).to.have.property('md');
+
+ const [attachment1, attachment2] = message.attachments || [];
+
+ expect(attachment1).to.have.property('descriptionMd');
+ expect(attachment2).to.not.have.property('descriptionMd');
+ });
});
diff --git a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
index 67b9aa5ab16d9..4755688c9f006 100644
--- a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
+++ b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
@@ -312,7 +312,9 @@ export class OmnichannelTranscript extends ServiceClass implements IOmnichannelT
}
}
- const msg = message.msg || '';
+ // When you send a file message, the things you type in the modal are not "msg", they're in "description" of the attachment
+ // So, we'll fetch the the msg, if empty, go for the first description on an attachment, if empty, empty string
+ const msg = message.msg || message.attachments.find((attachment) => attachment.description)?.description || '';
// Remove nulls from final array
messagesData.push({
msg,
diff --git a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts
index 7d236bcafa2be..82ce0db3325ef 100644
--- a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts
+++ b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts
@@ -7,6 +7,7 @@ export type MessageAttachmentBase = {
collapsed?: boolean;
/** description isn't being used on client for non-image attachments, we're keeping it for backward compatibility */
description?: string;
+ descriptionMd?: Root;
text?: string;
md?: Root;
size?: number;