Allow creating knock rooms (#11182)
Signed-off-by: Charly Nguyen <charly.nguyen@nordeck.net>
This commit is contained in:
parent
01bd80fe59
commit
fd749172e1
13 changed files with 197 additions and 3 deletions
|
@ -44,6 +44,10 @@ limitations under the License.
|
|||
mask-position: center;
|
||||
background-color: $secondary-content;
|
||||
}
|
||||
|
||||
&.mx_JoinRuleDropdown_knock::before {
|
||||
content: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,4 +67,11 @@ limitations under the License.
|
|||
mask-image: url("$(res)/img/element-icons/group-members.svg");
|
||||
mask-size: contain;
|
||||
}
|
||||
|
||||
.mx_JoinRuleDropdown_icon {
|
||||
color: $secondary-content;
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
|
|
1
res/img/element-icons/ask-to-join.svg
Normal file
1
res/img/element-icons/ask-to-join.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="12" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M6.806 16c-1.211 0-2.242-.336-3.091-1.008-.85-.673-1.458-1.48-1.826-2.425L.217 8.25c-.206-.533-.265-.93-.179-1.192.087-.26.282-.391.585-.391.4 0 .714.12.941.358.227.239.411.542.552.908l.714 1.884c.01.033.097.094.26.183h.227V2.2c0-.233.084-.436.251-.608a.8.8 0 011.177 0 .858.858 0 01.244.608v5.467c0 .089.032.166.097.233a.307.307 0 00.454 0 .324.324 0 00.098-.233v-6.8c0-.234.084-.436.251-.609A.8.8 0 016.482 0a.8.8 0 01.592.258.843.843 0 01.252.609v6.8c0 .089.032.166.097.233a.307.307 0 00.455 0 .324.324 0 00.097-.233v-5.8c0-.234.084-.436.252-.609A.8.8 0 018.819 1a.8.8 0 01.592.258.843.843 0 01.252.609v5.8c0 .089.032.166.097.233a.307.307 0 00.455 0 .324.324 0 00.097-.233v-3.8c0-.234.084-.436.252-.609A.8.8 0 0111.156 3a.8.8 0 01.592.258.843.843 0 01.252.609v6.966c0 1.523-.495 2.764-1.485 3.725C9.525 15.52 8.289 16 6.806 16z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 932 B |
|
@ -286,6 +286,8 @@ function textForJoinRulesEvent(ev: MatrixEvent, client: MatrixClient, allowJSX:
|
|||
_t("%(senderDisplayName)s made the room invite only.", {
|
||||
senderDisplayName,
|
||||
});
|
||||
case JoinRule.Knock:
|
||||
return () => _t("%(senderDisplayName)s changed the join rule to ask to join.", { senderDisplayName });
|
||||
case JoinRule.Restricted:
|
||||
if (allowJSX) {
|
||||
return () => (
|
||||
|
|
|
@ -34,6 +34,7 @@ import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
|||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
interface IProps {
|
||||
type?: RoomType;
|
||||
|
@ -59,6 +60,7 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
private readonly askToJoinEnabled: boolean;
|
||||
private readonly supportsRestricted: boolean;
|
||||
private nameField = createRef<Field>();
|
||||
private aliasField = createRef<RoomAliasField>();
|
||||
|
@ -66,6 +68,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join");
|
||||
this.supportsRestricted = !!this.props.parentSpace;
|
||||
|
||||
let joinRule = JoinRule.Invite;
|
||||
|
@ -126,6 +129,10 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
opts.joinRule = JoinRule.Restricted;
|
||||
}
|
||||
|
||||
if (this.state.joinRule === JoinRule.Knock) {
|
||||
opts.joinRule = JoinRule.Knock;
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
|
@ -283,6 +290,14 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
{_t("You can change this at any time from room settings.")}
|
||||
</p>
|
||||
);
|
||||
} else if (this.state.joinRule === JoinRule.Knock) {
|
||||
publicPrivateLabel = (
|
||||
<p>
|
||||
{_t(
|
||||
"Anyone can request to join, but admins or moderators need to grant access. You can change this later.",
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
let e2eeSection: JSX.Element | undefined;
|
||||
|
@ -332,7 +347,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
let title: string;
|
||||
if (isVideoRoom) {
|
||||
title = _t("Create a video room");
|
||||
} else if (this.props.parentSpace) {
|
||||
} else if (this.props.parentSpace || this.state.joinRule === JoinRule.Knock) {
|
||||
title = _t("Create a room");
|
||||
} else {
|
||||
title = this.state.joinRule === JoinRule.Public ? _t("Create a public room") : _t("Create a private room");
|
||||
|
@ -365,6 +380,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
<JoinRuleDropdown
|
||||
label={_t("Room visibility")}
|
||||
labelInvite={_t("Private room (invite only)")}
|
||||
labelKnock={this.askToJoinEnabled ? _t("Ask to join") : undefined}
|
||||
labelPublic={_t("Public room")}
|
||||
labelRestricted={this.supportsRestricted ? _t("Visible to space members") : undefined}
|
||||
value={this.state.joinRule}
|
||||
|
|
|
@ -19,12 +19,14 @@ import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
|||
|
||||
import Dropdown from "./Dropdown";
|
||||
import { NonEmptyArray } from "../../../@types/common";
|
||||
import { Icon as AskToJoinIcon } from "../../../../res/img/element-icons/ask-to-join.svg";
|
||||
|
||||
interface IProps {
|
||||
value: JoinRule;
|
||||
label: string;
|
||||
width?: number;
|
||||
labelInvite: string;
|
||||
labelKnock?: string;
|
||||
labelPublic: string;
|
||||
labelRestricted?: string; // if omitted then this option will be hidden, e.g if unsupported
|
||||
onChange(value: JoinRule): void;
|
||||
|
@ -33,6 +35,7 @@ interface IProps {
|
|||
const JoinRuleDropdown: React.FC<IProps> = ({
|
||||
label,
|
||||
labelInvite,
|
||||
labelKnock,
|
||||
labelPublic,
|
||||
labelRestricted,
|
||||
value,
|
||||
|
@ -48,6 +51,17 @@ const JoinRuleDropdown: React.FC<IProps> = ({
|
|||
</div>,
|
||||
] as NonEmptyArray<ReactElement & { key: string }>;
|
||||
|
||||
if (labelKnock) {
|
||||
options.unshift(
|
||||
(
|
||||
<div key={JoinRule.Knock} className="mx_JoinRuleDropdown_knock">
|
||||
<AskToJoinIcon className="mx_Icon mx_Icon_16 mx_JoinRuleDropdown_icon" />
|
||||
{labelKnock}
|
||||
</div>
|
||||
) as ReactElement & { key: string },
|
||||
);
|
||||
}
|
||||
|
||||
if (labelRestricted) {
|
||||
options.unshift(
|
||||
(
|
||||
|
|
|
@ -222,6 +222,10 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
|
|||
});
|
||||
}
|
||||
|
||||
if (opts.joinRule === JoinRule.Knock) {
|
||||
createOpts.room_version = PreferredRoomVersions.KnockRooms;
|
||||
}
|
||||
|
||||
if (opts.parentSpace) {
|
||||
createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
|
||||
if (!opts.historyVisibility) {
|
||||
|
|
|
@ -527,6 +527,7 @@
|
|||
"%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgraded this room.",
|
||||
"%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s made the room public to whoever knows the link.",
|
||||
"%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s made the room invite only.",
|
||||
"%(senderDisplayName)s changed the join rule to ask to join.": "%(senderDisplayName)s changed the join rule to ask to join.",
|
||||
"%(senderDisplayName)s changed who can join this room. <a>View settings</a>.": "%(senderDisplayName)s changed who can join this room. <a>View settings</a>.",
|
||||
"%(senderDisplayName)s changed who can join this room.": "%(senderDisplayName)s changed who can join this room.",
|
||||
"%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s changed the join rule to %(rule)s",
|
||||
|
@ -1003,6 +1004,7 @@
|
|||
"Insert a trailing colon after user mentions at the start of a message": "Insert a trailing colon after user mentions at the start of a message",
|
||||
"Hide notification dot (only display counters badges)": "Hide notification dot (only display counters badges)",
|
||||
"Enable intentional mentions": "Enable intentional mentions",
|
||||
"Enable ask to join": "Enable ask to join",
|
||||
"Use a more compact 'Modern' layout": "Use a more compact 'Modern' layout",
|
||||
"Show a placeholder for removed messages": "Show a placeholder for removed messages",
|
||||
"Show join/leave messages (invites/removes/bans unaffected)": "Show join/leave messages (invites/removes/bans unaffected)",
|
||||
|
@ -2783,6 +2785,7 @@
|
|||
"Anyone will be able to find and join this room, not just members of <SpaceName/>.": "Anyone will be able to find and join this room, not just members of <SpaceName/>.",
|
||||
"Anyone will be able to find and join this room.": "Anyone will be able to find and join this room.",
|
||||
"Only people invited will be able to find and join this room.": "Only people invited will be able to find and join this room.",
|
||||
"Anyone can request to join, but admins or moderators need to grant access. You can change this later.": "Anyone can request to join, but admins or moderators need to grant access. You can change this later.",
|
||||
"You can't disable this later. The room will be encrypted but the embedded call will not.": "You can't disable this later. The room will be encrypted but the embedded call will not.",
|
||||
"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.",
|
||||
|
@ -2796,6 +2799,7 @@
|
|||
"Topic (optional)": "Topic (optional)",
|
||||
"Room visibility": "Room visibility",
|
||||
"Private room (invite only)": "Private room (invite only)",
|
||||
"Ask to join": "Ask to join",
|
||||
"Visible to space members": "Visible to space members",
|
||||
"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 video room": "Create video room",
|
||||
|
|
|
@ -556,6 +556,13 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
|||
["org.matrix.msc3952_intentional_mentions"],
|
||||
]),
|
||||
},
|
||||
"feature_ask_to_join": {
|
||||
default: false,
|
||||
displayName: _td("Enable ask to join"),
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Rooms,
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
},
|
||||
"useCompactLayout": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
displayName: _td("Use a more compact 'Modern' layout"),
|
||||
|
|
|
@ -23,6 +23,11 @@ limitations under the License.
|
|||
* Loosely follows https://spec.matrix.org/latest/rooms/#feature-matrix
|
||||
*/
|
||||
export class PreferredRoomVersions {
|
||||
/**
|
||||
* The room version to use when creating "knock" rooms.
|
||||
*/
|
||||
public static readonly KnockRooms = "7";
|
||||
|
||||
/**
|
||||
* The room version to use when creating "restricted" rooms.
|
||||
*/
|
||||
|
|
|
@ -36,6 +36,14 @@ describe("doesRoomVersionSupport", () => {
|
|||
expect(doesRoomVersionSupport("3.1", "2.2")).toBe(true); // newer
|
||||
});
|
||||
|
||||
it("should detect knock rooms in v7 and above", () => {
|
||||
expect(doesRoomVersionSupport("6", PreferredRoomVersions.KnockRooms)).toBe(false);
|
||||
expect(doesRoomVersionSupport("7", PreferredRoomVersions.KnockRooms)).toBe(true);
|
||||
expect(doesRoomVersionSupport("8", PreferredRoomVersions.KnockRooms)).toBe(true);
|
||||
expect(doesRoomVersionSupport("9", PreferredRoomVersions.KnockRooms)).toBe(true);
|
||||
expect(doesRoomVersionSupport("10", PreferredRoomVersions.KnockRooms)).toBe(true);
|
||||
});
|
||||
|
||||
it("should detect restricted rooms in v9 and v10", () => {
|
||||
// Dev note: we consider it a feature that v8 rooms have to upgrade considering the bug in v8.
|
||||
// https://spec.matrix.org/v1.3/rooms/v8/#redactions
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventType, MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, JoinRule, MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render } from "@testing-library/react";
|
||||
import { ReactElement } from "react";
|
||||
import { Mocked, mocked } from "jest-mock";
|
||||
|
@ -512,4 +512,63 @@ describe("TextForEvent", () => {
|
|||
).toMatchInlineSnapshot(`"Andy changed their display name and profile picture"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("textForJoinRulesEvent()", () => {
|
||||
type TestCase = [string, { result: string }];
|
||||
const testCases: TestCase[] = [
|
||||
[JoinRule.Public, { result: "@a made the room public to whoever knows the link." }],
|
||||
[JoinRule.Invite, { result: "@a made the room invite only." }],
|
||||
[JoinRule.Knock, { result: "@a changed the join rule to ask to join." }],
|
||||
[JoinRule.Restricted, { result: "@a changed who can join this room." }],
|
||||
];
|
||||
|
||||
it.each(testCases)("returns correct message when room join rule changed to %s", (joinRule, { result }) => {
|
||||
expect(
|
||||
textForEvent(
|
||||
new MatrixEvent({
|
||||
type: "m.room.join_rules",
|
||||
sender: "@a",
|
||||
content: {
|
||||
join_rule: joinRule,
|
||||
},
|
||||
state_key: "",
|
||||
}),
|
||||
mockClient,
|
||||
),
|
||||
).toEqual(result);
|
||||
});
|
||||
|
||||
it(`returns correct JSX message when room join rule changed to ${JoinRule.Restricted}`, () => {
|
||||
expect(
|
||||
textForEvent(
|
||||
new MatrixEvent({
|
||||
type: "m.room.join_rules",
|
||||
sender: "@a",
|
||||
content: {
|
||||
join_rule: JoinRule.Restricted,
|
||||
},
|
||||
state_key: "",
|
||||
}),
|
||||
mockClient,
|
||||
true,
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("returns correct default message", () => {
|
||||
expect(
|
||||
textForEvent(
|
||||
new MatrixEvent({
|
||||
type: "m.room.join_rules",
|
||||
sender: "@a",
|
||||
content: {
|
||||
join_rule: "a not implemented one",
|
||||
},
|
||||
state_key: "",
|
||||
}),
|
||||
mockClient,
|
||||
),
|
||||
).toEqual("@a changed the join rule to a not implemented one");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
18
test/__snapshots__/TextForEvent-test.ts.snap
Normal file
18
test/__snapshots__/TextForEvent-test.ts.snap
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TextForEvent textForJoinRulesEvent() returns correct JSX message when room join rule changed to restricted 1`] = `
|
||||
<span>
|
||||
<span>
|
||||
@a changed who can join this room.
|
||||
<AccessibleButton
|
||||
kind="link_inline"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
View settings
|
||||
</AccessibleButton>
|
||||
.
|
||||
</span>
|
||||
</span>
|
||||
`;
|
|
@ -16,10 +16,11 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, within } from "@testing-library/react";
|
||||
import { MatrixError, Preset, Visibility } from "matrix-js-sdk/src/matrix";
|
||||
import { JoinRule, MatrixError, Preset, Visibility } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import CreateRoomDialog from "../../../../src/components/views/dialogs/CreateRoomDialog";
|
||||
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
describe("<CreateRoomDialog />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
|
@ -208,6 +209,50 @@ describe("<CreateRoomDialog />", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("for a knock room", () => {
|
||||
it("should not have the option to create a knock room", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
getComponent();
|
||||
fireEvent.click(screen.getByLabelText("Room visibility"));
|
||||
|
||||
expect(screen.queryByRole("option", { name: "Ask to join" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should create a knock room", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "feature_ask_to_join");
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
await flushPromises();
|
||||
|
||||
const roomName = "Test Room Name";
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } });
|
||||
|
||||
fireEvent.click(screen.getByLabelText("Room visibility"));
|
||||
fireEvent.click(screen.getByRole("option", { name: "Ask to join" }));
|
||||
|
||||
fireEvent.click(screen.getByText("Create room"));
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByText("Create a room")).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Anyone can request to join, but admins or moderators need to grant access. You can change this later.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith(true, {
|
||||
createOpts: {
|
||||
name: roomName,
|
||||
},
|
||||
encryption: true,
|
||||
joinRule: JoinRule.Knock,
|
||||
parentSpace: undefined,
|
||||
roomType: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for a public room", () => {
|
||||
it("should set join rule to public defaultPublic is truthy", async () => {
|
||||
const onFinished = jest.fn();
|
||||
|
|
Loading…
Reference in a new issue