;
+ }
+
// wrap a TimelinePanel with the jump-to-event bits turned off.
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
var Loader = sdk.getComponent("elements.Spinner");
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index a64ae0a25c..e559a21e1a 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -127,18 +127,6 @@ export default React.createClass({
var handled = false;
switch (ev.keyCode) {
- case KeyCode.ESCAPE:
-
- // Implemented this way so possible handling for other pages is neater
- switch (this.props.page_type) {
- case PageTypes.UserSettings:
- this.props.onUserSettingsClose();
- handled = true;
- break;
- }
-
- break;
-
case KeyCode.UP:
case KeyCode.DOWN:
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index ee7236bb15..85c12979f6 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -17,27 +17,24 @@ limitations under the License.
import q from 'q';
-var React = require('react');
-var Matrix = require("matrix-js-sdk");
+import React from 'react';
+import Matrix from "matrix-js-sdk";
-var MatrixClientPeg = require("../../MatrixClientPeg");
-var PlatformPeg = require("../../PlatformPeg");
-var SdkConfig = require("../../SdkConfig");
-var ContextualMenu = require("./ContextualMenu");
-var RoomListSorter = require("../../RoomListSorter");
-var UserActivity = require("../../UserActivity");
-var Presence = require("../../Presence");
-var dis = require("../../dispatcher");
+import MatrixClientPeg from "../../MatrixClientPeg";
+import PlatformPeg from "../../PlatformPeg";
+import SdkConfig from "../../SdkConfig";
+import * as RoomListSorter from "../../RoomListSorter";
+import dis from "../../dispatcher";
-var Modal = require("../../Modal");
-var Tinter = require("../../Tinter");
-var sdk = require('../../index');
-var Rooms = require('../../Rooms');
-var linkifyMatrix = require("../../linkify-matrix");
-var Lifecycle = require('../../Lifecycle');
-var PageTypes = require('../../PageTypes');
+import Modal from "../../Modal";
+import Tinter from "../../Tinter";
+import sdk from '../../index';
+import * as Rooms from '../../Rooms';
+import linkifyMatrix from "../../linkify-matrix";
+import * as Lifecycle from '../../Lifecycle';
+import PageTypes from '../../PageTypes';
-var createRoom = require("../../createRoom");
+import createRoom from "../../createRoom";
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
module.exports = React.createClass({
@@ -89,7 +86,7 @@ module.exports = React.createClass({
},
getInitialState: function() {
- var s = {
+ const s = {
loading: true,
screen: undefined,
screenAfterLogin: this.props.initialScreenAfterLogin,
@@ -156,11 +153,9 @@ module.exports = React.createClass({
return this.state.register_hs_url;
} else if (MatrixClientPeg.get()) {
return MatrixClientPeg.get().getHomeserverUrl();
- }
- else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
+ } else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
return window.localStorage.getItem("mx_hs_url");
- }
- else {
+ } else {
return this.getDefaultHsUrl();
}
},
@@ -178,11 +173,9 @@ module.exports = React.createClass({
return this.state.register_is_url;
} else if (MatrixClientPeg.get()) {
return MatrixClientPeg.get().getIdentityServerUrl();
- }
- else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
+ } else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
return window.localStorage.getItem("mx_is_url");
- }
- else {
+ } else {
return this.getDefaultIsUrl();
}
},
@@ -324,28 +317,14 @@ module.exports = React.createClass({
onAction: function(payload) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
- var roomIndexDelta = 1;
+ const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
- var self = this;
switch (payload.action) {
case 'logout':
Lifecycle.logout();
break;
case 'start_registration':
- const params = payload.params || {};
- this.setStateForNewScreen({
- screen: 'register',
- // these params may be undefined, but if they are,
- // unset them from our state: we don't want to
- // resume a previous registration session if the
- // user just clicked 'register'
- register_client_secret: params.client_secret,
- register_session_id: params.session_id,
- register_hs_url: params.hs_url,
- register_is_url: params.is_url,
- register_id_sid: params.sid,
- });
- this.notifyNewScreen('register');
+ this._startRegistration(payload.params || {});
break;
case 'start_login':
if (MatrixClientPeg.get() &&
@@ -362,7 +341,7 @@ module.exports = React.createClass({
break;
case 'start_post_registration':
this.setState({ // don't clobber loggedIn status
- screen: 'post_registration'
+ screen: 'post_registration',
});
break;
case 'start_upgrade_registration':
@@ -392,34 +371,7 @@ module.exports = React.createClass({
this.notifyNewScreen('forgot_password');
break;
case 'leave_room':
- const roomToLeave = MatrixClientPeg.get().getRoom(payload.room_id);
- Modal.createDialog(QuestionDialog, {
- title: "Leave room",
- description: Are you sure you want to leave the room {roomToLeave.name}?,
- onFinished: (should_leave) => {
- if (should_leave) {
- const d = MatrixClientPeg.get().leave(payload.room_id);
-
- // FIXME: controller shouldn't be loading a view :(
- const Loader = sdk.getComponent("elements.Spinner");
- const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
-
- d.then(() => {
- modal.close();
- if (this.currentRoomId === payload.room_id) {
- dis.dispatch({action: 'view_next_room'});
- }
- }, (err) => {
- modal.close();
- console.error("Failed to leave room " + payload.room_id + " " + err);
- Modal.createDialog(ErrorDialog, {
- title: "Failed to leave room",
- description: (err && err.message ? err.message : "Server may be unavailable, overloaded, or you hit a bug."),
- });
- });
- }
- }
- });
+ this._leaveRoom(payload.room_id);
break;
case 'reject_invite':
Modal.createDialog(QuestionDialog, {
@@ -440,11 +392,11 @@ module.exports = React.createClass({
modal.close();
Modal.createDialog(ErrorDialog, {
title: "Failed to reject invitation",
- description: err.toString()
+ description: err.toString(),
});
});
}
- }
+ },
});
break;
case 'view_user':
@@ -469,30 +421,13 @@ module.exports = React.createClass({
this._viewRoom(payload);
break;
case 'view_prev_room':
- roomIndexDelta = -1;
+ this._viewNextRoom(-1);
+ break;
case 'view_next_room':
- var allRooms = RoomListSorter.mostRecentActivityFirst(
- MatrixClientPeg.get().getRooms()
- );
- var roomIndex = -1;
- for (var i = 0; i < allRooms.length; ++i) {
- if (allRooms[i].roomId == this.state.currentRoomId) {
- roomIndex = i;
- break;
- }
- }
- roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
- if (roomIndex < 0) roomIndex = allRooms.length - 1;
- this._viewRoom({ room_id: allRooms[roomIndex].roomId });
+ this._viewNextRoom(1);
break;
case 'view_indexed_room':
- var allRooms = RoomListSorter.mostRecentActivityFirst(
- MatrixClientPeg.get().getRooms()
- );
- var roomIndex = payload.roomIndex;
- if (allRooms[roomIndex]) {
- this._viewRoom({ room_id: allRooms[roomIndex].roomId });
- }
+ this._viewIndexedRoom(payload.roomIndex);
break;
case 'view_user_settings':
this._setPage(PageTypes.UserSettings);
@@ -589,7 +524,7 @@ module.exports = React.createClass({
case 'new_version':
this.onVersion(
payload.currentVersion, payload.newVersion,
- payload.releaseNotes
+ payload.releaseNotes,
);
break;
}
@@ -601,6 +536,47 @@ module.exports = React.createClass({
});
},
+ _startRegistration: function(params) {
+ this.setStateForNewScreen({
+ screen: 'register',
+ // these params may be undefined, but if they are,
+ // unset them from our state: we don't want to
+ // resume a previous registration session if the
+ // user just clicked 'register'
+ register_client_secret: params.client_secret,
+ register_session_id: params.session_id,
+ register_hs_url: params.hs_url,
+ register_is_url: params.is_url,
+ register_id_sid: params.sid,
+ });
+ this.notifyNewScreen('register');
+ },
+
+ _viewNextRoom: function(roomIndexDelta) {
+ const allRooms = RoomListSorter.mostRecentActivityFirst(
+ MatrixClientPeg.get().getRooms(),
+ );
+ let roomIndex = -1;
+ for (let i = 0; i < allRooms.length; ++i) {
+ if (allRooms[i].roomId == this.state.currentRoomId) {
+ roomIndex = i;
+ break;
+ }
+ }
+ roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
+ if (roomIndex < 0) roomIndex = allRooms.length - 1;
+ this._viewRoom({ room_id: allRooms[roomIndex].roomId });
+ },
+
+ _viewIndexedRoom: function(roomIndex) {
+ const allRooms = RoomListSorter.mostRecentActivityFirst(
+ MatrixClientPeg.get().getRooms(),
+ );
+ if (allRooms[roomIndex]) {
+ this._viewRoom({ room_id: allRooms[roomIndex].roomId });
+ }
+ },
+
// switch view to the given room
//
// @param {Object} room_info Object containing data about the room to be joined
@@ -620,7 +596,7 @@ module.exports = React.createClass({
_viewRoom: function(room_info) {
this.focusComposer = true;
- var newState = {
+ const newState = {
initialEventId: room_info.event_id,
highlightedEventId: room_info.event_id,
initialEventPixelOffset: undefined,
@@ -640,7 +616,7 @@ module.exports = React.createClass({
//
// TODO: do this in RoomView rather than here
if (!room_info.event_id && this.refs.loggedInView) {
- var scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id);
+ const scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id);
if (scrollState) {
newState.initialEventId = scrollState.focussedEvent;
newState.initialEventPixelOffset = scrollState.pixelOffset;
@@ -710,7 +686,7 @@ module.exports = React.createClass({
},
_invite: function(roomId) {
- var ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
+ const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createDialog(ChatInviteDialog, {
title: "Invite new room members",
button: "Send Invites",
@@ -719,6 +695,41 @@ module.exports = React.createClass({
});
},
+ _leaveRoom: function(roomId) {
+ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+
+ const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
+ Modal.createDialog(QuestionDialog, {
+ title: "Leave room",
+ description: Are you sure you want to leave the room {roomToLeave.name}?,
+ onFinished: (shouldLeave) => {
+ if (shouldLeave) {
+ const d = MatrixClientPeg.get().leave(roomId);
+
+ // FIXME: controller shouldn't be loading a view :(
+ const Loader = sdk.getComponent("elements.Spinner");
+ const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
+
+ d.then(() => {
+ modal.close();
+ if (this.currentRoomId === roomId) {
+ dis.dispatch({action: 'view_next_room'});
+ }
+ }, (err) => {
+ modal.close();
+ console.error("Failed to leave room " + roomId + " " + err);
+ Modal.createDialog(ErrorDialog, {
+ title: "Failed to leave room",
+ description: (err && err.message ? err.message :
+ "Server may be unavailable, overloaded, or you hit a bug."),
+ });
+ });
+ }
+ },
+ });
+ },
+
/**
* Called when the sessionloader has finished
*/
@@ -737,6 +748,8 @@ module.exports = React.createClass({
/**
* Called whenever someone changes the theme
+ *
+ * @param {string} theme new theme
*/
_onSetTheme: function(theme) {
if (!theme) {
@@ -745,12 +758,12 @@ module.exports = React.createClass({
// look for the stylesheet elements.
// styleElements is a map from style name to HTMLLinkElement.
- var styleElements = Object.create(null);
- var i, a;
- for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
- var href = a.getAttribute("href");
+ const styleElements = Object.create(null);
+ let a;
+ for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
+ const href = a.getAttribute("href");
// shouldn't we be using the 'title' tag rather than the href?
- var match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
+ const match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
if (match) {
styleElements[match[1]] = a;
}
@@ -773,14 +786,15 @@ module.exports = React.createClass({
// abuse the tinter to change all the SVG's #fff to #2d2d2d
// XXX: obviously this shouldn't be hardcoded here.
Tinter.tintSvgWhite('#2d2d2d');
- }
- else {
+ } else {
Tinter.tintSvgWhite('#ffffff');
}
},
/**
* Called when a new logged in session has started
+ *
+ * @param {string} teamToken
*/
_onLoggedIn: function(teamToken) {
this.setState({
@@ -811,7 +825,7 @@ module.exports = React.createClass({
if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) {
this.showScreen(
this.state.screenAfterLogin.screen,
- this.state.screenAfterLogin.params
+ this.state.screenAfterLogin.params,
);
this.notifyNewScreen(this.state.screenAfterLogin.screen);
this.setState({screenAfterLogin: null});
@@ -852,8 +866,8 @@ module.exports = React.createClass({
* (useful for setting listeners)
*/
_onWillStartClient() {
- var self = this;
- var cli = MatrixClientPeg.get();
+ const self = this;
+ const cli = MatrixClientPeg.get();
// Allow the JS SDK to reap timeline events. This reduces the amount of
// memory consumed as the JS SDK stores multiple distinct copies of room
@@ -894,17 +908,17 @@ module.exports = React.createClass({
cli.on('Call.incoming', function(call) {
dis.dispatch({
action: 'incoming_call',
- call: call
+ call: call,
});
});
cli.on('Session.logged_out', function(call) {
- var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Signed Out",
- description: "For security, this session has been signed out. Please sign in again."
+ description: "For security, this session has been signed out. Please sign in again.",
});
dis.dispatch({
- action: 'logout'
+ action: 'logout',
});
});
cli.on("accountData", function(ev) {
@@ -927,17 +941,17 @@ module.exports = React.createClass({
if (screen == 'register') {
dis.dispatch({
action: 'start_registration',
- params: params
+ params: params,
});
} else if (screen == 'login') {
dis.dispatch({
action: 'start_login',
- params: params
+ params: params,
});
} else if (screen == 'forgot_password') {
dis.dispatch({
action: 'start_password_recovery',
- params: params
+ params: params,
});
} else if (screen == 'new') {
dis.dispatch({
@@ -960,26 +974,26 @@ module.exports = React.createClass({
action: 'start_post_registration',
});
} else if (screen.indexOf('room/') == 0) {
- var segments = screen.substring(5).split('/');
- var roomString = segments[0];
- var eventId = segments[1]; // undefined if no event id given
+ const segments = screen.substring(5).split('/');
+ const roomString = segments[0];
+ const eventId = segments[1]; // undefined if no event id given
// FIXME: sort_out caseConsistency
- var third_party_invite = {
+ const thirdPartyInvite = {
inviteSignUrl: params.signurl,
invitedEmail: params.email,
};
- var oob_data = {
+ const oobData = {
name: params.room_name,
avatarUrl: params.room_avatar_url,
inviterName: params.inviter_name,
};
- var payload = {
+ const payload = {
action: 'view_room',
event_id: eventId,
- third_party_invite: third_party_invite,
- oob_data: oob_data,
+ third_party_invite: thirdPartyInvite,
+ oob_data: oobData,
};
if (roomString[0] == '#') {
payload.room_alias = roomString;
@@ -993,19 +1007,18 @@ module.exports = React.createClass({
dis.dispatch(payload);
}
} else if (screen.indexOf('user/') == 0) {
- var userId = screen.substring(5);
+ const userId = screen.substring(5);
this.setState({ viewUserId: userId });
this._setPage(PageTypes.UserView);
this.notifyNewScreen('user/' + userId);
- var member = new Matrix.RoomMember(null, userId);
+ const member = new Matrix.RoomMember(null, userId);
if (member) {
dis.dispatch({
action: 'view_user',
member: member,
});
}
- }
- else {
+ } else {
console.info("Ignoring showScreen for '%s'", screen);
}
},
@@ -1024,7 +1037,7 @@ module.exports = React.createClass({
onUserClick: function(event, userId) {
event.preventDefault();
- var member = new Matrix.RoomMember(null, userId);
+ const member = new Matrix.RoomMember(null, userId);
if (!member) { return; }
dis.dispatch({
action: 'view_user',
@@ -1034,17 +1047,17 @@ module.exports = React.createClass({
onLogoutClick: function(event) {
dis.dispatch({
- action: 'logout'
+ action: 'logout',
});
event.stopPropagation();
event.preventDefault();
},
handleResize: function(e) {
- var hideLhsThreshold = 1000;
- var showLhsThreshold = 1000;
- var hideRhsThreshold = 820;
- var showRhsThreshold = 820;
+ const hideLhsThreshold = 1000;
+ const showLhsThreshold = 1000;
+ const hideRhsThreshold = 820;
+ const showRhsThreshold = 820;
if (this.state.width > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
dis.dispatch({ action: 'hide_left_panel' });
@@ -1062,10 +1075,10 @@ module.exports = React.createClass({
this.setState({width: window.innerWidth});
},
- onRoomCreated: function(room_id) {
+ onRoomCreated: function(roomId) {
dis.dispatch({
action: "view_room",
- room_id: room_id,
+ room_id: roomId,
});
},
@@ -1099,7 +1112,7 @@ module.exports = React.createClass({
onFinishPostRegistration: function() {
// Don't confuse this with "PageType" which is the middle window to show
this.setState({
- screen: undefined
+ screen: undefined,
});
this.showScreen("settings");
},
@@ -1114,10 +1127,10 @@ module.exports = React.createClass({
},
updateStatusIndicator: function(state, prevState) {
- var notifCount = 0;
+ let notifCount = 0;
- var rooms = MatrixClientPeg.get().getRooms();
- for (var i = 0; i < rooms.length; ++i) {
+ const rooms = MatrixClientPeg.get().getRooms();
+ for (let i = 0; i < rooms.length; ++i) {
if (rooms[i].hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite')) {
notifCount++;
} else if (rooms[i].getUnreadNotificationCount()) {
@@ -1144,19 +1157,18 @@ module.exports = React.createClass({
action: 'view_room',
room_id: this.state.currentRoomId,
});
- }
- else {
+ } else {
dis.dispatch({
action: 'view_room_directory',
});
}
},
- onRoomIdResolved: function(room_id) {
+ onRoomIdResolved: function(roomId) {
// It's the RoomView's resposibility to look up room aliases, but we need the
// ID to pass into things like the Member List, so the Room View tells us when
// its done that resolution so we can display things that take a room ID.
- this.setState({currentRoomId: room_id});
+ this.setState({currentRoomId: roomId});
},
_makeRegistrationUrl: function(params) {
@@ -1179,14 +1191,20 @@ module.exports = React.createClass({
);
}
+
// needs to be before normal PageTypes as you are logged in technically
- else if (this.state.screen == 'post_registration') {
+ if (this.state.screen == 'post_registration') {
const PostRegistration = sdk.getComponent('structures.login.PostRegistration');
return (
);
- } else if (this.state.loggedIn && this.state.ready) {
+ }
+
+ // `ready` and `loggedIn` may be set before `page_type` (because the
+ // latter is set via the dispatcher). If we don't yet have a `page_type`,
+ // keep showing the spinner for now.
+ if (this.state.loggedIn && this.state.ready && this.state.page_type) {
/* for now, we stuff the entirety of our props and state into the LoggedInView.
* we should go through and figure out what we actually need to pass down, as well
* as using something like redux to avoid having a billion bits of state kicking around.
@@ -1269,5 +1287,5 @@ module.exports = React.createClass({
/>
);
}
- }
+ },
});
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 92049bb113..55cefc89af 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -1760,6 +1760,7 @@ module.exports = React.createClass({
oobData={this.props.oobData}
editing={this.state.editingRoomSettings}
saving={this.state.uploadingRoomSettings}
+ inRoom={myMember && myMember.membership === 'join'}
collapsedRhs={ this.props.collapsedRhs }
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index 7c89694a29..8794713501 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -177,8 +177,8 @@ var TimelinePanel = React.createClass({
componentWillMount: function() {
debuglog("TimelinePanel: mounting");
- this.last_rr_sent_event_id = undefined;
- this.last_rm_sent_event_id = undefined;
+ this.lastRRSentEventId = undefined;
+ this.lastRMSentEventId = undefined;
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
@@ -504,12 +504,13 @@ var TimelinePanel = React.createClass({
// very possible have logged out within that timeframe, so check
// we still have a client.
const cli = MatrixClientPeg.get();
- // if no client or client is guest don't send RR
+ // if no client or client is guest don't send RR or RM
if (!cli || cli.isGuest()) return;
- var currentReadUpToEventId = this._getCurrentReadReceipt(true);
- var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);
+ let shouldSendRR = true;
+ const currentRREventId = this._getCurrentReadReceipt(true);
+ const currentRREventIndex = this._indexForEventId(currentRREventId);
// We want to avoid sending out read receipts when we are looking at
// events in the past which are before the latest RR.
//
@@ -523,43 +524,60 @@ var TimelinePanel = React.createClass({
// RRs) - but that is a bit of a niche case. It will sort itself out when
// the user eventually hits the live timeline.
//
- if (currentReadUpToEventId && currentReadUpToEventIndex === null &&
+ if (currentRREventId && currentRREventIndex === null &&
this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
- return;
+ shouldSendRR = false;
}
- var lastReadEventIndex = this._getLastDisplayedEventIndex({
- ignoreOwn: true
+ const lastReadEventIndex = this._getLastDisplayedEventIndex({
+ ignoreOwn: true,
});
- if (lastReadEventIndex === null) return;
+ if (lastReadEventIndex === null) {
+ shouldSendRR = false;
+ }
+ let lastReadEvent = this.state.events[lastReadEventIndex];
+ shouldSendRR = shouldSendRR &&
+ // Only send a RR if the last read event is ahead in the timeline relative to
+ // the current RR event.
+ lastReadEventIndex > currentRREventIndex &&
+ // Only send a RR if the last RR set != the one we would send
+ this.lastRRSentEventId != lastReadEvent.getId();
- var lastReadEvent = this.state.events[lastReadEventIndex];
+ // Only send a RM if the last RM sent != the one we would send
+ const shouldSendRM =
+ this.lastRMSentEventId != this.state.readMarkerEventId;
// we also remember the last read receipt we sent to avoid spamming the
// same one at the server repeatedly
- if ((lastReadEventIndex > currentReadUpToEventIndex &&
- this.last_rr_sent_event_id != lastReadEvent.getId()) ||
- this.last_rm_sent_event_id != this.state.readMarkerEventId) {
-
- this.last_rr_sent_event_id = lastReadEvent.getId();
- this.last_rm_sent_event_id = this.state.readMarkerEventId;
+ if (shouldSendRR || shouldSendRM) {
+ if (shouldSendRR) {
+ this.lastRRSentEventId = lastReadEvent.getId();
+ } else {
+ lastReadEvent = null;
+ }
+ this.lastRMSentEventId = this.state.readMarkerEventId;
+ debuglog('TimelinePanel: Sending Read Markers for ',
+ this.props.timelineSet.room.roomId,
+ 'rm', this.state.readMarkerEventId,
+ lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',
+ );
MatrixClientPeg.get().setRoomReadMarkers(
this.props.timelineSet.room.roomId,
this.state.readMarkerEventId,
- lastReadEvent
+ lastReadEvent, // Could be null, in which case no RR is sent
).catch((e) => {
// /read_markers API is not implemented on this HS, fallback to just RR
- if (e.errcode === 'M_UNRECOGNIZED') {
+ if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
return MatrixClientPeg.get().sendReadReceipt(
- lastReadEvent
+ lastReadEvent,
).catch(() => {
- this.last_rr_sent_event_id = undefined;
+ this.lastRRSentEventId = undefined;
});
}
// it failed, so allow retries next time the user is active
- this.last_rr_sent_event_id = undefined;
- this.last_rm_sent_event_id = undefined;
+ this.lastRRSentEventId = undefined;
+ this.lastRMSentEventId = undefined;
});
// do a quick-reset of our unreadNotificationCount to avoid having
@@ -572,7 +590,6 @@ var TimelinePanel = React.createClass({
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
dis.dispatch({
action: 'on_room_read',
- room: this.props.timelineSet.room,
});
}
}
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 998199b598..ededd8de8b 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -29,6 +29,7 @@ const Email = require('../../email');
const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig');
import AccessibleButton from '../views/elements/AccessibleButton';
+import * as FormattingUtils from '../../utils/FormattingUtils';
// if this looks like a release, use the 'version' from package.json; else use
// the git sha. Prepend version with v, to look like riot-web version
@@ -36,7 +37,7 @@ const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJ
// Simple method to help prettify GH Release Tags and Commit Hashes.
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
-const gHVersionLabel = function(repo, token) {
+const gHVersionLabel = function(repo, token='') {
const match = token.match(semVerRegex);
let url;
if (match && match[1]) { // basic semVer string possibly with commit hash
@@ -151,10 +152,10 @@ module.exports = React.createClass({
getInitialState: function() {
return {
avatarUrl: null,
- threePids: [],
+ threepids: [],
phase: "UserSettings.LOADING", // LOADING, DISPLAY
email_add_pending: false,
- vectorVersion: null,
+ vectorVersion: undefined,
rejectingInvites: false,
};
},
@@ -600,7 +601,12 @@ module.exports = React.createClass({
_renderCryptoInfo: function() {
const client = MatrixClientPeg.get();
const deviceId = client.deviceId;
- const identityKey = client.getDeviceEd25519Key() || "";
+ let identityKey = client.getDeviceEd25519Key();
+ if (!identityKey) {
+ identityKey = "";
+ } else {
+ identityKey = FormattingUtils.formatCryptoKey(identityKey);
+ }
let importExportButtons = null;
@@ -848,6 +854,7 @@ module.exports = React.createClass({
addEmailSection = (
+
- riot-web version: {(this.state.vectorVersion !== null)
+ riot-web version: {(this.state.vectorVersion !== undefined)
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
: 'unknown'
}
diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js
index 02460148b3..0b2ca5225d 100644
--- a/src/components/views/dialogs/BaseDialog.js
+++ b/src/components/views/dialogs/BaseDialog.js
@@ -47,35 +47,18 @@ export default React.createClass({
children: React.PropTypes.node,
},
- componentWillMount: function() {
- this.priorActiveElement = document.activeElement;
- },
-
- componentWillUnmount: function() {
- if (this.priorActiveElement !== null) {
- this.priorActiveElement.focus();
- }
- },
-
- // Don't let key{down,press} events escape the modal. Consume them all.
- _eatKeyEvent: function(e) {
- e.stopPropagation();
- },
-
- // Must be when the key is released (and not pressed) otherwise componentWillUnmount
- // will focus another element which will receive future key events
- _onKeyUp: function(e) {
+ _onKeyDown: function(e) {
if (e.keyCode === KeyCode.ESCAPE) {
+ e.stopPropagation();
e.preventDefault();
this.props.onFinished();
} else if (e.keyCode === KeyCode.ENTER) {
if (this.props.onEnterPressed) {
+ e.stopPropagation();
e.preventDefault();
this.props.onEnterPressed(e);
}
}
- // Consume all keyup events while Modal is open
- e.stopPropagation();
},
_onCancelClick: function(e) {
@@ -84,13 +67,9 @@ export default React.createClass({
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
-
+
return (
-
+
diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js
new file mode 100644
index 0000000000..f9feb718b0
--- /dev/null
+++ b/src/components/views/dialogs/DeviceVerifyDialog.js
@@ -0,0 +1,76 @@
+/*
+Copyright 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations 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.
+*/
+
+import React from 'react';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import sdk from '../../../index';
+import * as FormattingUtils from '../../../utils/FormattingUtils';
+
+export default function DeviceVerifyDialog(props) {
+ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+
+ const key = FormattingUtils.formatCryptoKey(props.device.getFingerprint());
+ const body = (
+
+
+ To verify that this device can be trusted, please contact its
+ owner using some other means (e.g. in person or a phone call)
+ and ask them whether the key they see in their User Settings
+ for this device matches the key below:
+
+
+
+
{ props.device.getDisplayName() }
+
{ props.device.deviceId}
+
{ key }
+
+
+
+ If it matches, press the verify button below.
+ If it doesnt, then someone else is intercepting this device
+ and you probably want to press the blacklist button instead.
+
+
+ In future this verification process will be more sophisticated.
+