Render captions to m.file, m.image, m.video, and m.audio (#2059)
Some checks failed
Deploy to Netlify (dev) / Deploy to Netlify (push) Has been cancelled

* Add rendering image captions

* Handle sending captions for images

* Fix caption rendering on m.video and m.audio too

* Remove unused renderBody() parameter

* Fix m.file rendering body instead of filename where possible

* Add caption rendering for generic files

+ Fix video and audio not properly sending captions

* Use m.text for captions & render on demand

* Allow custom HTML in sending captions

* Don't *send* captions

* mvoe content const into renderCaption()

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
This commit is contained in:
nexy7574 2025-01-06 01:44:22 +00:00 committed by GitHub
parent 3c5afaf33a
commit 02437a6a20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 118 additions and 73 deletions

View file

@ -29,6 +29,7 @@ import { ImageViewer } from './image-viewer';
import { PdfViewer } from './Pdf-viewer'; import { PdfViewer } from './Pdf-viewer';
import { TextViewer } from './text-viewer'; import { TextViewer } from './text-viewer';
import { testMatrixTo } from '../plugins/matrix-to'; import { testMatrixTo } from '../plugins/matrix-to';
import {IImageContent} from "../../types/matrix/common";
type RenderMessageContentProps = { type RenderMessageContentProps = {
displayName: string; displayName: string;
@ -67,38 +68,63 @@ export function RenderMessageContent({
</UrlPreviewHolder> </UrlPreviewHolder>
); );
}; };
const renderCaption = () => {
const content: IImageContent = getContent();
if(content.filename && content.filename !== content.body) {
return (
<MText
edited={edited}
content={content}
renderBody={(props) => (
<RenderBody
{...props}
highlightRegex={highlightRegex}
htmlReactParserOptions={htmlReactParserOptions}
linkifyOpts={linkifyOpts}
/>
)}
renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined}
/>
)
}
return null;
}
const renderFile = () => ( const renderFile = () => (
<MFile <>
content={getContent()} <MFile
renderFileContent={({ body, mimeType, info, encInfo, url }) => ( content={getContent()}
<FileContent renderFileContent={({ body, mimeType, info, encInfo, url }) => (
body={body} <FileContent
mimeType={mimeType}
renderAsPdfFile={() => (
<ReadPdfFile
body={body} body={body}
mimeType={mimeType} mimeType={mimeType}
url={url} renderAsPdfFile={() => (
encInfo={encInfo} <ReadPdfFile
renderViewer={(p) => <PdfViewer {...p} />} body={body}
/> mimeType={mimeType}
)} url={url}
renderAsTextFile={() => ( encInfo={encInfo}
<ReadTextFile renderViewer={(p) => <PdfViewer {...p} />}
body={body} />
mimeType={mimeType} )}
url={url} renderAsTextFile={() => (
encInfo={encInfo} <ReadTextFile
renderViewer={(p) => <TextViewer {...p} />} body={body}
/> mimeType={mimeType}
)} url={url}
> encInfo={encInfo}
<DownloadFile body={body} mimeType={mimeType} url={url} encInfo={encInfo} info={info} /> renderViewer={(p) => <TextViewer {...p} />}
</FileContent> />
)} )}
outlined={outlineAttachment} >
/> <DownloadFile body={body} mimeType={mimeType} url={url} encInfo={encInfo} info={info} />
</FileContent>
)}
outlined={outlineAttachment}
/>
{renderCaption()}
</>
); );
if (msgType === MsgType.Text) { if (msgType === MsgType.Text) {
@ -158,36 +184,40 @@ export function RenderMessageContent({
if (msgType === MsgType.Image) { if (msgType === MsgType.Image) {
return ( return (
<MImage <>
content={getContent()} <MImage
renderImageContent={(props) => ( content={getContent()}
<ImageContent renderImageContent={(props) => (
{...props} <ImageContent
autoPlay={mediaAutoLoad} {...props}
renderImage={(p) => <Image {...p} loading="lazy" />} autoPlay={mediaAutoLoad}
renderViewer={(p) => <ImageViewer {...p} />} renderImage={(p) => <Image {...p} loading="lazy" />}
/> renderViewer={(p) => <ImageViewer {...p} />}
)} />
outlined={outlineAttachment} )}
/> outlined={outlineAttachment}
/>
{renderCaption()}
</>
); );
} }
if (msgType === MsgType.Video) { if (msgType === MsgType.Video) {
return ( return (
<MVideo <>
content={getContent()} <MVideo
renderAsFile={renderFile} content={getContent()}
renderVideoContent={({ body, info, mimeType, url, encInfo }) => ( renderAsFile={renderFile}
<VideoContent renderVideoContent={({ body, info, mimeType, url, encInfo }) => (
body={body} <VideoContent
info={info} body={body}
mimeType={mimeType} info={info}
url={url} mimeType={mimeType}
encInfo={encInfo} url={url}
renderThumbnail={ encInfo={encInfo}
mediaAutoLoad renderThumbnail={
? () => ( mediaAutoLoad
? () => (
<ThumbnailContent <ThumbnailContent
info={info} info={info}
renderImage={(src) => ( renderImage={(src) => (
@ -195,26 +225,33 @@ export function RenderMessageContent({
)} )}
/> />
) )
: undefined : undefined
} }
renderVideo={(p) => <Video {...p} />} renderVideo={(p) => <Video {...p} />}
/> />
)} )}
outlined={outlineAttachment} outlined={outlineAttachment}
/> />
{renderCaption()}
</>
); );
} }
if (msgType === MsgType.Audio) { if (msgType === MsgType.Audio) {
return ( return (
<MAudio <>
content={getContent()} <MAudio
renderAsFile={renderFile} content={getContent()}
renderAudioContent={(props) => ( renderAsFile={renderFile}
<AudioContent {...props} renderMediaControl={(p) => <MediaControl {...p} />} /> renderAudioContent={(props) => (
)} <AudioContent {...props} renderMediaControl={(p) => <MediaControl {...p} />} />
outlined={outlineAttachment} )}
/> outlined={outlineAttachment}
/>
{renderCaption()}
</>
); );
} }

View file

@ -172,6 +172,7 @@ export function MNotice({ edited, content, renderBody, renderUrlsPreview }: MNot
type RenderImageContentProps = { type RenderImageContentProps = {
body: string; body: string;
filename?: string;
info?: IImageInfo & IThumbnailContent; info?: IImageInfo & IThumbnailContent;
mimeType?: string; mimeType?: string;
url: string; url: string;
@ -282,7 +283,7 @@ export function MAudio({ content, renderAsFile, renderAudioContent, outlined }:
return ( return (
<Attachment outlined={outlined}> <Attachment outlined={outlined}>
<AttachmentHeader> <AttachmentHeader>
<FileHeader body={content.body ?? 'Audio'} mimeType={safeMimeType} /> <FileHeader body={content.filename ?? content.body ?? 'Audio'} mimeType={safeMimeType} />
</AttachmentHeader> </AttachmentHeader>
<AttachmentBox> <AttachmentBox>
<AttachmentContent> <AttachmentContent>
@ -322,14 +323,14 @@ export function MFile({ content, renderFileContent, outlined }: MFileProps) {
<Attachment outlined={outlined}> <Attachment outlined={outlined}>
<AttachmentHeader> <AttachmentHeader>
<FileHeader <FileHeader
body={content.body ?? 'Unnamed File'} body={content.filename ?? content.body ?? 'Unnamed File'}
mimeType={fileInfo?.mimetype ?? FALLBACK_MIMETYPE} mimeType={fileInfo?.mimetype ?? FALLBACK_MIMETYPE}
/> />
</AttachmentHeader> </AttachmentHeader>
<AttachmentBox> <AttachmentBox>
<AttachmentContent> <AttachmentContent>
{renderFileContent({ {renderFileContent({
body: content.body ?? 'File', body: content.filename ?? content.body ?? 'File',
info: fileInfo ?? {}, info: fileInfo ?? {},
mimeType: fileInfo?.mimetype ?? FALLBACK_MIMETYPE, mimeType: fileInfo?.mimetype ?? FALLBACK_MIMETYPE,
url: mxcUrl, url: mxcUrl,

View file

@ -42,7 +42,7 @@ const generateThumbnailContent = async (
export const getImageMsgContent = async ( export const getImageMsgContent = async (
mx: MatrixClient, mx: MatrixClient,
item: TUploadItem, item: TUploadItem,
mxc: string mxc: string,
): Promise<IContent> => { ): Promise<IContent> => {
const { file, originalFile, encInfo } = item; const { file, originalFile, encInfo } = item;
const [imgError, imgEl] = await to(loadImageElement(getImageFileUrl(originalFile))); const [imgError, imgEl] = await to(loadImageElement(getImageFileUrl(originalFile)));
@ -50,6 +50,7 @@ export const getImageMsgContent = async (
const content: IContent = { const content: IContent = {
msgtype: MsgType.Image, msgtype: MsgType.Image,
filename: file.name,
body: file.name, body: file.name,
}; };
if (imgEl) { if (imgEl) {
@ -74,7 +75,7 @@ export const getImageMsgContent = async (
export const getVideoMsgContent = async ( export const getVideoMsgContent = async (
mx: MatrixClient, mx: MatrixClient,
item: TUploadItem, item: TUploadItem,
mxc: string mxc: string,
): Promise<IContent> => { ): Promise<IContent> => {
const { file, originalFile, encInfo } = item; const { file, originalFile, encInfo } = item;
@ -83,6 +84,7 @@ export const getVideoMsgContent = async (
const content: IContent = { const content: IContent = {
msgtype: MsgType.Video, msgtype: MsgType.Video,
filename: file.name,
body: file.name, body: file.name,
}; };
if (videoEl) { if (videoEl) {
@ -122,6 +124,7 @@ export const getAudioMsgContent = (item: TUploadItem, mxc: string): IContent =>
const { file, encInfo } = item; const { file, encInfo } = item;
const content: IContent = { const content: IContent = {
msgtype: MsgType.Audio, msgtype: MsgType.Audio,
filename: file.name,
body: file.name, body: file.name,
info: { info: {
mimetype: file.type, mimetype: file.type,

View file

@ -43,6 +43,7 @@ export type IThumbnailContent = {
export type IImageContent = { export type IImageContent = {
msgtype: MsgType.Image; msgtype: MsgType.Image;
body?: string; body?: string;
filename?: string;
url?: string; url?: string;
info?: IImageInfo & IThumbnailContent; info?: IImageInfo & IThumbnailContent;
file?: IEncryptedFile; file?: IEncryptedFile;
@ -51,6 +52,7 @@ export type IImageContent = {
export type IVideoContent = { export type IVideoContent = {
msgtype: MsgType.Video; msgtype: MsgType.Video;
body?: string; body?: string;
filename?: string;
url?: string; url?: string;
info?: IVideoInfo & IThumbnailContent; info?: IVideoInfo & IThumbnailContent;
file?: IEncryptedFile; file?: IEncryptedFile;
@ -59,6 +61,7 @@ export type IVideoContent = {
export type IAudioContent = { export type IAudioContent = {
msgtype: MsgType.Audio; msgtype: MsgType.Audio;
body?: string; body?: string;
filename?: string;
url?: string; url?: string;
info?: IAudioInfo; info?: IAudioInfo;
file?: IEncryptedFile; file?: IEncryptedFile;
@ -67,6 +70,7 @@ export type IAudioContent = {
export type IFileContent = { export type IFileContent = {
msgtype: MsgType.File; msgtype: MsgType.File;
body?: string; body?: string;
filename?: string;
url?: string; url?: string;
info?: IFileInfo & IThumbnailContent; info?: IFileInfo & IThumbnailContent;
file?: IEncryptedFile; file?: IEncryptedFile;