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

This commit is contained in:
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;
},
whoIsTypingString: function(room) {
var whoIsTyping = this.usersTypingApartFromMe(room);
whoIsTypingString: function(room, limit) {
const whoIsTyping = this.usersTypingApartFromMe(room);
const othersCount = limit === undefined ?
0 : Math.max(whoIsTyping.length - limit, 0);
if (whoIsTyping.length == 0) {
return null;
return '';
} else if (whoIsTyping.length == 1) {
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 {
var names = whoIsTyping.map(function(m) {
return m.name;
});
var lastPerson = names.shift();
const lastPerson = names.pop();
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);
import views$dialogs$InteractiveAuthDialog from './components/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';
views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog);
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);
import views$dialogs$TextInputDialog from './components/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';
views$elements$AddressSelector && (module.exports.components['views.elements.AddressSelector'] = views$elements$AddressSelector);
import views$elements$AddressTile from './components/views/elements/AddressTile';

View file

@ -21,8 +21,6 @@ var WhoIsTyping = require("../../WhoIsTyping");
var MatrixClientPeg = require("../../MatrixClientPeg");
const MemberAvatar = require("../views/avatars/MemberAvatar");
const TYPING_AVATARS_LIMIT = 2;
const HIDE_DEBOUNCE_MS = 10000;
const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1;
@ -53,6 +51,10 @@ module.exports = React.createClass({
// more interesting)
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
// 'unsent messages' bar
onResendAllClick: React.PropTypes.func,
@ -77,10 +79,19 @@ module.exports = React.createClass({
onVisible: React.PropTypes.func,
},
getDefaultProps: function() {
return {
whoIsTypingLimit: 2,
};
},
getInitialState: function() {
return {
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) {
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) {
return (
<div className="mx_RoomStatusBar_typingIndicatorAvatars">
{this._renderTypingIndicatorAvatars(TYPING_AVATARS_LIMIT)}
{this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit)}
</div>
);
}

View file

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

View file

@ -26,7 +26,7 @@ var UserSettingsStore = require('../../UserSettingsStore');
var GeminiScrollbar = require('react-gemini-scrollbar');
var Email = require('../../email');
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
// the git sha.
@ -229,8 +229,26 @@ module.exports = React.createClass({
},
onLogoutClicked: function(ev) {
var LogoutPrompt = sdk.getComponent('dialogs.LogoutPrompt');
this.logoutModal = Modal.createDialog(LogoutPrompt);
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
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) {

View file

@ -87,10 +87,26 @@ module.exports = React.createClass({
this.showErrorDialog("New passwords must match each other.");
}
else {
this.submitPasswordReset(
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl,
this.state.email, this.state.password
);
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
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 AvatarLogic = require("../../../Avatar");
import sdk from '../../../index';
var AccessibleButton = require('../elements/AccessibleButton');
import AccessibleButton from '../elements/AccessibleButton';
module.exports = React.createClass({
displayName: 'BaseAvatar',

View file

@ -24,7 +24,7 @@ var DMRoomMap = require('../../../utils/DMRoomMap');
var rate_limited_func = require("../../../ratelimitedfunc");
var dis = require("../../../dispatcher");
var Modal = require('../../../Modal');
var AccessibleButton = require('../elements/AccessibleButton');
import AccessibleButton from '../elements/AccessibleButton';
const TRUNCATE_QUERY_LIST = 40;
@ -437,7 +437,8 @@ module.exports = React.createClass({
<div className="mx_Dialog_title">
{this.props.title}
</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" />
</AccessibleButton>
<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 MatrixClientPeg from '../../../MatrixClientPeg';
/**
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default React.createClass({
displayName: 'SetDisplayNameDialog',
propTypes: {
@ -42,10 +47,6 @@ export default React.createClass({
this.refs.input_value.select();
},
getValue: function() {
return this.state.value;
},
onValueChange: function(ev) {
this.setState({
value: ev.target.value
@ -54,7 +55,7 @@ export default React.createClass({
onFormSubmit: function(ev) {
ev.preventDefault();
this.props.onFinished(true);
this.props.onFinished(true, this.state.value);
return false;
},

View file

@ -17,8 +17,12 @@
import React from 'react';
/**
* AccessibleButton is a generic wrapper for any element that should be treated as a button.
* Identifies the element as a button, setting proper tab indexing and keyboard activation behavior.
* AccessibleButton is a generic wrapper for any element that should be treated
* 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) {
const {element, onClick, children, ...restProps} = props;
@ -26,7 +30,7 @@ export default function AccessibleButton(props) {
restProps.onKeyDown = function(e) {
if (e.keyCode == 13 || e.keyCode == 32) return onClick();
};
restProps.tabIndex = restProps.tabIndex || "0";
restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
return React.createElement(element, restProps, children);
}
@ -44,7 +48,7 @@ AccessibleButton.propTypes = {
};
AccessibleButton.defaultProps = {
element: 'div'
element: 'div',
};
AccessibleButton.displayName = "AccessibleButton";

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ limitations under the License.
var React = require('react');
var sdk = require('../../../index');
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

View file

@ -18,8 +18,9 @@ limitations under the License.
var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var sdk = require("../../../index");
var AccessibleButton = require('../elements/AccessibleButton');
import AccessibleButton from '../elements/AccessibleButton';
module.exports = React.createClass({
displayName: 'ChangePassword',
@ -66,26 +67,42 @@ module.exports = React.createClass({
changePassword: function(old_password, new_password) {
var cli = MatrixClientPeg.get();
var authDict = {
type: 'm.login.password',
user: cli.credentials.userId,
password: old_password
};
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning",
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({
phase: this.Phases.Uploading
this.setState({
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() {
@ -137,7 +154,8 @@ module.exports = React.createClass({
<input id="password2" type="password" ref="confirm_input" />
</div>
</div>
<AccessibleButton className={buttonClassName} onClick={this.onClickChange}>
<AccessibleButton className={buttonClassName}
onClick={this.onClickChange}>
Change Password
</AccessibleButton>
</div>