Skip to content

Commit 2356c88

Browse files
authored
feat: Add alt description support for image uploads (RocketChat#40075)
1 parent 5af4cf4 commit 2356c88

File tree

20 files changed

+139
-55
lines changed

20 files changed

+139
-55
lines changed

.changeset/wicked-drinks-think.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@rocket.chat/i18n': minor
3+
'@rocket.chat/meteor': minor
4+
---
5+
6+
Adds alternative text field to image uploads to improve accessibility

apps/meteor/client/components/ImageGallery/ImageGallery.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,20 @@ export const ImageGallery = ({ images, onClose, loadMore }: { images: IUpload[];
192192
onReachBeginning={loadMore}
193193
initialSlide={images.length - 1}
194194
>
195-
{[...images].reverse().map(({ _id, path, url }) => (
195+
{[...images].reverse().map(({ _id, path, url, description }) => (
196196
<SwiperSlide key={_id}>
197197
<div className='swiper-zoom-container'>
198198
{/* eslint-disable-next-line
199199
jsx-a11y/no-noninteractive-element-interactions,
200200
jsx-a11y/click-events-have-key-events
201201
*/}
202-
<img src={path || url} loading='lazy' alt='' data-qa-zoom-scale={zoomScale} onClick={preventPropagation} />
202+
<img
203+
src={path || url}
204+
loading='lazy'
205+
alt={description || ''}
206+
data-qa-zoom-scale={zoomScale}
207+
onClick={preventPropagation}
208+
/>
203209
<div className='rcx-lazy-preloader'>
204210
<Throbber inheritColor />
205211
</div>

apps/meteor/client/components/message/content/attachments/DefaultAttachment.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ const DefaultAttachment = (attachment: DefaultAttachmentProps): ReactElement =>
9696
})}
9797
/>
9898
)}
99-
{attachment.image_url && <AttachmentImage {...(attachment.image_dimensions as any)} src={attachment.image_url} />}
99+
{attachment.image_url && (
100+
<AttachmentImage {...(attachment.image_dimensions as any)} src={attachment.image_url} alt={attachment.description || ''} />
101+
)}
100102
{/* DEPRECATED */}
101103
{isActionAttachment(attachment) && <ActionAttachment {...attachment} />}
102104
</>

apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const ImageAttachment = ({
1515
width: 368,
1616
height: 368,
1717
},
18+
description,
1819
title_link: link,
1920
title_link_download: hasDownload,
2021
collapsed,
@@ -33,6 +34,7 @@ const ImageAttachment = ({
3334
src={getURL(url)}
3435
previewUrl={`data:image/png;base64,${imagePreview}`}
3536
id={id}
37+
alt={description}
3638
/>
3739
</MessageCollapsible>
3840
</>

apps/meteor/client/components/message/content/attachments/structure/AttachmentImage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type AttachmentImageProps = {
1515
id: string | undefined;
1616
width: number;
1717
height: number;
18+
alt?: string;
1819
} & ({ loadImage: true } | { loadImage: false; setLoadImage: () => void });
1920

2021
const getDimensions = (
@@ -36,7 +37,7 @@ const getDimensions = (
3637
return { width, height, ratio: (height / width) * 100 };
3738
};
3839

39-
const AttachmentImage = ({ id, previewUrl, dataSrc, loadImage = true, setLoadImage, src, ...size }: AttachmentImageProps) => {
40+
const AttachmentImage = ({ id, previewUrl, dataSrc, loadImage = true, setLoadImage, src, alt = '', ...size }: AttachmentImageProps) => {
4041
const limits = useAttachmentDimensions();
4142

4243
const [error, setError] = useState(false);
@@ -82,7 +83,7 @@ const AttachmentImage = ({ id, previewUrl, dataSrc, loadImage = true, setLoadIma
8283
className='gallery-item'
8384
data-src={dataSrc || src}
8485
src={src}
85-
alt=''
86+
alt={alt}
8687
width={dimensions.width}
8788
height={dimensions.height}
8889
loading='lazy'

apps/meteor/client/lib/chats/ChatAPI.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export type UploadsAPI = {
122122
cancel(id: Upload['id']): void;
123123
removeUpload(id: Upload['id']): void;
124124
editUploadFileName: (id: Upload['id'], fileName: string) => void;
125+
editUploadDescription: (id: Upload['id'], description: string) => void;
125126
send(file: File, encrypted?: never): Promise<void>;
126127
send(file: File, encrypted: EncryptedFileUploadContent): Promise<void>;
127128
};

apps/meteor/client/lib/chats/Upload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type NonEncryptedUpload = {
66
readonly url?: string;
77
readonly percentage: number;
88
readonly error?: Error;
9+
readonly description?: string;
910
};
1011

1112
export type EncryptedUpload = NonEncryptedUpload & {

apps/meteor/client/lib/chats/flows/processMessageUploads.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const getAttachmentForFile = async (fileToUpload: EncryptedUpload): Promise<File
5959
[`${fileType}_size`]: fileToUpload.file.size,
6060
...(fileType === 'image' && {
6161
image_dimensions: await getHeightAndWidthFromDataUrl(window.URL.createObjectURL(fileToUpload.file)),
62+
description: fileToUpload.description,
6263
}),
6364
};
6465
};
@@ -97,7 +98,11 @@ async function continueSendingMessage(store: UploadsAPI, message: IMessage) {
9798
const filesToUpload = store.get();
9899

99100
const confirmFilesQueue: (IUploadToConfirm & {
100-
composedMessage: AtLeast<IMessage, 'msg' | 'tmid' | 't' | 'content'> & { fileName?: string; fileContent?: IE2EEMessage['content'] };
101+
composedMessage: AtLeast<IMessage, 'msg' | 'tmid' | 't' | 'content'> & {
102+
fileName?: string;
103+
fileContent?: IE2EEMessage['content'];
104+
description?: string;
105+
};
101106
})[] = [];
102107

103108
const validFiles = filesToUpload.filter((file) => !file.error);
@@ -118,7 +123,7 @@ async function continueSendingMessage(store: UploadsAPI, message: IMessage) {
118123
confirmFilesQueue.push({
119124
_id: upload.id,
120125
name: upload.file.name,
121-
composedMessage: { tmid, msg: currentMsg, fileName: upload.file.name },
126+
composedMessage: { tmid, msg: currentMsg, fileName: upload.file.name, description: upload.description },
122127
});
123128
continue;
124129
}

apps/meteor/client/lib/chats/uploads.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ class UploadsStore extends Emitter<{ update: void; [x: `cancelling-${Upload['id'
6363
}
6464
};
6565

66+
editUploadDescription = (uploadId: Upload['id'], description: string) => {
67+
this.set(
68+
this.uploads.map((upload) => {
69+
if (upload.id !== uploadId) {
70+
return upload;
71+
}
72+
73+
return {
74+
...upload,
75+
description,
76+
...(isEncryptedUpload(upload) && {
77+
metadataForEncryption: { ...upload.metadataForEncryption, description },
78+
}),
79+
};
80+
}),
81+
);
82+
};
83+
6684
editUploadFileName = (uploadId: Upload['id'], fileName: Upload['file']['name']) => {
6785
try {
6886
this.set(

apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ import { getMimeType } from '../../../../../app/utils/lib/mimeTypes';
99
import { usePreventPropagation } from '../../../../hooks/usePreventPropagation';
1010
import type { Upload } from '../../../../lib/chats/Upload';
1111
import { formatBytes } from '../../../../lib/utils/formatBytes';
12+
import { useChat } from '../../contexts/ChatContext';
1213
import FileUploadModal from '../../modals/FileUploadModal';
1314

1415
type MessageComposerFileItemProps = {
1516
upload: Upload;
1617
onRemove: (id: string) => void;
17-
onEdit: (id: Upload['id'], fileName: string) => void;
18+
onEdit: (id: Upload['id'], fileName: string, description?: string) => void;
1819
onCancel: (id: Upload['id']) => void;
1920
disabled: boolean;
2021
};
2122

2223
const MessageComposerFileItem = ({ upload, onRemove, onEdit, onCancel, disabled, ...props }: MessageComposerFileItemProps) => {
2324
const { t } = useTranslation();
25+
const chat = useChat();
2426
const [isActive, setIsActive] = useState(false);
2527
const setModal = useSetModal();
2628

@@ -35,11 +37,13 @@ const MessageComposerFileItem = ({ upload, onRemove, onEdit, onCancel, disabled,
3537

3638
setModal(
3739
<FileUploadModal
38-
onSubmit={(name) => {
39-
onEdit(upload.id, name);
40+
onSubmit={(name, description) => {
41+
onEdit(upload.id, name, description);
4042
setModal(null);
43+
chat?.composer?.focus();
4144
}}
4245
fileName={upload.file.name}
46+
fileDescription={upload.description}
4347
file={upload.file}
4448
onClose={() => setModal(null)}
4549
/>,

0 commit comments

Comments
 (0)