diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 190181c875..98b698ca3e 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -18,6 +18,8 @@ limitations under the License.
import Matrix from 'matrix-js-sdk';
import utils from 'matrix-js-sdk/lib/utils';
+import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';
+import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
const localStorage = window.localStorage;
@@ -104,6 +106,13 @@ class MatrixClientPeg {
this.matrixClient.setMaxListeners(500);
this.matrixClient.setGuest(Boolean(creds.guest));
+
+ var notifTimelineSet = new EventTimelineSet(null, {
+ timelineSupport: true
+ });
+ // XXX: what is our initial pagination token?! it somehow needs to be synchronised with /sync.
+ notifTimelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS);
+ this.matrixClient.setNotifTimelineSet(notifTimelineSet);
}
}
diff --git a/src/Notifier.js b/src/Notifier.js
index 99fef9d671..4390083129 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -190,7 +190,7 @@ var Notifier = {
setToolbarHidden: function(hidden, persistent = true) {
this.toolbarHidden = hidden;
-
+
// XXX: why are we dispatching this here?
// this is nothing to do with notifier_enabled
dis.dispatch({
@@ -224,10 +224,12 @@ var Notifier = {
}
},
- onRoomTimeline: function(ev, room, toStartOfTimeline) {
+ onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
if (toStartOfTimeline) return;
+ if (!room) return;
if (!this.isPrepared) return; // don't alert for any messages initially
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
+ if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions && actions.notify) {
diff --git a/src/Rooms.js b/src/Rooms.js
index 436580371e..281ff842e9 100644
--- a/src/Rooms.js
+++ b/src/Rooms.js
@@ -93,14 +93,12 @@ export function setDMRoom(roomId, userId) {
if (mDirectEvent !== undefined) dmRoomMap = mDirectEvent.getContent();
+ // remove it from the lists of any others users
+ // (it can only be a DM room for one person)
for (const thisUserId of Object.keys(dmRoomMap)) {
const roomList = dmRoomMap[thisUserId];
- if (thisUserId == userId) {
- if (roomList.indexOf(roomId) == -1) {
- roomList.push(roomId);
- }
- } else {
+ if (thisUserId != userId) {
const indexOfRoom = roomList.indexOf(roomId);
if (indexOfRoom > -1) {
roomList.splice(indexOfRoom, 1);
@@ -108,6 +106,14 @@ export function setDMRoom(roomId, userId) {
}
}
+ // now add it, if it's not already there
+ const roomList = dmRoomMap[userId] || [];
+ if (roomList.indexOf(roomId) == -1) {
+ roomList.push(roomId);
+ }
+ dmRoomMap[userId] = roomList;
+
+
return MatrixClientPeg.get().setAccountData('m.direct', dmRoomMap);
}
diff --git a/src/UserActivity.js b/src/UserActivity.js
index 5c80f4743e..e7338e17e9 100644
--- a/src/UserActivity.js
+++ b/src/UserActivity.js
@@ -37,7 +37,8 @@ class UserActivity {
// itself being scrolled. Need to use addEventListener's useCapture.
// also this needs to be the wheel event, not scroll, as scroll is
// fired when the view scrolls down for a new message.
- window.addEventListener('wheel', this._onUserActivity.bind(this), true);
+ window.addEventListener('wheel', this._onUserActivity.bind(this),
+ { passive: true, capture: true });
this.lastActivityAtTs = new Date().getTime();
this.lastDispatchAtTs = 0;
this.activityEndTimer = undefined;
@@ -50,7 +51,8 @@ class UserActivity {
document.onmousedown = undefined;
document.onmousemove = undefined;
document.onkeypress = undefined;
- window.removeEventListener('wheel', this._onUserActivity.bind(this), true);
+ window.removeEventListener('wheel', this._onUserActivity.bind(this),
+ { passive: true, capture: true });
}
/**
diff --git a/src/component-index.js b/src/component-index.js
index c35b287257..11c711d239 100644
--- a/src/component-index.js
+++ b/src/component-index.js
@@ -27,8 +27,10 @@ limitations under the License.
module.exports.components = {};
module.exports.components['structures.ContextualMenu'] = require('./components/structures/ContextualMenu');
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
+module.exports.components['structures.FilePanel'] = require('./components/structures/FilePanel');
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
module.exports.components['structures.MessagePanel'] = require('./components/structures/MessagePanel');
+module.exports.components['structures.NotificationPanel'] = require('./components/structures/NotificationPanel');
module.exports.components['structures.RoomStatusBar'] = require('./components/structures/RoomStatusBar');
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
@@ -47,6 +49,7 @@ module.exports.components['views.create_room.Presets'] = require('./components/v
module.exports.components['views.create_room.RoomAlias'] = require('./components/views/create_room/RoomAlias');
module.exports.components['views.dialogs.ChatInviteDialog'] = require('./components/views/dialogs/ChatInviteDialog');
module.exports.components['views.dialogs.DeactivateAccountDialog'] = require('./components/views/dialogs/DeactivateAccountDialog');
+module.exports.components['views.dialogs.EncryptedEventDialog'] = require('./components/views/dialogs/EncryptedEventDialog');
module.exports.components['views.dialogs.ErrorDialog'] = require('./components/views/dialogs/ErrorDialog');
module.exports.components['views.dialogs.LogoutPrompt'] = require('./components/views/dialogs/LogoutPrompt');
module.exports.components['views.dialogs.MultiInviteDialog'] = require('./components/views/dialogs/MultiInviteDialog');
diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js
new file mode 100644
index 0000000000..0dd16a7e99
--- /dev/null
+++ b/src/components/structures/FilePanel.js
@@ -0,0 +1,121 @@
+/*
+Copyright 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 ReactDOM = require("react-dom");
+
+var Matrix = require("matrix-js-sdk");
+var sdk = require('../../index');
+var MatrixClientPeg = require("../../MatrixClientPeg");
+var dis = require("../../dispatcher");
+
+/*
+ * Component which shows the filtered file using a TimelinePanel
+ */
+var FilePanel = React.createClass({
+ displayName: 'FilePanel',
+
+ propTypes: {
+ roomId: React.PropTypes.string.isRequired,
+ },
+
+ getInitialState: function() {
+ return {
+ timelineSet: null,
+ }
+ },
+
+ componentWillMount: function() {
+ this.updateTimelineSet(this.props.roomId);
+ },
+
+ componentWillReceiveProps: function(nextProps) {
+ if (nextProps.roomId !== this.props.roomId) {
+ // otherwise we race between re-rendering the TimelinePanel and setting the new timelineSet.
+ //
+ // FIXME: this race only happens because of the promise returned by getOrCreateFilter().
+ // We should only need to create the containsUrl filter once per login session, so in practice
+ // it shouldn't be being done here at all. Then we could just update the timelineSet directly
+ // without resetting it first, and speed up room-change.
+ this.setState({ timelineSet: null });
+ this.updateTimelineSet(nextProps.roomId);
+ }
+ },
+
+ updateTimelineSet: function(roomId) {
+ var client = MatrixClientPeg.get();
+ var room = client.getRoom(roomId);
+
+ if (room) {
+ var filter = new Matrix.Filter(client.credentials.userId);
+ filter.setDefinition(
+ {
+ "room": {
+ "timeline": {
+ "contains_url": true
+ },
+ }
+ }
+ );
+
+ // FIXME: we shouldn't be doing this every time we change room - see comment above.
+ client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then(
+ (filterId)=>{
+ filter.filterId = filterId;
+ var timelineSet = room.getOrCreateFilteredTimelineSet(filter);
+ this.setState({ timelineSet: timelineSet });
+ },
+ (error)=>{
+ console.error("Failed to get or create file panel filter", error);
+ }
+ );
+ }
+ else {
+ console.error("Failed to add filtered timelineSet for FilePanel as no room!");
+ }
+ },
+
+ render: function() {
+ // wrap a TimelinePanel with the jump-to-event bits turned off.
+ var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
+ var Loader = sdk.getComponent("elements.Spinner");
+
+ if (this.state.timelineSet) {
+ // console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " +
+ // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId);
+ return (
+
+ );
+ }
+ else {
+ return (
+
+
+
+ );
+ }
+ },
+});
+
+module.exports = FilePanel;
diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index 73ea2fd1a0..31ef15b6dc 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -60,6 +60,9 @@ module.exports = React.createClass({
// true to suppress the date at the start of the timeline
suppressFirstDateSeparator: React.PropTypes.bool,
+ // whether to show read receipts
+ manageReadReceipts: React.PropTypes.bool,
+
// true if updates to the event list should cause the scroll panel to
// scroll down when we are at the bottom of the window. See ScrollPanel
// for more details.
@@ -73,6 +76,12 @@ module.exports = React.createClass({
// opacity for dynamic UI fading effects
opacity: React.PropTypes.number,
+
+ // className for the panel
+ className: React.PropTypes.string.isRequired,
+
+ // shape parameter to be passed to EventTiles
+ tileShape: React.PropTypes.string,
},
componentWillMount: function() {
@@ -337,6 +346,7 @@ module.exports = React.createClass({
continuation = true;
}
+/*
// Work out if this is still a continuation, as we are now showing commands
// and /me messages with their own little avatar. The case of a change of
// event type (commands) is handled above, but we need to handle the /me
@@ -348,6 +358,7 @@ module.exports = React.createClass({
&& prevEvent.getContent().msgtype === 'm.emote') {
continuation = false;
}
+*/
// local echoes have a fake date, which could even be yesterday. Treat them
// as 'today' for the date separators.
@@ -370,7 +381,10 @@ module.exports = React.createClass({
// Local echos have a send "status".
var scrollToken = mxEv.status ? undefined : eventId;
- var readReceipts = this._getReadReceiptsForEvent(mxEv);
+ var readReceipts;
+ if (this.props.manageReadReceipts) {
+ readReceipts = this._getReadReceiptsForEvent(mxEv);
+ }
ret.push(
+ );
+ }
+ },
+});
+
+module.exports = NotificationPanel;
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 041493d420..49d171f631 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -340,8 +340,12 @@ module.exports = React.createClass({
if (this.unmounted) return;
// ignore events for other rooms
+ if (!room) return;
if (!this.state.room || room.roomId != this.state.room.roomId) return;
+ // ignore events from filtered timelines
+ if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
+
if (ev.getType() === "org.matrix.room.preview_urls") {
this._updatePreviewUrlVisibility(room);
}
@@ -1570,7 +1574,9 @@ module.exports = React.createClass({
var messagePanel = (
);
var topUnreadMessagesBar = null;
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index c80a8c89d7..be35e9cbdd 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -50,9 +50,15 @@ var TimelinePanel = React.createClass({
displayName: 'TimelinePanel',
propTypes: {
- // The js-sdk Room object for the room whose timeline we are
- // representing.
- room: React.PropTypes.object.isRequired,
+ // The js-sdk EventTimelineSet object for the timeline sequence we are
+ // representing. This may or may not have a room, depending on what it's
+ // a timeline representing. If it has a room, we maintain RRs etc for
+ // that room.
+ timelineSet: React.PropTypes.object.isRequired,
+
+ // Enable managing RRs and RMs. These require the timelineSet to have a room.
+ manageReadReceipts: React.PropTypes.bool,
+ manageReadMarkers: React.PropTypes.bool,
// true to give the component a 'display: none' style.
hidden: React.PropTypes.bool,
@@ -84,6 +90,12 @@ var TimelinePanel = React.createClass({
// maximum number of events to show in a timeline
timelineCap: React.PropTypes.number,
+
+ // classname to use for the messagepanel
+ className: React.PropTypes.string,
+
+ // shape property to be passed to EventTiles
+ tileShape: React.PropTypes.string,
},
statics: {
@@ -97,13 +109,18 @@ var TimelinePanel = React.createClass({
getDefaultProps: function() {
return {
timelineCap: 250,
+ className: 'mx_RoomView_messagePanel',
};
},
getInitialState: function() {
- var initialReadMarker =
- TimelinePanel.roomReadMarkerMap[this.props.room.roomId]
- || this._getCurrentReadReceipt();
+ // XXX: we could track RM per TimelineSet rather than per Room.
+ // but for now we just do it per room for simplicity.
+ if (this.props.manageReadMarkers) {
+ var initialReadMarker =
+ TimelinePanel.roomReadMarkerMap[this.props.timelineSet.room.roomId]
+ || this._getCurrentReadReceipt();
+ }
return {
events: [],
@@ -137,7 +154,7 @@ var TimelinePanel = React.createClass({
canForwardPaginate: false,
// start with the read-marker visible, so that we see its animated
- // disappearance when swtitching into the room.
+ // disappearance when switching into the room.
readMarkerVisible: true,
readMarkerEventId: initialReadMarker,
@@ -163,8 +180,8 @@ var TimelinePanel = React.createClass({
},
componentWillReceiveProps: function(newProps) {
- if (newProps.room !== this.props.room) {
- // throw new Error("changing room on a TimelinePanel is not supported");
+ if (newProps.timelineSet !== this.props.timelineSet) {
+ // throw new Error("changing timelineSet on a TimelinePanel is not supported");
// regrettably, this does happen; in particular, when joining a
// room with /join. In that case, there are two Rooms in
@@ -175,7 +192,7 @@ var TimelinePanel = React.createClass({
//
// for now, just warn about this. But we're going to end up paginating
// both rooms separately, and it's all bad.
- console.warn("Replacing room on a TimelinePanel - confusion may ensue");
+ console.warn("Replacing timelineSet on a TimelinePanel - confusion may ensue");
}
if (newProps.eventId != this.props.eventId) {
@@ -280,11 +297,13 @@ var TimelinePanel = React.createClass({
this.props.onScroll();
}
- // we hide the read marker when it first comes onto the screen, but if
- // it goes back off the top of the screen (presumably because the user
- // clicks on the 'jump to bottom' button), we need to re-enable it.
- if (this.getReadMarkerPosition() < 0) {
- this.setState({readMarkerVisible: true});
+ if (this.props.manageReadMarkers) {
+ // we hide the read marker when it first comes onto the screen, but if
+ // it goes back off the top of the screen (presumably because the user
+ // clicks on the 'jump to bottom' button), we need to re-enable it.
+ if (this.getReadMarkerPosition() < 0) {
+ this.setState({readMarkerVisible: true});
+ }
}
},
@@ -304,8 +323,8 @@ var TimelinePanel = React.createClass({
},
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
- // ignore events for other rooms
- if (room !== this.props.room) return;
+ // ignore events for other timeline sets
+ if (data.timeline.getTimelineSet() !== this.props.timelineSet) return;
// ignore anything but real-time updates at the end of the room:
// updates from pagination will happen when the paginate completes.
@@ -337,40 +356,42 @@ var TimelinePanel = React.createClass({
var lastEv = events[events.length-1];
// if we're at the end of the live timeline, append the pending events
- if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
- events.push(... this.props.room.getPendingEvents());
+ if (this.props.timelineSet.room && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
+ events.push(... this.props.timelineSet.room.getPendingEvents());
}
var updatedState = {events: events};
- // when a new event arrives when the user is not watching the
- // window, but the window is in its auto-scroll mode, make sure the
- // read marker is visible.
- //
- // We ignore events we have sent ourselves; we don't want to see the
- // read-marker when a remote echo of an event we have just sent takes
- // more than the timeout on userCurrentlyActive.
- //
- var myUserId = MatrixClientPeg.get().credentials.userId;
- var sender = ev.sender ? ev.sender.userId : null;
- var callback = null;
- if (sender != myUserId && !UserActivity.userCurrentlyActive()) {
- updatedState.readMarkerVisible = true;
- } else if(lastEv && this.getReadMarkerPosition() === 0) {
- // we know we're stuckAtBottom, so we can advance the RM
- // immediately, to save a later render cycle
- this._setReadMarker(lastEv.getId(), lastEv.getTs(), true);
- updatedState.readMarkerVisible = false;
- updatedState.readMarkerEventId = lastEv.getId();
- callback = this.props.onReadMarkerUpdated;
+ if (this.props.manageReadMarkers) {
+ // when a new event arrives when the user is not watching the
+ // window, but the window is in its auto-scroll mode, make sure the
+ // read marker is visible.
+ //
+ // We ignore events we have sent ourselves; we don't want to see the
+ // read-marker when a remote echo of an event we have just sent takes
+ // more than the timeout on userCurrentlyActive.
+ //
+ var myUserId = MatrixClientPeg.get().credentials.userId;
+ var sender = ev.sender ? ev.sender.userId : null;
+ var callback = null;
+ if (sender != myUserId && !UserActivity.userCurrentlyActive()) {
+ updatedState.readMarkerVisible = true;
+ } else if(lastEv && this.getReadMarkerPosition() === 0) {
+ // we know we're stuckAtBottom, so we can advance the RM
+ // immediately, to save a later render cycle
+ this._setReadMarker(lastEv.getId(), lastEv.getTs(), true);
+ updatedState.readMarkerVisible = false;
+ updatedState.readMarkerEventId = lastEv.getId();
+ callback = this.props.onReadMarkerUpdated;
+ }
}
this.setState(updatedState, callback);
});
},
- onRoomTimelineReset: function(room) {
- if (room !== this.props.room) return;
+ onRoomTimelineReset: function(room, timelineSet) {
+ if (timelineSet !== this.props.timelineSet) return;
if (this.refs.messagePanel && this.refs.messagePanel.isAtBottom()) {
this._loadTimeline();
@@ -381,7 +402,7 @@ var TimelinePanel = React.createClass({
if (this.unmounted) return;
// ignore events for other rooms
- if (room !== this.props.room) return;
+ if (room !== this.props.timelineSet.room) return;
// we could skip an update if the event isn't in our timeline,
// but that's probably an early optimisation.
@@ -392,7 +413,7 @@ var TimelinePanel = React.createClass({
if (this.unmounted) return;
// ignore events for other rooms
- if (room !== this.props.room) return;
+ if (room !== this.props.timelineSet.room) return;
this.forceUpdate();
},
@@ -401,7 +422,7 @@ var TimelinePanel = React.createClass({
if (this.unmounted) return;
// ignore events for other rooms
- if (room !== this.props.room) return;
+ if (room !== this.props.timelineSet.room) return;
this._reloadEvents();
},
@@ -409,12 +430,13 @@ var TimelinePanel = React.createClass({
sendReadReceipt: function() {
if (!this.refs.messagePanel) return;
+ if (!this.props.manageReadReceipts) return;
// if we are scrolled to the bottom, do a quick-reset of our unreadNotificationCount
// to avoid having to wait from the remote echo from the homeserver.
if (this.isAtEndOfLiveTimeline()) {
- this.props.room.setUnreadNotificationCount('total', 0);
- this.props.room.setUnreadNotificationCount('highlight', 0);
+ this.props.timelineSet.room.setUnreadNotificationCount('total', 0);
+ this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
// XXX: i'm a bit surprised we don't have to emit an event or dispatch to get this picked up
}
@@ -461,6 +483,7 @@ var TimelinePanel = React.createClass({
// if the read marker is on the screen, we can now assume we've caught up to the end
// of the screen, so move the marker down to the bottom of the screen.
updateReadMarker: function() {
+ if (!this.props.manageReadMarkers) return;
if (this.getReadMarkerPosition() !== 0) {
return;
}
@@ -498,6 +521,8 @@ var TimelinePanel = React.createClass({
// advance the read marker past any events we sent ourselves.
_advanceReadMarkerPastMyEvents: function() {
+ if (!this.props.manageReadMarkers) return;
+
// we call _timelineWindow.getEvents() rather than using
// this.state.events, because react batches the update to the latter, so it
// may not have been updated yet.
@@ -548,11 +573,9 @@ var TimelinePanel = React.createClass({
* the container.
*/
jumpToReadMarker: function() {
- if (!this.refs.messagePanel)
- return;
-
- if (!this.state.readMarkerEventId)
- return;
+ if (!this.props.manageReadMarkers) return;
+ if (!this.refs.messagePanel) return;
+ if (!this.state.readMarkerEventId) return;
// we may not have loaded the event corresponding to the read-marker
// into the _timelineWindow. In that case, attempts to scroll to it
@@ -579,10 +602,12 @@ var TimelinePanel = React.createClass({
/* update the read-up-to marker to match the read receipt
*/
forgetReadMarker: function() {
+ if (!this.props.manageReadMarkers) return;
+
var rmId = this._getCurrentReadReceipt();
// see if we know the timestamp for the rr event
- var tl = this.props.room.getTimelineForEvent(rmId);
+ var tl = this.props.timelineSet.getTimelineForEvent(rmId);
var rmTs;
if (tl) {
var event = tl.getEvents().find((e) => { return e.getId() == rmId });
@@ -622,7 +647,9 @@ var TimelinePanel = React.createClass({
// 0: read marker is visible
// +1: read marker is below the window
getReadMarkerPosition: function() {
- if (!this.refs.messagePanel) { return null; }
+ if (!this.props.manageReadMarkers) return null;
+ if (!this.refs.messagePanel) return null;
+
var ret = this.refs.messagePanel.getReadMarkerPosition();
if (ret !== null) {
return ret;
@@ -630,7 +657,7 @@ var TimelinePanel = React.createClass({
// the messagePanel doesn't know where the read marker is.
// if we know the timestamp of the read marker, make a guess based on that.
- var rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.room.roomId];
+ var rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.timelineSet.roomId];
if (rmTs && this.state.events.length > 0) {
if (rmTs < this.state.events[0].getTs()) {
return -1;
@@ -691,7 +718,7 @@ var TimelinePanel = React.createClass({
*/
_loadTimeline: function(eventId, pixelOffset, offsetBase) {
this._timelineWindow = new Matrix.TimelineWindow(
- MatrixClientPeg.get(), this.props.room,
+ MatrixClientPeg.get(), this.props.timelineSet,
{windowLimit: this.props.timelineCap});
var onLoaded = () => {
@@ -745,7 +772,7 @@ var TimelinePanel = React.createClass({
// go via the dispatcher so that the URL is updated
dis.dispatch({
action: 'view_room',
- room_id: this.props.room.roomId,
+ room_id: this.props.timelineSet.roomId,
});
};
}
@@ -807,7 +834,7 @@ var TimelinePanel = React.createClass({
// if we're at the end of the live timeline, append the pending events
if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
- events.push(... this.props.room.getPendingEvents());
+ events.push(... this.props.timelineSet.getPendingEvents());
}
return events;
@@ -873,11 +900,13 @@ var TimelinePanel = React.createClass({
return null;
var myUserId = client.credentials.userId;
- return this.props.room.getEventReadUpTo(myUserId, ignoreSynthesized);
+ return this.props.timelineSet.room.getEventReadUpTo(myUserId, ignoreSynthesized);
},
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
- if (TimelinePanel.roomReadMarkerMap[this.props.room.roomId] == eventId) {
+ var roomId = this.props.timelineSet.room.roomId;
+
+ if (TimelinePanel.roomReadMarkerMap[roomId] == eventId) {
// don't update the state (and cause a re-render) if there is
// no change to the RM.
return;
@@ -885,11 +914,11 @@ var TimelinePanel = React.createClass({
// ideally we'd sync these via the server, but for now just stash them
// in a map.
- TimelinePanel.roomReadMarkerMap[this.props.room.roomId] = eventId;
+ TimelinePanel.roomReadMarkerMap[roomId] = eventId;
// in order to later figure out if the read marker is
// above or below the visible timeline, we stash the timestamp.
- TimelinePanel.roomReadMarkerTsMap[this.props.room.roomId] = eventTs;
+ TimelinePanel.roomReadMarkerTsMap[roomId] = eventTs;
if (inhibitSetState) {
return;
@@ -919,7 +948,7 @@ var TimelinePanel = React.createClass({
// exist.
if (this.state.timelineLoading) {
return (
-
+
);
@@ -946,11 +975,14 @@ var TimelinePanel = React.createClass({
readMarkerVisible={ this.state.readMarkerVisible }
suppressFirstDateSeparator={ this.state.canBackPaginate }
showUrlPreview = { this.props.showUrlPreview }
+ manageReadReceipts = { this.props.manageReadReceipts }
ourUserId={ MatrixClientPeg.get().credentials.userId }
stickyBottom={ stickyBottom }
onScroll={ this.onMessageListScroll }
onFillRequest={ this.onMessageListFillRequest }
opacity={ this.props.opacity }
+ className={ this.props.className }
+ tileShape={ this.props.tileShape }
/>
);
},
diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js
index 3cfe7303be..e4ed432acd 100644
--- a/src/components/views/dialogs/ChatInviteDialog.js
+++ b/src/components/views/dialogs/ChatInviteDialog.js
@@ -188,7 +188,10 @@ module.exports = React.createClass({
for (let i = 0; i < dmRooms.length; i++) {
let room = MatrixClientPeg.get().getRoom(dmRooms[i]);
if (room) {
- return room;
+ const me = room.getMember(MatrixClientPeg.get().credentials.userId);
+ if (me.membership == 'join') {
+ return room;
+ }
}
}
}
@@ -221,6 +224,17 @@ module.exports = React.createClass({
})
.done();
}
+// // Start the chat
+// createRoom({dmUserId: addr})
+// .catch(function(err) {
+// var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+// Modal.createDialog(ErrorDialog, {
+// title: "Failure to invite user",
+// description: err.toString()
+// });
+// return null;
+// })
+// .done();
// Close - this will happen before the above, as that is async
this.props.onFinished(true, addr);
diff --git a/src/components/views/dialogs/EncryptedEventDialog.js b/src/components/views/dialogs/EncryptedEventDialog.js
new file mode 100644
index 0000000000..c8bcc52a06
--- /dev/null
+++ b/src/components/views/dialogs/EncryptedEventDialog.js
@@ -0,0 +1,123 @@
+/*
+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 sdk = require('../../../index');
+var MatrixClientPeg = require("../../../MatrixClientPeg");
+
+module.exports = React.createClass({
+ displayName: 'EncryptedEventDialog',
+
+ propTypes: {
+ onFinished: React.PropTypes.func,
+ },
+
+ componentWillMount: function() {
+ var client = MatrixClientPeg.get();
+ client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
+ },
+
+ componentWillUnmount: function() {
+ var client = MatrixClientPeg.get();
+ if (client) {
+ client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
+ }
+ },
+
+ refreshDevice: function() {
+ // XXX: gutwrench - is there any reason not to expose this on MatrixClient itself?
+ return MatrixClientPeg.get()._crypto.getDeviceByIdentityKey(
+ this.props.event.getSender(),
+ this.props.event.getWireContent().algorithm,
+ this.props.event.getWireContent().sender_key
+ );
+ },
+
+ getInitialState: function() {
+ return { device: this.refreshDevice() };
+ },
+
+ onDeviceVerificationChanged: function(userId, device) {
+ if (userId == this.props.event.getSender()) {
+ this.setState({ device: this.refreshDevice() });
+ }
+ },
+
+ render: function() {
+ var event = this.props.event;
+ var device = this.state.device;
+
+ var MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
+
+ return (
+
+
+ End-to-end encryption information
+
+
+
+
+
+
Sent by
+
{ event.getSender() }
+
+
+
Sender device name
+
{ device.getDisplayName() }
+
+
+
Sender device ID
+
{ device.deviceId }
+
+
+
Sender device verification:
+
{ MatrixClientPeg.get().isEventSenderVerified(event) ? "verified" : NOT verified }