2020-09-08 08:48:03 +01:00
|
|
|
/*
|
2025-03-21 17:08:37 +00:00
|
|
|
Copyright 2024, 2025 New Vector Ltd.
|
2020-09-08 08:48:03 +01:00
|
|
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
2025-01-06 11:18:54 +00:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
2024-09-09 14:57:16 +01:00
|
|
|
Please see LICENSE files in the repository root for full details.
|
2020-09-08 08:48:03 +01:00
|
|
|
*/
|
|
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
import React, { useEffect, useState, type JSX } from "react";
|
2020-09-08 08:48:03 +01:00
|
|
|
import classNames from "classnames";
|
2024-05-10 11:00:43 +01:00
|
|
|
import {
|
|
|
|
|
MenuItem,
|
|
|
|
|
Separator,
|
|
|
|
|
ToggleMenuItem,
|
|
|
|
|
Text,
|
|
|
|
|
Badge,
|
|
|
|
|
Heading,
|
|
|
|
|
IconButton,
|
|
|
|
|
Link,
|
2024-07-08 10:57:41 +01:00
|
|
|
Search,
|
|
|
|
|
Form,
|
2024-05-10 11:00:43 +01:00
|
|
|
} from "@vector-im/compound-web";
|
2024-07-24 10:49:33 +01:00
|
|
|
import FavouriteIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite";
|
2024-10-04 13:42:15 +01:00
|
|
|
import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
|
2024-07-24 10:49:33 +01:00
|
|
|
import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
|
|
|
|
|
import SettingsIcon from "@vector-im/compound-design-tokens/assets/web/icons/settings";
|
2024-10-04 13:42:15 +01:00
|
|
|
import ExportArchiveIcon from "@vector-im/compound-design-tokens/assets/web/icons/export-archive";
|
2024-07-24 10:49:33 +01:00
|
|
|
import LeaveIcon from "@vector-im/compound-design-tokens/assets/web/icons/leave";
|
|
|
|
|
import FilesIcon from "@vector-im/compound-design-tokens/assets/web/icons/files";
|
2024-10-03 09:59:41 +01:00
|
|
|
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";
|
2024-07-24 10:49:33 +01:00
|
|
|
import PollsIcon from "@vector-im/compound-design-tokens/assets/web/icons/polls";
|
|
|
|
|
import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin";
|
2024-10-04 13:42:15 +01:00
|
|
|
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";
|
2024-07-24 10:49:33 +01:00
|
|
|
import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
|
2025-03-24 10:03:34 -04:00
|
|
|
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
|
|
|
|
|
import ErrorSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
2024-10-04 13:42:15 +01:00
|
|
|
import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
|
2025-05-15 16:17:21 +02:00
|
|
|
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
|
2026-07-14 09:56:03 +01:00
|
|
|
import { Box, Flex, HistoryVisibilityBadge, LinkedText, StatusTextView } from "@element-hq/web-shared-components";
|
2020-09-08 08:48:03 +01:00
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
import BaseCard from "./BaseCard.tsx";
|
|
|
|
|
import { _t } from "../../../languageHandler.tsx";
|
|
|
|
|
import RoomAvatar from "../avatars/RoomAvatar.tsx";
|
|
|
|
|
import { E2EStatus } from "../../../utils/ShieldUtils.ts";
|
|
|
|
|
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks.ts";
|
2026-03-12 15:54:01 +00:00
|
|
|
import { topicToHtml } from "../../../HtmlUtils.tsx";
|
2025-05-15 16:17:21 +02:00
|
|
|
import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx";
|
2025-04-17 17:56:19 +02:00
|
|
|
import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";
|
2026-02-24 11:04:05 +00:00
|
|
|
import { useRoomName } from "../../../hooks/useRoomName.ts";
|
2020-09-08 08:48:03 +01:00
|
|
|
|
|
|
|
|
interface IProps {
|
|
|
|
|
room: Room;
|
2023-02-28 09:39:55 +13:00
|
|
|
permalinkCreator: RoomPermalinkCreator;
|
2025-04-30 12:23:35 +01:00
|
|
|
onSearchChange?: (term: string) => void;
|
2024-07-08 10:57:41 +01:00
|
|
|
onSearchCancel?: () => void;
|
|
|
|
|
focusRoomSearch?: boolean;
|
2025-04-30 12:23:35 +01:00
|
|
|
searchTerm?: string;
|
2020-09-08 08:48:03 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-10 11:00:43 +01:00
|
|
|
const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
|
2025-04-17 17:56:19 +02:00
|
|
|
const vm = useRoomTopicViewModel(room);
|
2024-05-10 11:00:43 +01:00
|
|
|
|
2025-04-17 17:56:19 +02:00
|
|
|
const body = topicToHtml(vm.topic?.text, vm.topic?.html);
|
2024-05-10 11:00:43 +01:00
|
|
|
|
2025-04-17 17:56:19 +02:00
|
|
|
if (!body && !vm.canEditTopic) {
|
2024-07-31 10:23:01 +01:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-10 11:00:43 +01:00
|
|
|
if (!body) {
|
|
|
|
|
return (
|
|
|
|
|
<Flex
|
|
|
|
|
as="section"
|
|
|
|
|
direction="column"
|
|
|
|
|
justify="center"
|
|
|
|
|
gap="var(--cpd-space-2x)"
|
|
|
|
|
className="mx_RoomSummaryCard_topic"
|
|
|
|
|
>
|
2025-07-22 18:25:45 +02:00
|
|
|
<Box flex="1" className="mx_RoomSummaryCard_topic_box">
|
2025-04-17 17:56:19 +02:00
|
|
|
<Link kind="primary" onClick={vm.onEditClick}>
|
2024-05-10 11:00:43 +01:00
|
|
|
<Text size="sm" weight="regular">
|
|
|
|
|
{_t("right_panel|add_topic")}
|
|
|
|
|
</Text>
|
|
|
|
|
</Link>
|
|
|
|
|
</Box>
|
|
|
|
|
</Flex>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 15:54:01 +00:00
|
|
|
const content = vm.expanded ? <LinkedText as="span">{body}</LinkedText> : body;
|
2025-05-15 16:17:21 +02:00
|
|
|
|
2024-05-10 11:00:43 +01:00
|
|
|
return (
|
|
|
|
|
<Flex
|
|
|
|
|
as="section"
|
|
|
|
|
direction="column"
|
|
|
|
|
justify="center"
|
|
|
|
|
gap="var(--cpd-space-2x)"
|
|
|
|
|
className={classNames("mx_RoomSummaryCard_topic", {
|
2025-04-17 17:56:19 +02:00
|
|
|
mx_RoomSummaryCard_topic_collapsed: !vm.expanded,
|
2024-05-10 11:00:43 +01:00
|
|
|
})}
|
|
|
|
|
>
|
2025-07-22 18:25:45 +02:00
|
|
|
<Box flex="1" className="mx_RoomSummaryCard_topic_container mx_RoomSummaryCard_topic_box">
|
2025-04-17 17:56:19 +02:00
|
|
|
<Text size="sm" weight="regular" onClick={vm.onTopicLinkClick}>
|
2024-05-10 11:00:43 +01:00
|
|
|
{content}
|
|
|
|
|
</Text>
|
2025-04-17 17:56:19 +02:00
|
|
|
<IconButton className="mx_RoomSummaryCard_topic_chevron" size="24px" onClick={vm.onExpandedClick}>
|
2024-05-10 11:00:43 +01:00
|
|
|
<ChevronDownIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Box>
|
2025-04-17 17:56:19 +02:00
|
|
|
{vm.expanded && vm.canEditTopic && (
|
2024-05-10 11:00:43 +01:00
|
|
|
<Box flex="1" className="mx_RoomSummaryCard_topic_edit">
|
2025-04-17 17:56:19 +02:00
|
|
|
<Link kind="primary" onClick={vm.onEditClick}>
|
2024-05-10 11:00:43 +01:00
|
|
|
<Text size="sm" weight="regular">
|
|
|
|
|
{_t("action|edit")}
|
|
|
|
|
</Text>
|
|
|
|
|
</Link>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
</Flex>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
const RoomSummaryCardView: React.FC<IProps> = ({
|
2024-07-08 10:57:41 +01:00
|
|
|
room,
|
|
|
|
|
permalinkCreator,
|
|
|
|
|
onSearchChange,
|
|
|
|
|
onSearchCancel,
|
|
|
|
|
focusRoomSearch,
|
2025-05-06 17:24:06 +02:00
|
|
|
searchTerm = "",
|
2024-07-08 10:57:41 +01:00
|
|
|
}) => {
|
2025-05-15 16:17:21 +02:00
|
|
|
const vm = useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel);
|
2026-02-24 11:04:05 +00:00
|
|
|
// XXX: this name should be part of the view model
|
|
|
|
|
const name = useRoomName(room);
|
2024-07-08 10:57:41 +01:00
|
|
|
|
2025-05-06 17:24:06 +02:00
|
|
|
// The search field is controlled and onSearchChange is debounced in RoomView,
|
|
|
|
|
// so we need to set the value of the input right away
|
|
|
|
|
const [searchValue, setSearchValue] = useState(searchTerm);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setSearchValue(searchTerm);
|
|
|
|
|
}, [searchTerm]);
|
|
|
|
|
|
2024-10-03 09:59:41 +01:00
|
|
|
const roomInfo = (
|
2023-10-20 14:30:37 +01:00
|
|
|
<header className="mx_RoomSummaryCard_container">
|
2023-11-11 07:24:48 +00:00
|
|
|
<RoomAvatar room={room} size="80px" viewAvatarOnClick />
|
2026-02-24 11:04:05 +00:00
|
|
|
<Heading
|
|
|
|
|
as="h1"
|
|
|
|
|
size="md"
|
|
|
|
|
weight="semibold"
|
|
|
|
|
className="mx_RoomSummaryCard_roomName text-primary"
|
|
|
|
|
title={name}
|
|
|
|
|
>
|
|
|
|
|
{name}
|
|
|
|
|
</Heading>
|
2026-07-14 09:56:03 +01:00
|
|
|
{vm.userStatus && <StatusTextView status={vm.userStatus} />}
|
2023-11-11 07:24:48 +00:00
|
|
|
<Text
|
|
|
|
|
as="div"
|
|
|
|
|
size="sm"
|
|
|
|
|
weight="semibold"
|
|
|
|
|
className="mx_RoomSummaryCard_alias text-secondary"
|
2025-05-15 16:17:21 +02:00
|
|
|
title={vm.alias}
|
2023-11-11 07:24:48 +00:00
|
|
|
>
|
2025-05-15 16:17:21 +02:00
|
|
|
{vm.alias}
|
2023-11-11 07:24:48 +00:00
|
|
|
</Text>
|
|
|
|
|
|
2026-02-03 12:50:00 +00:00
|
|
|
<Flex
|
|
|
|
|
as="section"
|
|
|
|
|
justify="center"
|
|
|
|
|
gap="var(--cpd-space-2x)"
|
|
|
|
|
wrap="wrap"
|
|
|
|
|
className="mx_RoomSummaryCard_badges"
|
|
|
|
|
>
|
2025-05-15 16:17:21 +02:00
|
|
|
{!vm.isDirectMessage && vm.roomJoinRule === JoinRule.Public && (
|
2025-07-24 16:52:59 +02:00
|
|
|
<Badge kind="blue">
|
2026-03-26 21:29:21 +01:00
|
|
|
<PublicIcon width="1rem" height="1rem" color="var(--cpd-color-icon-info-primary)" />
|
2023-11-11 07:24:48 +00:00
|
|
|
{_t("common|public_room")}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
{vm.isRoomEncrypted && vm.e2eStatus !== E2EStatus.Warning && (
|
2024-10-15 09:56:43 +01:00
|
|
|
<Badge kind="green">
|
2026-03-26 21:29:21 +01:00
|
|
|
<LockIcon width="1rem" height="1rem" />
|
2023-11-11 07:24:48 +00:00
|
|
|
{_t("common|encrypted")}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
{!vm.isRoomEncrypted && (
|
2025-07-24 16:52:59 +02:00
|
|
|
<Badge kind="blue">
|
2026-03-26 21:29:21 +01:00
|
|
|
<LockOffIcon width="1rem" height="1rem" color="var(--cpd-color-icon-info-primary)" />
|
2023-11-11 07:24:48 +00:00
|
|
|
{_t("common|unencrypted")}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
{vm.e2eStatus === E2EStatus.Warning && (
|
2024-10-15 09:56:43 +01:00
|
|
|
<Badge kind="red">
|
2026-03-26 21:29:21 +01:00
|
|
|
<ErrorSolidIcon width="1rem" height="1rem" />
|
2023-11-11 07:24:48 +00:00
|
|
|
{_t("common|not_trusted")}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
2026-02-03 12:50:00 +00:00
|
|
|
|
|
|
|
|
<HistoryVisibilityBadge historyVisibility={vm.historyVisibility} />
|
2023-11-11 07:24:48 +00:00
|
|
|
</Flex>
|
2024-05-10 11:00:43 +01:00
|
|
|
|
|
|
|
|
<RoomTopic room={room} />
|
2023-10-20 14:30:37 +01:00
|
|
|
</header>
|
2020-09-08 08:48:03 +01:00
|
|
|
);
|
|
|
|
|
|
2024-10-03 09:59:41 +01:00
|
|
|
const header = onSearchChange && (
|
|
|
|
|
<Form.Root className="mx_RoomSummaryCard_search" onSubmit={(e) => e.preventDefault()}>
|
|
|
|
|
<Search
|
|
|
|
|
placeholder={_t("room|search|placeholder")}
|
|
|
|
|
name="room_message_search"
|
2025-04-30 12:23:35 +01:00
|
|
|
onChange={(e) => {
|
2025-05-06 17:24:06 +02:00
|
|
|
setSearchValue(e.currentTarget.value);
|
2025-04-30 12:23:35 +01:00
|
|
|
onSearchChange(e.currentTarget.value);
|
|
|
|
|
}}
|
2025-05-06 17:24:06 +02:00
|
|
|
value={searchValue}
|
2024-10-03 09:59:41 +01:00
|
|
|
className="mx_no_textinput"
|
2025-05-15 16:17:21 +02:00
|
|
|
ref={vm.searchInputRef}
|
2024-10-03 09:59:41 +01:00
|
|
|
autoFocus={focusRoomSearch}
|
2025-05-15 16:17:21 +02:00
|
|
|
onKeyDown={vm.onUpdateSearchInput}
|
2024-10-03 09:59:41 +01:00
|
|
|
/>
|
|
|
|
|
</Form.Root>
|
|
|
|
|
);
|
|
|
|
|
|
2020-09-08 08:48:03 +01:00
|
|
|
return (
|
2024-07-09 17:06:50 +05:30
|
|
|
<BaseCard
|
|
|
|
|
id="room-summary-panel"
|
|
|
|
|
className="mx_RoomSummaryCard"
|
|
|
|
|
ariaLabelledBy="room-summary-panel-tab"
|
|
|
|
|
role="tabpanel"
|
2024-10-03 09:59:41 +01:00
|
|
|
header={header}
|
2024-07-09 17:06:50 +05:30
|
|
|
>
|
2024-10-03 09:59:41 +01:00
|
|
|
{roomInfo}
|
2023-11-16 03:25:34 +00:00
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
2024-06-17 09:27:15 +01:00
|
|
|
<div role="menubar" aria-orientation="vertical">
|
|
|
|
|
<ToggleMenuItem
|
|
|
|
|
Icon={FavouriteIcon}
|
|
|
|
|
label={_t("room|context_menu|favourite")}
|
2025-05-15 16:17:21 +02:00
|
|
|
checked={vm.isFavorite}
|
|
|
|
|
onSelect={vm.onFavoriteToggleClick}
|
2024-06-17 09:27:15 +01:00
|
|
|
/>
|
|
|
|
|
<MenuItem
|
|
|
|
|
Icon={UserAddIcon}
|
|
|
|
|
label={_t("action|invite")}
|
2025-05-15 16:17:21 +02:00
|
|
|
disabled={!vm.canInviteToState}
|
|
|
|
|
onSelect={vm.onInviteToRoomClick}
|
2024-06-17 09:27:15 +01:00
|
|
|
/>
|
2023-11-16 03:25:34 +00:00
|
|
|
|
2024-06-17 09:27:15 +01:00
|
|
|
<Separator />
|
2024-08-01 13:00:40 +01:00
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
<MenuItem Icon={UserProfileIcon} label={_t("common|people")} onSelect={vm.onRoomMembersClick} />
|
|
|
|
|
<MenuItem Icon={ThreadsIcon} label={_t("common|threads")} onSelect={vm.onRoomThreadsClick} />
|
|
|
|
|
{!vm.isVideoRoom && (
|
2024-06-17 09:27:15 +01:00
|
|
|
<>
|
2025-09-03 16:25:49 +01:00
|
|
|
<MenuItem
|
|
|
|
|
Icon={PinIcon}
|
|
|
|
|
label={_t("right_panel|pinned_messages_button")}
|
|
|
|
|
onSelect={vm.onRoomPinsClick}
|
2024-09-16 16:51:59 +02:00
|
|
|
>
|
2025-09-03 16:25:49 +01:00
|
|
|
<Text as="span" size="sm">
|
|
|
|
|
{vm.pinCount}
|
|
|
|
|
</Text>
|
|
|
|
|
</MenuItem>
|
2025-05-15 16:17:21 +02:00
|
|
|
<MenuItem
|
|
|
|
|
Icon={FilesIcon}
|
|
|
|
|
label={_t("right_panel|files_button")}
|
|
|
|
|
onSelect={vm.onRoomFilesClick}
|
|
|
|
|
/>
|
2024-10-03 09:59:41 +01:00
|
|
|
<MenuItem
|
|
|
|
|
Icon={ExtensionsIcon}
|
|
|
|
|
label={_t("right_panel|extensions_button")}
|
2025-05-15 16:17:21 +02:00
|
|
|
onSelect={vm.onRoomExtensionsClick}
|
2024-10-03 09:59:41 +01:00
|
|
|
/>
|
2024-08-01 13:00:40 +01:00
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={vm.onShareRoomClick} />
|
2024-08-01 13:00:40 +01:00
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
{!vm.isVideoRoom && (
|
2024-08-01 13:00:40 +01:00
|
|
|
<>
|
|
|
|
|
<MenuItem
|
|
|
|
|
Icon={PollsIcon}
|
|
|
|
|
label={_t("right_panel|polls_button")}
|
2025-05-15 16:17:21 +02:00
|
|
|
onSelect={vm.onRoomPollHistoryClick}
|
2024-08-01 13:00:40 +01:00
|
|
|
/>
|
2024-06-17 09:27:15 +01:00
|
|
|
<MenuItem
|
|
|
|
|
Icon={ExportArchiveIcon}
|
|
|
|
|
label={_t("export_chat|title")}
|
2025-05-15 16:17:21 +02:00
|
|
|
onSelect={vm.onRoomExportClick}
|
2024-06-17 09:27:15 +01:00
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2023-11-16 03:25:34 +00:00
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={vm.onRoomSettingsClick} />
|
2024-08-01 13:00:40 +01:00
|
|
|
|
2024-06-17 09:27:15 +01:00
|
|
|
<Separator />
|
2025-03-21 17:08:37 +00:00
|
|
|
<div className="mx_RoomSummaryCard_bottomOptions">
|
2025-04-03 14:25:19 +01:00
|
|
|
<MenuItem
|
|
|
|
|
Icon={ErrorIcon}
|
|
|
|
|
kind="critical"
|
|
|
|
|
label={_t("action|report_room")}
|
2025-05-15 16:17:21 +02:00
|
|
|
onSelect={vm.onReportRoomClick}
|
2025-04-03 14:25:19 +01:00
|
|
|
/>
|
2025-03-21 17:08:37 +00:00
|
|
|
<MenuItem
|
|
|
|
|
className="mx_RoomSummaryCard_leave"
|
|
|
|
|
Icon={LeaveIcon}
|
|
|
|
|
kind="critical"
|
|
|
|
|
label={_t("action|leave_room")}
|
2025-05-15 16:17:21 +02:00
|
|
|
onSelect={vm.onLeaveRoomClick}
|
2025-03-21 17:08:37 +00:00
|
|
|
/>
|
|
|
|
|
</div>
|
2024-06-17 09:27:15 +01:00
|
|
|
</div>
|
2020-09-08 08:48:03 +01:00
|
|
|
</BaseCard>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-15 16:17:21 +02:00
|
|
|
export default RoomSummaryCardView;
|