2016-03-13 17:16:53 +01:00
|
|
|
// Converse.js (A browser based XMPP chat client)
|
|
|
|
// http://conversejs.org
|
|
|
|
//
|
2017-02-02 19:29:38 +01:00
|
|
|
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
|
2016-03-13 17:16:53 +01:00
|
|
|
// Licensed under the Mozilla Public License (MPLv2)
|
|
|
|
//
|
|
|
|
/*global Backbone, define */
|
|
|
|
|
|
|
|
(function (root, factory) {
|
2017-02-13 15:37:17 +01:00
|
|
|
define([
|
2017-02-14 15:08:39 +01:00
|
|
|
"converse-core",
|
2016-09-23 10:54:55 +02:00
|
|
|
"tpl!chatbox",
|
|
|
|
"tpl!new_day",
|
|
|
|
"tpl!action",
|
|
|
|
"tpl!message",
|
2016-10-14 23:37:11 +02:00
|
|
|
"tpl!toolbar",
|
|
|
|
"tpl!avatar"
|
2016-09-23 10:54:55 +02:00
|
|
|
], factory);
|
|
|
|
}(this, function (
|
2016-12-20 11:42:20 +01:00
|
|
|
converse,
|
2016-09-23 10:54:55 +02:00
|
|
|
tpl_chatbox,
|
|
|
|
tpl_new_day,
|
|
|
|
tpl_action,
|
|
|
|
tpl_message,
|
2016-10-14 23:37:11 +02:00
|
|
|
tpl_toolbar,
|
|
|
|
tpl_avatar
|
2016-09-23 10:54:55 +02:00
|
|
|
) {
|
2016-03-13 17:16:53 +01:00
|
|
|
"use strict";
|
2016-12-20 11:42:20 +01:00
|
|
|
var $ = converse.env.jQuery,
|
|
|
|
utils = converse.env.utils,
|
|
|
|
Strophe = converse.env.Strophe,
|
|
|
|
$msg = converse.env.$msg,
|
|
|
|
_ = converse.env._,
|
|
|
|
moment = converse.env.moment;
|
2016-12-20 10:30:20 +01:00
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
var KEY = {
|
|
|
|
ENTER: 13,
|
|
|
|
FORWARD_SLASH: 47
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-12-20 11:42:20 +01:00
|
|
|
converse.plugins.add('converse-chatview', {
|
2016-03-13 17:16:53 +01:00
|
|
|
|
|
|
|
overrides: {
|
|
|
|
// Overrides mentioned here will be picked up by converse.js's
|
|
|
|
// plugin architecture they will replace existing methods on the
|
|
|
|
// relevant objects or classes.
|
|
|
|
//
|
|
|
|
// New functions which don't exist yet can also be added.
|
|
|
|
|
|
|
|
ChatBoxViews: {
|
|
|
|
onChatBoxAdded: function (item) {
|
2016-12-20 11:42:20 +01:00
|
|
|
var _converse = this.__super__._converse;
|
2016-03-13 17:16:53 +01:00
|
|
|
var view = this.get(item.get('id'));
|
2016-03-21 10:57:38 +01:00
|
|
|
if (!view) {
|
2016-12-20 10:30:20 +01:00
|
|
|
view = new _converse.ChatBoxView({model: item});
|
2016-03-13 17:16:53 +01:00
|
|
|
this.add(item.get('id'), view);
|
2016-03-19 22:56:55 +01:00
|
|
|
return view;
|
2016-03-13 17:16:53 +01:00
|
|
|
} else {
|
2016-08-31 12:06:17 +02:00
|
|
|
return this.__super__.onChatBoxAdded.apply(this, arguments);
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
initialize: function () {
|
|
|
|
/* The initialize function gets called as soon as the plugin is
|
|
|
|
* loaded by converse.js's plugin machinery.
|
|
|
|
*/
|
2016-12-20 11:42:20 +01:00
|
|
|
var _converse = this._converse,
|
|
|
|
__ = _converse.__;
|
|
|
|
|
|
|
|
// Add new HTML templates.
|
|
|
|
_converse.templates.chatbox = tpl_chatbox;
|
|
|
|
_converse.templates.new_day = tpl_new_day;
|
|
|
|
_converse.templates.action = tpl_action;
|
|
|
|
_converse.templates.message = tpl_message;
|
|
|
|
_converse.templates.toolbar = tpl_toolbar;
|
|
|
|
_converse.templates.avatar = tpl_avatar;
|
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
this.updateSettings({
|
|
|
|
show_toolbar: true,
|
2016-10-14 23:37:11 +02:00
|
|
|
chatview_avatar_width: 32,
|
|
|
|
chatview_avatar_height: 32,
|
2016-10-26 12:50:00 +02:00
|
|
|
visible_toolbar_buttons: {
|
|
|
|
'emoticons': true,
|
|
|
|
'call': false,
|
2016-10-27 13:30:58 +02:00
|
|
|
'clear': true
|
2016-10-26 12:50:00 +02:00
|
|
|
},
|
2016-03-13 17:16:53 +01:00
|
|
|
});
|
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.ChatBoxView = Backbone.View.extend({
|
2016-03-13 17:16:53 +01:00
|
|
|
length: 200,
|
|
|
|
tagName: 'div',
|
2016-11-22 17:42:58 +01:00
|
|
|
className: 'chatbox hidden',
|
2016-10-26 12:50:00 +02:00
|
|
|
is_chatroom: false, // Leaky abstraction from MUC
|
2016-03-13 17:16:53 +01:00
|
|
|
|
|
|
|
events: {
|
|
|
|
'click .close-chatbox-button': 'close',
|
2016-10-28 15:31:53 +02:00
|
|
|
'keypress .chat-textarea': 'keyPressed',
|
2016-03-13 17:16:53 +01:00
|
|
|
'click .toggle-smiley': 'toggleEmoticonMenu',
|
|
|
|
'click .toggle-smiley ul li': 'insertEmoticon',
|
|
|
|
'click .toggle-clear': 'clearMessages',
|
2016-05-28 14:25:44 +02:00
|
|
|
'click .toggle-call': 'toggleCall',
|
|
|
|
'click .new-msgs-indicator': 'viewUnreadMessages'
|
2016-03-13 17:16:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
initialize: function () {
|
|
|
|
this.model.messages.on('add', this.onMessageAdded, this);
|
|
|
|
this.model.on('show', this.show, this);
|
|
|
|
this.model.on('destroy', this.hide, this);
|
|
|
|
// TODO check for changed fullname as well
|
|
|
|
this.model.on('change:chat_state', this.sendChatState, this);
|
|
|
|
this.model.on('change:chat_status', this.onChatStatusChanged, this);
|
|
|
|
this.model.on('change:image', this.renderAvatar, this);
|
|
|
|
this.model.on('change:status', this.onStatusChanged, this);
|
|
|
|
this.model.on('showHelpMessages', this.showHelpMessages, this);
|
|
|
|
this.model.on('sendMessage', this.sendMessage, this);
|
2016-11-22 17:42:58 +01:00
|
|
|
this.render().fetchMessages().insertIntoDOM();
|
2016-05-25 15:14:12 +02:00
|
|
|
// XXX: adding the event below to the events map above doesn't work.
|
|
|
|
// The code that gets executed because of that looks like this:
|
|
|
|
// this.$el.on('scroll', '.chat-content', this.markScrolled.bind(this));
|
|
|
|
// Which for some reason doesn't work.
|
|
|
|
// So working around that fact here:
|
|
|
|
this.$el.find('.chat-content').on('scroll', this.markScrolled.bind(this));
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.emit('chatBoxInitialized', this);
|
2016-03-13 17:16:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
render: function () {
|
|
|
|
this.$el.attr('id', this.model.get('box_id'))
|
2016-12-20 10:30:20 +01:00
|
|
|
.html(_converse.templates.chatbox(
|
2016-03-13 17:16:53 +01:00
|
|
|
_.extend(this.model.toJSON(), {
|
2016-12-20 10:30:20 +01:00
|
|
|
show_toolbar: _converse.show_toolbar,
|
2016-03-13 17:16:53 +01:00
|
|
|
show_textarea: true,
|
|
|
|
title: this.model.get('fullname'),
|
2016-05-28 10:55:03 +02:00
|
|
|
unread_msgs: __('You have unread messages'),
|
2016-03-13 17:16:53 +01:00
|
|
|
info_close: __('Close this chat box'),
|
|
|
|
label_personal_message: __('Personal message')
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
this.$content = this.$el.find('.chat-content');
|
|
|
|
this.renderToolbar().renderAvatar();
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.emit('chatBoxOpened', this);
|
2016-10-17 13:07:19 +02:00
|
|
|
utils.refreshWebkit();
|
2016-03-13 17:16:53 +01:00
|
|
|
return this.showStatusMessage();
|
|
|
|
},
|
|
|
|
|
2016-03-31 11:17:39 +02:00
|
|
|
afterMessagesFetched: function () {
|
|
|
|
// Provides a hook for plugins, such as converse-mam.
|
|
|
|
return;
|
2016-03-13 17:16:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
fetchMessages: function () {
|
|
|
|
this.model.messages.fetch({
|
|
|
|
'add': true,
|
2016-03-31 11:17:39 +02:00
|
|
|
'success': this.afterMessagesFetched.bind(this)
|
2016-03-13 17:16:53 +01:00
|
|
|
});
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2016-06-22 15:54:22 +02:00
|
|
|
insertIntoDOM: function () {
|
2016-03-13 17:16:53 +01:00
|
|
|
/* This method gets overridden in src/converse-controlbox.js if
|
2016-03-19 23:16:00 +01:00
|
|
|
* the controlbox plugin is active.
|
|
|
|
*/
|
2016-03-13 17:16:53 +01:00
|
|
|
$('#conversejs').prepend(this.$el);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
clearStatusNotification: function () {
|
|
|
|
this.$content.find('div.chat-event').remove();
|
|
|
|
},
|
|
|
|
|
2016-09-12 13:12:14 +02:00
|
|
|
showStatusNotification: function (message, keep_old, permanent) {
|
2016-03-13 17:16:53 +01:00
|
|
|
if (!keep_old) {
|
|
|
|
this.clearStatusNotification();
|
|
|
|
}
|
2016-09-12 13:12:14 +02:00
|
|
|
var $el = $('<div class="chat-info"></div>').text(message);
|
|
|
|
if (!permanent) {
|
|
|
|
$el.addClass('chat-event');
|
|
|
|
}
|
|
|
|
this.$content.append($el);
|
2016-05-25 15:14:12 +02:00
|
|
|
this.scrollDown();
|
2016-03-13 17:16:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
addSpinner: function () {
|
|
|
|
if (!this.$content.first().hasClass('spinner')) {
|
|
|
|
this.$content.prepend('<span class="spinner"/>');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
clearSpinner: function () {
|
|
|
|
if (this.$content.children(':first').is('span.spinner')) {
|
|
|
|
this.$content.children(':first').remove();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-05-25 08:23:00 +02:00
|
|
|
insertDayIndicator: function (date, prepend) {
|
|
|
|
/* Appends (or prepends if "prepend" is truthy) an indicator
|
|
|
|
* into the chat area, showing the day as given by the
|
|
|
|
* passed in date.
|
2016-03-19 23:16:00 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (String) date - An ISO8601 date string.
|
|
|
|
*/
|
2016-03-13 17:16:53 +01:00
|
|
|
var day_date = moment(date).startOf('day');
|
2016-05-25 08:23:00 +02:00
|
|
|
var insert = prepend ? this.$content.prepend: this.$content.append;
|
2016-12-20 10:30:20 +01:00
|
|
|
insert.call(this.$content, _converse.templates.new_day({
|
2016-03-13 17:16:53 +01:00
|
|
|
isodate: day_date.format(),
|
|
|
|
datestring: day_date.format("dddd MMM Do YYYY")
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2016-05-25 08:23:00 +02:00
|
|
|
insertMessage: function (attrs, prepend) {
|
|
|
|
/* Helper method which appends a message (or prepends if the
|
|
|
|
* 2nd parameter is set to true) to the end of the chat box's
|
|
|
|
* content area.
|
2016-03-19 23:16:00 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Object) attrs: An object containing the message attributes.
|
|
|
|
*/
|
2016-11-02 14:33:20 +01:00
|
|
|
var that = this;
|
2016-05-25 08:23:00 +02:00
|
|
|
var insert = prepend ? this.$content.prepend : this.$content.append;
|
2017-01-26 15:49:02 +01:00
|
|
|
_.flow(
|
2016-05-25 08:23:00 +02:00
|
|
|
function ($el) {
|
2016-11-02 14:33:20 +01:00
|
|
|
insert.call(that.$content, $el);
|
2016-05-25 08:23:00 +02:00
|
|
|
return $el;
|
2017-01-26 15:49:02 +01:00
|
|
|
},
|
|
|
|
this.scrollDownMessageHeight.bind(this)
|
2016-03-13 17:16:53 +01:00
|
|
|
)(this.renderMessage(attrs));
|
|
|
|
},
|
|
|
|
|
|
|
|
showMessage: function (attrs) {
|
|
|
|
/* Inserts a chat message into the content area of the chat box.
|
2016-03-19 23:16:00 +01:00
|
|
|
* Will also insert a new day indicator if the message is on a
|
|
|
|
* different day.
|
|
|
|
*
|
|
|
|
* The message to show may either be newer than the newest
|
|
|
|
* message, or older than the oldest message.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Object) attrs: An object containing the message attributes.
|
|
|
|
*/
|
2016-05-25 08:23:00 +02:00
|
|
|
var msg_dates, idx,
|
2016-11-02 14:38:42 +01:00
|
|
|
$first_msg = this.$content.find('.chat-message:first'),
|
2016-03-13 17:16:53 +01:00
|
|
|
first_msg_date = $first_msg.data('isodate'),
|
2016-05-25 08:23:00 +02:00
|
|
|
current_msg_date = moment(attrs.time) || moment,
|
2016-11-02 14:38:42 +01:00
|
|
|
last_msg_date = this.$content.find('.chat-message:last').data('isodate');
|
2016-05-25 08:23:00 +02:00
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
if (!first_msg_date) {
|
2016-05-25 08:23:00 +02:00
|
|
|
// This is the first received message, so we insert a
|
|
|
|
// date indicator before it.
|
|
|
|
this.insertDayIndicator(current_msg_date);
|
|
|
|
this.insertMessage(attrs);
|
2016-03-13 17:16:53 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-05-25 08:23:00 +02:00
|
|
|
if (current_msg_date.isAfter(last_msg_date) || current_msg_date.isSame(last_msg_date)) {
|
2016-03-13 17:16:53 +01:00
|
|
|
// The new message is after the last message
|
|
|
|
if (current_msg_date.isAfter(last_msg_date, 'day')) {
|
|
|
|
// Append a new day indicator
|
2016-05-25 08:23:00 +02:00
|
|
|
this.insertDayIndicator(current_msg_date);
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
2016-05-25 08:23:00 +02:00
|
|
|
this.insertMessage(attrs);
|
2016-03-13 17:16:53 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-05-25 08:23:00 +02:00
|
|
|
if (current_msg_date.isBefore(first_msg_date) || current_msg_date.isSame(first_msg_date)) {
|
|
|
|
// 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).
|
|
|
|
this.insertMessage(attrs, 'prepend');
|
2016-03-13 17:16:53 +01:00
|
|
|
if (current_msg_date.isBefore(first_msg_date, 'day')) {
|
2016-05-25 08:23:00 +02:00
|
|
|
// This message is also on a different day, so we prepend a day indicator.
|
|
|
|
this.insertDayIndicator(current_msg_date, 'prepend');
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
2016-05-25 08:23:00 +02:00
|
|
|
return;
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
2016-05-25 08:23:00 +02:00
|
|
|
// Find the correct place to position the message
|
|
|
|
current_msg_date = current_msg_date.format();
|
2016-11-02 14:38:42 +01:00
|
|
|
msg_dates = _.map(this.$content.find('.chat-message'), function (el) {
|
2016-05-25 08:23:00 +02:00
|
|
|
return $(el).data('isodate');
|
|
|
|
});
|
|
|
|
msg_dates.push(current_msg_date);
|
|
|
|
msg_dates.sort();
|
|
|
|
idx = msg_dates.indexOf(current_msg_date)-1;
|
2017-01-26 15:49:02 +01:00
|
|
|
_.flow(
|
|
|
|
function ($el) {
|
|
|
|
$el.insertAfter(this.$content.find('.chat-message[data-isodate="'+msg_dates[idx]+'"]'));
|
|
|
|
return $el;
|
|
|
|
}.bind(this),
|
|
|
|
this.scrollDownMessageHeight.bind(this)
|
|
|
|
)(this.renderMessage(attrs));
|
|
|
|
},
|
|
|
|
|
|
|
|
getExtraMessageTemplateAttributes: function () {
|
|
|
|
/* Provides a hook for sending more attributes to the
|
|
|
|
* message template.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Object) attrs: An object containing message attributes.
|
|
|
|
*/
|
2016-07-01 14:27:26 +02:00
|
|
|
return {};
|
|
|
|
},
|
|
|
|
|
2017-02-15 21:30:32 +01:00
|
|
|
getExtraMessageClasses: function (attrs) {
|
|
|
|
return attrs.delayed && 'delayed' || '';
|
|
|
|
},
|
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
renderMessage: function (attrs) {
|
|
|
|
/* Renders a chat message based on the passed in attributes.
|
2016-03-19 23:16:00 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Object) attrs: An object containing the message attributes.
|
|
|
|
*
|
|
|
|
* Returns:
|
|
|
|
* The DOM element representing the message.
|
|
|
|
*/
|
2016-03-13 17:16:53 +01:00
|
|
|
var msg_time = moment(attrs.time) || moment,
|
|
|
|
text = attrs.message,
|
|
|
|
match = text.match(/^\/(.*?)(?: (.*))?$/),
|
|
|
|
fullname = this.model.get('fullname') || attrs.fullname,
|
|
|
|
template, username;
|
|
|
|
|
|
|
|
if ((match) && (match[1] === 'me')) {
|
|
|
|
text = text.replace(/^\/me/, '');
|
2016-12-20 10:30:20 +01:00
|
|
|
template = _converse.templates.action;
|
2017-02-15 22:03:02 +01:00
|
|
|
if (attrs.sender === 'me') {
|
|
|
|
fullname = _converse.xmppstatus.get('fullname');
|
|
|
|
username = _.isNil(fullname)? _converse.bare_jid: fullname;
|
|
|
|
} else {
|
|
|
|
username = attrs.fullname;
|
|
|
|
}
|
2016-03-13 17:16:53 +01:00
|
|
|
} else {
|
2016-12-20 10:30:20 +01:00
|
|
|
template = _converse.templates.message;
|
2016-03-13 17:16:53 +01:00
|
|
|
username = attrs.sender === 'me' && __('me') || fullname;
|
|
|
|
}
|
|
|
|
this.$content.find('div.chat-event').remove();
|
|
|
|
|
2016-09-12 13:12:14 +02:00
|
|
|
if (text.length > 8000) {
|
|
|
|
text = text.substring(0, 10) + '...';
|
|
|
|
this.showStatusNotification(
|
|
|
|
__("A very large message has been received."+
|
|
|
|
"This might be due to an attack meant to degrade the chat performance."+
|
|
|
|
"Output has been shortened."),
|
|
|
|
true, true);
|
|
|
|
}
|
2016-11-02 14:42:20 +01:00
|
|
|
var $msg = $(template(
|
|
|
|
_.extend(this.getExtraMessageTemplateAttributes(attrs), {
|
|
|
|
'msgid': attrs.msgid,
|
|
|
|
'sender': attrs.sender,
|
|
|
|
'time': msg_time.format('hh:mm'),
|
|
|
|
'isodate': msg_time.format(),
|
|
|
|
'username': username,
|
2017-02-15 21:30:32 +01:00
|
|
|
'extra_classes': this.getExtraMessageClasses(attrs)
|
2016-11-02 14:42:20 +01:00
|
|
|
})
|
|
|
|
));
|
|
|
|
$msg.find('.chat-msg-content').first()
|
|
|
|
.text(text)
|
|
|
|
.addHyperlinks()
|
2016-12-20 10:30:20 +01:00
|
|
|
.addEmoticons(_converse.visible_toolbar_buttons.emoticons);
|
2016-11-02 14:42:20 +01:00
|
|
|
return $msg;
|
2016-03-13 17:16:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
showHelpMessages: function (msgs, type, spinner) {
|
|
|
|
var i, msgs_length = msgs.length;
|
|
|
|
for (i=0; i<msgs_length; i++) {
|
|
|
|
this.$content.append($('<div class="chat-'+(type||'info')+'">'+msgs[i]+'</div>'));
|
|
|
|
}
|
|
|
|
if (spinner === true) {
|
|
|
|
this.$content.append('<span class="spinner"/>');
|
|
|
|
} else if (spinner === false) {
|
|
|
|
this.$content.find('span.spinner').remove();
|
|
|
|
}
|
|
|
|
return this.scrollDown();
|
|
|
|
},
|
|
|
|
|
|
|
|
handleChatStateMessage: function (message) {
|
2016-12-20 10:30:20 +01:00
|
|
|
if (message.get('chat_state') === _converse.COMPOSING) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.showStatusNotification(message.get('fullname')+' '+__('is typing'));
|
2016-08-18 12:19:43 +02:00
|
|
|
this.clear_status_timeout = window.setTimeout(this.clearStatusNotification.bind(this), 30000);
|
2016-12-20 10:30:20 +01:00
|
|
|
} else if (message.get('chat_state') === _converse.PAUSED) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.showStatusNotification(message.get('fullname')+' '+__('has stopped typing'));
|
2016-12-20 10:30:20 +01:00
|
|
|
} else if (_.includes([_converse.INACTIVE, _converse.ACTIVE], message.get('chat_state'))) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.$content.find('div.chat-event').remove();
|
2016-12-20 10:30:20 +01:00
|
|
|
} else if (message.get('chat_state') === _converse.GONE) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.showStatusNotification(message.get('fullname')+' '+__('has gone away'));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-03-28 16:08:50 +02:00
|
|
|
shouldShowOnTextMessage: function () {
|
|
|
|
return !this.$el.is(':visible');
|
|
|
|
},
|
|
|
|
|
2016-06-16 10:36:55 +02:00
|
|
|
updateNewMessageIndicators: function (message) {
|
|
|
|
/* We have two indicators of new messages. The unread messages
|
|
|
|
* counter, which shows the number of unread messages in
|
|
|
|
* the document.title, and the "new messages" indicator in
|
|
|
|
* a chat area, if it's scrolled up so that new messages
|
|
|
|
* aren't visible.
|
|
|
|
*
|
|
|
|
* In both cases we ignore MAM messages.
|
|
|
|
*/
|
|
|
|
if (!message.get('archive_id')) {
|
|
|
|
if (this.model.get('scrolled', true)) {
|
|
|
|
this.$el.find('.new-msgs-indicator').removeClass('hidden');
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.windowState === 'hidden' || this.model.get('scrolled', true)) {
|
|
|
|
_converse.incrementMsgCounter();
|
2016-05-27 14:42:31 +02:00
|
|
|
}
|
2016-06-16 10:36:55 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
handleTextMessage: function (message) {
|
|
|
|
this.showMessage(_.clone(message.attributes));
|
|
|
|
if (message.get('sender') !== 'me') {
|
|
|
|
this.updateNewMessageIndicators(message);
|
2016-05-27 14:42:31 +02:00
|
|
|
} else {
|
|
|
|
// We remove the "scrolled" flag so that the chat area
|
|
|
|
// gets scrolled down. We always want to scroll down
|
|
|
|
// when the user writes a message as opposed to when a
|
|
|
|
// message is received.
|
|
|
|
this.model.set('scrolled', false);
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
2016-03-28 16:08:50 +02:00
|
|
|
if (this.shouldShowOnTextMessage()) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.show();
|
2016-05-30 16:32:05 +02:00
|
|
|
} else {
|
|
|
|
this.scrollDown();
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-06-22 18:47:07 +02:00
|
|
|
handleErrorMessage: function (message) {
|
|
|
|
var $message = $('[data-msgid='+message.get('msgid')+']');
|
|
|
|
if ($message.length) {
|
2016-06-23 08:55:25 +02:00
|
|
|
$message.after($('<div class="chat-info chat-error"></div>').text(message.get('message')));
|
2016-06-22 18:47:07 +02:00
|
|
|
this.scrollDown();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
onMessageAdded: function (message) {
|
|
|
|
/* Handler that gets called when a new message object is created.
|
2016-03-19 23:16:00 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Object) message - The message Backbone object that was added.
|
|
|
|
*/
|
2017-01-26 15:49:02 +01:00
|
|
|
if (!_.isUndefined(this.clear_status_timeout)) {
|
2016-03-13 17:16:53 +01:00
|
|
|
window.clearTimeout(this.clear_status_timeout);
|
|
|
|
delete this.clear_status_timeout;
|
|
|
|
}
|
2016-06-22 18:47:07 +02:00
|
|
|
if (message.get('type') === 'error') {
|
|
|
|
this.handleErrorMessage(message);
|
|
|
|
} else if (!message.get('message')) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.handleChatStateMessage(message);
|
|
|
|
} else {
|
|
|
|
this.handleTextMessage(message);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
createMessageStanza: function (message) {
|
|
|
|
return $msg({
|
2016-12-20 10:30:20 +01:00
|
|
|
from: _converse.connection.jid,
|
2016-03-13 17:16:53 +01:00
|
|
|
to: this.model.get('jid'),
|
|
|
|
type: 'chat',
|
|
|
|
id: message.get('msgid')
|
|
|
|
}).c('body').t(message.get('message')).up()
|
2016-12-20 10:30:20 +01:00
|
|
|
.c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up();
|
2016-03-13 17:16:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
sendMessage: function (message) {
|
|
|
|
/* Responsible for sending off a text message.
|
2016-03-19 23:16:00 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Message) message - The chat message
|
|
|
|
*/
|
2016-03-13 17:16:53 +01:00
|
|
|
// TODO: We might want to send to specfic resources.
|
|
|
|
// Especially in the OTR case.
|
|
|
|
var messageStanza = this.createMessageStanza(message);
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send(messageStanza);
|
|
|
|
if (_converse.forward_messages) {
|
2016-03-13 17:16:53 +01:00
|
|
|
// Forward the message, so that other connected resources are also aware of it.
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send(
|
|
|
|
$msg({ to: _converse.bare_jid, type: 'chat', id: message.get('msgid') })
|
2016-03-13 17:16:53 +01:00
|
|
|
.c('forwarded', {xmlns:'urn:xmpp:forward:0'})
|
|
|
|
.c('delay', {xmns:'urn:xmpp:delay',stamp:(new Date()).getTime()}).up()
|
|
|
|
.cnode(messageStanza.tree())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onMessageSubmitted: function (text) {
|
|
|
|
/* This method gets called once the user has typed a message
|
2016-03-19 23:16:00 +01:00
|
|
|
* and then pressed enter in a chat box.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (string) text - The chat message text.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
if (!_converse.connection.authenticated) {
|
2016-03-13 17:16:53 +01:00
|
|
|
return this.showHelpMessages(
|
|
|
|
['Sorry, the connection has been lost, '+
|
|
|
|
'and your message could not be sent'],
|
|
|
|
'error'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
var match = text.replace(/^\s*/, "").match(/^\/(.*)\s*$/), msgs;
|
|
|
|
if (match) {
|
|
|
|
if (match[1] === "clear") {
|
|
|
|
return this.clearMessages();
|
|
|
|
}
|
|
|
|
else if (match[1] === "help") {
|
|
|
|
msgs = [
|
|
|
|
'<strong>/help</strong>:'+__('Show this menu')+'',
|
|
|
|
'<strong>/me</strong>:'+__('Write in the third person')+'',
|
|
|
|
'<strong>/clear</strong>:'+__('Remove messages')+''
|
|
|
|
];
|
|
|
|
this.showHelpMessages(msgs);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
var fullname = _converse.xmppstatus.get('fullname');
|
|
|
|
fullname = _.isEmpty(fullname)? _converse.bare_jid: fullname;
|
2016-03-13 17:16:53 +01:00
|
|
|
var message = this.model.messages.create({
|
|
|
|
fullname: fullname,
|
|
|
|
sender: 'me',
|
|
|
|
time: moment().format(),
|
|
|
|
message: text
|
|
|
|
});
|
|
|
|
this.sendMessage(message);
|
|
|
|
},
|
|
|
|
|
|
|
|
sendChatState: function () {
|
|
|
|
/* Sends a message with the status of the user in this chat session
|
2016-03-19 23:16:00 +01:00
|
|
|
* as taken from the 'chat_state' attribute of the chat box.
|
|
|
|
* See XEP-0085 Chat State Notifications.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send(
|
2016-03-13 17:16:53 +01:00
|
|
|
$msg({'to':this.model.get('jid'), 'type': 'chat'})
|
2016-05-30 18:53:31 +02:00
|
|
|
.c(this.model.get('chat_state'), {'xmlns': Strophe.NS.CHATSTATES}).up()
|
|
|
|
.c('no-store', {'xmlns': Strophe.NS.HINTS}).up()
|
|
|
|
.c('no-permanent-store', {'xmlns': Strophe.NS.HINTS})
|
2016-03-13 17:16:53 +01:00
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
setChatState: function (state, no_save) {
|
|
|
|
/* Mutator for setting the chat state of this chat session.
|
2016-03-19 23:16:00 +01:00
|
|
|
* Handles clearing of any chat state notification timeouts and
|
|
|
|
* setting new ones if necessary.
|
|
|
|
* Timeouts are set when the state being set is COMPOSING or PAUSED.
|
|
|
|
* After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE.
|
|
|
|
* See XEP-0085 Chat State Notifications.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
|
|
|
|
* (Boolean) no_save - Just do the cleanup or setup but don't actually save the state.
|
|
|
|
*/
|
2017-01-26 15:49:02 +01:00
|
|
|
if (!_.isUndefined(this.chat_state_timeout)) {
|
2016-03-13 17:16:53 +01:00
|
|
|
window.clearTimeout(this.chat_state_timeout);
|
|
|
|
delete this.chat_state_timeout;
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
if (state === _converse.COMPOSING) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.chat_state_timeout = window.setTimeout(
|
2016-12-20 10:30:20 +01:00
|
|
|
this.setChatState.bind(this), _converse.TIMEOUTS.PAUSED, _converse.PAUSED);
|
|
|
|
} else if (state === _converse.PAUSED) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.chat_state_timeout = window.setTimeout(
|
2016-12-20 10:30:20 +01:00
|
|
|
this.setChatState.bind(this), _converse.TIMEOUTS.INACTIVE, _converse.INACTIVE);
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
|
|
|
if (!no_save && this.model.get('chat_state') !== state) {
|
|
|
|
this.model.set('chat_state', state);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
keyPressed: function (ev) {
|
|
|
|
/* Event handler for when a key is pressed in a chat box textarea.
|
2016-03-19 23:16:00 +01:00
|
|
|
*/
|
2017-02-01 18:36:20 +01:00
|
|
|
var textarea = ev.target, message;
|
2016-03-13 17:16:53 +01:00
|
|
|
if (ev.keyCode === KEY.ENTER) {
|
|
|
|
ev.preventDefault();
|
2017-02-01 18:36:20 +01:00
|
|
|
message = textarea.value;
|
|
|
|
textarea.value = '';
|
|
|
|
textarea.focus();
|
2016-03-13 17:16:53 +01:00
|
|
|
if (message !== '') {
|
2016-08-12 14:52:33 +02:00
|
|
|
this.onMessageSubmitted(message);
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.emit('messageSend', message);
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
this.setChatState(_converse.ACTIVE);
|
2016-08-12 14:52:33 +02:00
|
|
|
} else {
|
2016-03-13 17:16:53 +01:00
|
|
|
// Set chat state to composing if keyCode is not a forward-slash
|
|
|
|
// (which would imply an internal command and not a message).
|
2016-12-20 10:30:20 +01:00
|
|
|
this.setChatState(_converse.COMPOSING, ev.keyCode === KEY.FORWARD_SLASH);
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
clearMessages: function (ev) {
|
|
|
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
|
|
|
var result = confirm(__("Are you sure you want to clear the messages from this chat box?"));
|
|
|
|
if (result === true) {
|
|
|
|
this.$content.empty();
|
|
|
|
this.model.messages.reset();
|
|
|
|
this.model.messages.browserStorage._clear();
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2016-07-26 11:04:05 +02:00
|
|
|
insertIntoTextArea: function (value) {
|
|
|
|
var $textbox = this.$el.find('textarea.chat-textarea');
|
|
|
|
var existing = $textbox.val();
|
|
|
|
if (existing && (existing[existing.length-1] !== ' ')) {
|
|
|
|
existing = existing + ' ';
|
|
|
|
}
|
|
|
|
$textbox.focus().val(existing+value+' ');
|
|
|
|
},
|
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
insertEmoticon: function (ev) {
|
|
|
|
ev.stopPropagation();
|
|
|
|
this.$el.find('.toggle-smiley ul').slideToggle(200);
|
|
|
|
var $target = $(ev.target);
|
|
|
|
$target = $target.is('a') ? $target : $target.children('a');
|
2016-07-26 11:04:05 +02:00
|
|
|
this.insertIntoTextArea($target.data('emoticon'));
|
2016-03-13 17:16:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
toggleEmoticonMenu: function (ev) {
|
|
|
|
ev.stopPropagation();
|
|
|
|
this.$el.find('.toggle-smiley ul').slideToggle(200);
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleCall: function (ev) {
|
|
|
|
ev.stopPropagation();
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.emit('callButtonClicked', {
|
|
|
|
connection: _converse.connection,
|
2016-03-13 17:16:53 +01:00
|
|
|
model: this.model
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
onChatStatusChanged: function (item) {
|
|
|
|
var chat_status = item.get('chat_status'),
|
|
|
|
fullname = item.get('fullname');
|
|
|
|
fullname = _.isEmpty(fullname)? item.get('jid'): fullname;
|
|
|
|
if (this.$el.is(':visible')) {
|
|
|
|
if (chat_status === 'offline') {
|
|
|
|
this.showStatusNotification(fullname+' '+__('has gone offline'));
|
|
|
|
} else if (chat_status === 'away') {
|
|
|
|
this.showStatusNotification(fullname+' '+__('has gone away'));
|
|
|
|
} else if ((chat_status === 'dnd')) {
|
|
|
|
this.showStatusNotification(fullname+' '+__('is busy'));
|
|
|
|
} else if (chat_status === 'online') {
|
|
|
|
this.$el.find('div.chat-event').remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onStatusChanged: function (item) {
|
|
|
|
this.showStatusMessage();
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.emit('contactStatusMessageChanged', {
|
2016-03-13 17:16:53 +01:00
|
|
|
'contact': item.attributes,
|
|
|
|
'message': item.get('status')
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
showStatusMessage: function (msg) {
|
|
|
|
msg = msg || this.model.get('status');
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isString(msg)) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.$el.find('p.user-custom-message').text(msg).attr('title', msg);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
close: function (ev) {
|
|
|
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.connection.connected) {
|
2016-03-28 13:42:33 +02:00
|
|
|
// Immediately sending the chat state, because the
|
|
|
|
// model is going to be destroyed afterwards.
|
2016-12-20 10:30:20 +01:00
|
|
|
this.model.set('chat_state', _converse.INACTIVE);
|
2016-03-28 13:42:33 +02:00
|
|
|
this.sendChatState();
|
2016-03-13 17:16:53 +01:00
|
|
|
this.model.destroy();
|
|
|
|
}
|
2016-03-28 13:42:33 +02:00
|
|
|
this.remove();
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.emit('chatBoxClosed', this);
|
2016-03-13 17:16:53 +01:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2016-10-27 13:30:58 +02:00
|
|
|
getToolbarOptions: function (options) {
|
|
|
|
return _.extend(options || {}, {
|
|
|
|
'label_clear': __('Clear all messages'),
|
|
|
|
'label_insert_smiley': __('Insert a smiley'),
|
|
|
|
'label_start_call': __('Start a call'),
|
2016-12-20 10:30:20 +01:00
|
|
|
'show_call_button': _converse.visible_toolbar_buttons.call,
|
|
|
|
'show_clear_button': _converse.visible_toolbar_buttons.clear,
|
|
|
|
'show_emoticons': _converse.visible_toolbar_buttons.emoticons,
|
2016-03-13 17:16:53 +01:00
|
|
|
});
|
2016-10-27 13:30:58 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
renderToolbar: function (toolbar, options) {
|
2016-12-20 10:30:20 +01:00
|
|
|
if (!_converse.show_toolbar) { return; }
|
|
|
|
toolbar = toolbar || _converse.templates.toolbar;
|
2016-10-27 13:30:58 +02:00
|
|
|
options = _.extend(
|
|
|
|
this.model.toJSON(),
|
|
|
|
this.getToolbarOptions(options || {})
|
|
|
|
);
|
|
|
|
this.$el.find('.chat-toolbar').html(toolbar(options));
|
2016-03-13 17:16:53 +01:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
renderAvatar: function () {
|
|
|
|
if (!this.model.get('image')) {
|
|
|
|
return;
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
var width = _converse.chatview_avatar_width;
|
|
|
|
var height = _converse.chatview_avatar_height;
|
2016-03-13 17:16:53 +01:00
|
|
|
var img_src = 'data:'+this.model.get('image_type')+';base64,'+this.model.get('image'),
|
2016-12-20 10:30:20 +01:00
|
|
|
canvas = $(_converse.templates.avatar({
|
2016-10-14 23:37:11 +02:00
|
|
|
'width': width,
|
|
|
|
'height': height
|
|
|
|
})).get(0);
|
2016-03-13 17:16:53 +01:00
|
|
|
|
|
|
|
if (!(canvas.getContext && canvas.getContext('2d'))) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
var img = new Image(); // Create new Image object
|
|
|
|
img.onload = function () {
|
|
|
|
var ratio = img.width/img.height;
|
|
|
|
if (ratio < 1) {
|
2016-10-14 23:37:11 +02:00
|
|
|
ctx.drawImage(img, 0,0, width, height*(1/ratio));
|
2016-03-13 17:16:53 +01:00
|
|
|
} else {
|
2016-10-14 23:37:11 +02:00
|
|
|
ctx.drawImage(img, 0,0, width, height*ratio);
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
img.src = img_src;
|
|
|
|
this.$el.find('.chat-title').before(canvas);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
focus: function () {
|
|
|
|
this.$el.find('.chat-textarea').focus();
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.emit('chatBoxFocused', this);
|
2016-03-13 17:16:53 +01:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
hide: function () {
|
2016-11-22 17:42:58 +01:00
|
|
|
this.el.classList.add('hidden');
|
2016-06-17 17:43:09 +02:00
|
|
|
utils.refreshWebkit();
|
2016-03-13 17:16:53 +01:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-02-17 22:07:11 +01:00
|
|
|
afterShown: function (focus) {
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.connection.connected) {
|
2016-03-28 16:52:00 +02:00
|
|
|
// Without a connection, we haven't yet initialized
|
|
|
|
// localstorage
|
|
|
|
this.model.save();
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
this.setChatState(_converse.ACTIVE);
|
2016-03-28 16:52:00 +02:00
|
|
|
this.scrollDown();
|
|
|
|
if (focus) {
|
|
|
|
this.focus();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-04-01 14:46:19 +02:00
|
|
|
_show: function (focus) {
|
|
|
|
/* Inner show method that gets debounced */
|
|
|
|
if (this.$el.is(':visible') && this.$el.css('opacity') === "1") {
|
|
|
|
if (focus) { this.focus(); }
|
|
|
|
return;
|
|
|
|
}
|
2017-02-17 22:07:11 +01:00
|
|
|
utils.fadeIn(this.el, _.bind(this.afterShown, this, focus));
|
2016-04-01 14:46:19 +02:00
|
|
|
},
|
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
show: function (focus) {
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isUndefined(this.debouncedShow)) {
|
2016-03-13 17:16:53 +01:00
|
|
|
/* We wrap the method in a debouncer and set it on the
|
2016-03-19 23:16:00 +01:00
|
|
|
* instance, so that we have it debounced per instance.
|
|
|
|
* Debouncing it on the class-level is too broad.
|
|
|
|
*/
|
2017-01-26 15:49:02 +01:00
|
|
|
this.debouncedShow = _.debounce(this._show, 250, {'leading': true});
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
|
|
|
this.debouncedShow.apply(this, arguments);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2016-05-25 15:14:12 +02:00
|
|
|
markScrolled: _.debounce(function (ev) {
|
|
|
|
/* Called when the chat content is scrolled up or down.
|
|
|
|
* We want to record when the user has scrolled away from
|
|
|
|
* the bottom, so that we don't automatically scroll away
|
|
|
|
* from what the user is reading when new messages are
|
|
|
|
* received.
|
|
|
|
*/
|
|
|
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
2016-05-28 10:55:03 +02:00
|
|
|
var is_at_bottom = this.$content.scrollTop() + this.$content.innerHeight() >= this.$content[0].scrollHeight-10;
|
2016-05-25 15:14:12 +02:00
|
|
|
if (is_at_bottom) {
|
|
|
|
this.model.set('scrolled', false);
|
2016-05-28 10:55:03 +02:00
|
|
|
this.$el.find('.new-msgs-indicator').addClass('hidden');
|
2016-05-25 15:14:12 +02:00
|
|
|
} 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);
|
|
|
|
}
|
2016-05-28 10:55:03 +02:00
|
|
|
}, 150),
|
2016-05-25 15:14:12 +02:00
|
|
|
|
2016-05-28 14:25:44 +02:00
|
|
|
|
|
|
|
viewUnreadMessages: function () {
|
|
|
|
this.model.set('scrolled', false);
|
|
|
|
this.scrollDown();
|
|
|
|
},
|
|
|
|
|
2016-03-13 17:16:53 +01:00
|
|
|
scrollDownMessageHeight: function ($message) {
|
2016-05-25 15:14:12 +02:00
|
|
|
if (this.$content.is(':visible') && !this.model.get('scrolled')) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.$content.scrollTop(this.$content.scrollTop() + $message[0].scrollHeight);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-02-02 16:06:49 +01:00
|
|
|
_scrollDown: function () {
|
|
|
|
/* Inner method that gets debounced */
|
2016-05-25 15:14:12 +02:00
|
|
|
if (this.$content.is(':visible') && !this.model.get('scrolled')) {
|
2016-03-13 17:16:53 +01:00
|
|
|
this.$content.scrollTop(this.$content[0].scrollHeight);
|
2016-05-28 10:55:03 +02:00
|
|
|
this.$el.find('.new-msgs-indicator').addClass('hidden');
|
2016-03-13 17:16:53 +01:00
|
|
|
}
|
2017-02-02 16:06:49 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
scrollDown: function () {
|
|
|
|
if (_.isUndefined(this.debouncedScrollDown)) {
|
|
|
|
/* We wrap the method in a debouncer and set it on the
|
|
|
|
* instance, so that we have it debounced per instance.
|
|
|
|
* Debouncing it on the class-level is too broad.
|
|
|
|
*/
|
|
|
|
this.debouncedScrollDown = _.debounce(this._scrollDown, 250, {'leading': true});
|
|
|
|
}
|
|
|
|
this.debouncedScrollDown.apply(this, arguments);
|
2016-03-13 17:16:53 +01:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2017-02-14 16:34:32 +01:00
|
|
|
|
|
|
|
return converse;
|
2016-03-13 17:16:53 +01:00
|
|
|
}));
|