web-apps/apps/common/main/lib/view/Chat.js

392 lines
15 KiB
JavaScript

/**
* Chat.js
*
* View
*
* Created by Maxim Kadushkin on 27 February 2014
* Copyright (c) 2014 Ascensio System SIA. All rights reserved.
*
*/
if (Common === undefined)
var Common = {};
Common.Views = Common.Views || {};
define([
'text!common/main/lib/template/Chat.template',
'common/main/lib/util/utils',
'common/main/lib/component/BaseView',
'common/main/lib/component/Layout'
], function (template) {
'use strict';
Common.Views.Chat = Common.UI.BaseView.extend(_.extend({
el: '#left-panel-chat',
template: _.template(template),
storeUsers: undefined,
storeMessages: undefined,
tplUser: ['<li id="chat-user-<%= user.get("id") %>"<% if (!user.get("online")) { %> class="offline"<% } %>>',
'<div class="color" style="background-color: <%= user.get("color") %>;" >',
'<label class="name"><%= scope.getUserName(user.get("username")) %></label>',
'</div>',
'</li>'].join(''),
templateUserList: _.template('<ul>' +
'<% _.each(users, function(item) { %>' +
'<%= _.template(usertpl, {user: item, scope: scope}) %>' +
'<% }); %>' +
'</ul>'),
tplMsg: ['<li>',
'<% if (msg.get("type")==1) { %>',
'<div class="message service" data-can-copy="true"><%= msg.get("message") %></div>',
'<% } else { %>',
'<div class="user" data-can-copy="true" style="color: <%= msg.get("usercolor") %>;"><%= scope.getUserName(msg.get("username")) %></div>',
'<label class="message user-select" data-can-copy="true"><%= msg.get("message") %></label>',
'<% } %>',
'</li>'].join(''),
templateMsgList: _.template('<ul>' +
'<% _.each(messages, function(item) { %>' +
'<%= _.template(msgtpl, {msg: item, scope: scope}) %>' +
'<% }); %>' +
'</ul>'),
events: {
},
usersBoxHeight: 72,
messageBoxHeight: 70,
addMessageBoxHeight: 110,
initialize: function(options) {
_.extend(this, options);
Common.UI.BaseView.prototype.initialize.call(this, arguments);
this.storeUsers.bind({
add : _.bind(this._onAddUser, this),
change : _.bind(this._onUsersChanged, this),
reset : _.bind(this._onResetUsers, this)
});
this.storeMessages.bind({
add : _.bind(this._onAddMessage, this),
reset : _.bind(this._onResetMessages, this)
});
},
render: function(el) {
el = el || this.el;
$(el).html(this.template({scope: this, maxMsgLength: c_oAscMaxCellOrCommentLength}));
this.panelBox = $('#chat-box', this.el);
this.panelUsers = $('#chat-users', this.el);
this.panelMessages = $('#chat-messages', this.el);
this.txtMessage = $('#chat-msg-text', this.el);
this.panelOptions = $('#chat-options', this.el);
this.panelUsers.scroller = new Common.UI.Scroller({
el : $('#chat-users'),
useKeyboard : true,
minScrollbarLength : 25
});
this.panelMessages.scroller = new Common.UI.Scroller({
el : $('#chat-messages'),
includePadding : true,
useKeyboard : true,
minScrollbarLength : 40
});
$('#chat-msg-btn-add', this.el).on('click', _.bind(this._onBtnAddMessage, this));
this.txtMessage.on('keydown', _.bind(this._onKeyDown, this));
this.setupLayout();
return this;
},
focus: function() {
var me = this;
_.defer(function(){
me.txtMessage.focus();
}, 100);
this.updateLayout(true);
this.setupAutoSizingTextBox();
},
_onKeyDown: function(event) {
if (event.keyCode == Common.UI.Keys.RETURN) {
if ((event.ctrlKey || event.metaKey) && !event.altKey) {
this._onBtnAddMessage(event);
}
} else
if (event.keyCode == Common.UI.Keys.ESC) {
this.hide();
}
},
_onAddUser: function(m, c, opts) {
if (this.panelUsers) {
this.panelUsers.find('ul').append(_.template(this.tplUser, {user: m, scope: this}));
this.panelUsers.scroller.update({minScrollbarLength : 25, alwaysVisibleY: true});
}
},
_onUsersChanged: function(m) {
if (m.changed.online != undefined && this.panelUsers) {
this.panelUsers.find('#chat-user-'+ m.get('id'))[m.changed.online?'removeClass':'addClass']('offline');
this.panelUsers.scroller.update({minScrollbarLength : 25, alwaysVisibleY: true});
}
},
_onResetUsers: function(c, opts) {
if (this.panelUsers) {
this.panelUsers.html(this.templateUserList({users: c.models, usertpl: this.tplUser, scope: this}));
this.panelUsers.scroller.update({minScrollbarLength : 25, alwaysVisibleY: true});
}
},
_onAddMessage: function(m, c, opts) {
if (this.panelMessages) {
var content = this.panelMessages.find('ul');
if (content && content.length) {
this._prepareMessage(m);
content.append(_.template(this.tplMsg, {msg: m, scope: this}));
// scroll to end
this.panelMessages.scroller.update({minScrollbarLength : 40, alwaysVisibleY: true});
this.panelMessages.scroller.scrollTop(content.get(0).getBoundingClientRect().height);
}
}
},
_onResetMessages: function(c, opts) {
if (this.panelMessages) {
var user, color;
c.each(function(msg){
this._prepareMessage(msg);
}, this);
this.panelMessages.html(this.templateMsgList({messages: c.models, msgtpl: this.tplMsg, scope: this}));
this.panelMessages.scroller.update({minScrollbarLength : 40, alwaysVisibleY: true});
}
},
_onBtnAddMessage: function(e) {
if (this.txtMessage) {
this.fireEvent('message:add', [this, this.txtMessage.val().trim()]);
this.txtMessage.val('');
this.focus();
}
},
_prepareMessage: function(m) {
var user = this.storeUsers.findUser(m.get('userid'));
m.set({
usercolor : user ? user.get('color') : '#000',
message : this._pickLink(Common.Utils.String.htmlEncode(m.get('message')))
}, {silent:true});
},
_pickLink: function(message) {
var arr = [], offset, len;
message.replace(Common.Utils.emailStrongRe, function(subStr) {
offset = arguments[arguments.length-2];
arr.push({start: offset, end: subStr.length+offset, str: '<a href="' + subStr + '">' + subStr + '</a>'});
return '';
});
message.replace(Common.Utils.ipStrongRe, function(subStr) {
offset = arguments[arguments.length-2];
len = subStr.length;
var elem = _.find(arr, function(item){
return ( (offset>=item.start) && (offset<item.end) ||
(offset<=item.start) && (offset+len>item.start));
});
if (!elem)
arr.push({start: offset, end: len+offset, str: '<a href="' + subStr + '" target="_blank" data-can-copy="true">' + subStr + '</a>'});
return '';
});
if (message.length<1000 || message.search(/\S{255,}/)<0)
message.replace(Common.Utils.hostnameStrongRe, function(subStr) {
var ref = (! /(((^https?)|(^ftp)):\/\/)/i.test(subStr) ) ? ('http://' + subStr) : subStr;
offset = arguments[arguments.length-2];
len = subStr.length;
var elem = _.find(arr, function(item){
return ( (offset>=item.start) && (offset<item.end) ||
(offset<=item.start) && (offset+len>item.start));
});
if (!elem)
arr.push({start: offset, end: len+offset, str: '<a href="' + ref + '" target="_blank" data-can-copy="true">' + subStr + '</a>'});
return '';
});
arr = _.sortBy(arr, function(item){ return item.start; });
var str_res = (arr.length>0) ? ( message.substring(0, arr[0].start) + arr[0].str) : message;
for (var i=1; i<arr.length; i++) {
str_res += (message.substring(arr[i-1].end, arr[i].start) + arr[i].str);
}
if (arr.length>0) {
str_res += message.substring(arr[i-1].end, message.length);
}
return str_res;
},
getUserName: function (username) {
return Common.Utils.String.htmlEncode(username);
},
hide: function () {
Common.UI.BaseView.prototype.hide.call(this,arguments);
this.fireEvent('hide', this );
},
setupLayout: function () {
var me = this, parent = $(me.el), items = this.panelBox.find(' > .layout-item');
me.layout = new Common.UI.VBoxLayout({
box: this.panelBox,
items: [
{el: items[0], rely: true, behaviour: 'splitter',
resize: {
hidden: false,
autohide: false,
fmin: (function () {
return me.usersBoxHeight;
}),
fmax: (function () {
return me.panelBox.height() * 0.5 - me.messageBoxHeight;
})
}},
{el: items[1], rely: true, behaviour: 'splitter',
resize: {
hidden: false,
autohide: false,
fmin: (function () {
return Math.max(me.messageBoxHeight + me.usersBoxHeight, me.panelBox.height() * 0.5);
}),
fmax: (function () {
return me.panelBox.height() - me.addMessageBoxHeight;
})
}},
{el: items[2], stretch: true}
]
});
me.layout.on('layout:resizedrag', function(resizer) {
me.updateScrolls();
me.usersCachedHeigt = me.panelUsers.height() + 8 + 1; // resizeHeight * 2 + 1
if (!resizer.index) {
me.textBoxAutoSizeLocked = true;
}
}, this);
$(window).on('resize', function() {
if (parent.css('display') !== 'none') {
me.updateLayout();
}
});
this.updateLayout();
// default sizes
var height = this.panelBox.height();
this.layout.setResizeValue(0, this.usersBoxHeight);
this.layout.setResizeValue(1,
Math.max (this.addMessageBoxHeight,
Math.max (height * 0.5, height - me.panelOptions.height() - 4)));
// text box setup autosize input text
this.setupAutoSizingTextBox();
},
updateLayout: function (applyUsersAutoSizig) {
var me = this;
var height = this.panelBox.height();
me.layout.setResizeValue(1,
Math.max (me.addMessageBoxHeight,
Math.max (height * 0.5, height - me.panelOptions.height() - 4)));
if (applyUsersAutoSizig) {
var oldHeight = this.panelUsers.css('height');
this.panelUsers.css('height', '1px');
var content = this.panelUsers.get(0).scrollHeight;
me.layout.setResizeValue(0, Math.max(me.usersBoxHeight,
Math.min(content+2, Math.floor(height * 0.5) - me.messageBoxHeight)));
} else {
me.layout.setResizeValue(0, Math.max(me.usersBoxHeight,
Math.min(me.usersCachedHeigt + 2, Math.floor(height * 0.5) - me.messageBoxHeight)));
}
me.updateScrolls();
me.updateHeightTextBox(null);
},
setupAutoSizingTextBox: function () {
this.lineHeight = 0;
this.minHeight = 44;
this.lineHeight = parseInt(this.txtMessage.css('lineHeight'), 10) * 1.25; // TODO: need fix
this.updateHeightTextBox(true);
this.txtMessage.bind('input propertychange', _.bind(this.updateHeightTextBox, this));
},
updateHeightTextBox: function (event) {
var textBox, controlHeight, contentHeight, height,
textBoxMinHeightIndent = 36 + 4; // 4px - autosize line height + big around border
textBox = $('#chat-msg-text', this.el);
height = this.panelBox.height();
if (event && 0 == textBox.val().length) {
this.layout.setResizeValue(1, Math.max(-this.addMessageBoxHeight, height - this.addMessageBoxHeight));
this.textBoxAutoSizeLocked = undefined;
return;
}
if (!_.isUndefined(this.textBoxAutoSizeLocked))
return;
controlHeight = textBox.height();
contentHeight = textBox.get(0).scrollHeight;
// calculate text content height
textBox.css({height: this.minHeight + 'px'});
controlHeight = textBox.height();
contentHeight = Math.max(textBox.get(0).scrollHeight + this.lineHeight, 1);
textBox.css({height: '100%'});
height = this.panelBox.height();
this.layout.setResizeValue(1,
Math.max(-this.addMessageBoxHeight,
Math.min(height - contentHeight - textBoxMinHeightIndent, height - this.addMessageBoxHeight)));
},
updateScrolls: function () {
if (this.panelUsers && this.panelUsers.scroller && this.panelMessages && this.panelMessages.scroller) {
this.panelUsers.scroller.update({minScrollbarLength: 25, alwaysVisibleY: true});
this.panelMessages.scroller.update({minScrollbarLength: 40, alwaysVisibleY: true});
}
},
textSend: "Send"
}, Common.Views.Chat || {}))
});