Add native joining of 3p networks to room dir
Use the 3rd party location lookup API to accept third-party locations in their native form and look up the corresponding portal room for that location. Also give the network dropdown some placeholder text. Fixes https://github.com/vector-im/vector-web/issues/2374
This commit is contained in:
parent
65f14c7d21
commit
761600f325
3 changed files with 205 additions and 38 deletions
|
@ -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({
|
|||
</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)
|
||||
) {
|
||||
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({
|
|||
<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>
|
||||
|
|
|
@ -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 = <img src="img/network-matrix.svg" width="16" height="16" />;
|
||||
span_class = 'mx_NetworkDropdown_menu_network';
|
||||
} else {
|
||||
name = this.props.config.networkNames[network];
|
||||
icon = <img src={this.props.config.networkIcons[network]} />;
|
||||
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 = <img src={this.props.config.networks[network].icon} />;
|
||||
} else {
|
||||
icon = <img src={iconPath} width="16" height="16" />;
|
||||
}
|
||||
}
|
||||
|
||||
span_class = 'mx_NetworkDropdown_menu_network';
|
||||
}
|
||||
|
||||
|
@ -199,6 +210,7 @@ export default class NetworkDropdown extends React.Component {
|
|||
</div>;
|
||||
current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
|
||||
ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
|
||||
placeholder="matrix.org"
|
||||
/>
|
||||
} else {
|
||||
current_value = this._makeMenuOption(
|
||||
|
|
|
@ -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]+$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue