mirror of https://github.com/vector-im/riot-web
				
				
				
			Merge remote-tracking branch 'origin/develop' into dbkr/scalar
						commit
						fdcebe1e56
					
				
							
								
								
									
										40
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										40
									
								
								CHANGELOG.md
								
								
								
								
							|  | @ -1,3 +1,43 @@ | |||
| Changes in [0.6.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.3) (2016-06-03) | ||||
| =================================================================================================== | ||||
| [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.2...v0.6.3) | ||||
| 
 | ||||
|  * Change invite text field wording | ||||
|  * Fix bug with new email invite UX where the invite could get wedged | ||||
|  * Label app versions sensibly in UserSettings | ||||
| 
 | ||||
| Changes in [0.6.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.2) (2016-06-02) | ||||
| =================================================================================================== | ||||
| [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.1...v0.6.2) | ||||
| 
 | ||||
|  * Correctly bump dep on matrix-js-sdk 0.5.4 | ||||
| 
 | ||||
| Changes in [0.6.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.1) (2016-06-02) | ||||
| =================================================================================================== | ||||
| [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.0...v0.6.1) | ||||
| 
 | ||||
|  * Fix focusing race in new UX for 3pid invites | ||||
|  * Fix jenkins.sh | ||||
| 
 | ||||
| Changes in [0.6.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.0) (2016-06-02) | ||||
| =================================================================================================== | ||||
| [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.5.2...v0.6.0) | ||||
| 
 | ||||
