Commit 7c057910 authored by Novokreshchenov Konstantin's avatar Novokreshchenov Konstantin Committed by JC Brand

Inconsistent unread messages count updating #873 (#874)

* Consistent unread messages count updating on ChatBoxView, Minimized ChatBoxView, RosterView and page's title

* Add tests for checking unread messages count updates in different GUI parts

* Update docs/CHANGES.md

* document windowStateChanged event in events.rst
parent bbc4a0e1
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
`chatbox` attributes, instead of just the stanza. [jcbrand] `chatbox` attributes, instead of just the stanza. [jcbrand]
- #567 Unreaded message count reset on page load [novokrest] - #567 Unreaded message count reset on page load [novokrest]
- #591 Unread message counter is reset when the chatbox is closed [novokrest] - #591 Unread message counter is reset when the chatbox is closed [novokrest]
- #873 Inconsistent unread messages count updating [novokrest]
- Remove all inline CSS to comply with strict Content-Security-Policy headers [mathiasertl] - Remove all inline CSS to comply with strict Content-Security-Policy headers [mathiasertl]
## 3.0.2 (2017-04-23) ## 3.0.2 (2017-04-23)
......
...@@ -283,3 +283,10 @@ serviceDiscovered ...@@ -283,3 +283,10 @@ serviceDiscovered
When converse.js has learned of a service provided by the XMPP server. See XEP-0030. When converse.js has learned of a service provided by the XMPP server. See XEP-0030.
``_converse.on('serviceDiscovered', function (service) { ... });`` ``_converse.on('serviceDiscovered', function (service) { ... });``
windowStateChanged
~~~~~~~~~~~~~~~~~~
When window state has changed. Used to determine when a user left the page and when came back.
``_converse.on('windowStateChanged', function (data) { ... });``
\ No newline at end of file
This diff is collapsed.
...@@ -86,6 +86,15 @@ ...@@ -86,6 +86,15 @@
}, },
}); });
var onWindowStateChanged = function (data) {
var state = data.state;
_converse.chatboxviews.each(function (chatboxview) {
chatboxview.onWindowStateChanged(state);
})
};
_converse.api.listen.on('windowStateChanged', onWindowStateChanged);
_converse.ChatBoxView = Backbone.View.extend({ _converse.ChatBoxView = Backbone.View.extend({
length: 200, length: 200,
tagName: 'div', tagName: 'div',
...@@ -422,12 +431,16 @@ ...@@ -422,12 +431,16 @@
if (this.model.get('scrolled', true)) { if (this.model.get('scrolled', true)) {
this.$el.find('.new-msgs-indicator').removeClass('hidden'); this.$el.find('.new-msgs-indicator').removeClass('hidden');
} }
if (_converse.windowState === 'hidden' || this.model.get('scrolled', true)) { if (this.isNewMessageHidden()) {
_converse.incrementMsgCounter(); this.model.incrementUnreadMsgCounter();
} }
} }
}, },
isNewMessageHidden: function() {
return _converse.windowState === 'hidden' || this.model.isScrolledUp();
},
handleTextMessage: function (message) { handleTextMessage: function (message) {
this.showMessage(_.clone(message.attributes)); this.showMessage(_.clone(message.attributes));
if (message.get('sender') !== 'me') { if (message.get('sender') !== 'me') {
...@@ -844,8 +857,8 @@ ...@@ -844,8 +857,8 @@
(this.$content.scrollTop() + this.$content.innerHeight()) >= (this.$content.scrollTop() + this.$content.innerHeight()) >=
this.$content[0].scrollHeight-10; this.$content[0].scrollHeight-10;
if (is_at_bottom) { if (is_at_bottom) {
this.hideNewMessagesIndicator();
this.model.save('scrolled', false); this.model.save('scrolled', false);
this.onScrolledDown();
} else { } else {
// We're not at the bottom of the chat area, so we mark // We're not at the bottom of the chat area, so we mark
// that the box is in a scrolled-up state. // that the box is in a scrolled-up state.
...@@ -862,11 +875,19 @@ ...@@ -862,11 +875,19 @@
/* Inner method that gets debounced */ /* Inner method that gets debounced */
if (this.$content.is(':visible') && !this.model.get('scrolled')) { if (this.$content.is(':visible') && !this.model.get('scrolled')) {
this.$content.scrollTop(this.$content[0].scrollHeight); this.$content.scrollTop(this.$content[0].scrollHeight);
this.hideNewMessagesIndicator(); this.onScrolledDown();
this.model.save({'auto_scrolled': true}); this.model.save({'auto_scrolled': true});
} }
}, },
onScrolledDown: function() {
this.hideNewMessagesIndicator();
if (_converse.windowState !== 'hidden') {
this.model.clearUnreadMsgCounter();
}
_converse.emit('chatBoxScrolledDown', {'chatbox': this.model});
},
scrollDown: function () { scrollDown: function () {
if (_.isUndefined(this.debouncedScrollDown)) { if (_.isUndefined(this.debouncedScrollDown)) {
/* We wrap the method in a debouncer and set it on the /* We wrap the method in a debouncer and set it on the
...@@ -877,6 +898,12 @@ ...@@ -877,6 +898,12 @@
} }
this.debouncedScrollDown.apply(this, arguments); this.debouncedScrollDown.apply(this, arguments);
return this; return this;
},
onWindowStateChanged: function (state) {
if (this.model.get('num_unread', 0) && !this.isNewMessageHidden()) {
this.model.clearUnreadMsgCounter();
}
} }
}); });
} }
......
...@@ -519,26 +519,21 @@ ...@@ -519,26 +519,21 @@
} }
}; };
this.updateMsgCounter = function () {
if (this.msg_counter > 0) {
if (document.title.search(/^Messages \(\d+\) /) === -1) {
document.title = "Messages (" + this.msg_counter + ") " + document.title;
} else {
document.title = document.title.replace(/^Messages \(\d+\) /, "Messages (" + this.msg_counter + ") ");
}
} else if (document.title.search(/^Messages \(\d+\) /) !== -1) {
document.title = document.title.replace(/^Messages \(\d+\) /, "");
}
};
this.incrementMsgCounter = function () { this.incrementMsgCounter = function () {
this.msg_counter += 1; this.msg_counter += 1;
this.updateMsgCounter(); var unreadMsgCount = this.msg_counter;
if (document.title.search(/^Messages \(\d+\) /) === -1) {
document.title = "Messages (" + unreadMsgCount + ") " + document.title;
} else {
document.title = document.title.replace(/^Messages \(\d+\) /, "Messages (" + unreadMsgCount + ") ");
}
}; };
this.clearMsgCounter = function () { this.clearMsgCounter = function () {
this.msg_counter = 0; this.msg_counter = 0;
this.updateMsgCounter(); if (document.title.search(/^Messages \(\d+\) /) !== -1) {
document.title = document.title.replace(/^Messages \(\d+\) /, "");
}
}; };
this.initStatus = function () { this.initStatus = function () {
...@@ -605,6 +600,7 @@ ...@@ -605,6 +600,7 @@
_converse.clearMsgCounter(); _converse.clearMsgCounter();
} }
_converse.windowState = state; _converse.windowState = state;
_converse.emit('windowStateChanged', {state: state})
}; };
this.registerGlobalEventHandlers = function () { this.registerGlobalEventHandlers = function () {
...@@ -1415,6 +1411,19 @@ ...@@ -1415,6 +1411,19 @@
createMessage: function (message, delay, original_stanza) { createMessage: function (message, delay, original_stanza) {
return this.messages.create(this.getMessageAttributes.apply(this, arguments)); return this.messages.create(this.getMessageAttributes.apply(this, arguments));
},
incrementUnreadMsgCounter: function() {
this.save({'num_unread': this.get('num_unread') + 1});
_converse.incrementMsgCounter();
},
clearUnreadMsgCounter: function() {
this.save({'num_unread': 0});
},
isScrolledUp: function () {
return this.get('scrolled', true);
} }
}); });
......
...@@ -104,6 +104,11 @@ ...@@ -104,6 +104,11 @@
} }
}, },
isNewMessageHidden: function () {
return this.model.get('minimized') ||
this.__super__.isNewMessageHidden.apply(this, arguments);
},
shouldShowOnTextMessage: function () { shouldShowOnTextMessage: function () {
return !this.model.get('minimized') && return !this.model.get('minimized') &&
this.__super__.shouldShowOnTextMessage.apply(this, arguments); this.__super__.shouldShowOnTextMessage.apply(this, arguments);
...@@ -133,6 +138,9 @@ ...@@ -133,6 +138,9 @@
// Restores a minimized chat box // Restores a minimized chat box
var _converse = this.__super__._converse; var _converse = this.__super__._converse;
this.$el.insertAfter(_converse.chatboxviews.get("controlbox").$el); this.$el.insertAfter(_converse.chatboxviews.get("controlbox").$el);
if (!this.model.isScrolledUp()) {
this.model.clearUnreadMsgCounter();
}
this.show(); this.show();
_converse.emit('chatBoxMaximized', this); _converse.emit('chatBoxMaximized', this);
return this; return this;
...@@ -312,15 +320,7 @@ ...@@ -312,15 +320,7 @@
}, },
initialize: function () { initialize: function () {
this.model.messages.on('add', function (m) { this.model.on('change:num_unread', this.render, this);
if (m.get('message')) {
this.updateUnreadMessagesCounter();
}
}, this);
this.model.on('change:minimized', this.clearUnreadMessagesCounter, this);
// OTR stuff, doesn't require this module to depend on OTR.
this.model.on('showReceivedOTRMessage', this.updateUnreadMessagesCounter, this);
this.model.on('showSentOTRMessage', this.updateUnreadMessagesCounter, this);
}, },
render: function () { render: function () {
...@@ -338,16 +338,6 @@ ...@@ -338,16 +338,6 @@
return this.$el.html(tpl_trimmed_chat(data)); return this.$el.html(tpl_trimmed_chat(data));
}, },
clearUnreadMessagesCounter: function () {
this.model.save({'num_unread': 0});
this.render();
},
updateUnreadMessagesCounter: function () {
this.model.save({'num_unread': this.model.get('num_unread') + 1});
this.render();
},
close: function (ev) { close: function (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
this.remove(); this.remove();
...@@ -365,7 +355,7 @@ ...@@ -365,7 +355,7 @@
restore: _.debounce(function (ev) { restore: _.debounce(function (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
this.model.messages.off('add',null,this); this.model.off('change:num_unread', null, this);
this.remove(); this.remove();
this.model.maximize(); this.model.maximize();
}, 200, {'leading': true}) }, 200, {'leading': true})
......
...@@ -689,7 +689,6 @@ ...@@ -689,7 +689,6 @@
openChat: function (ev) { openChat: function (ev) {
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
this.model.save({'num_unread': 0});
return _converse.chatboxviews.showChat(this.model.attributes, true); return _converse.chatboxviews.showChat(this.model.attributes, true);
}, },
...@@ -935,14 +934,14 @@ ...@@ -935,14 +934,14 @@
var onChatBoxMaximized = function (chatboxview) { var onChatBoxMaximized = function (chatboxview) {
/* When a chat box gets maximized, the num_unread counter needs /* When a chat box gets maximized, the num_unread counter needs
* to be cleared. * to be cleared, but if chatbox is scrolled up, then num_unread should not be cleared.
*/ */
var chatbox = chatboxview.model; var chatbox = chatboxview.model;
if (chatbox.get('type') === 'chatroom') { if (chatbox.get('type') === 'chatroom') {
// TODO // TODO
} else { } else {
var contact = _.head(_converse.roster.where({'jid': chatbox.get('jid')})); var contact = _.head(_converse.roster.where({'jid': chatbox.get('jid')}));
if (!_.isUndefined(contact)) { if (!_.isUndefined(contact) && !chatbox.isScrolledUp()) {
contact.save({'num_unread': 0}); contact.save({'num_unread': 0});
} }
} }
...@@ -960,9 +959,9 @@ ...@@ -960,9 +959,9 @@
return; // The message has no text return; // The message has no text
} }
var new_message = !(sizzle('result[xmlns="'+Strophe.NS.MAM+'"]', data.stanza).length); var new_message = !(sizzle('result[xmlns="'+Strophe.NS.MAM+'"]', data.stanza).length);
var hidden_or_minimized_chatbox = chatbox.get('hidden') || chatbox.get('minimized'); var is_new_message_hidden = chatbox.get('hidden') || chatbox.get('minimized') || chatbox.isScrolledUp();
if (hidden_or_minimized_chatbox && new_message) { if (is_new_message_hidden && new_message) {
if (chatbox.get('type') === 'chatroom') { if (chatbox.get('type') === 'chatroom') {
// TODO // TODO
} else { } else {
...@@ -974,6 +973,17 @@ ...@@ -974,6 +973,17 @@
} }
}; };
var onChatBoxScrolledDown = function (data) {
var chatbox = data.chatbox;
if (_.isUndefined(chatbox)) {
return;
}
var contact = _.head(_converse.roster.where({'jid': chatbox.get('jid')}));
if (!_.isUndefined(contact)) {
contact.save({'num_unread': 0});
}
};
var initRoster = function () { var initRoster = function () {
/* Create an instance of RosterView once the RosterGroups /* Create an instance of RosterView once the RosterGroups
* collection has been created (in converse-core.js) * collection has been created (in converse-core.js)
...@@ -987,6 +997,7 @@ ...@@ -987,6 +997,7 @@
_converse.api.listen.on('rosterReadyAfterReconnection', initRoster); _converse.api.listen.on('rosterReadyAfterReconnection', initRoster);
_converse.api.listen.on('message', onMessageReceived); _converse.api.listen.on('message', onMessageReceived);
_converse.api.listen.on('chatBoxMaximized', onChatBoxMaximized); _converse.api.listen.on('chatBoxMaximized', onChatBoxMaximized);
_converse.api.listen.on('chatBoxScrolledDown', onChatBoxScrolledDown);
} }
}); });
})); }));
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
}(this, function (converse_api, Promise, mock, waitUntilPromise) { }(this, function (converse_api, Promise, mock, waitUntilPromise) {
var _ = converse_api.env._; var _ = converse_api.env._;
var $ = converse_api.env.jQuery; var $ = converse_api.env.jQuery;
var $msg = converse_api.env.$msg;
var $pres = converse_api.env.$pres; var $pres = converse_api.env.$pres;
var $iq = converse_api.env.$iq; var $iq = converse_api.env.$iq;
var Strophe = converse_api.env.Strophe; var Strophe = converse_api.env.Strophe;
...@@ -216,6 +217,17 @@ ...@@ -216,6 +217,17 @@
}, converse)); }, converse));
}; };
utils.createChatMessage = function (_converse, sender_jid, message) {
return $msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: (new Date()).getTime()
})
.c('body').t(message).up()
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
}
utils.sendMessage = function (chatboxview, message) { utils.sendMessage = function (chatboxview, message) {
chatboxview.$el.find('.chat-textarea').val(message); chatboxview.$el.find('.chat-textarea').val(message);
chatboxview.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13})); chatboxview.$el.find('textarea.chat-textarea').trigger($.Event('keypress', {keyCode: 13}));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment