Merge pull request #2750 from vector-im/dbkr/roomdir_remove_client_filter
Remove the client side filtering from the room dirpull/2754/head
						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. | ||||
| 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. `roomDirectory`: config for the public room directory. This section encodes behaviour | ||||
|    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`: config for the public room directory. This section is optional. | ||||
| 1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop | ||||
|    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 | ||||
|    updates from. This should be the path to the directory containing `macos` | ||||
|    and `win32` (for update packages, not installer packages). | ||||
|  |  | |||
|  | @ -8,64 +8,6 @@ | |||
|     "roomDirectory": { | ||||
|         "servers": [ | ||||
|             "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 q = require('q'); | ||||
| 
 | ||||
| import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils'; | ||||
| 
 | ||||
| linkifyMatrix(linkify); | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|  | @ -42,9 +44,7 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|     getDefaultProps: function() { | ||||
|         return { | ||||
|             config: { | ||||
|                 networks: [], | ||||
|             }, | ||||
|             config: {}, | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|  | @ -52,37 +52,26 @@ module.exports = React.createClass({ | |||
|         return { | ||||
|             publicRooms: [], | ||||
|             loading: true, | ||||
|             network: null, | ||||
|             instance_id: null, | ||||
|             protocolsLoading: true, | ||||
|             instanceId: null, | ||||
|             includeAll: false, | ||||
|             roomServer: null, | ||||
|             filterString: null, | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     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.filterTimeout = null; | ||||
|         this.scrollPanel = null; | ||||
|         this.protocols = null; | ||||
| 
 | ||||
|         this.setState({protocolsLoading: true}); | ||||
|         MatrixClientPeg.get().getThirdpartyProtocols().done((response) => { | ||||
|             this.protocols = response; | ||||
|             this.setState({protocolsLoading: false}); | ||||
|         }, (err) => { | ||||
|             this.setState({protocolsLoading: false}); | ||||
|             if (MatrixClientPeg.get().isGuest()) { | ||||
|                 // Guests currently aren't allowed to use this API, so
 | ||||
|                 // ignore this as otherwise this error is literally the
 | ||||
|  | @ -132,9 +121,9 @@ module.exports = React.createClass({ | |||
|         if (my_server != MatrixClientPeg.getHomeServerName()) { | ||||
|             opts.server = my_server; | ||||
|         } | ||||
|         if (this.state.instance_id) { | ||||
|             opts.third_party_instance_id = this.state.instance_id; | ||||
|         } else if (this.state.network !== '_matrix') { | ||||
|         if (this.state.instanceId) { | ||||
|             opts.third_party_instance_id = this.state.instanceId; | ||||
|         } else if (this.state.includeAll) { | ||||
|             opts.include_all_networks = true; | ||||
|         } | ||||
|         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
 | ||||
|         this.nextBatch = null; | ||||
|         this.setState({ | ||||
|  | @ -246,8 +235,8 @@ module.exports = React.createClass({ | |||
|             // to clear the list anyway.
 | ||||
|             publicRooms: [], | ||||
|             roomServer: server, | ||||
|             network: network, | ||||
|             instance_id: instance_id, | ||||
|             instanceId: instanceId, | ||||
|             includeAll: includeAll, | ||||
|         }, this.refreshRoomList); | ||||
|         // We also refresh the room list each time even though this
 | ||||
|         // filtering is client-side. It hopefully won't be client side
 | ||||
|  | @ -278,7 +267,7 @@ module.exports = React.createClass({ | |||
|         this.filterTimeout = setTimeout(() => { | ||||
|             this.filterTimeout = null; | ||||
|             this.refreshRoomList(); | ||||
|         }, 300); | ||||
|         }, 700); | ||||
|     }, | ||||
| 
 | ||||
|     onFilterClear: function() { | ||||
|  | @ -293,14 +282,19 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     onJoinClick: function(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') { | ||||
|         // If we don't have a particular instance id selected, just show that rooms alias
 | ||||
|         if (!this.state.instanceId) { | ||||
|             // 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); | ||||
|         } else { | ||||
|             // This is a 3rd party protocol. Let's see if we
 | ||||
|             // can join it
 | ||||
|             const fields = this._getFieldsForThirdPartyLocation(alias, this.state.network); | ||||
|             // This is a 3rd party protocol. Let's see if we can join it
 | ||||
|             const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId); | ||||
|             const instance = instanceForInstanceId(this.protocols, this.state.instanceId); | ||||
|             const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null; | ||||
|             if (!fields) { | ||||
|                 const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|                 Modal.createDialog(ErrorDialog, { | ||||
|  | @ -309,8 +303,7 @@ module.exports = React.createClass({ | |||
|                 }); | ||||
|                 return; | ||||
|             } | ||||
|             const protocol = this._protocolForThirdPartyNetwork(this.state.network); | ||||
|             MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => { | ||||
|             MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => { | ||||
|                 if (resp.length > 0 && resp[0].alias) { | ||||
|                     this.showRoomAlias(resp[0].alias); | ||||
|                 } else { | ||||
|  | @ -379,13 +372,7 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         if (!this.state.publicRooms) return []; | ||||
| 
 | ||||
|         var rooms = this.state.publicRooms.filter((a) => { | ||||
|             if (this.state.network) { | ||||
|                 if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.network)) return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
|         var rooms = this.state.publicRooms; | ||||
|         var rows = []; | ||||
|         var self = this; | ||||
|         var guestRead, guestJoin, perms; | ||||
|  | @ -447,119 +434,46 @@ module.exports = React.createClass({ | |||
|         this.scrollPanel = element; | ||||
|     }, | ||||
| 
 | ||||
|     /** | ||||
|      * 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) { | ||||
|     _stringLooksLikeId: function(s, field_type) { | ||||
|         let pat = /^#[^\s]+:[^\s]/; | ||||
|         if ( | ||||
|             network && network != '_matrix' && | ||||
|             this.nativePatterns[network] | ||||
|         ) { | ||||
|             pat = this.nativePatterns[network]; | ||||
|         if (field_type && field_type.regexp) { | ||||
|             pat = new RegExp(field_type.regexp); | ||||
|         } | ||||
| 
 | ||||
|         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] && | ||||
|             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
 | ||||
|     _getFieldsForThirdPartyLocation: function(userInput, protocol, instance) { | ||||
|         // 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 requiredFields = protocol.location_fields; | ||||
|         if (!requiredFields) return null; | ||||
|         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]; | ||||
|         for (let i = 0; i < requiredFields.length - 1; ++i) { | ||||
|             const thisField = requiredFields[i]; | ||||
|             if (instance.fields[thisField] === undefined) return null; | ||||
|             fields[thisField] = instance.fields[thisField]; | ||||
|         } | ||||
|         fields[required_fields[required_fields.length - 1]] = user_input; | ||||
|         fields[requiredFields[requiredFields.length - 1]] = userInput; | ||||
|         return fields; | ||||
|     }, | ||||
| 
 | ||||
|     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; | ||||
|         if (this.state.loading) { | ||||
|             const Loader = sdk.getComponent("elements.Spinner"); | ||||
|             content = <div className="mx_RoomDirectory"> | ||||
|                 <Loader /> | ||||
|             </div>; | ||||
|  | @ -590,26 +504,35 @@ 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) | ||||
|         const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId); | ||||
|         let instance_expected_field_type; | ||||
|         if ( | ||||
|             protocolName && | ||||
|             this.protocols && | ||||
|             this.protocols[protocolName] && | ||||
|             this.protocols[protocolName].location_fields.length > 0 && | ||||
|             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') { | ||||
|             if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) === null) { | ||||
| 
 | ||||
|         let placeholder = 'Search for a room'; | ||||
|         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; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader'); | ||||
|         const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown'); | ||||
|         const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox'); | ||||
|         return ( | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ limitations under the License. | |||
| 
 | ||||
| import React from 'react'; | ||||
| import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; | ||||
| import {instanceForInstanceId} from '../../../utils/DirectoryUtils'; | ||||
| 
 | ||||
| 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.onDocumentClick = this.onDocumentClick.bind(this); | ||||
|         this.onMenuOptionClick = this.onMenuOptionClick.bind(this); | ||||
|         this.onMenuOptionClickProtocolInstance = this.onMenuOptionClickProtocolInstance.bind(this); | ||||
|         this.onInputKeyUp = this.onInputKeyUp.bind(this); | ||||
|         this.collectRoot = this.collectRoot.bind(this); | ||||
|         this.collectInputTextBox = this.collectInputTextBox.bind(this); | ||||
|  | @ -38,20 +38,11 @@ export default class NetworkDropdown extends React.Component { | |||
|         this.inputTextBox = null; | ||||
| 
 | ||||
|         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 = { | ||||
|             expanded: false, | ||||
|             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); | ||||
| 
 | ||||
|         // 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() { | ||||
|  | @ -101,24 +92,14 @@ export default class NetworkDropdown extends React.Component { | |||
|         ev.preventDefault(); | ||||
|     } | ||||
| 
 | ||||
|     onMenuOptionClick(server, network) { | ||||
|     onMenuOptionClick(server, instance, includeAll) { | ||||
|         this.setState({ | ||||
|             expanded: false, | ||||
|             selectedServer: server, | ||||
|             selectedNetwork: network, | ||||
|             selectedInstanceId: null, | ||||
|             selectedInstanceId: instance ? instance.instance_id : null, | ||||
|             includeAll: includeAll, | ||||
|         }); | ||||
|         this.props.onOptionChange(server, network); | ||||
|     } | ||||
| 
 | ||||
|     onMenuOptionClickProtocolInstance(server, instance_id) { | ||||
|         this.setState({ | ||||
|             expanded: false, | ||||
|             selectedServer: server, | ||||
|             selectedNetwork: null, | ||||
|             selectedInstanceId: instance_id, | ||||
|         }); | ||||
|         this.props.onOptionChange(server, null, instance_id); | ||||
|         this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll); | ||||
|     } | ||||
| 
 | ||||
|     onInputKeyUp(e) { | ||||
|  | @ -158,33 +139,21 @@ export default class NetworkDropdown extends React.Component { | |||
|             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
 | ||||
|         // response to get the server to filter the room list by network for us (if the
 | ||||
|         // server is new enough), although for now we prefer the config if it exists.
 | ||||
|         // For remote HSes, we use the data from the config.
 | ||||
|         // response to get the server to filter the room list by network for us.
 | ||||
|         // We can't get thirdparty protocols for remote server yet though, so for those
 | ||||
|         // we can only show the default room list.
 | ||||
|         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)); | ||||
|                 } | ||||
|             } else if (server == MatrixClientPeg.getHomeServerName() && use_protocols) { | ||||
|                 options.push(this._makeMenuOption(server, '_matrix')); | ||||
|                 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) { | ||||
|                         options.push(this._makeMenuOptionFromProtocolInstance(server, this.props.protocols[proto], instance)); | ||||
|             options.push(this._makeMenuOption(server, null, true)); | ||||
|             if (server == MatrixClientPeg.getHomeServerName()) { | ||||
|                 options.push(this._makeMenuOption(server, null, false)); | ||||
|                 if (this.props.protocols) { | ||||
|                     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) continue; | ||||
|                             options.push(this._makeMenuOption(server, instance, false)); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | @ -193,82 +162,36 @@ export default class NetworkDropdown extends React.Component { | |||
|         return options; | ||||
|     } | ||||
| 
 | ||||
|     _makeMenuOptionFromProtocolInstance(server, protocol, instance, handleClicks) { | ||||
|     _makeMenuOption(server, instance, includeAll, handleClicks) { | ||||
|         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 name; | ||||
|         let span_class; | ||||
|         let key; | ||||
| 
 | ||||
|         if (network === null) { | ||||
|         if (!instance && includeAll) { | ||||
|             key = server; | ||||
|             name = server; | ||||
|             span_class = 'mx_NetworkDropdown_menu_all'; | ||||
|         } else if (network == '_matrix') { | ||||
|         } else if (!instance) { | ||||
|             key = server + '_all'; | ||||
|             name = 'Matrix'; | ||||
|             icon = <img src="img/network-matrix.svg" />; | ||||
|             span_class = 'mx_NetworkDropdown_menu_network'; | ||||
|         } else { | ||||
|             if (this.props.config.networks[network] === undefined) { | ||||
|                 throw new Error(network + ' network missing from config'); | ||||
|             } | ||||
|             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} />; | ||||
|             } | ||||
| 
 | ||||
|             key = server + '_inst_' + instance.instance_id; | ||||
|             icon = <img src={instance.icon || DEFAULT_ICON_URL} />; | ||||
|             name = instance.desc; | ||||
|             span_class = 'mx_NetworkDropdown_menu_network'; | ||||
|         } | ||||
| 
 | ||||
|         const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, network) : null; | ||||
| 
 | ||||
|         let key = server; | ||||
|         if (network !== null) { | ||||
|             key += '_' + network; | ||||
|         } | ||||
|         const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null; | ||||
| 
 | ||||
|         return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}> | ||||
|             {icon} | ||||
|             <span className={span_class}>{name}</span> | ||||
|         </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; | ||||
|             } | ||||
|         } | ||||
|             <span className="mx_NetworkDropdown_menu_network">{name}</span> | ||||
|         </div> | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|  | @ -285,17 +208,10 @@ export default class NetworkDropdown extends React.Component { | |||
|                 placeholder="matrix.org" // 'matrix.org' as an example of an HS name
 | ||||
|             /> | ||||
|         } else { | ||||
|             if (this.state.selectedInstanceId) { | ||||
|                 const protocolName = this._protocolNameForInstanceId(this.state.selectedInstanceId); | ||||
|                 const instance = this.instanceForInstanceId(this.state.selectedInstanceId); | ||||
|                 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 | ||||
|                 ); | ||||
|             } | ||||
|             const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId); | ||||
|             current_value = this._makeMenuOption( | ||||
|                 this.state.selectedServer, instance, this.state.includeAll, false | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return <div className="mx_NetworkDropdown" ref={this.collectRoot}> | ||||
|  | @ -310,14 +226,12 @@ export default class NetworkDropdown extends React.Component { | |||
| 
 | ||||
| NetworkDropdown.propTypes = { | ||||
|     onOptionChange: React.PropTypes.func.isRequired, | ||||
|     config: 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 = { | ||||
|     config: { | ||||
|         networks: [], | ||||
|     }, | ||||
|     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('GET', '/sync').respond(200, {}); | ||||
|             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 }); | ||||
| 
 | ||||
|             // start with a logged-in client
 | ||||
|  | @ -132,6 +133,12 @@ describe('joining a room', function () { | |||
|                 httpBackend.when('POST', '/join/'+encodeURIComponent(ROOM_ALIAS)) | ||||
|                     .respond(200, {room_id: ROOM_ID}); | ||||
|                 return httpBackend.flush(); | ||||
|             }).then(() => { | ||||
|                 // wait for the join request to be made
 | ||||
|                 return q.delay(1); | ||||
|             }).then(() => { | ||||
|                 // flush it through
 | ||||
|                 return httpBackend.flush(); | ||||
|             }).then(() => { | ||||
|                 httpBackend.verifyNoOutstandingExpectation(); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker