Merge branch 'develop' into wmwragg/direct-chat-sublist

This commit is contained in:
wmwragg 2016-08-23 10:52:50 +01:00
commit 6d1f9003e2
24 changed files with 593 additions and 195 deletions

View file

@ -58,6 +58,10 @@ module.exports = React.createClass({
// called when the session load completes
onLoadCompleted: React.PropTypes.func,
// displayname, if any, to set on the device when logging
// in/registering.
defaultDeviceDisplayName: React.PropTypes.string,
},
PageTypes: {
@ -185,6 +189,7 @@ module.exports = React.createClass({
enableGuest: this.props.enableGuest,
guestHsUrl: this.getCurrentHsUrl(),
guestIsUrl: this.getCurrentIsUrl(),
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
}).done(()=>{
// stuff this through the dispatcher so that it happens
// after the on_logged_in action.
@ -193,7 +198,7 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
this._stopMatrixClient();
Lifecycle.stopMatrixClient();
dis.unregister(this.dispatcherRef);
document.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("focus", this.onFocus);
@ -601,16 +606,6 @@ module.exports = React.createClass({
});
},
// stop all the background processes related to the current client
_stopMatrixClient: function() {
Notifier.stop();
UserActivity.stop();
Presence.stop();
MatrixClientPeg.get().stopClient();
MatrixClientPeg.get().removeAllListeners();
MatrixClientPeg.unset();
},
onKeyDown: function(ev) {
/*
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
@ -935,10 +930,8 @@ module.exports = React.createClass({
var NewVersionBar = sdk.getComponent('globals.NewVersionBar');
var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
// work out the HS URL prompts we should show for
console.log("rendering; loading="+this.state.loading+"; screen="+this.state.screen +
"; logged_in="+this.state.logged_in+"; ready="+this.state.ready);
// console.log("rendering; loading="+this.state.loading+"; screen="+this.state.screen +
// "; logged_in="+this.state.logged_in+"; ready="+this.state.ready);
if (this.state.loading) {
var Spinner = sdk.getComponent('elements.Spinner');
@ -1051,6 +1044,7 @@ module.exports = React.createClass({
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
registrationUrl={this.props.registrationUrl}
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
onLoggedIn={this.onRegistered}
onLoginClick={this.onLoginClick}
onRegisterClick={this.onRegisterClick}
@ -1077,6 +1071,7 @@ module.exports = React.createClass({
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
fallbackHsUrl={this.getFallbackHsUrl()}
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
onForgotPasswordClick={this.onForgotPasswordClick}
enableGuest={this.props.enableGuest}
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}

View file

@ -44,6 +44,8 @@ module.exports = React.createClass({
// different home server without confusing users.
fallbackHsUrl: React.PropTypes.string,
defaultDeviceDisplayName: React.PropTypes.string,
// login shouldn't know or care how registration is done.
onRegisterClick: React.PropTypes.func.isRequired,
@ -136,7 +138,9 @@ module.exports = React.createClass({
var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl);
var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl, {
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
});
this._loginLogic = loginLogic;
loginLogic.getFlows().then(function(flows) {

View file

@ -45,6 +45,9 @@ module.exports = React.createClass({
email: React.PropTypes.string,
username: React.PropTypes.string,
guestAccessToken: React.PropTypes.string,
defaultDeviceDisplayName: React.PropTypes.string,
// registration shouldn't know or care how login is done.
onLoginClick: React.PropTypes.func.isRequired,
onCancelClick: React.PropTypes.func
@ -71,7 +74,9 @@ module.exports = React.createClass({
this.dispatcherRef = dis.register(this.onAction);
// attach this to the instance rather than this.state since it isn't UI
this.registerLogic = new Signup.Register(
this.props.customHsUrl, this.props.customIsUrl
this.props.customHsUrl, this.props.customIsUrl, {
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
}
);
this.registerLogic.setClientSecret(this.props.clientSecret);
this.registerLogic.setSessionId(this.props.sessionId);
@ -154,6 +159,7 @@ module.exports = React.createClass({
}
self.props.onLoggedIn({
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: self.registerLogic.getHomeserverUrl(),
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
accessToken: response.access_token
@ -279,7 +285,7 @@ module.exports = React.createClass({
var returnToAppJsx;
if (this.props.onCancelClick) {
returnToAppJsx =
returnToAppJsx =
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
Return to app
</a>

View file

@ -60,7 +60,7 @@ module.exports = React.createClass({
return (
<span className="mx_MFileBody">
<div className="mx_MImageBody_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
<TintableSvg src="img/download.svg" width="12" height="14"/>
Download {text}
</a>

View file

@ -134,7 +134,7 @@ module.exports = React.createClass({
onMouseLeave={this.onImageLeave} />
</a>
<div className="mx_MImageBody_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
<TintableSvg src="img/download.svg" width="12" height="14"/>
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
</a>

View file

@ -123,7 +123,7 @@ module.exports = React.createClass({
<div className="mx_LinkPreviewWidget" >
{ img }
<div className="mx_LinkPreviewWidget_caption">
<div className="mx_LinkPreviewWidget_title"><a href={ this.props.link } target="_blank">{ p["og:title"] }</a></div>
<div className="mx_LinkPreviewWidget_title"><a href={ this.props.link } target="_blank" rel="noopener">{ p["og:title"] }</a></div>
<div className="mx_LinkPreviewWidget_siteName">{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }</div>
<div className="mx_LinkPreviewWidget_description" ref="description">
{ p["og:description"] }

View file

@ -67,6 +67,11 @@ module.exports = React.createClass({
componentWillMount: function() {
this._cancelDeviceList = null;
// only display the devices list if our client supports E2E *and* the
// feature is enabled in the user settings
this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() &&
UserSettingsStore.isFeatureEnabled("e2e_encryption");
this.setState({
existingOneToOneRoomId: this.getExistingOneToOneRoomId()
});
@ -147,6 +152,10 @@ module.exports = React.createClass({
},
onDeviceVerificationChanged: function(userId, device) {
if (!this._enableDevices) {
return;
}
if (userId == this.props.member.userId) {
// no need to re-download the whole thing; just update our copy of
// the list.
@ -170,6 +179,10 @@ module.exports = React.createClass({
},
_downloadDeviceList: function(member) {
if (!this._enableDevices) {
return;
}
var cancelled = false;
this._cancelDeviceList = function() { cancelled = true; }
@ -532,7 +545,7 @@ module.exports = React.createClass({
},
_renderDevices: function() {
if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
if (!this._enableDevices) {
return null;
}

View file

@ -180,7 +180,7 @@ module.exports = React.createClass({
},
_doInvite(address) {
Invite.inviteToRoom(self.props.roomId, address).catch((err) => {
Invite.inviteToRoom(this.props.roomId, address).catch((err) => {
if (err !== null) {
console.error("Failed to invite: %s", JSON.stringify(err));
if (err.errcode == 'M_FORBIDDEN') {
@ -196,7 +196,7 @@ module.exports = React.createClass({
}
}
}).finally(() => {
self.setState({
this.setState({
inviting: false
});
// XXX: hacky focus on the invite box
@ -207,7 +207,7 @@ module.exports = React.createClass({
}
}, 0);
}).done();
self.setState({
this.setState({
inviting: true
});
},
@ -283,7 +283,7 @@ module.exports = React.createClass({
if (inputs.length == 1) {
// for a single address, we just send the invite
promise.done(() => {
this.doInvite(inputs[0]);
this._doInvite(inputs[0]);
});
} else {
// if there are several, display the confirmation/progress dialog

View file

@ -47,16 +47,6 @@ module.exports = React.createClass({
tags[tagName] = ['yep'];
});
var areNotifsMuted = false;
if (!MatrixClientPeg.get().isGuest()) {
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
areNotifsMuted = true;
}
}
}
return {
name: this._yankValueFromEvent("m.room.name", "name"),
topic: this._yankValueFromEvent("m.room.topic", "topic"),
@ -66,7 +56,6 @@ module.exports = React.createClass({
power_levels_changed: false,
tags_changed: false,
tags: tags,
areNotifsMuted: areNotifsMuted,
// isRoomPublished is loaded async in componentWillMount so when the component
// inits, the saved value will always be undefined, however getInitialState()
// is also called from the saving code so we must return the correct value here
@ -188,12 +177,6 @@ module.exports = React.createClass({
}
if (this.state.areNotifsMuted !== originalState.areNotifsMuted) {
promises.push(MatrixClientPeg.get().setRoomMutePushRule(
"global", roomId, this.state.areNotifsMuted
));
}
// power levels
var powerLevels = this._getPowerLevels();
if (powerLevels) {
@ -647,12 +630,6 @@ module.exports = React.createClass({
{ tagsSection }
<div className="mx_RoomSettings_toggles">
<label>
<input type="checkbox" disabled={ cli.isGuest() }
onChange={this._onToggle.bind(this, "areNotifsMuted", true, false)}
defaultChecked={this.state.areNotifsMuted}/>
'Mention only' notifications for this room
</label>
<div className="mx_RoomSettings_settings">
<h3>Who can access this room?</h3>
{ inviteGuestWarning }

View file

@ -22,6 +22,7 @@ var dis = require("../../../dispatcher");
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var ContextualMenu = require('../../structures/ContextualMenu');
var RoomNotifs = require('../../../RoomNotifs');
module.exports = React.createClass({
displayName: 'RoomTile',
@ -43,43 +44,41 @@ module.exports = React.createClass({
},
getInitialState: function() {
var areNotifsMuted = false;
var cli = MatrixClientPeg.get();
if (!cli.isGuest()) {
var roomPushRule = cli.getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
areNotifsMuted = true;
}
}
}
return({
hover : false,
badgeHover : false,
notificationTagMenu: false,
roomTagMenu: false,
areNotifsMuted: areNotifsMuted,
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
});
},
onAction: function(payload) {
switch (payload.action) {
case 'notification_change':
// Is the notification about this room?
if (payload.roomId === this.props.room.roomId) {
this.setState( { areNotifsMuted : payload.areNotifsMuted });
}
break;
_shouldShowNotifBadge: function() {
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
return showBadgeInStates.indexOf(this.state.notifState) > -1;
},
_shouldShowMentionBadge: function() {
return this.state.notifState != RoomNotifs.MUTE;
},
onAccountData: function(accountDataEvent) {
if (accountDataEvent.getType() == 'm.push_rules') {
this.setState({
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
});
}
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
componentWillMount: function() {
MatrixClientPeg.get().on("accountData", this.onAccountData);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
var cli = MatrixClientPeg.get();
if (cli) {
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
}
},
onClick: function() {
@ -179,15 +178,19 @@ module.exports = React.createClass({
var notificationCount = this.props.room.getUnreadNotificationCount();
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
const badges = notifBadges || mentionBadges;
var classes = classNames({
'mx_RoomTile': true,
'mx_RoomTile_selected': this.props.selected,
'mx_RoomTile_unread': this.props.unread,
'mx_RoomTile_unreadNotify': notificationCount > 0 && !this.state.areNotifsMuted,
'mx_RoomTile_highlight': this.props.highlight,
'mx_RoomTile_unreadNotify': notifBadges,
'mx_RoomTile_highlight': mentionBadges,
'mx_RoomTile_invited': (me && me.membership == 'invite'),
'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu,
'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted))
'mx_RoomTile_noBadges': !badges,
});
var avatarClasses = classNames({
@ -214,7 +217,7 @@ module.exports = React.createClass({
if (this.state.badgeHover || this.state.notificationTagMenu) {
badgeContent = "\u00B7\u00B7\u00B7";
} else if (this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) {
} else if (badges) {
var limitedCount = (notificationCount > 99) ? '99+' : notificationCount;
badgeContent = notificationCount ? limitedCount : '!';
} else {
@ -230,7 +233,7 @@ module.exports = React.createClass({
var nameClasses = classNames({
'mx_RoomTile_name': true,
'mx_RoomTile_invite': this.props.isInvite,
'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted) || this.state.badgeHover || this.state.notificationTagMenu,
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.notificationTagMenu,
});
if (this.props.selected) {

View file

@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component {
(error) => {
if (this._unmounted) { return; }
var errtxt;
if (err.httpStatus == 404) {
if (error.httpStatus == 404) {
// 404 probably means the HS doesn't yet support the API.
errtxt = "Your home server does not support device management.";
} else {
@ -127,6 +127,7 @@ export default class DevicesPanel extends React.Component {
return (
<div className={classes}>
<div className="mx_DevicesPanel_header">
<div className="mx_DevicesPanel_deviceId">ID</div>
<div className="mx_DevicesPanel_deviceName">Name</div>
<div className="mx_DevicesPanel_deviceLastSeen">Last seen</div>
<div className="mx_DevicesPanel_deviceButtons"></div>

View file

@ -109,6 +109,9 @@ export default class DevicesPanelEntry extends React.Component {
return (
<div className="mx_DevicesPanel_device">
<div className="mx_DevicesPanel_deviceId">
{device.device_id}
</div>
<div className="mx_DevicesPanel_deviceName">
<EditableTextContainer initialValue={device.display_name}
onSubmit={this._onDisplayNameChanged}