diff --git a/src/Modal.js b/src/Modal.js index d3a5404e1e..44072b9278 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -35,36 +35,13 @@ module.exports = { return container; }, - createDialogWithElement: function(element, props, className) { - var self = this; - - var closeDialog = function() { - ReactDOM.unmountComponentAtNode(self.getOrCreateContainer()); - - if (props && props.onFinished) props.onFinished.apply(null, arguments); - }; - - var dialog = ( -
-
- {element} -
-
-
- ); - - ReactDOM.render(dialog, this.getOrCreateContainer()); - - return {close: closeDialog}; - }, - createDialog: function (Element, props, className) { var self = this; + // never call this via modal.close() from onFinished() otherwise it will loop var closeDialog = function() { - ReactDOM.unmountComponentAtNode(self.getOrCreateContainer()); - if (props && props.onFinished) props.onFinished.apply(null, arguments); + ReactDOM.unmountComponentAtNode(self.getOrCreateContainer()); }; // FIXME: If a dialog uses getDefaultProps it clobbers the onFinished @@ -74,7 +51,7 @@ module.exports = {
-
+
); diff --git a/src/Notifier.js b/src/Notifier.js index 08322e91bc..65a222c730 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -205,7 +205,6 @@ var Notifier = { }, onSyncStateChange: function(state) { - console.log("sync state change: " + state); if (state === "PREPARED" || state === "SYNCING") { this.isPrepared = true; } diff --git a/src/TabComplete.js b/src/TabComplete.js index 3005411e13..0cf6e1948a 100644 --- a/src/TabComplete.js +++ b/src/TabComplete.js @@ -83,10 +83,39 @@ class TabComplete { this._notifyStateChange(); } - startTabCompleting() { + startTabCompleting(passive) { + this.originalText = this.textArea.value; // cache starting text + + // grab the partial word from the text which we'll be tab-completing + var res = MATCH_REGEX.exec(this.originalText); + if (!res) { + this.matchedList = []; + return; + } + // ES6 destructuring; ignore first element (the complete match) + var [ , boundaryGroup, partialGroup] = res; + + if (partialGroup.length === 0 && passive) { + return; + } + + this.isFirstWord = partialGroup.length === this.originalText.length; + this.completing = true; this.currentIndex = 0; - this._calculateCompletions(); + + this.matchedList = [ + new Entry(partialGroup) // first entry is always the original partial + ]; + + // find matching entries in the set of entries given to us + this.list.forEach((entry) => { + if (entry.text.toLowerCase().indexOf(partialGroup.toLowerCase()) === 0) { + this.matchedList.push(entry); + } + }); + + // console.log("calculated completions => %s", JSON.stringify(this.matchedList)); } /** @@ -137,7 +166,7 @@ class TabComplete { this.inPassiveMode = passive; if (!this.completing) { - this.startTabCompleting(); + this.startTabCompleting(passive); } if (shiftKey) { @@ -270,33 +299,6 @@ class TabComplete { }); } - _calculateCompletions() { - this.originalText = this.textArea.value; // cache starting text - - // grab the partial word from the text which we'll be tab-completing - var res = MATCH_REGEX.exec(this.originalText); - if (!res) { - this.matchedList = []; - return; - } - // ES6 destructuring; ignore first element (the complete match) - var [ , boundaryGroup, partialGroup] = res; - this.isFirstWord = partialGroup.length === this.originalText.length; - - this.matchedList = [ - new Entry(partialGroup) // first entry is always the original partial - ]; - - // find matching entries in the set of entries given to us - this.list.forEach((entry) => { - if (entry.text.toLowerCase().indexOf(partialGroup.toLowerCase()) === 0) { - this.matchedList.push(entry); - } - }); - - // console.log("_calculateCompletions => %s", JSON.stringify(this.matchedList)); - } - _notifyStateChange() { if (this.opts.onStateChange) { this.opts.onStateChange(this.completing); diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 10e529dfa7..93f5f7c4e2 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -55,13 +55,13 @@ function textForMemberEvent(ev) { } else if (!ev.getPrevContent().displayname && ev.getContent().displayname) { return ev.getSender() + " set their display name to " + ev.getContent().displayname; } else if (ev.getPrevContent().displayname && !ev.getContent().displayname) { - return ev.getSender() + " removed their display name"; + return ev.getSender() + " removed their display name (" + ev.getPrevContent().displayname + ")"; } else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) { - return ev.getSender() + " removed their profile picture"; + return senderName + " removed their profile picture"; } else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) { - return ev.getSender() + " changed their profile picture"; + return senderName + " changed their profile picture"; } else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) { - return ev.getSender() + " set a profile picture"; + return senderName + " set a profile picture"; } } else { if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index ebd9dad2cd..ba87e178e2 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -525,16 +525,22 @@ module.exports = React.createClass({ var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog'); var dialog_defer = q.defer(); var dialog_ref; - var modal; - var dialog_instance = { + Modal.createDialog(SetDisplayNameDialog, { + currentDisplayName: result.displayname, + ref: (r) => { dialog_ref = r; - }} onFinished={() => { - cli.setDisplayName(dialog_ref.getValue()).done(() => { - dialog_defer.resolve(); - }); - modal.close(); - }} /> - modal = Modal.createDialogWithElement(dialog_instance); + }, + onFinished: (submitted) => { + if (submitted) { + cli.setDisplayName(dialog_ref.getValue()).done(() => { + dialog_defer.resolve(); + }); + } + else { + dialog_defer.reject(); + } + } + }); return dialog_defer.promise; } }); @@ -565,6 +571,8 @@ module.exports = React.createClass({ joining: false, joinError: error }); + + if (!error) return; var msg = error.message ? error.message : JSON.stringify(error); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { @@ -1118,6 +1126,7 @@ module.exports = React.createClass({ spinner={this.state.joining} inviterName={inviterName} invitedEmail={invitedEmail} + room={this.state.room} />
@@ -1159,6 +1168,7 @@ module.exports = React.createClass({ inviterName={ inviterName } canJoin={ true } canPreview={ false } spinner={this.state.joining} + room={this.state.room} />
@@ -1238,6 +1248,7 @@ module.exports = React.createClass({ inviterName={inviterName} invitedEmail={invitedEmail} canPreview={this.state.canPeek} + room={this.state.room} /> ); } diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 9b85854016..129cbd4581 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -19,7 +19,7 @@ var ReactDOM = require("react-dom"); var GeminiScrollbar = require('react-gemini-scrollbar'); var q = require("q"); -var DEBUG_SCROLL = false; +var DEBUG_SCROLL = true; if (DEBUG_SCROLL) { // using bind means that we get to keep useful line numbers in the console diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index bf66e43ab8..99198ac691 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -126,9 +126,7 @@ module.exports = React.createClass({ onLogoutClicked: function(ev) { var LogoutPrompt = sdk.getComponent('dialogs.LogoutPrompt'); - this.logoutModal = Modal.createDialog( - LogoutPrompt, {onCancel: this.onLogoutPromptCancel} - ); + this.logoutModal = Modal.createDialog(LogoutPrompt); }, onPasswordChangeError: function(err) { @@ -162,10 +160,6 @@ module.exports = React.createClass({ }); }, - onLogoutPromptCancel: function() { - this.logoutModal.closeDialog(); - }, - onEnableNotificationsChange: function(event) { UserSettingsStore.setEnableNotifications(event.target.checked); }, diff --git a/src/components/views/dialogs/ErrorDialog.js b/src/components/views/dialogs/ErrorDialog.js index d06cf2de84..c3f4ca6a55 100644 --- a/src/components/views/dialogs/ErrorDialog.js +++ b/src/components/views/dialogs/ErrorDialog.js @@ -31,6 +31,10 @@ module.exports = React.createClass({ displayName: 'ErrorDialog', propTypes: { title: React.PropTypes.string, + description: React.PropTypes.oneOfType([ + React.PropTypes.element, + React.PropTypes.string, + ]), button: React.PropTypes.string, focus: React.PropTypes.bool, onFinished: React.PropTypes.func.isRequired, diff --git a/src/components/views/dialogs/QuestionDialog.js b/src/components/views/dialogs/QuestionDialog.js index 4eeecd64b3..88620e3495 100644 --- a/src/components/views/dialogs/QuestionDialog.js +++ b/src/components/views/dialogs/QuestionDialog.js @@ -20,7 +20,10 @@ module.exports = React.createClass({ displayName: 'QuestionDialog', propTypes: { title: React.PropTypes.string, - description: React.PropTypes.string, + description: React.PropTypes.oneOfType([ + React.PropTypes.element, + React.PropTypes.string, + ]), button: React.PropTypes.string, focus: React.PropTypes.bool, onFinished: React.PropTypes.func.isRequired, diff --git a/src/components/views/dialogs/SetDisplayNameDialog.js b/src/components/views/dialogs/SetDisplayNameDialog.js index 624bb50a46..81ceb21696 100644 --- a/src/components/views/dialogs/SetDisplayNameDialog.js +++ b/src/components/views/dialogs/SetDisplayNameDialog.js @@ -54,7 +54,7 @@ module.exports = React.createClass({ onFormSubmit: function(ev) { ev.preventDefault(); - this.props.onFinished(); + this.props.onFinished(true); return false; }, diff --git a/src/components/views/dialogs/TextInputDialog.js b/src/components/views/dialogs/TextInputDialog.js index 3cda852449..d81ae98718 100644 --- a/src/components/views/dialogs/TextInputDialog.js +++ b/src/components/views/dialogs/TextInputDialog.js @@ -20,7 +20,10 @@ module.exports = React.createClass({ displayName: 'TextInputDialog', propTypes: { title: React.PropTypes.string, - description: React.PropTypes.string, + description: React.PropTypes.oneOfType([ + React.PropTypes.element, + React.PropTypes.string, + ]), value: React.PropTypes.string, button: React.PropTypes.string, focus: React.PropTypes.bool, diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index cfff797e7d..9d5a17b3d2 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -268,10 +268,17 @@ module.exports = React.createClass({ this.readAvatarNode = ReactDom.findDOMNode(node); }, - onMemberAvatarClicked: function(sender) { + onMemberAvatarClick: function(event) { dispatcher.dispatch({ action: 'view_user', - member: sender + member: this.props.mxEvent.sender, + }); + }, + + onSenderProfileClick: function(event) { + dispatcher.dispatch({ + action: 'insert_displayname', + displayname: this.props.mxEvent.sender.name, }); }, @@ -318,12 +325,12 @@ module.exports = React.createClass({ avatar = (
+ onClick={ this.onMemberAvatarClick } />
); } if (EventTileType.needsSenderProfile()) { - sender = ; + sender = ; } } return ( diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index dadc0407c1..b15e5bdc94 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -217,11 +217,8 @@ module.exports = React.createClass({ console.log( "Invite %s to %s - isEmail=%s", inputText, this.props.roomId, isEmailAddress ); - promise.done(function(res) { + promise.then(function(res) { console.log("Invited %s", inputText); - self.setState({ - inviting: false - }); }, function(err) { if (err !== null) { console.error("Failed to invite: %s", JSON.stringify(err)); @@ -230,9 +227,17 @@ module.exports = React.createClass({ description: err.message }); } + }).finally(function() { self.setState({ inviting: false }); + // XXX: hacky focus on the invite box + setTimeout(function() { + var inviteBox = document.getElementById("mx_SearchableEntityList_query"); + if (inviteBox) { + inviteBox.focus(); + } + }, 0); }); }, diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 6744de5d14..c3e02975e0 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -192,9 +192,29 @@ module.exports = React.createClass({ }, onAction: function(payload) { + var textarea = this.refs.textarea; switch (payload.action) { case 'focus_composer': - this.refs.textarea.focus(); + textarea.focus(); + break; + case 'insert_displayname': + if (textarea.value.length) { + var left = textarea.value.substring(0, textarea.selectionStart); + var right = textarea.value.substring(textarea.selectionEnd); + if (right.length) { + left += payload.displayname; + } + else { + left = left.replace(/( ?)$/, " " + payload.displayname); + } + textarea.value = left + right; + textarea.focus(); + textarea.setSelectionRange(left.length, left.length); + } + else { + textarea.value = payload.displayname + ": "; + textarea.focus(); + } break; } }, @@ -497,7 +517,7 @@ module.exports = React.createClass({
-