Merge remote-tracking branch 'origin/develop' into rav/base_dialog

pull/21833/head
Richard van der Hoff 2017-01-25 15:01:11 +00:00
commit adf8f50f6e
18 changed files with 162 additions and 135 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
src/component-index.js

View File

@ -32,17 +32,24 @@ module.exports = {
return whoIsTyping; return whoIsTyping;
}, },
whoIsTypingString: function(room) { whoIsTypingString: function(room, limit) {
var whoIsTyping = this.usersTypingApartFromMe(room); const whoIsTyping = this.usersTypingApartFromMe(room);
const othersCount = limit === undefined ?
0 : Math.max(whoIsTyping.length - limit, 0);
if (whoIsTyping.length == 0) { if (whoIsTyping.length == 0) {
return null; return '';
} else if (whoIsTyping.length == 1) { } else if (whoIsTyping.length == 1) {
return whoIsTyping[0].name + ' is typing'; return whoIsTyping[0].name + ' is typing';
}
const names = whoIsTyping.map(function(m) {
return m.name;
});
if (othersCount) {
const other = ' other' + (othersCount > 1 ? 's' : '');
return names.slice(0, limit).join(', ') + ' and ' +
othersCount + other + ' are typing';
} else { } else {
var names = whoIsTyping.map(function(m) { const lastPerson = names.pop();
return m.name;
});
var lastPerson = names.shift();
return names.join(', ') + ' and ' + lastPerson + ' are typing'; return names.join(', ') + ' and ' + lastPerson + ' are typing';
} }
} }

View File

@ -81,8 +81,6 @@ import views$dialogs$ErrorDialog from './components/views/dialogs/ErrorDialog';
views$dialogs$ErrorDialog && (module.exports.components['views.dialogs.ErrorDialog'] = views$dialogs$ErrorDialog); views$dialogs$ErrorDialog && (module.exports.components['views.dialogs.ErrorDialog'] = views$dialogs$ErrorDialog);
import views$dialogs$InteractiveAuthDialog from './components/views/dialogs/InteractiveAuthDialog'; import views$dialogs$InteractiveAuthDialog from './components/views/dialogs/InteractiveAuthDialog';
views$dialogs$InteractiveAuthDialog && (module.exports.components['views.dialogs.InteractiveAuthDialog'] = views$dialogs$InteractiveAuthDialog); views$dialogs$InteractiveAuthDialog && (module.exports.components['views.dialogs.InteractiveAuthDialog'] = views$dialogs$InteractiveAuthDialog);
import views$dialogs$LogoutPrompt from './components/views/dialogs/LogoutPrompt';
views$dialogs$LogoutPrompt && (module.exports.components['views.dialogs.LogoutPrompt'] = views$dialogs$LogoutPrompt);
import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedToRegisterDialog'; import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedToRegisterDialog';
views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog); views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog);
import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog'; import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog';
@ -91,6 +89,8 @@ import views$dialogs$SetDisplayNameDialog from './components/views/dialogs/SetDi
views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog); views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog);
import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog'; import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog';
views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog); views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog);
import views$elements$AccessibleButton from './components/views/elements/AccessibleButton';
views$elements$AccessibleButton && (module.exports.components['views.elements.AccessibleButton'] = views$elements$AccessibleButton);
import views$elements$AddressSelector from './components/views/elements/AddressSelector'; import views$elements$AddressSelector from './components/views/elements/AddressSelector';
views$elements$AddressSelector && (module.exports.components['views.elements.AddressSelector'] = views$elements$AddressSelector); views$elements$AddressSelector && (module.exports.components['views.elements.AddressSelector'] = views$elements$AddressSelector);
import views$elements$AddressTile from './components/views/elements/AddressTile'; import views$elements$AddressTile from './components/views/elements/AddressTile';

View File

