Refactored the code showing messages and day indicators.

Fixes the issue where a topmost day indicator wasn't shown when doing infinite
scrolling upwards.
This commit is contained in:
JC Brand 2015-07-19 23:23:02 +02:00
parent e8fba4a7d0
commit 7ff38896e9

View File

@ -968,7 +968,8 @@
this.Message = Backbone.Model; this.Message = Backbone.Model;
this.Messages = Backbone.Collection.extend({ this.Messages = Backbone.Collection.extend({
model: converse.Message model: converse.Message,
comparator: 'time'
}); });
this.ChatBox = Backbone.Model.extend({ this.ChatBox = Backbone.Model.extend({
@ -1233,7 +1234,7 @@
}, },
initialize: function () { initialize: function () {
this.model.messages.on('add', _.compose(this.onMessageAdded, this.showNewDay), this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.on('show', this.show, this); this.model.on('show', this.show, this);
this.model.on('destroy', this.hide, this); this.model.on('destroy', this.hide, this);
// TODO check for changed fullname as well // TODO check for changed fullname as well
@ -1278,17 +1279,13 @@
}, },
onScroll: function (ev) { onScroll: function (ev) {
var oldest; if ($(ev.target).scrollTop() === 0 && this.model.messages.length) {
if ($(ev.target).scrollTop() === 0) { this.$content.prepend('<span class="spinner"/>');
oldest = this.model.messages.where({'time': this.model.messages.pluck('time').sort()[0]}); this.fetchArchivedMessages({
if (oldest) { 'before': this.model.messages.at(0).get('archive_id'),
this.$content.prepend('<span class="spinner"/>'); 'with': this.model.get('jid'),
this.fetchArchivedMessages({ 'max': converse.archived_messages_batch_size
'before': oldest[0].get('archive_id'), });
'with': this.model.get('jid'),
'max': converse.archived_messages_batch_size
});
}
} }
}, },
@ -1391,59 +1388,123 @@
} }
}, },
showMessage: function (msg_dict) { prependDayIndicator: function (date) {
var msg_time = moment(msg_dict.time) || moment, /* Prepends an indicator into the chat area, showing the day as
text = msg_dict.message, * given by the passed in date.
match = text.match(/^\/(.*?)(?: (.*))?$/), *
fullname = this.model.get('fullname') || msg_dict.fullname, * Parameters:
extra_classes = msg_dict.delayed && 'delayed' || '', * (String) date - An ISO8601 date string.
num_messages = this.model.messages.length, */
has_scrollbar = this.$content.get(0).scrollHeight > this.$content[0].clientHeight, var day_date = moment(date).startOf('day');
template, username, insertMessage; this.$content.prepend(converse.templates.new_day({
isodate: day_date.format(),
datestring: day_date.format("dddd MMM Do YYYY")
}));
},
// TODO: A better approach here is probably to look at what is showMessage: function (attrs) {
// already inside the content area, and from that determine if /* Inserts a chat message into the content area of the chat box.
// the message must be prepended or appended. Similar to showNewDay * Will also insert a new day indicator if the message is on a
if (num_messages && msg_time.isBefore(this.model.messages.at(0).get('time'))) { * different day.
insertMessage = _.compose( *
this.scrollDownMessageHeight.bind(this), * The message to show may either be newer than the newest
function ($el) { * message, or older than the oldest message.
this.$content.prepend($el); *
return $el; * Parameters:
}.bind(this) * (Object) attrs: An object containing the message attributes.
); */
} else { var current_msg_date = moment(attrs.time) || moment,
insertMessage = _.compose(_.debounce(this.scrollDown.bind(this), 50), this.$content.append.bind(this.$content)); $first_msg = this.$content.children('.chat-message:first'),
first_msg_date = $first_msg.data('isodate'),
last_msg_date = this.$content.children(':last').data('isodate'),
day_date;
if (typeof first_msg_date !== "undefined" &&
(current_msg_date.isBefore(first_msg_date) ||
(current_msg_date.isSame(first_msg_date) && !current_msg_date.isSame(last_msg_date)))) {
if ($first_msg.prev().length === 0) {
// There's no day indicator before the first message, so we prepend one.
this.prependDayIndicator(first_msg_date);
}
if (current_msg_date.isBefore(first_msg_date, 'day')) {
_.compose(
this.scrollDownMessageHeight.bind(this),
function ($el) {
this.$content.prepend($el);
return $el;
}.bind(this)
)(this.renderMessage(attrs));
// This message is on a different day, so we add a day indicator.
this.prependDayIndicator(current_msg_date);
} else {
// The message is before the first, but on the same day.
// We need to prepend the message immediately before the
// first message (so that it'll still be after the day indicator).
_.compose(
this.scrollDownMessageHeight.bind(this),
function ($el) {
$el.insertBefore($first_msg);
return $el;
}
)(this.renderMessage(attrs));
}
return;
} else if (current_msg_date.isAfter(last_msg_date, 'day')) {
// Append a new day indicator
day_date = moment(current_msg_date).startOf('day');
this.$content.append(converse.templates.new_day({
isodate: current_msg_date.format(),
datestring: current_msg_date.format("dddd MMM Do YYYY")
}));
} }
_.compose(
_.debounce(this.scrollDown.bind(this), 50),
this.$content.append.bind(this.$content)
)(this.renderMessage(attrs));
},
renderMessage: function (attrs) {
/* Renders a chat message based on the passed in attributes.
*
* Parameters:
* (Object) attrs: An object containing the message attributes.
*
* Returns:
* The DOM element representing the message.
*/
var msg_time = moment(attrs.time) || moment,
text = attrs.message,
match = text.match(/^\/(.*?)(?: (.*))?$/),
fullname = this.model.get('fullname') || attrs.fullname,
extra_classes = attrs.delayed && 'delayed' || '',
template, username;
if ((match) && (match[1] === 'me')) { if ((match) && (match[1] === 'me')) {
text = text.replace(/^\/me/, ''); text = text.replace(/^\/me/, '');
template = converse.templates.action; template = converse.templates.action;
username = fullname; username = fullname;
} else { } else {
template = converse.templates.message; template = converse.templates.message;
username = msg_dict.sender === 'me' && __('me') || fullname; username = attrs.sender === 'me' && __('me') || fullname;
} }
this.$content.find('div.chat-event').remove(); this.$content.find('div.chat-event').remove();
if (this.is_chatroom && msg_dict.sender == 'them' && (new RegExp("\\b"+this.model.get('nick')+"\\b")).test(text)) { if (this.is_chatroom && attrs.sender == 'them' && (new RegExp("\\b"+this.model.get('nick')+"\\b")).test(text)) {
// Add special class to mark groupchat messages in which we // Add special class to mark groupchat messages in which we
// are mentioned. // are mentioned.
extra_classes += ' mentioned'; extra_classes += ' mentioned';
} }
var message = template({ return $(template({
'sender': msg_dict.sender, 'sender': attrs.sender,
'time': msg_time.format('hh:mm'), 'time': msg_time.format('hh:mm'),
'isodate': msg_time.format(), 'isodate': msg_time.format(),
'username': username, 'username': username,
'message': '', 'message': '',
'extra_classes': extra_classes 'extra_classes': extra_classes
}); })).children('.chat-message-content').first().text(text)
insertMessage(
$(message).children('.chat-message-content').first().text(text)
.addHyperlinks() .addHyperlinks()
.addEmoticons(converse.visible_toolbar_buttons.emoticons) .addEmoticons(converse.visible_toolbar_buttons.emoticons).parent();
.parent()
);
}, },
showHelpMessages: function (msgs, type, spinner) { showHelpMessages: function (msgs, type, spinner) {
@ -1459,41 +1520,12 @@
return this.scrollDown(); return this.scrollDown();
}, },
showNewDay: function (message) { onMessageAdded: function (message) {
/* Messages may be received chronologically, from old to new or /* Handler that gets called when a new message object is created.
* new to old.
*
* If this message is older than the oldest, or newer then the
* newest, we show a new day indication in the chat content
* area.
* *
* Parameters: * Parameters:
* (XMLElement) message - The message stanza received from the XMPP server. * (Object) message - The message Backbone object that was added.
*/ */
var first_message_date = this.$content.children(':first').data('isodate');
if (typeof(first_message_date) == "undefined") {
return message;
}
var last_message_date = this.$content.children(':last').data('isodate');
var this_date = moment(message.get('time'));
var day_date;
if (this_date.isBefore(first_message_date, 'day')) {
day_date = moment(first_message_date).startOf('day');
this.$content.prepend(converse.templates.new_day({
isodate: day_date.format(),
datestring: day_date.format("dddd MMM Do YYYY")
}));
} else if (this_date.isAfter(last_message_date, 'day')) {
day_date = moment(this_date).startOf('day');
this.$content.append(converse.templates.new_day({
isodate: this_date.format(),
datestring: this_date.format("dddd MMM Do YYYY")
}));
}
return message;
},
onMessageAdded: function (message) {
if (!message.get('message')) { if (!message.get('message')) {
if (message.get('chat_state') === COMPOSING) { if (message.get('chat_state') === COMPOSING) {
this.showStatusNotification(message.get('fullname')+' '+__('is typing')); this.showStatusNotification(message.get('fullname')+' '+__('is typing'));
@ -1615,7 +1647,7 @@
* *
* Parameters: * Parameters:
* (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE) * (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
* (no_save) no_save - Just do the cleanup or setup but don't actually save the state. * (Boolean) no_save - Just do the cleanup or setup but don't actually save the state.
*/ */
if (typeof this.chat_state_timeout !== 'undefined') { if (typeof this.chat_state_timeout !== 'undefined') {
clearTimeout(this.chat_state_timeout); clearTimeout(this.chat_state_timeout);
@ -2705,7 +2737,7 @@
is_chatroom: true, is_chatroom: true,
initialize: function () { initialize: function () {
this.model.messages.on('add', _.compose(this.onMessageAdded, this.showNewDay), this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.on('change:minimized', function (item) { this.model.on('change:minimized', function (item) {
if (item.get('minimized')) { if (item.get('minimized')) {
this.hide(); this.hide();