Merge pull request #2750 from vector-im/dbkr/roomdir_remove_client_filter
Remove the client side filtering from the room dir
This commit is contained in:
commit
09f79b94dd
26
README.md
26
README.md
|
@ -81,33 +81,9 @@ You can configure the app by copying `config.sample.json` to
|
||||||
and https://vector.im. In future identity servers will be decentralised.
|
and https://vector.im. In future identity servers will be decentralised.
|
||||||
1. `integrations_ui_url`: URL to the web interface for the integrations server.
|
1. `integrations_ui_url`: URL to the web interface for the integrations server.
|
||||||
1. `integrations_rest_url`: URL to the REST interface for the integrations server.
|
1. `integrations_rest_url`: URL to the REST interface for the integrations server.
|
||||||
1. `roomDirectory`: config for the public room directory. This section encodes behaviour
|
1. `roomDirectory`: config for the public room directory. This section is optional.
|
||||||
on the room directory screen for filtering the list by server / network type and joining
|
|
||||||
third party networks. This config section will disappear once APIs are available to
|
|
||||||
get this information for home servers. This section is optional.
|
|
||||||
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
|
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
|
||||||
down list. Optional.
|
down list. Optional.
|
||||||
1. `roomDirectory.serverConfig`: Config for each server in `roomDirectory.servers`. Optional.
|
|
||||||
1. `roomDirectory.serverConfig.<server_name>.networks`: List of networks (named
|
|
||||||
in `roomDirectory.networks`) to include for this server. Optional. If set, this will
|
|
||||||
override any networks sent by the Home Server (eg. if ASes are configured).
|
|
||||||
1. `roomDirectory.networks`: config for each network type. Optional.
|
|
||||||
1. `roomDirectory.<network_type>.name`: Human-readable name for the network. Required.
|
|
||||||
1. `roomDirectory.<network_type>.protocol`: Protocol as given by the server in
|
|
||||||
`/_matrix/client/unstable/thirdparty/protocols` response. Required to be able to join
|
|
||||||
this type of third party network.
|
|
||||||
1. `roomDirectory.<network_type>.domain`: Domain as given by the server in
|
|
||||||
`/_matrix/client/unstable/thirdparty/protocols` response, if present. Required to be
|
|
||||||
able to join this type of third party network, if present in `thirdparty/protocols`.
|
|
||||||
1. `roomDirectory.<network_type>.portalRoomPattern`: Regular expression matching aliases
|
|
||||||
for portal rooms to locations on this network. Required.
|
|
||||||
1. `roomDirectory.<network_type>.icon`: URL to an icon to be displayed for this network. Required.
|
|
||||||
1. `roomDirectory.<network_type>.example`: Textual example of a location on this network,
|
|
||||||
eg. '#channel' for an IRC network. Optional.
|
|
||||||
1. `roomDirectory.<network_type>.nativePattern`: Regular expression that matches a
|
|
||||||
valid location on this network. This is used as a hint to the user to indicate
|
|
||||||
when a valid location has been entered so it's not necessary for this to be
|
|
||||||
exactly correct. Optional.
|
|
||||||
1. `update_base_url` (electron app only): HTTPS URL to a web server to download
|
1. `update_base_url` (electron app only): HTTPS URL to a web server to download
|
||||||
updates from. This should be the path to the directory containing `macos`
|
updates from. This should be the path to the directory containing `macos`
|
||||||
and `win32` (for update packages, not installer packages).
|
and `win32` (for update packages, not installer packages).
|
||||||
|
|
|
@ -8,64 +8,6 @@
|
||||||
"roomDirectory": {
|
"roomDirectory": {
|
||||||
"servers": [
|
"servers": [
|
||||||
"matrix.org"
|
"matrix.org"
|
||||||
],
|
]
|
||||||
"serverConfig": {
|
|
||||||
"matrix.org": {
|
|
||||||
"networks": [
|
|
||||||
"_matrix",
|
|
||||||
"gitter",
|
|
||||||
"irc:freenode",
|
|
||||||
"irc:mozilla",
|
|
||||||
"irc:snoonet",
|
|
||||||
"irc:oftc"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"networks": {
|
|
||||||
"gitter": {
|
|
||||||
"protocol": "gitter",
|
|
||||||
"portalRoomPattern": "#gitter_.*:matrix.org",
|
|
||||||
"name": "Gitter",
|
|
||||||
"icon": "//gitter.im/favicon.ico",
|
|
||||||
"example": "org/community",
|
|
||||||
"nativePattern": "[^\\s]+/[^\\s]+$"
|
|
||||||
},
|
|
||||||
"irc:freenode": {
|
|
||||||
"protocol": "irc",
|
|
||||||
"domain": "chat.freenode.net",
|
|
||||||
"portalRoomPattern": "#freenode_.*:matrix.org",
|
|
||||||
"name": "Freenode",
|
|
||||||
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
|
||||||
"example": "#channel",
|
|
||||||
"nativePattern": "^#[^\\s]+$"
|
|
||||||
},
|
|
||||||
"irc:mozilla": {
|
|
||||||
"protocol": "irc",
|
|
||||||
"domain": "irc.mozilla.org",
|
|
||||||
"portalRoomPattern": "#mozilla_.*:matrix.org",
|
|
||||||
"name": "Mozilla",
|
|
||||||
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
|
||||||
"example": "#channel",
|
|
||||||
"nativePattern": "^#[^\\s]+$"
|
|
||||||
},
|
|
||||||
"irc:snoonet": {
|
|
||||||
"protocol": "irc",
|
|
||||||
"domain": "ipv6-irc.snoonet.org",
|
|
||||||
"portalRoomPattern": "#_snoonet_.*:matrix.org",
|
|
||||||
"name": "Snoonet",
|
|
||||||
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
|
||||||
"example": "#channel",
|
|
||||||
"nativePattern": "^#[^\\s]+$"
|
|
||||||
},
|
|
||||||
"irc:oftc": {
|
|
||||||
"protocol": "irc",
|
|
||||||
"domain": "irc.oftc.net",
|
|
||||||
"portalRoomPattern": "#_oftc_.*:matrix.org",
|
|
||||||
"name": "OFTC",
|
|
||||||
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
|
||||||
"example": "#channel",
|
|
||||||
"nativePattern": "^#[^\\s]+$"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
|
||||||
var sanitizeHtml = require('sanitize-html');
|
var sanitizeHtml = require('sanitize-html');
|
||||||
var q = require('q');
|
var q = require('q');
|
||||||
|
|
||||||
|
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -42,9 +44,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
config: {
|
config: {},
|
||||||
networks: [],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -52,37 +52,26 @@ module.exports = React.createClass({
|
||||||
return {
|
return {
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
network: null,
|
protocolsLoading: true,
|
||||||
instance_id: null,
|
instanceId: null,
|
||||||
|
includeAll: false,
|
||||||
roomServer: null,
|
roomServer: null,
|
||||||
filterString: null,
|
filterString: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
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.nextBatch = null;
|
||||||
this.filterTimeout = null;
|
this.filterTimeout = null;
|
||||||
this.scrollPanel = null;
|
this.scrollPanel = null;
|
||||||
this.protocols = null;
|
this.protocols = null;
|
||||||
|
|
||||||
|
this.setState({protocolsLoading: true});
|
||||||
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
||||||
this.protocols = response;
|
this.protocols = response;
|
||||||
|
this.setState({protocolsLoading: false});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
this.setState({protocolsLoading: false});
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
// Guests currently aren't allowed to use this API, so
|
// Guests currently aren't allowed to use this API, so
|
||||||
// ignore this as otherwise this error is literally the
|
// ignore this as otherwise this error is literally the
|
||||||
|
@ -132,9 +121,9 @@ module.exports = React.createClass({
|
||||||
if (my_server != MatrixClientPeg.getHomeServerName()) {
|
if (my_server != MatrixClientPeg.getHomeServerName()) {
|
||||||
opts.server = my_server;
|
opts.server = my_server;
|
||||||
}
|
}
|
||||||
if (this.state.instance_id) {
|
if (this.state.instanceId) {
|
||||||
opts.third_party_instance_id = this.state.instance_id;
|
opts.third_party_instance_id = this.state.instanceId;
|
||||||
} else if (this.state.network !== '_matrix') {
|
} else if (this.state.includeAll) {
|
||||||
opts.include_all_networks = true;
|
opts.include_all_networks = true;
|
||||||
}
|
}
|
||||||
if (this.nextBatch) opts.since = this.nextBatch;
|
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
|
// clear next batch so we don't try to load more rooms
|
||||||
this.nextBatch = null;
|
this.nextBatch = null;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -246,8 +235,8 @@ module.exports = React.createClass({
|
||||||
// to clear the list anyway.
|
// to clear the list anyway.
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
roomServer: server,
|
roomServer: server,
|
||||||
network: network,
|
instanceId: instanceId,
|
||||||
instance_id: instance_id,
|
includeAll: includeAll,
|
||||||
}, this.refreshRoomList);
|
}, this.refreshRoomList);
|
||||||
// We also refresh the room list each time even though this
|
// We also refresh the room list each time even though this
|
||||||
// filtering is client-side. It hopefully won't be client side
|
// filtering is client-side. It hopefully won't be client side
|
||||||
|
@ -278,7 +267,7 @@ module.exports = React.createClass({
|
||||||
this.filterTimeout = setTimeout(() => {
|
this.filterTimeout = setTimeout(() => {
|
||||||
this.filterTimeout = null;
|
this.filterTimeout = null;
|
||||||
this.refreshRoomList();
|
this.refreshRoomList();
|
||||||
}, 300);
|
}, 700);
|
||||||
},
|
},
|
||||||
|
|
||||||
onFilterClear: function() {
|
onFilterClear: function() {
|
||||||
|
@ -293,14 +282,19 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onJoinClick: function(alias) {
|
onJoinClick: function(alias) {
|
||||||
// If we're on the 'Matrix' network (or all networks),
|
// If we don't have a particular instance id selected, just show that rooms alias
|
||||||
// just show that rooms alias
|
if (!this.state.instanceId) {
|
||||||
if (this.state.network == null || this.state.network == '_matrix') {
|
// 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);
|
this.showRoomAlias(alias);
|
||||||
} else {
|
} else {
|
||||||
// This is a 3rd party protocol. Let's see if we
|
// This is a 3rd party protocol. Let's see if we can join it
|
||||||
// can join it
|
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
|
||||||
const fields = this._getFieldsForThirdPartyLocation(alias, this.state.network);
|
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||||
|
const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null;
|
||||||
if (!fields) {
|
if (!fields) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
@ -309,8 +303,7 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const protocol = this._protocolForThirdPartyNetwork(this.state.network);
|
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
|
||||||
MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => {
|
|
||||||
if (resp.length > 0 && resp[0].alias) {
|
if (resp.length > 0 && resp[0].alias) {
|
||||||
this.showRoomAlias(resp[0].alias);
|
this.showRoomAlias(resp[0].alias);
|
||||||
} else {
|
} else {
|
||||||
|
@ -379,13 +372,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (!this.state.publicRooms) return [];
|
if (!this.state.publicRooms) return [];
|
||||||
|
|
||||||
var rooms = this.state.publicRooms.filter((a) => {
|
var rooms = this.state.publicRooms;
|
||||||
if (this.state.network) {
|
|
||||||
if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.network)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
var rows = [];
|
var rows = [];
|
||||||
var self = this;
|
var self = this;
|
||||||
var guestRead, guestJoin, perms;
|
var guestRead, guestJoin, perms;
|
||||||
|
@ -447,119 +434,46 @@ module.exports = React.createClass({
|
||||||
this.scrollPanel = element;
|
this.scrollPanel = element;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_stringLooksLikeId: function(s, field_type) {
|
||||||
* 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) {
|
|
||||||
let pat = /^#[^\s]+:[^\s]/;
|
let pat = /^#[^\s]+:[^\s]/;
|
||||||
if (
|
if (field_type && field_type.regexp) {
|
||||||
network && network != '_matrix' &&
|
pat = new RegExp(field_type.regexp);
|
||||||
this.nativePatterns[network]
|
|
||||||
) {
|
|
||||||
pat = this.nativePatterns[network];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pat.test(s);
|
return pat.test(s);
|
||||||
},
|
},
|
||||||
|
|
||||||
_protocolForThirdPartyNetwork: function(network) {
|
_getFieldsForThirdPartyLocation: function(userInput, protocol, instance) {
|
||||||
if (
|
// make an object with the fields specified by that protocol. We
|
||||||
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
|
|
||||||
// require that the values of all but the last field come from the
|
// require that the values of all but the last field come from the
|
||||||
// instance. The last is the user input.
|
// instance. The last is the user input.
|
||||||
const required_fields = this.protocols[network_info.protocol].location_fields;
|
const requiredFields = protocol.location_fields;
|
||||||
|
if (!requiredFields) return null;
|
||||||
const fields = {};
|
const fields = {};
|
||||||
for (let i = 0; i < required_fields.length - 1; ++i) {
|
for (let i = 0; i < requiredFields.length - 1; ++i) {
|
||||||
const this_field = required_fields[i];
|
const thisField = requiredFields[i];
|
||||||
if (matched_instance.fields[this_field] === undefined) return null;
|
if (instance.fields[thisField] === undefined) return null;
|
||||||
fields[this_field] = matched_instance.fields[this_field];
|
fields[thisField] = instance.fields[thisField];
|
||||||
}
|
}
|
||||||
fields[required_fields[required_fields.length - 1]] = user_input;
|
fields[requiredFields[requiredFields.length - 1]] = userInput;
|
||||||
return fields;
|
return fields;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
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;
|
let content;
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
content = <div className="mx_RoomDirectory">
|
content = <div className="mx_RoomDirectory">
|
||||||
<Loader />
|
<Loader />
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -590,26 +504,35 @@ module.exports = React.createClass({
|
||||||
</ScrollPanel>;
|
</ScrollPanel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let placeholder = 'Search for a room';
|
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
|
||||||
if (this.state.network === null || this.state.network === '_matrix') {
|
let instance_expected_field_type;
|
||||||
placeholder = '#example:' + this.state.roomServer;
|
if (
|
||||||
} else if (
|
protocolName &&
|
||||||
this.props.config.networks &&
|
this.protocols &&
|
||||||
this.props.config.networks[this.state.network] &&
|
this.protocols[protocolName] &&
|
||||||
this.props.config.networks[this.state.network].example &&
|
this.protocols[protocolName].location_fields.length > 0 &&
|
||||||
this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network)
|
this.protocols[protocolName].field_types
|
||||||
) {
|
) {
|
||||||
placeholder = this.props.config.networks[this.state.network].example;
|
const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
|
||||||
|
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
|
||||||
}
|
}
|
||||||
|
|
||||||
let showJoinButton = this._stringLooksLikeId(this.state.filterString, this.state.network);
|
|
||||||
if (this.state.network && this.state.network != '_matrix') {
|
let placeholder = 'Search for a room';
|
||||||
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) === null) {
|
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 (protocolName) {
|
||||||
|
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||||
|
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
|
||||||
showJoinButton = false;
|
showJoinButton = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
|
||||||
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
|
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
|
||||||
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
|
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
|
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
|
||||||
|
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
|
||||||
|
|
||||||
const DEFAULT_ICON_URL = "img/network-matrix.svg";
|
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.onRootClick = this.onRootClick.bind(this);
|
||||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
this.onDocumentClick = this.onDocumentClick.bind(this);
|
||||||
this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
|
this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
|
||||||
this.onMenuOptionClickProtocolInstance = this.onMenuOptionClickProtocolInstance.bind(this);
|
|
||||||
this.onInputKeyUp = this.onInputKeyUp.bind(this);
|
this.onInputKeyUp = this.onInputKeyUp.bind(this);
|
||||||
this.collectRoot = this.collectRoot.bind(this);
|
this.collectRoot = this.collectRoot.bind(this);
|
||||||
this.collectInputTextBox = this.collectInputTextBox.bind(this);
|
this.collectInputTextBox = this.collectInputTextBox.bind(this);
|
||||||
|
@ -38,20 +38,11 @@ export default class NetworkDropdown extends React.Component {
|
||||||
this.inputTextBox = null;
|
this.inputTextBox = null;
|
||||||
|
|
||||||
const server = MatrixClientPeg.getHomeServerName();
|
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 = {
|
this.state = {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
selectedServer: server,
|
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);
|
document.addEventListener('click', this.onDocumentClick, false);
|
||||||
|
|
||||||
// fire this now so the defaults can be set up
|
// 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() {
|
componentWillUnmount() {
|
||||||
|
@ -101,24 +92,14 @@ export default class NetworkDropdown extends React.Component {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenuOptionClick(server, network) {
|
onMenuOptionClick(server, instance, includeAll) {
|
||||||
this.setState({
|
this.setState({
|
||||||
expanded: false,
|
expanded: false,
|
||||||
selectedServer: server,
|
selectedServer: server,
|
||||||
selectedNetwork: network,
|
selectedInstanceId: instance ? instance.instance_id : null,
|
||||||
selectedInstanceId: null,
|
includeAll: includeAll,
|
||||||
});
|
});
|
||||||
this.props.onOptionChange(server, network);
|
this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
|
||||||
}
|
|
||||||
|
|
||||||
onMenuOptionClickProtocolInstance(server, instance_id) {
|
|
||||||
this.setState({
|
|
||||||
expanded: false,
|
|
||||||
selectedServer: server,
|
|
||||||
selectedNetwork: null,
|
|
||||||
selectedInstanceId: instance_id,
|
|
||||||
});
|
|
||||||
this.props.onOptionChange(server, null, instance_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputKeyUp(e) {
|
onInputKeyUp(e) {
|
||||||
|
@ -158,33 +139,21 @@ export default class NetworkDropdown extends React.Component {
|
||||||
servers.unshift(MatrixClientPeg.getHomeServerName());
|
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
|
// 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
|
// response to get the server to filter the room list by network for us.
|
||||||
// server is new enough), although for now we prefer the config if it exists.
|
// We can't get thirdparty protocols for remote server yet though, so for those
|
||||||
// For remote HSes, we use the data from the config.
|
// we can only show the default room list.
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
options.push(this._makeMenuOption(server, null));
|
options.push(this._makeMenuOption(server, null, true));
|
||||||
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
|
if (server == MatrixClientPeg.getHomeServerName()) {
|
||||||
for (const network of this.props.config.serverConfig[server].networks) {
|
options.push(this._makeMenuOption(server, null, false));
|
||||||
options.push(this._makeMenuOption(server, network));
|
if (this.props.protocols) {
|
||||||
}
|
for (const proto of Object.keys(this.props.protocols)) {
|
||||||
} else if (server == MatrixClientPeg.getHomeServerName() && use_protocols) {
|
if (!this.props.protocols[proto].instances) continue;
|
||||||
options.push(this._makeMenuOption(server, '_matrix'));
|
for (const instance of this.props.protocols[proto].instances) {
|
||||||
for (const proto of Object.keys(this.props.protocols)) {
|
if (!instance.instance_id) continue;
|
||||||
if (!this.props.protocols[proto].instances) continue;
|
options.push(this._makeMenuOption(server, instance, false));
|
||||||
for (const instance of this.props.protocols[proto].instances) {
|
}
|
||||||
options.push(this._makeMenuOptionFromProtocolInstance(server, this.props.protocols[proto], instance));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,82 +162,36 @@ export default class NetworkDropdown extends React.Component {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
_makeMenuOptionFromProtocolInstance(server, protocol, instance, handleClicks) {
|
_makeMenuOption(server, instance, includeAll, handleClicks) {
|
||||||
if (handleClicks === undefined) handleClicks = true;
|
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 icon;
|
||||||
let name;
|
let name;
|
||||||
let span_class;
|
let span_class;
|
||||||
|
let key;
|
||||||
|
|
||||||
if (network === null) {
|
if (!instance && includeAll) {
|
||||||
|
key = server;
|
||||||
name = server;
|
name = server;
|
||||||
span_class = 'mx_NetworkDropdown_menu_all';
|
span_class = 'mx_NetworkDropdown_menu_all';
|
||||||
} else if (network == '_matrix') {
|
} else if (!instance) {
|
||||||
|
key = server + '_all';
|
||||||
name = 'Matrix';
|
name = 'Matrix';
|
||||||
icon = <img src="img/network-matrix.svg" />;
|
icon = <img src="img/network-matrix.svg" />;
|
||||||
span_class = 'mx_NetworkDropdown_menu_network';
|
span_class = 'mx_NetworkDropdown_menu_network';
|
||||||
} else {
|
} else {
|
||||||
if (this.props.config.networks[network] === undefined) {
|
key = server + '_inst_' + instance.instance_id;
|
||||||
throw new Error(network + ' network missing from config');
|
icon = <img src={instance.icon || DEFAULT_ICON_URL} />;
|
||||||
}
|
name = instance.desc;
|
||||||
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} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
span_class = 'mx_NetworkDropdown_menu_network';
|
span_class = 'mx_NetworkDropdown_menu_network';
|
||||||
}
|
}
|
||||||
|
|
||||||
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, network) : null;
|
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
|
||||||
|
|
||||||
let key = server;
|
|
||||||
if (network !== null) {
|
|
||||||
key += '_' + network;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className={span_class}>{name}</span>
|
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
||||||
</div>;
|
</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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -285,17 +208,10 @@ export default class NetworkDropdown extends React.Component {
|
||||||
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
||||||
/>
|
/>
|
||||||
} else {
|
} else {
|
||||||
if (this.state.selectedInstanceId) {
|
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
||||||
const protocolName = this._protocolNameForInstanceId(this.state.selectedInstanceId);
|
current_value = this._makeMenuOption(
|
||||||
const instance = this.instanceForInstanceId(this.state.selectedInstanceId);
|
this.state.selectedServer, instance, this.state.includeAll, false
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_NetworkDropdown" ref={this.collectRoot}>
|
return <div className="mx_NetworkDropdown" ref={this.collectRoot}>
|
||||||
|
@ -310,14 +226,12 @@ export default class NetworkDropdown extends React.Component {
|
||||||
|
|
||||||
NetworkDropdown.propTypes = {
|
NetworkDropdown.propTypes = {
|
||||||
onOptionChange: React.PropTypes.func.isRequired,
|
onOptionChange: React.PropTypes.func.isRequired,
|
||||||
config: React.PropTypes.object,
|
|
||||||
protocols: React.PropTypes.object,
|
protocols: React.PropTypes.object,
|
||||||
|
// The room directory config. May have a 'servers' key that is a list of server names to include in the dropdown
|
||||||
|
config: React.PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
NetworkDropdown.defaultProps = {
|
NetworkDropdown.defaultProps = {
|
||||||
config: {
|
|
||||||
networks: [],
|
|
||||||
},
|
|
||||||
protocols: {},
|
protocols: {},
|
||||||
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Find a protocol 'instance' with a given instance_id
|
||||||
|
// in the supplied protocols dict
|
||||||
|
export function instanceForInstanceId(protocols, instance_id) {
|
||||||
|
if (!instance_id) return null;
|
||||||
|
for (const proto of Object.keys(protocols)) {
|
||||||
|
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
|
||||||
|
for (const instance of protocols[proto].instances) {
|
||||||
|
if (instance.instance_id == instance_id) return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// given an instance_id, return the name of the protocol for
|
||||||
|
// that instance ID in the supplied protocols dict
|
||||||
|
export function protocolNameForInstanceId(protocols, instance_id) {
|
||||||
|
if (!instance_id) return null;
|
||||||
|
for (const proto of Object.keys(protocols)) {
|
||||||
|
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
|
||||||
|
for (const instance of protocols[proto].instances) {
|
||||||
|
if (instance.instance_id == instance_id) return proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,6 +77,7 @@ describe('joining a room', function () {
|
||||||
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
||||||
httpBackend.when('GET', '/sync').respond(200, {});
|
httpBackend.when('GET', '/sync').respond(200, {});
|
||||||
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
|
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
|
||||||
|
httpBackend.when('GET', '/thirdparty/protocols').respond(200, {});
|
||||||
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });
|
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });
|
||||||
|
|
||||||
// start with a logged-in client
|
// start with a logged-in client
|
||||||
|
@ -132,6 +133,12 @@ describe('joining a room', function () {
|
||||||
httpBackend.when('POST', '/join/'+encodeURIComponent(ROOM_ALIAS))
|
httpBackend.when('POST', '/join/'+encodeURIComponent(ROOM_ALIAS))
|
||||||
.respond(200, {room_id: ROOM_ID});
|
.respond(200, {room_id: ROOM_ID});
|
||||||
return httpBackend.flush();
|
return httpBackend.flush();
|
||||||
|
}).then(() => {
|
||||||
|
// wait for the join request to be made
|
||||||
|
return q.delay(1);
|
||||||
|
}).then(() => {
|
||||||
|
// flush it through
|
||||||
|
return httpBackend.flush();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
httpBackend.verifyNoOutstandingExpectation();
|
httpBackend.verifyNoOutstandingExpectation();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue