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
|
||||
Source: http://jquery.com/
|
||||
Copyright 2005, 2014 jQuery Foundation, Inc.
|
||||
Copyright: 2005, 2014 jQuery Foundation, Inc.
|
||||
License: MIT license
|
||||
-----------------------------------------------------------------------------
|
||||
jquery-ui.js
|
||||
|
@ -135,9 +135,14 @@ License: MIT license
|
|||
-----------------------------------------------------------------------------
|
||||
jquery.hotkeys.js
|
||||
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.
|
||||
-----------------------------------------------------------------------------
|
||||
jquery.visible.js
|
||||
Source: https://github.com/customd/jquery-visible
|
||||
Copyright: 2012, Digital Fusion
|
||||
License: MIT license
|
||||
-----------------------------------------------------------------------------
|
||||
underscore.js
|
||||
Source: http://underscorejs.org
|
||||
Copyright: 2009-2015 Jeremy Ashkenas, DocumentCloud
|
||||
|
@ -155,6 +160,6 @@ License: MIT license
|
|||
-----------------------------------------------------------------------------
|
||||
word_filter.js
|
||||
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
|
||||
-----------------------------------------------------------------------------
|
||||
|
|
|
@ -611,6 +611,7 @@ class ChatWindow extends App.Controller
|
|||
@isTyping = true
|
||||
@maybeAddTimestamp()
|
||||
@body.append App.view('customer_chat/chat_loader')()
|
||||
return if !@el.find('.js-loader').visible(true)
|
||||
@scrollToBottom()
|
||||
|
||||
# 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')()
|
||||
|
||||
# only if typing indicator is shown
|
||||
return if !@isVisible(@el.find('.zammad-chat-message--typing'), true)
|
||||
@scrollToBottom()
|
||||
|
||||
onAgentTypingEnd: =>
|
||||
|
@ -898,4 +900,56 @@ do($ = window.jQuery, window) ->
|
|||
overflow: ''
|
||||
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
|
||||
|
|
|
@ -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); }; },
|
||||
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; },
|
||||
|
@ -955,6 +894,9 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
|||
}
|
||||
this.maybeAddTimestamp();
|
||||
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();
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
})(Base);
|
||||
return window.ZammadChat = ZammadChat;
|
||||
})(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):
|
||||
|
|
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