-
|
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index 9e7d9ca205..010780565b 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -140,7 +140,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
e.preventDefault();
e.stopPropagation();
onFinished();
- showCreateNewRoom(MatrixClientPeg.get(), SpaceStore.instance.activeSpace);
+ showCreateNewRoom(SpaceStore.instance.activeSpace);
}}
disabled={!canAddRooms}
tooltip={canAddRooms ? undefined
@@ -153,7 +153,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
e.preventDefault();
e.stopPropagation();
onFinished();
- showAddExistingRooms(MatrixClientPeg.get(), SpaceStore.instance.activeSpace);
+ showAddExistingRooms(SpaceStore.instance.activeSpace);
}}
disabled={!canAddRooms}
tooltip={canAddRooms ? undefined
@@ -428,7 +428,9 @@ export default class RoomList extends React.PureComponent {
groupId={g.groupId}
groupName={g.name}
groupAvatarUrl={g.avatarUrl}
- width={32} height={32} resizeMethod='crop'
+ width={32}
+ height={32}
+ resizeMethod='crop'
/>
);
const openGroup = () => {
diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index 3cd34b1966..b8a4315e2d 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -536,8 +536,10 @@ export default class RoomPreviewBar extends React.Component {
"If you think you're seeing this message in error, please " +
"submit a bug report.",
{ errcode: this.props.error.errcode },
- { issueLink: label => { label } },
+ { issueLink: label => { label } },
),
];
break;
diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.js
index 768a456b35..a2b5566e39 100644
--- a/src/components/views/rooms/SimpleRoomHeader.js
+++ b/src/components/views/rooms/SimpleRoomHeader.js
@@ -35,13 +35,15 @@ export default class SimpleRoomHeader extends React.Component {
let icon;
if (this.props.icon) {
icon = ;
}
return (
-
+
{ icon }
{ this.props.title }
diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js
index c0e6826ba5..6649948331 100644
--- a/src/components/views/rooms/Stickerpicker.js
+++ b/src/components/views/rooms/Stickerpicker.js
@@ -403,8 +403,7 @@ export default class Stickerpicker extends React.PureComponent {
onClick={this._onHideStickersClick}
active={this.state.showStickers.toString()}
title={_t("Hide Stickers")}
- >
- ;
+ />;
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
stickerPicker =
- ;
+ />;
}
return
{ stickersButton }
diff --git a/src/components/views/rooms/TopUnreadMessagesBar.js b/src/components/views/rooms/TopUnreadMessagesBar.js
index 0f632e7128..d2a3e3a303 100644
--- a/src/components/views/rooms/TopUnreadMessagesBar.js
+++ b/src/components/views/rooms/TopUnreadMessagesBar.js
@@ -32,14 +32,16 @@ export default class TopUnreadMessagesBar extends React.Component {
render() {
return (
-
-
-
+
-
+ onClick={this.props.onCloseClick}
+ />
);
}
diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx
index f0df64fcb4..8323320520 100644
--- a/src/components/views/rooms/VoiceRecordComposerTile.tsx
+++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx
@@ -20,7 +20,7 @@ import React, { ReactNode } from "react";
import {
RecordingState,
VoiceRecording,
-} from "../../../voice/VoiceRecording";
+} from "../../../audio/VoiceRecording";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import classNames from "classnames";
@@ -189,7 +189,6 @@ export default class VoiceRecordComposerTile extends React.PureComponent;
}
diff --git a/src/components/views/settings/BridgeTile.tsx b/src/components/views/settings/BridgeTile.tsx
index 7228e4b939..5dd5ed9ba1 100644
--- a/src/components/views/settings/BridgeTile.tsx
+++ b/src/components/views/settings/BridgeTile.tsx
@@ -124,7 +124,7 @@ export default class BridgeTile extends React.PureComponent {
url={avatarUrl}
/>;
} else {
- networkIcon = ;
+ networkIcon = ;
}
let networkItem = null;
if (network) {
diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js
index 36d5d4aa0c..c3a1544cdc 100644
--- a/src/components/views/settings/ChangeAvatar.js
+++ b/src/components/views/settings/ChangeAvatar.js
@@ -148,13 +148,22 @@ export default class ChangeAvatar extends React.Component {
if (this.props.room && !this.avatarSet) {
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
avatarImg = ;
} else {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
- avatarImg = ;
+ avatarImg = ;
}
let uploadSection;
diff --git a/src/components/views/settings/EventIndexPanel.tsx b/src/components/views/settings/EventIndexPanel.tsx
index de49c2a980..9966e38de8 100644
--- a/src/components/views/settings/EventIndexPanel.tsx
+++ b/src/components/views/settings/EventIndexPanel.tsx
@@ -178,8 +178,11 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
"appear in search results.",
) }
-
+
{ _t("Enable") }
{ this.state.enabling ? : }
@@ -203,8 +206,10 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
brand,
},
{
- nativeLink: sub => { sub },
},
) }
@@ -219,8 +224,10 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
brand,
},
{
- desktopLink: sub => { sub },
},
) }
diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js
index 02eaaaeea8..d05fca983c 100644
--- a/src/components/views/settings/ProfileSettings.js
+++ b/src/components/views/settings/ProfileSettings.js
@@ -172,7 +172,8 @@ export default class ProfileSettings extends React.Component {
>
@@ -181,7 +182,8 @@ export default class ProfileSettings extends React.Component {
{ _t("Profile") }
diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx
index fd8abc0dbe..1f488f1e67 100644
--- a/src/components/views/settings/SetIdServer.tsx
+++ b/src/components/views/settings/SetIdServer.tsx
@@ -426,7 +426,9 @@ export default class SetIdServer extends React.Component {
disabled={this.state.busy}
forceValidity={this.state.error ? false : null}
/>
- { _t("Change") }
diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
index e2f30192b9..b90fb310e0 100644
--- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
+++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
@@ -97,9 +97,12 @@ export default class GeneralRoomSettingsTab extends React.Component {
{ _t("Room Addresses") }
{ _t("Other") }
{ flairSection }
diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx
index edc0220921..9225bc6b94 100644
--- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx
@@ -346,8 +346,11 @@ export default class RolesRoomSettingsTab extends React.Component {
let bannedBy = member.events.member.getSender(); // start by falling back to mxid
if (sender) bannedBy = sender.name;
return (
-
);
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index 88bc2046ce..ede9a5ddb5 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -15,52 +15,43 @@ limitations under the License.
*/
import React from 'react';
-import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "matrix-js-sdk/src/@types/partials";
+import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { EventType } from 'matrix-js-sdk/src/@types/event';
+
import { _t } from "../../../../../languageHandler";
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import Modal from "../../../../../Modal";
import QuestionDialog from "../../../dialogs/QuestionDialog";
-import StyledRadioGroup from '../../../elements/StyledRadioGroup';
+import StyledRadioGroup, { IDefinition } from '../../../elements/StyledRadioGroup';
import { SettingLevel } from "../../../../../settings/SettingLevel";
import SettingsStore from "../../../../../settings/SettingsStore";
import { UIFeature } from "../../../../../settings/UIFeature";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
+import AccessibleButton from "../../../elements/AccessibleButton";
+import SpaceStore from "../../../../../stores/SpaceStore";
+import RoomAvatar from "../../../avatars/RoomAvatar";
+import ManageRestrictedJoinRuleDialog from '../../../dialogs/ManageRestrictedJoinRuleDialog';
+import RoomUpgradeWarningDialog from '../../../dialogs/RoomUpgradeWarningDialog';
+import { upgradeRoom } from "../../../../../utils/RoomUpgrade";
+import { arrayHasDiff } from "../../../../../utils/arrays";
import SettingsFlag from '../../../elements/SettingsFlag';
-// Knock and private are reserved keywords which are not yet implemented.
-export enum JoinRule {
- Public = "public",
- Knock = "knock",
- Invite = "invite",
- /**
- * @deprecated Reserved. Should not be used.
- */
- Private = "private",
-}
-
-export enum GuestAccess {
- CanJoin = "can_join",
- Forbidden = "forbidden",
-}
-
-export enum HistoryVisibility {
- Invited = "invited",
- Joined = "joined",
- Shared = "shared",
- WorldReadable = "world_readable",
-}
-
interface IProps {
roomId: string;
}
interface IState {
joinRule: JoinRule;
+ restrictedAllowRoomIds?: string[];
guestAccess: GuestAccess;
history: HistoryVisibility;
hasAliases: boolean;
encrypted: boolean;
+ roomSupportsRestricted?: boolean;
+ preferredRestrictionVersion?: string;
+ showAdvancedSection: boolean;
}
@replaceableComponent("views.settings.tabs.room.SecurityRoomSettingsTab")
@@ -70,44 +61,58 @@ export default class SecurityRoomSettingsTab extends React.Component(
+ joinRuleEvent,
'join_rule',
JoinRule.Invite,
);
- const guestAccess: GuestAccess = this.pullContentPropertyFromEvent(
- state.getStateEvents("m.room.guest_access", ""),
+ const restrictedAllowRoomIds = joinRule === JoinRule.Restricted
+ ? joinRuleEvent?.getContent().allow
+ ?.filter(a => a.type === RestrictedAllowType.RoomMembership)
+ ?.map(a => a.room_id)
+ : undefined;
+
+ const guestAccess: GuestAccess = this.pullContentPropertyFromEvent(
+ state.getStateEvents(EventType.RoomGuestAccess, ""),
'guest_access',
GuestAccess.Forbidden,
);
- const history: HistoryVisibility = this.pullContentPropertyFromEvent(
- state.getStateEvents("m.room.history_visibility", ""),
+ const history: HistoryVisibility = this.pullContentPropertyFromEvent(
+ state.getStateEvents(EventType.RoomHistoryVisibility, ""),
'history_visibility',
HistoryVisibility.Shared,
);
+
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
- this.setState({ joinRule, guestAccess, history, encrypted });
- const hasAliases = await this.hasAliases();
- this.setState({ hasAliases });
+ const restrictedRoomCapabilities = SpaceStore.instance.restrictedJoinRuleSupport;
+ const roomSupportsRestricted = Array.isArray(restrictedRoomCapabilities?.support)
+ && restrictedRoomCapabilities.support.includes(room.getVersion());
+ const preferredRestrictionVersion = roomSupportsRestricted ? undefined : restrictedRoomCapabilities?.preferred;
+ this.setState({ joinRule, restrictedAllowRoomIds, guestAccess, history, encrypted,
+ roomSupportsRestricted, preferredRestrictionVersion });
+
+ this.hasAliases().then(hasAliases => this.setState({ hasAliases }));
}
private pullContentPropertyFromEvent(event: MatrixEvent, key: string, defaultValue: T): T {
- if (!event || !event.getContent()) return defaultValue;
- return event.getContent()[key] || defaultValue;
+ return event?.getContent()[key] || defaultValue;
}
componentWillUnmount() {
@@ -115,13 +120,13 @@ export default class SecurityRoomSettingsTab extends React.Component {
- const refreshWhenTypes = [
- 'm.room.join_rules',
- 'm.room.guest_access',
- 'm.room.history_visibility',
- 'm.room.encryption',
+ const refreshWhenTypes: EventType[] = [
+ EventType.RoomJoinRules,
+ EventType.RoomGuestAccess,
+ EventType.RoomHistoryVisibility,
+ EventType.RoomEncryption,
];
- if (refreshWhenTypes.includes(e.getType())) this.forceUpdate();
+ if (refreshWhenTypes.includes(e.getType() as EventType)) this.forceUpdate();
};
private onEncryptionChange = () => {
@@ -133,8 +138,10 @@ export default class SecurityRoomSettingsTab extends React.ComponentLearn more about encryption.",
{},
{
- a: sub => { sub },
},
),
@@ -147,7 +154,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
console.error(e);
@@ -157,89 +164,91 @@ export default class SecurityRoomSettingsTab extends React.Component {
- e.preventDefault();
- e.stopPropagation();
-
- const joinRule = JoinRule.Invite;
- const guestAccess = GuestAccess.CanJoin;
-
+ private onJoinRuleChange = async (joinRule: JoinRule) => {
const beforeJoinRule = this.state.joinRule;
- const beforeGuestAccess = this.state.guestAccess;
- this.setState({ joinRule, guestAccess });
+
+ let restrictedAllowRoomIds: string[];
+ if (joinRule === JoinRule.Restricted) {
+ const matrixClient = MatrixClientPeg.get();
+ const roomId = this.props.roomId;
+ const room = matrixClient.getRoom(roomId);
+
+ if (beforeJoinRule === JoinRule.Restricted || this.state.roomSupportsRestricted) {
+ // Have the user pick which spaces to allow joins from
+ restrictedAllowRoomIds = await this.editRestrictedRoomIds();
+ if (!Array.isArray(restrictedAllowRoomIds)) return;
+ } else if (this.state.preferredRestrictionVersion) {
+ // Block this action on a room upgrade otherwise it'd make their room unjoinable
+ const targetVersion = this.state.preferredRestrictionVersion;
+ Modal.createTrackedDialog('Restricted join rule upgrade', '', RoomUpgradeWarningDialog, {
+ roomId,
+ targetVersion,
+ description: _t("This upgrade will allow members of selected spaces " +
+ "access to this room without an invite."),
+ onFinished: (resp) => {
+ if (!resp?.continue) return;
+ upgradeRoom(room, targetVersion, resp.invite);
+ },
+ });
+ return;
+ }
+ }
+
+ if (beforeJoinRule === joinRule && !restrictedAllowRoomIds) return;
+
+ const content: IContent = {
+ join_rule: joinRule,
+ };
+
+ // pre-set the accepted spaces with the currently viewed one as per the microcopy
+ if (joinRule === JoinRule.Restricted) {
+ content.allow = restrictedAllowRoomIds.map(roomId => ({
+ "type": RestrictedAllowType.RoomMembership,
+ "room_id": roomId,
+ }));
+ }
+
+ this.setState({ joinRule, restrictedAllowRoomIds });
const client = MatrixClientPeg.get();
- client.sendStateEvent(
- this.props.roomId,
- "m.room.join_rules",
- { join_rule: joinRule },
- "",
- ).catch((e) => {
+ client.sendStateEvent(this.props.roomId, EventType.RoomJoinRules, content, "").catch((e) => {
console.error(e);
- this.setState({ joinRule: beforeJoinRule });
- });
- client.sendStateEvent(
- this.props.roomId,
- "m.room.guest_access",
- { guest_access: guestAccess },
- "",
- ).catch((e) => {
- console.error(e);
- this.setState({ guestAccess: beforeGuestAccess });
+ this.setState({
+ joinRule: beforeJoinRule,
+ restrictedAllowRoomIds: undefined,
+ });
});
};
- private onRoomAccessRadioToggle = (roomAccess: string) => {
- // join_rule
- // INVITE | PUBLIC
- // ----------------------+----------------
- // guest CAN_JOIN | inv_only | pub_with_guest
- // access ----------------------+----------------
- // FORBIDDEN | inv_only | pub_no_guest
- // ----------------------+----------------
-
- // we always set guests can_join here as it makes no sense to have
- // an invite-only room that guests can't join. If you explicitly
- // invite them, you clearly want them to join, whether they're a
- // guest or not. In practice, guest_access should probably have
- // been implemented as part of the join_rules enum.
- let joinRule = JoinRule.Invite;
- let guestAccess = GuestAccess.CanJoin;
-
- switch (roomAccess) {
- case "invite_only":
- // no change - use defaults above
- break;
- case "public_no_guests":
- joinRule = JoinRule.Public;
- guestAccess = GuestAccess.Forbidden;
- break;
- case "public_with_guests":
- joinRule = JoinRule.Public;
- guestAccess = GuestAccess.CanJoin;
- break;
- }
-
- const beforeJoinRule = this.state.joinRule;
- const beforeGuestAccess = this.state.guestAccess;
- this.setState({ joinRule, guestAccess });
+ private onRestrictedRoomIdsChange = (restrictedAllowRoomIds: string[]) => {
+ const beforeRestrictedAllowRoomIds = this.state.restrictedAllowRoomIds;
+ if (!arrayHasDiff(beforeRestrictedAllowRoomIds || [], restrictedAllowRoomIds)) return;
+ this.setState({ restrictedAllowRoomIds });
const client = MatrixClientPeg.get();
- client.sendStateEvent(
- this.props.roomId,
- "m.room.join_rules",
- { join_rule: joinRule },
- "",
- ).catch((e) => {
+ client.sendStateEvent(this.props.roomId, EventType.RoomJoinRules, {
+ join_rule: JoinRule.Restricted,
+ allow: restrictedAllowRoomIds.map(roomId => ({
+ "type": RestrictedAllowType.RoomMembership,
+ "room_id": roomId,
+ })),
+ }, "").catch((e) => {
console.error(e);
- this.setState({ joinRule: beforeJoinRule });
+ this.setState({ restrictedAllowRoomIds: beforeRestrictedAllowRoomIds });
});
- client.sendStateEvent(
- this.props.roomId,
- "m.room.guest_access",
- { guest_access: guestAccess },
- "",
- ).catch((e) => {
+ };
+
+ private onGuestAccessChange = (allowed: boolean) => {
+ const guestAccess = allowed ? GuestAccess.CanJoin : GuestAccess.Forbidden;
+ const beforeGuestAccess = this.state.guestAccess;
+ if (beforeGuestAccess === guestAccess) return;
+
+ this.setState({ guestAccess });
+
+ const client = MatrixClientPeg.get();
+ client.sendStateEvent(this.props.roomId, EventType.RoomGuestAccess, {
+ guest_access: guestAccess,
+ }, "").catch((e) => {
console.error(e);
this.setState({ guestAccess: beforeGuestAccess });
});
@@ -247,8 +256,10 @@ export default class SecurityRoomSettingsTab extends React.Component {
const beforeHistory = this.state.history;
+ if (beforeHistory === history) return;
+
this.setState({ history: history });
- MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", {
+ MatrixClientPeg.get().sendStateEvent(this.props.roomId, EventType.RoomHistoryVisibility, {
history_visibility: history,
}, "").catch((e) => {
console.error(e);
@@ -268,36 +279,48 @@ export default class SecurityRoomSettingsTab extends React.Component (ev.getContent().aliases || []).length > 0);
return hasAliases;
}
}
- private renderRoomAccess() {
+ private editRestrictedRoomIds = async (): Promise => {
+ let selected = this.state.restrictedAllowRoomIds;
+ if (!selected?.length && SpaceStore.instance.activeSpace) {
+ selected = [SpaceStore.instance.activeSpace.roomId];
+ }
+
+ const matrixClient = MatrixClientPeg.get();
+ const { finished } = Modal.createTrackedDialog('Edit restricted', '', ManageRestrictedJoinRuleDialog, {
+ matrixClient,
+ room: matrixClient.getRoom(this.props.roomId),
+ selected,
+ }, "mx_ManageRestrictedJoinRuleDialog_wrapper");
+
+ const [restrictedAllowRoomIds] = await finished;
+ return restrictedAllowRoomIds;
+ };
+
+ private onEditRestrictedClick = async () => {
+ const restrictedAllowRoomIds = await this.editRestrictedRoomIds();
+ if (!Array.isArray(restrictedAllowRoomIds)) return;
+ if (restrictedAllowRoomIds.length > 0) {
+ this.onRestrictedRoomIdsChange(restrictedAllowRoomIds);
+ } else {
+ this.onJoinRuleChange(JoinRule.Invite);
+ }
+ };
+
+ private renderJoinRule() {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const joinRule = this.state.joinRule;
- const guestAccess = this.state.guestAccess;
- const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client)
- && room.currentState.mayClientSendStateEvent("m.room.guest_access", client);
-
- let guestWarning = null;
- if (joinRule !== 'public' && guestAccess === 'forbidden') {
- guestWarning = (
-
- );
- }
+ const canChangeJoinRule = room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, client);
let aliasWarning = null;
- if (joinRule === 'public' && !this.state.hasAliases) {
+ if (joinRule === JoinRule.Public && !this.state.hasAliases) {
aliasWarning = (
@@ -308,34 +331,107 @@ export default class SecurityRoomSettingsTab extends React.Component [] = [{
+ value: JoinRule.Invite,
+ label: _t("Private (invite only)"),
+ description: _t("Only invited people can join."),
+ checked: this.state.joinRule === JoinRule.Invite
+ || (this.state.joinRule === JoinRule.Restricted && !this.state.restrictedAllowRoomIds?.length),
+ }, {
+ value: JoinRule.Public,
+ label: _t("Public"),
+ description: _t("Anyone can find and join."),
+ }];
+
+ if (this.state.roomSupportsRestricted ||
+ this.state.preferredRestrictionVersion ||
+ joinRule === JoinRule.Restricted
+ ) {
+ let upgradeRequiredPill;
+ if (this.state.preferredRestrictionVersion) {
+ upgradeRequiredPill =
+ { _t("Upgrade required") }
+ ;
+ }
+
+ let description;
+ if (joinRule === JoinRule.Restricted && this.state.restrictedAllowRoomIds?.length) {
+ const shownSpaces = this.state.restrictedAllowRoomIds
+ .map(roomId => client.getRoom(roomId))
+ .filter(room => room?.isSpaceRoom())
+ .slice(0, 4);
+
+ let moreText;
+ if (shownSpaces.length < this.state.restrictedAllowRoomIds.length) {
+ if (shownSpaces.length > 0) {
+ moreText = _t("& %(count)s more", {
+ count: this.state.restrictedAllowRoomIds.length - shownSpaces.length,
+ });
+ } else {
+ moreText = _t("Currently, %(count)s spaces have access", {
+ count: this.state.restrictedAllowRoomIds.length,
+ });
+ }
+ }
+
+ description =
+
+ { _t("Anyone in a space can find and join. Edit which spaces can access here.", {}, {
+ a: sub =>
+ { sub }
+ ,
+ }) }
+
+
+
+ { _t("Spaces with access") }
+ { shownSpaces.map(room => {
+ return
+
+ { room.name }
+ ;
+ }) }
+ { moreText && { moreText } }
+
+ ;
+ } else if (SpaceStore.instance.activeSpace) {
+ description = _t("Anyone in %(spaceName)s can find and join. You can select other spaces too.", {
+ spaceName: SpaceStore.instance.activeSpace.name,
+ });
+ } else {
+ description = _t("Anyone in a space can find and join. You can select multiple spaces.");
+ }
+
+ radioDefinitions.splice(1, 0, {
+ value: JoinRule.Restricted,
+ label: <>
+ { _t("Space members") }
+ { upgradeRequiredPill }
+ >,
+ description,
+ // if there are 0 allowed spaces then render it as invite only instead
+ checked: this.state.joinRule === JoinRule.Restricted && !!this.state.restrictedAllowRoomIds?.length,
+ });
+ }
+
return (
-
- { guestWarning }
+
+
+ { _t("Decide who can join %(roomName)s.", {
+ roomName: client.getRoom(this.props.roomId)?.name,
+ }) }
+
{ aliasWarning }
);
@@ -345,7 +441,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
+ this.setState({ showAdvancedSection: !this.state.showAdvancedSection });
+ };
+
+ private renderAdvanced() {
+ const client = MatrixClientPeg.get();
+ const guestAccess = this.state.guestAccess;
+ const state = client.getRoom(this.props.roomId).currentState;
+ const canSetGuestAccess = state.mayClientSendStateEvent(EventType.RoomGuestAccess, client);
+
+ return
+
+
+ { _t("People with supported clients will be able to join " +
+ "the room without having a registered account.") }
+
+ ;
+ }
+
render() {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const isEncrypted = this.state.encrypted;
- const hasEncryptionPermission = room.currentState.mayClientSendStateEvent("m.room.encryption", client);
+ const hasEncryptionPermission = room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, client);
const canEnableEncryption = !isEncrypted && hasEncryptionPermission;
let encryptionSettings = null;
@@ -424,18 +544,30 @@ export default class SecurityRoomSettingsTab extends React.Component
{ _t("Once enabled, encryption cannot be disabled.") }
-
{ encryptionSettings }
- { _t("Who can access this room?") }
+ { _t("Access") }
- { this.renderRoomAccess() }
+ { this.renderJoinRule() }
+
+ { this.state.showAdvancedSection ? _t("Hide advanced") : _t("Show advanced") }
+
+ { this.state.showAdvancedSection && this.renderAdvanced() }
+
{ historySection }
);
diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
index d1c497b351..44873816dc 100644
--- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
@@ -303,9 +303,12 @@ export default class AppearanceUserSettingsTab extends React.Component
{ _t("Add theme") }
+ >
+ { _t("Add theme") }
+
{ messageElement }
diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
index 2a6e8937a3..238d6cca21 100644
--- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
@@ -426,9 +426,13 @@ export default class GeneralUserSettingsTab extends React.Component {
const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck();
const discoWarning = this.state.requiredPolicyInfo.hasTerms
- ?
+ width="18"
+ height="18"
+ alt={_t("Warning")}
+ />
: null;
let accountManagementSection;
diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
index 33de634611..eaf52e6062 100644
--- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
@@ -134,28 +134,39 @@ export default class HelpUserSettingsTab extends React.Component
{ _t("Credits") }
@@ -254,7 +265,8 @@ export default class HelpUserSettingsTab extends React.Component
"Security Disclosure Policy.", {},
{
a: sub => { sub },
},
) }
diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js
index aace4ca557..fa854fc4d8 100644
--- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js
@@ -86,8 +86,11 @@ export default class LabsUserSettingsTab extends React.Component {
'test out new features and help shape them before they actually launch. ' +
'Learn more.', {}, {
'a': (sub) => {
- return { sub };
+ return { sub };
},
})
}
diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx
index 6d2cc1f5db..9d3696c5a9 100644
--- a/src/components/views/spaces/SpaceBasicSettings.tsx
+++ b/src/components/views/spaces/SpaceBasicSettings.tsx
@@ -57,11 +57,15 @@ export const SpaceAvatar = ({
src={avatar}
alt=""
/>
- {
- avatarUploadRef.current.value = "";
- setAvatarDataUrl(undefined);
- setAvatar(undefined);
- }} kind="link" className="mx_SpaceBasicSettings_avatar_remove">
+ {
+ avatarUploadRef.current.value = "";
+ setAvatarDataUrl(undefined);
+ setAvatar(undefined);
+ }}
+ kind="link"
+ className="mx_SpaceBasicSettings_avatar_remove"
+ >
{ _t("Delete") }
;
@@ -77,16 +81,21 @@ export const SpaceAvatar = ({
return
{ avatarSection }
- {
- if (!e.target.files?.length) return;
- const file = e.target.files[0];
- setAvatar(file);
- const reader = new FileReader();
- reader.onload = (ev) => {
- setAvatarDataUrl(ev.target.result as string);
- };
- reader.readAsDataURL(file);
- }} accept="image/*" />
+ {
+ if (!e.target.files?.length) return;
+ const file = e.target.files[0];
+ setAvatar(file);
+ const reader = new FileReader();
+ reader.onload = (ev) => {
+ setAvatarDataUrl(ev.target.result as string);
+ };
+ reader.readAsDataURL(file);
+ }}
+ accept="image/*"
+ />
;
};
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index ec17551d93..b48f5c79c6 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -18,13 +18,13 @@ 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 { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import AliasSettings from "../room_settings/AliasSettings";
import { useStateToggle } from "../../../hooks/useStateToggle";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
-import { GuestAccess, HistoryVisibility, JoinRule } from "../settings/tabs/room/SecurityRoomSettingsTab";
import StyledRadioGroup from "../elements/StyledRadioGroup";
interface IProps {
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index c4a2a8f9d3..90584a5361 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -203,7 +203,7 @@ export class SpaceItem extends React.PureComponent {
ev.preventDefault();
ev.stopPropagation();
- showSpaceSettings(this.context, this.props.space);
+ showSpaceSettings(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu
};
@@ -222,7 +222,7 @@ export class SpaceItem extends React.PureComponent {
ev.preventDefault();
ev.stopPropagation();
- showCreateNewRoom(this.context, this.props.space);
+ showCreateNewRoom(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu
};
@@ -230,7 +230,7 @@ export class SpaceItem extends React.PureComponent {
ev.preventDefault();
ev.stopPropagation();
- showAddExistingRooms(this.context, this.props.space);
+ showAddExistingRooms(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu
};
@@ -285,7 +285,7 @@ export class SpaceItem extends React.PureComponent {
let settingsOption;
let leaveSection;
- if (shouldShowSpaceSettings(this.context, this.props.space)) {
+ if (shouldShowSpaceSettings(this.props.space)) {
settingsOption = (
{
let fullScreenButton;
if (this.props.call.type === CallType.Video && !this.props.pipMode) {
- fullScreenButton = ;
}
let expandButton;
if (this.props.pipMode) {
- expandButton = ;
}
@@ -685,7 +689,7 @@ export default class CallView extends React.Component {
let header: React.ReactNode;
if (!this.props.pipMode) {
header =
-
+
{ callTypeText }
{ headerControls }
;
diff --git a/src/components/views/voip/DialPad.tsx b/src/components/views/voip/DialPad.tsx
index 2af8bd6989..3b4a29b3f9 100644
--- a/src/components/views/voip/DialPad.tsx
+++ b/src/components/views/voip/DialPad.tsx
@@ -68,13 +68,19 @@ export default class Dialpad extends React.PureComponent {
for (let i = 0; i < BUTTONS.length; i++) {
const button = BUTTONS[i];
const digitSubtext = BUTTON_LETTERS[i];
- buttonNodes.push();
}
if (this.props.hasDial) {
- buttonNodes.push();
}
diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx
index 0bba65e44f..a36fc37dff 100644
--- a/src/components/views/voip/DialPadModal.tsx
+++ b/src/components/views/voip/DialPadModal.tsx
@@ -81,14 +81,18 @@ export default class DialpadModal extends React.PureComponent {
// Only show the backspace button if the field has content
let dialPadField;
if (this.state.value.length !== 0) {
- dialPadField = ;
} else {
- dialPadField = {
const avatarSize = this.props.pipMode ? 76 : 160;
return (
-
+
{
+export default async function createRoom(opts: IOpts): Promise {
opts = opts || {};
if (opts.spinner === undefined) opts.spinner = true;
if (opts.guestAccess === undefined) opts.guestAccess = true;
@@ -85,7 +86,7 @@ export default function createRoom(opts: IOpts): Promise {
const client = MatrixClientPeg.get();
if (client.isGuest()) {
dis.dispatch({ action: 'require_registration' });
- return Promise.resolve(null);
+ return null;
}
const defaultPreset = opts.dmUserId ? Preset.TrustedPrivateChat : Preset.PrivateChat;
@@ -142,13 +143,37 @@ export default function createRoom(opts: IOpts): Promise {
}
if (opts.parentSpace) {
- opts.createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
- opts.createOpts.initial_state.push({
+ createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
+ createOpts.initial_state.push({
type: EventType.RoomHistoryVisibility,
content: {
- "history_visibility": opts.createOpts.preset === Preset.PublicChat ? "world_readable" : "invited",
+ "history_visibility": createOpts.preset === Preset.PublicChat ? "world_readable" : "invited",
},
});
+
+ if (opts.joinRule === JoinRule.Restricted) {
+ if (SpaceStore.instance.restrictedJoinRuleSupport?.preferred) {
+ createOpts.room_version = SpaceStore.instance.restrictedJoinRuleSupport.preferred;
+
+ createOpts.initial_state.push({
+ type: EventType.RoomJoinRules,
+ content: {
+ "join_rule": JoinRule.Restricted,
+ "allow": [{
+ "type": RestrictedAllowType.RoomMembership,
+ "room_id": opts.parentSpace.roomId,
+ }],
+ },
+ });
+ }
+ }
+ }
+
+ if (opts.joinRule !== JoinRule.Restricted) {
+ createOpts.initial_state.push({
+ type: EventType.RoomJoinRules,
+ content: { join_rule: opts.joinRule },
+ });
}
let modal;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index f48f06a791..b36910b41b 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -437,8 +437,6 @@
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
"Upgrades a room to a new version": "Upgrades a room to a new version",
"You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.",
- "Error upgrading room": "Error upgrading room",
- "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.",
"Changes your display nickname": "Changes your display nickname",
"Changes your display nickname in the current room only": "Changes your display nickname in the current room only",
"Changes the avatar of the current room": "Changes the avatar of the current room",
@@ -719,6 +717,8 @@
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
+ "Error upgrading room": "Error upgrading room",
+ "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.",
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
"Share your public space": "Share your public space",
"Unknown App": "Unknown App",
@@ -765,6 +765,16 @@
"The person who invited you already left the room.": "The person who invited you already left the room.",
"The person who invited you already left the room, or their server is offline.": "The person who invited you already left the room, or their server is offline.",
"Failed to join room": "Failed to join room",
+ "New in the Spaces beta": "New in the Spaces beta",
+ "Help people in spaces to find and join private rooms": "Help people in spaces to find and join private rooms",
+ "Learn more": "Learn more",
+ "Help space members find private rooms": "Help space members find private rooms",
+ "To help space members find and join a private room, go to that room's Security & Privacy settings.": "To help space members find and join a private room, go to that room's Security & Privacy settings.",
+ "General": "General",
+ "Security & Privacy": "Security & Privacy",
+ "Roles & Permissions": "Roles & Permissions",
+ "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.",
+ "Skip": "Skip",
"You joined the call": "You joined the call",
"%(senderName)s joined the call": "%(senderName)s joined the call",
"Call in progress": "Call in progress",
@@ -1025,7 +1035,6 @@
"Invite people": "Invite people",
"Invite with email or username": "Invite with email or username",
"Failed to save space settings.": "Failed to save space settings.",
- "General": "General",
"Edit settings relating to your space.": "Edit settings relating to your space.",
"Saving...": "Saving...",
"Save Changes": "Save Changes",
@@ -1415,27 +1424,34 @@
"Muted Users": "Muted Users",
"Banned users": "Banned users",
"Send %(eventType)s events": "Send %(eventType)s events",
- "Roles & Permissions": "Roles & Permissions",
"Permissions": "Permissions",
"Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room",
"Enable encryption?": "Enable encryption?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.",
- "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.",
- "Click here to fix": "Click here to fix",
+ "This upgrade will allow members of selected spaces access to this room without an invite.": "This upgrade will allow members of selected spaces access to this room without an invite.",
"To link to this room, please add an address.": "To link to this room, please add an address.",
- "Only people who have been invited": "Only people who have been invited",
- "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests",
- "Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests",
+ "Private (invite only)": "Private (invite only)",
+ "Only invited people can join.": "Only invited people can join.",
+ "Anyone can find and join.": "Anyone can find and join.",
+ "Upgrade required": "Upgrade required",
+ "& %(count)s more|other": "& %(count)s more",
+ "Currently, %(count)s spaces have access|other": "Currently, %(count)s spaces have access",
+ "Anyone in a space can find and join. Edit which spaces can access here.": "Anyone in a space can find and join. Edit which spaces can access here.",
+ "Spaces with access": "Spaces with access",
+ "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Anyone in %(spaceName)s can find and join. You can select other spaces too.",
+ "Anyone in a space can find and join. You can select multiple spaces.": "Anyone in a space can find and join. You can select multiple spaces.",
+ "Space members": "Space members",
+ "Decide who can join %(roomName)s.": "Decide who can join %(roomName)s.",
"Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)",
"Members only (since they were invited)": "Members only (since they were invited)",
"Members only (since they joined)": "Members only (since they joined)",
"Anyone": "Anyone",
"Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.",
+ "People with supported clients will be able to join the room without having a registered account.": "People with supported clients will be able to join the room without having a registered account.",
"Who can read history?": "Who can read history?",
- "Security & Privacy": "Security & Privacy",
"Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.",
"Encrypted": "Encrypted",
- "Who can access this room?": "Who can access this room?",
+ "Access": "Access",
"Unable to revoke sharing for email address": "Unable to revoke sharing for email address",
"Unable to share email address": "Unable to share email address",
"Your email address hasn't been verified yet": "Your email address hasn't been verified yet",
@@ -2147,7 +2163,6 @@
"People you know on %(brand)s": "People you know on %(brand)s",
"Hide": "Hide",
"Show": "Show",
- "Skip": "Skip",
"Send %(count)s invites|other": "Send %(count)s invites",
"Send %(count)s invites|one": "Send %(count)s invite",
"Invite people to join %(communityName)s": "Invite people to join %(communityName)s",
@@ -2176,18 +2191,25 @@
"Community ID": "Community ID",
"example": "example",
"Please enter a name for the room": "Please enter a name for the room",
- "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.",
"Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.",
+ "Everyone in will be able to find and join this room.": "Everyone in will be able to find and join this room.",
+ "You can change this at any time from room settings.": "You can change this at any time from room settings.",
+ "Anyone will be able to find and join this room, not just members of .": "Anyone will be able to find and join this room, not just members of .",
+ "Only people invited will be able to find and join this room.": "Only people invited will be able to find and join this room.",
"You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.",
"Your server requires encryption to be enabled in private rooms.": "Your server requires encryption to be enabled in private rooms.",
"Enable end-to-end encryption": "Enable end-to-end encryption",
"You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.",
"You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.",
+ "Create a room": "Create a room",
+ "Create a room in %(communityName)s": "Create a room in %(communityName)s",
"Create a public room": "Create a public room",
"Create a private room": "Create a private room",
- "Create a room in %(communityName)s": "Create a room in %(communityName)s",
+ "Private room (invite only)": "Private room (invite only)",
+ "Public room": "Public room",
+ "Visible to space members": "Visible to space members",
"Topic (optional)": "Topic (optional)",
- "Make this room public": "Make this room public",
+ "Room visibility": "Room visibility",
"Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.",
"Create Room": "Create Room",
"Sign out": "Sign out",
@@ -2341,6 +2363,17 @@
"Manually export keys": "Manually export keys",
"You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages",
"Are you sure you want to sign out?": "Are you sure you want to sign out?",
+ "%(count)s members|other": "%(count)s members",
+ "%(count)s members|one": "%(count)s member",
+ "%(count)s rooms|other": "%(count)s rooms",
+ "%(count)s rooms|one": "%(count)s room",
+ "You're removing all spaces. Access will default to invite only": "You're removing all spaces. Access will default to invite only",
+ "Select spaces": "Select spaces",
+ "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Decide which spaces can access this room. If a space is selected, its members can find and join .",
+ "Search spaces": "Search spaces",
+ "Spaces you know that contain this room": "Spaces you know that contain this room",
+ "Other spaces or rooms you might not know": "Other spaces or rooms you might not know",
+ "These are likely ones other room admins are a part of.": "These are likely ones other room admins are a part of.",
"Confirm by comparing the following with the User Settings in your other session:": "Confirm by comparing the following with the User Settings in your other session:",
"Confirm this user's session by comparing the following with their User Settings:": "Confirm this user's session by comparing the following with their User Settings:",
"Session name": "Session name",
@@ -2384,12 +2417,13 @@
"Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room",
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room",
"Put a link back to the old room at the start of the new room so people can see old messages": "Put a link back to the old room at the start of the new room so people can see old messages",
- "Automatically invite users": "Automatically invite users",
+ "Automatically invite members from this room to the new one": "Automatically invite members from this room to the new one",
"Upgrade private room": "Upgrade private room",
"Upgrade public room": "Upgrade public room",
"This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.",
"This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.",
"Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.",
+ "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.",
"You'll upgrade this room from to .": "You'll upgrade this room from to .",
"Resend": "Resend",
"You're all caught up.": "You're all caught up.",
@@ -2412,7 +2446,6 @@
"We call the places where you can host your account ‘homeservers’.": "We call the places where you can host your account ‘homeservers’.",
"Other homeserver": "Other homeserver",
"Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.",
- "Learn more": "Learn more",
"About homeservers": "About homeservers",
"Reset event store?": "Reset event store?",
"You most likely do not want to reset your event index store": "You most likely do not want to reset your event index store",
@@ -2647,6 +2680,7 @@
"You are an administrator of this community": "You are an administrator of this community",
"You are a member of this community": "You are a member of this community",
"Who can join this community?": "Who can join this community?",
+ "Only people who have been invited": "Only people who have been invited",
"Everyone": "Everyone",
"Your community hasn't got a Long Description, a HTML page to show to community members. Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members. Click here to open settings and give it one!",
"Long Description (HTML)": "Long Description (HTML)",
@@ -2746,10 +2780,6 @@
"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.",
"You don't have permission": "You don't have permission",
- "%(count)s members|other": "%(count)s members",
- "%(count)s members|one": "%(count)s member",
- "%(count)s rooms|other": "%(count)s rooms",
- "%(count)s rooms|one": "%(count)s room",
"This room is suggested as a good one to join": "This room is suggested as a good one to join",
"Suggested": "Suggested",
"Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.",
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 65201134bf..a338e71838 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -14,11 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import React from "react";
import { ListIteratee, Many, sortBy, throttle } from "lodash";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ISpaceSummaryRoom } from "matrix-js-sdk/src/@types/spaces";
+import { JoinRule } from "matrix-js-sdk/src/@types/partials";
+import { IRoomCapability } from "matrix-js-sdk/src/client";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
@@ -39,6 +42,12 @@ import { objectDiff } from "../utils/objects";
import { arrayHasOrderChange } from "../utils/arrays";
import { reorderLexicographically } from "../utils/stringOrderField";
import { TAG_ORDER } from "../components/views/rooms/RoomList";
+import { shouldShowSpaceSettings } from "../utils/space";
+import ToastStore from "./ToastStore";
+import { _t } from "../languageHandler";
+import GenericToast from "../components/views/toasts/GenericToast";
+import Modal from "../Modal";
+import InfoDialog from "../components/views/dialogs/InfoDialog";
type SpaceKey = string | symbol;
@@ -114,6 +123,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set();
private spaceOrderLocalEchoMap = new Map();
+ private _restrictedJoinRuleSupport?: IRoomCapability;
public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
@@ -131,7 +141,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
return this._suggestedRooms;
}
- public async setActiveRoomInSpace(space: Room | null) {
+ public async setActiveRoomInSpace(space: Room | null): Promise {
if (space && !space.isSpaceRoom()) return;
if (space !== this.activeSpace) await this.setActiveSpace(space);
@@ -166,6 +176,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
}
+ public get restrictedJoinRuleSupport(): IRoomCapability {
+ return this._restrictedJoinRuleSupport;
+ }
+
/**
* Sets the active space, updates room list filters,
* optionally switches the user's room back to where they were when they last viewed that space.
@@ -215,6 +229,65 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY);
}
+ // New in Spaces beta toast for Restricted Join Rule
+ const lsKey = "mx_SpaceBeta_restrictedJoinRuleToastSeen";
+ if (contextSwitch && space?.getJoinRule() === JoinRule.Invite && shouldShowSpaceSettings(space) &&
+ space.getJoinedMemberCount() > 1 && !localStorage.getItem(lsKey)
+ && this.restrictedJoinRuleSupport?.preferred
+ ) {
+ const toastKey = "restrictedjoinrule";
+ ToastStore.sharedInstance().addOrReplaceToast({
+ key: toastKey,
+ title: _t("New in the Spaces beta"),
+ props: {
+ description: _t("Help people in spaces to find and join private rooms"),
+ acceptLabel: _t("Learn more"),
+ onAccept: () => {
+ localStorage.setItem(lsKey, "true");
+ ToastStore.sharedInstance().dismissToast(toastKey);
+
+ Modal.createTrackedDialog("New in the Spaces beta", "restricted join rule", InfoDialog, {
+ title: _t("Help space members find private rooms"),
+ description: <>
+ { _t("To help space members find and join a private room, " +
+ "go to that room's Security & Privacy settings.") }
+
+ { /* Reuses classes from TabbedView for simplicity, non-interactive */ }
+
+
+
+ { _t("General") }
+
+
+
+ { _t("Security & Privacy") }
+
+
+
+ { _t("Roles & Permissions") }
+
+
+
+ { _t("This makes it easy for rooms to stay private to a space, " +
+ "while letting people in the space find and join them. " +
+ "All new rooms in a space will have this option available.") }
+ >,
+ button: _t("OK"),
+ hasCloseButton: false,
+ fixedWidth: true,
+ });
+ },
+ rejectLabel: _t("Skip"),
+ onReject: () => {
+ localStorage.setItem(lsKey, "true");
+ ToastStore.sharedInstance().dismissToast(toastKey);
+ },
+ },
+ component: GenericToast,
+ priority: 35,
+ });
+ }
+
if (space) {
const suggestedRooms = await this.fetchSuggestedRooms(space);
if (this._activeSpace === space) {
@@ -301,6 +374,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
return sortBy(parents, r => r.roomId)?.[0] || null;
}
+ public getKnownParents(roomId: string): Set {
+ return this.parentMap.get(roomId) || new Set();
+ }
+
public getSpaceFilteredRoomIds = (space: Room | null): Set => {
if (!space && spacesTweakAllRoomsEnabled) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
@@ -678,6 +755,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.matrixClient.on("accountData", this.onAccountData);
}
+ this.matrixClient.getCapabilities().then(capabilities => {
+ this._restrictedJoinRuleSupport = capabilities
+ ?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"];
+ });
+
await this.onSpaceUpdate(); // trigger an initial update
// restore selected state from last session if any and still valid
diff --git a/src/stores/VoiceRecordingStore.ts b/src/stores/VoiceRecordingStore.ts
index 81c19e7e82..df837fec88 100644
--- a/src/stores/VoiceRecordingStore.ts
+++ b/src/stores/VoiceRecordingStore.ts
@@ -17,7 +17,7 @@ limitations under the License.
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads";
-import { VoiceRecording } from "../voice/VoiceRecording";
+import { VoiceRecording } from "../audio/VoiceRecording";
interface IState {
recording?: VoiceRecording;
diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts
new file mode 100644
index 0000000000..e632ec6345
--- /dev/null
+++ b/src/utils/RoomUpgrade.ts
@@ -0,0 +1,93 @@
+/*
+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 { Room } from "matrix-js-sdk/src/models/room";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+
+import { inviteUsersToRoom } from "../RoomInvite";
+import Modal from "../Modal";
+import { _t } from "../languageHandler";
+import ErrorDialog from "../components/views/dialogs/ErrorDialog";
+import SpaceStore from "../stores/SpaceStore";
+
+export async function upgradeRoom(
+ room: Room,
+ targetVersion: string,
+ inviteUsers = false,
+ handleError = true,
+ updateSpaces = true,
+): Promise {
+ const cli = room.client;
+
+ let newRoomId: string;
+ try {
+ ({ replacement_room: newRoomId } = await cli.upgradeRoom(room.roomId, targetVersion));
+ } catch (e) {
+ if (!handleError) throw e;
+ console.error(e);
+
+ Modal.createTrackedDialog("Room Upgrade Error", "", ErrorDialog, {
+ title: _t('Error upgrading room'),
+ description: _t('Double check that your server supports the room version chosen and try again.'),
+ });
+ throw e;
+ }
+
+ // We have to wait for the js-sdk to give us the room back so
+ // we can more effectively abuse the MultiInviter behaviour
+ // which heavily relies on the Room object being available.
+ if (inviteUsers) {
+ const checkForUpgradeFn = async (newRoom: Room): Promise => {
+ // The upgradePromise should be done by the time we await it here.
+ if (newRoom.roomId !== newRoomId) return;
+
+ const toInvite = [
+ ...room.getMembersWithMembership("join"),
+ ...room.getMembersWithMembership("invite"),
+ ].map(m => m.userId).filter(m => m !== cli.getUserId());
+
+ if (toInvite.length > 0) {
+ // Errors are handled internally to this function
+ await inviteUsersToRoom(newRoomId, toInvite);
+ }
+
+ cli.removeListener('Room', checkForUpgradeFn);
+ };
+ cli.on('Room', checkForUpgradeFn);
+ }
+
+ if (updateSpaces) {
+ const parents = SpaceStore.instance.getKnownParents(room.roomId);
+ try {
+ for (const parentId of parents) {
+ const parent = cli.getRoom(parentId);
+ if (!parent?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())) continue;
+
+ const currentEv = parent.currentState.getStateEvents(EventType.SpaceChild, room.roomId);
+ await cli.sendStateEvent(parentId, EventType.SpaceChild, {
+ ...(currentEv?.getContent() || {}), // copy existing attributes like suggested
+ via: [cli.getDomain()],
+ }, newRoomId);
+ await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, room.roomId);
+ }
+ } catch (e) {
+ // These errors are not critical to the room upgrade itself
+ console.warn("Failed to update parent spaces during room upgrade", e);
+ }
+ }
+
+ return newRoomId;
+}
diff --git a/src/utils/space.tsx b/src/utils/space.tsx
index 38f6e348d7..c238a83bc2 100644
--- a/src/utils/space.tsx
+++ b/src/utils/space.tsx
@@ -16,10 +16,9 @@ limitations under the License.
import React 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 { calculateRoomVia } from "../utils/permalinks/Permalinks";
+import { calculateRoomVia } from "./permalinks/Permalinks";
import Modal from "../Modal";
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
@@ -30,8 +29,8 @@ import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
import InfoDialog from "../components/views/dialogs/InfoDialog";
import { showRoomInviteDialog } from "../RoomInvite";
-export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
- const userId = cli.getUserId();
+export const shouldShowSpaceSettings = (space: Room) => {
+ const userId = space.client.getUserId();
return space.getMyMembership() === "join"
&& (space.currentState.maySendStateEvent(EventType.RoomAvatar, userId)
|| space.currentState.maySendStateEvent(EventType.RoomName, userId)
@@ -48,20 +47,20 @@ export const makeSpaceParentEvent = (room: Room, canonical = false) => ({
state_key: room.roomId,
});
-export const showSpaceSettings = (cli: MatrixClient, space: Room) => {
+export const showSpaceSettings = (space: Room) => {
Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, {
- matrixClient: cli,
+ matrixClient: space.client,
space,
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
};
-export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => {
+export const showAddExistingRooms = async (space: Room) => {
return Modal.createTrackedDialog(
"Space Landing",
"Add Existing",
AddExistingToSpaceDialog,
{
- matrixClient: cli,
+ matrixClient: space.client,
onCreateRoomClick: showCreateNewRoom,
space,
},
@@ -69,7 +68,7 @@ export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => {
).finished;
};
-export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => {
+export const showCreateNewRoom = async (space: Room) => {
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
"Space Landing",
"Create Room",
diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js
index f415b85105..dea5bcefb7 100644
--- a/test/components/structures/MessagePanel-test.js
+++ b/test/components/structures/MessagePanel-test.js
@@ -312,8 +312,12 @@ describe('MessagePanel', function() {
it('should insert the read-marker in the right place', function() {
const res = TestUtils.renderIntoDocument(
- ,
+ ,
);
const tiles = TestUtils.scryRenderedComponentsWithType(
@@ -330,8 +334,12 @@ describe('MessagePanel', function() {
it('should show the read-marker that fall in summarised events after the summary', function() {
const melsEvents = mkMelsEvents();
const res = TestUtils.renderIntoDocument(
- ,
+ ,
);
const summary = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_EventListSummary');
@@ -348,8 +356,12 @@ describe('MessagePanel', function() {
it('should hide the read-marker at the end of summarised events', function() {
const melsEvents = mkMelsEventsOnly();
const res = TestUtils.renderIntoDocument(
- ,
+ ,
);
const summary = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_EventListSummary');
@@ -371,7 +383,10 @@ describe('MessagePanel', function() {
// first render with the RM in one place
let mp = ReactDOM.render(
- , parentDiv);
@@ -387,7 +402,10 @@ describe('MessagePanel', function() {
// now move the RM
mp = ReactDOM.render(
- , parentDiv);
diff --git a/test/end-to-end-tests/src/scenarios/directory.js b/test/end-to-end-tests/src/scenarios/directory.js
index 53b790c174..fffca2b05c 100644
--- a/test/end-to-end-tests/src/scenarios/directory.js
+++ b/test/end-to-end-tests/src/scenarios/directory.js
@@ -25,7 +25,7 @@ module.exports = async function roomDirectoryScenarios(alice, bob) {
console.log(" creating a public room and join through directory:");
const room = 'test';
await createRoom(alice, room);
- await changeRoomSettings(alice, { directory: true, visibility: "public_no_guests", alias: "#test" });
+ await changeRoomSettings(alice, { directory: true, visibility: "public", alias: "#test" });
await join(bob, room); //looks up room in directory
const bobMessage = "hi Alice!";
await sendMessage(bob, bobMessage);
diff --git a/test/end-to-end-tests/src/scenarios/lazy-loading.js b/test/end-to-end-tests/src/scenarios/lazy-loading.js
index 1b5d449af9..406f7b24a3 100644
--- a/test/end-to-end-tests/src/scenarios/lazy-loading.js
+++ b/test/end-to-end-tests/src/scenarios/lazy-loading.js
@@ -51,7 +51,7 @@ const charlyMsg2 = "how's it going??";
async function setupRoomWithBobAliceAndCharlies(alice, bob, charlies) {
await createRoom(bob, room);
- await changeRoomSettings(bob, { directory: true, visibility: "public_no_guests", alias });
+ await changeRoomSettings(bob, { directory: true, visibility: "public", alias });
// wait for alias to be set by server after clicking "save"
// so the charlies can join it.
await bob.delay(500);
diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js
index b40afe76bf..01431197a7 100644
--- a/test/end-to-end-tests/src/usecases/room-settings.js
+++ b/test/end-to-end-tests/src/usecases/room-settings.js
@@ -98,18 +98,14 @@ async function checkRoomSettings(session, expectedSettings) {
if (expectedSettings.visibility) {
session.log.step(`checks visibility is ${expectedSettings.visibility}`);
const radios = await session.queryAll(".mx_RoomSettingsDialog input[type=radio]");
- assert.equal(radios.length, 7);
- const inviteOnly = radios[0];
- const publicNoGuests = radios[1];
- const publicWithGuests = radios[2];
+ assert.equal(radios.length, 6);
+ const [inviteOnlyRoom, publicRoom] = radios;
let expectedRadio = null;
if (expectedSettings.visibility === "invite_only") {
- expectedRadio = inviteOnly;
- } else if (expectedSettings.visibility === "public_no_guests") {
- expectedRadio = publicNoGuests;
- } else if (expectedSettings.visibility === "public_with_guests") {
- expectedRadio = publicWithGuests;
+ expectedRadio = inviteOnlyRoom;
+ } else if (expectedSettings.visibility === "public") {
+ expectedRadio = publicRoom;
} else {
throw new Error(`unrecognized room visibility setting: ${expectedSettings.visibility}`);
}
@@ -165,17 +161,13 @@ async function changeRoomSettings(session, settings) {
if (settings.visibility) {
session.log.step(`sets visibility to ${settings.visibility}`);
const radios = await session.queryAll(".mx_RoomSettingsDialog label");
- assert.equal(radios.length, 7);
- const inviteOnly = radios[0];
- const publicNoGuests = radios[1];
- const publicWithGuests = radios[2];
+ assert.equal(radios.length, 6);
+ const [inviteOnlyRoom, publicRoom] = radios;
if (settings.visibility === "invite_only") {
- await inviteOnly.click();
- } else if (settings.visibility === "public_no_guests") {
- await publicNoGuests.click();
- } else if (settings.visibility === "public_with_guests") {
- await publicWithGuests.click();
+ await inviteOnlyRoom.click();
+ } else if (settings.visibility === "public") {
+ await publicRoom.click();
} else {
throw new Error(`unrecognized room visibility setting: ${settings.visibility}`);
}
diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts
index eb28a72d67..8855f4e470 100644
--- a/test/stores/SpaceStore-test.ts
+++ b/test/stores/SpaceStore-test.ts
@@ -52,32 +52,6 @@ const emitPromise = (e: EventEmitter, k: string | symbol) => new Promise(r => e.
const testUserId = "@test:user";
-let rooms = [];
-
-const mkRoom = (roomId: string) => {
- const room = mkStubRoom(roomId);
- room.currentState.getStateEvents.mockImplementation(mockStateEventImplementation([]));
- rooms.push(room);
- return room;
-};
-
-const mkSpace = (spaceId: string, children: string[] = []) => {
- const space = mkRoom(spaceId);
- space.isSpaceRoom.mockReturnValue(true);
- space.currentState.getStateEvents.mockImplementation(mockStateEventImplementation(children.map(roomId =>
- mkEvent({
- event: true,
- type: EventType.SpaceChild,
- room: spaceId,
- user: testUserId,
- skey: roomId,
- content: { via: [] },
- ts: Date.now(),
- }),
- )));
- return space;
-};
-
const getUserIdForRoomId = jest.fn();
// @ts-ignore
DMRoomMap.sharedInstance = { getUserIdForRoomId };
@@ -107,6 +81,32 @@ describe("SpaceStore", () => {
const store = SpaceStore.instance;
const client = MatrixClientPeg.get();
+ let rooms = [];
+
+ const mkRoom = (roomId: string) => {
+ const room = mkStubRoom(roomId, roomId, client);
+ room.currentState.getStateEvents.mockImplementation(mockStateEventImplementation([]));
+ rooms.push(room);
+ return room;
+ };
+
+ const mkSpace = (spaceId: string, children: string[] = []) => {
+ const space = mkRoom(spaceId);
+ space.isSpaceRoom.mockReturnValue(true);
+ space.currentState.getStateEvents.mockImplementation(mockStateEventImplementation(children.map(roomId =>
+ mkEvent({
+ event: true,
+ type: EventType.SpaceChild,
+ room: spaceId,
+ user: testUserId,
+ skey: roomId,
+ content: { via: [] },
+ ts: Date.now(),
+ }),
+ )));
+ return space;
+ };
+
const viewRoom = roomId => defaultDispatcher.dispatch({ action: "view_room", room_id: roomId }, true);
const run = async () => {
diff --git a/test/test-utils.js b/test/test-utils.js
index d75abc80f0..5e29fd242e 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -97,6 +97,7 @@ export function createTestClient() {
},
decryptEventIfNeeded: () => Promise.resolve(),
isUserIgnored: jest.fn().mockReturnValue(false),
+ getCapabilities: jest.fn().mockResolvedValue({}),
};
}
@@ -220,7 +221,7 @@ export function mkMessage(opts) {
return mkEvent(opts);
}
-export function mkStubRoom(roomId = null, name) {
+export function mkStubRoom(roomId = null, name, client) {
const stubTimeline = { getEvents: () => [] };
return {
roomId,
@@ -235,6 +236,7 @@ export function mkStubRoom(roomId = null, name) {
}),
getMembersWithMembership: jest.fn().mockReturnValue([]),
getJoinedMembers: jest.fn().mockReturnValue([]),
+ getJoinedMemberCount: jest.fn().mockReturnValue(1),
getMembers: jest.fn().mockReturnValue([]),
getPendingEvents: () => [],
getLiveTimeline: () => stubTimeline,
@@ -269,6 +271,8 @@ export function mkStubRoom(roomId = null, name) {
getCanonicalAlias: jest.fn(),
getAltAliases: jest.fn().mockReturnValue([]),
timeline: [],
+ getJoinRule: jest.fn().mockReturnValue("invite"),
+ client,
};
}
diff --git a/yarn.lock b/yarn.lock
index 5283f5778a..c576148e19 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3234,8 +3234,8 @@ eslint-config-google@^0.14.0:
integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main":
- version "0.3.2"
- resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/8529f1d77863db6327cf1a1a4fa65d06cc26f91b"
+ version "0.3.3"
+ resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/50d6bdf6704dd95016d5f1f824f00cac6eaa64e1"
eslint-plugin-react-hooks@^4.2.0:
version "4.2.0"
|