From f3ce26768812cf068c15891d758c5b4602eae9e1 Mon Sep 17 00:00:00 2001 From: ankur12-1610 Date: Sat, 9 Jul 2022 21:14:11 +0530 Subject: [PATCH 1/8] Added `feature_knocking` in Settings.tsx Signed-off-by: ankur12-1610 --- src/settings/Settings.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index c5f1511420c..0ac937005dc 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -256,6 +256,14 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_knocking": { + isFeature: true, + labsGroup: LabGroup.Rooms, + supportedLevels: LEVELS_FEATURE, + displayName: _td("Knocking"), + description: _td("Let users knock on a room to join it."), + default: false, + }, "feature_thread": { isFeature: true, labsGroup: LabGroup.Messaging, From 04af2d8a572c97071175e5dfe289965ecfe1e08d Mon Sep 17 00:00:00 2001 From: ankur12-1610 Date: Tue, 12 Jul 2022 19:09:36 +0530 Subject: [PATCH 2/8] Added knocking option in CreateRoomDialog Signed-off-by: ankur12-1610 --- res/css/views/dialogs/_JoinRuleDropdown.pcss | 5 +++++ src/components/views/dialogs/CreateRoomDialog.tsx | 11 ++++++++--- src/components/views/elements/JoinRuleDropdown.tsx | 5 +++++ src/i18n/strings/en_EN.json | 1 + 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/res/css/views/dialogs/_JoinRuleDropdown.pcss b/res/css/views/dialogs/_JoinRuleDropdown.pcss index b4d13909a77..ac6fe9215f5 100644 --- a/res/css/views/dialogs/_JoinRuleDropdown.pcss +++ b/res/css/views/dialogs/_JoinRuleDropdown.pcss @@ -54,6 +54,11 @@ limitations under the License. mask-size: contain; } + .mx_JoinRuleDropdown_knock::before { + mask-image: url('$(res)/img/element-icons/collapse-message.svg'); + mask-size: contain; + } + .mx_JoinRuleDropdown_public::before { mask-image: url('$(res)/img/globe.svg'); mask-size: 12px; diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 2217af93879..4650a806bf9 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -265,9 +265,13 @@ export default class CreateRoomDialog extends React.Component {

