Fixed#184 - prevent chat window from jumping whiling reading without new message, just with shown/hidden typing indicator.

This commit is contained in:
Martin Edenhofer 2016-06-28 11:34:57 +02:00
parent 47d63aa7b3
commit a88e6b03b3
6 changed files with 248 additions and 66 deletions

View file

@ -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
-----------------------------------------------------------------------------

View file

@ -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

View 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);

View file

@ -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

View file

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
}
(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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
}
(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):

File diff suppressed because one or more lines are too long