diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 0111ee31..4c306a7a 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -52,23 +52,41 @@ 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)) {
+ if (this.props.config.networks[network].portalRoomPattern) {
+ this.portalRoomPatterns[network] = new RegExp(this.props.config.networks[network].portalRoomPattern);
+ }
+ if (this.props.config.networks[network].nativePattern) {
+ this.nativePatterns[network] = new RegExp(this.props.config.networks[network].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) => {
+ 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 +115,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 +125,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 (this.state.filterString) 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 +147,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 +233,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 +250,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 +268,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 +366,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 +437,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 +451,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 +461,59 @@ 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) {
+ const getfields_funcs = {
+ irc: (user_input, network) => {
+ if (!this.protocols.irc) return;
+ const domain = this.props.config.networks[network].domain;
+ // search through to make sure this is a domain actually
+ // recognised by the HS
+ for (const inst of this.protocols.irc.instances) {
+ if (inst.fields && inst.fields.domain == domain) {
+ return {
+ domain: domain,
+ channel: user_input,
+ }
+ }
+ }
+ return null;
+ },
+ gitter: (user_input, network) => {
+ if (!this.protocols.gitter) return;
+ return {
+ room: user_input,
+ }
+ },
+ };
+
+ const protocol = this._protocolForThirdPartyNetwork(network);
+ if (getfields_funcs[protocol]) {
+ return getfields_funcs[protocol](user_input, network);
+ }
+ },
+
render: function() {
let content;
if (this.state.loading) {
@@ -430,6 +538,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');
@@ -441,6 +566,7 @@ module.exports = React.createClass({
diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js
index e6596319..cacea190 100644
--- a/src/components/views/directory/NetworkDropdown.js
+++ b/src/components/views/directory/NetworkDropdown.js
@@ -40,7 +40,7 @@ export default class NetworkDropdown extends React.Component {
this.props.config.serverConfig &&
this.props.config.serverConfig[server] &&
this.props.config.serverConfig[server].networks &&
- '_matrix' in this.props.config.serverConfig[server].networks
+ this.props.config.serverConfig[server].networks.indexOf('_matrix') > -1
) {
defaultNetwork = '_matrix';
}
@@ -170,8 +170,19 @@ export default class NetworkDropdown extends React.Component {
icon = ;
span_class = 'mx_NetworkDropdown_menu_network';
} else {
- name = this.props.config.networkNames[network];
- icon = ;
+ if (this.props.config.networks[network]) {
+ if (this.props.config.networks[network].name) {
+ name = this.props.config.networks[network].name;
+ } else {
+ name = network;
+ }
+ if (this.props.config.networks[network].icon) {
+ icon = ;
+ } else {
+ icon = ;
+ }
+ }
+
span_class = 'mx_NetworkDropdown_menu_network';
}
@@ -199,6 +210,7 @@ export default class NetworkDropdown extends React.Component {
;
current_value =
} else {
current_value = this._makeMenuOption(
diff --git a/vector/config.sample.json b/vector/config.sample.json
index ad7cec95..fd4a0b31 100644
--- a/vector/config.sample.json
+++ b/vector/config.sample.json
@@ -15,24 +15,53 @@
"_matrix",
"gitter",
"irc:freenode",
- "irc:mozilla"
+ "irc:mozilla",
+ "irc:snoonet",
+ "irc:oftc"
]
}
},
- "networkPatterns": {
- "gitter": "#gitter_.*:matrix.org",
- "irc:freenode": "#freenode_.*:matrix.org",
- "irc:mozilla": "#mozilla_.*:matrix.org"
- },
- "networkNames": {
- "irc:freenode": "Freenode",
- "irc:mozilla": "Mozilla",
- "gitter": "Gitter"
- },
- "networkIcons": {
- "irc:freenode": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
- "irc:mozilla": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
- "gitter": "//gitter.im/favicon.ico"
+ "networks": {
+ "gitter": {
+ "protocol": "gitter",
+ "portalRoomPattern": "#gitter_.*:matrix.org",
+ "name": "Gitter",
+ "icon": "//gitter.im/favicon.ico",
+ "example": "org/community",
+ "nativePattern": "[^\\s]+/[^\\s]+$"
+ },
+ "irc:freenode": {
+ "portalRoomPattern": "#freenode_.*:matrix.org",
+ "name": "Freenode",
+ "icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
+ "example": "#channel",
+ "nativePattern": "^#[^\\s]+$"
+ },
+ "irc:mozilla": {
+ "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]+$"
+ }
}
}
}