Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit eee0b2a

Browse files
authored
Add room topic to right panel room info (#12503)
* Add room topic to right panel room info Signed-off-by: Michael Telatynski <[email protected]> * Tweak styles Signed-off-by: Michael Telatynski <[email protected]> * Update snapshot Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Update snapshots Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Add snapshot tests Signed-off-by: Michael Telatynski <[email protected]> * Update snapshots Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent 3889392 commit eee0b2a

File tree

18 files changed

+1057
-35
lines changed

18 files changed

+1057
-35
lines changed
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

res/css/views/right_panel/_BaseCard.pcss

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ limitations under the License.
9090
min-height: 0;
9191
width: 100%;
9292
height: 100%;
93+
scrollbar-gutter: stable;
9394
}
9495

9596
.mx_BaseCard_Group {

res/css/views/right_panel/_RoomSummaryCard.pcss

+46
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,52 @@ limitations under the License.
5151
}
5252
}
5353

54+
.mx_RoomSummaryCard_topic {
55+
padding: 0 12px;
56+
57+
.mx_Box {
58+
width: 100%;
59+
}
60+
61+
.mx_RoomSummaryCard_topic_container {
62+
display: flex;
63+
}
64+
65+
.mx_RoomSummaryCard_topic_edit {
66+
width: max-content;
67+
}
68+
69+
p {
70+
white-space: pre-wrap;
71+
width: 100%;
72+
min-width: 0;
73+
margin: 0;
74+
}
75+
76+
a {
77+
cursor: pointer;
78+
}
79+
80+
.mx_RoomSummaryCard_topic_chevron {
81+
transition: transform 0.3s;
82+
}
83+
84+
&.mx_RoomSummaryCard_topic_collapsed {
85+
p {
86+
overflow: hidden;
87+
text-overflow: ellipsis;
88+
white-space: normal;
89+
display: -webkit-box;
90+
-webkit-box-orient: vertical;
91+
-webkit-line-clamp: 2;
92+
}
93+
94+
.mx_RoomSummaryCard_topic_chevron {
95+
transform: rotate(-90deg);
96+
}
97+
}
98+
}
99+
54100
.mx_RoomSummaryCard_appsGroup {
55101
.mx_RoomSummaryCard_Button {
56102
/* this button is special so we have to override some of the original styling */

src/HtmlUtils.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -461,9 +461,13 @@ export function topicToHtml(
461461
emojiBodyElements = formatEmojis(topic, false);
462462
}
463463

464-
return isFormattedTopic ? (
465-
<span ref={ref} dangerouslySetInnerHTML={{ __html: safeTopic }} dir="auto" />
466-
) : (
464+
if (isFormattedTopic) {
465+
if (!safeTopic) return null;
466+
return <span ref={ref} dangerouslySetInnerHTML={{ __html: safeTopic }} dir="auto" />;
467+
}
468+
469+
if (!emojiBodyElements && !topic) return null;
470+
return (
467471
<span ref={ref} dir="auto">
468472
{emojiBodyElements || topic}
469473
</span>

src/components/views/elements/RoomTopic.tsx

+12-8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ interface IProps extends React.HTMLProps<HTMLDivElement> {
3636
room: Room;
3737
}
3838

39+
export function onRoomTopicLinkClick(e: React.MouseEvent): void {
40+
const anchor = e.target as HTMLLinkElement;
41+
const localHref = tryTransformPermalinkToLocalHref(anchor.href);
42+
43+
if (localHref !== anchor.href) {
44+
// it could be converted to a localHref -> therefore handle locally
45+
e.preventDefault();
46+
window.location.hash = localHref;
47+
}
48+
}
49+
3950
export default function RoomTopic({ room, className, ...props }: IProps): JSX.Element {
4051
const client = useContext(MatrixClientContext);
4152
const ref = useRef<HTMLDivElement>(null);
@@ -54,14 +65,7 @@ export default function RoomTopic({ room, className, ...props }: IProps): JSX.El
5465
return;
5566
}
5667

57-
const anchor = e.target as HTMLLinkElement;
58-
const localHref = tryTransformPermalinkToLocalHref(anchor.href);
59-
60-
if (localHref !== anchor.href) {
61-
// it could be converted to a localHref -> therefore handle locally
62-
e.preventDefault();
63-
window.location.hash = localHref;
64-
}
68+
onRoomTopicLinkClick(e);
6569
},
6670
[props],
6771
);

src/components/views/right_panel/RoomSummaryCard.tsx

+97-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,19 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
17+
import React, { SyntheticEvent, useCallback, useContext, useEffect, useMemo, useState } from "react";
1818
import classNames from "classnames";
19-
import { MenuItem, Tooltip, Separator, ToggleMenuItem, Text, Badge, Heading } from "@vector-im/compound-web";
19+
import {
20+
MenuItem,
21+
Tooltip,
22+
Separator,
23+
ToggleMenuItem,
24+
Text,
25+
Badge,
26+
Heading,
27+
IconButton,
28+
Link,
29+
} from "@vector-im/compound-web";
2030
import { Icon as SearchIcon } from "@vector-im/compound-design-tokens/icons/search.svg";
2131
import { Icon as FavouriteIcon } from "@vector-im/compound-design-tokens/icons/favourite.svg";
2232
import { Icon as UserAddIcon } from "@vector-im/compound-design-tokens/icons/user-add.svg";
@@ -32,6 +42,7 @@ import { Icon as LockIcon } from "@vector-im/compound-design-tokens/icons/lock-s
3242
import { Icon as LockOffIcon } from "@vector-im/compound-design-tokens/icons/lock-off.svg";
3343
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
3444
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
45+
import { Icon as ChevronDownIcon } from "@vector-im/compound-design-tokens/icons/chevron-down.svg";
3546
import { EventType, JoinRule, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
3647

3748
import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -74,6 +85,10 @@ import { canInviteTo } from "../../../utils/room/canInviteTo";
7485
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
7586
import { useAccountData } from "../../../hooks/useAccountData";
7687
import { useRoomState } from "../../../hooks/useRoomState";
88+
import { useTopic } from "../../../hooks/room/useTopic";
89+
import { Linkify, topicToHtml } from "../../../HtmlUtils";
90+
import { Box } from "../../utils/Box";
91+
import { onRoomTopicLinkClick } from "../elements/RoomTopic";
7792

7893
interface IProps {
7994
room: Room;
@@ -271,6 +286,84 @@ const onRoomSettingsClick = (ev: Event): void => {
271286
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
272287
};
273288

289+
const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
290+
const [expanded, setExpanded] = useState(false);
291+
292+
const topic = useTopic(room);
293+
const body = topicToHtml(topic?.text, topic?.html);
294+
295+
const onEditClick = (e: SyntheticEvent): void => {
296+
e.preventDefault();
297+
e.stopPropagation();
298+
defaultDispatcher.dispatch({ action: "open_room_settings" });
299+
};
300+
301+
if (!body) {
302+
return (
303+
<Flex
304+
as="section"
305+
direction="column"
306+
justify="center"
307+
gap="var(--cpd-space-2x)"
308+
className="mx_RoomSummaryCard_topic"
309+
>
310+
<Box flex="1">
311+
<Link kind="primary" onClick={onEditClick}>
312+
<Text size="sm" weight="regular">
313+
{_t("right_panel|add_topic")}
314+
</Text>
315+
</Link>
316+
</Box>
317+
</Flex>
318+
);
319+
}
320+
321+
const content = expanded ? <Linkify>{body}</Linkify> : body;
322+
return (
323+
<Flex
324+
as="section"
325+
direction="column"
326+
justify="center"
327+
gap="var(--cpd-space-2x)"
328+
className={classNames("mx_RoomSummaryCard_topic", {
329+
mx_RoomSummaryCard_topic_collapsed: !expanded,
330+
})}
331+
>
332+
<Box flex="1" className="mx_RoomSummaryCard_topic_container">
333+
<Text
334+
size="sm"
335+
weight="regular"
336+
onClick={(ev: React.MouseEvent): void => {
337+
if (ev.target instanceof HTMLAnchorElement) {
338+
onRoomTopicLinkClick(ev);
339+
return;
340+
}
341+
setExpanded(!expanded);
342+
}}
343+
>
344+
{content}
345+
</Text>
346+
<IconButton
347+
className="mx_RoomSummaryCard_topic_chevron"
348+
size="24px"
349+
onClick={() => setExpanded(!expanded)}
350+
>
351+
<ChevronDownIcon />
352+
</IconButton>
353+
</Box>
354+
{expanded && (
355+
<Box flex="1" className="mx_RoomSummaryCard_topic_edit">
356+
<Link kind="primary" onClick={onEditClick}>
357+
<Text size="sm" weight="regular">
358+
{_t("action|edit")}
359+
</Text>
360+
</Link>
361+
</Box>
362+
)}
363+
</Flex>
364+
);
365+
};
366+
274367
const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, onSearchClick }) => {
275368
const cli = useContext(MatrixClientContext);
276369

@@ -382,6 +475,8 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
382475
</Badge>
383476
)}
384477
</Flex>
478+
479+
<RoomTopic room={room} />
385480
</header>
386481
);
387482