; } else if (this.state.joinRule === JoinRule.Invite) { publicPrivateLabel =

- { _t( - "Only people invited will be able to find and join this room.", - ) } + { _t("Only people invited will be able to find and join this room.") } +   + { _t("You can change this at any time from room settings.") } +

; + } else if (this.state.joinRule === JoinRule.Knock) { + publicPrivateLabel =

+ { _t("Anyone can knock on this room to join.") }   { _t("You can change this at any time from room settings.") }

; @@ -349,6 +353,7 @@ export default class CreateRoomDialog extends React.Component { { labelInvite } , +
+ { labelKnock } +
,
{ labelPublic }
, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5135e51da88..048e0724045 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2568,6 +2568,7 @@ "Topic (optional)": "Topic (optional)", "Room visibility": "Room visibility", "Private room (invite only)": "Private room (invite only)", + "Anyone can knock to join" : "Anyone can knock 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", From a4984b914918a80c9abcb04ecdc620652f596e88 Mon Sep 17 00:00:00 2001 From: ankur12-1610 Date: Sat, 23 Jul 2022 09:58:42 +0530 Subject: [PATCH 3/8] Added conditional rendering for the JoinRule DropDown based on labs feature Signed-off-by: ankur12-1610 --- src/components/views/dialogs/CreateRoomDialog.tsx | 9 +++++++-- src/components/views/elements/JoinRuleDropdown.tsx | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 4650a806bf9..003fb992970 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -20,6 +20,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { RoomType } from "matrix-js-sdk/src/@types/event"; import { JoinRule, Preset, Visibility } from "matrix-js-sdk/src/@types/partials"; +import SettingsStore from '../../../settings/SettingsStore'; import SdkConfig from '../../../SdkConfig'; import withValidation, { IFieldState } from '../elements/Validation'; import { _t } from '../../../languageHandler'; @@ -59,6 +60,7 @@ interface IState { export default class CreateRoomDialog extends React.Component { private readonly supportsRestricted: boolean; + private readonly knockingEnabled: boolean; private nameField = createRef(); private aliasField = createRef(); @@ -66,12 +68,15 @@ export default class CreateRoomDialog extends React.Component { super(props); this.supportsRestricted = !!this.props.parentSpace; + this.knockingEnabled = SettingsStore.getValue("feature_knocking"); let joinRule = JoinRule.Invite; if (this.props.defaultPublic) { joinRule = JoinRule.Public; } else if (this.supportsRestricted) { joinRule = JoinRule.Restricted; + } else if (this.knockingEnabled) { + joinRule = JoinRule.Knock; } this.state = { @@ -269,7 +274,7 @@ export default class CreateRoomDialog extends React.Component {   { _t("You can change this at any time from room settings.") }

; - } else if (this.state.joinRule === JoinRule.Knock) { + } else if (this.state.joinRule === JoinRule.Knock && this.knockingEnabled) { publicPrivateLabel =

{ _t("Anyone can knock on this room to join.") }   @@ -353,7 +358,7 @@ export default class CreateRoomDialog extends React.Component { { labelInvite } , -

- { labelKnock } -
,
{ labelPublic }
, @@ -56,6 +53,10 @@ const JoinRuleDropdown = ({ options.unshift(
{ labelRestricted }
); + } else if (labelKnock) { + options.unshift(
+ { labelKnock } +
); } return Date: Sun, 24 Jul 2022 16:23:27 +0530 Subject: [PATCH 4/8] Resolved the nits and added the knocking svg Signed-off-by: ankur12-1610 --- res/css/views/dialogs/_JoinRuleDropdown.pcss | 2 +- res/img/element-icons/knocking.svg | 3 +++ src/components/views/dialogs/CreateRoomDialog.tsx | 6 ++---- src/components/views/elements/JoinRuleDropdown.tsx | 2 +- src/i18n/strings/en_EN.json | 5 ++++- 5 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 res/img/element-icons/knocking.svg diff --git a/res/css/views/dialogs/_JoinRuleDropdown.pcss b/res/css/views/dialogs/_JoinRuleDropdown.pcss index ac6fe9215f5..d3d8a1669f5 100644 --- a/res/css/views/dialogs/_JoinRuleDropdown.pcss +++ b/res/css/views/dialogs/_JoinRuleDropdown.pcss @@ -55,7 +55,7 @@ limitations under the License. } .mx_JoinRuleDropdown_knock::before { - mask-image: url('$(res)/img/element-icons/collapse-message.svg'); + mask-image: url('$(res)/img/element-icons/knocking.svg'); mask-size: contain; } diff --git a/res/img/element-icons/knocking.svg b/res/img/element-icons/knocking.svg new file mode 100644 index 00000000000..718a5b6a910 --- /dev/null +++ b/res/img/element-icons/knocking.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 003fb992970..7c4ae328289 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -75,8 +75,6 @@ export default class CreateRoomDialog extends React.Component { joinRule = JoinRule.Public; } else if (this.supportsRestricted) { joinRule = JoinRule.Restricted; - } else if (this.knockingEnabled) { - joinRule = JoinRule.Knock; } this.state = { @@ -276,7 +274,7 @@ export default class CreateRoomDialog extends React.Component {

; } else if (this.state.joinRule === JoinRule.Knock && this.knockingEnabled) { publicPrivateLabel =

- { _t("Anyone can knock on this room to join.") } + { _t("This room type requires users to be granted access in order to join.") }   { _t("You can change this at any time from room settings.") }

; @@ -358,7 +356,7 @@ export default class CreateRoomDialog extends React.Component { Learn more.": "Threads help keep conversations on-topic and easy to track. Learn more.", @@ -2555,6 +2557,7 @@ "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 .", "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.", + "This room type requires users to be granted access in order to join.": "This room type requires users to be granted access in order to join.", "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.", @@ -2568,7 +2571,7 @@ "Topic (optional)": "Topic (optional)", "Room visibility": "Room visibility", "Private room (invite only)": "Private room (invite only)", - "Anyone can knock to join" : "Anyone can knock to join", + "Ask to join (Anyone can knock to join)": "Ask to join (Anyone can knock 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", From aaecd1850c775e11d31ce03204ce1956103e7d97 Mon Sep 17 00:00:00 2001 From: ankur12-1610 Date: Mon, 1 Aug 2022 12:33:18 +0530 Subject: [PATCH 5/8] Added `Ask to join` option in Room Settings Signed-off-by: ankur12-1610 --- .../views/settings/JoinRuleSettings.tsx | 15 +++++++++++++++ src/i18n/strings/en_EN.json | 2 ++ src/utils/PreferredRoomVersions.ts | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index 49295f0e83b..ea70085280a 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -24,6 +24,7 @@ import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import RoomAvatar from "../avatars/RoomAvatar"; import SpaceStore from "../../../stores/spaces/SpaceStore"; +import SettingsStore from "../../../settings/SettingsStore"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Modal from "../../../Modal"; import ManageRestrictedJoinRuleDialog from "../dialogs/ManageRestrictedJoinRuleDialog"; @@ -49,6 +50,8 @@ interface IProps { const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeChange, closeSettingsFn }: IProps) => { const cli = room.client; + const roomSupportsKnocking = doesRoomVersionSupport(room.getVersion(), PreferredRoomVersions.KnockingRooms); + const roomSupportsRestricted = doesRoomVersionSupport(room.getVersion(), PreferredRoomVersions.RestrictedRooms); const preferredRestrictionVersion = !roomSupportsRestricted && promptUpgrade ? PreferredRoomVersions.RestrictedRooms @@ -56,6 +59,8 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh const disabled = !room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, cli); + const knockingEnabled = SettingsStore.getValue("feature_knocking"); + const [content, setContent] = useLocalEcho( () => room.currentState.getStateEvents(EventType.RoomJoinRules, "")?.getContent(), content => cli.sendStateEvent(room.roomId, EventType.RoomJoinRules, content, ""), @@ -89,6 +94,11 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh label: _t("Private (invite only)"), description: _t("Only invited people can join."), checked: joinRule === JoinRule.Invite || (joinRule === JoinRule.Restricted && !restrictedAllowRoomIds?.length), + }, { + value: JoinRule.Knock, + label: _t("Ask to join"), + description: _t("Requires users to be granted access in order to join"), + checked: joinRule === JoinRule.Knock && knockingEnabled && roomSupportsKnocking, }, { value: JoinRule.Public, label: _t("Public"), @@ -98,6 +108,11 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh , }]; + if (!knockingEnabled || !roomSupportsKnocking) { + definitions.splice(1, 1); // removes the knock option if the room isn't compatible for the same + definitions[0].checked = true; //makes invite only room as default + } + if (roomSupportsRestricted || preferredRestrictionVersion || joinRule === JoinRule.Restricted) { let upgradeRequiredPill; if (preferredRestrictionVersion) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4b25e821d51..e595283ee7b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1328,6 +1328,8 @@ "Integration manager": "Integration manager", "Private (invite only)": "Private (invite only)", "Only invited people can join.": "Only invited people can join.", + "Ask to join": "Ask to join", + "Requires users to be granted access in order to join": "Requires users to be granted access in order to join", "Anyone can find and join.": "Anyone can find and join.", "Upgrade required": "Upgrade required", "& %(count)s more|other": "& %(count)s more", diff --git a/src/utils/PreferredRoomVersions.ts b/src/utils/PreferredRoomVersions.ts index 2dc269da6c2..377c6dda100 100644 --- a/src/utils/PreferredRoomVersions.ts +++ b/src/utils/PreferredRoomVersions.ts @@ -28,6 +28,11 @@ export class PreferredRoomVersions { */ public static readonly RestrictedRooms = "9"; + /** + * The room version to use when creating "knocking" rooms. + */ + public static readonly KnockingRooms = "9"; + private constructor() { // readonly, static, class } From 23e9f63d4c0c33e8224bae6fde48b97b74edfd8a Mon Sep 17 00:00:00 2001 From: ankur12-1610 Date: Wed, 10 Aug 2022 13:17:24 +0530 Subject: [PATCH 6/8] Added iterative method to splice the definitions --- src/components/views/settings/JoinRuleSettings.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index ea70085280a..3b156f3d955 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -109,7 +109,13 @@ const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeCh }]; if (!knockingEnabled || !roomSupportsKnocking) { - definitions.splice(1, 1); // removes the knock option if the room isn't compatible for the same + // removes the knock option if the room isn't compatible for the same + for (let i = 0; i < definitions.length; i++) { + if (definitions[i].value === JoinRule.Knock) { + definitions.splice(i, 1); + break; + } + } definitions[0].checked = true; //makes invite only room as default } From 82993af25a3e697f0b37978889c912df7a2ee803 Mon Sep 17 00:00:00 2001 From: ankur12-1610 Date: Tue, 16 Aug 2022 13:45:23 +0530 Subject: [PATCH 7/8] Added cypress test for the implemented milestone --- cypress/e2e/knocking/knocking.spec.ts | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 cypress/e2e/knocking/knocking.spec.ts diff --git a/cypress/e2e/knocking/knocking.spec.ts b/cypress/e2e/knocking/knocking.spec.ts new file mode 100644 index 00000000000..800596bf179 --- /dev/null +++ b/cypress/e2e/knocking/knocking.spec.ts @@ -0,0 +1,75 @@ +/* +Copyright 2022 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 { SynapseInstance } from "../../plugins/synapsedocker"; +import { MatrixClient } from "../../global"; +import Chainable = Cypress.Chainable; + +function openCreateRoomDialog(): Chainable> { + cy.get('[aria-label="Add room"]').click(); + cy.get('.mx_ContextualMenu [aria-label="New room"]').click(); + return cy.get(".mx_CreateRoomDialog"); +} + +describe("Knocking", () => { + let synapse: SynapseInstance; + + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + + cy.initTestUser(synapse, "Tom"); + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + }); + + it("should be able to create a room with knock JoinRule", () => { + // Enables labs flag feature + cy.enableLabsFeature("feature_knocking"); + const name = "Test room 1"; + const topic = "This is a test room"; + + // Create a room with knock JoinRule + openCreateRoomDialog().within(() => { + cy.get('[label="Name"]').type(name); + cy.get('[label="Topic (optional)"]').type(topic); + cy.get(".mx_JoinRuleDropdown").click(); + cy.get(".mx_JoinRuleDropdown_knock").click(); + cy.startMeasuring("from-submit-to-room"); + cy.get(".mx_Dialog_primary").click(); + }); + + // Change room settings + cy.openRoomSettings("Security & Privacy"); + cy.get(".mx_StyledRadioButton_content").contains("Ask to join").click(); + cy.closeDialog(); + + //Check if the room settings are visible if labs flag is disabled + cy.openUserSettings("Labs").within(() => { + //disables labs flag feature + cy.get("[aria-label='Knocking']").click(); + }); + cy.closeDialog(); + //the default joinRule is set to Private (invite only) + cy.openRoomSettings("Security & Privacy"); + + cy.stopMeasuring("from-submit-to-room"); + cy.get(".mx_RoomHeader_nametext").contains(name); + cy.get(".mx_RoomHeader_topic").contains(topic); + }); +}); From 8e7b212f72becb27e11434f6f1dce2b249b655ba Mon Sep 17 00:00:00 2001 From: ankur12-1610 Date: Tue, 16 Aug 2022 13:51:17 +0530 Subject: [PATCH 8/8] Resolved linting errors --- cypress/e2e/knocking/knocking.spec.ts | 12 ++++++++---- cypress/support/labs.ts | 8 ++++++++ src/TextForEvent.tsx | 4 ++++ src/components/views/dialogs/CreateRoomDialog.tsx | 3 +++ src/i18n/strings/en_EN.json | 1 + 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/knocking/knocking.spec.ts b/cypress/e2e/knocking/knocking.spec.ts index 800596bf179..a2f9e9ea3bb 100644 --- a/cypress/e2e/knocking/knocking.spec.ts +++ b/cypress/e2e/knocking/knocking.spec.ts @@ -14,7 +14,6 @@ limitations under the License. /// import { SynapseInstance } from "../../plugins/synapsedocker"; -import { MatrixClient } from "../../global"; import Chainable = Cypress.Chainable; function openCreateRoomDialog(): Chainable> { @@ -54,19 +53,24 @@ describe("Knocking", () => { cy.get(".mx_Dialog_primary").click(); }); - // Change room settings + // The room settings initially are set to Ask to join cy.openRoomSettings("Security & Privacy"); - cy.get(".mx_StyledRadioButton_content").contains("Ask to join").click(); cy.closeDialog(); //Check if the room settings are visible if labs flag is disabled cy.openUserSettings("Labs").within(() => { //disables labs flag feature cy.get("[aria-label='Knocking']").click(); + // cy.disableLabsFeature("feature_knocking"); }); cy.closeDialog(); - //the default joinRule is set to Private (invite only) + + //the default joinRule is set to Private (invite only) when the labs flag is disabled cy.openRoomSettings("Security & Privacy"); + cy.closeDialog(); + + // Click the expand link button to get more detailed view + cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click(); cy.stopMeasuring("from-submit-to-room"); cy.get(".mx_RoomHeader_nametext").contains(name); diff --git a/cypress/support/labs.ts b/cypress/support/labs.ts index 3fff154e140..16e6f038cde 100644 --- a/cypress/support/labs.ts +++ b/cypress/support/labs.ts @@ -28,6 +28,7 @@ declare global { * @param feature labsFeature to enable (e.g. "feature_spotlight") */ enableLabsFeature(feature: string): Chainable; + disableLabsFeature(feature: string): Chainable; } } } @@ -38,5 +39,12 @@ Cypress.Commands.add("enableLabsFeature", (feature: string): Chainable => }).then(() => null); }); +Cypress.Commands.add("disableLabsFeature", (feature: string): Chainable => { + return cy.window({ log: false }).then(win => { + win.localStorage.removeItem(`mx_labs_feature_${feature}`); + win.localStorage.setItem(`mx_labs_feature_${feature}`, "false"); + }).then(() => null); +}); + // Needed to make this file a module export { }; diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 9310391e3e2..c4fefc1e851 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -235,6 +235,10 @@ function textForJoinRulesEvent(ev: MatrixEvent, allowJSX: boolean): () => Render return () => _t('%(senderDisplayName)s made the room invite only.', { senderDisplayName, }); + case JoinRule.Knock: + return () => _t('%(senderDisplayName)s made the room knock only.', { + senderDisplayName, + }); case JoinRule.Restricted: if (allowJSX) { return () => diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 7c4ae328289..53364d3f3be 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -123,6 +123,9 @@ export default class CreateRoomDialog extends React.Component { opts.joinRule = JoinRule.Restricted; } + if (this.state.joinRule === JoinRule.Knock) { + opts.joinRule = JoinRule.Knock; + } return opts; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e595283ee7b..06f9d4aa6d6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -504,6 +504,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 made the room knock only.": "%(senderDisplayName)s made the room knock only.", "%(senderDisplayName)s changed who can join this room. View settings.": "%(senderDisplayName)s changed who can join this room. View settings.", "%(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",