|
|
|
@ -52,23 +52,48 @@ module.exports = React.createClass({
|
|
|
|
|
return { |
|
|
|
|
publicRooms: [], |
|
|
|
|
loading: true, |
|
|
|
|
filterByNetwork: null, |
|
|
|
|
network: null, |
|
|
|
|
roomServer: null, |
|
|
|
|
filterString: null, |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
componentWillMount: function() { |
|
|
|
|
// precompile Regexps
|
|
|
|
|
this.networkPatterns = {}; |
|
|
|
|
if (this.props.config.networkPatterns) { |
|
|
|
|
for (const network of Object.keys(this.props.config.networkPatterns)) { |
|
|
|
|
this.networkPatterns[network] = new RegExp(this.props.config.networkPatterns[network]); |
|
|
|
|
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.filterString = null; |
|
|
|
|
this.filterTimeout = null; |
|
|
|
|
this.scrollPanel = null; |
|
|
|
|
this.protocols = null; |
|
|
|
|
|
|
|
|
|
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => { |
|
|
|
|
this.protocols = response; |
|
|
|
|
}, (err) => { |
|
|
|
|
if (MatrixClientPeg.get().isGuest()) { |
|
|
|
|
// Guests currently aren't allowed to use this API, so
|
|
|
|
|
// ignore this as otherwise this error is literally the
|
|
|
|
|
// thing you see when loading the client!
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); |
|
|
|
|
Modal.createDialog(ErrorDialog, { |
|
|
|
|
title: "Failed to get protocol list from Home Server", |
|
|
|
|
description: "The Home Server may be too old to support third party networks", |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// dis.dispatch({
|
|
|
|
|
// action: 'ui_opacity',
|
|
|
|
@ -97,7 +122,7 @@ module.exports = React.createClass({
|
|
|
|
|
getMoreRooms: function() { |
|
|
|
|
if (!MatrixClientPeg.get()) return q(); |
|
|
|
|
|
|
|
|
|
const my_filter_string = this.filterString; |
|
|
|
|
const my_filter_string = this.state.filterString; |
|
|
|
|
const my_server = this.state.roomServer; |
|
|
|
|
// remember the next batch token when we sent the request
|
|
|
|
|
// too. If it's changed, appending to the list will corrupt it.
|
|
|
|
@ -107,10 +132,10 @@ module.exports = React.createClass({
|
|
|
|
|
opts.server = my_server; |
|
|
|
|
} |
|
|
|
|
if (this.nextBatch) opts.since = this.nextBatch; |
|
|
|
|
if (this.filterString) opts.filter = { generic_search_term: my_filter_string } ; |
|
|
|
|
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ; |
|
|
|
|
return MatrixClientPeg.get().publicRooms(opts).then((data) => { |
|
|
|
|
if ( |
|
|
|
|
my_filter_string != this.filterString || |
|
|
|
|
my_filter_string != this.state.filterString || |
|
|
|
|
my_server != this.state.roomServer || |
|
|
|
|
my_next_batch != this.nextBatch) |
|
|
|
|
{ |
|
|
|
@ -129,7 +154,7 @@ module.exports = React.createClass({
|
|
|
|
|
return Boolean(data.next_batch); |
|
|
|
|
}, (err) => { |
|
|
|
|
if ( |
|
|
|
|
my_filter_string != this.filterString || |
|
|
|
|
my_filter_string != this.state.filterString || |
|
|
|
|
my_server != this.state.roomServer || |
|
|
|
|
my_next_batch != this.nextBatch) |
|
|
|
|
{ |
|
|
|
@ -215,7 +240,7 @@ module.exports = React.createClass({
|
|
|
|
|
// to clear the list anyway.
|
|
|
|
|
publicRooms: [], |
|
|
|
|
roomServer: server, |
|
|
|
|
filterByNetwork: network, |
|
|
|
|
network: network, |
|
|
|
|
}, this.refreshRoomList); |
|
|
|
|
// We also refresh the room list each time even though this
|
|
|
|
|
// filtering is client-side. It hopefully won't be client side
|
|
|
|
@ -232,7 +257,9 @@ module.exports = React.createClass({
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
onFilterChange: function(alias) { |
|
|
|
|
this.filterString = alias || null; |
|
|
|
|
this.setState({ |
|
|
|
|
filterString: alias || null, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// don't send the request for a little bit,
|
|
|
|
|
// no point hammering the server with a
|
|
|
|
@ -248,17 +275,52 @@ module.exports = React.createClass({
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
onFilterClear: function() { |
|
|
|
|
this.filterString = null; |
|
|
|
|
// update immediately
|
|
|
|
|
this.setState({ |
|
|
|
|
filterString: null, |
|
|
|
|
}, this.refreshRoomList); |
|
|
|
|
|
|
|
|
|
if (this.filterTimeout) { |
|
|
|
|
clearTimeout(this.filterTimeout); |
|
|
|
|
} |
|
|
|
|
// update immediately
|
|
|
|
|
this.refreshRoomList(); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
onJoinClick: function(alias) { |
|
|
|
|
this.showRoomAlias(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') { |
|
|
|
|
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); |
|
|
|
|
if (!fields) { |
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); |
|
|
|
|
Modal.createDialog(ErrorDialog, { |
|
|
|
|
title: "Unable to join network", |
|
|
|
|
description: "Riot does not know how to join a room on this network", |
|
|
|
|
}); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
const protocol = this._protocolForThirdPartyNetwork(this.state.network); |
|
|
|
|
MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => { |
|
|
|
|
if (resp.length > 0 && resp[0].alias) { |
|
|
|
|
this.showRoomAlias(resp[0].alias); |
|
|
|
|
} else { |
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); |
|
|
|
|
Modal.createDialog(ErrorDialog, { |
|
|
|
|
title: "Room not found", |
|
|
|
|
description: "Couldn't find a matching Matrix room", |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}, (e) => { |
|
|
|
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); |
|
|
|
|
Modal.createDialog(ErrorDialog, { |
|
|
|
|
title: "Fetching third party location failed", |
|
|
|
|
description: "Unable to look up room ID from server", |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
showRoomAlias: function(alias) { |
|
|
|
@ -311,8 +373,8 @@ module.exports = React.createClass({
|
|
|
|
|
if (!this.state.publicRooms) return []; |
|
|
|
|
|
|
|
|
|
var rooms = this.state.publicRooms.filter((a) => { |
|
|
|
|
if (this.state.filterByNetwork) { |
|
|
|
|
if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.filterByNetwork)) return false; |
|
|
|
|
if (this.state.network) { |
|
|
|
|
if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.network)) return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
@ -382,7 +444,7 @@ module.exports = React.createClass({
|
|
|
|
|
* Terrible temporary function that guess what network a public room |
|
|
|
|
* entry is in, until synapse is able to tell us |
|
|
|
|
*/ |
|
|
|
|
_isRoomInNetwork(room, server, network) { |
|
|
|
|
_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
|
|
|
|
@ -396,7 +458,7 @@ module.exports = React.createClass({
|
|
|
|
|
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.networkPatterns[n]; |
|
|
|
|
const pat = this.portalRoomPatterns[n]; |
|
|
|
|
if (pat && pat.test(room.aliases[0])) { |
|
|
|
|
roomNetwork = n; |
|
|
|
|
} |
|
|
|
@ -406,6 +468,83 @@ module.exports = React.createClass({
|
|
|
|
|
return roomNetwork == network; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
_stringLooksLikeId: function(s, network) { |
|
|
|
|
let pat = /^#[^\s]+:[^\s]/; |
|
|
|
|
if ( |
|
|
|
|
network && network != '_matrix' && |
|
|
|
|
this.nativePatterns[network] |
|
|
|
|
) { |
|
|
|
|
pat = this.nativePatterns[network]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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].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
|
|
|
|
|
// instance. The last is the user input.
|
|
|
|
|
const required_fields = this.protocols[network_info.protocol].location_fields; |
|
|
|
|
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]; |
|
|
|
|
} |
|
|
|
|
fields[required_fields[required_fields.length - 1]] = user_input; |
|
|
|
|
return fields; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
render: function() { |
|
|
|
|
let content; |
|
|
|
|
if (this.state.loading) { |
|
|
|
@ -435,6 +574,23 @@ module.exports = React.createClass({
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
) { |
|
|
|
|
placeholder = this.props.config.networks[this.state.network].example; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const showJoinButton = ( |
|
|
|
|
this._stringLooksLikeId(this.state.filterString, this.state.network) && |
|
|
|
|
this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader'); |
|
|
|
|
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown'); |
|
|
|
|
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox'); |
|
|
|
@ -446,6 +602,7 @@ module.exports = React.createClass({
|
|
|
|
|
<DirectorySearchBox |
|
|
|
|
className="mx_RoomDirectory_searchbox" |
|
|
|
|
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick} |
|
|
|
|
placeholder={placeholder} showJoinButton={showJoinButton} |
|
|
|
|
/> |
|
|
|
|
<NetworkDropdown config={this.props.config} onOptionChange={this.onOptionChange} /> |
|
|
|
|
</div> |
|
|
|
|