src/i18n/strings/en_EN.json

+1
Original file line numberDiff line numberDiff line change
@@ -1821,6 +1821,7 @@
18211821
},
18221822
"right_panel": {
18231823
"add_integrations": "Add widgets, bridges & bots",
1824+
"add_topic": "Add topic",
18241825
"edit_integrations": "Edit widgets, bridges & bots",
18251826
"export_chat_button": "Export chat",
18261827
"files_button": "Files",

test/components/structures/__snapshots__/RoomView-test.tsx.snap

+4-20
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
4747
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
4848
dir="auto"
4949
tabindex="0"
50-
>
51-
<span
52-
dir="auto"
53-
/>
54-
</div>
50+
/>
5551
</div>
5652
</header>
5753
<div
@@ -129,11 +125,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
129125
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
130126
dir="auto"
131127
tabindex="0"
132-
>
133-
<span
134-
dir="auto"
135-
/>
136-
</div>
128+
/>
137129
</div>
138130
</header>
139131
<main
@@ -296,11 +288,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
296288
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
297289
dir="auto"
298290
tabindex="0"
299-
>
300-
<span
301-
dir="auto"
302-
/>
303-
</div>
291+
/>
304292
</div>
305293
</header>
306294
<main
@@ -547,11 +535,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
547535
class="mx_LegacyRoomHeader_topic mx_RoomTopic"
548536
dir="auto"
549537
tabindex="0"
550-
>
551-
<span
552-
dir="auto"
553-
/>
554-
</div>
538+
/>
555539
</div>
556540
</header>
557541
<main

test/components/views/right_panel/RoomSummaryCard-test.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,40 @@ describe("<RoomSummaryCard />", () => {
107107
expect(container).toMatchSnapshot();
108108
});
109109

110+
it("renders the room topic in the summary", () => {
111+
room.currentState.setStateEvents([
112+
new MatrixEvent({
113+
type: "m.room.topic",
114+
room_id: roomId,
115+
sender: userId,
116+
content: {
117+
topic: "This is the room's topic.",
118+
},
119+
state_key: "",
120+
}),
121+
]);
122+
const { container } = getComponent();
123+
expect(container).toMatchSnapshot();
124+
});
125+
126+
it("has button to edit topic when expanded", () => {
127+
room.currentState.setStateEvents([
128+
new MatrixEvent({
129+
type: "m.room.topic",
130+
room_id: roomId,
131+
sender: userId,
132+
content: {
133+
topic: "This is the room's topic.",
134+
},
135+
state_key: "",
136+
}),
137+
]);
138+
const { container, getByText } = getComponent();
139+
fireEvent.click(screen.getByText("This is the room's topic."));
140+
expect(getByText("Edit")).toBeInTheDocument();
141+
expect(container).toMatchSnapshot();
142+
});
143+
110144
it("opens the search", async () => {
111145
const onSearchClick = jest.fn();
112146
const { getByLabelText } = getComponent({

0 commit comments

Comments
 (0)