/* Copyright 2024 New Vector Ltd. Copyright 2020 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import React, { ChangeEvent, SyntheticEvent, useContext, useEffect, useRef, useState } from "react"; import classNames from "classnames"; import { MenuItem, Separator, ToggleMenuItem, Text, Badge, Heading, IconButton, Link, Search, Form, } from "@vector-im/compound-web"; import FavouriteIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite"; import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add"; import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link"; import SettingsIcon from "@vector-im/compound-design-tokens/assets/web/icons/settings"; import ExportArchiveIcon from "@vector-im/compound-design-tokens/assets/web/icons/export-archive"; import LeaveIcon from "@vector-im/compound-design-tokens/assets/web/icons/leave"; import FilesIcon from "@vector-im/compound-design-tokens/assets/web/icons/files"; import ExtensionsIcon from "@vector-im/compound-design-tokens/assets/web/icons/extensions"; import UserProfileIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile"; import ThreadsIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads"; import PollsIcon from "@vector-im/compound-design-tokens/assets/web/icons/polls"; import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin"; import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid"; import LockOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-off"; import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public"; import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error"; import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down"; import { EventType, JoinRule, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useIsEncrypted } from "../../../hooks/useIsEncrypted"; import BaseCard from "./BaseCard"; import { _t } from "../../../languageHandler"; import RoomAvatar from "../avatars/RoomAvatar"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; import Modal from "../../../Modal"; import ShareDialog from "../dialogs/ShareDialog"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { E2EStatus } from "../../../utils/ShieldUtils"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import RoomName from "../elements/RoomName"; import ExportDialog from "../dialogs/ExportDialog"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import PosthogTrackers from "../../../PosthogTrackers"; import { PollHistoryDialog } from "../dialogs/PollHistoryDialog"; import { Flex } from "../../utils/Flex"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import { DefaultTagID } from "../../../stores/room-list/models"; import { tagRoom } from "../../../utils/room/tagRoom"; import { canInviteTo } from "../../../utils/room/canInviteTo"; import { inviteToRoom } from "../../../utils/room/inviteToRoom"; import { useAccountData } from "../../../hooks/useAccountData"; import { useRoomState } from "../../../hooks/useRoomState"; import { useTopic } from "../../../hooks/room/useTopic"; import { Linkify, topicToHtml } from "../../../HtmlUtils"; import { Box } from "../../utils/Box"; import { onRoomTopicLinkClick } from "../elements/RoomTopic"; import { useDispatcher } from "../../../hooks/useDispatcher"; import { Action } from "../../../dispatcher/actions"; import { Key } from "../../../Keyboard"; import { useTransition } from "../../../hooks/useTransition"; import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx"; interface IProps { room: Room; permalinkCreator: RoomPermalinkCreator; onSearchChange?: (e: ChangeEvent) => void; onSearchCancel?: () => void; focusRoomSearch?: boolean; } const onRoomMembersClick = (): void => { RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, true); }; const onRoomThreadsClick = (): void => { RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true); }; const onRoomFilesClick = (): void => { RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true); }; const onRoomExtensionsClick = (): void => { RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true); }; const onRoomPinsClick = (): void => { PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton"); RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true); }; const onRoomSettingsClick = (ev: Event): void => { defaultDispatcher.dispatch({ action: "open_room_settings" }); PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev); }; const RoomTopic: React.FC> = ({ room }): JSX.Element | null => { const [expanded, setExpanded] = useState(true); const topic = useTopic(room); const body = topicToHtml(topic?.text, topic?.html); const canEditTopic = useRoomState(room, (state) => state.maySendStateEvent(EventType.RoomTopic, room.client.getSafeUserId()), ); const onEditClick = (e: SyntheticEvent): void => { e.preventDefault(); e.stopPropagation(); defaultDispatcher.dispatch({ action: "open_room_settings" }); }; if (!body && !canEditTopic) { return null; } if (!body) { return ( {_t("right_panel|add_topic")} ); } const content = expanded ? {body} : body; return ( { if (ev.target instanceof HTMLAnchorElement) { onRoomTopicLinkClick(ev); return; } }} > {content} setExpanded(!expanded)} > {expanded && canEditTopic && ( {_t("action|edit")} )} ); }; const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onSearchChange, onSearchCancel, focusRoomSearch, }) => { const cli = useContext(MatrixClientContext); const onShareRoomClick = (): void => { Modal.createDialog(ShareDialog, { target: room, }); }; const onRoomExportClick = async (): Promise => { Modal.createDialog(ExportDialog, { room, }); }; const onRoomPollHistoryClick = (): void => { Modal.createDialog(PollHistoryDialog, { room, matrixClient: cli, permalinkCreator, }); }; const onLeaveRoomClick = (): void => { defaultDispatcher.dispatch({ action: "leave_room", room_id: room.roomId, }); }; const isRoomEncrypted = useIsEncrypted(cli, room); const roomContext = useContext(RoomContext); const e2eStatus = roomContext.e2eStatus; const isVideoRoom = calcIsVideoRoom(room); const roomState = useRoomState(room); const directRoomsList = useAccountData>(room.client, EventType.Direct); const [isDirectMessage, setDirectMessage] = useState(false); useEffect(() => { for (const [, dmRoomList] of Object.entries(directRoomsList)) { if (dmRoomList.includes(room?.roomId ?? "")) { setDirectMessage(true); break; } } }, [room, directRoomsList]); const searchInputRef = useRef(null); useDispatcher(defaultDispatcher, (payload) => { if (payload.action === Action.FocusMessageSearch) { searchInputRef.current?.focus(); } }); // Clear the search field when the user leaves the search view useTransition( (prevTimelineRenderingType) => { if ( prevTimelineRenderingType === TimelineRenderingType.Search && roomContext.timelineRenderingType !== TimelineRenderingType.Search && searchInputRef.current ) { searchInputRef.current.value = ""; } }, [roomContext.timelineRenderingType], ); const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; const roomInfo = (
{(name) => ( {name} )} {alias} {!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && ( {_t("common|public_room")} )} {isRoomEncrypted && e2eStatus !== E2EStatus.Warning && ( {_t("common|encrypted")} )} {!e2eStatus && ( {_t("common|unencrypted")} )} {e2eStatus === E2EStatus.Warning && ( {_t("common|not_trusted")} )}
); const pinCount = usePinnedEvents(room).length; const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => RoomListStore.instance.getTagsForRoom(room), ); const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room)); const isFavorite = roomTags.includes(DefaultTagID.Favourite); const header = onSearchChange && ( e.preventDefault()}> { if (searchInputRef.current && e.key === Key.ESCAPE) { searchInputRef.current.value = ""; onSearchCancel?.(); } }} /> ); return ( {roomInfo}
tagRoom(room, DefaultTagID.Favourite)} /> inviteToRoom(room)} /> {!isVideoRoom && ( <>
{pinCount}
)} {!isVideoRoom && ( <> )}
); }; export default RoomSummaryCard;