bug fixes and typing notifications
This commit is contained in:
+4
-4
@@ -234,7 +234,7 @@ html, body {
|
|||||||
.chatBox > li > .chatBox-panel {
|
.chatBox > li > .chatBox-panel {
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
float: right;
|
float: right;
|
||||||
background-color: #EFE;
|
background-color: #DFD;
|
||||||
border: 1px solid #CDC;
|
border: 1px solid #CDC;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
@@ -260,14 +260,14 @@ html, body {
|
|||||||
right: -11px;
|
right: -11px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-top: 11px solid transparent;
|
border-top: 11px solid transparent;
|
||||||
border-left: 11px solid #EFE;
|
border-left: 11px solid #DFD;
|
||||||
border-right: 0 solid #EEF;
|
border-right: 0 solid #DDF;
|
||||||
border-bottom: 11px solid transparent;
|
border-bottom: 11px solid transparent;
|
||||||
content: " ";
|
content: " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatBox > li.chatBox-inverted > .chatBox-panel {
|
.chatBox > li.chatBox-inverted > .chatBox-panel {
|
||||||
background-color: #EEF;
|
background-color: #DDF;
|
||||||
border: 1px solid #CCD;
|
border: 1px solid #CCD;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-24
@@ -24,6 +24,8 @@
|
|||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
var HOST = "api.tinode.co";
|
var HOST = "api.tinode.co";
|
||||||
var API_KEY = "AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K";
|
var API_KEY = "AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K";
|
||||||
|
// Minimum time between two keypress notifications, milliseconds
|
||||||
|
var KEYPRESS_NOTIFICATION = 5*1000;
|
||||||
|
|
||||||
function shortDateFormat(then) {
|
function shortDateFormat(then) {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
@@ -332,12 +334,16 @@
|
|||||||
this.props.onSelected(this.props.topic);
|
this.props.onSelected(this.props.topic);
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var title = this.props.public;
|
||||||
|
if (title.length > 26) {
|
||||||
|
title = title.substring(0, 24) + "...";
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<li className="media contact" onClick={this.handleClick}>
|
<li className="media contact" onClick={this.handleClick}>
|
||||||
<a href="#" className="pull-left"><img src="img/avatar.svg" className="avatar img-circle" alt="" /></a>
|
<a href="#" className="pull-left"><img src="img/avatar.svg" className="avatar img-circle" alt="" /></a>
|
||||||
<div className="media-body">
|
<div className="media-body">
|
||||||
<Contact.Online when={this.props.when} now={this.props.now} />
|
<Contact.Online when={this.props.when} now={this.props.now} />
|
||||||
<strong>{this.props.public}</strong> <Contact.Unread count={this.props.unread} />
|
<strong>{title}</strong> <Contact.Unread count={this.props.unread} />
|
||||||
<p>{this.props.comment}</p>
|
<p>{this.props.comment}</p>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -389,8 +395,10 @@
|
|||||||
if (this.props.data) {
|
if (this.props.data) {
|
||||||
contactNodes = Object.keys(this.props.data).map(function(key) {
|
contactNodes = Object.keys(this.props.data).map(function(key) {
|
||||||
var c = this.props.data[key];
|
var c = this.props.data[key];
|
||||||
|
// FIXME(gene): add support for received/read count
|
||||||
|
var unread = c.seq;
|
||||||
return (
|
return (
|
||||||
<Contact public={c.public} comment={c.private} unread={c.unread}
|
<Contact public={c.public} comment={c.private} unread={unread}
|
||||||
onSelected={this.onSelectionChange} topic={c.topic} key={c.topic} />
|
onSelected={this.onSelectionChange} topic={c.topic} key={c.topic} />
|
||||||
);
|
);
|
||||||
}, this);
|
}, this);
|
||||||
@@ -422,6 +430,7 @@
|
|||||||
/* END Contact list */
|
/* END Contact list */
|
||||||
|
|
||||||
/* BEGIN Conversation panel */
|
/* BEGIN Conversation panel */
|
||||||
|
/* Don't delete it for now: maybe needed for debugging
|
||||||
var messagesList = [
|
var messagesList = [
|
||||||
{
|
{
|
||||||
id: 1, content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet tellus interdum. \
|
id: 1, content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet tellus interdum. \
|
||||||
@@ -449,21 +458,22 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
|
|||||||
{id: 6, content: "No way.", timestamp: new Date(2016, 0, 3, 15, 16, 17, 567), response: false},
|
{id: 6, content: "No way.", timestamp: new Date(2016, 0, 3, 15, 16, 17, 567), response: false},
|
||||||
{id: 7, content: "OK!", timestamp: new Date(2016, 0, 3, 18, 19, 20, 567), response: true}
|
{id: 7, content: "OK!", timestamp: new Date(2016, 0, 3, 18, 19, 20, 567), response: true}
|
||||||
];
|
];
|
||||||
|
*/
|
||||||
|
var received = {
|
||||||
|
SENDING: -1,
|
||||||
|
SERVER: 0,
|
||||||
|
CLIENT: 1,
|
||||||
|
READ: 2
|
||||||
|
};
|
||||||
var Message = React.createClass({
|
var Message = React.createClass({
|
||||||
getInitialState: function() {
|
|
||||||
return {received: 0};
|
|
||||||
},
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var timestamp = shortDateFormat(this.props.timestamp);
|
|
||||||
return (
|
return (
|
||||||
<li className={this.props.response ? 'chatBox-inverted' : null}>
|
<li className={this.props.response ? 'chatBox-inverted' : null}>
|
||||||
<div className="chatBox-panel">
|
<div className="chatBox-panel">
|
||||||
<div className="chatBox-body">
|
<div className="chatBox-body">
|
||||||
<p>{this.props.content}
|
<p>{this.props.content}
|
||||||
<span className="pull-right timestamp">
|
<Message.ReceivedMarker response={this.props.response}
|
||||||
<small className="text-muted">{timestamp}</small> <Message.ReceivedMarker />
|
timestamp={this.props.timestamp} received={this.props.received} />
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -474,30 +484,60 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
|
|||||||
|
|
||||||
/* Received/read indicator */
|
/* Received/read indicator */
|
||||||
Message.ReceivedMarker = React.createClass({
|
Message.ReceivedMarker = React.createClass({
|
||||||
getInitialState: function() {
|
|
||||||
return {received: 0};
|
|
||||||
},
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var checkMark = null;
|
var timestamp = (this.props.received === received.SENDING) ?
|
||||||
if (this.state.received > 0) {
|
"sending ..." :
|
||||||
checkMark = '✔';
|
shortDateFormat(this.props.timestamp);
|
||||||
if (this.state.received > 1) {
|
var marker = null;
|
||||||
checkMark += <span style="margin-left:-0.5em;">✔</span>;
|
// These markers make sense for own messages only.
|
||||||
|
if (!this.props.response) {
|
||||||
|
if (this.props.received === received.SENDING) {
|
||||||
|
marker = '\u231A'; // watch face
|
||||||
|
} else if (this.props.received === received.SERVER || this.props.received === received.CLIENT) {
|
||||||
|
marker = '\u2714'; // checkmark
|
||||||
|
if (this.props.received === received.CLIENT) {
|
||||||
|
var style = {marginLeft: "-0.5em"};
|
||||||
|
marker += <span style={style}>✔</span>;
|
||||||
|
}
|
||||||
|
} else if (this.props.received === received.READ) {
|
||||||
|
marker = '\u1F441'; // open eye
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span>{checkMark}</span>
|
<span className="pull-right timestamp">
|
||||||
|
<small className="text-muted">{timestamp}</small> {marker}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Send message form */
|
/* Send message form */
|
||||||
Message.Send = React.createClass({
|
Message.Send = React.createClass({
|
||||||
|
getInitialState:function() {
|
||||||
|
return {
|
||||||
|
message: '',
|
||||||
|
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
||||||
|
keypressTimestamp: new Date().getTime() - KEYPRESS_NOTIFICATION - 1
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleMessageTyping: function(e) {
|
||||||
|
var newState = {message: e.target.value};
|
||||||
|
var now = new Date().getTime();
|
||||||
|
if (now - this.state.keypressTimestamp > KEYPRESS_NOTIFICATION) {
|
||||||
|
var topic = Tinode.getTopic(this.props.topic);
|
||||||
|
if (topic.isSubscribed()) {
|
||||||
|
topic.noteKeyPress();
|
||||||
|
}
|
||||||
|
newState.keypressTimestamp = now;
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<label htmlFor="sendMessage" className="sr-only">Add topic</label>
|
<label htmlFor="sendMessage" className="sr-only">Add topic</label>
|
||||||
<input id="sendMessage" className="form-control" type="text" placeholder="New message" />
|
<input id="sendMessage" className="form-control" type="text" placeholder="New message"
|
||||||
|
value={this.state.message} onChange={this.handleMessageTyping} />
|
||||||
<span className= "input-group-btn">
|
<span className= "input-group-btn">
|
||||||
<button className= "btn btn-default" type = "button">
|
<button className= "btn btn-default" type = "button">
|
||||||
<i className="glyphicon glyphicon-send"></i>
|
<i className="glyphicon glyphicon-send"></i>
|
||||||
@@ -526,6 +566,7 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
|
|||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
messages: [],
|
messages: [],
|
||||||
|
topic: '',
|
||||||
title: ''
|
title: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -536,17 +577,20 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
|
|||||||
this.propsChange(nextProps);
|
this.propsChange(nextProps);
|
||||||
},
|
},
|
||||||
propsChange: function(props) {
|
propsChange: function(props) {
|
||||||
|
var msgs = [];
|
||||||
var topic = Tinode.getTopic(props.topic);
|
var topic = Tinode.getTopic(props.topic);
|
||||||
// Clear messages cached in the component
|
|
||||||
this.setState({messages: []});
|
|
||||||
if (topic.isSubscribed()) {
|
if (topic.isSubscribed()) {
|
||||||
this.handleInfoChange(topic);
|
this.handleInfoChange(topic);
|
||||||
|
topic.messages(function(msg) {
|
||||||
|
msgs = [msg].concat(msgs);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
topic.onData = this.handleNewMessage;
|
topic.onData = this.handleNewMessage;
|
||||||
topic.onInfoChange = this.handleInfoChange;
|
topic.onInfoChange = this.handleInfoChange;
|
||||||
topic.onSubsUpdated = this.handleSubsUpdated;
|
topic.onSubsUpdated = this.handleSubsUpdated;
|
||||||
topic.subscribe({get: "info sub data"});
|
topic.subscribe({get: "info sub data"});
|
||||||
}
|
}
|
||||||
|
this.setState({messages: msgs, topic: props.topic});
|
||||||
},
|
},
|
||||||
handleInfoChange: function(info) {
|
handleInfoChange: function(info) {
|
||||||
console.log("handleInfoChange: " + info);
|
console.log("handleInfoChange: " + info);
|
||||||
@@ -556,10 +600,15 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
|
|||||||
|
|
||||||
},
|
},
|
||||||
handleNewMessage: function(msg) {
|
handleNewMessage: function(msg) {
|
||||||
this.setState({messages: this.state.messages.concat(msg)});
|
// FIXME(gene): handleNewMessage is bound to all loaded topics as onData. Unbind it when switching topics.
|
||||||
|
// Ignore messages from all other topics but the current one.
|
||||||
|
if (this.state.topic === msg.topic) {
|
||||||
|
this.setState({messages: [msg].concat(this.state.messages)});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
var me = Tinode.getCurrentUserID();
|
var me = Tinode.getCurrentUserID();
|
||||||
|
var topic = Tinode.getTopic(this.state.topic);
|
||||||
var messageNodes = this.state.messages.map(function(msg) {
|
var messageNodes = this.state.messages.map(function(msg) {
|
||||||
var isReply = !(msg.from === me);
|
var isReply = !(msg.from === me);
|
||||||
return (
|
return (
|
||||||
@@ -577,7 +626,7 @@ dignissim a diam a hendrerit. Nulla vel est ut quam rhoncus pulvinar. In hac hab
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-footer" id="sendMessagePanel">
|
<div className="panel-footer" id="sendMessagePanel">
|
||||||
<Message.Send />
|
<Message.Send topic={this.props.topic} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user