/* eslint-disable react/destructuring-assignment */ import React, { forwardRef, MouseEventHandler, useMemo, useRef } from 'react'; import { MatrixEvent, Room } from 'matrix-js-sdk'; import { Avatar, Box, Chip, color, config, Header, Icon, IconButton, Icons, Menu, Scroll, Text, toRem, } from 'folds'; import { Opts as LinkifyOpts } from 'linkifyjs'; import { HTMLReactParserOptions } from 'html-react-parser'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents'; import * as css from './ThreadsMenu.css'; import { SequenceCard } from '../../../components/sequence-card'; import { useRoomEvent } from '../../../hooks/useRoomEvent'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { AvatarBase, DefaultPlaceholder, ImageContent, MessageNotDecryptedContent, MessageUnsupportedContent, ModernLayout, MSticker, RedactedContent, Reply, Time, Username, UsernameBold, } from '../../../components/message'; import { UserAvatar } from '../../../components/user-avatar'; import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { getEditedEvent, getMemberAvatarMxc, getMemberDisplayName } from '../../../utils/room'; import { GetContentCallback, MessageEvent } from '../../../../types/matrix/room'; import { useMentionClickHandler } from '../../../hooks/useMentionClickHandler'; import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler'; import { factoryRenderLinkifyWithMention, getReactCustomHtmlParser, LINKIFY_OPTS, makeMentionCustomProps, renderMatrixMention, } from '../../../plugins/react-custom-html-parser'; import { RenderMatrixEvent, useMatrixEventRenderer } from '../../../hooks/useMatrixEventRenderer'; import { RenderMessageContent } from '../../../components/RenderMessageContent'; import { useSetting } from '../../../state/hooks/settings'; import { settingsAtom } from '../../../state/settings'; import * as customHtmlCss from '../../../styles/CustomHtml.css'; import { EncryptedContent } from '../message'; import { Image } from '../../../components/media'; import { ImageViewer } from '../../../components/image-viewer'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; import { VirtualTile } from '../../../components/virtualizer'; import { usePowerLevelsAPI, usePowerLevelsContext } from '../../../hooks/usePowerLevels'; import { ContainerColor } from '../../../styles/ContainerColor.css'; import { getTagIconSrc, useAccessibleTagColors, usePowerLevelTags, } from '../../../hooks/usePowerLevelTags'; import { useTheme } from '../../../hooks/useTheme'; import { PowerIcon } from '../../../components/power'; import colorMXID from '../../../../util/colorMXID'; import { useIsDirectRoom } from '../../../hooks/useRoom'; type ThreadsProps = { room: Room; eventId: string; renderContent: RenderMatrixEvent<[MatrixEvent, string, GetContentCallback]>; onOpen: (roomId: string, eventId: string) => void; }; function Threads({ room, eventId, renderContent, onOpen }: ThreadsProps) { const pinnedEvent = useRoomEvent(room, eventId); const useAuthentication = useMediaAuthentication(); const mx = useMatrixClient(); const direct = useIsDirectRoom(); const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor'); const powerLevels = usePowerLevelsContext(); const { getPowerLevel } = usePowerLevelsAPI(powerLevels); const [powerLevelTags, getPowerLevelTag] = usePowerLevelTags(room, powerLevels); const theme = useTheme(); const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags); const handleOpenClick: MouseEventHandler = (evt) => { evt.stopPropagation(); const evtId = evt.currentTarget.getAttribute('data-event-id'); if (!evtId) return; onOpen(room.roomId, evtId); }; const renderOptions = () => ( Open ); if (pinnedEvent === undefined) return ; if (pinnedEvent === null) return ( Failed to load message! {renderOptions()} ); const sender = pinnedEvent.getSender()!; const displayName = getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender; const senderAvatarMxc = getMemberAvatarMxc(room, sender); const getContent = (() => pinnedEvent.getContent()) as GetContentCallback; const senderPowerLevel = getPowerLevel(sender); const powerLevelTag = getPowerLevelTag(senderPowerLevel); const tagColor = powerLevelTag?.color ? accessibleTagColors?.get(powerLevelTag.color) : undefined; const tagIconSrc = powerLevelTag?.icon ? getTagIconSrc(mx, useAuthentication, powerLevelTag.icon) : undefined; const usernameColor = legacyUsernameColor || direct ? colorMXID(sender) : tagColor; return ( } /> } > {displayName} {tagIconSrc && } {renderOptions()} {pinnedEvent.replyEventId && ( )} {renderContent(pinnedEvent.getType(), false, pinnedEvent, displayName, getContent)} ); } type ThreadsMenuProps = { room: Room; requestClose: () => void; }; export const ThreadsMenu = forwardRef( ({ room, requestClose }, ref) => { const mx = useMatrixClient(); const pinnedEvents = useRoomPinnedEvents(room); const sortedPinnedEvent = useMemo(() => Array.from(pinnedEvents).reverse(), [pinnedEvents]); const useAuthentication = useMediaAuthentication(); const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [urlPreview] = useSetting(settingsAtom, 'urlPreview'); const { navigateRoom } = useRoomNavigate(); const scrollRef = useRef(null); const virtualizer = useVirtualizer({ count: sortedPinnedEvent.length, getScrollElement: () => scrollRef.current, estimateSize: () => 75, overscan: 4, }); const mentionClickHandler = useMentionClickHandler(room.roomId); const spoilerClickHandler = useSpoilerClickHandler(); const linkifyOpts = useMemo( () => ({ ...LINKIFY_OPTS, render: factoryRenderLinkifyWithMention((href) => renderMatrixMention(mx, room.roomId, href, makeMentionCustomProps(mentionClickHandler)) ), }), [mx, room, mentionClickHandler] ); const htmlReactParserOptions = useMemo( () => getReactCustomHtmlParser(mx, room.roomId, { linkifyOpts, useAuthentication, handleSpoilerClick: spoilerClickHandler, handleMentionClick: mentionClickHandler, }), [mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication] ); const renderMatrixEvent = useMatrixEventRenderer<[MatrixEvent, string, GetContentCallback]>( { [MessageEvent.RoomMessage]: (event, displayName, getContent) => { if (event.isRedacted()) { return ( ); } return ( ); }, [MessageEvent.RoomMessageEncrypted]: (event, displayName) => { const eventId = event.getId()!; const evtTimeline = room.getTimelineForEvent(eventId); const mEvent = evtTimeline?.getEvents().find((e) => e.getId() === eventId); if (!mEvent || !evtTimeline) { return ( {event.getType()} {' event'} ); } return ( {() => { if (mEvent.isRedacted()) return ; if (mEvent.getType() === MessageEvent.Sticker) return ( ( } renderViewer={(p) => } /> )} /> ); if (mEvent.getType() === MessageEvent.RoomMessage) { const editedEvent = getEditedEvent(eventId, mEvent, evtTimeline.getTimelineSet()); const getContent = (() => editedEvent?.getContent()['m.new_content'] ?? mEvent.getContent()) as GetContentCallback; return ( ); } if (mEvent.getType() === MessageEvent.RoomMessageEncrypted) return ( ); return ( ); }} ); }, [MessageEvent.Sticker]: (event, displayName, getContent) => { if (event.isRedacted()) { return ( ); } return ( ( } renderViewer={(p) => } /> )} /> ); }, }, undefined, (event) => { if (event.isRedacted()) { return ; } return ( {event.getType()} {' event'} ); } ); const handleOpen = (roomId: string, eventId: string) => { navigateRoom(roomId, eventId); requestClose(); }; return ( Threads {sortedPinnedEvent.length > 0 ? ( {virtualizer.getVirtualItems().map((vItem) => { const eventId = sortedPinnedEvent[vItem.index]; if (!eventId) return null; return ( ); })} ) : ( No Threads This room does not have any threads yet. )} ); } );
{event.getType()}