MVVM RoomSummaryCard Topic (#29710)

* feat: create roomSummaryCardTopic view model

* chore: add comments and small update on test mock
This commit is contained in:
Marc
2025-04-17 17:56:19 +02:00
committed by GitHub
parent 1077729a19
commit 09ceb3c580
3 changed files with 187 additions and 36 deletions

View File

@@ -0,0 +1,84 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { type SyntheticEvent, useState } from "react";
import { EventType, type Room, type ContentHelpers } from "matrix-js-sdk/src/matrix";
import { type Optional } from "matrix-events-sdk";
import { useRoomState } from "../../../hooks/useRoomState";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { onRoomTopicLinkClick } from "../../views/elements/RoomTopic";
import { useTopic } from "../../../hooks/room/useTopic";
export interface RoomTopicState {
/**
* The topic of the room, the value is taken from the room state
*/
topic: Optional<ContentHelpers.TopicState>;
/**
* Whether the topic is expanded or not
*/
expanded: boolean;
/**
* Whether the user have the permission to edit the topic
*/
canEditTopic: boolean;
/**
* The callback when the edit button is clicked
*/
onEditClick: (e: SyntheticEvent) => void;
/**
* When the expand button is clicked, it changes expanded state
*/
onExpandedClick: (ev: SyntheticEvent) => void;
/**
* The callback when the topic link is clicked
*/
onTopicLinkClick: React.MouseEventHandler<HTMLElement>;
}
/**
* The view model for the room topic used in the RoomSummaryCard
* @param room - the room to get the topic from
* @returns the room topic state
*/
export function useRoomTopicViewModel(room: Room): RoomTopicState {
const [expanded, setExpanded] = useState(true);
const topic = useTopic(room);
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" });
};
const onExpandedClick = (e: SyntheticEvent): void => {
e.preventDefault();
e.stopPropagation();
setExpanded((_expanded) => !_expanded);
};
const onTopicLinkClick = (e: React.MouseEvent): void => {
if (e.target instanceof HTMLAnchorElement) {
onRoomTopicLinkClick(e);
return;
}
};
return {
topic,
expanded,
canEditTopic,
onEditClick,
onExpandedClick,
onTopicLinkClick,
};
}

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ChangeEvent, type SyntheticEvent, useContext, useEffect, useRef, useState } from "react";
import React, { type JSX, type ChangeEvent, useContext, useEffect, useRef, useState } from "react";
import classNames from "classnames";
import {
MenuItem,
@@ -66,10 +66,8 @@ 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";
@@ -79,6 +77,7 @@ import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
import { ReportRoomDialog } from "../dialogs/ReportRoomDialog.tsx";
import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";
interface IProps {
room: Room;
@@ -115,21 +114,11 @@ const onRoomSettingsClick = (ev: Event): void => {
};
const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
const [expanded, setExpanded] = useState(true);
const vm = useRoomTopicViewModel(room);
const topic = useTopic(room);
const body = topicToHtml(topic?.text, topic?.html);
const body = topicToHtml(vm.topic?.text, vm.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) {
if (!body && !vm.canEditTopic) {
return null;
}
@@ -143,7 +132,7 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
className="mx_RoomSummaryCard_topic"
>
<Box flex="1">
<Link kind="primary" onClick={onEditClick}>
<Link kind="primary" onClick={vm.onEditClick}>
<Text size="sm" weight="regular">
{_t("right_panel|add_topic")}
</Text>
@@ -153,7 +142,7 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
);
}
const content = expanded ? <Linkify>{body}</Linkify> : body;
const content = vm.expanded ? <Linkify>{body}</Linkify> : body;
return (
<Flex
as="section"
@@ -161,33 +150,20 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
justify="center"
gap="var(--cpd-space-2x)"
className={classNames("mx_RoomSummaryCard_topic", {
mx_RoomSummaryCard_topic_collapsed: !expanded,
mx_RoomSummaryCard_topic_collapsed: !vm.expanded,
})}
>
<Box flex="1" className="mx_RoomSummaryCard_topic_container">
<Text
size="sm"
weight="regular"
onClick={(ev: React.MouseEvent): void => {
if (ev.target instanceof HTMLAnchorElement) {
onRoomTopicLinkClick(ev);
return;
}
}}
>
<Text size="sm" weight="regular" onClick={vm.onTopicLinkClick}>
{content}
</Text>
<IconButton
className="mx_RoomSummaryCard_topic_chevron"
size="24px"
onClick={() => setExpanded(!expanded)}
>
<IconButton className="mx_RoomSummaryCard_topic_chevron" size="24px" onClick={vm.onExpandedClick}>
<ChevronDownIcon />
</IconButton>
</Box>
{expanded && canEditTopic && (
{vm.expanded && vm.canEditTopic && (
<Box flex="1" className="mx_RoomSummaryCard_topic_edit">
<Link kind="primary" onClick={onEditClick}>
<Link kind="primary" onClick={vm.onEditClick}>
<Text size="sm" weight="regular">
{_t("action|edit")}
</Text>