Browse Source

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

Martin Edenhofer 8 years ago
parent
commit
a88e6b03b3

+ 8 - 3
LICENSE-3RD-PARTY.txt

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

+ 1 - 0
app/assets/javascripts/app/controllers/chat.coffee

@@ -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 - 0
app/assets/javascripts/app/lib/base/jquery.visible.js

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

+ 54 - 0
public/assets/chat/chat.coffee

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

+ 115 - 61
public/assets/chat/chat.js

@@ -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 it is too large
+ 0 - 0
public/assets/chat/chat.min.js


Some files were not shown because too many files changed in this diff