diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss
index 241c921f20..d6ae0bb739 100644
--- a/res/css/structures/_RoomDirectory.scss
+++ b/res/css/structures/_RoomDirectory.scss
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2020 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.
@@ -45,7 +46,7 @@ limitations under the License.
}
.mx_RoomDirectory_listheader {
- display: flex;
+ display: block;
margin-top: 13px;
margin-bottom: 13px;
}
diff --git a/res/css/views/directory/_NetworkDropdown.scss b/res/css/views/directory/_NetworkDropdown.scss
index d402f6c48f..3dc3a99dd4 100644
--- a/res/css/views/directory/_NetworkDropdown.scss
+++ b/res/css/views/directory/_NetworkDropdown.scss
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2020 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.
@@ -15,70 +16,143 @@ limitations under the License.
*/
.mx_NetworkDropdown {
+ height: 32px;
position: relative;
-}
+ width: max-content;
+ padding-right: 32px;
+ margin-left: auto;
+ margin-right: 9px;
+ margin-top: 12px;
-.mx_NetworkDropdown_input {
- position: relative;
- border-radius: 3px;
- border: 1px solid $strong-input-border-color;
- font-weight: 300;
- font-size: 13px;
- user-select: none;
-}
-
-.mx_NetworkDropdown_arrow {
- border-color: $primary-fg-color transparent transparent;
- border-style: solid;
- border-width: 5px 5px 0;
- display: block;
- height: 0;
- position: absolute;
- right: 10px;
- top: 16px;
- width: 0;
-}
-
-.mx_NetworkDropdown_networkoption {
- height: 37px;
- line-height: 37px;
- padding-left: 8px;
- padding-right: 8px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
-
-.mx_NetworkDropdown_networkoption img {
- margin: 5px;
- width: 25px;
- vertical-align: middle;
-}
-
-input.mx_NetworkDropdown_networkoption, input.mx_NetworkDropdown_networkoption:focus {
- border: 0;
- padding-top: 0;
- padding-bottom: 0;
+ .mx_AccessibleButton {
+ width: max-content;
+ }
}
.mx_NetworkDropdown_menu {
- position: absolute;
- left: -1px;
- right: -1px;
- top: 100%;
- z-index: 2;
+ //position: absolute;
+ //left: -1px;
+ //right: -1px;
+ //top: 100%;
+ //z-index: 2;
+ width: 204px;
margin: 0;
- padding: 0px;
- border-radius: 3px;
+ box-sizing: border-box;
+ border-radius: 4px;
border: 1px solid $accent-color;
background-color: $primary-bg-color;
}
-.mx_NetworkDropdown_menu .mx_NetworkDropdown_networkoption:hover {
- background-color: $focus-bg-color;
-}
-
.mx_NetworkDropdown_menu_network {
font-weight: bold;
}
+.mx_NetworkDropdown_server {
+ padding: 12px 0;
+ border-bottom: 1px solid $input-darker-fg-color;
+
+ .mx_NetworkDropdown_server_title {
+ padding: 0 10px;
+ font-size: 15px;
+ font-weight: 600;
+ line-height: 20px;
+ margin-bottom: 4px;
+
+ // remove server button
+ .mx_AccessibleButton {
+ position: absolute;
+ display: inline;
+ right: 0;
+
+ &::before {
+ content: "";
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ right: 12px;
+ top: 4px;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ mask-image: url('$(res)/img/feather-customised/x.svg');
+ background-color: $notice-primary-color;
+ }
+ }
+ }
+
+ .mx_NetworkDropdown_server_subtitle {
+ padding: 0 10px;
+ font-size: 10px;
+ line-height: 14px;
+ margin-top: -4px;
+ margin-bottom: 4px;
+ color: $muted-fg-color;
+ }
+
+ .mx_NetworkDropdown_server_network {
+ font-size: 12px;
+ line-height: 16px;
+ padding: 4px 10px;
+ cursor: pointer;
+ position: relative;
+
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ &[aria-checked=true]::after {
+ content: "";
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ right: 10px;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ mask-image: url('$(res)/img/feather-customised/check.svg');
+ background-color: $input-valid-border-color;
+ }
+ }
+}
+
+.mx_NetworkDropdown_server_add,
+.mx_NetworkDropdown_server_network {
+ &:hover {
+ background-color: $header-panel-bg-color;
+ }
+}
+
+.mx_NetworkDropdown_server_add {
+ padding: 16px 10px 16px 32px;
+ position: relative;
+
+ &::before {
+ content: "";
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ left: 7px;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ mask-image: url('$(res)/img/feather-customised/plus.svg');
+ background-color: $muted-fg-color;
+ }
+}
+
+.mx_NetworkDropdown_handle {
+ position: relative;
+
+ &::after {
+ content: "";
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ right: -28px; // - (24 + 4)
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+ background-color: $primary-fg-color;
+ }
+}
diff --git a/res/css/views/elements/_DirectorySearchBox.scss b/res/css/views/elements/_DirectorySearchBox.scss
index ef944f6fa0..75ef3fbabd 100644
--- a/res/css/views/elements/_DirectorySearchBox.scss
+++ b/res/css/views/elements/_DirectorySearchBox.scss
@@ -18,7 +18,6 @@ limitations under the License.
display: flex;
padding-left: 9px;
padding-right: 9px;
- margin: 0 5px 0 0 !important;
}
.mx_DirectorySearchBox_joinButton {
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index a0b9a8fe57..0099af8398 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -600,9 +600,8 @@ export default createReactClass({
break;
case 'view_room_directory': {
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
- Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
- config: this.props.config,
- }, 'mx_RoomDirectory_dialogWrapper');
+ Modal.createTrackedDialog('Room directory', '', RoomDirectory, {},
+ 'mx_RoomDirectory_dialogWrapper', false, true);
// View the welcome or home page if we need something to look at
this._viewSomethingBehindModal();
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 3c3d67578e..ddc172fca7 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -28,6 +28,7 @@ import { _t } from '../../languageHandler';
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
import Analytics from '../../Analytics';
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
+import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
const MAX_NAME_LENGTH = 80;
const MAX_TOPIC_LENGTH = 160;
@@ -40,25 +41,17 @@ export default createReactClass({
displayName: 'RoomDirectory',
propTypes: {
- config: PropTypes.object,
onFinished: PropTypes.func.isRequired,
},
- getDefaultProps: function() {
- return {
- config: {},
- };
- },
-
getInitialState: function() {
return {
publicRooms: [],
loading: true,
protocolsLoading: true,
error: null,
- instanceId: null,
- includeAll: false,
- roomServer: null,
+ instanceId: undefined,
+ roomServer: MatrixClientPeg.getHomeserverName(),
filterString: null,
};
},
@@ -98,6 +91,10 @@ export default createReactClass({
});
},
+ componentDidMount: function() {
+ this.refreshRoomList();
+ },
+
componentWillUnmount: function() {
if (this.filterTimeout) {
clearTimeout(this.filterTimeout);
@@ -130,10 +127,10 @@ export default createReactClass({
if (my_server != MatrixClientPeg.getHomeserverName()) {
opts.server = my_server;
}
- if (this.state.instanceId) {
- opts.third_party_instance_id = this.state.instanceId;
- } else if (this.state.includeAll) {
+ if (this.state.instanceId === ALL_ROOMS) {
opts.include_all_networks = true;
+ } else if (this.state.instanceId) {
+ opts.third_party_instance_id = this.state.instanceId;
}
if (this.nextBatch) opts.since = this.nextBatch;
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string };
@@ -247,7 +244,7 @@ export default createReactClass({
}
},
- onOptionChange: function(server, instanceId, includeAll) {
+ onOptionChange: function(server, instanceId) {
// clear next batch so we don't try to load more rooms
this.nextBatch = null;
this.setState({
@@ -257,7 +254,6 @@ export default createReactClass({
publicRooms: [],
roomServer: server,
instanceId: instanceId,
- includeAll: includeAll,
error: null,
}, this.refreshRoomList);
// We also refresh the room list each time even though this
@@ -305,7 +301,7 @@ export default createReactClass({
onJoinFromSearchClick: function(alias) {
// If we don't have a particular instance id selected, just show that rooms alias
- if (!this.state.instanceId) {
+ if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
// If the user specified an alias without a domain, add on whichever server is selected
// in the dropdown
if (alias.indexOf(':') == -1) {
@@ -587,7 +583,7 @@ export default createReactClass({
}
let placeholder = _t('Find a room…');
- if (!this.state.instanceId) {
+ if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
placeholder = _t("Find a room… (e.g. %(exampleRoom)s)", {exampleRoom: "#example:" + this.state.roomServer});
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
@@ -604,10 +600,18 @@ export default createReactClass({
listHeader =
+
-
;
}
const explanation =
diff --git a/src/components/views/dialogs/TextInputDialog.js b/src/components/views/dialogs/TextInputDialog.js
index 0ffc072cc0..4b21e8206e 100644
--- a/src/components/views/dialogs/TextInputDialog.js
+++ b/src/components/views/dialogs/TextInputDialog.js
@@ -28,9 +28,11 @@ export default createReactClass({
PropTypes.string,
]),
value: PropTypes.string,
+ placeholder: PropTypes.string,
button: PropTypes.string,
focus: PropTypes.bool,
onFinished: PropTypes.func.isRequired,
+ hasCancel: PropTypes.bool,
},
getDefaultProps: function() {
@@ -39,6 +41,7 @@ export default createReactClass({
value: "",
description: "",
focus: true,
+ hasCancel: true,
};
},
@@ -80,13 +83,17 @@ export default createReactClass({
className="mx_TextInputDialog_input"
defaultValue={this.props.value}
autoFocus={this.props.focus}
+ placeholder={this.props.placeholder}
size="64" />
-
+ onCancel={this.onCancel}
+ hasCancel={this.props.hasCancel}
+ />
);
},
diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js
index cb6a015d86..229f80ef29 100644
--- a/src/components/views/directory/NetworkDropdown.js
+++ b/src/components/views/directory/NetworkDropdown.js
@@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
+Copyright 2020 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.
@@ -17,239 +18,225 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
+
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
+import {ContextMenu, useContextMenu, ContextMenuButton, MenuItemRadio, MenuItem} from "../../structures/ContextMenu";
+import {_t} from "../../../languageHandler";
+import SdkConfig from "../../../SdkConfig";
+import {useSettingValue} from "../../../hooks/useSettings";
+import * as sdk from "../../../index";
+import Modal from "../../../Modal";
+import SettingsStore from "../../../settings/SettingsStore";
+import AccessibleButton from "../elements/AccessibleButton";
-const DEFAULT_ICON_URL = require("../../../../res/img/network-matrix.svg");
+export const ALL_ROOMS = Symbol("ALL_ROOMS");
-export default class NetworkDropdown extends React.Component {
- constructor(props) {
- super(props);
+const SETTING_NAME = "room_directory_servers";
- this.dropdownRootElement = null;
- this.ignoreEvent = null;
+const inPlaceOf = (elementRect) => ({
+ right: window.innerWidth - elementRect.right,
+ top: elementRect.top,
+ chevronOffset: 0,
+ chevronFace: "none",
+});
- this.onInputClick = this.onInputClick.bind(this);
- this.onRootClick = this.onRootClick.bind(this);
- this.onDocumentClick = this.onDocumentClick.bind(this);
- this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
- this.onInputKeyUp = this.onInputKeyUp.bind(this);
- this.collectRoot = this.collectRoot.bind(this);
- this.collectInputTextBox = this.collectInputTextBox.bind(this);
+// This dropdown sources homeservers from three places:
+// + your currently connected homeserver
+// + homeservers in config.json["roomDirectory"]
+// + homeservers in SettingsStore["room_directory_servers"]
+// if a server exists in multiple, only keep the top-most entry.
- this.inputTextBox = null;
+const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, selectedInstanceId}) => {
+ const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
+ const userDefinedServers = useSettingValue(SETTING_NAME);
- const server = MatrixClientPeg.getHomeserverName();
- this.state = {
- expanded: false,
- selectedServer: server,
- selectedInstanceId: null,
- includeAllNetworks: false,
+ const handlerFactory = (server, instanceId) => {
+ return () => {
+ onOptionChange(server, instanceId);
+ closeMenu();
};
- }
+ };
- componentWillMount() {
- // Listen for all clicks on the document so we can close the
- // menu when the user clicks somewhere else
- document.addEventListener('click', this.onDocumentClick, false);
+ // we either show the button or the dropdown in its place.
+ let content;
+ if (menuDisplayed) {
+ const config = SdkConfig.get();
+ const roomDirectory = config.roomDirectory || {};
- // fire this now so the defaults can be set up
- const {selectedServer, selectedInstanceId, includeAllNetworks} = this.state;
- this.props.onOptionChange(selectedServer, selectedInstanceId, includeAllNetworks);
- }
+ const hsName = MatrixClientPeg.getHomeserverName();
+ const configServers = new Set(roomDirectory.servers);
- componentWillUnmount() {
- document.removeEventListener('click', this.onDocumentClick, false);
- }
-
- componentDidUpdate() {
- if (this.state.expanded && this.inputTextBox) {
- this.inputTextBox.focus();
- }
- }
-
- onDocumentClick(ev) {
- // Close the dropdown if the user clicks anywhere that isn't
- // within our root element
- if (ev !== this.ignoreEvent) {
- this.setState({
- expanded: false,
- });
- }
- }
-
- onRootClick(ev) {
- // This captures any clicks that happen within our elements,
- // such that we can then ignore them when they're seen by the
- // click listener on the document handler, ie. not close the
- // dropdown immediately after opening it.
- // NB. We can't just stopPropagation() because then the event
- // doesn't reach the React onClick().
- this.ignoreEvent = ev;
- }
-
- onInputClick(ev) {
- this.setState({
- expanded: !this.state.expanded,
- });
- ev.preventDefault();
- }
-
- onMenuOptionClick(server, instance, includeAll) {
- this.setState({
- expanded: false,
- selectedServer: server,
- selectedInstanceId: instance ? instance.instance_id : null,
- includeAllNetworks: includeAll,
- });
- this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
- }
-
- onInputKeyUp(e) {
- if (e.key === 'Enter') {
- this.setState({
- expanded: false,
- selectedServer: e.target.value,
- selectedNetwork: null,
- includeAllNetworks: false,
- });
- this.props.onOptionChange(e.target.value, null);
- }
- }
-
- collectRoot(e) {
- if (this.dropdownRootElement) {
- this.dropdownRootElement.removeEventListener('click', this.onRootClick, false);
- }
- if (e) {
- e.addEventListener('click', this.onRootClick, false);
- }
- this.dropdownRootElement = e;
- }
-
- collectInputTextBox(e) {
- this.inputTextBox = e;
- }
-
- _getMenuOptions() {
- const options = [];
- const roomDirectory = this.props.config.roomDirectory || {};
-
- let servers = [];
- if (roomDirectory.servers) {
- servers = servers.concat(roomDirectory.servers);
- }
-
- if (!servers.includes(MatrixClientPeg.getHomeserverName())) {
- servers.unshift(MatrixClientPeg.getHomeserverName());
- }
+ // configured servers take preference over user-defined ones, if one occurs in both ignore the latter one.
+ const removableServers = new Set(userDefinedServers.filter(s => !configServers.has(s) && s !== hsName));
+ const servers = [
+ // we always show our connected HS, this takes precedence over it being configured or user-defined
+ hsName,
+ ...Array.from(configServers).filter(s => s !== hsName).sort(),
+ ...Array.from(removableServers).sort(),
+ ];
// For our own HS, we can use the instance_ids given in the third party protocols
// response to get the server to filter the room list by network for us.
// We can't get thirdparty protocols for remote server yet though, so for those
// we can only show the default room list.
- for (const server of servers) {
- options.push(this._makeMenuOption(server, null, true));
- if (server === MatrixClientPeg.getHomeserverName()) {
- options.push(this._makeMenuOption(server, null, false));
- if (this.props.protocols) {
- for (const proto of Object.keys(this.props.protocols)) {
- if (!this.props.protocols[proto].instances) continue;
+ const options = servers.map(server => {
+ const serverSelected = server === selectedServerName;
+ const entries = [];
- const sortedInstances = this.props.protocols[proto].instances;
- sortedInstances.sort(function(x, y) {
- const a = x.desc;
- const b = y.desc;
- if (a < b) {
- return -1;
- } else if (a > b) {
- return 1;
- } else {
- return 0;
- }
- });
-
- for (const instance of sortedInstances) {
- if (!instance.instance_id) continue;
- options.push(this._makeMenuOption(server, instance, false));
- }
- }
- }
+ const protocolsList = server === hsName ? Object.values(protocols) : [];
+ if (protocolsList.length > 0) {
+ // add a fake protocol with the ALL_ROOMS symbol
+ protocolsList.push({
+ instances: [{
+ instance_id: ALL_ROOMS,
+ desc: _t("All rooms"),
+ }],
+ });
}
- }
- return options;
- }
+ protocolsList.forEach(({instances=[]}) => {
+ [...instances].sort((b, a) => {
+ return a.desc.localeCompare(b.desc);
+ }).forEach(({desc, instance_id: instanceId}) => {
+ entries.push(
+
+ { desc }
+ );
+ });
+ });
- _makeMenuOption(server, instance, includeAll, handleClicks) {
- if (handleClicks === undefined) handleClicks = true;
+ let subtitle;
+ if (server === hsName) {
+ subtitle = (
+
+ {_t("Your server")}
+
+ );
+ }
- let icon;
- let name;
- let key;
+ let removeButton;
+ if (removableServers.has(server)) {
+ const onClick = async () => {
+ closeMenu();
+ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+ const {finished} = Modal.createTrackedDialog("Network Dropdown", "Remove server", QuestionDialog, {
+ title: _t("Are you sure?"),
+ description: _t("Are you sure you want to remove %(serverName)s", {
+ serverName: server,
+ }, {
+ b: serverName => { serverName },
+ }),
+ button: _t("Remove"),
+ });
- if (!instance && includeAll) {
- key = server;
- name = server;
- } else if (!instance) {
- key = server + '_all';
- name = 'Matrix';
- icon = ;
- } else {
- key = server + '_inst_' + instance.instance_id;
- const imgUrl = instance.icon ?
- MatrixClientPeg.get().mxcUrlToHttp(instance.icon, 25, 25, 'crop', true) :
- DEFAULT_ICON_URL;
- icon = ;
- name = instance.desc;
- }
+ const [ok] = await finished;
+ if (!ok) return;
- const clickHandler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
+ // delete from setting
+ await SettingsStore.setValue(SETTING_NAME, null, "account", servers.filter(s => s !== server));
- return
- {icon}
- {name}
-
;
- }
+ // the selected server is being removed, reset to our HS
+ if (serverSelected === server) {
+ onOptionChange(hsName, undefined);
+ }
+ };
+ removeButton = ;
+ }
- render() {
- let currentValue;
+ return (
+
+
+ { server }
+ { removeButton }
+
+ { subtitle }
- let menu;
- if (this.state.expanded) {
- const menuOptions = this._getMenuOptions();
- menu =
- {menuOptions}
-
;
- currentValue =
;
- } else {
- const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
- currentValue = this._makeMenuOption(
- this.state.selectedServer, instance, this.state.includeAllNetworks, false,
+
+ {_t("Matrix")}
+
+ { entries }
+
);
+ });
+
+ const onClick = async () => {
+ closeMenu();
+ const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
+ const { finished } = Modal.createTrackedDialog("Network Dropdown", "Add a new server", TextInputDialog, {
+ title: _t("Add a new server"),
+ description: _t("Enter the address of a new server you want to explore."),
+ button: _t("Add"),
+ hasCancel: false,
+ placeholder: _t("Server address"),
+ });
+
+ const [ok, newServer] = await finished;
+ if (!ok) return;
+
+ if (!userDefinedServers.includes(newServer)) {
+ const servers = [...userDefinedServers, newServer];
+ await SettingsStore.setValue(SETTING_NAME, null, "account", servers);
+ }
+
+ onOptionChange(newServer); // change filter to the new server
+ };
+
+ const buttonRect = handle.current.getBoundingClientRect();
+ content =
+
+ {options}
+
+
+ ;
+ } else {
+ let currentValue;
+ if (selectedInstanceId === ALL_ROOMS) {
+ currentValue = _t("All rooms");
+ } else if (selectedInstanceId) {
+ const instance = instanceForInstanceId(protocols, selectedInstanceId);
+ currentValue = _t("%(networkName)s rooms", {
+ networkName: instance.desc,
+ });
+ } else {
+ currentValue = _t("Matrix rooms");
}
- return
-
+ content =
+
{currentValue}
-
- {menu}
-
-
;
+
+ ({selectedServerName})
+
+ ;
}
-}
+
+ return
+ {content}
+
;
+};
NetworkDropdown.propTypes = {
onOptionChange: PropTypes.func.isRequired,
protocols: PropTypes.object,
- // The room directory config. May have a 'servers' key that is a list of server names to include in the dropdown
- config: PropTypes.object,
};
-NetworkDropdown.defaultProps = {
- protocols: {},
- config: {},
-};
+export default NetworkDropdown;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 2f8f5c48ad..a5763fe5c5 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1437,6 +1437,15 @@
"And %(count)s more...|other": "And %(count)s more...",
"ex. @bob:example.com": "ex. @bob:example.com",
"Add User": "Add User",
+ "All rooms": "All rooms",
+ "Your server": "Your server",
+ "Matrix": "Matrix",
+ "Add a new server": "Add a new server",
+ "Enter the address of a new server you want to explore.": "Enter the address of a new server you want to explore.",
+ "Server address": "Server address",
+ "Add a new server...": "Add a new server...",
+ "%(networkName)s rooms": "%(networkName)s rooms",
+ "Matrix rooms": "Matrix rooms",
"Matrix ID": "Matrix ID",
"Matrix Room ID": "Matrix Room ID",
"email address": "email address",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index b77fb392e9..8024c411f7 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -324,6 +324,10 @@ export const SETTINGS = {
supportedLevels: ['account'],
default: [],
},
+ "room_directory_servers": {
+ supportedLevels: ['account'],
+ default: [],
+ },
"integrationProvisioning": {
supportedLevels: ['account'],
default: true,