diff --git a/res/css/views/settings/_JoinRuleSettings.pcss b/res/css/views/settings/_JoinRuleSettings.pcss index 62debe28a13..fcd89f7bd92 100644 --- a/res/css/views/settings/_JoinRuleSettings.pcss +++ b/res/css/views/settings/_JoinRuleSettings.pcss @@ -74,3 +74,8 @@ limitations under the License. } } } + +.mx_JoinRuleSettings_labelledCheckbox { + font: var(--cpd-font-body-md-regular); + margin-top: var(--cpd-space-2x); +} diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index b17f177c13f..3c5fd814a57 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -14,8 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; -import { IJoinRuleEventContent, JoinRule, RestrictedAllowType, Room, EventType } from "matrix-js-sdk/src/matrix"; +import React, { ReactNode, useEffect, useState } from "react"; +import { + IJoinRuleEventContent, + JoinRule, + RestrictedAllowType, + Room, + EventType, + Visibility, +} from "matrix-js-sdk/src/matrix"; import StyledRadioGroup, { IDefinition } from "../elements/StyledRadioGroup"; import { _t } from "../../../languageHandler"; @@ -34,6 +41,7 @@ import { Action } from "../../../dispatcher/actions"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { doesRoomVersionSupport, PreferredRoomVersions } from "../../../utils/PreferredRoomVersions"; import SettingsStore from "../../../settings/SettingsStore"; +import LabelledCheckbox from "../elements/LabelledCheckbox"; export interface JoinRuleSettingsProps { room: Room; @@ -76,6 +84,22 @@ const JoinRuleSettings: React.FC = ({ ? content?.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id) : undefined; + const [isPublicKnockRoom, setIsPublicKnockRoom] = useState(false); + + useEffect(() => { + if (joinRule === JoinRule.Knock) { + cli.getRoomDirectoryVisibility(room.roomId) + .then(({ visibility }) => setIsPublicKnockRoom(visibility === Visibility.Public)) + .catch(onError); + } + }, [cli, joinRule, onError, room.roomId]); + + const onIsPublicKnockRoomChange = (checked: boolean): void => { + cli.setRoomDirectoryVisibility(room.roomId, checked ? Visibility.Public : Visibility.Private) + .then(() => setIsPublicKnockRoom(checked)) + .catch(onError); + }; + const editRestrictedRoomIds = async (): Promise => { let selected = restrictedAllowRoomIds; if (!selected?.length && SpaceStore.instance.activeSpaceRoom) { @@ -297,7 +321,22 @@ const JoinRuleSettings: React.FC = ({ {preferredKnockVersion && upgradeRequiredPill} ), - description: _t("People cannot join unless access is granted."), + description: ( + <> + {_t("People cannot join unless access is granted.")} + + + ), }); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c1772c76010..b26953189a5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1291,6 +1291,8 @@ "Space members": "Space members", "Ask to join": "Ask to join", "People cannot join unless access is granted.": "People cannot join unless access is granted.", + "Make this space visible in the public room directory.": "Make this space visible in the public room directory.", + "Make this room visible in the public room directory.": "Make this room visible in the public room directory.", "This room is in some spaces you're not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.": "This room is in some spaces you're not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.", "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.", "IRC (Experimental)": "IRC (Experimental)", @@ -2708,7 +2710,6 @@ "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.", - "Make this room visible in the public room directory.": "Make this room visible in the public room directory.", "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.", diff --git a/test/components/views/settings/JoinRuleSettings-test.tsx b/test/components/views/settings/JoinRuleSettings-test.tsx index 2b25da37116..0095bfa03d3 100644 --- a/test/components/views/settings/JoinRuleSettings-test.tsx +++ b/test/components/views/settings/JoinRuleSettings-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { fireEvent, render, screen, within } from "@testing-library/react"; +import { act, fireEvent, render, screen, within } from "@testing-library/react"; import { EventType, GuestAccess, @@ -25,6 +25,8 @@ import { Room, ClientEvent, RoomMember, + MatrixError, + Visibility, } from "matrix-js-sdk/src/matrix"; import { defer, IDeferred } from "matrix-js-sdk/src/utils"; @@ -51,6 +53,8 @@ describe("", () => { getProfileInfo: jest.fn(), invite: jest.fn().mockResolvedValue(undefined), isRoomEncrypted: jest.fn().mockReturnValue(false), + getRoomDirectoryVisibility: jest.fn(), + setRoomDirectoryVisibility: jest.fn(), }); const roomId = "!room:server.org"; const newRoomId = "!roomUpgraded:server.org"; @@ -270,10 +274,77 @@ describe("", () => { }); }); + describe("knock rooms directory visibility", () => { + const getCheckbox = () => screen.getByRole("checkbox"); + let room: Room; + + beforeEach(() => (room = new Room(roomId, client, userId))); + + describe("when join rule is knock", () => { + beforeEach(() => setRoomStateEvents(room, PreferredRoomVersions.KnockRooms, JoinRule.Knock)); + + it("should set the visibility to public", async () => { + jest.spyOn(client, "getRoomDirectoryVisibility").mockResolvedValue({ visibility: Visibility.Private }); + jest.spyOn(client, "setRoomDirectoryVisibility").mockResolvedValue({}); + getComponent({ room }); + fireEvent.click(getCheckbox()); + await act(async () => await flushPromises()); + expect(client.setRoomDirectoryVisibility).toHaveBeenCalledWith(roomId, Visibility.Public); + expect(getCheckbox()).toBeChecked(); + }); + + it("should set the visibility to private", async () => { + jest.spyOn(client, "getRoomDirectoryVisibility").mockResolvedValue({ visibility: Visibility.Public }); + jest.spyOn(client, "setRoomDirectoryVisibility").mockResolvedValue({}); + getComponent({ room }); + await act(async () => await flushPromises()); + fireEvent.click(getCheckbox()); + await act(async () => await flushPromises()); + expect(client.setRoomDirectoryVisibility).toHaveBeenCalledWith(roomId, Visibility.Private); + expect(getCheckbox()).not.toBeChecked(); + }); + + it("should call onError if setting visibility fails", async () => { + const error = new MatrixError(); + jest.spyOn(client, "getRoomDirectoryVisibility").mockResolvedValue({ visibility: Visibility.Private }); + jest.spyOn(client, "setRoomDirectoryVisibility").mockRejectedValue(error); + getComponent({ room }); + fireEvent.click(getCheckbox()); + await act(async () => await flushPromises()); + expect(getCheckbox()).not.toBeChecked(); + expect(defaultProps.onError).toHaveBeenCalledWith(error); + }); + }); + + describe("when the room version is unsupported and upgrade is enabled", () => { + it("should disable the checkbox", () => { + setRoomStateEvents(room, "6", JoinRule.Invite); + getComponent({ promptUpgrade: true, room }); + expect(getCheckbox()).toBeDisabled(); + }); + }); + + describe("when join rule is not knock", () => { + beforeEach(() => { + setRoomStateEvents(room, PreferredRoomVersions.KnockRooms, JoinRule.Invite); + getComponent({ room }); + }); + + it("should disable the checkbox", () => { + expect(getCheckbox()).toBeDisabled(); + }); + + it("should set the visibility to private by default", () => { + expect(getCheckbox()).not.toBeChecked(); + }); + }); + }); + it("should not show knock room join rule", async () => { jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); const room = new Room(newRoomId, client, userId); - getComponent({ room: room }); + setRoomStateEvents(room, PreferredRoomVersions.KnockRooms); + getComponent({ room }); expect(screen.queryByText("Ask to join")).not.toBeInTheDocument(); }); });