From 85ea45a64a4c8f04029666631b89f333b9279a25 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 27 Sep 2016 19:39:20 +0100 Subject: [PATCH 1/9] Room dir: New filtering & 3rd party networks Changes filtering on 3rd party networks to divide into portal / non portal rooms and not show portal rooms by default. Adds a special '_matrix' network for all rooms that aren't portal rooms. Also adds ability to query 3rd party directory servers. --- src/components/structures/RoomDirectory.js | 53 ++++++----- .../views/directory/NetworkDropdown.js | 94 ++++++++++++++++--- .../views/directory/NetworkDropdown.css | 4 + src/skins/vector/img/network-matrix.svg | 14 +++ 4 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 src/skins/vector/img/network-matrix.svg diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 90afff5c..8a2b52b9 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -53,6 +53,7 @@ module.exports = React.createClass({ publicRooms: [], loading: true, filterByNetwork: null, + roomServer: null, } }, @@ -76,10 +77,6 @@ module.exports = React.createClass({ // }); }, - componentDidMount: function() { - this.refreshRoomList(); - }, - componentWillUnmount: function() { // dis.dispatch({ // action: 'ui_opacity', @@ -102,6 +99,9 @@ module.exports = React.createClass({ const my_filter_string = this.filterString; const opts = {limit: 20}; + if (this.state.roomServer != MatrixClientPeg.getHomeServerName()) { + opts.server = this.state.roomServer; + } if (this.nextBatch) opts.since = this.nextBatch; if (this.filterString) opts.filter = { generic_search_term: my_filter_string } ; return MatrixClientPeg.get().publicRooms(opts).then((data) => { @@ -194,18 +194,11 @@ module.exports = React.createClass({ } }, - onNetworkChange: function(network) { + onOptionChange: function(server, network) { this.setState({ + roomServer: server, filterByNetwork: network, - }, () => { - // we just filtered out a bunch of rooms, so check to see if - // we need to fill up the scrollpanel again - // NB. Because we filter the results, the HS can keep giving - // us more rooms and we'll keep requesting more if none match - // the filter, which is pretty terrible. We need a way - // to filter by network on the server. - if (this.scrollPanel) this.scrollPanel.checkFillState(); - }); + }, this.refreshRoomList); }, onFillRequest: function(backwards) { @@ -295,7 +288,7 @@ module.exports = React.createClass({ var rooms = this.state.publicRooms.filter((a) => { if (this.state.filterByNetwork) { - if (!this._isRoomInNetwork(a, this.state.filterByNetwork)) return false; + if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.filterByNetwork)) return false; } return true; @@ -365,14 +358,30 @@ 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, network) { - if (room.aliases && this.networkPatterns[network]) { - for (const alias of room.aliases) { - if (this.networkPatterns[network].test(alias)) return true; + _isRoomInNetwork(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. It's 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.networkPatterns[n]; + if (pat && pat) { + if (this.networkPatterns[n].test(room.aliases[0])) { + roomNetwork = n; + } + } + } } } - - return false; + return roomNetwork == network; }, render: function() { @@ -411,7 +420,7 @@ module.exports = React.createClass({ className="mx_RoomDirectory_searchbox" onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick} /> - + {content} diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js index e1de4ffe..e98922f2 100644 --- a/src/components/views/directory/NetworkDropdown.js +++ b/src/components/views/directory/NetworkDropdown.js @@ -15,6 +15,7 @@ limitations under the License. */ import React from 'react'; +import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; export default class NetworkDropdown extends React.Component { constructor() { @@ -26,12 +27,17 @@ export default class NetworkDropdown extends React.Component { this.onInputClick = this.onInputClick.bind(this); this.onRootClick = this.onRootClick.bind(this); this.onDocumentClick = this.onDocumentClick.bind(this); - this.onNetworkClick = this.onNetworkClick.bind(this); + this.onMenuOptionClick = this.onMenuOptionClick.bind(this); + this.onInputKeyUp = this.onInputKeyUp.bind(this); this.collectRoot = this.collectRoot.bind(this); + this.collectInputTextBox = this.collectInputTextBox.bind(this); + + this.inputTextBox = null; this.state = { expanded: false, - selectedNetwork: null, + selectedServer: MatrixClientPeg.getHomeServerName(), + selectedNetwork: '_matrix', }; } @@ -39,6 +45,9 @@ export default class NetworkDropdown extends React.Component { // Listen for all clicks on the document so we can close the // menu when the user clicks somewhere else 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); } componentWillUnmount() { @@ -72,12 +81,24 @@ export default class NetworkDropdown extends React.Component { ev.preventDefault(); } - onNetworkClick(network, ev) { + onMenuOptionClick(server, network, ev) { this.setState({ expanded: false, + selectedServer: server, selectedNetwork: network, }); - this.props.onNetworkChange(network); + this.props.onOptionChange(server, network); + } + + onInputKeyUp(e) { + if (e.key == 'Enter') { + this.setState({ + expanded: false, + selectedServer: e.target.value, + selectedNetwork: null, + }); + this.props.onOptionChange(e.target.value, null); + } } collectRoot(e) { @@ -90,41 +111,86 @@ export default class NetworkDropdown extends React.Component { this.dropdownRootElement = e; } - _optionForNetwork(network, wire_onclick) { + collectInputTextBox(e) { + this.inputTextBox = e; + } + + _getMenuOptions() { + const options = []; + + let servers = []; + if (this.props.config.servers) { + servers = servers.concat(this.props.config.servers); + } + + if (servers.indexOf(MatrixClientPeg.getHomeServerName()) == -1) { + servers.unshift(MatrixClientPeg.getHomeServerName()); + } + + 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)); + } + } + } + + return options; + } + + _makeMenuOption(server, network, wire_onclick) { if (wire_onclick === undefined) wire_onclick = true; let icon; let name; let span_class; if (network === null) { - name = 'All networks'; + name = server; span_class = 'mx_NetworkDropdown_menu_all'; + } else if (network == '_matrix') { + name = 'Matrix'; + icon = ; + span_class = 'mx_NetworkDropdown_menu_network'; } else { name = this.props.config.networkNames[network]; icon = ; span_class = 'mx_NetworkDropdown_menu_network'; } - const click_handler = wire_onclick ? this.onNetworkClick.bind(this, network) : null; + const click_handler = wire_onclick ? this.onMenuOptionClick.bind(this, server, network) : null; - return
+ let key = server; + if (network !== null) { + key += '_' + network; + } + + return
{icon} {name}
; } + componentDidUpdate() { + if (this.state.expanded && this.inputTextBox) { + this.inputTextBox.focus(); + } + } + render() { - const current_value = this._optionForNetwork(this.state.selectedNetwork, false); + let current_value = this._makeMenuOption( + this.state.selectedServer, this.state.selectedNetwork, false + ); let menu; if (this.state.expanded) { - const menu_options = [this._optionForNetwork(null)]; - for (const network of this.props.config.networks) { - menu_options.push(this._optionForNetwork(network)); - } + const menu_options = this._getMenuOptions(); menu =
{menu_options}
; + current_value = } return
@@ -138,7 +204,7 @@ export default class NetworkDropdown extends React.Component { } NetworkDropdown.propTypes = { - onNetworkChange: React.PropTypes.func.isRequired, + onOptionChange: React.PropTypes.func.isRequired, config: React.PropTypes.object, }; diff --git a/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css b/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css index cc152802..b500ffa2 100644 --- a/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css +++ b/src/skins/vector/css/vector-web/views/directory/NetworkDropdown.css @@ -52,6 +52,10 @@ limitations under the License. vertical-align: middle; } +input.mx_NetworkDropdown_networkoption, input.mx_NetworkDropdown_networkoption:focus { + border: 0; +} + .mx_NetworkDropdown_menu { position: absolute; left: -1px; diff --git a/src/skins/vector/img/network-matrix.svg b/src/skins/vector/img/network-matrix.svg new file mode 100644 index 00000000..bb8278ae --- /dev/null +++ b/src/skins/vector/img/network-matrix.svg @@ -0,0 +1,14 @@ + + + + + + + From 6ff1c30a4b894abe4dd14c16ccb1fbac539441b2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 28 Sep 2016 10:08:03 +0100 Subject: [PATCH 2/9] Fix spurious fill requests when switching networks Ignore responses for old servers too, don't trigger a backfill request when we re-render before refresh. Also a few more comments. --- src/components/structures/RoomDirectory.js | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 8a2b52b9..d506e583 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -98,15 +98,16 @@ module.exports = React.createClass({ if (!MatrixClientPeg.get()) return q(); const my_filter_string = this.filterString; + const my_server = this.state.roomServer; const opts = {limit: 20}; - if (this.state.roomServer != MatrixClientPeg.getHomeServerName()) { - opts.server = this.state.roomServer; + if (my_server != MatrixClientPeg.getHomeServerName()) { + opts.server = my_server; } if (this.nextBatch) opts.since = this.nextBatch; if (this.filterString) opts.filter = { generic_search_term: my_filter_string } ; return MatrixClientPeg.get().publicRooms(opts).then((data) => { - if (my_filter_string != this.filterString) { - // if the filter has changed since this request was sent, + if (my_filter_string != this.filterString || my_server != this.state.roomServer) { + // if the filter or server has changed since this request was sent, // throw away the result (don't even clear the busy flag // since we must still have a request in flight) return; @@ -120,7 +121,7 @@ module.exports = React.createClass({ }); return Boolean(data.next_batch); }, (err) => { - if (my_filter_string != this.filterString) { + if (my_filter_string != this.filterString || my_server != this.state.roomServer) { // as above: we don't care about errors for old // requests either return; @@ -195,10 +196,22 @@ module.exports = React.createClass({ }, onOptionChange: function(server, network) { + // clear next batch so we don't try to load more rooms + this.nextBatch = null; this.setState({ + // Clear the public rooms out here otherwise we needlessly + // spend time filtering lots of rooms when we're about to + // to clear the list anyway. + publicRooms: [], roomServer: server, filterByNetwork: 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 + // for very long, and we may have fetched a thousand rooms to + // find the five gitter ones, at which point we do not want + // to render all those rooms when switching back to 'all networks'. + // Easiest to just blow away the state & re-fetch. }, onFillRequest: function(backwards) { From f852e8a01dcb91513c6fc32d516e37a95aaca41c Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 28 Sep 2016 10:13:35 +0100 Subject: [PATCH 3/9] Update sample config --- vector/config.sample.json | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/vector/config.sample.json b/vector/config.sample.json index e4fd3469..48054f65 100644 --- a/vector/config.sample.json +++ b/vector/config.sample.json @@ -6,30 +6,29 @@ "integrations_rest_url": "http://localhost:5050", "enableLabs": true, "roomDirectory": { - "networks": [ - "matrix:example_com", - "matrix:matrix_org", - "gitter", - "irc:freenode", - "irc:mozilla" + "servers": [ + "matrix.org" ], + "serverConfig": { + "matrix.org": { + "networks": [ + "gitter", + "irc:freenode", + "irc:mozilla" + ] + } + }, "networkPatterns": { - "matrix:example_com": "#.*:example.com", - "matrix:matrix_org": "#.*:matrix.org", "gitter": "#gitter_.*:matrix.org", "irc:freenode": "#freenode_.*:matrix.org", "irc:mozilla": "#mozilla_.*:matrix.org" }, "networkNames": { - "matrix:example_com": "example.com", - "matrix:matrix_org": "matrix.org", "irc:freenode": "Freenode", "irc:mozilla": "Mozilla", "gitter": "Gitter" }, "networkIcons": { - "matrix:example_com": "//matrix.org/favicon.ico", - "matrix:matrix_org": "//matrix.org/favicon.ico", "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" From b2dd3ecf3a412bcf9aa778e6b6774cb876cff6cc Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 28 Sep 2016 10:44:29 +0100 Subject: [PATCH 5/9] Add the _matrix network to the sample config --- vector/config.sample.json | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/config.sample.json b/vector/config.sample.json index 48054f65..bf9bff18 100644 --- a/vector/config.sample.json +++ b/vector/config.sample.json @@ -12,6 +12,7 @@ "serverConfig": { "matrix.org": { "networks": [ + "_matrix", "gitter", "irc:freenode", "irc:mozilla" From a32abae5a37d2a77328127ff79e492917cb87474 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 28 Sep 2016 10:58:01 +0100 Subject: [PATCH 6/9] Don't use _matrix as default if there isn't one --- .../views/directory/NetworkDropdown.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js index e98922f2..3fcd8bb4 100644 --- a/src/components/views/directory/NetworkDropdown.js +++ b/src/components/views/directory/NetworkDropdown.js @@ -18,8 +18,8 @@ import React from 'react'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; export default class NetworkDropdown extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this.dropdownRootElement = null; this.ignoreEvent = null; @@ -34,10 +34,20 @@ export default class NetworkDropdown extends React.Component { this.inputTextBox = null; + let defaultNetwork = null; + if ( + this.props.config.serverConfig && + this.props.config.serverConfig[server] && + this.props.config.serverConfig[server].networks && + '_matrix' in this.props.config.serverConfig[server].networks + ) { + defaultNetwork = '_matrix'; + } + this.state = { expanded: false, selectedServer: MatrixClientPeg.getHomeServerName(), - selectedNetwork: '_matrix', + selectedNetwork: defaultNetwork, }; } From 5ca391f914356e731b4624c29124ed2f64193825 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 28 Sep 2016 11:04:13 +0100 Subject: [PATCH 7/9] Replace double truth test with something sane Also typo --- src/components/structures/RoomDirectory.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index d506e583..28136e15 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -376,7 +376,7 @@ module.exports = React.createClass({ // 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. It's network is the key + // 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. @@ -386,10 +386,8 @@ module.exports = React.createClass({ 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]; - if (pat && pat) { - if (this.networkPatterns[n].test(room.aliases[0])) { - roomNetwork = n; - } + if (pat && pat.test(room.aliases[0])) { + roomNetwork = n; } } } From 455ee4f91bf2e74d89970fe5d84b122819801703 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 28 Sep 2016 11:04:54 +0100 Subject: [PATCH 8/9] Argh, tabs --- vector/config.sample.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/config.sample.json b/vector/config.sample.json index bf9bff18..ad7cec95 100644 --- a/vector/config.sample.json +++ b/vector/config.sample.json @@ -9,7 +9,7 @@ "servers": [ "matrix.org" ], - "serverConfig": { + "serverConfig": { "matrix.org": { "networks": [ "_matrix", @@ -18,7 +18,7 @@ "irc:mozilla" ] } - }, + }, "networkPatterns": { "gitter": "#gitter_.*:matrix.org", "irc:freenode": "#freenode_.*:matrix.org", From 0db12fcd22ceed8e0ed6944ed2125ebef1f8d7a5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 28 Sep 2016 11:05:14 +0100 Subject: [PATCH 9/9] Move method & don't wastefullt create elements Put did update with the other react interface methods & don't bother creating the 'current_value' if we throw it away later. --- .../views/directory/NetworkDropdown.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js index 3fcd8bb4..48d5152f 100644 --- a/src/components/views/directory/NetworkDropdown.js +++ b/src/components/views/directory/NetworkDropdown.js @@ -64,6 +64,12 @@ export default class NetworkDropdown extends React.Component { document.removeEventListener('click', this.onDocumentClick, false); } + componentDidUpdate() { + if (this.state.expanded && this.inputTextBox) { + this.inputTextBox.focus(); + } + } + onDocumentClick(ev) { // Close the dropdown if the user clicks anywhere that isn't // within our root element @@ -181,16 +187,8 @@ export default class NetworkDropdown extends React.Component {
; } - componentDidUpdate() { - if (this.state.expanded && this.inputTextBox) { - this.inputTextBox.focus(); - } - } - render() { - let current_value = this._makeMenuOption( - this.state.selectedServer, this.state.selectedNetwork, false - ); + let current_value; let menu; if (this.state.expanded) { @@ -201,6 +199,10 @@ export default class NetworkDropdown extends React.Component { current_value = + } else { + current_value = this._makeMenuOption( + this.state.selectedServer, this.state.selectedNetwork, false + ); } return