@ -21,8 +21,6 @@ var WhoIsTyping = require("../../WhoIsTyping");
var MatrixClientPeg = require("../../MatrixClientPeg"); var MatrixClientPeg = require("../../MatrixClientPeg");
const MemberAvatar = require("../views/avatars/MemberAvatar"); const MemberAvatar = require("../views/avatars/MemberAvatar");
const TYPING_AVATARS_LIMIT = 2;
const HIDE_DEBOUNCE_MS = 10000; const HIDE_DEBOUNCE_MS = 10000;
const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1; const STATUS_BAR_EXPANDED = 1;
@ -53,6 +51,10 @@ module.exports = React.createClass({
// more interesting) // more interesting)
hasActiveCall: React.PropTypes.bool, hasActiveCall: React.PropTypes.bool,
// Number of names to display in typing indication. E.g. set to 3, will
// result in "X, Y, Z and 100 others are typing."
whoIsTypingLimit: React.PropTypes.number,
// callback for when the user clicks on the 'resend all' button in the // callback for when the user clicks on the 'resend all' button in the
// 'unsent messages' bar // 'unsent messages' bar
onResendAllClick: React.PropTypes.func, onResendAllClick: React.PropTypes.func,
@ -77,10 +79,19 @@ module.exports = React.createClass({
onVisible: React.PropTypes.func, onVisible: React.PropTypes.func,
}, },
getDefaultProps: function() {
return {
whoIsTypingLimit: 2,
};
},
getInitialState: function() { getInitialState: function() {
return { return {
syncState: MatrixClientPeg.get().getSyncState(), syncState: MatrixClientPeg.get().getSyncState(),
whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room), whoisTypingString: WhoIsTyping.whoIsTypingString(
this.props.room,
this.props.whoIsTypingLimit
),
}; };
}, },
@ -127,7 +138,10 @@ module.exports = React.createClass({
onRoomMemberTyping: function(ev, member) { onRoomMemberTyping: function(ev, member) {
this.setState({ this.setState({
whoisTypingString: WhoIsTyping.whoIsTypingString(this.props.room), whoisTypingString: WhoIsTyping.whoIsTypingString(
this.props.room,
this.props.whoIsTypingLimit
),
}); });
}, },
@ -194,7 +208,7 @@ module.exports = React.createClass({
if (wantPlaceholder) { if (wantPlaceholder) {
return ( return (
<div className="mx_RoomStatusBar_typingIndicatorAvatars"> <div className="mx_RoomStatusBar_typingIndicatorAvatars">
{this._renderTypingIndicatorAvatars(TYPING_AVATARS_LIMIT)} {this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit)}
</div> </div>
); );
} }

View File

@ -722,15 +722,11 @@ module.exports = React.createClass({
if (!result.displayname) { if (!result.displayname) {
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;
Modal.createDialog(SetDisplayNameDialog, { Modal.createDialog(SetDisplayNameDialog, {
currentDisplayName: result.displayname, currentDisplayName: result.displayname,
ref: (r) => { onFinished: (submitted, newDisplayName) => {
dialog_ref = r;
},
onFinished: (submitted) => {
if (submitted) { if (submitted) {
cli.setDisplayName(dialog_ref.getValue()).done(() => { cli.setDisplayName(newDisplayName).done(() => {
dialog_defer.resolve(); dialog_defer.resolve();
}); });
} }
@ -1531,6 +1527,7 @@ module.exports = React.createClass({
onResize={this.onChildResize} onResize={this.onChildResize}
onVisible={this.onStatusBarVisible} onVisible={this.onStatusBarVisible}
onHidden={this.onStatusBarHidden} onHidden={this.onStatusBarHidden}
whoIsTypingLimit={2}
/>; />;
} }

View File

@ -26,7 +26,7 @@ var UserSettingsStore = require('../../UserSettingsStore');
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
var Email = require('../../email'); var Email = require('../../email');
var AddThreepid = require('../../AddThreepid'); var AddThreepid = require('../../AddThreepid');
var AccessibleButton = require('../views/elements/AccessibleButton'); import AccessibleButton from '../views/elements/AccessibleButton';
// if this looks like a release, use the 'version' from package.json; else use // if this looks like a release, use the 'version' from package.json; else use
// the git sha. // the git sha.
@ -229,8 +229,26 @@ module.exports = React.createClass({
}, },
onLogoutClicked: function(ev) { onLogoutClicked: function(ev) {
var LogoutPrompt = sdk.getComponent('dialogs.LogoutPrompt'); var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
this.logoutModal = Modal.createDialog(LogoutPrompt); Modal.createDialog(QuestionDialog, {
title: "Sign out?",
description:
<div>
For security, logging out will delete any end-to-end encryption keys from this browser,
making previous encrypted chat history unreadable if you log back in.
In future this <a href="https://github.com/vector-im/riot-web/issues/2108">will be improved</a>,
but for now be warned.
</div>,
button: "Sign out",
onFinished: (confirmed) => {
if (confirmed) {
dis.dispatch({action: 'logout'});
if (this.props.onFinished) {
this.props.onFinished();
}
}
},
});
}, },
onPasswordChangeError: function(err) { onPasswordChangeError: function(err) {

View File

@ -87,10 +87,26 @@ module.exports = React.createClass({
this.showErrorDialog("New passwords must match each other."); this.showErrorDialog("New passwords must match each other.");
} }
else { else {
this.submitPasswordReset( var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl, Modal.createDialog(QuestionDialog, {
this.state.email, this.state.password title: "Warning",
); description:
<div>
Resetting password will currently reset any end-to-end encryption keys on all devices,
making encrypted chat history unreadable.
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">may be improved</a>,
but for now be warned.
</div>,
button: "Continue",
onFinished: (confirmed) => {
if (confirmed) {
this.submitPasswordReset(
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl,
this.state.email, this.state.password
);
}
},
});
} }
}, },

View File

@ -19,7 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var AvatarLogic = require("../../../Avatar"); var AvatarLogic = require("../../../Avatar");
import sdk from '../../../index'; import sdk from '../../../index';
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'BaseAvatar', displayName: 'BaseAvatar',

View File

@ -24,7 +24,7 @@ var DMRoomMap = require('../../../utils/DMRoomMap');
var rate_limited_func = require("../../../ratelimitedfunc"); var rate_limited_func = require("../../../ratelimitedfunc");
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
var Modal = require('../../../Modal'); var Modal = require('../../../Modal');
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
const TRUNCATE_QUERY_LIST = 40; const TRUNCATE_QUERY_LIST = 40;
@ -437,7 +437,8 @@ module.exports = React.createClass({
<div className="mx_Dialog_title"> <div className="mx_Dialog_title">
{this.props.title} {this.props.title}
</div> </div>
<AccessibleButton className="mx_ChatInviteDialog_cancel" onClick={this.onCancel} > <AccessibleButton className="mx_ChatInviteDialog_cancel"
onClick={this.onCancel} >
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" /> <TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
</AccessibleButton> </AccessibleButton>
<div className="mx_ChatInviteDialog_label"> <div className="mx_ChatInviteDialog_label">

View File

@ -1,61 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var dis = require("../../../dispatcher");
module.exports = React.createClass({
displayName: 'LogoutPrompt',
propTypes: {
onFinished: React.PropTypes.func,
},
logOut: function() {
dis.dispatch({action: 'logout'});
if (this.props.onFinished) {
this.props.onFinished();
}
},
cancelPrompt: function() {
if (this.props.onFinished) {
this.props.onFinished();
}
},
onKeyDown: function(e) {
if (e.keyCode === 27) { // escape
e.stopPropagation();
e.preventDefault();
this.cancelPrompt();
}
},
render: function() {
return (
<div>
<div className="mx_Dialog_content">
Sign out?
</div>
<div className="mx_Dialog_buttons" onKeyDown={ this.onKeyDown }>
<button className="mx_Dialog_primary" autoFocus onClick={this.logOut}>Sign Out</button>
<button onClick={this.cancelPrompt}>Cancel</button>
</div>
</div>
);
}
});

View File

@ -18,6 +18,11 @@ import React from 'react';
import sdk from '../../../index'; import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
/**
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default React.createClass({ export default React.createClass({
displayName: 'SetDisplayNameDialog', displayName: 'SetDisplayNameDialog',
propTypes: { propTypes: {
@ -42,10 +47,6 @@ export default React.createClass({
this.refs.input_value.select(); this.refs.input_value.select();
}, },
getValue: function() {
return this.state.value;
},
onValueChange: function(ev) { onValueChange: function(ev) {
this.setState({ this.setState({
value: ev.target.value value: ev.target.value
@ -54,7 +55,7 @@ export default React.createClass({
onFormSubmit: function(ev) { onFormSubmit: function(ev) {
ev.preventDefault(); ev.preventDefault();
this.props.onFinished(true); this.props.onFinished(true, this.state.value);
return false; return false;
}, },

View File

@ -17,8 +17,12 @@
import React from 'react'; import React from 'react';
/** /**
* AccessibleButton is a generic wrapper for any element that should be treated as a button. * AccessibleButton is a generic wrapper for any element that should be treated
* Identifies the element as a button, setting proper tab indexing and keyboard activation behavior. * as a button. Identifies the element as a button, setting proper tab
* indexing and keyboard activation behavior.
*
* @param {Object} props react element properties
* @returns {Object} rendered react
*/ */
export default function AccessibleButton(props) { export default function AccessibleButton(props) {
const {element, onClick, children, ...restProps} = props; const {element, onClick, children, ...restProps} = props;
@ -44,7 +48,7 @@ AccessibleButton.propTypes = {
}; };
AccessibleButton.defaultProps = { AccessibleButton.defaultProps = {
element: 'div' element: 'div',
}; };
AccessibleButton.displayName = "AccessibleButton"; AccessibleButton.displayName = "AccessibleButton";

View File

@ -20,7 +20,7 @@ var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index'); var sdk = require('../../../index');
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
var PRESENCE_CLASS = { var PRESENCE_CLASS = {

View File

@ -35,7 +35,7 @@ var DMRoomMap = require('../../../utils/DMRoomMap');
var Unread = require('../../../Unread'); var Unread = require('../../../Unread');
var Receipt = require('../../../utils/Receipt'); var Receipt = require('../../../utils/Receipt');
var WithMatrixClient = require('../../../wrappers/WithMatrixClient'); var WithMatrixClient = require('../../../wrappers/WithMatrixClient');
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
module.exports = WithMatrixClient(React.createClass({ module.exports = WithMatrixClient(React.createClass({
displayName: 'MemberInfo', displayName: 'MemberInfo',
@ -636,20 +636,31 @@ module.exports = WithMatrixClient(React.createClass({
} }
if (this.state.can.kick) { if (this.state.can.kick) {
kickButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onKick}> const membership = this.props.member.membership;
{ this.props.member.membership === "invite" ? "Disinvite" : "Kick" } const kickLabel = membership === "invite" ? "Disinvite" : "Kick";
</AccessibleButton>; kickButton = (
<AccessibleButton className="mx_MemberInfo_field"
onClick={this.onKick}>
{kickLabel}
</AccessibleButton>
);
} }
if (this.state.can.ban) { if (this.state.can.ban) {
banButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onBan}> banButton = (
Ban <AccessibleButton className="mx_MemberInfo_field"
</AccessibleButton>; onClick={this.onBan}>
Ban
</AccessibleButton>
);
} }
if (this.state.can.mute) { if (this.state.can.mute) {
var muteLabel = this.state.muted ? "Unmute" : "Mute"; const muteLabel = this.state.muted ? "Unmute" : "Mute";
muteButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onMuteToggle}> muteButton = (
{muteLabel} <AccessibleButton className="mx_MemberInfo_field"
</AccessibleButton>; onClick={this.onMuteToggle}>
{muteLabel}
</AccessibleButton>
);
} }
if (this.state.can.toggleMod) { if (this.state.can.toggleMod) {
var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator"; var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator";

View File

@ -26,7 +26,7 @@ var rate_limited_func = require('../../../ratelimitedfunc');
var linkify = require('linkifyjs'); var linkify = require('linkifyjs');
var linkifyElement = require('linkifyjs/element'); var linkifyElement = require('linkifyjs/element');
var linkifyMatrix = require('../../../linkify-matrix'); var linkifyMatrix = require('../../../linkify-matrix');
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
linkifyMatrix(linkify); linkifyMatrix(linkify);

View File

@ -26,7 +26,7 @@ var sdk = require('../../../index');
var ContextualMenu = require('../../structures/ContextualMenu'); var ContextualMenu = require('../../structures/ContextualMenu');
var RoomNotifs = require('../../../RoomNotifs'); var RoomNotifs = require('../../../RoomNotifs');
var FormattingUtils = require('../../../utils/FormattingUtils'); var FormattingUtils = require('../../../utils/FormattingUtils');
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
var UserSettingsStore = require('../../../UserSettingsStore'); var UserSettingsStore = require('../../../UserSettingsStore');
module.exports = React.createClass({ module.exports = React.createClass({

View File

@ -19,7 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var sdk = require('../../../index'); var sdk = require('../../../index');
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
/* /*
* A stripped-down room header used for things like the user settings * A stripped-down room header used for things like the user settings

View File

@ -18,8 +18,9 @@ limitations under the License.
var React = require('react'); var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var sdk = require("../../../index"); var sdk = require("../../../index");
var AccessibleButton = require('../elements/AccessibleButton'); import AccessibleButton from '../elements/AccessibleButton';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'ChangePassword', displayName: 'ChangePassword',
@ -66,26 +67,42 @@ module.exports = React.createClass({
changePassword: function(old_password, new_password) { changePassword: function(old_password, new_password) {
var cli = MatrixClientPeg.get(); var cli = MatrixClientPeg.get();
var authDict = { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
type: 'm.login.password', Modal.createDialog(QuestionDialog, {
user: cli.credentials.userId, title: "Warning",
password: old_password description:
}; <div>
Changing password will currently reset any end-to-end encryption keys on all devices,
making encrypted chat history unreadable.
This will be <a href="https://github.com/vector-im/riot-web/issues/2671">improved shortly</a>,
but for now be warned.
</div>,
button: "Continue",
onFinished: (confirmed) => {
if (confirmed) {
var authDict = {
type: 'm.login.password',
user: cli.credentials.userId,
password: old_password
};
this.setState({ this.setState({
phase: this.Phases.Uploading phase: this.Phases.Uploading
});
var self = this;
cli.setPassword(authDict, new_password).then(function() {
self.props.onFinished();
}, function(err) {
self.props.onError(err);
}).finally(function() {
self.setState({
phase: self.Phases.Edit
});
}).done();
}
},
}); });
var self = this;
cli.setPassword(authDict, new_password).then(function() {
self.props.onFinished();
}, function(err) {
self.props.onError(err);
}).finally(function() {
self.setState({
phase: self.Phases.Edit
});
}).done();
}, },
onClickChange: function() { onClickChange: function() {
@ -137,7 +154,8 @@ module.exports = React.createClass({
<input id="password2" type="password" ref="confirm_input" /> <input id="password2" type="password" ref="confirm_input" />
</div> </div>
</div> </div>
<AccessibleButton className={buttonClassName} onClick={this.onClickChange}> <AccessibleButton className={buttonClassName}
onClick={this.onClickChange}>
Change Password Change Password
</AccessibleButton> </AccessibleButton>
</div> </div>