Fixed#184 - prevent chat window from jumping whiling reading without new message, just with shown/hidden typing indicator.
This commit is contained in:
parent
47d63aa7b3
commit
a88e6b03b3
6 changed files with 248 additions and 66 deletions
|
@ -125,7 +125,7 @@ License: MIT license
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
jquery.js
|
jquery.js
|
||||||
Source: http://jquery.com/
|
Source: http://jquery.com/
|
||||||
Copyright 2005, 2014 jQuery Foundation, Inc.
|
Copyright: 2005, 2014 jQuery Foundation, Inc.
|
||||||
License: MIT license
|
License: MIT license
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
jquery-ui.js
|
jquery-ui.js
|
||||||
|
@ -135,9 +135,14 @@ License: MIT license
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
jquery.hotkeys.js
|
jquery.hotkeys.js
|
||||||
Source: https://github.com/jeresig/jquery.hotkeys
|
Source: https://github.com/jeresig/jquery.hotkeys
|
||||||
Copyright 2010, John Resig
|
Copyright: 2010 John Resig
|
||||||
License: Dual licensed under the MIT or GPL Version 2 licenses.
|
License: Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
|
jquery.visible.js
|
||||||
|
Source: https://github.com/customd/jquery-visible
|
||||||
|
Copyright: 2012, Digital Fusion
|
||||||
|
License: MIT license
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
underscore.js
|
underscore.js
|
||||||
Source: http://underscorejs.org
|
Source: http://underscorejs.org
|
||||||
Copyright: 2009-2015 Jeremy Ashkenas, DocumentCloud
|
Copyright: 2009-2015 Jeremy Ashkenas, DocumentCloud
|
||||||
|
@ -155,6 +160,6 @@ License: MIT license
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
word_filter.js
|
word_filter.js
|
||||||
Source: https://gist.github.com/sbrin/6801034
|
Source: https://gist.github.com/sbrin/6801034
|
||||||
Copyright (c) 20015, sbrin - https://github.com/sbrin
|
Copyright: 2015, sbrin - https://github.com/sbrin
|
||||||
License: MIT license
|
License: MIT license
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
|
|
|
@ -611,6 +611,7 @@ class ChatWindow extends App.Controller
|
||||||
@isTyping = true
|
@isTyping = true
|
||||||
@maybeAddTimestamp()
|
@maybeAddTimestamp()
|
||||||
@body.append App.view('customer_chat/chat_loader')()
|
@body.append App.view('customer_chat/chat_loader')()
|
||||||
|
return if !@el.find('.js-loader').visible(true)
|
||||||
@scrollToBottom()
|
@scrollToBottom()
|
||||||
|
|
||||||
# clear old delay, set new
|
# clear old delay, set new
|
||||||
|
|
68
app/assets/javascripts/app/lib/base/jquery.visible.js
Normal file
68
app/assets/javascripts/app/lib/base/jquery.visible.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
(function($){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2012, Digital Fusion
|
||||||
|
* Licensed under the MIT license.
|
||||||
|
* http://teamdf.com/jquery-plugins/license/
|
||||||
|
*
|
||||||
|
* @author Sam Sehnert
|
||||||
|
* @desc A small plugin that checks whether elements are within
|
||||||
|
* the user visible viewport of a web browser.
|
||||||
|
* only accounts for vertical position, not horizontal.
|
||||||
|
*/
|
||||||
|
var $w = $(window);
|
||||||
|
$.fn.visible = function(partial,hidden,direction){
|
||||||
|
|
||||||
|
if (this.length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var $t = this.length > 1 ? this.eq(0) : this,
|
||||||
|
t = $t.get(0),
|
||||||
|
vpWidth = $w.width(),
|
||||||
|
vpHeight = $w.height(),
|
||||||
|
direction = (direction) ? direction : 'both',
|
||||||
|
clientSize = hidden === true ? t.offsetWidth * t.offsetHeight : true;
|
||||||
|
|
||||||
|
if (typeof t.getBoundingClientRect === 'function'){
|
||||||
|
|
||||||
|
// Use this native browser method, if available.
|
||||||
|
var rec = t.getBoundingClientRect(),
|
||||||
|
tViz = rec.top >= 0 && rec.top < vpHeight,
|
||||||
|
bViz = rec.bottom > 0 && rec.bottom <= vpHeight,
|
||||||
|
lViz = rec.left >= 0 && rec.left < vpWidth,
|
||||||
|
rViz = rec.right > 0 && rec.right <= vpWidth,
|
||||||
|
vVisible = partial ? tViz || bViz : tViz && bViz,
|
||||||
|
hVisible = partial ? lViz || rViz : lViz && rViz;
|
||||||
|
|
||||||
|
if(direction === 'both')
|
||||||
|
return clientSize && vVisible && hVisible;
|
||||||
|
else if(direction === 'vertical')
|
||||||
|
return clientSize && vVisible;
|
||||||
|
else if(direction === 'horizontal')
|
||||||
|
return clientSize && hVisible;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var viewTop = $w.scrollTop(),
|
||||||
|
viewBottom = viewTop + vpHeight,
|
||||||
|
viewLeft = $w.scrollLeft(),
|
||||||
|
viewRight = viewLeft + vpWidth,
|
||||||
|
offset = $t.offset(),
|
||||||
|
_top = offset.top,
|
||||||
|
_bottom = _top + $t.height(),
|
||||||
|
_left = offset.left,
|
||||||
|
_right = _left + $t.width(),
|
||||||
|
compareTop = partial === true ? _bottom : _top,
|
||||||
|
compareBottom = partial === true ? _top : _bottom,
|
||||||
|
compareLeft = partial === true ? _right : _left,
|
||||||
|
compareRight = partial === true ? _left : _right;
|
||||||
|
|
||||||
|
if(direction === 'both')
|
||||||
|
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop)) && ((compareRight <= viewRight) && (compareLeft >= viewLeft));
|
||||||
|
else if(direction === 'vertical')
|
||||||
|
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop));
|
||||||
|
else if(direction === 'horizontal')
|
||||||
|
return !!clientSize && ((compareRight <= viewRight) && (compareLeft >= viewLeft));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
|
@ -668,6 +668,8 @@ do($ = window.jQuery, window) ->
|
||||||
|
|
||||||
@el.find('.zammad-chat-body').append @view('typingIndicator')()
|
@el.find('.zammad-chat-body').append @view('typingIndicator')()
|
||||||
|
|
||||||
|
# only if typing indicator is shown
|
||||||
|
return if !@isVisible(@el.find('.zammad-chat-message--typing'), true)
|
||||||
@scrollToBottom()
|
@scrollToBottom()
|
||||||
|
|
||||||
onAgentTypingEnd: =>
|
onAgentTypingEnd: =>
|
||||||
|
@ -898,4 +900,56 @@ do($ = window.jQuery, window) ->
|
||||||
overflow: ''
|
overflow: ''
|
||||||
position: ''
|
position: ''
|
||||||
|
|
||||||
|
# based on https://github.com/customd/jquery-visible/blob/master/jquery.visible.js
|
||||||
|
# to have not dependency, port to coffeescript
|
||||||
|
isVisible: (el, partial, hidden, direction) ->
|
||||||
|
return if el.length < 1
|
||||||
|
|
||||||
|
$w = $(window)
|
||||||
|
$t = if el.length > 1 then el.eq(0) else el
|
||||||
|
t = $t.get(0)
|
||||||
|
vpWidth = $w.width()
|
||||||
|
vpHeight = $w.height()
|
||||||
|
direction = if direction then direction else 'both'
|
||||||
|
clientSize = if hidden is true then t.offsetWidth * t.offsetHeight else true
|
||||||
|
|
||||||
|
if typeof t.getBoundingClientRect is 'function'
|
||||||
|
|
||||||
|
# Use this native browser method, if available.
|
||||||
|
rec = t.getBoundingClientRect()
|
||||||
|
tViz = rec.top >= 0 && rec.top < vpHeight
|
||||||
|
bViz = rec.bottom > 0 && rec.bottom <= vpHeight
|
||||||
|
lViz = rec.left >= 0 && rec.left < vpWidth
|
||||||
|
rViz = rec.right > 0 && rec.right <= vpWidth
|
||||||
|
vVisible = if partial then tViz || bViz else tViz && bViz
|
||||||
|
hVisible = if partial then lViz || rViz else lViz && rViz
|
||||||
|
|
||||||
|
if direction is 'both'
|
||||||
|
return clientSize && vVisible && hVisible
|
||||||
|
else if direction is 'vertical'
|
||||||
|
return clientSize && vVisible
|
||||||
|
else if direction is 'horizontal'
|
||||||
|
return clientSize && hVisible
|
||||||
|
else
|
||||||
|
viewTop = $w.scrollTop()
|
||||||
|
viewBottom = viewTop + vpHeight
|
||||||
|
viewLeft = $w.scrollLeft()
|
||||||
|
viewRight = viewLeft + vpWidth
|
||||||
|
offset = $t.offset()
|
||||||
|
_top = offset.top
|
||||||
|
_bottom = _top + $t.height()
|
||||||
|
_left = offset.left
|
||||||
|
_right = _left + $t.width()
|
||||||
|
compareTop = if partial is true then _bottom else _top
|
||||||
|
compareBottom = if partial is true then _top else _bottom
|
||||||
|
compareLeft = if partial is true then _right else _left
|
||||||
|
compareRight = if partial is true then _left else _right
|
||||||
|
|
||||||
|
if direction is 'both'
|
||||||
|
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop)) && ((compareRight <= viewRight) && (compareLeft >= viewLeft))
|
||||||
|
else if direction is 'vertical'
|
||||||
|
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop))
|
||||||
|
else if direction is 'horizontal'
|
||||||
|
return !!clientSize && ((compareRight <= viewRight) && (compareLeft >= viewLeft))
|
||||||
|
|
||||||
window.ZammadChat = ZammadChat
|
window.ZammadChat = ZammadChat
|
||||||
|
|
|
@ -1,64 +1,3 @@
|
||||||
if (!window.zammadChatTemplates) {
|
|
||||||
window.zammadChatTemplates = {};
|
|
||||||
}
|
|
||||||
window.zammadChatTemplates["agent"] = function (__obj) {
|
|
||||||
if (!__obj) __obj = {};
|
|
||||||
var __out = [], __capture = function(callback) {
|
|
||||||
var out = __out, result;
|
|
||||||
__out = [];
|
|
||||||
callback.call(this);
|
|
||||||
result = __out.join('');
|
|
||||||
__out = out;
|
|
||||||
return __safe(result);
|
|
||||||
}, __sanitize = function(value) {
|
|
||||||
if (value && value.ecoSafe) {
|
|
||||||
return value;
|
|
||||||
} else if (typeof value !== 'undefined' && value != null) {
|
|
||||||
return __escape(value);
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}, __safe, __objSafe = __obj.safe, __escape = __obj.escape;
|
|
||||||
__safe = __obj.safe = function(value) {
|
|
||||||
if (value && value.ecoSafe) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
if (!(typeof value !== 'undefined' && value != null)) value = '';
|
|
||||||
var result = new String(value);
|
|
||||||
result.ecoSafe = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (!__escape) {
|
|
||||||
__escape = __obj.escape = function(value) {
|
|
||||||
return ('' + value)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
(function() {
|
|
||||||
(function() {
|
|
||||||
if (this.agent.avatar) {
|
|
||||||
__out.push('\n<img class="zammad-chat-agent-avatar" src="');
|
|
||||||
__out.push(__sanitize(this.agent.avatar));
|
|
||||||
__out.push('">\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
__out.push('\n<span class="zammad-chat-agent-sentence">\n <span class="zammad-chat-agent-name">');
|
|
||||||
|
|
||||||
__out.push(__sanitize(this.agent.name));
|
|
||||||
|
|
||||||
__out.push('</span>\n</span>');
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
}).call(__obj);
|
|
||||||
__obj.safe = __objSafe, __obj.escape = __escape;
|
|
||||||
return __out.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||||
slice = [].slice,
|
slice = [].slice,
|
||||||
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||||
|
@ -955,6 +894,9 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
}
|
}
|
||||||
this.maybeAddTimestamp();
|
this.maybeAddTimestamp();
|
||||||
this.el.find('.zammad-chat-body').append(this.view('typingIndicator')());
|
this.el.find('.zammad-chat-body').append(this.view('typingIndicator')());
|
||||||
|
if (!this.isVisible(this.el.find('.zammad-chat-message--typing'), true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return this.scrollToBottom();
|
return this.scrollToBottom();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1235,12 +1177,124 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ZammadChat.prototype.isVisible = function(el, partial, hidden, direction) {
|
||||||
|
var $t, $w, _bottom, _left, _right, _top, bViz, clientSize, compareBottom, compareLeft, compareRight, compareTop, hVisible, lViz, offset, rViz, rec, t, tViz, vVisible, viewBottom, viewLeft, viewRight, viewTop, vpHeight, vpWidth;
|
||||||
|
if (el.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$w = $(window);
|
||||||
|
$t = el.length > 1 ? el.eq(0) : el;
|
||||||
|
t = $t.get(0);
|
||||||
|
vpWidth = $w.width();
|
||||||
|
vpHeight = $w.height();
|
||||||
|
direction = direction ? direction : 'both';
|
||||||
|
clientSize = hidden === true ? t.offsetWidth * t.offsetHeight : true;
|
||||||
|
if (typeof t.getBoundingClientRect === 'function') {
|
||||||
|
rec = t.getBoundingClientRect();
|
||||||
|
tViz = rec.top >= 0 && rec.top < vpHeight;
|
||||||
|
bViz = rec.bottom > 0 && rec.bottom <= vpHeight;
|
||||||
|
lViz = rec.left >= 0 && rec.left < vpWidth;
|
||||||
|
rViz = rec.right > 0 && rec.right <= vpWidth;
|
||||||
|
vVisible = partial ? tViz || bViz : tViz && bViz;
|
||||||
|
hVisible = partial ? lViz || rViz : lViz && rViz;
|
||||||
|
if (direction === 'both') {
|
||||||
|
return clientSize && vVisible && hVisible;
|
||||||
|
} else if (direction === 'vertical') {
|
||||||
|
return clientSize && vVisible;
|
||||||
|
} else if (direction === 'horizontal') {
|
||||||
|
return clientSize && hVisible;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewTop = $w.scrollTop();
|
||||||
|
viewBottom = viewTop + vpHeight;
|
||||||
|
viewLeft = $w.scrollLeft();
|
||||||
|
viewRight = viewLeft + vpWidth;
|
||||||
|
offset = $t.offset();
|
||||||
|
_top = offset.top;
|
||||||
|
_bottom = _top + $t.height();
|
||||||
|
_left = offset.left;
|
||||||
|
_right = _left + $t.width();
|
||||||
|
compareTop = partial === true ? _bottom : _top;
|
||||||
|
compareBottom = partial === true ? _top : _bottom;
|
||||||
|
compareLeft = partial === true ? _right : _left;
|
||||||
|
compareRight = partial === true ? _left : _right;
|
||||||
|
if (direction === 'both') {
|
||||||
|
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop)) && ((compareRight <= viewRight) && (compareLeft >= viewLeft));
|
||||||
|
} else if (direction === 'vertical') {
|
||||||
|
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop));
|
||||||
|
} else if (direction === 'horizontal') {
|
||||||
|
return !!clientSize && ((compareRight <= viewRight) && (compareLeft >= viewLeft));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return ZammadChat;
|
return ZammadChat;
|
||||||
|
|
||||||
})(Base);
|
})(Base);
|
||||||
return window.ZammadChat = ZammadChat;
|
return window.ZammadChat = ZammadChat;
|
||||||
})(window.jQuery, window);
|
})(window.jQuery, window);
|
||||||
|
|
||||||
|
if (!window.zammadChatTemplates) {
|
||||||
|
window.zammadChatTemplates = {};
|
||||||
|
}
|
||||||
|
window.zammadChatTemplates["agent"] = function (__obj) {
|
||||||
|
if (!__obj) __obj = {};
|
||||||
|
var __out = [], __capture = function(callback) {
|
||||||
|
var out = __out, result;
|
||||||
|
__out = [];
|
||||||
|
callback.call(this);
|
||||||
|
result = __out.join('');
|
||||||
|
__out = out;
|
||||||
|
return __safe(result);
|
||||||
|
}, __sanitize = function(value) {
|
||||||
|
if (value && value.ecoSafe) {
|
||||||
|
return value;
|
||||||
|
} else if (typeof value !== 'undefined' && value != null) {
|
||||||
|
return __escape(value);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, __safe, __objSafe = __obj.safe, __escape = __obj.escape;
|
||||||
|
__safe = __obj.safe = function(value) {
|
||||||
|
if (value && value.ecoSafe) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
if (!(typeof value !== 'undefined' && value != null)) value = '';
|
||||||
|
var result = new String(value);
|
||||||
|
result.ecoSafe = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!__escape) {
|
||||||
|
__escape = __obj.escape = function(value) {
|
||||||
|
return ('' + value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(function() {
|
||||||
|
(function() {
|
||||||
|
if (this.agent.avatar) {
|
||||||
|
__out.push('\n<img class="zammad-chat-agent-avatar" src="');
|
||||||
|
__out.push(__sanitize(this.agent.avatar));
|
||||||
|
__out.push('">\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
__out.push('\n<span class="zammad-chat-agent-sentence">\n <span class="zammad-chat-agent-name">');
|
||||||
|
|
||||||
|
__out.push(__sanitize(this.agent.name));
|
||||||
|
|
||||||
|
__out.push('</span>\n</span>');
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
|
}).call(__obj);
|
||||||
|
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||||
|
return __out.join('');
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* ----------------------------------------------------------------------------
|
* ----------------------------------------------------------------------------
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||||
|
|
4
public/assets/chat/chat.min.js
vendored
4
public/assets/chat/chat.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue