).": "Start a conversation with someone using their name or username (like
).",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click
here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click
here",
"Go": "Go",
- "Invite to this space": "Invite to this space",
"Invite someone using their name, email address, username (like
) or
share this room.": "Invite someone using their name, email address, username (like
) or
share this room.",
"Invite someone using their name, username (like
) or
share this room.": "Invite someone using their name, username (like
) or
share this room.",
"Invite someone using their name, email address, username (like
) or
share this space.": "Invite someone using their name, email address, username (like
) or
share this space.",
From dfd0aaffe348e85c32b13b702e6a7cf0feb048c5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 2 Mar 2021 09:55:55 +0000
Subject: [PATCH 2/7] Iterate copy for some global warning prompts for spaces
---
src/components/structures/MatrixChat.tsx | 14 ++++++++++----
src/i18n/strings/en_EN.json | 3 +++
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 8e3d3e6b5f..d9d8b659c9 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -1072,6 +1072,7 @@ export default class MatrixChat extends React.PureComponent
{
private leaveRoomWarnings(roomId: string) {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
+ const isSpace = roomToLeave?.isSpaceRoom();
// Show a warning if there are additional complications.
const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', '');
const warnings = [];
@@ -1081,7 +1082,9 @@ export default class MatrixChat extends React.PureComponent {
warnings.push((
{' '/* Whitespace, otherwise the sentences get smashed together */ }
- { _t("This room is not public. You will not be able to rejoin without an invite.") }
+ { isSpace
+ ? _t("This space is not public. You will not be able to rejoin without an invite.")
+ : _t("This room is not public. You will not be able to rejoin without an invite.") }
));
}
@@ -1094,11 +1097,14 @@ export default class MatrixChat extends React.PureComponent {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
const warnings = this.leaveRoomWarnings(roomId);
- Modal.createTrackedDialog('Leave room', '', QuestionDialog, {
- title: _t("Leave room"),
+ const isSpace = roomToLeave?.isSpaceRoom();
+ Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, {
+ title: isSpace ? _t("Leave space") : _t("Leave room"),
description: (
- { _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
+ { isSpace
+ ? _t("Are you sure you want to leave the space '%(spaceName)s'?", {spaceName: roomToLeave.name})
+ : _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
{ warnings }
),
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 472fd9b1e7..6603a83496 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2493,7 +2493,10 @@
"Failed to reject invitation": "Failed to reject invitation",
"Cannot create rooms in this community": "Cannot create rooms in this community",
"You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.",
+ "This space is not public. You will not be able to rejoin without an invite.": "This space is not public. You will not be able to rejoin without an invite.",
"This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.",
+ "Leave space": "Leave space",
+ "Are you sure you want to leave the space '%(spaceName)s'?": "Are you sure you want to leave the space '%(spaceName)s'?",
"Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?",
"Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s",
"Signed Out": "Signed Out",
From 926e226a784d5bd66dee9f788618c060cb693e46 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 2 Mar 2021 10:07:43 +0000
Subject: [PATCH 3/7] Add Invite CTA to Space View
---
res/css/structures/_SpaceRoomView.scss | 54 +++++++++++++++++++
src/RoomInvite.js | 14 ++---
src/components/structures/SpaceRoomView.tsx | 18 ++++++-
.../views/spaces/SpacePublicShare.tsx | 4 +-
src/i18n/strings/en_EN.json | 1 +
5 files changed, 78 insertions(+), 13 deletions(-)
diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index 559f405e59..946856eed3 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -135,6 +135,60 @@ $SpaceRoomViewInnerWidth: 428px;
padding: 8px 22px;
}
}
+
+ .mx_SpaceRoomView_landing_adminButtons {
+ margin-top: 32px;
+
+ .mx_AccessibleButton {
+ position: relative;
+ width: 160px;
+ height: 124px;
+ box-sizing: border-box;
+ padding: 72px 16px 0;
+ border-radius: 12px;
+ border: 1px solid $space-button-outline-color;
+ margin-right: 28px;
+ margin-bottom: 28px;
+ font-size: $font-14px;
+ display: inline-block;
+ vertical-align: bottom;
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ &:hover {
+ background-color: rgba(141, 151, 165, 0.1);
+ }
+
+ &::before, &::after {
+ position: absolute;
+ content: "";
+ left: 16px;
+ top: 16px;
+ height: 40px;
+ width: 40px;
+ border-radius: 20px;
+ }
+
+ &::after {
+ mask-position: center;
+ mask-size: 30px;
+ mask-repeat: no-repeat;
+ background: #ffffff; // white icon fill
+ }
+
+ &.mx_SpaceRoomView_landing_inviteButton {
+ &::before {
+ background-color: $accent-color;
+ }
+
+ &::after {
+ mask-image: url('$(res)/img/element-icons/room/invite.svg');
+ }
+ }
+ }
+ }
}
.mx_SpaceRoomView_privateScope {
diff --git a/src/RoomInvite.js b/src/RoomInvite.js
index 728ae11e79..503411d2b3 100644
--- a/src/RoomInvite.js
+++ b/src/RoomInvite.js
@@ -50,10 +50,13 @@ export function showStartChatInviteDialog(initialText) {
}
export function showRoomInviteDialog(roomId) {
+ const isSpace = MatrixClientPeg.get()?.getRoom(roomId)?.isSpaceRoom();
// This dialog handles the room creation internally - we don't need to worry about it.
- const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog(
- 'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId},
+ "Invite Users", isSpace ? "Space" : "Room", InviteDialog, {
+ kind: isSpace ? KIND_SPACE_INVITE : KIND_INVITE,
+ roomId,
+ },
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
);
}
@@ -75,13 +78,6 @@ export function showCommunityInviteDialog(communityId) {
}
}
-export const showSpaceInviteDialog = (roomId) => {
- Modal.createTrackedDialog("Invite Users", "Space", InviteDialog, {
- kind: KIND_SPACE_INVITE,
- roomId,
- }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
-};
-
/**
* Checks if the given MatrixEvent is a valid 3rd party user invite.
* @param {MatrixEvent} event The event to check
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index 6c64df31eb..9e73b97d5a 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -25,7 +25,7 @@ import AccessibleButton from "../views/elements/AccessibleButton";
import RoomName from "../views/elements/RoomName";
import RoomTopic from "../views/elements/RoomTopic";
import FormButton from "../views/elements/FormButton";
-import {inviteMultipleToRoom, showSpaceInviteDialog} from "../../RoomInvite";
+import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite";
import {useRoomMembers} from "../../hooks/useRoomMembers";
import createRoom, {IOpts, Preset} from "../../createRoom";
import Field from "../views/elements/Field";
@@ -108,6 +108,17 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
;
}
+ let inviteButton;
+ if (myMembership === "join" && space.canInvite(userId)) {
+ inviteButton = (
+
@@ -167,6 +178,9 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
{ joinButtons }
+
+ { inviteButton }
+
;
};
@@ -361,7 +375,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
showSpaceInviteDialog(space.roomId)}
+ onClick={() => showRoomInviteDialog(space.roomId)}
>
{ _t("Invite by username") }
diff --git a/src/components/views/spaces/SpacePublicShare.tsx b/src/components/views/spaces/SpacePublicShare.tsx
index 064d1640a2..3930c1db16 100644
--- a/src/components/views/spaces/SpacePublicShare.tsx
+++ b/src/components/views/spaces/SpacePublicShare.tsx
@@ -22,7 +22,7 @@ import AccessibleButton from "../elements/AccessibleButton";
import {copyPlaintext} from "../../../utils/strings";
import {sleep} from "../../../utils/promise";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
-import {showSpaceInviteDialog} from "../../../RoomInvite";
+import {showRoomInviteDialog} from "../../../RoomInvite";
interface IProps {
space: Room;
@@ -53,7 +53,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
{
- showSpaceInviteDialog(space.roomId);
+ showRoomInviteDialog(space.roomId);
onFinished();
}}
>
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 6603a83496..5f3d293571 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2561,6 +2561,7 @@
"You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.",
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
"Accept Invite": "Accept Invite",
+ "Invite people": "Invite people",
"%(count)s members|other": "%(count)s members",
"%(count)s members|one": "%(count)s member",
" invited you to ": " invited you to ",
From ab4b7b73ea7f8eeb18be6ab7491e2e1649b35969 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 2 Mar 2021 10:34:28 +0000
Subject: [PATCH 4/7] Add a basic Space Settings view
---
res/css/_components.scss | 1 +
res/css/structures/_SpaceRoomView.scss | 10 ++
.../views/dialogs/_SpaceSettingsDialog.scss | 55 ++++++
src/components/structures/MatrixChat.tsx | 4 +
src/components/structures/SpaceRoomView.tsx | 12 +-
.../views/dialogs/SpaceSettingsDialog.tsx | 162 ++++++++++++++++++
src/i18n/strings/en_EN.json | 8 +
src/utils/space.ts | 9 +
8 files changed, 260 insertions(+), 1 deletion(-)
create mode 100644 res/css/views/dialogs/_SpaceSettingsDialog.scss
create mode 100644 src/components/views/dialogs/SpaceSettingsDialog.tsx
diff --git a/res/css/_components.scss b/res/css/_components.scss
index ca66aa60ec..db73eed3f2 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -91,6 +91,7 @@
@import "./views/dialogs/_SettingsDialog.scss";
@import "./views/dialogs/_ShareDialog.scss";
@import "./views/dialogs/_SlashCommandHelpDialog.scss";
+@import "./views/dialogs/_SpaceSettingsDialog.scss";
@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss";
@import "./views/dialogs/_TermsDialog.scss";
@import "./views/dialogs/_UploadConfirmDialog.scss";
diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index 946856eed3..0a42db130a 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -187,6 +187,16 @@ $SpaceRoomViewInnerWidth: 428px;
mask-image: url('$(res)/img/element-icons/room/invite.svg');
}
}
+
+ &.mx_SpaceRoomView_landing_settingsButton {
+ &::before {
+ background-color: #5c56f5;
+ }
+
+ &::after {
+ mask-image: url('$(res)/img/element-icons/settings.svg');
+ }
+ }
}
}
}
diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss
new file mode 100644
index 0000000000..c1fa539e9b
--- /dev/null
+++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss
@@ -0,0 +1,55 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_SpaceSettingsDialog {
+ width: 480px;
+ color: $primary-fg-color;
+
+ .mx_SpaceSettings_errorText {
+ font-weight: $font-semi-bold;
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $notice-primary-color;
+ margin-bottom: 28px;
+ }
+
+ .mx_ToggleSwitch {
+ display: inline-block;
+ vertical-align: middle;
+ margin-left: 16px;
+ }
+
+ .mx_AccessibleButton_kind_danger {
+ margin-top: 28px;
+ }
+
+ .mx_SpaceSettingsDialog_buttons {
+ display: flex;
+ margin-top: 64px;
+
+ .mx_AccessibleButton {
+ display: inline-block;
+ }
+
+ .mx_AccessibleButton_kind_link {
+ margin-left: auto;
+ }
+ }
+
+ .mx_FormButton {
+ padding: 8px 22px;
+ }
+}
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index d9d8b659c9..83b3565738 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -1118,6 +1118,10 @@ export default class MatrixChat extends React.PureComponent {
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
d.finally(() => modal.close());
+ dis.dispatch({
+ action: "after_leave_room",
+ room_id: roomId,
+ });
}
},
});
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index 9e73b97d5a..49af14017e 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -46,7 +46,7 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
import {useStateArray} from "../../hooks/useStateArray";
import SpacePublicShare from "../views/spaces/SpacePublicShare";
-import {shouldShowSpaceSettings} from "../../utils/space";
+import {shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
import MemberAvatar from "../views/avatars/MemberAvatar";
interface IProps {
@@ -119,6 +119,15 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
);
}
+ let settingsButton;
+ if (shouldShowSpaceSettings(cli, space)) {
+ settingsButton = {
+ showSpaceSettings(cli, space);
+ }}>
+ { _t("Settings") }
+ ;
+ }
+
return
@@ -180,6 +189,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
{ joinButtons }
{ inviteButton }
+ { settingsButton }
;
};
diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx
new file mode 100644
index 0000000000..f6bf5b87e6
--- /dev/null
+++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx
@@ -0,0 +1,162 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React, {useState} from 'react';
+import {Room} from "matrix-js-sdk/src/models/room";
+import {MatrixClient} from "matrix-js-sdk/src/client";
+import {EventType} from "matrix-js-sdk/src/@types/event";
+
+import {_t} from '../../../languageHandler';
+import {IDialogProps} from "./IDialogProps";
+import BaseDialog from "./BaseDialog";
+import DevtoolsDialog from "./DevtoolsDialog";
+import SpaceBasicSettings from '../spaces/SpaceBasicSettings';
+import {getTopic} from "../elements/RoomTopic";
+import {avatarUrlForRoom} from "../../../Avatar";
+import ToggleSwitch from "../elements/ToggleSwitch";
+import AccessibleButton from "../elements/AccessibleButton";
+import FormButton from "../elements/FormButton";
+import Modal from "../../../Modal";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import {allSettled} from "../../../utils/promise";
+import {useDispatcher} from "../../../hooks/useDispatcher";
+
+interface IProps extends IDialogProps {
+ matrixClient: MatrixClient;
+ space: Room;
+}
+
+const SpaceSettingsDialog: React.FC
= ({ matrixClient: cli, space, onFinished }) => {
+ useDispatcher(defaultDispatcher, ({action, ...params}) => {
+ if (action === "after_leave_room" && params.room_id === space.roomId) {
+ onFinished(false);
+ }
+ });
+
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+
+ const userId = cli.getUserId();
+
+ const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar
+ const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId);
+ const avatarChanged = newAvatar !== null;
+
+ const [name, setName] = useState(space.name);
+ const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId);
+ const nameChanged = name !== space.name;
+
+ const currentTopic = getTopic(space);
+ const [topic, setTopic] = useState(currentTopic);
+ const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId);
+ const topicChanged = topic !== currentTopic;
+
+ const currentJoinRule = space.getJoinRule();
+ const [joinRule, setJoinRule] = useState(currentJoinRule);
+ const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId);
+ const joinRuleChanged = joinRule !== currentJoinRule;
+
+ const onSave = async () => {
+ setBusy(true);
+ const promises = [];
+
+ if (avatarChanged) {
+ promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {
+ url: await cli.uploadContent(newAvatar),
+ }, ""));
+ }
+
+ if (nameChanged) {
+ promises.push(cli.setRoomName(space.roomId, name));
+ }
+
+ if (topicChanged) {
+ promises.push(cli.setRoomTopic(space.roomId, topic));
+ }
+
+ if (joinRuleChanged) {
+ promises.push(cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, { join_rule: joinRule }, ""));
+ }
+
+ const results = await allSettled(promises);
+ setBusy(false);
+ const failures = results.filter(r => r.status === "rejected");
+ if (failures.length > 0) {
+ console.error("Failed to save space settings: ", failures);
+ setError(_t("Failed to save space settings."));
+ }
+ };
+
+ return
+
+
{ _t("Edit settings relating to your space.") }
+
+ { error &&
{ error }
}
+
+
+
+
+ { _t("Make this space private") }
+ setJoinRule(checked ? "private" : "invite")}
+ disabled={!canSetJoinRule}
+ aria-label={_t("Make this space private")}
+ />
+
+
+
{
+ defaultDispatcher.dispatch({
+ action: "leave_room",
+ room_id: space.roomId,
+ });
+ }}
+ />
+
+
+
Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}>
+ { _t("View dev tools") }
+
+
+ { _t("Cancel") }
+
+
+
+
+ ;
+};
+
+export default SpaceSettingsDialog;
+
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 5f3d293571..cd2fcf1117 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2282,6 +2282,14 @@
"Link to selected message": "Link to selected message",
"Copy": "Copy",
"Command Help": "Command Help",
+ "Failed to save space settings.": "Failed to save space settings.",
+ "Space settings": "Space settings",
+ "Edit settings relating to your space.": "Edit settings relating to your space.",
+ "Make this space private": "Make this space private",
+ "Leave Space": "Leave Space",
+ "View dev tools": "View dev tools",
+ "Saving...": "Saving...",
+ "Save Changes": "Save Changes",
"To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.",
"Missing session data": "Missing session data",
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.",
diff --git a/src/utils/space.ts b/src/utils/space.ts
index 98801cabd0..2ee4d0071e 100644
--- a/src/utils/space.ts
+++ b/src/utils/space.ts
@@ -19,6 +19,8 @@ import {MatrixClient} from "matrix-js-sdk/src/client";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {calculateRoomVia} from "../utils/permalinks/Permalinks";
+import Modal from "../Modal";
+import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
const userId = cli.getUserId();
@@ -37,3 +39,10 @@ export const makeSpaceParentEvent = (room: Room, canonical = false) => ({
},
state_key: room.roomId,
});
+
+export const showSpaceSettings = (cli: MatrixClient, space: Room) => {
+ Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, {
+ matrixClient: cli,
+ space,
+ }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
+};
From a687b9883ca42c927ec89d20ff066620a10b17ed Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 2 Mar 2021 13:28:05 +0000
Subject: [PATCH 5/7] Add a create room in space CTA to Space View
---
res/css/structures/_SpaceRoomView.scss | 10 ++++++++++
src/components/structures/SpaceRoomView.tsx | 16 +++++++++++++++-
.../views/dialogs/CreateRoomDialog.js | 7 +++++++
src/i18n/strings/en_EN.json | 1 +
src/utils/space.ts | 18 ++++++++++++++++++
5 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index 0a42db130a..eaaaa2f797 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -188,6 +188,16 @@ $SpaceRoomViewInnerWidth: 428px;
}
}
+ &.mx_SpaceRoomView_landing_createButton {
+ &::before {
+ background-color: #368bd6;
+ }
+
+ &::after {
+ mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
+ }
+ }
+
&.mx_SpaceRoomView_landing_settingsButton {
&::before {
background-color: #5c56f5;
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index 49af14017e..4159a38cfe 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -46,7 +46,7 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
import {useStateArray} from "../../hooks/useStateArray";
import SpacePublicShare from "../views/spaces/SpacePublicShare";
-import {shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
+import {showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
import MemberAvatar from "../views/avatars/MemberAvatar";
interface IProps {
@@ -119,6 +119,19 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
);
}
+ const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
+
+ let addRoomButtons;
+ if (canAddRooms) {
+ addRoomButtons =
+ {
+ showCreateNewRoom(cli, space);
+ }}>
+ { _t("Create a new room") }
+
+ ;
+ }
+
let settingsButton;
if (shouldShowSpaceSettings(cli, space)) {
settingsButton = {
@@ -189,6 +202,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
{ joinButtons }
{ inviteButton }
+ { addRoomButtons }
{ settingsButton }
;
diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js
index 2b6bb5e187..0771b0ec45 100644
--- a/src/components/views/dialogs/CreateRoomDialog.js
+++ b/src/components/views/dialogs/CreateRoomDialog.js
@@ -17,6 +17,8 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
+import {Room} from "matrix-js-sdk/src/models/room";
+
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import withValidation from '../elements/Validation';
@@ -30,6 +32,7 @@ export default class CreateRoomDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
defaultPublic: PropTypes.bool,
+ parentSpace: PropTypes.instanceOf(Room),
};
constructor(props) {
@@ -85,6 +88,10 @@ export default class CreateRoomDialog extends React.Component {
opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId();
}
+ if (this.props.parentSpace) {
+ opts.parentSpace = this.props.parentSpace;
+ }
+
return opts;
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index cd2fcf1117..aeef76bf22 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2570,6 +2570,7 @@
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
"Accept Invite": "Accept Invite",
"Invite people": "Invite people",
+ "Create a new room": "Create a new room",
"%(count)s members|other": "%(count)s members",
"%(count)s members|one": "%(count)s member",
" invited you to ": " invited you to ",
diff --git a/src/utils/space.ts b/src/utils/space.ts
index 2ee4d0071e..c995b860ee 100644
--- a/src/utils/space.ts
+++ b/src/utils/space.ts
@@ -21,6 +21,8 @@ import {EventType} from "matrix-js-sdk/src/@types/event";
import {calculateRoomVia} from "../utils/permalinks/Permalinks";
import Modal from "../Modal";
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
+import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog";
+import createRoom, {IOpts} from "../createRoom";
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
const userId = cli.getUserId();
@@ -46,3 +48,19 @@ export const showSpaceSettings = (cli: MatrixClient, space: Room) => {
space,
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
};
+
+export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => {
+ const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
+ "Space Landing",
+ "Create Room",
+ CreateRoomDialog,
+ {
+ defaultPublic: space.getJoinRule() === "public",
+ parentSpace: space,
+ },
+ );
+ const [shouldCreate, opts] = await modal.finished;
+ if (shouldCreate) {
+ await createRoom(opts);
+ }
+};
From e479edd47a9849a5cf12523b82eba92f1c9fe59b Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 2 Mar 2021 13:32:24 +0000
Subject: [PATCH 6/7] Add an add existing room to space CTA to Space View
---
res/css/_components.scss | 1 +
res/css/structures/_SpaceRoomView.scss | 10 +
.../dialogs/_AddExistingToSpaceDialog.scss | 185 ++++++++++++++++
src/components/structures/SpaceRoomView.tsx | 10 +-
.../dialogs/AddExistingToSpaceDialog.tsx | 208 ++++++++++++++++++
src/i18n/strings/en_EN.json | 11 +-
src/utils/space.ts | 15 ++
7 files changed, 438 insertions(+), 2 deletions(-)
create mode 100644 res/css/views/dialogs/_AddExistingToSpaceDialog.scss
create mode 100644 src/components/views/dialogs/AddExistingToSpaceDialog.tsx
diff --git a/res/css/_components.scss b/res/css/_components.scss
index db73eed3f2..8569f62de9 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -58,6 +58,7 @@
@import "./views/context_menus/_MessageContextMenu.scss";
@import "./views/context_menus/_StatusMessageContextMenu.scss";
@import "./views/context_menus/_TagTileContextMenu.scss";
+@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
@import "./views/dialogs/_AddressPickerDialog.scss";
@import "./views/dialogs/_Analytics.scss";
@import "./views/dialogs/_BugReportDialog.scss";
diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index eaaaa2f797..ee60389c59 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -188,6 +188,16 @@ $SpaceRoomViewInnerWidth: 428px;
}
}
+ &.mx_SpaceRoomView_landing_addButton {
+ &::before {
+ background-color: #ac3ba8;
+ }
+
+ &::after {
+ mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
+ }
+ }
+
&.mx_SpaceRoomView_landing_createButton {
&::before {
background-color: #368bd6;
diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
new file mode 100644
index 0000000000..0c9d8e3840
--- /dev/null
+++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
@@ -0,0 +1,185 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_AddExistingToSpaceDialog_wrapper {
+ .mx_Dialog {
+ display: flex;
+ flex-direction: column;
+ }
+}
+
+.mx_AddExistingToSpaceDialog {
+ width: 480px;
+ color: $primary-fg-color;
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ min-height: 0;
+
+ .mx_Dialog_title {
+ display: flex;
+
+ .mx_BaseAvatar {
+ display: inline-flex;
+ margin: 5px 16px 5px 5px;
+ vertical-align: middle;
+ }
+
+ .mx_BaseAvatar_image {
+ border-radius: 8px;
+ margin: 0;
+ vertical-align: unset;
+ }
+
+ > div {
+ > h1 {
+ font-weight: $font-semi-bold;
+ font-size: $font-18px;
+ line-height: $font-22px;
+ margin: 0;
+ }
+
+ .mx_AddExistingToSpaceDialog_onlySpace {
+ color: $secondary-fg-color;
+ font-size: $font-15px;
+ line-height: $font-24px;
+ }
+ }
+
+ .mx_Dropdown_input {
+ border: none;
+
+ > .mx_Dropdown_option {
+ padding-left: 0;
+ flex: unset;
+ height: unset;
+ color: $secondary-fg-color;
+ font-size: $font-15px;
+ line-height: $font-24px;
+
+ .mx_BaseAvatar {
+ display: none;
+ }
+ }
+
+ .mx_Dropdown_menu {
+ .mx_AddExistingToSpaceDialog_dropdownOptionActive {
+ color: $accent-color;
+ padding-right: 32px;
+ position: relative;
+
+ &::before {
+ content: '';
+ width: 20px;
+ height: 20px;
+ top: 8px;
+ right: 0;
+ position: absolute;
+ mask-position: center;
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ background-color: $accent-color;
+ mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
+ }
+ }
+ }
+ }
+ }
+
+ .mx_SearchBox {
+ margin: 0;
+ }
+
+ .mx_AddExistingToSpaceDialog_errorText {
+ font-weight: $font-semi-bold;
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $notice-primary-color;
+ margin-bottom: 28px;
+ }
+
+ .mx_AddExistingToSpaceDialog_content {
+ .mx_AddExistingToSpaceDialog_noResults {
+ margin-top: 24px;
+ }
+ }
+
+ .mx_AddExistingToSpaceDialog_section {
+ margin-top: 24px;
+
+ > h3 {
+ margin: 0;
+ color: $secondary-fg-color;
+ font-size: $font-12px;
+ font-weight: $font-semi-bold;
+ line-height: $font-15px;
+ }
+
+ .mx_AddExistingToSpaceDialog_entry {
+ display: flex;
+ margin-top: 12px;
+
+ .mx_BaseAvatar {
+ margin-right: 12px;
+ }
+
+ .mx_AddExistingToSpaceDialog_entry_name {
+ font-size: $font-15px;
+ line-height: 30px;
+ flex-grow: 1;
+ }
+
+ .mx_FormButton {
+ min-width: 92px;
+ font-weight: normal;
+ box-sizing: border-box;
+ }
+ }
+ }
+
+ .mx_AddExistingToSpaceDialog_section_spaces {
+ .mx_BaseAvatar_image {
+ border-radius: 8px;
+ }
+ }
+
+ .mx_AddExistingToSpaceDialog_footer {
+ display: flex;
+ margin-top: 32px;
+
+ > span {
+ flex-grow: 1;
+ font-size: $font-12px;
+ line-height: $font-15px;
+
+ > * {
+ vertical-align: middle;
+ }
+ }
+
+ .mx_AccessibleButton {
+ display: inline-block;
+ }
+
+ .mx_AccessibleButton_kind_link {
+ padding: 0;
+ }
+ }
+
+ .mx_FormButton {
+ padding: 8px 22px;
+ }
+}
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index 4159a38cfe..f1a8a4d71b 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -46,7 +46,7 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
import {useStateArray} from "../../hooks/useStateArray";
import SpacePublicShare from "../views/spaces/SpacePublicShare";
-import {showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
+import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
import MemberAvatar from "../views/avatars/MemberAvatar";
interface IProps {
@@ -124,6 +124,14 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
let addRoomButtons;
if (canAddRooms) {
addRoomButtons =
+ {
+ const [added] = await showAddExistingRooms(cli, space);
+ if (added) {
+ // TODO update rooms shown once we show hierarchy here
+ }
+ }}>
+ { _t("Add existing rooms & spaces") }
+
{
showCreateNewRoom(cli, space);
}}>
diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
new file mode 100644
index 0000000000..66efaefd9d
--- /dev/null
+++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
@@ -0,0 +1,208 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React, {useState} from "react";
+import classNames from "classnames";
+import {Room} from "matrix-js-sdk/src/models/room";
+import {MatrixClient} from "matrix-js-sdk/src/client";
+
+import {_t} from '../../../languageHandler';
+import {IDialogProps} from "./IDialogProps";
+import BaseDialog from "./BaseDialog";
+import FormButton from "../elements/FormButton";
+import Dropdown from "../elements/Dropdown";
+import SearchBox from "../../structures/SearchBox";
+import SpaceStore from "../../../stores/SpaceStore";
+import RoomAvatar from "../avatars/RoomAvatar";
+import {getDisplayAliasForRoom} from "../../../Rooms";
+import AccessibleButton from "../elements/AccessibleButton";
+import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
+import {allSettled} from "../../../utils/promise";
+import DMRoomMap from "../../../utils/DMRoomMap";
+import {calculateRoomVia} from "../../../utils/permalinks/Permalinks";
+import StyledCheckbox from "../elements/StyledCheckbox";
+
+interface IProps extends IDialogProps {
+ matrixClient: MatrixClient;
+ space: Room;
+ onCreateRoomClick(cli: MatrixClient, space: Room): void;
+}
+
+const Entry = ({ room, checked, onChange }) => {
+ return
+
+ { room.name }
+ onChange(e.target.checked)} checked={checked} />
+
;
+};
+
+const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => {
+ const [query, setQuery] = useState("");
+ const lcQuery = query.toLowerCase();
+
+ const [selectedSpace, setSelectedSpace] = useState(space);
+ const [selectedToAdd, setSelectedToAdd] = useState(new Set());
+
+ const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
+ const existingSubspacesSet = new Set(existingSubspaces);
+ const spaces = SpaceStore.instance.getSpaces().filter(s => {
+ return !existingSubspacesSet.has(s) // not already in space
+ && space !== s // not the top-level space
+ && selectedSpace !== s // not the selected space
+ && s.name.toLowerCase().includes(lcQuery); // contains query
+ });
+
+ const existingRooms = SpaceStore.instance.getChildRooms(space.roomId);
+ const existingRoomsSet = new Set(existingRooms);
+ const rooms = cli.getVisibleRooms().filter(room => {
+ return !existingRoomsSet.has(room) // not already in space
+ && room.name.toLowerCase().includes(lcQuery) // contains query
+ && !DMRoomMap.shared().getUserIdForRoomId(room.roomId); // not a DM
+ });
+
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+
+ let spaceOptionSection;
+ if (existingSubspacesSet.size > 0) {
+ const options = [space, ...existingSubspaces].map((space) => {
+ const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", {
+ mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace,
+ });
+ return
+
+ { space.name || getDisplayAliasForRoom(space) || space.roomId }
+
;
+ });
+
+ spaceOptionSection = (
+ {
+ setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space);
+ }}
+ value={selectedSpace.roomId}
+ label={_t("Space selection")}
+ >
+ { options }
+
+ );
+ } else {
+ spaceOptionSection =
+ { space.name || getDisplayAliasForRoom(space) || space.roomId }
+
;
+ }
+
+ const title =
+
+
+
{ _t("Add existing spaces/rooms") }
+ { spaceOptionSection }
+
+ ;
+
+ return
+ { error && { error }
}
+
+
+
+ { spaces.length > 0 ? (
+
+
{ _t("Spaces") }
+ { spaces.map(space => {
+ return {
+ if (checked) {
+ selectedToAdd.add(space);
+ } else {
+ selectedToAdd.delete(space);
+ }
+ setSelectedToAdd(new Set(selectedToAdd));
+ }}
+ />;
+ }) }
+
+ ) : null }
+
+ { rooms.length > 0 ? (
+
+
{ _t("Rooms") }
+ { rooms.map(room => {
+ return {
+ if (checked) {
+ selectedToAdd.add(room);
+ } else {
+ selectedToAdd.delete(room);
+ }
+ setSelectedToAdd(new Set(selectedToAdd));
+ }}
+ />;
+ }) }
+
+ ) : undefined }
+
+ { spaces.length + rooms.length < 1 ?
+ { _t("No results") }
+ : undefined }
+
+
+
+
+ { _t("Don't want to add an existing room?") }
+ onCreateRoomClick(cli, space)} kind="link">
+ { _t("Create a new room") }
+
+
+
+
{
+ setBusy(true);
+ try {
+ await allSettled(Array.from(selectedToAdd).map((room) =>
+ SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room))));
+ onFinished(true);
+ } catch (e) {
+ console.error("Failed to add rooms to space", e);
+ setError(_t("Failed to add rooms to space"));
+ }
+ setBusy(false);
+ }}
+ />
+
+ ;
+};
+
+export default AddExistingToSpaceDialog;
+
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index aeef76bf22..bb800b2af2 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1978,6 +1978,15 @@
"Add a new server...": "Add a new server...",
"%(networkName)s rooms": "%(networkName)s rooms",
"Matrix rooms": "Matrix rooms",
+ "Space selection": "Space selection",
+ "Add existing spaces/rooms": "Add existing spaces/rooms",
+ "Filter your rooms and spaces": "Filter your rooms and spaces",
+ "Spaces": "Spaces",
+ "Don't want to add an existing room?": "Don't want to add an existing room?",
+ "Create a new room": "Create a new room",
+ "Applying...": "Applying...",
+ "Apply": "Apply",
+ "Failed to add rooms to space": "Failed to add rooms to space",
"Matrix ID": "Matrix ID",
"Matrix Room ID": "Matrix Room ID",
"email address": "email address",
@@ -2570,7 +2579,7 @@
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
"Accept Invite": "Accept Invite",
"Invite people": "Invite people",
- "Create a new room": "Create a new room",
+ "Add existing rooms & spaces": "Add existing rooms & spaces",
"%(count)s members|other": "%(count)s members",
"%(count)s members|one": "%(count)s member",
" invited you to ": " invited you to ",
diff --git a/src/utils/space.ts b/src/utils/space.ts
index c995b860ee..bc31829f45 100644
--- a/src/utils/space.ts
+++ b/src/utils/space.ts
@@ -21,6 +21,7 @@ import {EventType} from "matrix-js-sdk/src/@types/event";
import {calculateRoomVia} from "../utils/permalinks/Permalinks";
import Modal from "../Modal";
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
+import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog";
import createRoom, {IOpts} from "../createRoom";
@@ -49,6 +50,20 @@ export const showSpaceSettings = (cli: MatrixClient, space: Room) => {
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
};
+export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => {
+ return Modal.createTrackedDialog(
+ "Space Landing",
+ "Add Existing",
+ AddExistingToSpaceDialog,
+ {
+ matrixClient: cli,
+ onCreateRoomClick: showCreateNewRoom,
+ space,
+ },
+ "mx_AddExistingToSpaceDialog_wrapper",
+ ).finished;
+};
+
export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => {
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
"Space Landing",
From 85985db441da38bf3872f6483d15758af49f1b15 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 3 Mar 2021 11:50:41 +0000
Subject: [PATCH 7/7] add comment
---
res/css/views/rooms/_MemberList.scss | 2 ++
1 file changed, 2 insertions(+)
diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss
index 631ddc484f..075e9ff585 100644
--- a/res/css/views/rooms/_MemberList.scss
+++ b/res/css/views/rooms/_MemberList.scss
@@ -46,6 +46,8 @@ limitations under the License.
}
.mx_RightPanel_scopeHeader {
+ // vertically align with position on other right panel cards
+ // to prevent it bouncing as user navigates right panel
margin-top: -8px;
}
}