import { Box, Text, as, color, toRem } from 'folds'; import { Icon, Icons } from '../icons'; import { EventTimelineSet, Room } from 'matrix-js-sdk'; import React, { MouseEventHandler, ReactNode, useCallback, useMemo } from 'react'; import classNames from 'classnames'; import { getMemberAvatarMxc, 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'; import { useRoomEvent } from '../../hooks/useRoomEvent'; import colorMXID from '../../../util/colorMXID'; import { GetMemberPowerTag } from '../../hooks/useMemberPowerTag'; import { useOtherUserColor } from '../../hooks/useUserColor'; import { isStationeryTheme, useTheme } from '../../hooks/useTheme'; import { STATIONERY_NAME_INK } from '../../utils/paperSafeInk'; type ReplyLayoutProps = { userColor?: string; username?: ReactNode; }; export const ReplyLayout = as<'div', ReplyLayoutProps>( ({ username, userColor, className, children, ...props }, ref) => { const theme = useTheme(); const isStationery = isStationeryTheme(theme); return ( {username} {children} ); } ); export const ThreadIndicator = as<'div'>(({ ...props }, ref) => ( Thread )); type ReplyProps = { room: Room; timelineSet?: EventTimelineSet | undefined; replyEventId: string; threadRootId?: string | undefined; onClick?: MouseEventHandler | undefined; /** Called when the thread indicator badge is clicked. Receives the thread root event ID. */ onOpenThread?: (threadRootId: string) => void; getMemberPowerTag?: GetMemberPowerTag; accessibleTagColors?: Map; legacyUsernameColor?: boolean; }; export const Reply = as<'div', ReplyProps>( ( { room, timelineSet, replyEventId, threadRootId, onClick, onOpenThread, getMemberPowerTag, accessibleTagColors, legacyUsernameColor, ...props }, ref ) => { const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []); const getFromLocalTimeline = useCallback( () => timelineSet?.findEventById(replyEventId), [timelineSet, replyEventId] ); const replyEvent = useRoomEvent(room, replyEventId, getFromLocalTimeline); const { body } = replyEvent?.getContent() ?? {}; const sender = replyEvent?.getSender(); const senderAvatarMxc = sender ? getMemberAvatarMxc(room, sender) : undefined; const customUserColor = useOtherUserColor(sender ?? '', senderAvatarMxc); const powerTag = sender ? getMemberPowerTag?.(sender) : undefined; const tagColor = powerTag?.color ? accessibleTagColors?.get(powerTag.color) : undefined; // Priority: custom user color > tag color (non-legacy) > colorMXID (legacy) const usernameColor = customUserColor ?? (legacyUsernameColor ? colorMXID(sender ?? replyEventId) : tagColor); const fallbackBody = replyEvent?.isRedacted() ? ( ) : ( ); const badEncryption = replyEvent?.getContent().msgtype === 'm.bad.encrypted'; const bodyJSX = body ? scaleSystemEmoji(trimReplyFromBody(body)) : fallbackBody; return ( {threadRootId && ( onOpenThread(threadRootId) : onClick} /> )} {getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)} ) } data-event-id={replyEventId} onClick={onClick} > {replyEvent !== undefined ? ( {badEncryption ? : bodyJSX} ) : ( )} ); } );