|  * implement new UX for 3pid invites | ||||
|    [\#297](https://github.com/matrix-org/matrix-react-sdk/pull/297) | ||||
|  * multiple URL preview support | ||||
|    [\#290](https://github.com/matrix-org/matrix-react-sdk/pull/290) | ||||
|  * Add a fallback home server to log into | ||||
|    [\#293](https://github.com/matrix-org/matrix-react-sdk/pull/293) | ||||
|  * Hopefully fix memory leak with velocity | ||||
|    [\#291](https://github.com/matrix-org/matrix-react-sdk/pull/291) | ||||
|  * Support for enabling email notifications | ||||
|    [\#289](https://github.com/matrix-org/matrix-react-sdk/pull/289) | ||||
|  * Correct Readme instructions how to customize the UI | ||||
|    [\#286](https://github.com/matrix-org/matrix-react-sdk/pull/286) | ||||
|  * Avoid rerendering during Room unmount | ||||
|    [\#285](https://github.com/matrix-org/matrix-react-sdk/pull/285) | ||||
| 
 | ||||
| Changes in [0.5.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.5.2) (2016-04-22) | ||||
| =================================================================================================== | ||||
| [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.5.1...v0.5.2) | ||||
|  |  | |||
|  | @ -8,9 +8,6 @@ nvm use 4 | |||
| 
 | ||||
| set -x | ||||
| 
 | ||||
| # install the version of js-sdk provided to us by jenkins | ||||
| npm install ./node_modules/matrix-js-sdk-*.tgz | ||||
| 
 | ||||
| # install the other dependencies | ||||
| npm install | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "matrix-react-sdk", | ||||
|   "version": "0.5.2", | ||||
|   "version": "0.6.3", | ||||
|   "description": "SDK for matrix.org using React", | ||||
|   "author": "matrix.org", | ||||
|   "repository": { | ||||
|  | @ -31,7 +31,7 @@ | |||
|     "highlight.js": "^8.9.1", | ||||
|     "linkifyjs": "^2.0.0-beta.4", | ||||
|     "marked": "^0.3.5", | ||||
|     "matrix-js-sdk": "^0.5.2", | ||||
|     "matrix-js-sdk": "^0.5.4", | ||||
|     "optimist": "^0.6.1", | ||||
|     "q": "^1.4.1", | ||||
|     "react": "^15.0.1", | ||||
|  |  | |||
|  | @ -293,8 +293,9 @@ class Register extends Signup { | |||
| 
 | ||||
| 
 | ||||
| class Login extends Signup { | ||||
|     constructor(hsUrl, isUrl) { | ||||
|     constructor(hsUrl, isUrl, fallbackHsUrl) { | ||||
|         super(hsUrl, isUrl); | ||||
|         this._fallbackHsUrl = fallbackHsUrl; | ||||
|         this._currentFlowIndex = 0; | ||||
|         this._flows = []; | ||||
|     } | ||||
|  | @ -359,6 +360,30 @@ class Login extends Signup { | |||
|                 error.friendlyText = ( | ||||
|                     'Incorrect username and/or password.' | ||||
|                 ); | ||||
|                 if (self._fallbackHsUrl) { | ||||
|                     // as per elsewhere, it would be much nicer to not replace the global
 | ||||
|                     // client just to try an alternate HS
 | ||||
|                     MatrixClientPeg.replaceUsingUrls( | ||||
|                         self._fallbackHsUrl, | ||||
|                         self._isUrl | ||||
|                     ); | ||||
|                     return MatrixClientPeg.get().login('m.login.password', loginParams).then(function(data) { | ||||
|                         return q({ | ||||
|                             homeserverUrl: self._fallbackHsUrl, | ||||
|                             identityServerUrl: self._isUrl, | ||||
|                             userId: data.user_id, | ||||
|                             accessToken: data.access_token | ||||
|                         }); | ||||
|                     }, function(fallback_error) { | ||||
|                         // We also have to put the default back again if it fails...
 | ||||
|                         MatrixClientPeg.replaceUsingUrls( | ||||
|                             this._hsUrl, | ||||
|                             this._isUrl | ||||
|                         ); | ||||
|                         // throw the original error
 | ||||
|                         throw error; | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 error.friendlyText = ( | ||||
|  |  | |||
|  | @ -330,7 +330,7 @@ module.exports = { | |||
|      * Returns null if the input didn't match a command. | ||||
|      */ | ||||
|     processInput: function(roomId, input) { | ||||
|         // trim any trailing whitespace, as it can confuse the parser for 
 | ||||
|         // trim any trailing whitespace, as it can confuse the parser for
 | ||||
|         // IRC-style commands
 | ||||
|         input = input.replace(/\s+$/, ""); | ||||
|         if (input[0] === "/" && input[1] !== "/") { | ||||
|  |  | |||
|  | @ -100,7 +100,7 @@ module.exports = { | |||
|         return this.getEmailPusher(pushers, address) !== undefined; | ||||
|     }, | ||||
| 
 | ||||
|     addEmailPusher: function(address) { | ||||
|     addEmailPusher: function(address, data) { | ||||
|         return MatrixClientPeg.get().setPusher({ | ||||
|             kind: 'email', | ||||
|             app_id: "m.email", | ||||
|  | @ -108,7 +108,7 @@ module.exports = { | |||
|             app_display_name: 'Email Notifications', | ||||
|             device_display_name: address, | ||||
|             lang: navigator.language, | ||||
|             data: {}, | ||||
|             data: data, | ||||
|             append: true,  // We always append for email pushers since we don't want to stop other accounts notifying to the same email address
 | ||||
|         }); | ||||
|     }, | ||||
|  |  | |||
|  | @ -106,6 +106,18 @@ module.exports = React.createClass({ | |||
|             }); | ||||
| 
 | ||||
|             //console.log("enter: "+JSON.stringify(node.props._restingStyle));
 | ||||
|         } else if (node === null) { | ||||
|             // Velocity stores data on elements using the jQuery .data()
 | ||||
|             // method, and assumes you'll be using jQuery's .remove() to
 | ||||
|             // remove the element, but we don't use jQuery, so we need to
 | ||||
|             // blow away the element's data explicitly otherwise it will leak.
 | ||||
|             // This uses Velocity's internal jQuery compatible wrapper.
 | ||||
|             // See the bug at
 | ||||
|             // https://github.com/julianshapiro/velocity/issues/300
 | ||||
|             // and the FAQ entry, "Preventing memory leaks when
 | ||||
|             // creating/destroying large numbers of elements"
 | ||||
|             // (https://github.com/julianshapiro/velocity/issues/47)
 | ||||
|             Velocity.Utilities.removeData(this.nodes[k]); | ||||
|         } | ||||
|         this.nodes[k] = node; | ||||
|     }, | ||||
|  |  | |||
|  | @ -104,6 +104,10 @@ module.exports = React.createClass({ | |||
|         return "https://matrix.org"; | ||||
|     }, | ||||
| 
 | ||||
|     getFallbackHsUrl: function() { | ||||
|         return this.props.config.fallback_hs_url; | ||||
|     }, | ||||
| 
 | ||||
|     getCurrentIsUrl: function() { | ||||
|         if (this.state.register_is_url) { | ||||
|             return this.state.register_is_url; | ||||
|  | @ -490,6 +494,7 @@ module.exports = React.createClass({ | |||
|                             }, | ||||
|                             type: 'm.room.guest_access', | ||||
|                             state_key: '', | ||||
|                             visibility: 'private', | ||||
|                         } | ||||
|                     ], | ||||
|                 }).done(function(res) { | ||||
|  | @ -1157,6 +1162,7 @@ module.exports = React.createClass({ | |||
|                     guestAccessToken={this.state.guestAccessToken} | ||||
|                     defaultHsUrl={this.props.config.default_hs_url} | ||||
|                     defaultIsUrl={this.props.config.default_is_url} | ||||
|                     brand={this.props.config.brand} | ||||
|                     customHsUrl={this.getCurrentHsUrl()} | ||||
|                     customIsUrl={this.getCurrentIsUrl()} | ||||
|                     registrationUrl={this.props.registrationUrl} | ||||
|  | @ -1185,6 +1191,7 @@ module.exports = React.createClass({ | |||
|                     defaultIsUrl={this.props.config.default_is_url} | ||||
|                     customHsUrl={this.getCurrentHsUrl()} | ||||
|                     customIsUrl={this.getCurrentIsUrl()} | ||||
|                     fallbackHsUrl={this.getFallbackHsUrl()} | ||||
|                     onForgotPasswordClick={this.onForgotPasswordClick} | ||||
|                     onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest.bind(this, true) : undefined} | ||||
|                     onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null } | ||||
|  |  | |||
|  | @ -677,6 +677,16 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|     uploadFile: function(file) { | ||||
|         var self = this; | ||||
| 
 | ||||
|         if (MatrixClientPeg.get().isGuest()) { | ||||
|             var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); | ||||
|             Modal.createDialog(NeedToRegisterDialog, { | ||||
|                 title: "Please Register", | ||||
|                 description: "Guest users can't upload files. Please register to upload." | ||||
|             }); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         ContentMessages.sendContentToRoom( | ||||
|             file, this.state.room.roomId, MatrixClientPeg.get() | ||||
|         ).done(undefined, function(error) { | ||||
|  |  | |||
|  | @ -401,7 +401,7 @@ var TimelinePanel = React.createClass({ | |||
| 
 | ||||
|         // if we are scrolled to the bottom, do a quick-reset of our unreadNotificationCount
 | ||||
|         // to avoid having to wait from the remote echo from the homeserver.
 | ||||
|         if (this.getScrollState().stuckAtBottom) { | ||||
|         if (this.isAtEndOfLiveTimeline()) { | ||||
|             this.props.room.setUnreadNotificationCount('total', 0); | ||||
|             this.props.room.setUnreadNotificationCount('highlight', 0); | ||||
|             // XXX: i'm a bit surprised we don't have to emit an event or dispatch to get this picked up
 | ||||
|  |  | |||
|  | @ -299,7 +299,7 @@ module.exports = React.createClass({ | |||
|                             onValueChanged={ this.onAddThreepidClicked } /> | ||||
|                     </div> | ||||
|                     <div className="mx_UserSettings_addThreepid"> | ||||
|                          <img src="img/plus.svg" width="14" height="14" alt="Add" onClick={ this.onAddThreepidClicked }/> | ||||
|                          <img src="img/plus.svg" width="14" height="14" alt="Add" onClick={ this.onAddThreepidClicked.bind(this, undefined, true) }/> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             ); | ||||
|  | @ -397,9 +397,14 @@ module.exports = React.createClass({ | |||
|                         Logged in as {this._me} | ||||
|                     </div> | ||||
|                     <div className="mx_UserSettings_advanced"> | ||||
|                         Version {this.state.clientVersion} | ||||
|                         <br /> | ||||
|                         {this.props.version} | ||||
|                         Homeserver is { MatrixClientPeg.get().getHomeserverUrl() } | ||||
|                     </div> | ||||
|                     <div className="mx_UserSettings_advanced"> | ||||
|                         Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() } | ||||
|                     </div> | ||||
|                     <div className="mx_UserSettings_advanced"> | ||||
|                         matrix-react-sdk version: {this.state.clientVersion}<br/> | ||||
|                         vector-web version: {this.props.version}<br/> | ||||
|                     </div> | ||||
|                 </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,6 +35,10 @@ module.exports = React.createClass({displayName: 'Login', | |||
|         customIsUrl: React.PropTypes.string, | ||||
|         defaultHsUrl: React.PropTypes.string, | ||||
|         defaultIsUrl: React.PropTypes.string, | ||||
|         // Secondary HS which we try to log into if the user is using
 | ||||
|         // the default HS but login fails. Useful for migrating to a
 | ||||
|         // different home server without confusing users.
 | ||||
|         fallbackHsUrl: React.PropTypes.string, | ||||
| 
 | ||||
|         // login shouldn't know or care how registration is done.
 | ||||
|         onRegisterClick: React.PropTypes.func.isRequired, | ||||
|  | @ -105,7 +109,9 @@ module.exports = React.createClass({displayName: 'Login', | |||
|         hsUrl = hsUrl || this.state.enteredHomeserverUrl; | ||||
|         isUrl = isUrl || this.state.enteredIdentityServerUrl; | ||||
| 
 | ||||
|         var loginLogic = new Signup.Login(hsUrl, isUrl); | ||||
|         var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null; | ||||
| 
 | ||||
|         var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl); | ||||
|         this._loginLogic = loginLogic; | ||||
| 
 | ||||
|         loginLogic.getFlows().then(function(flows) { | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ var sdk = require('../../../index'); | |||
| var dis = require('../../../dispatcher'); | ||||
| var Signup = require("../../../Signup"); | ||||
| var ServerConfig = require("../../views/login/ServerConfig"); | ||||
| var MatrixClientPeg = require("../../../MatrixClientPeg"); | ||||
| var RegistrationForm = require("../../views/login/RegistrationForm"); | ||||
| var CaptchaForm = require("../../views/login/CaptchaForm"); | ||||
| 
 | ||||
|  | @ -40,6 +41,7 @@ module.exports = React.createClass({ | |||
|         customIsUrl: React.PropTypes.string, | ||||
|         defaultHsUrl: React.PropTypes.string, | ||||
|         defaultIsUrl: React.PropTypes.string, | ||||
|         brand: React.PropTypes.string, | ||||
|         email: React.PropTypes.string, | ||||
|         username: React.PropTypes.string, | ||||
|         guestAccessToken: React.PropTypes.string, | ||||
|  | @ -145,6 +147,26 @@ module.exports = React.createClass({ | |||
|                 identityServerUrl: self.registerLogic.getIdentityServerUrl(), | ||||
|                 accessToken: response.access_token | ||||
|             }); | ||||
| 
 | ||||
|             if (self.props.brand) { | ||||
|                 MatrixClientPeg.get().getPushers().done((resp)=>{ | ||||
|                     var pushers = resp.pushers; | ||||
|                     for (var i = 0; i < pushers.length; ++i) { | ||||
|                         if (pushers[i].kind == 'email') { | ||||
|                             var emailPusher = pushers[i]; | ||||
|                             emailPusher.data = { brand: self.props.brand }; | ||||
|                             MatrixClientPeg.get().setPusher(emailPusher).done(() => { | ||||
|                                 console.log("Set email branding to " + self.props.brand); | ||||
|                             }, (error) => { | ||||
|                                 console.error("Couldn't set email branding: " + error); | ||||
|                             }); | ||||
|                         } | ||||
|                     } | ||||
|                 }, (error) => { | ||||
|                     console.error("Couldn't get pushers: " + error); | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|         }, function(err) { | ||||
|             if (err.message) { | ||||
|                 self.setState({ | ||||
|  |  | |||
|  | @ -39,11 +39,11 @@ module.exports = React.createClass({ | |||
|             focus: true | ||||
|         }; | ||||
|     }, | ||||
|      | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|         if (this.props.focus) { | ||||
|             // Set the cursor at the end of the text input 
 | ||||
|             this.refs.textinput.value = this.props.value;         | ||||
|             // Set the cursor at the end of the text input
 | ||||
|             this.refs.textinput.value = this.props.value; | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|  | @ -83,13 +83,12 @@ module.exports = React.createClass({ | |||
|                     </div> | ||||
|                 </div> | ||||
|                 <div className="mx_Dialog_buttons"> | ||||
|                     <button onClick={this.onOk}> | ||||
|                         {this.props.button} | ||||
|                     </button> | ||||
| 
 | ||||
|                     <button onClick={this.onCancel}> | ||||
|                         Cancel | ||||
|                     </button> | ||||
|                     <button onClick={this.onOk}> | ||||
|                         {this.props.button} | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         ); | ||||
|  |  | |||
|  | @ -45,9 +45,9 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|     getInitialState: function() { | ||||
|         return { | ||||
|             // the URL (if any) to be previewed with a LinkPreviewWidget
 | ||||
|             // the URLs (if any) to be previewed with a LinkPreviewWidget
 | ||||
|             // inside this TextualBody.
 | ||||
|             link: null, | ||||
|             links: [], | ||||
| 
 | ||||
|             // track whether the preview widget is hidden
 | ||||
|             widgetHidden: false, | ||||
|  | @ -57,9 +57,11 @@ module.exports = React.createClass({ | |||
|     componentDidMount: function() { | ||||
|         linkifyElement(this.refs.content, linkifyMatrix.options); | ||||
| 
 | ||||
|         var link = this.findLink(this.refs.content.children); | ||||
|         if (link) { | ||||
|             this.setState({ link: link.getAttribute("href") }); | ||||
|         var links = this.findLinks(this.refs.content.children); | ||||
|         if (links.length) { | ||||
|             this.setState({ links: links.map((link)=>{ | ||||
|                 return link.getAttribute("href"); | ||||
|             })}); | ||||
| 
 | ||||
|             // lazy-load the hidden state of the preview widget from localstorage
 | ||||
|             if (global.localStorage) { | ||||
|  | @ -74,27 +76,32 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|     shouldComponentUpdate: function(nextProps, nextState) { | ||||
|         // exploit that events are immutable :)
 | ||||
|         // ...and that .links is only ever set in componentDidMount and never changes
 | ||||
|         return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() || | ||||
|                 nextProps.highlights !== this.props.highlights || | ||||
|                 nextProps.highlightLink !== this.props.highlightLink || | ||||
|                 nextState.link !== this.state.link || | ||||
|                 nextState.links !== this.state.links || | ||||
|                 nextState.widgetHidden !== this.state.widgetHidden); | ||||
|     }, | ||||
| 
 | ||||
|     findLink: function(nodes) { | ||||
|     findLinks: function(nodes) { | ||||
|         var links = []; | ||||
|         for (var i = 0; i < nodes.length; i++) { | ||||
|             var node = nodes[i]; | ||||
|             if (node.tagName === "A" && node.getAttribute("href")) | ||||
|             { | ||||
|                 return this.isLinkPreviewable(node) ? node : undefined; | ||||
|                 if (this.isLinkPreviewable(node)) { | ||||
|                     links.push(node); | ||||
|                 } | ||||
|             } | ||||
|             else if (node.tagName === "PRE" || node.tagName === "CODE") { | ||||
|                 return; | ||||
|                 continue; | ||||
|             } | ||||
|             else if (node.children && node.children.length) { | ||||
|                 return this.findLink(node.children) | ||||
|                 links = links.concat(this.findLinks(node.children)); | ||||
|             } | ||||
|         } | ||||
|         return links; | ||||
|     }, | ||||
| 
 | ||||
|     isLinkPreviewable: function(node) { | ||||
|  | @ -117,7 +124,7 @@ module.exports = React.createClass({ | |||
|         else { | ||||
|             var url = node.getAttribute("href"); | ||||
|             var host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1]; | ||||
|             if (node.textContent.trim().startsWith(host)) { | ||||
|             if (node.textContent.toLowerCase().trim().startsWith(host.toLowerCase())) { | ||||
|                 // it's a "foo.pl" style link
 | ||||
|                 return; | ||||
|             } | ||||
|  | @ -160,14 +167,17 @@ module.exports = React.createClass({ | |||
|                                        {highlightLink: this.props.highlightLink}); | ||||
| 
 | ||||
| 
 | ||||
|         var widget; | ||||
|         if (this.state.link && !this.state.widgetHidden) { | ||||
|         var widgets; | ||||
|         if (this.state.links.length && !this.state.widgetHidden) { | ||||
|             var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); | ||||
|             widget = <LinkPreviewWidget | ||||
|                 link={ this.state.link } | ||||
|                 mxEvent={ this.props.mxEvent } | ||||
|                 onCancelClick={ this.onCancelClick } | ||||
|                 onWidgetLoad={ this.props.onWidgetLoad }/>; | ||||
|             widgets = this.state.links.map((link)=>{ | ||||
|                 return <LinkPreviewWidget | ||||
|                             key={ link } | ||||
|                             link={ link } | ||||
|                             mxEvent={ this.props.mxEvent } | ||||
|                             onCancelClick={ this.onCancelClick } | ||||
|                             onWidgetLoad={ this.props.onWidgetLoad }/>; | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         switch (content.msgtype) { | ||||
|  | @ -176,21 +186,21 @@ module.exports = React.createClass({ | |||
|                 return ( | ||||
|                     <span ref="content" className="mx_MEmoteBody mx_EventTile_content"> | ||||
|                         * { name } { body } | ||||
|                         { widget } | ||||
|                         { widgets } | ||||
|                     </span> | ||||
|                 ); | ||||
|             case "m.notice": | ||||
|                 return ( | ||||
|                     <span ref="content" className="mx_MNoticeBody mx_EventTile_content"> | ||||
|                         { body } | ||||
|                         { widget } | ||||
|                         { widgets } | ||||
|                     </span> | ||||
|                 ); | ||||
|             default: // including "m.text"
 | ||||
|                 return ( | ||||
|                     <span ref="content" className="mx_MTextBody mx_EventTile_content"> | ||||
|                         { body } | ||||
|                         { widget } | ||||
|                         { widgets } | ||||
|                     </span> | ||||
|                 ); | ||||
|         } | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ module.exports = React.createClass({ | |||
|     propTypes: { | ||||
|         roomId: React.PropTypes.string.isRequired, | ||||
|         onInvite: React.PropTypes.func.isRequired, // fn(inputText)
 | ||||
|         onThirdPartyInvite: React.PropTypes.func.isRequired, // fn(inputText)
 | ||||
|         onSearchQueryChanged: React.PropTypes.func // fn(inputText)
 | ||||
|     }, | ||||
| 
 | ||||
|  | @ -49,10 +50,19 @@ module.exports = React.createClass({ | |||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     componentDidMount: function() { | ||||
|         // initialise the email tile
 | ||||
|         this.onSearchQueryChanged(''); | ||||
|     }, | ||||
| 
 | ||||
|     onInvite: function(ev) { | ||||
|         this.props.onInvite(this._input); | ||||
|     }, | ||||
| 
 | ||||
|     onThirdPartyInvite: function(ev) { | ||||
|         this.props.onThirdPartyInvite(this._input); | ||||
|     }, | ||||
| 
 | ||||
|     onSearchQueryChanged: function(input) { | ||||
|         this._input = input; | ||||
|         var EntityTile = sdk.getComponent("rooms.EntityTile"); | ||||
|  | @ -68,9 +78,10 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|         this._emailEntity = new Entities.newEntity( | ||||
|             <EntityTile key="dynamic_invite_tile" suppressOnHover={true} showInviteButton={true} | ||||
|             avatarJsx={ <BaseAvatar name="@" width={36} height={36} /> } | ||||
|             className="mx_EntityTile_invitePlaceholder" | ||||
|             presenceState="online" onClick={this.onInvite} name={label} />, | ||||
|                 avatarJsx={ <BaseAvatar name="@" width={36} height={36} /> } | ||||
|                 className="mx_EntityTile_invitePlaceholder" | ||||
|                 presenceState="online" onClick={this.onThirdPartyInvite} name={"Invite by email"}  | ||||
|             />, | ||||
|             function(query) { | ||||
|                 return true; // always show this
 | ||||
|             } | ||||
|  | @ -89,7 +100,7 @@ module.exports = React.createClass({ | |||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <SearchableEntityList searchPlaceholderText={"Invite/search by name, email, id"} | ||||
|             <SearchableEntityList searchPlaceholderText={"Search/invite by name, email, id"} | ||||
|                 onSubmit={this.props.onInvite} | ||||
|                 onQueryChanged={this.onSearchQueryChanged} | ||||
|                 entities={entities} | ||||
|  |  | |||
|  | @ -340,6 +340,7 @@ module.exports = React.createClass({ | |||
|                         }, | ||||
|                         type: 'm.room.guest_access', | ||||
|                         state_key: '', | ||||
|                         visibility: 'private', | ||||
|                     } | ||||
|                 ], | ||||
|             }).then( | ||||
|  | @ -367,7 +368,7 @@ module.exports = React.createClass({ | |||
|             action: 'leave_room', | ||||
|             room_id: this.props.member.roomId, | ||||
|         }); | ||||
|         this.props.onFinished();         | ||||
|         this.props.onFinished(); | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|  |  | |||
|  | @ -166,6 +166,25 @@ module.exports = React.createClass({ | |||
|         }); | ||||
|     }, 500), | ||||
| 
 | ||||
|     onThirdPartyInvite: function(inputText) { | ||||
|         var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); | ||||
|         Modal.createDialog(TextInputDialog, { | ||||
|             title: "Invite members by email", | ||||
|             description: "Please enter one or more email addresses", | ||||
|             value: inputText, | ||||
|             button: "Invite", | ||||
|             onFinished: (should_invite, addresses)=>{ | ||||
|                 if (should_invite) { | ||||
|                     // defer the actual invite to the next event loop to give this
 | ||||
|                     // Modal a chance to unmount in case onInvite() triggers a new one
 | ||||
|                     setTimeout(()=>{ | ||||
|                         this.onInvite(addresses); | ||||
|                     }, 0); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     onInvite: function(inputText) { | ||||
|         var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); | ||||
|         var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); | ||||
|  | @ -514,6 +533,7 @@ module.exports = React.createClass({ | |||
|             inviteMemberListSection = ( | ||||
|                 <InviteMemberList roomId={this.props.roomId} | ||||
|                     onSearchQueryChanged={this.onSearchQueryChanged} | ||||
|                     onThirdPartyInvite={this.onThirdPartyInvite} | ||||
|                     onInvite={this.onInvite} /> | ||||
|             ); | ||||
|         } | ||||
|  |  | |||
|  | @ -46,6 +46,15 @@ module.exports = React.createClass({ | |||
|     }, | ||||
| 
 | ||||
|     onUploadClick: function(ev) { | ||||
|         if (MatrixClientPeg.get().isGuest()) { | ||||
|             var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); | ||||
|             Modal.createDialog(NeedToRegisterDialog, { | ||||
|                 title: "Please Register", | ||||
|                 description: "Guest users can't upload files. Please register to upload." | ||||
|             }); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.refs.uploadInput.click(); | ||||
|     }, | ||||
| 
 | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ module.exports = React.createClass({ | |||
|     getInitialState: function() { | ||||
|         var tags = {}; | ||||
|         Object.keys(this.props.room.tags).forEach(function(tagName) { | ||||
|             tags[tagName] = {}; | ||||
|             tags[tagName] = ['yep']; | ||||
|         }); | ||||
| 
 | ||||
|         var areNotifsMuted = false; | ||||
|  | @ -186,7 +186,7 @@ module.exports = React.createClass({ | |||
|         // tags
 | ||||
|         if (this.state.tags_changed) { | ||||
|             var tagDiffs = ObjectUtils.getKeyValueArrayDiffs(originalState.tags, this.state.tags); | ||||
|             // [ {place: add, key: "m.favourite", val: "yep"} ]
 | ||||
|             // [ {place: add, key: "m.favourite", val: ["yep"]} ]
 | ||||
|             tagDiffs.forEach(function(diff) { | ||||
|                 switch (diff.place) { | ||||
|                     case "add": | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ var SearchableEntityList = React.createClass({ | |||
|     getInitialState: function() { | ||||
|         return { | ||||
|             query: "", | ||||
|             focused: false, | ||||
|             truncateAt: this.props.truncateAt, | ||||
|             results: this.getSearchResults("", this.props.entities) | ||||
|         }; | ||||
|  | @ -101,7 +102,7 @@ var SearchableEntityList = React.createClass({ | |||
| 
 | ||||
|     getSearchResults: function(query, entities) { | ||||
|         if (!query || query.length === 0) { | ||||
|             return this.props.emptyQueryShowsAll ? entities : [] | ||||
|             return this.props.emptyQueryShowsAll ? entities : [ entities[0] ] | ||||
|         } | ||||
|         return entities.filter(function(e) { | ||||
|             return e.matches(query); | ||||
|  | @ -134,13 +135,27 @@ var SearchableEntityList = React.createClass({ | |||
|                 <form onSubmit={this.onQuerySubmit} autoComplete="off"> | ||||
|                     <input className="mx_SearchableEntityList_query" id="mx_SearchableEntityList_query" type="text" | ||||
|                         onChange={this.onQueryChanged} value={this.state.query} | ||||
|                         onFocus={ ()=>{ | ||||
|                             if (this._blurTimeout) { | ||||
|                                 clearTimeout(this.blurTimeout); | ||||
|                             } | ||||
|                             this.setState({ focused: true }); | ||||
|                         } } | ||||
|                         onBlur={ ()=>{ | ||||
|                             // nasty setTimeout heuristic to avoid the 'invite by email' prompt disappearing
 | ||||
|                             // due to the onBlur before we can click on it
 | ||||
|                             this._blurTimeout = setTimeout( | ||||
|                                 ()=>{ this.setState({ focused: false }) }, | ||||
|                                 300 | ||||
|                             ); | ||||
|                         } } | ||||
|                         placeholder={this.props.searchPlaceholderText} /> | ||||
|                 </form> | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         var list; | ||||
|         if (this.state.results.length) { | ||||
|         if (this.state.results.length > 1 || this.state.focused) { | ||||
|             if (this.props.truncateAt) { // caller wants list truncated
 | ||||
|                 var TruncatedList = sdk.getComponent("elements.TruncatedList"); | ||||
|                 list = ( | ||||
|  | @ -172,10 +187,10 @@ var SearchableEntityList = React.createClass({ | |||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|             <div className={ "mx_SearchableEntityList " + (this.state.query.length ? "mx_SearchableEntityList_expanded" : "") }> | ||||
|             <div className={ "mx_SearchableEntityList " + (list ? "mx_SearchableEntityList_expanded" : "") }> | ||||
|                 { inputBox } | ||||
|                 { list } | ||||
|                 { this.state.query.length ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' } | ||||
|                 { list ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' } | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker