diff --git a/src/Invite.js b/src/Invite.js
index de1f9bd1bb..494d5f5662 100644
--- a/src/Invite.js
+++ b/src/Invite.js
@@ -57,4 +57,20 @@ export function inviteToRoom(roomId, addr) {
export function inviteMultipleToRoom(roomId, addrs) {
this.inviter = new MultiInviter(roomId);
return inviter.invite(addrs);
+
+export function isValidAddress(addr) {
+ // Check if the addr is a valid type
+ var addrType = this.getAddressType(addr);
+ if (addrType === "mx") {
+ let user = MatrixClientPeg.get().getUser(addr);
+ if (user) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (addrType === "email") {
+ return true;
+ } else {
+ return false;
+ }
}
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 (
+