diff --git a/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png b/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png index d3c8961391..98c1ff245d 100644 Binary files a/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png and b/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png differ diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png index 9cc13698cf..c8b8dba45b 100644 Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png differ diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png index 44d8129404..852cb85518 100644 Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png differ diff --git a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png index 2fc33b1f0b..c85c583a19 100644 Binary files a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png and b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png differ diff --git a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png index a4c053e7a7..b6990e727e 100644 Binary files a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png and b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png differ diff --git a/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png b/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png index ad49c25abc..b783826727 100644 Binary files a/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png and b/playwright/snapshots/right-panel/file-panel.spec.ts/file-tiles-list-linux.png differ diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png index e53470df87..f383a828e2 100644 Binary files a/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png and b/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png index 7ef5b40543..9fc79671a1 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png and b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png differ diff --git a/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png b/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png index 9d9b431b0c..61ab660157 100644 Binary files a/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png and b/playwright/snapshots/user-view/user-view.spec.ts/user-info-linux.png differ diff --git a/res/css/views/right_panel/_BaseCard.pcss b/res/css/views/right_panel/_BaseCard.pcss index 6d17930fce..67eb9b7e49 100644 --- a/res/css/views/right_panel/_BaseCard.pcss +++ b/res/css/views/right_panel/_BaseCard.pcss @@ -90,6 +90,7 @@ limitations under the License. min-height: 0; width: 100%; height: 100%; + scrollbar-gutter: stable; } .mx_BaseCard_Group { diff --git a/res/css/views/right_panel/_RoomSummaryCard.pcss b/res/css/views/right_panel/_RoomSummaryCard.pcss index 72b23d860e..4c3ff2f888 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.pcss +++ b/res/css/views/right_panel/_RoomSummaryCard.pcss @@ -51,6 +51,52 @@ limitations under the License. } } + .mx_RoomSummaryCard_topic { + padding: 0 12px; + + .mx_Box { + width: 100%; + } + + .mx_RoomSummaryCard_topic_container { + display: flex; + } + + .mx_RoomSummaryCard_topic_edit { + width: max-content; + } + + p { + white-space: pre-wrap; + width: 100%; + min-width: 0; + margin: 0; + } + + a { + cursor: pointer; + } + + .mx_RoomSummaryCard_topic_chevron { + transition: transform 0.3s; + } + + &.mx_RoomSummaryCard_topic_collapsed { + p { + overflow: hidden; + text-overflow: ellipsis; + white-space: normal; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + } + + .mx_RoomSummaryCard_topic_chevron { + transform: rotate(-90deg); + } + } + } + .mx_RoomSummaryCard_appsGroup { .mx_RoomSummaryCard_Button { /* this button is special so we have to override some of the original styling */ diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index e7d6ce2890..b63ed1dcf0 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -461,9 +461,13 @@ export function topicToHtml( emojiBodyElements = formatEmojis(topic, false); } - return isFormattedTopic ? ( - - ) : ( + if (isFormattedTopic) { + if (!safeTopic) return null; + return ; + } + + if (!emojiBodyElements && !topic) return null; + return ( {emojiBodyElements || topic} diff --git a/src/components/views/elements/RoomTopic.tsx b/src/components/views/elements/RoomTopic.tsx index fa9fc0fd34..f926ef5cf4 100644 --- a/src/components/views/elements/RoomTopic.tsx +++ b/src/components/views/elements/RoomTopic.tsx @@ -36,6 +36,17 @@ interface IProps extends React.HTMLProps { room: Room; } +export function onRoomTopicLinkClick(e: React.MouseEvent): void { + const anchor = e.target as HTMLLinkElement; + const localHref = tryTransformPermalinkToLocalHref(anchor.href); + + if (localHref !== anchor.href) { + // it could be converted to a localHref -> therefore handle locally + e.preventDefault(); + window.location.hash = localHref; + } +} + export default function RoomTopic({ room, className, ...props }: IProps): JSX.Element { const client = useContext(MatrixClientContext); const ref = useRef(null); @@ -54,14 +65,7 @@ export default function RoomTopic({ room, className, ...props }: IProps): JSX.El return; } - const anchor = e.target as HTMLLinkElement; - const localHref = tryTransformPermalinkToLocalHref(anchor.href); - - if (localHref !== anchor.href) { - // it could be converted to a localHref -> therefore handle locally - e.preventDefault(); - window.location.hash = localHref; - } + onRoomTopicLinkClick(e); }, [props], ); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 13f77eff14..b259d5e3ad 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -14,9 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import React, { SyntheticEvent, useCallback, useContext, useEffect, useMemo, useState } from "react"; import classNames from "classnames"; -import { MenuItem, Tooltip, Separator, ToggleMenuItem, Text, Badge, Heading } from "@vector-im/compound-web"; +import { + MenuItem, + Tooltip, + Separator, + ToggleMenuItem, + Text, + Badge, + Heading, + IconButton, + Link, +} from "@vector-im/compound-web"; import { Icon as SearchIcon } from "@vector-im/compound-design-tokens/icons/search.svg"; import { Icon as FavouriteIcon } from "@vector-im/compound-design-tokens/icons/favourite.svg"; 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 import { Icon as LockOffIcon } from "@vector-im/compound-design-tokens/icons/lock-off.svg"; import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg"; import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg"; +import { Icon as ChevronDownIcon } from "@vector-im/compound-design-tokens/icons/chevron-down.svg"; import { EventType, JoinRule, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; @@ -74,6 +85,10 @@ 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"; interface IProps { room: Room; @@ -271,6 +286,84 @@ const onRoomSettingsClick = (ev: Event): void => { PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev); }; +const RoomTopic: React.FC> = ({ room }): JSX.Element | null => { + const [expanded, setExpanded] = useState(false); + + const topic = useTopic(room); + const body = topicToHtml(topic?.text, topic?.html); + + const onEditClick = (e: SyntheticEvent): void => { + e.preventDefault(); + e.stopPropagation(); + defaultDispatcher.dispatch({ action: "open_room_settings" }); + }; + + if (!body) { + return ( + + + + + {_t("right_panel|add_topic")} + + + + + ); + } + + const content = expanded ? {body} : body; + return ( + + + { + if (ev.target instanceof HTMLAnchorElement) { + onRoomTopicLinkClick(ev); + return; + } + setExpanded(!expanded); + }} + > + {content} + + setExpanded(!expanded)} + > + + + + {expanded && ( + + + + {_t("action|edit")} + + + + )} + + ); +}; + const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onClose, onSearchClick }) => { const cli = useContext(MatrixClientContext); @@ -382,6 +475,8 @@ const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onClose, on )} + + ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9423801774..cdeecd98f6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1821,6 +1821,7 @@ }, "right_panel": { "add_integrations": "Add widgets, bridges & bots", + "add_topic": "Add topic", "edit_integrations": "Edit widgets, bridges & bots", "export_chat_button": "Export chat", "files_button": "Files", diff --git a/test/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/components/structures/__snapshots__/RoomView-test.tsx.snap index 062aeeffdd..91bfd33e83 100644 --- a/test/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -47,11 +47,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1 class="mx_LegacyRoomHeader_topic mx_RoomTopic" dir="auto" tabindex="0" - > - - + />
- -
+ />
- - + />
- - + />
", () => { expect(container).toMatchSnapshot(); }); + it("renders the room topic in the summary", () => { + room.currentState.setStateEvents([ + new MatrixEvent({ + type: "m.room.topic", + room_id: roomId, + sender: userId, + content: { + topic: "This is the room's topic.", + }, + state_key: "", + }), + ]); + const { container } = getComponent(); + expect(container).toMatchSnapshot(); + }); + + it("has button to edit topic when expanded", () => { + room.currentState.setStateEvents([ + new MatrixEvent({ + type: "m.room.topic", + room_id: roomId, + sender: userId, + content: { + topic: "This is the room's topic.", + }, + state_key: "", + }), + ]); + const { container, getByText } = getComponent(); + fireEvent.click(screen.getByText("This is the room's topic.")); + expect(getByText("Edit")).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + it("opens the search", async () => { const onSearchClick = jest.fn(); const { getByLabelText } = getComponent({ diff --git a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap index f9e95c4cb9..784be90168 100644 --- a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap +++ b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap @@ -1,5 +1,429 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` has button to edit topic when expanded 1`] = ` +
+
+
+
+ +
+
+
+ + ! + +

+ !room:domain.org +

+
+
+ +
+ Not encrypted + +
+
+
+

+ + This is the room's topic. + +

+ +
+ +
+
+