diff --git a/.changeset/slick-hats-arrive.md b/.changeset/slick-hats-arrive.md new file mode 100644 index 0000000000000..4ce84a4a831ee --- /dev/null +++ b/.changeset/slick-hats-arrive.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/federation-matrix': patch +'@rocket.chat/core-typings': patch +'@rocket.chat/meteor': patch +--- + +Fixes an issue where `description` was incorrectly being used as alternative text for image attachments diff --git a/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts b/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts index ec08a5d835a74..fe0fc015db704 100644 --- a/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts +++ b/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts @@ -73,7 +73,7 @@ export const parseFileIntoMessageAttachments = async ( const attachment: FileAttachmentProps = { title: file.name, type: 'file', - description: file?.description, + image_alt: file?.description, title_link: fileUrl, title_link_download: true, image_url: fileUrl, diff --git a/apps/meteor/client/components/message/content/attachments/DefaultAttachment.tsx b/apps/meteor/client/components/message/content/attachments/DefaultAttachment.tsx index 2d6ae957f7209..d763362275959 100644 --- a/apps/meteor/client/components/message/content/attachments/DefaultAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/DefaultAttachment.tsx @@ -98,7 +98,7 @@ const DefaultAttachment = (attachment: DefaultAttachmentProps) => { /> )} {attachment.image_url && ( - + )} {/* DEPRECATED */} {isActionAttachment(attachment) && } 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 29c1bf5b7c2f3..f653806208656 100644 --- a/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx @@ -17,6 +17,7 @@ const ImageAttachment = ({ width: 368, height: 368, }, + image_alt: altText, description, descriptionMd, title_link: link, @@ -38,7 +39,7 @@ const ImageAttachment = ({ src={getURL(url)} previewUrl={`data:image/png;base64,${imagePreview}`} id={id} - alt={description} + alt={altText} /> diff --git a/apps/meteor/client/lib/chats/ChatAPI.ts b/apps/meteor/client/lib/chats/ChatAPI.ts index 2826f3f56de58..e7cc2cf0fb6dd 100644 --- a/apps/meteor/client/lib/chats/ChatAPI.ts +++ b/apps/meteor/client/lib/chats/ChatAPI.ts @@ -122,7 +122,7 @@ export type UploadsAPI = { cancel(id: Upload['id']): void; removeUpload(id: Upload['id']): void; editUploadFileName: (id: Upload['id'], fileName: string) => void; - editUploadDescription: (id: Upload['id'], description: string) => void; + editUploadAltText: (id: Upload['id'], altText: string) => void; send(file: File, encrypted?: never): Promise; send(file: File, encrypted: EncryptedFileUploadContent): Promise; }; diff --git a/apps/meteor/client/lib/chats/Upload.ts b/apps/meteor/client/lib/chats/Upload.ts index 6916d90b9101c..7e48a9733dc12 100644 --- a/apps/meteor/client/lib/chats/Upload.ts +++ b/apps/meteor/client/lib/chats/Upload.ts @@ -6,7 +6,7 @@ export type NonEncryptedUpload = { readonly url?: string; readonly percentage: number; readonly error?: Error; - readonly description?: string; + readonly altText?: string; }; export type EncryptedUpload = NonEncryptedUpload & { diff --git a/apps/meteor/client/lib/chats/flows/processMessageUploads.ts b/apps/meteor/client/lib/chats/flows/processMessageUploads.ts index 10ac33f930e52..2c3cb00ec41b6 100644 --- a/apps/meteor/client/lib/chats/flows/processMessageUploads.ts +++ b/apps/meteor/client/lib/chats/flows/processMessageUploads.ts @@ -59,7 +59,7 @@ const getAttachmentForFile = async (fileToUpload: EncryptedUpload): Promise { + editUploadAltText = (uploadId: Upload['id'], altText: string) => { this.set( this.uploads.map((upload) => { if (upload.id !== uploadId) { @@ -72,9 +72,9 @@ class UploadsStore extends Emitter<{ update: void; [x: `cancelling-${Upload['id' return { ...upload, - description, + altText, ...(isEncryptedUpload(upload) && { - metadataForEncryption: { ...upload.metadataForEncryption, description }, + metadataForEncryption: { ...upload.metadataForEncryption, altText }, }), }; }), diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx index 6ceece6a89f59..fcd87b9d3e37a 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx @@ -7,7 +7,7 @@ import { isPreviewableImage } from '../../../../lib/utils/isPreviewableImage'; export type MessageComposerFileItemProps = { upload: Upload; onRemove: (id: string) => void; - onEdit: (id: Upload['id'], fileName: string, description?: string) => void; + onEdit: (id: Upload['id'], fileName: string, altText?: string) => void; onCancel: (id: Upload['id']) => void; disabled: boolean; shouldPreview?: boolean; diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageComposerFiles.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageComposerFiles.tsx index 84f0056513d77..c8e6d60f30ed0 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageComposerFiles.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageComposerFiles.tsx @@ -11,10 +11,10 @@ const MessageComposerFiles = () => { const { uploads, uploadsStore, isProcessingUploads, hasUploads } = useFileUpload(); const handleEdit = useCallback( - (id: Upload['id'], fileName: string, description?: string) => { + (id: Upload['id'], fileName: string, altText?: string) => { uploadsStore?.editUploadFileName(id, fileName); - if (description !== undefined) { - uploadsStore?.editUploadDescription(id, description); + if (altText !== undefined) { + uploadsStore?.editUploadAltText(id, altText); } }, [uploadsStore], diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageComposerGenericFile.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageComposerGenericFile.tsx index d24e22b54c404..d68119b58dc6b 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageComposerGenericFile.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageComposerGenericFile.tsx @@ -39,13 +39,13 @@ const MessageComposerGenericFile = ({ setModal( { - onEdit(upload.id, name, description); + onSubmit={(name, altText) => { + onEdit(upload.id, name, altText); setModal(null); chat?.composer?.focus(); }} fileName={upload.file.name} - fileDescription={upload.description} + fileAltText={upload.altText} file={upload.file} onClose={() => setModal(null)} />, @@ -86,7 +86,7 @@ const MessageComposerGenericFile = ({ fileTitle={upload.file.name} fileSubtitle={`${fileSize} - ${fileExtension}`} previewUrl={shouldPreview ? previewUrl : undefined} - alt={upload.description} + alt={upload.altText} fileFormat={getFileExtension(upload.file.name)} showPreview={shouldPreview} actionIcon={actionIcon} diff --git a/apps/meteor/client/views/room/modals/FileUploadModal/FilePreview.tsx b/apps/meteor/client/views/room/modals/FileUploadModal/FilePreview.tsx index 6254c5740f329..35af01a288aa6 100644 --- a/apps/meteor/client/views/room/modals/FileUploadModal/FilePreview.tsx +++ b/apps/meteor/client/views/room/modals/FileUploadModal/FilePreview.tsx @@ -39,14 +39,14 @@ const shouldShowMediaPreview = (file: File, fileType: FilePreviewType | undefine type FilePreviewProps = { file: File; - description?: string; + altText?: string; }; -const FilePreview = ({ file, description }: FilePreviewProps) => { +const FilePreview = ({ file, altText }: FilePreviewProps) => { const fileType = getFileType(file.type); if (shouldShowMediaPreview(file, fileType)) { - return ; + return ; } return ; diff --git a/apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx b/apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx index 29e13e1cb3963..f970f92cbdcb9 100644 --- a/apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx +++ b/apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx @@ -30,13 +30,13 @@ import { getMimeTypeFromFileName } from '../../../../../app/utils/lib/mimeTypes' type FileUploadModalProps = { onClose: () => void; - onSubmit: (name: string, description?: string) => void; + onSubmit: (name: string, altText?: string) => void; file: File; fileName: string; - fileDescription?: string; + fileAltText?: string; }; -const FileUploadModal = ({ onClose, file, fileName, fileDescription = '', onSubmit }: FileUploadModalProps) => { +const FileUploadModal = ({ onClose, file, fileName, fileAltText = '', onSubmit }: FileUploadModalProps) => { const { t } = useTranslation(); const fileUploadFormId = useId(); const isImage = file.type.startsWith('image/'); @@ -45,7 +45,7 @@ const FileUploadModal = ({ onClose, file, fileName, fileDescription = '', onSubm control, handleSubmit, formState: { errors, isDirty, isSubmitting }, - } = useForm({ mode: 'onBlur', defaultValues: { name: fileName, description: fileDescription } }); + } = useForm({ mode: 'onBlur', defaultValues: { name: fileName, altText: fileAltText } }); const validateFileName = useCallback( (fieldValue: string) => { @@ -63,12 +63,7 @@ const FileUploadModal = ({ onClose, file, fileName, fileDescription = '', onSubm ) => ( - onSubmit(name, description?.trim() || undefined))} - {...props} - /> + onSubmit(name, altText?.trim()))} {...props} /> )} > @@ -78,7 +73,7 @@ const FileUploadModal = ({ onClose, file, fileName, fileDescription = '', onSubm - + @@ -101,7 +96,7 @@ const FileUploadModal = ({ onClose, file, fileName, fileDescription = '', onSubm {t('Alternative_text')} {t('Alt_text_description')} - } /> + } /> )} diff --git a/apps/meteor/client/views/room/modals/FileUploadModal/ImagePreview.tsx b/apps/meteor/client/views/room/modals/FileUploadModal/ImagePreview.tsx index e00cd4cdfe6b1..635c7d4429e2b 100644 --- a/apps/meteor/client/views/room/modals/FileUploadModal/ImagePreview.tsx +++ b/apps/meteor/client/views/room/modals/FileUploadModal/ImagePreview.tsx @@ -7,10 +7,10 @@ import PreviewSkeleton from './PreviewSkeleton'; type ImagePreviewProps = { url: string; file: File; - alt?: string; + altText?: string; }; -const ImagePreview = ({ url, file, alt = '' }: ImagePreviewProps) => { +const ImagePreview = ({ url, file, altText = '' }: ImagePreviewProps) => { const [error, setError] = useState(false); const [loading, setLoading] = useState(true); @@ -30,7 +30,7 @@ const ImagePreview = ({ url, file, alt = '' }: ImagePreviewProps) => { { +const MediaPreview = ({ file, fileType, altText }: MediaPreviewProps) => { const [loaded, url] = useFileAsDataURL(file); const { t } = useTranslation(); @@ -32,7 +32,7 @@ const MediaPreview = ({ file, fileType, description }: MediaPreviewProps) => { } if (fileType === FilePreviewType.IMAGE) { - return ; + return ; } if (fileType === FilePreviewType.VIDEO) { diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index f677aae918196..350c31c190454 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -641,7 +641,7 @@ describe('[Rooms]', () => { expect(res.body.message.files).to.be.an('array').of.length(2); expect(res.body.message.files[0]).to.have.property('type', 'image/png'); expect(res.body.message.files[0]).to.have.property('name', '1024x1024.png'); - expect(res.body.message.attachments[0]).to.have.property('description', 'some_file_description'); + expect(res.body.message.attachments[0]).to.have.property('image_alt', 'some_file_description'); }); }); diff --git a/ee/packages/federation-matrix/tests/end-to-end/messaging.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/messaging.spec.ts index ffb0248a4a086..db88965674933 100644 --- a/ee/packages/federation-matrix/tests/end-to-end/messaging.spec.ts +++ b/ee/packages/federation-matrix/tests/end-to-end/messaging.spec.ts @@ -758,7 +758,7 @@ import { SynapseClient } from '../helper/synapse-client'; expect(rcMessage?.attachments?.[0]?.title_link).toMatch(/^\/file-upload\/[^/]+\/.+$/); expect(rcMessage?.attachments?.[0]?.title_link_download).toBe(true); expect((rcMessage?.attachments?.[0] as any)?.type).toBe('file'); - expect(rcMessage?.attachments?.[0]?.description).toBe(fileInfo.description); + expect((rcMessage?.attachments?.[0] as any)?.image_alt).toBe(fileInfo.description); expect((rcMessage?.attachments?.[0] as any)?.image_url).toMatch(/^\/file-upload\/[^/]+\/.+$/); expect((rcMessage?.attachments?.[0] as any)?.image_type).toBe('image/webp'); expect((rcMessage?.attachments?.[0] as any)?.image_size).toBe(uploadResponse.message.files?.[0]?.size); diff --git a/packages/core-typings/src/IMessage/MessageAttachment/Files/ImageAttachmentProps.ts b/packages/core-typings/src/IMessage/MessageAttachment/Files/ImageAttachmentProps.ts index 20d1d17390109..b94689a47dfee 100644 --- a/packages/core-typings/src/IMessage/MessageAttachment/Files/ImageAttachmentProps.ts +++ b/packages/core-typings/src/IMessage/MessageAttachment/Files/ImageAttachmentProps.ts @@ -11,6 +11,8 @@ export type ImageAttachmentProps = { image_url: string; image_type?: string; image_size?: number; + /** Accessibility alternative text for the image. Kept separate from `description` so it is not rendered as a visible caption. */ + image_alt?: string; file?: FileProp; } & MessageAttachmentBase; diff --git a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts index 395c5f437962c..5c9f4cda338ee 100644 --- a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts +++ b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentDefault.ts @@ -20,6 +20,8 @@ export type MessageAttachmentDefault = { // footer_icon image_url?: string; + /** Accessibility alternative text for the image. Kept separate from `description` so it is not rendered as a visible caption. */ + image_alt?: string; image_dimensions?: { width: number; height: number;