{toggleButton}
{summaryContainer}
{expanded ?
: null}
diff --git a/src/components/views/elements/RoomDirectoryButton.js b/src/components/views/elements/RoomDirectoryButton.js
new file mode 100644
index 0000000000..5e68776a15
--- /dev/null
+++ b/src/components/views/elements/RoomDirectoryButton.js
@@ -0,0 +1,38 @@
+/*
+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 sdk from '../../../index';
+import PropTypes from 'prop-types';
+
+const RoomDirectoryButton = function(props) {
+ const ActionButton = sdk.getComponent('elements.ActionButton');
+ return (
+
+ );
+};
+
+RoomDirectoryButton.propTypes = {
+ size: PropTypes.string,
+ tooltip: PropTypes.bool,
+};
+
+export default RoomDirectoryButton;
diff --git a/src/components/views/elements/SettingsButton.js b/src/components/views/elements/SettingsButton.js
new file mode 100644
index 0000000000..c6438da277
--- /dev/null
+++ b/src/components/views/elements/SettingsButton.js
@@ -0,0 +1,38 @@
+/*
+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 sdk from '../../../index';
+import PropTypes from 'prop-types';
+
+const SettingsButton = function(props) {
+ const ActionButton = sdk.getComponent('elements.ActionButton');
+ return (
+
+ );
+};
+
+SettingsButton.propTypes = {
+ size: PropTypes.string,
+ tooltip: PropTypes.bool,
+};
+
+export default SettingsButton;
diff --git a/src/components/views/elements/StartChatButton.js b/src/components/views/elements/StartChatButton.js
new file mode 100644
index 0000000000..747f75d1b3
--- /dev/null
+++ b/src/components/views/elements/StartChatButton.js
@@ -0,0 +1,38 @@
+/*
+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 sdk from '../../../index';
+import PropTypes from 'prop-types';
+
+const StartChatButton = function(props) {
+ const ActionButton = sdk.getComponent('elements.ActionButton');
+ return (
+
+ );
+};
+
+StartChatButton.propTypes = {
+ size: PropTypes.string,
+ tooltip: PropTypes.bool,
+};
+
+export default StartChatButton;
diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js
index ef8fb29cbc..35e6d28b1f 100644
--- a/src/components/views/rooms/LinkPreviewWidget.js
+++ b/src/components/views/rooms/LinkPreviewWidget.js
@@ -100,7 +100,9 @@ module.exports = React.createClass({
render: function() {
var p = this.state.preview;
- if (!p) return
;
+ if (!p || Object.keys(p).length === 0) {
+ return
;
+ }
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
var image = p["og:image"];
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index 88230062fe..0ee3c2082d 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -43,6 +43,7 @@ export default class MessageComposer extends React.Component {
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this.onEvent = this.onEvent.bind(this);
+ this.onPageUnload = this.onPageUnload.bind(this);
this.state = {
autocompleteQuery: '',
@@ -64,12 +65,21 @@ export default class MessageComposer extends React.Component {
// marked as encrypted.
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
MatrixClientPeg.get().on("event", this.onEvent);
+
+ window.addEventListener('beforeunload', this.onPageUnload);
}
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("event", this.onEvent);
}
+ window.removeEventListener('beforeunload', this.onPageUnload);
+ }
+
+ onPageUnload(event) {
+ if (this.messageComposerInput) {
+ this.messageComposerInput.sentHistory.saveLastTextEntry();
+ }
}
onEvent(event) {
diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index 96ff65498f..a595a91ba9 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -1,5 +1,6 @@
/*
Copyright 2015, 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.
@@ -28,8 +29,16 @@ var Rooms = require('../../../Rooms');
import DMRoomMap from '../../../utils/DMRoomMap';
var Receipt = require('../../../utils/Receipt');
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
+import AccessibleButton from '../elements/AccessibleButton';
-var HIDE_CONFERENCE_CHANS = true;
+const HIDE_CONFERENCE_CHANS = true;
+
+const VERBS = {
+ 'm.favourite': 'favourite',
+ 'im.vector.fake.direct': 'tag direct chat',
+ 'im.vector.fake.recent': 'restore',
+ 'm.lowpriority': 'demote',
+};
module.exports = React.createClass({
displayName: 'RoomList',
@@ -53,6 +62,7 @@ module.exports = React.createClass({
getInitialState: function() {
return {
isLoadingLeftRooms: false,
+ totalRoomCount: null,
lists: {},
incomingCall: null,
};
@@ -73,8 +83,7 @@ module.exports = React.createClass({
// lookup for which lists a given roomId is currently in.
this.listsForRoomId = {};
- var s = this.getRoomLists();
- this.setState(s);
+ this.refreshRoomList();
// order of the sublists
//this.listOrder = [];
@@ -97,7 +106,7 @@ module.exports = React.createClass({
if (this.props.selectedRoom) {
constantTimeDispatcher.dispatch(
"RoomTile.select", this.props.selectedRoom, {}
- );
+ );
}
constantTimeDispatcher.dispatch(
"RoomTile.select", nextProps.selectedRoom, { selected: true }
@@ -265,7 +274,7 @@ module.exports = React.createClass({
},
onRoomStateMember: function(ev, state, member) {
- if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId &&
+ if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId &&
ev.getPrevContent() && ev.getPrevContent().membership === "invite")
{
this._delayedRefreshRoomList();
@@ -290,7 +299,7 @@ module.exports = React.createClass({
this._delayedRefreshRoomList();
}
else if (ev.getType() == 'm.push_rules') {
- this._delayedRefreshRoomList();
+ this._delayedRefreshRoomList();
}
},
@@ -317,21 +326,29 @@ module.exports = React.createClass({
// any changes to it incrementally, updating the appropriate sublists
// as needed.
// Alternatively we'd do something magical with Immutable.js or similar.
- this.setState(this.getRoomLists());
+ const lists = this.getRoomLists();
+ let totalRooms = 0;
+ for (const l of Object.values(lists)) {
+ totalRooms += l.length;
+ }
+ this.setState({
+ lists: this.getRoomLists(),
+ totalRoomCount: totalRooms,
+ });
// this._lastRefreshRoomListTs = Date.now();
},
getRoomLists: function() {
var self = this;
- var s = { lists: {} };
+ const lists = {};
- s.lists["im.vector.fake.invite"] = [];
- s.lists["m.favourite"] = [];
- s.lists["im.vector.fake.recent"] = [];
- s.lists["im.vector.fake.direct"] = [];
- s.lists["m.lowpriority"] = [];
- s.lists["im.vector.fake.archived"] = [];
+ lists["im.vector.fake.invite"] = [];
+ lists["m.favourite"] = [];
+ lists["im.vector.fake.recent"] = [];
+ lists["im.vector.fake.direct"] = [];
+ lists["m.lowpriority"] = [];
+ lists["im.vector.fake.archived"] = [];
this.listsForRoomId = {};
var otherTagNames = {};
@@ -341,7 +358,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().getRooms().forEach(function(room) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (!me) return;
-
+
// console.log("room = " + room.name + ", me.membership = " + me.membership +
// ", sender = " + me.events.member.getSender() +
// ", target = " + me.events.member.getStateKey() +
@@ -353,7 +370,7 @@ module.exports = React.createClass({
if (me.membership == "invite") {
self.listsForRoomId[room.roomId].push("im.vector.fake.invite");
- s.lists["im.vector.fake.invite"].push(room);
+ lists["im.vector.fake.invite"].push(room);
}
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
// skip past this room & don't put it in any lists
@@ -366,8 +383,8 @@ module.exports = React.createClass({
if (tagNames.length) {
for (var i = 0; i < tagNames.length; i++) {
var tagName = tagNames[i];
- s.lists[tagName] = s.lists[tagName] || [];
- s.lists[tagName].push(room);
+ lists[tagName] = lists[tagName] || [];
+ lists[tagName].push(room);
self.listsForRoomId[room.roomId].push(tagName);
otherTagNames[tagName] = 1;
}
@@ -375,67 +392,26 @@ module.exports = React.createClass({
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
- s.lists["im.vector.fake.direct"].push(room);
+ lists["im.vector.fake.direct"].push(room);
}
else {
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
- s.lists["im.vector.fake.recent"].push(room);
+ lists["im.vector.fake.recent"].push(room);
}
}
else if (me.membership === "leave") {
self.listsForRoomId[room.roomId].push("im.vector.fake.archived");
- s.lists["im.vector.fake.archived"].push(room);
+ lists["im.vector.fake.archived"].push(room);
}
else {
console.error("unrecognised membership: " + me.membership + " - this should never happen");
}
});
- if (s.lists["im.vector.fake.direct"].length == 0 &&
- MatrixClientPeg.get().getAccountData('m.direct') === undefined &&
- !MatrixClientPeg.get().isGuest())
- {
- // scan through the 'recents' list for any rooms which look like DM rooms
- // and make them DM rooms
- const oldRecents = s.lists["im.vector.fake.recent"];
- s.lists["im.vector.fake.recent"] = [];
-
- for (const room of oldRecents) {
- const me = room.getMember(MatrixClientPeg.get().credentials.userId);
-
- if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
- self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
- s.lists["im.vector.fake.direct"].push(room);
- } else {
- self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
- s.lists["im.vector.fake.recent"].push(room);
- }
- }
-
- // save these new guessed DM rooms into the account data
- const newMDirectEvent = {};
- for (const room of s.lists["im.vector.fake.direct"]) {
- const me = room.getMember(MatrixClientPeg.get().credentials.userId);
- const otherPerson = Rooms.getOnlyOtherMember(room, me);
- if (!otherPerson) continue;
-
- const roomList = newMDirectEvent[otherPerson.userId] || [];
- roomList.push(room.roomId);
- newMDirectEvent[otherPerson.userId] = roomList;
- }
-
- console.warn("Resetting room DM state to be " + JSON.stringify(newMDirectEvent));
-
- // if this fails, fine, we'll just do the same thing next time we get the room lists
- MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
- }
-
- //console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
-
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
// we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
-/*
+/*
this.listOrder = [
"im.vector.fake.invite",
"m.favourite",
@@ -449,7 +425,7 @@ module.exports = React.createClass({
];
*/
- return s;
+ return lists;
},
_getScrollNode: function() {
@@ -479,6 +455,7 @@ module.exports = React.createClass({
var incomingCallBox = document.getElementById("incomingCallBox");
if (incomingCallBox && incomingCallBox.parentElement) {
var scrollArea = this._getScrollNode();
+ if (!scrollArea) return;
// Use the offset of the top of the scroll area from the window
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
@@ -502,6 +479,7 @@ module.exports = React.createClass({
// properly through React
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
var scrollArea = this._getScrollNode();
+ if (!scrollArea) return;
// Use the offset of the top of the scroll area from the window
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
@@ -599,6 +577,58 @@ module.exports = React.createClass({
this.refs.gemscroll.forceUpdate();
},
+ _getEmptyContent: function(section) {
+ const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
+
+ if (this.props.collapsed) {
+ return
;
+ }
+
+ const StartChatButton = sdk.getComponent('elements.StartChatButton');
+ const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
+ const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
+ if (this.state.totalRoomCount === 0) {
+ const TintableSvg = sdk.getComponent('elements.TintableSvg');
+ switch (section) {
+ case 'im.vector.fake.direct':
+ return
+ Press
+
+ to start a chat with someone
+
;
+ case 'im.vector.fake.recent':
+ return
+ You're not in any rooms yet! Press
+
+ to make a room or
+
+ to browse the directory
+
;
+ }
+ }
+
+ const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section);
+
+ return
;
+ },
+
+ _getHeaderItems: function(section) {
+ const StartChatButton = sdk.getComponent('elements.StartChatButton');
+ const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
+ const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
+ switch (section) {
+ case 'im.vector.fake.direct':
+ return
+
+ ;
+ case 'im.vector.fake.recent':
+ return
+
+
+ ;
+ }
+ },
+
render: function() {
var RoomSubList = sdk.getComponent('structures.RoomSubList');
var self = this;
@@ -622,7 +652,7 @@ module.exports = React.createClass({
diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js
index 7fac244481..1aba7c9196 100644
--- a/src/components/views/rooms/SearchResultTile.js
+++ b/src/components/views/rooms/SearchResultTile.js
@@ -60,7 +60,7 @@ module.exports = React.createClass({
}
}
return (
-
);
},
diff --git a/test/components/structures/ScrollPanel-test.js b/test/components/structures/ScrollPanel-test.js
index eacaeb5fb4..7ecb74be6f 100644
--- a/test/components/structures/ScrollPanel-test.js
+++ b/test/components/structures/ScrollPanel-test.js
@@ -115,7 +115,7 @@ var Tester = React.createClass({
//
// there is an extra 50 pixels of margin at the bottom.
return (
-