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

Commit

Permalink
Allow creating knock rooms
Browse files Browse the repository at this point in the history
Signed-off-by: Charly Nguyen <charly.nguyen@nordeck.net>
  • Loading branch information
Charly Nguyen committed Jul 6, 2023
1 parent 902263d commit 333d929
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 2 deletions.
11 changes: 11 additions & 0 deletions res/css/views/dialogs/_JoinRuleDropdown.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ limitations under the License.
mask-position: center;
background-color: $secondary-content;
}

&.mx_JoinRuleDropdown_knock::before {
content: normal;
}
}
}

Expand All @@ -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 change: 1 addition & 0 deletions res/img/element-icons/ask-to-join.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion src/components/views/dialogs/CreateRoomDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -59,13 +60,15 @@ 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>();

public constructor(props: IProps) {
super(props);

this.askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join");
this.supportsRestricted = !!this.props.parentSpace;

let joinRule = JoinRule.Invite;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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}
Expand Down
14 changes: 14 additions & 0 deletions src/components/views/elements/JoinRuleDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,6 +35,7 @@ interface IProps {
const JoinRuleDropdown: React.FC<IProps> = ({
label,
labelInvite,
labelKnock,
labelPublic,
labelRestricted,
value,
Expand All @@ -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(
(
Expand Down
4 changes: 4 additions & 0 deletions src/createRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,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)",
Expand Down Expand Up @@ -2786,6 +2787,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.",
Expand All @@ -2799,6 +2801,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",
Expand Down
7 changes: 7 additions & 0 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
5 changes: 5 additions & 0 deletions src/utils/PreferredRoomVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
8 changes: 8 additions & 0 deletions test/PreferredRoomVersions-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 46 additions & 1 deletion test/components/views/dialogs/CreateRoomDialog-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 333d929

Please sign in to comment.