Indicate to the user when there are unread messages
further down in the chat box.
This commit is contained in:
parent
82ee7f694e
commit
c738d085c4
@ -1369,6 +1369,16 @@
|
||||
color: #FB5D50; }
|
||||
#conversejs .chatbox .chat-body .delayed .chat-msg-me {
|
||||
color: #7EABBB; }
|
||||
#conversejs .chatbox .new-msgs-indicator {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
background-color: #F4A261;
|
||||
color: #FCFDFD;
|
||||
padding: 0.3em;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
z-index: 20; }
|
||||
#conversejs .chatbox .chat-content {
|
||||
position: relative;
|
||||
padding: 0.5em;
|
||||
@ -1455,7 +1465,7 @@
|
||||
box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
margin: 0 0 1px 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 0; }
|
||||
#conversejs .chatbox form.sendXMPPMessage .chat-toolbar ul li {
|
||||
@ -1985,10 +1995,15 @@
|
||||
max-width: 70%;
|
||||
float: left;
|
||||
min-width: 200px; }
|
||||
#conversejs .chatroom .box-flyout .chatroom-body .chat-area .new-msgs-indicator {
|
||||
background-color: #E76F51;
|
||||
max-width: 70%; }
|
||||
#conversejs .chatroom .box-flyout .chatroom-body .chat-area .chat-content {
|
||||
padding: 0 0.5em 0 0.5em; }
|
||||
#conversejs .chatroom .box-flyout .chatroom-body .chat-area.full {
|
||||
max-width: 100%; }
|
||||
#conversejs .chatroom .box-flyout .chatroom-body .chat-area.full .new-msgs-indicator {
|
||||
max-width: 100%; }
|
||||
#conversejs .chatroom .box-flyout .chatroom-body .mentioned {
|
||||
font-weight: bold; }
|
||||
#conversejs .chatroom .box-flyout .chatroom-body .chat-msg-room {
|
||||
|
@ -187,6 +187,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.new-msgs-indicator {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
background-color: $chat-head-color;
|
||||
color: $light-background-color;
|
||||
padding: 0.3em;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
z-index: 20;
|
||||
}
|
||||
.chat-content {
|
||||
position: relative;
|
||||
padding: 0.5em;
|
||||
@ -283,7 +294,7 @@
|
||||
box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
margin: 0 0 1px 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
li {
|
||||
|
@ -50,12 +50,19 @@
|
||||
max-width: 70%;
|
||||
float: left;
|
||||
min-width: $chat-width;
|
||||
.new-msgs-indicator {
|
||||
background-color: $chatroom-head-color;
|
||||
max-width: 70%;
|
||||
}
|
||||
.chat-content {
|
||||
// There's an annoying Chrome box-sizing bug which prevents us from adding 0.5em padding here.
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
&.full {
|
||||
max-width: 100%;
|
||||
.new-msgs-indicator {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.mentioned {
|
||||
|
229
spec/chatbox.js
229
spec/chatbox.js
@ -413,10 +413,146 @@
|
||||
runs(function () {});
|
||||
});
|
||||
|
||||
|
||||
describe("when received from someone else", function () {
|
||||
it("can be received which will open a chatbox and be displayed inside it", function () {
|
||||
spyOn(converse, 'emit');
|
||||
var message = 'This is a received message';
|
||||
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
var msg = $msg({
|
||||
from: sender_jid,
|
||||
to: this.connection.jid,
|
||||
type: 'chat',
|
||||
id: (new Date()).getTime()
|
||||
}).c('body').t(message).up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
|
||||
|
||||
// We don't already have an open chatbox for this user
|
||||
expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
|
||||
|
||||
runs(function () {
|
||||
// onMessage is a handler for received XMPP messages
|
||||
this.chatboxes.onMessage(msg);
|
||||
expect(converse.emit).toHaveBeenCalledWith('message', msg);
|
||||
}.bind(converse));
|
||||
waits(50);
|
||||
runs(function () {
|
||||
// Check that the chatbox and its view now exist
|
||||
var chatbox = this.chatboxes.get(sender_jid);
|
||||
var chatboxview = this.chatboxviews.get(sender_jid);
|
||||
expect(chatbox).toBeDefined();
|
||||
expect(chatboxview).toBeDefined();
|
||||
// Check that the message was received and check the message parameters
|
||||
expect(chatbox.messages.length).toEqual(1);
|
||||
var msg_obj = chatbox.messages.models[0];
|
||||
expect(msg_obj.get('message')).toEqual(message);
|
||||
expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0]);
|
||||
expect(msg_obj.get('sender')).toEqual('them');
|
||||
expect(msg_obj.get('delayed')).toEqual(false);
|
||||
// Now check that the message appears inside the chatbox in the DOM
|
||||
var $chat_content = chatboxview.$el.find('.chat-content');
|
||||
var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
|
||||
expect(msg_txt).toEqual(message);
|
||||
var sender_txt = $chat_content.find('span.chat-msg-them').text();
|
||||
expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
|
||||
}.bind(converse));
|
||||
}.bind(converse));
|
||||
|
||||
it("will cause the chat area to be scrolled down only if it was at the bottom already", function () {
|
||||
// TODO
|
||||
var message = 'This message is received while the chat area is scrolled up';
|
||||
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
test_utils.openChatBoxFor(sender_jid);
|
||||
var chatboxview = converse.chatboxviews.get(sender_jid);
|
||||
spyOn(chatboxview, 'scrollDown').andCallThrough();
|
||||
runs(function () {
|
||||
/* Create enough messages so that there's a
|
||||
* scrollbar.
|
||||
*/
|
||||
for (var i=0; i<20; i++) {
|
||||
converse.chatboxes.onMessage($msg({
|
||||
from: sender_jid,
|
||||
to: converse.connection.jid,
|
||||
type: 'chat',
|
||||
id: (new Date()).getTime()
|
||||
}).c('body').t('Message: '+i).up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
|
||||
}
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
chatboxview.$content.scrollTop(0);
|
||||
});
|
||||
waits(250);
|
||||
runs(function () {
|
||||
converse.chatboxes.onMessage($msg({
|
||||
from: sender_jid,
|
||||
to: converse.connection.jid,
|
||||
type: 'chat',
|
||||
id: (new Date()).getTime()
|
||||
}).c('body').t(message).up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
|
||||
});
|
||||
waits(150);
|
||||
runs(function () {
|
||||
// Now check that the message appears inside the chatbox in the DOM
|
||||
var $chat_content = chatboxview.$el.find('.chat-content');
|
||||
var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
|
||||
expect(msg_txt).toEqual(message);
|
||||
expect(chatboxview.model.get('scrolled')).toBeTruthy();
|
||||
expect(chatboxview.$content.scrollTop()).toBe(0);
|
||||
expect(chatboxview.$('.new-msgs-indicator').is(':visible')).toBeTruthy();
|
||||
// Scroll down again
|
||||
chatboxview.$content.scrollTop(chatboxview.$content[0].scrollHeight);
|
||||
});
|
||||
waits(250);
|
||||
runs(function () {
|
||||
expect(chatboxview.$('.new-msgs-indicator').is(':visible')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it("is ignored if it's intended for a different resource and filter_by_resource is set to true", function () {
|
||||
// Send a message from a different resource
|
||||
var message, sender_jid, msg;
|
||||
spyOn(converse, 'log');
|
||||
spyOn(converse.chatboxes, 'getChatBox').andCallThrough();
|
||||
runs(function () {
|
||||
converse.filter_by_resource = true;
|
||||
sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
msg = $msg({
|
||||
from: sender_jid,
|
||||
to: converse.bare_jid+'/'+"some-other-resource",
|
||||
type: 'chat',
|
||||
id: (new Date()).getTime()
|
||||
}).c('body').t("This message will not be shown").up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
|
||||
converse.chatboxes.onMessage(msg);
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
expect(converse.log).toHaveBeenCalledWith(
|
||||
"onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource", "info");
|
||||
expect(converse.chatboxes.getChatBox).not.toHaveBeenCalled();
|
||||
converse.filter_by_resource = false;
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
message = "This message sent to a different resource will be shown";
|
||||
msg = $msg({
|
||||
from: sender_jid,
|
||||
to: converse.bare_jid+'/'+"some-other-resource",
|
||||
type: 'chat',
|
||||
id: '134234623462346'
|
||||
}).c('body').t(message).up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
|
||||
converse.chatboxes.onMessage(msg);
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
expect(converse.chatboxes.getChatBox).toHaveBeenCalled();
|
||||
var chatboxview = converse.chatboxviews.get(sender_jid);
|
||||
var $chat_content = chatboxview.$el.find('.chat-content:last');
|
||||
var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
|
||||
expect(msg_txt).toEqual(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -426,95 +562,6 @@
|
||||
});
|
||||
});
|
||||
|
||||
it("can be received which will open a chatbox and be displayed inside it", function () {
|
||||
spyOn(converse, 'emit');
|
||||
var message = 'This is a received message';
|
||||
var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
var msg = $msg({
|
||||
from: sender_jid,
|
||||
to: this.connection.jid,
|
||||
type: 'chat',
|
||||
id: (new Date()).getTime()
|
||||
}).c('body').t(message).up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
|
||||
|
||||
// We don't already have an open chatbox for this user
|
||||
expect(this.chatboxes.get(sender_jid)).not.toBeDefined();
|
||||
|
||||
runs(function () {
|
||||
// onMessage is a handler for received XMPP messages
|
||||
this.chatboxes.onMessage(msg);
|
||||
expect(converse.emit).toHaveBeenCalledWith('message', msg);
|
||||
}.bind(converse));
|
||||
waits(50);
|
||||
runs(function () {
|
||||
// Check that the chatbox and its view now exist
|
||||
var chatbox = this.chatboxes.get(sender_jid);
|
||||
var chatboxview = this.chatboxviews.get(sender_jid);
|
||||
expect(chatbox).toBeDefined();
|
||||
expect(chatboxview).toBeDefined();
|
||||
// Check that the message was received and check the message parameters
|
||||
expect(chatbox.messages.length).toEqual(1);
|
||||
var msg_obj = chatbox.messages.models[0];
|
||||
expect(msg_obj.get('message')).toEqual(message);
|
||||
expect(msg_obj.get('fullname')).toEqual(mock.cur_names[0]);
|
||||
expect(msg_obj.get('sender')).toEqual('them');
|
||||
expect(msg_obj.get('delayed')).toEqual(false);
|
||||
// Now check that the message appears inside the chatbox in the DOM
|
||||
var $chat_content = chatboxview.$el.find('.chat-content');
|
||||
var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
|
||||
expect(msg_txt).toEqual(message);
|
||||
var sender_txt = $chat_content.find('span.chat-msg-them').text();
|
||||
expect(sender_txt.match(/^[0-9][0-9]:[0-9][0-9] /)).toBeTruthy();
|
||||
}.bind(converse));
|
||||
}.bind(converse));
|
||||
|
||||
it("is ignored if it's intended for a different resource and filter_by_resource is set to true", function () {
|
||||
// Send a message from a different resource
|
||||
var message, sender_jid, msg;
|
||||
spyOn(converse, 'log');
|
||||
spyOn(converse.chatboxes, 'getChatBox').andCallThrough();
|
||||
runs(function () {
|
||||
converse.filter_by_resource = true;
|
||||
sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
msg = $msg({
|
||||
from: sender_jid,
|
||||
to: converse.bare_jid+'/'+"some-other-resource",
|
||||
type: 'chat',
|
||||
id: (new Date()).getTime()
|
||||
}).c('body').t("This message will not be shown").up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
|
||||
converse.chatboxes.onMessage(msg);
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
expect(converse.log).toHaveBeenCalledWith(
|
||||
"onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource", "info");
|
||||
expect(converse.chatboxes.getChatBox).not.toHaveBeenCalled();
|
||||
converse.filter_by_resource = false;
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
message = "This message sent to a different resource will be shown";
|
||||
msg = $msg({
|
||||
from: sender_jid,
|
||||
to: converse.bare_jid+'/'+"some-other-resource",
|
||||
type: 'chat',
|
||||
id: '134234623462346'
|
||||
}).c('body').t(message).up()
|
||||
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
|
||||
converse.chatboxes.onMessage(msg);
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
expect(converse.chatboxes.getChatBox).toHaveBeenCalled();
|
||||
var chatboxview = converse.chatboxviews.get(sender_jid);
|
||||
var $chat_content = chatboxview.$el.find('.chat-content:last');
|
||||
var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
|
||||
expect(msg_txt).toEqual(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("is ignored if it's a malformed headline message", function () {
|
||||
/* Ideally we wouldn't have to filter out headline
|
||||
* messages, but Prosody gives them the wrong 'type' :(
|
||||
|
@ -216,6 +216,50 @@
|
||||
expect(converse.emit.callCount, 1);
|
||||
});
|
||||
|
||||
it("will cause the chat area to be scrolled down only if it was at the bottom already", function () {
|
||||
var message = 'This message is received while the chat area is scrolled up';
|
||||
test_utils.openChatRoom('lounge', 'localhost', 'dummy');
|
||||
var view = converse.chatboxviews.get('lounge@localhost');
|
||||
spyOn(view, 'scrollDown').andCallThrough();
|
||||
runs(function () {
|
||||
/* Create enough messages so that there's a
|
||||
* scrollbar.
|
||||
*/
|
||||
for (var i=0; i<20; i++) {
|
||||
converse.chatboxes.onMessage(
|
||||
$msg({
|
||||
from: 'lounge@localhost/someone',
|
||||
to: 'dummy@localhost.com',
|
||||
type: 'groupchat',
|
||||
id: (new Date()).getTime(),
|
||||
}).c('body').t('Message: '+i).tree());
|
||||
}
|
||||
});
|
||||
waits(50);
|
||||
runs(function () {
|
||||
view.$content.scrollTop(0);
|
||||
});
|
||||
waits(250);
|
||||
runs(function () {
|
||||
expect(view.model.get('scrolled')).toBeTruthy();
|
||||
converse.chatboxes.onMessage(
|
||||
$msg({
|
||||
from: 'lounge@localhost/someone',
|
||||
to: 'dummy@localhost.com',
|
||||
type: 'groupchat',
|
||||
id: (new Date()).getTime(),
|
||||
}).c('body').t(message).tree());
|
||||
});
|
||||
waits(150);
|
||||
runs(function () {
|
||||
// Now check that the message appears inside the chatbox in the DOM
|
||||
var $chat_content = view.$el.find('.chat-content');
|
||||
var msg_txt = $chat_content.find('.chat-message:last').find('.chat-msg-content').text();
|
||||
expect(msg_txt).toEqual(message);
|
||||
expect(view.$content.scrollTop()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("shows received chatroom subject messages", function () {
|
||||
var text = 'Jabber/XMPP Development | RFCs and Extensions: http://xmpp.org/ | Protocol and XSF discussions: xsf@muc.xmpp.org';
|
||||
var stanza = Strophe.xmlHtmlNode(
|
||||
|
@ -99,6 +99,7 @@
|
||||
show_toolbar: converse.show_toolbar,
|
||||
show_textarea: true,
|
||||
title: this.model.get('fullname'),
|
||||
unread_msgs: __('You have unread messages'),
|
||||
info_close: __('Close this chat box'),
|
||||
label_personal_message: __('Personal message')
|
||||
}
|
||||
@ -333,6 +334,9 @@
|
||||
if (converse.windowState === 'blur' || this.model.get('scrolled', true)) {
|
||||
converse.incrementMsgCounter();
|
||||
}
|
||||
if (this.model.get('scrolled', true)) {
|
||||
this.$el.find('.new-msgs-indicator').removeClass('hidden');
|
||||
}
|
||||
} else {
|
||||
// We remove the "scrolled" flag so that the chat area
|
||||
// gets scrolled down. We always want to scroll down
|
||||
@ -688,15 +692,16 @@
|
||||
// and the user is scrolled away...
|
||||
// Should probably take a look at incrementMsgCounter
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
var is_at_bottom = this.$content.scrollTop() + this.$content.innerHeight() >= this.$content[0].scrollHeight;
|
||||
var is_at_bottom = this.$content.scrollTop() + this.$content.innerHeight() >= this.$content[0].scrollHeight-10;
|
||||
if (is_at_bottom) {
|
||||
this.model.set('scrolled', false);
|
||||
this.$el.find('.new-msgs-indicator').addClass('hidden');
|
||||
} else {
|
||||
// We're not at the bottom of the chat area, so we mark
|
||||
// that the box is in a scrolled-up state.
|
||||
this.model.set('scrolled', true);
|
||||
}
|
||||
}, 50),
|
||||
}, 150),
|
||||
|
||||
scrollDownMessageHeight: function ($message) {
|
||||
if (this.$content.is(':visible') && !this.model.get('scrolled')) {
|
||||
@ -708,6 +713,7 @@
|
||||
scrollDown: function () {
|
||||
if (this.$content.is(':visible') && !this.model.get('scrolled')) {
|
||||
this.$content.scrollTop(this.$content[0].scrollHeight);
|
||||
this.$el.find('.new-msgs-indicator').addClass('hidden');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -1577,6 +1577,9 @@
|
||||
});
|
||||
|
||||
this.setUpXMLLogging = function () {
|
||||
Strophe.log = function (level, msg) {
|
||||
converse.log(msg, level);
|
||||
};
|
||||
if (this.debug) {
|
||||
this.connection.xmlInput = function (body) { converse.log(body.outerHTML); };
|
||||
this.connection.xmlOutput = function (body) { converse.log(body.outerHTML); };
|
||||
|
@ -93,6 +93,7 @@
|
||||
show_toolbar: converse.show_toolbar,
|
||||
show_textarea: false,
|
||||
title: this.model.get('fullname'),
|
||||
unread_msgs: __('You have unread messages'),
|
||||
info_close: __('Close this box'),
|
||||
info_minimize: __('Minimize this box'),
|
||||
label_personal_message: ''
|
||||
|
@ -216,6 +216,7 @@
|
||||
this.$('.chatroom-body').empty()
|
||||
.append(
|
||||
converse.templates.chatarea({
|
||||
'unread_msgs': __('You have unread messages'),
|
||||
'show_toolbar': converse.show_toolbar,
|
||||
'label_message': __('Message')
|
||||
}))
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div class="chat-area">
|
||||
<div class="chat-content"></div>
|
||||
<div class="new-msgs-indicator hidden">▼ {{ unread_msgs }} ▼</div>
|
||||
<form class="sendXMPPMessage" action="" method="post">
|
||||
{[ if (show_toolbar) { ]}
|
||||
<ul class="chat-toolbar no-text-select"></ul>
|
||||
|
@ -17,6 +17,7 @@
|
||||
</div>
|
||||
<div class="chat-body">
|
||||
<div class="chat-content"></div>
|
||||
<div class="new-msgs-indicator hidden">▼ {{ unread_msgs }} ▼</div>
|
||||
{[ if (show_textarea) { ]}
|
||||
<form class="sendXMPPMessage" action="" method="post">
|
||||
{[ if (show_toolbar) { ]}
|
||||
|
@ -55,7 +55,7 @@ require([
|
||||
auto_login: true,
|
||||
jid: 'dummy@localhost',
|
||||
password: 'secret',
|
||||
debug: false
|
||||
debug: true
|
||||
}, function (converse) {
|
||||
window.converse = converse;
|
||||
window.crypto = {
|
||||
|
Loading…
Reference in New Issue
Block a user