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

420 lines
17 KiB
JavaScript
Raw Normal View History

2016-04-01 13:17:09 +00:00
/*
*
* (c) Copyright Ascensio System SIA 2010-2019
2016-04-01 13:17:09 +00:00
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
* street, Riga, Latvia, EU, LV-1050.
2016-04-01 13:17:09 +00:00
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
2016-03-11 00:48:53 +00:00
/**
* Chat.js
*
* View
*
* Created by Maxim Kadushkin on 27 February 2014
2018-03-01 12:16:38 +00:00
* Copyright (c) 2018 Ascensio System SIA. All rights reserved.
2016-03-11 00:48:53 +00:00
*
*/
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="<%= user.get("iid") %>"<% if (!user.get("online")) { %> class="offline"<% } %>>',
'<div class="name"><div class="color" style="background-color: <%= user.get("color") %>;" ></div><%= scope.getUserName(user.get("username")) %>',
2016-03-11 00:48:53 +00:00
'</div>',
'</li>'].join(''),
templateUserList: _.template('<ul>' +
'<% for (originalId in users) { %>' +
'<%= _.template(usertpl)({user: users[originalId][0], scope: scope}) %>' +
'<% } %>' +
2016-03-11 00:48:53 +00:00
'</ul>'),
tplMsg: ['<li>',
'<% if (msg.get("type")==1) { %>',
'<div class="message service" data-can-copy="true"><%= msg.get("message") %></div>',
'<% } else { %>',
2018-06-01 07:51:09 +00:00
'<div class="user-name" data-can-copy="true">',
'<div class="color" style="display: inline-block; background-color: <% if (msg.get("usercolor")!==null) { %><%=msg.get("usercolor")%><% } else { %> #cfcfcf <% } %>; " ></div><%= scope.getUserName(msg.get("username")) %>',
2018-06-01 07:51:09 +00:00
'</div>',
2021-03-09 21:46:55 +00:00
'<label class="message user-select" data-can-copy="true" tabindex="-1" oo_editor_input="true"><%= msg.get("message") %></label>',
2016-03-11 00:48:53 +00:00
'<% } %>',
'</li>'].join(''),
templateMsgList: _.template('<ul>' +
'<% _.each(messages, function(item) { %>' +
2017-04-21 08:10:07 +00:00
'<%= _.template(msgtpl)({msg: item, scope: scope}) %>' +
2016-03-11 00:48:53 +00:00
'<% }); %>' +
'</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._onResetUsers, this),
change : _.bind(this._onResetUsers, this),
2016-03-11 00:48:53 +00:00
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: Asc.c_oAscMaxCellOrCommentLength}));
2016-03-11 00:48:53 +00:00
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();
}
},
_onResetUsers: function(c, opts) {
if (this.panelUsers) {
this.panelUsers.html(this.templateUserList({users: this.storeUsers.chain().filter(function(item){return item.get('online');}).groupBy(function(item) {return item.get('idOriginal');}).value(),
usertpl: this.tplUser, scope: this}));
2016-03-11 00:48:53 +00:00
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);
2017-04-21 08:10:07 +00:00
content.append(_.template(this.tplMsg)({msg: m, scope: this}));
2016-03-11 00:48:53 +00:00
// 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.findOriginalUser(m.get('userid'));
2016-03-11 00:48:53 +00:00
m.set({
2018-06-01 07:51:09 +00:00
usercolor : user ? user.get('color') : null,
2019-10-08 14:43:24 +00:00
message : this._pickLink(m.get('message'))
2016-03-11 00:48:53 +00:00
}, {silent:true});
},
_pickLink: function(message) {
var arr = [], offset, len;
message.replace(Common.Utils.ipStrongRe, function(subStr) {
2019-09-29 10:22:16 +00:00
var result = /[\.,\?\+;:=!\(\)]+$/.exec(subStr);
if (result)
subStr = subStr.substring(0, result.index);
2016-03-11 00:48:53 +00:00
offset = arguments[arguments.length-2];
2016-08-09 08:50:51 +00:00
arr.push({start: offset, end: subStr.length+offset, str: '<a href="' + subStr + '" target="_blank" data-can-copy="true">' + subStr + '</a>'});
2016-03-11 00:48:53 +00:00
return '';
});
if (message.length<1000 || message.search(/\S{255,}/)<0)
message.replace(Common.Utils.hostnameStrongRe, function(subStr) {
2019-09-29 10:22:16 +00:00
var result = /[\.,\?\+;:=!\(\)]+$/.exec(subStr);
if (result)
subStr = subStr.substring(0, result.index);
2016-03-11 00:48:53 +00:00
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 '';
});
2016-08-09 08:50:51 +00:00
message.replace(Common.Utils.emailStrongRe, function(subStr) {
var ref = (! /((^mailto:)\/\/)/i.test(subStr) ) ? ('mailto:' + 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 + '">' + subStr + '</a>'});
return '';
});
2016-03-11 00:48:53 +00:00
arr = _.sortBy(arr, function(item){ return item.start; });
2019-10-08 14:43:24 +00:00
var str_res = (arr.length>0) ? ( Common.Utils.String.htmlEncode(message.substring(0, arr[0].start)) + arr[0].str) : Common.Utils.String.htmlEncode(message);
2016-03-11 00:48:53 +00:00
for (var i=1; i<arr.length; i++) {
2019-10-08 14:43:24 +00:00
str_res += (Common.Utils.String.htmlEncode(message.substring(arr[i-1].end, arr[i].start)) + arr[i].str);
2016-03-11 00:48:53 +00:00
}
if (arr.length>0) {
2019-10-08 14:43:24 +00:00
str_res += Common.Utils.String.htmlEncode(message.substring(arr[i-1].end, message.length));
2016-03-11 00:48:53 +00:00
}
return str_res;
},
getUserName: function (username) {
return Common.Utils.String.htmlEncode(Common.Utils.UserInfoParser.getParsedName(username));
2016-03-11 00:48:53 +00:00
},
hide: function () {
Common.UI.BaseView.prototype.hide.call(this,arguments);
this.fireEvent('hide', this );
2016-05-15 11:57:03 +00:00
this.textBoxAutoSizeLocked = undefined;
2016-03-11 00:48:53 +00:00
},
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();
2016-05-15 11:57:03 +00:00
this.txtMessage.bind('input propertychange', _.bind(this.updateHeightTextBox, this));
2016-03-11 00:48:53 +00:00
},
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);
},
updateHeightTextBox: function (event) {
2016-05-15 11:57:03 +00:00
var textBox = this.txtMessage,
controlHeight, contentHeight, height,
2016-03-11 00:48:53 +00:00
textBoxMinHeightIndent = 36 + 4; // 4px - autosize line height + big around border
height = this.panelBox.height();
if (event && 0 == textBox.val().length) {
2016-05-15 11:57:03 +00:00
this.layout.setResizeValue(1, Math.max(this.addMessageBoxHeight, height - this.addMessageBoxHeight));
2016-03-11 00:48:53 +00:00
this.textBoxAutoSizeLocked = undefined;
2018-03-12 12:34:11 +00:00
this.updateScrolls();
2016-03-11 00:48:53 +00:00
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();
2018-03-12 12:34:11 +00:00
if (this.layout.setResizeValue(1, Math.max(this.addMessageBoxHeight, Math.min(height - contentHeight - textBoxMinHeightIndent, height - this.addMessageBoxHeight))))
this.updateScrolls(); // update when resize position changed
2016-03-11 00:48:53 +00:00
},
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 || {}))
});