import { Box, Icon, Icons, Text, as, color, toRem } from 'folds'; import { EventTimelineSet, MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk'; import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend'; import React, { MouseEventHandler, ReactNode, useEffect, useMemo, useState } from 'react'; import to from 'await-to-js'; import classNames from 'classnames'; import colorMXID from '../../../util/colorMXID'; import { getMemberDisplayName, trimReplyFromBody } from '../../utils/room'; import { getMxIdLocalPart } from '../../utils/matrix'; import { LinePlaceholder } from './placeholder'; import { randomNumberBetween } from '../../utils/common'; import * as css from './Reply.css'; import { MessageBadEncryptedContent, MessageDeletedContent, MessageFailedContent } from './content'; import { scaleSystemEmoji } from '../../plugins/react-custom-html-parser'; type ReplyLayoutProps = { userColor?: string; username?: ReactNode; }; export const ReplyLayout = as<'div', ReplyLayoutProps>( ({ username, userColor, className, children, ...props }, ref) => ( {username} {children} ) ); export const ThreadIndicator = as<'div'>(({ ...props }, ref) => ( Threaded reply )); type ReplyProps = { mx: MatrixClient; room: Room; mxidColor?: boolean; timelineSet?: EventTimelineSet | undefined; replyEventId: string; threadRootId?: string | undefined; onClick?: MouseEventHandler | undefined; }; export const Reply = as<'div', ReplyProps>((_, ref) => { const { mx, room, mxidColor, timelineSet, replyEventId, threadRootId, onClick, ...props } = _; const [replyEvent, setReplyEvent] = useState( timelineSet?.findEventById(replyEventId) ); const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []); const { body } = replyEvent?.getContent() ?? {}; const sender = replyEvent?.getSender(); const fallbackBody = replyEvent?.isRedacted() ? ( ) : ( ); useEffect(() => { let disposed = false; const loadEvent = async () => { const [err, evt] = await to(mx.fetchRoomEvent(room.roomId, replyEventId)); const mEvent = new MatrixEvent(evt); if (disposed) return; if (err) { setReplyEvent(null); return; } if (mEvent.isEncrypted() && mx.getCrypto()) { await to(mEvent.attemptDecryption(mx.getCrypto() as CryptoBackend)); } setReplyEvent(mEvent); }; if (replyEvent === undefined) loadEvent(); return () => { disposed = true; }; }, [replyEvent, mx, room, replyEventId]); const badEncryption = replyEvent?.getContent().msgtype === 'm.bad.encrypted'; const bodyJSX = body ? scaleSystemEmoji(trimReplyFromBody(body)) : fallbackBody; return ( {threadRootId && ( )} {getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)} ) } data-event-id={replyEventId} onClick={onClick} > {replyEvent !== undefined ? ( {badEncryption ? : bodyJSX} ) : ( )} ); });