Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/wicked-drinks-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds alternative text field to image uploads to improve accessibility
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ export const parseFileIntoMessageAttachments = async (
const attachment: FileAttachmentProps = {
title: file.name,
type: 'file',
description: file.description,
title_link: fileUrl,
title_link_download: true,
audio_url: fileUrl,
Expand All @@ -131,7 +130,6 @@ export const parseFileIntoMessageAttachments = async (
const attachment: FileAttachmentProps = {
title: file.name,
type: 'file',
description: file.description,
title_link: fileUrl,
title_link_download: true,
video_url: fileUrl,
Expand All @@ -145,7 +143,6 @@ export const parseFileIntoMessageAttachments = async (
title: file.name,
type: 'file',
format: getFileExtension(file.name),
description: file.description,
title_link: fileUrl,
title_link_download: true,
size: file.size as number,
Expand Down
10 changes: 8 additions & 2 deletions apps/meteor/client/components/ImageGallery/ImageGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,20 @@ export const ImageGallery = ({ images, onClose, loadMore }: { images: IUpload[];
onReachBeginning={loadMore}
initialSlide={images.length - 1}
>
{[...images].reverse().map(({ _id, path, url }) => (
{[...images].reverse().map(({ _id, path, url, description }) => (
<SwiperSlide key={_id}>
<div className='swiper-zoom-container'>
{/* eslint-disable-next-line
jsx-a11y/no-noninteractive-element-interactions,
jsx-a11y/click-events-have-key-events
*/}
<img src={path || url} loading='lazy' alt='' data-qa-zoom-scale={zoomScale} onClick={preventPropagation} />
<img
src={path || url}
loading='lazy'
alt={description || ''}
data-qa-zoom-scale={zoomScale}
onClick={preventPropagation}
/>
<div className='rcx-lazy-preloader'>
<Throbber inheritColor />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ const DefaultAttachment = (attachment: DefaultAttachmentProps): ReactElement =>
})}
/>
)}
{attachment.image_url && <AttachmentImage {...(attachment.image_dimensions as any)} src={attachment.image_url} />}
{attachment.image_url && (
<AttachmentImage {...(attachment.image_dimensions as any)} src={attachment.image_url} alt={attachment.description || ''} />
)}
{/* DEPRECATED */}
{isActionAttachment(attachment) && <ActionAttachment {...attachment} />}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const ImageAttachment = ({
width: 368,
height: 368,
},
description,
title_link: link,
title_link_download: hasDownload,
collapsed,
Expand All @@ -33,6 +34,7 @@ const ImageAttachment = ({
src={getURL(url)}
previewUrl={`data:image/png;base64,${imagePreview}`}
id={id}
alt={description}
/>
</MessageCollapsible>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type AttachmentImageProps = {
id: string | undefined;
width: number;
height: number;
alt?: string;
} & ({ loadImage: true } | { loadImage: false; setLoadImage: () => void });

const getDimensions = (
Expand All @@ -36,7 +37,7 @@ const getDimensions = (
return { width, height, ratio: (height / width) * 100 };
};

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

const [error, setError] = useState(false);
Expand Down Expand Up @@ -82,7 +83,7 @@ const AttachmentImage = ({ id, previewUrl, dataSrc, loadImage = true, setLoadIma
className='gallery-item'
data-src={dataSrc || src}
src={src}
alt=''
alt={alt}
width={dimensions.width}
height={dimensions.height}
loading='lazy'
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/client/lib/chats/ChatAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +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;
send(file: File, encrypted?: never): Promise<void>;
send(file: File, encrypted: EncryptedFileUploadContent): Promise<void>;
};
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/client/lib/chats/Upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type NonEncryptedUpload = {
readonly url?: string;
readonly percentage: number;
readonly error?: Error;
readonly description?: string;
};

export type EncryptedUpload = NonEncryptedUpload & {
Expand Down
9 changes: 7 additions & 2 deletions apps/meteor/client/lib/chats/flows/processMessageUploads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const getAttachmentForFile = async (fileToUpload: EncryptedUpload): Promise<File
[`${fileType}_size`]: fileToUpload.file.size,
...(fileType === 'image' && {
image_dimensions: await getHeightAndWidthFromDataUrl(window.URL.createObjectURL(fileToUpload.file)),
description: fileToUpload.description,
}),
};
};
Expand Down Expand Up @@ -97,7 +98,11 @@ async function continueSendingMessage(store: UploadsAPI, message: IMessage) {
const filesToUpload = store.get();

const confirmFilesQueue: (IUploadToConfirm & {
composedMessage: AtLeast<IMessage, 'msg' | 'tmid' | 't' | 'content'> & { fileName?: string; fileContent?: IE2EEMessage['content'] };
composedMessage: AtLeast<IMessage, 'msg' | 'tmid' | 't' | 'content'> & {
fileName?: string;
fileContent?: IE2EEMessage['content'];
description?: string;
};
})[] = [];

const validFiles = filesToUpload.filter((file) => !file.error);
Expand All @@ -118,7 +123,7 @@ async function continueSendingMessage(store: UploadsAPI, message: IMessage) {
confirmFilesQueue.push({
_id: upload.id,
name: upload.file.name,
composedMessage: { tmid, msg: currentMsg, fileName: upload.file.name },
composedMessage: { tmid, msg: currentMsg, fileName: upload.file.name, description: upload.description },
});
continue;
}
Expand Down
18 changes: 18 additions & 0 deletions apps/meteor/client/lib/chats/uploads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ class UploadsStore extends Emitter<{ update: void; [x: `cancelling-${Upload['id'
}
};

editUploadDescription = (uploadId: Upload['id'], description: string) => {
this.set(
this.uploads.map((upload) => {
if (upload.id !== uploadId) {
return upload;
}

return {
...upload,
description,
...(isEncryptedUpload(upload) && {
metadataForEncryption: { ...upload.metadataForEncryption, description },
}),
};
}),
);
};

editUploadFileName = (uploadId: Upload['id'], fileName: Upload['file']['name']) => {
try {
this.set(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import { getMimeType } from '../../../../../app/utils/lib/mimeTypes';
import { usePreventPropagation } from '../../../../hooks/usePreventPropagation';
import type { Upload } from '../../../../lib/chats/Upload';
import { formatBytes } from '../../../../lib/utils/formatBytes';
import { useChat } from '../../contexts/ChatContext';
import FileUploadModal from '../../modals/FileUploadModal';

type MessageComposerFileItemProps = {
upload: Upload;
onRemove: (id: string) => void;
onEdit: (id: Upload['id'], fileName: string) => void;
onEdit: (id: Upload['id'], fileName: string, description?: string) => void;
onCancel: (id: Upload['id']) => void;
disabled: boolean;
};

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

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

setModal(
<FileUploadModal
onSubmit={(name) => {
onEdit(upload.id, name);
onSubmit={(name, description) => {
onEdit(upload.id, name, description);
setModal(null);
chat?.composer?.focus();
}}
fileName={upload.file.name}
fileDescription={upload.description}
file={upload.file}
onClose={() => setModal(null)}
/>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { MessageComposerFileGroup } from '@rocket.chat/ui-composer';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';

import MessageComposerFileItem from './MessageComposerFileItem';
import type { Upload } from '../../../../lib/chats/Upload';
import { useFileUpload } from '../../body/hooks/useFileUpload';

const MessageComposerFiles = () => {
const { t } = useTranslation();
const { uploads, uploadsStore, isProcessingUploads, hasUploads } = useFileUpload();

const handleEdit = useCallback(
(id: Upload['id'], fileName: string, description?: string) => {
uploadsStore?.editUploadFileName(id, fileName);
if (description !== undefined) {
uploadsStore?.editUploadDescription(id, description);
}
},
[uploadsStore],
);

if (!uploadsStore || !hasUploads) {
return null;
}
Expand All @@ -19,7 +31,7 @@ const MessageComposerFiles = () => {
key={upload.id}
upload={upload}
onRemove={uploadsStore.removeUpload}
onEdit={uploadsStore.editUploadFileName}
onEdit={handleEdit}
onCancel={uploadsStore.cancel}
disabled={isProcessingUploads}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ type FileItemProps = {

const FileItem = ({ rid, fileData, onClickDelete }: FileItemProps) => {
const format = useFormatDateAndTime();
const { _id, path, name, uploadedAt, type, typeGroup, user } = fileData;
const { _id, path, name, uploadedAt, type, typeGroup, user, description } = fileData;

const encryptedAnchorProps = useDownloadFromServiceWorker(path || '', name);
const normalizedUsername = user?.username ? normalizeUsername(user.username) : undefined;

return (
<>
{typeGroup === 'image' ? (
<ImageItem id={_id} url={path} name={name} username={normalizedUsername} timestamp={format(uploadedAt)} />
<ImageItem id={_id} url={path} name={name} username={normalizedUsername} timestamp={format(uploadedAt)} alt={description} />
) : (
<Box
is='a'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ type ImageItemProps = {
name: string | undefined;
timestamp: string;
username?: string;
alt?: string;
};

const ImageItem = ({ id, url, name, timestamp, username }: ImageItemProps) => {
const ImageItem = ({ id, url, name, timestamp, username, alt = '' }: ImageItemProps) => {
return (
<Box minWidth={0} data-id={id} className='gallery-item-container' title={name} display='flex' flexGrow={1} flexShrink={1}>
{url && (
<Box minWidth='x48'>
<Avatar size='x48' url={url} className='gallery-item' />
<Avatar size='x48' url={url} className='gallery-item' alt={alt} />
</Box>
)}
<Box mis={8} flexShrink={1} overflow='hidden' className='gallery-item' cursor='default'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ const shouldShowMediaPreview = (file: File, fileType: FilePreviewType | undefine

type FilePreviewProps = {
file: File;
description?: string;
};

const FilePreview = ({ file }: FilePreviewProps): ReactElement => {
const FilePreview = ({ file, description }: FilePreviewProps): ReactElement => {
const fileType = getFileType(file.type);

if (shouldShowMediaPreview(file, fileType)) {
return <MediaPreview file={file} fileType={fileType as FilePreviewType} />;
return <MediaPreview file={file} fileType={fileType as FilePreviewType} description={description} />;
}

return <GenericPreview file={file} />;
Expand Down
Loading
Loading