Merge remote-tracking branch 'origin/develop' into dbkr/show_invited_email

This commit is contained in:
David Baker 2016-03-18 15:34:15 +00:00
commit 3e915cf0e2
15 changed files with 121 additions and 92 deletions

View file

@ -35,36 +35,13 @@ module.exports = {
return container; 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 = (
<div className={"mx_Dialog_wrapper " + className}>
<div className="mx_Dialog">
{element}
</div>
<div className="mx_Dialog_background" onClick={closeDialog}></div>
</div>
);
ReactDOM.render(dialog, this.getOrCreateContainer());
return {close: closeDialog};
},
createDialog: function (Element, props, className) { createDialog: function (Element, props, className) {
var self = this; var self = this;
// never call this via modal.close() from onFinished() otherwise it will loop
var closeDialog = function() { var closeDialog = function() {
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(null, arguments); if (props && props.onFinished) props.onFinished.apply(null, arguments);
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
}; };
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished // FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
@ -74,7 +51,7 @@ module.exports = {
<div className="mx_Dialog"> <div className="mx_Dialog">
<Element {...props} onFinished={closeDialog}/> <Element {...props} onFinished={closeDialog}/>
</div> </div>
<div className="mx_Dialog_background" onClick={closeDialog}></div> <div className="mx_Dialog_background" onClick={ closeDialog.bind(this, false) }></div>
</div> </div>
); );

View file

@ -205,7 +205,6 @@ var Notifier = {
}, },
onSyncStateChange: function(state) { onSyncStateChange: function(state) {
console.log("sync state change: " + state);
if (state === "PREPARED" || state === "SYNCING") { if (state === "PREPARED" || state === "SYNCING") {
this.isPrepared = true; this.isPrepared = true;
} }

View file

@ -83,10 +83,39 @@ class TabComplete {
this._notifyStateChange(); 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.completing = true;
this.currentIndex = 0; 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; this.inPassiveMode = passive;
if (!this.completing) { if (!this.completing) {
this.startTabCompleting(); this.startTabCompleting(passive);
} }
if (shiftKey) { 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() { _notifyStateChange() {
if (this.opts.onStateChange) { if (this.opts.onStateChange) {
this.opts.onStateChange(this.completing); this.opts.onStateChange(this.completing);

View file

@ -55,13 +55,13 @@ function textForMemberEvent(ev) {
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) { } else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
return ev.getSender() + " set their display name to " + ev.getContent().displayname; return ev.getSender() + " set their display name to " + ev.getContent().displayname;
} else if (ev.getPrevContent().displayname && !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) { } 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) { } 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) { } else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
return ev.getSender() + " set a profile picture"; return senderName + " set a profile picture";
} }
} else { } else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);

View file

@ -525,16 +525,22 @@ module.exports = React.createClass({
var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog'); var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog');
var dialog_defer = q.defer(); var dialog_defer = q.defer();
var dialog_ref; var dialog_ref;
var modal; Modal.createDialog(SetDisplayNameDialog, {
var dialog_instance = <SetDisplayNameDialog currentDisplayName={result.displayname} ref={(r) => { currentDisplayName: result.displayname,
ref: (r) => {
dialog_ref = r; dialog_ref = r;
}} onFinished={() => { },
cli.setDisplayName(dialog_ref.getValue()).done(() => { onFinished: (submitted) => {
dialog_defer.resolve(); if (submitted) {
}); cli.setDisplayName(dialog_ref.getValue()).done(() => {
modal.close(); dialog_defer.resolve();
}} /> });
modal = Modal.createDialogWithElement(dialog_instance); }
else {
dialog_defer.reject();
}
}
});
return dialog_defer.promise; return dialog_defer.promise;
} }
}); });
@ -565,6 +571,8 @@ module.exports = React.createClass({
joining: false, joining: false,
joinError: error joinError: error
}); });
if (!error) return;
var msg = error.message ? error.message : JSON.stringify(error); var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
@ -1118,6 +1126,7 @@ module.exports = React.createClass({
spinner={this.state.joining} spinner={this.state.joining}
inviterName={inviterName} inviterName={inviterName}
invitedEmail={invitedEmail} invitedEmail={invitedEmail}
room={this.state.room}
/> />
</div> </div>
<div className="mx_RoomView_messagePanel"></div> <div className="mx_RoomView_messagePanel"></div>
@ -1159,6 +1168,7 @@ module.exports = React.createClass({
inviterName={ inviterName } inviterName={ inviterName }
canJoin={ true } canPreview={ false } canJoin={ true } canPreview={ false }
spinner={this.state.joining} spinner={this.state.joining}
room={this.state.room}
/> />
</div> </div>
<div className="mx_RoomView_messagePanel"></div> <div className="mx_RoomView_messagePanel"></div>
@ -1238,6 +1248,7 @@ module.exports = React.createClass({
inviterName={inviterName} inviterName={inviterName}
invitedEmail={invitedEmail} invitedEmail={invitedEmail}
canPreview={this.state.canPeek} canPreview={this.state.canPeek}
room={this.state.room}
/> />
); );
} }

View file

@ -19,7 +19,7 @@ var ReactDOM = require("react-dom");
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
var q = require("q"); var q = require("q");
var DEBUG_SCROLL = false; var DEBUG_SCROLL = true;
if (DEBUG_SCROLL) { if (DEBUG_SCROLL) {
// using bind means that we get to keep useful line numbers in the console // using bind means that we get to keep useful line numbers in the console

View file

@ -126,9 +126,7 @@ module.exports = React.createClass({
onLogoutClicked: function(ev) { onLogoutClicked: function(ev) {
var LogoutPrompt = sdk.getComponent('dialogs.LogoutPrompt'); var LogoutPrompt = sdk.getComponent('dialogs.LogoutPrompt');
this.logoutModal = Modal.createDialog( this.logoutModal = Modal.createDialog(LogoutPrompt);
LogoutPrompt, {onCancel: this.onLogoutPromptCancel}
);
}, },
onPasswordChangeError: function(err) { onPasswordChangeError: function(err) {
@ -162,10 +160,6 @@ module.exports = React.createClass({
}); });
}, },
onLogoutPromptCancel: function() {
this.logoutModal.closeDialog();
},
onEnableNotificationsChange: function(event) { onEnableNotificationsChange: function(event) {
UserSettingsStore.setEnableNotifications(event.target.checked); UserSettingsStore.setEnableNotifications(event.target.checked);
}, },

View file

@ -31,6 +31,10 @@ module.exports = React.createClass({
displayName: 'ErrorDialog', displayName: 'ErrorDialog',
propTypes: { propTypes: {
title: React.PropTypes.string, title: React.PropTypes.string,
description: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.string,
]),
button: React.PropTypes.string, button: React.PropTypes.string,
focus: React.PropTypes.bool, focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired, onFinished: React.PropTypes.func.isRequired,

View file

@ -20,7 +20,10 @@ module.exports = React.createClass({
displayName: 'QuestionDialog', displayName: 'QuestionDialog',
propTypes: { propTypes: {
title: React.PropTypes.string, title: React.PropTypes.string,
description: React.PropTypes.string, description: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.string,
]),
button: React.PropTypes.string, button: React.PropTypes.string,
focus: React.PropTypes.bool, focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired, onFinished: React.PropTypes.func.isRequired,

View file

@ -54,7 +54,7 @@ module.exports = React.createClass({
onFormSubmit: function(ev) { onFormSubmit: function(ev) {
ev.preventDefault(); ev.preventDefault();
this.props.onFinished(); this.props.onFinished(true);
return false; return false;
}, },

View file

@ -20,7 +20,10 @@ module.exports = React.createClass({
displayName: 'TextInputDialog', displayName: 'TextInputDialog',
propTypes: { propTypes: {
title: React.PropTypes.string, title: React.PropTypes.string,
description: React.PropTypes.string, description: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.string,
]),
value: React.PropTypes.string, value: React.PropTypes.string,
button: React.PropTypes.string, button: React.PropTypes.string,
focus: React.PropTypes.bool, focus: React.PropTypes.bool,

View file

@ -268,10 +268,17 @@ module.exports = React.createClass({
this.readAvatarNode = ReactDom.findDOMNode(node); this.readAvatarNode = ReactDom.findDOMNode(node);
}, },
onMemberAvatarClicked: function(sender) { onMemberAvatarClick: function(event) {
dispatcher.dispatch({ dispatcher.dispatch({
action: 'view_user', 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 = ( avatar = (
<div className="mx_EventTile_avatar"> <div className="mx_EventTile_avatar">
<MemberAvatar member={this.props.mxEvent.sender} width={24} height={24} <MemberAvatar member={this.props.mxEvent.sender} width={24} height={24}
onClick={ this.onMemberAvatarClicked.bind(this, this.props.mxEvent.sender) } /> onClick={ this.onMemberAvatarClick } />
</div> </div>
); );
} }
if (EventTileType.needsSenderProfile()) { if (EventTileType.needsSenderProfile()) {
sender = <SenderProfile mxEvent={this.props.mxEvent} aux={aux} />; sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />;
} }
} }
return ( return (

View file

@ -217,11 +217,8 @@ module.exports = React.createClass({
console.log( console.log(
"Invite %s to %s - isEmail=%s", inputText, this.props.roomId, isEmailAddress "Invite %s to %s - isEmail=%s", inputText, this.props.roomId, isEmailAddress
); );
promise.done(function(res) { promise.then(function(res) {
console.log("Invited %s", inputText); console.log("Invited %s", inputText);
self.setState({
inviting: false
});
}, function(err) { }, function(err) {
if (err !== null) { if (err !== null) {
console.error("Failed to invite: %s", JSON.stringify(err)); console.error("Failed to invite: %s", JSON.stringify(err));
@ -230,9 +227,17 @@ module.exports = React.createClass({
description: err.message description: err.message
}); });
} }
}).finally(function() {
self.setState({ self.setState({
inviting: false inviting: false
}); });
// XXX: hacky focus on the invite box
setTimeout(function() {
var inviteBox = document.getElementById("mx_SearchableEntityList_query");
if (inviteBox) {
inviteBox.focus();
}
}, 0);
}); });
}, },

View file

@ -192,9 +192,29 @@ module.exports = React.createClass({
}, },
onAction: function(payload) { onAction: function(payload) {
var textarea = this.refs.textarea;
switch (payload.action) { switch (payload.action) {
case 'focus_composer': 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; break;
} }
}, },
@ -497,7 +517,7 @@ module.exports = React.createClass({
<MemberAvatar member={me} width={24} height={24} /> <MemberAvatar member={me} width={24} height={24} />
</div> </div>
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }> <div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
<textarea ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder="Type a message..." /> <textarea autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder="Type a message..." />
</div> </div>
<div className="mx_MessageComposer_upload" onClick={this.onUploadClick} title="Upload file"> <div className="mx_MessageComposer_upload" onClick={this.onUploadClick} title="Upload file">
<TintableSvg src="img/upload.svg" width="19" height="24"/> <TintableSvg src="img/upload.svg" width="19" height="24"/>

View file

@ -36,6 +36,7 @@ module.exports = React.createClass({
canJoin: React.PropTypes.bool, canJoin: React.PropTypes.bool,
canPreview: React.PropTypes.bool, canPreview: React.PropTypes.bool,
spinner: React.PropTypes.bool, spinner: React.PropTypes.bool,
room: React.PropTypes.object,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -110,10 +111,13 @@ module.exports = React.createClass({
} }
else if (this.props.canJoin) { else if (this.props.canJoin) {
var name = this.props.room ? this.props.room.name : "";
name = name ? <b>{ name }</b> : "a room";
joinBlock = ( joinBlock = (
<div> <div>
<div className="mx_RoomPreviewBar_join_text"> <div className="mx_RoomPreviewBar_join_text">
Would you like to <a onClick={ this.props.onJoinClick }>join</a> this room? You are trying to access { name }.<br/>
Would you like to <a onClick={ this.props.onJoinClick }>join</a> in order to participate in the discussion?
</div> </div>
</div> </div>
); );