import React from 'react'; import { MsgType } from 'matrix-js-sdk'; import { HTMLReactParserOptions } from 'html-react-parser'; import { Opts } from 'linkifyjs'; import { config } from 'folds'; import { AudioContent, DownloadFile, FileContent, ImageContent, MAudio, MBadEncrypted, MEmote, MFile, MImage, MLocation, MNotice, MText, MVideo, ReadPdfFile, ReadTextFile, RenderBody, ThumbnailContent, UnsupportedContent, VideoContent, VideoFrameThumbnail, } from './message'; import { UrlPreviewCard, UrlPreviewHolder, YouTubeEmbed, isYouTubeUrl, TikTokEmbed, isTikTokUrl, GiphyEmbed, isGiphyUrl } from './url-preview'; import { Image, MediaControl, Video } from './media'; import { ImageViewer } from './image-viewer'; import { VideoViewer } from './video-viewer'; import { PdfViewer } from './Pdf-viewer'; import { TextViewer } from './text-viewer'; import { testMatrixTo } from '../plugins/matrix-to'; import { IImageContent } from '../../types/matrix/common'; import { filterDisabledUrls } from '../hooks/useRoomEmbedFilters'; import { pluginRegistry } from '../features/settings/plugins/PluginAPI'; type RenderMessageContentProps = { displayName: string; msgType: string; ts: number; edited?: boolean; getContent: () => T; mediaAutoLoad?: boolean; urlPreview?: boolean; highlightRegex?: RegExp; htmlReactParserOptions: HTMLReactParserOptions; linkifyOpts: Opts; outlineAttachment?: boolean; disabledEmbedPatterns?: string[]; }; export function RenderMessageContent({ displayName, msgType, ts, edited, getContent, mediaAutoLoad, urlPreview, highlightRegex, htmlReactParserOptions, linkifyOpts, outlineAttachment, disabledEmbedPatterns, }: RenderMessageContentProps) { const renderUrlsPreview = (urls: string[]) => { let filteredUrls = urls.filter((url) => !testMatrixTo(url)); filteredUrls = filterDisabledUrls(filteredUrls, disabledEmbedPatterns ?? []); if (filteredUrls.length === 0) return undefined; // Separate special URLs from generic ones const youtubeUrls = filteredUrls.filter(isYouTubeUrl); const tiktokUrls = filteredUrls.filter(isTikTokUrl); const giphyUrls = filteredUrls.filter(isGiphyUrl); const otherUrls = filteredUrls.filter((url) => !isYouTubeUrl(url) && !isTikTokUrl(url) && !isGiphyUrl(url)); return ( <> {/* Render YouTube embeds (ad-free via yt-dlp) */} {youtubeUrls.map((url) => ( ))} {/* Render TikTok embeds */} {tiktokUrls.map((url) => ( ))} {/* Render Giphy embeds */} {giphyUrls.map((url) => ( ))} {/* Render standard URL previews for other links */} {otherUrls.length > 0 && ( {otherUrls.map((url) => ( ))} )} ); }; const renderCaption = () => { const content: IImageContent = getContent(); if (content.filename && content.filename !== content.body) { return ( ( )} renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined} /> ); } return null; }; const renderFile = () => ( <> ( ( } /> )} renderAsTextFile={() => ( } /> )} > )} outlined={outlineAttachment} /> {renderCaption()} ); // Check for custom renderer from plugins BEFORE standard type handling // so plugins can intercept any msgtype including custom ones. const customRendererEarly = pluginRegistry.getRenderer('message'); if (customRendererEarly) { try { const messageDataEarly = { msgtype: msgType, ...getContent(), }; const customContentEarly = customRendererEarly(messageDataEarly, () => null); if (customContentEarly) { return customContentEarly as React.ReactElement; } } catch (error) { console.error('[RenderMessageContent] Custom renderer error (early):', error); } } if (msgType === MsgType.Text) { return ( ( )} renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined} /> ); } if (msgType === MsgType.Emote) { return ( ( )} renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined} /> ); } if (msgType === MsgType.Notice) { return ( ( )} renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined} /> ); } if (msgType === MsgType.Image) { return ( <> ( } renderViewer={(p) => } /> )} outlined={outlineAttachment} /> {renderCaption()} ); } if (msgType === MsgType.Video) { return ( <> { const hasValidThumbnail = (typeof info.thumbnail_file?.url === 'string' || typeof info.thumbnail_url === 'string') && typeof info.thumbnail_info?.mimetype === 'string'; return ( hasValidThumbnail ? ( ( {body} )} /> ) : ( ( {body} )} /> ) : undefined } renderViewer={(p) => } renderVideo={(p) =>