Remove the client side filtering from the room dir

This removes the ability for the client to filter remote room
directories by network, since the /thirdparty/protocols API does
not yet work for remote servers. They would only get the main list
now though anyway, so there is no point in us continuing to support
it.
This commit is contained in:
David Baker 2016-12-16 14:17:13 +00:00
parent fb08910db3
commit 994bc9279f
5 changed files with 134 additions and 355 deletions

View file

@ -31,6 +31,8 @@ var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
var sanitizeHtml = require('sanitize-html');
var q = require('q');
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
linkifyMatrix(linkify);
module.exports = React.createClass({
@ -42,9 +44,7 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
config: {
networks: [],
},
config: {},
}
},
@ -52,37 +52,26 @@ module.exports = React.createClass({
return {
publicRooms: [],
loading: true,
network: null,
instance_id: null,
protocolsLoading: true,
instanceId: null,
includeAll: false,
roomServer: null,
filterString: null,
}
},
componentWillMount: function() {
// precompile Regexps
this.portalRoomPatterns = {};
this.nativePatterns = {};
if (this.props.config.networks) {
for (const network of Object.keys(this.props.config.networks)) {
const network_info = this.props.config.networks[network];
if (network_info.portalRoomPattern) {
this.portalRoomPatterns[network] = new RegExp(network_info.portalRoomPattern);
}
if (network_info.nativePattern) {
this.nativePatterns[network] = new RegExp(network_info.nativePattern);
}
}
}
this.nextBatch = null;
this.filterTimeout = null;
this.scrollPanel = null;
this.protocols = null;
this.setState({protocolsLoading: true});
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
this.protocols = response;
this.setState({protocolsLoading: false});
}, (err) => {
this.setState({protocolsLoading: false});
if (MatrixClientPeg.get().isGuest()) {
// Guests currently aren't allowed to use this API, so
// ignore this as otherwise this error is literally the
@ -132,9 +121,9 @@ module.exports = React.createClass({
if (my_server != MatrixClientPeg.getHomeServerName()) {
opts.server = my_server;
}
if (this.state.instance_id) {
opts.third_party_instance_id = this.state.instance_id;
} else if (this.state.network !== '_matrix') {
if (this.state.instanceId) {
opts.third_party_instanceId = this.state.instanceId;
} else if (this.state.includeAll) {
opts.include_all_networks = true;
}
if (this.nextBatch) opts.since = this.nextBatch;
@ -237,7 +226,7 @@ module.exports = React.createClass({
}
},
onOptionChange: function(server, network, instance_id) {
onOptionChange: function(server, instanceId, includeAll) {
// clear next batch so we don't try to load more rooms
this.nextBatch = null;
this.setState({
@ -246,8 +235,8 @@ module.exports = React.createClass({
// to clear the list anyway.
publicRooms: [],
roomServer: server,
network: network,
instance_id: instance_id,
instanceId: instanceId,
includeAll: includeAll,
}, this.refreshRoomList);
// We also refresh the room list each time even though this
// filtering is client-side. It hopefully won't be client side
@ -278,7 +267,7 @@ module.exports = React.createClass({
this.filterTimeout = setTimeout(() => {
this.filterTimeout = null;
this.refreshRoomList();
}, 300);
}, 700);
},
onFilterClear: function() {
@ -295,12 +284,19 @@ module.exports = React.createClass({
onJoinClick: function(alias) {
// If we're on the 'Matrix' network (or all networks),
// just show that rooms alias
if (this.state.network == null || this.state.network == '_matrix') {
if (!this.state.instanceId) {
// If the user specified an alias without a domain, add on whichever server is selected
// in the dropdown
if (alias.indexOf(':') == -1) {
alias = alias + ':' + this.state.roomServer;
}
this.showRoomAlias(alias);
} else {
// This is a 3rd party protocol. Let's see if we
// can join it
const fields = this._getFieldsForThirdPartyLocation(alias, this.state.network);
const protocol_name = protocolNameForInstanceId(this.protocols, this.state.instanceId);
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
const fields = this._getFieldsForThirdPartyLocation(alias, this.protocols[protocol_name], instance);
if (!fields) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
@ -309,8 +305,7 @@ module.exports = React.createClass({
});
return;
}
const protocol = this._protocolForThirdPartyNetwork(this.state.network);
MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => {
MatrixClientPeg.get().getThirdpartyLocation(protocol_name, fields).done((resp) => {
if (resp.length > 0 && resp[0].alias) {
this.showRoomAlias(resp[0].alias);
} else {
@ -379,13 +374,7 @@ module.exports = React.createClass({
if (!this.state.publicRooms) return [];
var rooms = this.state.publicRooms.filter((a) => {
if (this.state.network) {
if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.network)) return false;
}
return true;
});
var rooms = this.state.publicRooms;
var rows = [];
var self = this;
var guestRead, guestJoin, perms;
@ -447,119 +436,46 @@ module.exports = React.createClass({
this.scrollPanel = element;
},
/**
* Terrible temporary function that guess what network a public room
* entry is in, until synapse is able to tell us
*/
_isRoomInNetwork: function(room, server, network) {
// We carve rooms into two categories here. 'portal' rooms are
// rooms created by a user joining a bridge 'portal' alias to
// participate in that room or a foreign network. A room is a
// portal room if it has exactly one alias and that alias matches
// a pattern defined in the config. Its network is the key
// of the pattern that it matches.
// All other rooms are considered 'native matrix' rooms, and
// go into the special '_matrix' network.
let roomNetwork = '_matrix';
if (room.aliases && room.aliases.length == 1) {
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
for (const n of this.props.config.serverConfig[server].networks) {
const pat = this.portalRoomPatterns[n];
if (pat && pat.test(room.aliases[0])) {
roomNetwork = n;
}
}
}
}
return roomNetwork == network;
},
_stringLooksLikeId: function(s, network) {
_stringLooksLikeId: function(s, field_type) {
let pat = /^#[^\s]+:[^\s]/;
if (
network && network != '_matrix' &&
this.nativePatterns[network]
) {
pat = this.nativePatterns[network];
if (field_type && field_type.regexp) {
pat = new RegExp(field_type.regexp);
}
return pat.test(s);
},
_protocolForThirdPartyNetwork: function(network) {
if (
this.props.config.networks &&
this.props.config.networks[network] &&
this.props.config.networks[network].protocol
) {
return this.props.config.networks[network].protocol;
}
},
_getFieldsForThirdPartyLocation: function(user_input, network) {
if (!this.props.config.networks || !this.props.config.networks[network]) return null;
const network_info = this.props.config.networks[network];
if (!network_info.protocol) return null;
if (!this.protocols) return null;
let matched_instance;
// Try to find which instance in the 'protocols' response
// matches this network. We look for a matching protocol
// and the existence of a 'domain' field and if present,
// its value.
if (
this.protocols[network_info.protocol] &&
this.protocols[network_info.protocol].instances &&
this.protocols[network_info.protocol].instances.length == 1
) {
const the_instance = this.protocols[network_info.protocol].instances[0];
// If there's only one instance in this protocol, use it
// as long as it has no domain (which we assume to mean it's
// there is only one possible instance).
if (
(
the_instance.fields.domain === undefined &&
network_info.domain === undefined
) ||
(
the_instance.fields.domain !== undefined &&
the_instance.fields.domain == network_info.domain
)
) {
matched_instance = the_instance;
}
} else if (network_info.domain) {
// otherwise, we look for one with a matching domain.
for (const this_instance of this.protocols[network_info.protocol].instances) {
if (this_instance.fields.domain == network_info.domain) {
matched_instance = this_instance;
}
}
}
if (matched_instance === undefined) return null;
// now make an object with the fields specified by that protocol. We
_getFieldsForThirdPartyLocation: function(user_input, protocol, instance) {
// make an object with the fields specified by that protocol. We
// require that the values of all but the last field come from the
// instance. The last is the user input.
const required_fields = this.protocols[network_info.protocol].location_fields;
const required_fields = protocol.location_fields;
if (!required_fields) return null;
const fields = {};
for (let i = 0; i < required_fields.length - 1; ++i) {
const this_field = required_fields[i];
if (matched_instance.fields[this_field] === undefined) return null;
fields[this_field] = matched_instance.fields[this_field];
if (instance.fields[this_field] === undefined) return null;
fields[this_field] = instance.fields[this_field];
}
fields[required_fields[required_fields.length - 1]] = user_input;
return fields;
},
render: function() {
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const Loader = sdk.getComponent("elements.Spinner");
if (this.state.protocolsLoading) {
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" />
<Loader />
</div>
);
}
let content;
if (this.state.loading) {
const Loader = sdk.getComponent("elements.Spinner");
content = <div className="mx_RoomDirectory">
<Loader />
</div>;
@ -590,26 +506,35 @@ module.exports = React.createClass({
</ScrollPanel>;
}
let placeholder = 'Search for a room';
if (this.state.network === null || this.state.network === '_matrix') {
placeholder = '#example:' + this.state.roomServer;
} else if (
this.props.config.networks &&
this.props.config.networks[this.state.network] &&
this.props.config.networks[this.state.network].example &&
this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network)
const protocol_name = protocolNameForInstanceId(this.protocols, this.state.instanceId);
let instance_expected_field_type;
if (
protocol_name &&
this.protocols &&
this.protocols[protocol_name] &&
this.protocols[protocol_name].location_fields.length > 0 &&
this.protocols[protocol_name].field_types
) {
placeholder = this.props.config.networks[this.state.network].example;
const last_field = this.protocols[protocol_name].location_fields.slice(-1)[0];
instance_expected_field_type = this.protocols[protocol_name].field_types[last_field];
}
let showJoinButton = this._stringLooksLikeId(this.state.filterString, this.state.network);
if (this.state.network && this.state.network != '_matrix') {
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) === null) {
let placeholder = 'Search for a room';
if (!this.state.instanceId) {
placeholder = '#example:' + this.state.roomServer;
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
}
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
if (protocol_name) {
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocol_name], instance) === null) {
showJoinButton = false;
}
}
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
return (

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
const DEFAULT_ICON_URL = "img/network-matrix.svg";
@ -30,7 +31,6 @@ export default class NetworkDropdown extends React.Component {
this.onRootClick = this.onRootClick.bind(this);
this.onDocumentClick = this.onDocumentClick.bind(this);
this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
this.onMenuOptionClickProtocolInstance = this.onMenuOptionClickProtocolInstance.bind(this);
this.onInputKeyUp = this.onInputKeyUp.bind(this);
this.collectRoot = this.collectRoot.bind(this);
this.collectInputTextBox = this.collectInputTextBox.bind(this);
@ -38,20 +38,11 @@ export default class NetworkDropdown extends React.Component {
this.inputTextBox = null;
const server = MatrixClientPeg.getHomeServerName();
let defaultNetwork = null;
if (
this.props.config.serverConfig &&
this.props.config.serverConfig[server] &&
this.props.config.serverConfig[server].networks &&
this.props.config.serverConfig[server].networks.indexOf('_matrix') > -1
) {
defaultNetwork = '_matrix';
}
this.state = {
expanded: false,
selectedServer: server,
selectedNetwork: defaultNetwork,
selectedInstance: null,
includeAllNetworks: false,
};
}
@ -61,7 +52,7 @@ export default class NetworkDropdown extends React.Component {
document.addEventListener('click', this.onDocumentClick, false);
// fire this now so the defaults can be set up
this.props.onOptionChange(this.state.selectedServer, this.state.selectedNetwork);
this.props.onOptionChange(this.state.selectedServer, this.state.selectedInstance, this.state.includeAllNetworks);
}
componentWillUnmount() {
@ -101,24 +92,14 @@ export default class NetworkDropdown extends React.Component {
ev.preventDefault();
}
onMenuOptionClick(server, network) {
onMenuOptionClick(server, instance, includeAll) {
this.setState({
expanded: false,
selectedServer: server,
selectedNetwork: network,
selectedInstanceId: null,
selectedInstanceId: instance ? instance.instance_id : null,
includeAll: includeAll,
});
this.props.onOptionChange(server, network);
}
onMenuOptionClickProtocolInstance(server, instance_id) {
this.setState({
expanded: false,
selectedServer: server,
selectedNetwork: null,
selectedInstanceId: instance_id,
});
this.props.onOptionChange(server, null, instance_id);
this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
}
onInputKeyUp(e) {
@ -158,33 +139,21 @@ export default class NetworkDropdown extends React.Component {
servers.unshift(MatrixClientPeg.getHomeServerName());
}
// if the thirdparty/protocols entries have instance_ids,
// we can get the local server listings from here. If not,
// the server is too old.
let use_protocols = true;
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
if (!instance.instance_id) use_protocols = false;
}
}
// 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 (if the
// server is new enough), although for now we prefer the config if it exists.
// For remote HSes, we use the data from the config.
// 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));
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
for (const network of this.props.config.serverConfig[server].networks) {
options.push(this._makeMenuOption(server, network));
}
} else if (server == MatrixClientPeg.getHomeServerName() && use_protocols) {
options.push(this._makeMenuOption(server, '_matrix'));
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
options.push(this._makeMenuOptionFromProtocolInstance(server, this.props.protocols[proto], instance));
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;
for (const instance of this.props.protocols[proto].instances) {
if (!instance.instance_id) continue;
options.push(this._makeMenuOption(server, instance, false));
}
}
}
}
@ -193,82 +162,36 @@ export default class NetworkDropdown extends React.Component {
return options;
}
_makeMenuOptionFromProtocolInstance(server, protocol, instance, handleClicks) {
_makeMenuOption(server, instance, includeAll, handleClicks) {
if (handleClicks === undefined) handleClicks = true;
const name = instance.desc;
const icon = <img src={instance.icon || DEFAULT_ICON_URL} />;
const key = instance.instance_id;
const click_handler = handleClicks ? this.onMenuOptionClickProtocolInstance.bind(this, server, instance.instance_id) : null;
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
{icon}
<span className="mx_NetworkDropdown_menu_network">{name}</span>
</div>;
}
_makeMenuOption(server, network, handleClicks) {
if (handleClicks === undefined) handleClicks = true;
let icon;
let name;
let span_class;
let key;
if (network === null) {
if (!instance && includeAll) {
key = server;
name = server;
span_class = 'mx_NetworkDropdown_menu_all';
} else if (network == '_matrix') {
} else if (!instance) {
key = server + '_all';
name = 'Matrix';
icon = <img src="img/network-matrix.svg" />;
span_class = 'mx_NetworkDropdown_menu_network';
} else {
if (this.props.config.networks[network] === undefined) {
throw new Error(network + ' network missing from config');
}
if (this.props.config.networks[network].name) {
name = this.props.config.networks[network].name;
} else {
name = network;
}
if (this.props.config.networks[network].icon) {
// omit height here so if people define a non-square logo in the config, it
// will keep the aspect when it scales
icon = <img src={this.props.config.networks[network].icon} />;
} else {
icon = <img src={iconPath} />;
}
key = server + '_inst_'+instance.instance_id;
icon = <img src={instance.icon || DEFAULT_ICON_URL} />;
name = instance.desc;
span_class = 'mx_NetworkDropdown_menu_network';
}
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, network) : null;
let key = server;
if (network !== null) {
key += '_' + network;
}
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
{icon}
<span className={span_class}>{name}</span>
</div>;
}
_protocolNameForInstanceId(instance_id) {
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
if (instance.instance_id == instance_id) return proto;
}
}
}
instanceForInstanceId(instance_id) {
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
if (instance.instance_id == instance_id) return instance;
}
}
<span className="mx_NetworkDropdown_menu_network">{name}</span>
</div>
}
render() {
@ -285,17 +208,10 @@ export default class NetworkDropdown extends React.Component {
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
/>
} else {
if (this.state.selectedInstanceId) {
const protocolName = this._protocolNameForInstanceId(this.state.selectedInstanceId);
const instance = this.instanceForInstanceId(this.state.selectedInstanceId);
current_value = this._makeMenuOptionFromProtocolInstance(
this.state.selectedServer, this.props.protocols[protocolName], instance, false
);
} else {
current_value = this._makeMenuOption(
this.state.selectedServer, this.state.selectedNetwork, false
);
}
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
current_value = this._makeMenuOption(
this.state.selectedServer, instance, this.state.includeAll, false
);
}
return <div className="mx_NetworkDropdown" ref={this.collectRoot}>
@ -310,14 +226,11 @@ export default class NetworkDropdown extends React.Component {
NetworkDropdown.propTypes = {
onOptionChange: React.PropTypes.func.isRequired,
config: React.PropTypes.object,
protocols: React.PropTypes.object,
config: React.PropTypes.object,
};
NetworkDropdown.defaultProps = {
config: {
networks: [],
},
protocols: {},
config: {},
};