diff --git a/skins/base/views/atoms/EditableText.js b/skins/base/views/atoms/EditableText.js
index 07bdc911..a4508744 100644
--- a/skins/base/views/atoms/EditableText.js
+++ b/skins/base/views/atoms/EditableText.js
@@ -44,7 +44,7 @@ module.exports = React.createClass({
onFinish: function(ev) {
if (ev.target.value) {
- this.setValue(ev.target.value);
+ this.setValue(ev.target.value, ev.key === "Enter");
} else {
this.cancelEdit();
}
diff --git a/skins/base/views/molecules/MemberInfo.js b/skins/base/views/molecules/MemberInfo.js
index 19e0dc46..d8559dbb 100644
--- a/skins/base/views/molecules/MemberInfo.js
+++ b/skins/base/views/molecules/MemberInfo.js
@@ -19,11 +19,33 @@ limitations under the License.
var React = require('react');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
-//var MemberInfoController = require("../../../../src/controllers/molecules/MemberInfo");
+var MemberInfoController = require("../../../../src/controllers/molecules/MemberInfo");
module.exports = React.createClass({
displayName: 'MemberInfo',
- //mixins: [MemberInfoController],
+ mixins: [MemberInfoController],
+
+ getDuration: function(time) {
+ if (!time) return;
+ var t = parseInt(time / 1000);
+ var s = t % 60;
+ var m = parseInt(t / 60) % 60;
+ var h = parseInt(t / (60 * 60)) % 24;
+ var d = parseInt(t / (60 * 60 * 24));
+ if (t < 60) {
+ if (t < 0) {
+ return "0s";
+ }
+ return s + "s";
+ }
+ if (t < 60 * 60) {
+ return m + "m";
+ }
+ if (t < 24 * 60 * 60) {
+ return h + "h";
+ }
+ return d + "d ";
+ },
render: function() {
var power;
@@ -31,6 +53,10 @@ module.exports = React.createClass({
var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png";
power = ;
}
+ var activeAgo = "unknown";
+ if (this.state.active >= 0) {
+ activeAgo = this.getDuration(this.state.active);
+ }
return (
@@ -41,9 +67,9 @@ module.exports = React.createClass({
width="128" height="128" alt=""/>
{this.props.member.userId}
- Presence: {this.props.member.presence}
- Last active: {this.props.member.last_active_ago}
- Start chat
+ Presence: {this.state.presence}
+ Last active: {activeAgo}
+ Start chat
);
}
diff --git a/skins/base/views/organisms/MemberList.js b/skins/base/views/organisms/MemberList.js
index 590ebb55..9283a928 100644
--- a/skins/base/views/organisms/MemberList.js
+++ b/skins/base/views/organisms/MemberList.js
@@ -23,6 +23,7 @@ var MemberListController = require("../../../../src/controllers/organisms/Member
var ComponentBroker = require('../../../../src/ComponentBroker');
var MemberTile = ComponentBroker.get("molecules/MemberTile");
+var EditableText = ComponentBroker.get("atoms/EditableText");
module.exports = React.createClass({
@@ -39,6 +40,31 @@ module.exports = React.createClass({
});
},
+ onPopulateInvite: function(inputText, shouldSubmit) {
+ // reset back to placeholder
+ this.refs.invite.setValue("Invite", false, true);
+ if (!shouldSubmit) {
+ return; // enter key wasn't pressed
+ }
+ this.onInvite(inputText);
+ },
+
+ inviteTile: function() {
+ if (this.state.inviting) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ },
+
render: function() {
return (
@@ -49,10 +75,7 @@ module.exports = React.createClass({
Members
{this.makeMemberTiles()}
-
+ {this.inviteTile()}
diff --git a/src/TextForEvent.js b/src/TextForEvent.js
index e302b647..c8f2f71b 100644
--- a/src/TextForEvent.js
+++ b/src/TextForEvent.js
@@ -2,7 +2,7 @@
function textForMemberEvent(ev) {
// XXX: SYJS-16
var senderName = ev.sender ? ev.sender.name : ev.getSender();
- var targetName = ev.target ? ev.target.name : ev.getContent().state_key;
+ var targetName = ev.target ? ev.target.name : ev.getStateKey();
var reason = ev.getContent().reason ? (
" Reason: " + ev.getContent().reason
) : "";
diff --git a/src/controllers/atoms/EditableText.js b/src/controllers/atoms/EditableText.js
index 4d9eb010..15ee58a7 100644
--- a/src/controllers/atoms/EditableText.js
+++ b/src/controllers/atoms/EditableText.js
@@ -55,11 +55,16 @@ module.exports = {
return this.state.value;
},
- setValue: function(val) {
+ setValue: function(val, shouldSubmit, suppressListener) {
+ var self = this;
this.setState({
value: val,
phase: this.Phases.Display,
- }, this.onValueChanged);
+ }, function() {
+ if (!suppressListener) {
+ self.onValueChanged(shouldSubmit);
+ }
+ });
},
edit: function() {
@@ -74,7 +79,7 @@ module.exports = {
});
},
- onValueChanged: function() {
- this.props.onValueChanged(this.state.value);
+ onValueChanged: function(shouldSubmit) {
+ this.props.onValueChanged(this.state.value, shouldSubmit);
},
};
diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js
new file mode 100644
index 00000000..96f8bb8c
--- /dev/null
+++ b/src/controllers/molecules/MemberInfo.js
@@ -0,0 +1,115 @@
+/*
+Copyright 2015 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.
+*/
+
+/*
+ * State vars:
+ * 'presence' : string (online|offline|unavailable etc)
+ * 'active' : number (ms ago; can be -1)
+ */
+
+'use strict';
+var MatrixClientPeg = require("../../MatrixClientPeg");
+var dis = require("../../dispatcher");
+
+module.exports = {
+ componentDidMount: function() {
+ var self = this;
+ function updateUserState(event, user) {
+ if (!self.props.member) { return; }
+
+ if (user.userId === self.props.member.userId) {
+ self.setState({
+ presence: user.presence,
+ active: user.lastActiveAgo
+ });
+ }
+ }
+ MatrixClientPeg.get().on("User.presence", updateUserState);
+ this.userPresenceFn = updateUserState;
+
+ if (this.props.member) {
+ var usr = MatrixClientPeg.get().getUser(this.props.member.userId);
+ if (!usr) {
+ return;
+ }
+ this.setState({
+ presence: usr.presence,
+ active: usr.lastActiveAgo
+ });
+ }
+ },
+
+ componentWillUnmount: function() {
+ MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
+ },
+
+ onChatClick: function() {
+ // check if there are any existing rooms with just us and them (1:1)
+ // If so, just view that room. If not, create a private room with them.
+ var rooms = MatrixClientPeg.get().getRooms();
+ var userIds = [
+ this.props.member.userId,
+ MatrixClientPeg.get().credentials.userId
+ ];
+ var existingRoomId = null;
+ for (var i = 0; i < rooms.length; i++) {
+ var members = rooms[i].getJoinedMembers();
+ if (members.length === 2) {
+ var hasTargetUsers = true;
+ for (var j = 0; j < members.length; j++) {
+ if (userIds.indexOf(members[j].userId) === -1) {
+ hasTargetUsers = false;
+ break;
+ }
+ }
+ if (hasTargetUsers) {
+ existingRoomId = rooms[i].roomId;
+ break;
+ }
+ }
+ }
+
+ if (existingRoomId) {
+ dis.dispatch({
+ action: 'view_room',
+ room_id: existingRoomId
+ });
+ }
+ else {
+ MatrixClientPeg.get().createRoom({
+ invite: [this.props.member.userId],
+ preset: "private_chat"
+ }).done(function(res) {
+ dis.dispatch({
+ action: 'view_room',
+ room_id: res.room_id
+ });
+ }, function(err) {
+ console.error(
+ "Failed to create room: %s", JSON.stringify(err)
+ );
+ });
+ }
+ },
+
+ getInitialState: function() {
+ return {
+ presence: "offline",
+ active: -1
+ }
+ }
+};
+
diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js
index fb5c0028..6021d0fc 100644
--- a/src/controllers/organisms/MemberList.js
+++ b/src/controllers/organisms/MemberList.js
@@ -61,6 +61,32 @@ module.exports = {
});
},
+ onInvite: function(inputText) {
+ var self = this;
+ // sanity check the input
+ inputText = inputText.trim(); // react requires es5-shim so we know trim() exists
+ if (inputText[0] !== '@' || inputText.indexOf(":") === -1) {
+ console.error("Bad user ID to invite: %s", inputText);
+ return;
+ }
+ self.setState({
+ inviting: true
+ });
+ console.log("Invite %s to %s", inputText, this.props.roomId);
+ MatrixClientPeg.get().invite(this.props.roomId, inputText).done(
+ function(res) {
+ console.log("Invited");
+ self.setState({
+ inviting: false
+ });
+ }, function(err) {
+ console.error("Failed to invite: %s", JSON.stringify(err));
+ self.setState({
+ inviting: false
+ });
+ });
+ },
+
roomMembers: function(limit) {
if (!this.props.roomId) return {};
var cli = MatrixClientPeg.get();