diff --git a/public/assets/chat/chat.coffee b/public/assets/chat/chat.coffee index 23aeff61e..a932b6c16 100644 --- a/public/assets/chat/chat.coffee +++ b/public/assets/chat/chat.coffee @@ -158,6 +158,7 @@ do($ = window.jQuery, window) -> buttonClass: 'open-zammad-chat' inactiveClass: 'is-inactive' title: 'Chat with us!' + scrollHint: 'Scrolle nach unten um neue Nachrichten zu sehen' idleTimeout: 6 idleTimeoutIntervallCheck: 0.5 inactiveTimeout: 8 @@ -180,6 +181,7 @@ do($ = window.jQuery, window) -> translations: de: 'Chat with us!': 'Chatte mit uns!' + 'Scroll down to see new messages': 'Scrolle nach unten um neue Nachrichten zu sehen' 'Online': 'Online' 'Online': 'Online' 'Offline': 'Offline' @@ -194,6 +196,8 @@ do($ = window.jQuery, window) -> 'Since you didn\'t respond in the last %s minutes your conversation with %s got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit %s geschlossen.' 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.' sessionId: undefined + scrolledToBottom: true + scrollSnapTolerance: 10 T: (string, items...) => if @options.lang && @options.lang isnt 'en' @@ -295,7 +299,8 @@ do($ = window.jQuery, window) -> renderBase: -> @el = $(@view('chat')( - title: @options.title + title: @options.title, + scrollHint: @options.scrollHint )) @options.target.append @el @@ -305,6 +310,8 @@ do($ = window.jQuery, window) -> @el.find('.js-chat-open').click @open @el.find('.js-chat-toggle').click @toggle @el.find('.zammad-chat-controls').on 'submit', @onSubmit + @el.find('.zammad-chat-body').on 'scroll', @detectScrolledtoBottom + @el.find('.zammad-scroll-hint').click @onScrollHintClick @input.on keydown: @checkForEnter input: @onInput @@ -503,11 +510,12 @@ do($ = window.jQuery, window) -> id: data.id from: 'agent' + @scrollToBottom showHint: true + renderMessage: (data) => @lastAddedType = "message--#{ data.from }" data.unreadClass = if document.hidden then ' zammad-chat-message--unread' else '' @el.find('.zammad-chat-body').append @view('message')(data) - @scrollToBottom() open: => if @isOpen @@ -717,8 +725,25 @@ do($ = window.jQuery, window) -> @scrollToBottom() - scrollToBottom: -> - @el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')) + detectScrolledtoBottom: => + scrollBottom = @el.find('.zammad-chat-body').scrollTop() + @el.find('.zammad-chat-body').outerHeight() + @scrolledToBottom = Math.abs(scrollBottom - @el.find('.zammad-chat-body').prop('scrollHeight')) <= @scrollSnapTolerance + @el.find('.zammad-scroll-hint').addClass('is-hidden') if @scrolledToBottom + + showScrollHint: -> + @el.find('.zammad-scroll-hint').removeClass('is-hidden') + # compensate scroll + @el.find('.zammad-chat-body').scrollTop(@el.find('.zammad-chat-body').scrollTop() + @el.find('.zammad-scroll-hint').outerHeight()) + + onScrollHintClick: => + # animate scroll + @el.find('.zammad-chat-body').animate({scrollTop: @el.find('.zammad-chat-body').prop('scrollHeight')}, 300) + + scrollToBottom: ({ showHint } = { showHint: false }) -> + if @scrolledToBottom + @el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')) + else if showHint + @showScrollHint() destroy: (params = {}) => @log.debug 'destroy widget', params diff --git a/public/assets/chat/chat.css b/public/assets/chat/chat.css index 632bcb3ec..774230bd4 100644 --- a/public/assets/chat/chat.css +++ b/public/assets/chat/chat.css @@ -191,6 +191,25 @@ margin-right: 8px; vertical-align: middle; } +.zammad-scroll-hint { + background: #f9fafa; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + border-bottom: 1px solid #e8e8e8; + padding: 7px 10px 6px; + color: #999999; + cursor: pointer; } + .zammad-scroll-hint.is-hidden { + display: none; } + +.zammad-scroll-hint-icon { + fill: #c4c7ca; + margin-right: 8px; } + .zammad-chat-body { padding: 0.5em 1em; overflow: auto; diff --git a/public/assets/chat/chat.js b/public/assets/chat/chat.js index ee0b8f560..ef4ce3006 100644 --- a/public/assets/chat/chat.js +++ b/public/assets/chat/chat.js @@ -271,6 +271,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); buttonClass: 'open-zammad-chat', inactiveClass: 'is-inactive', title: 'Chat with us!', + scrollHint: 'Scrolle nach unten um neue Nachrichten zu sehen', idleTimeout: 6, idleTimeoutIntervallCheck: 0.5, inactiveTimeout: 8, @@ -306,6 +307,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); ZammadChat.prototype.translations = { de: { 'Chat with us!': 'Chatte mit uns!', + 'Scroll down to see new messages': 'Scrolle nach unten um neue Nachrichten zu sehen', 'Online': 'Online', 'Online': 'Online', 'Offline': 'Offline', @@ -324,6 +326,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); ZammadChat.prototype.sessionId = void 0; + ZammadChat.prototype.scrolledToBottom = true; + + ZammadChat.prototype.scrollSnapTolerance = 10; + ZammadChat.prototype.T = function() { var i, item, items, len, string, translations; string = arguments[0], items = 2 <= arguments.length ? slice.call(arguments, 1) : []; @@ -371,6 +377,8 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); this.onConnectionReestablished = bind(this.onConnectionReestablished, this); this.reconnect = bind(this.reconnect, this); this.destroy = bind(this.destroy, this); + this.onScrollHintClick = bind(this.onScrollHintClick, this); + this.detectScrolledtoBottom = bind(this.detectScrolledtoBottom, this); this.onLeaveTemporary = bind(this.onLeaveTemporary, this); this.onAgentTypingEnd = bind(this.onAgentTypingEnd, this); this.onAgentTypingStart = bind(this.onAgentTypingStart, this); @@ -471,13 +479,16 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); ZammadChat.prototype.renderBase = function() { this.el = $(this.view('chat')({ - title: this.options.title + title: this.options.title, + scrollHint: this.options.scrollHint })); this.options.target.append(this.el); this.input = this.el.find('.zammad-chat-input'); this.el.find('.js-chat-open').click(this.open); this.el.find('.js-chat-toggle').click(this.toggle); this.el.find('.zammad-chat-controls').on('submit', this.onSubmit); + this.el.find('.zammad-chat-body').on('scroll', this.detectScrolledtoBottom); + this.el.find('.zammad-scroll-hint').click(this.onScrollHintClick); this.input.on({ keydown: this.checkForEnter, input: this.onInput @@ -712,18 +723,20 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); this.inactiveTimeout.start(); this.onAgentTypingEnd(); this.maybeAddTimestamp(); - return this.renderMessage({ + this.renderMessage({ message: data.message.content, id: data.id, from: 'agent' }); + return this.scrollToBottom({ + showHint: true + }); }; ZammadChat.prototype.renderMessage = function(data) { this.lastAddedType = "message--" + data.from; data.unreadClass = document.hidden ? ' zammad-chat-message--unread' : ''; - this.el.find('.zammad-chat-body').append(this.view('message')(data)); - return this.scrollToBottom(); + return this.el.find('.zammad-chat-body').append(this.view('message')(data)); }; ZammadChat.prototype.open = function() { @@ -955,8 +968,36 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); return this.scrollToBottom(); }; - ZammadChat.prototype.scrollToBottom = function() { - return this.el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')); + ZammadChat.prototype.detectScrolledtoBottom = function() { + var scrollBottom; + scrollBottom = this.el.find('.zammad-chat-body').scrollTop() + this.el.find('.zammad-chat-body').outerHeight(); + this.scrolledToBottom = Math.abs(scrollBottom - this.el.find('.zammad-chat-body').prop('scrollHeight')) <= this.scrollSnapTolerance; + if (this.scrolledToBottom) { + return this.el.find('.zammad-scroll-hint').addClass('is-hidden'); + } + }; + + ZammadChat.prototype.showScrollHint = function() { + this.el.find('.zammad-scroll-hint').removeClass('is-hidden'); + return this.el.find('.zammad-chat-body').scrollTop(this.el.find('.zammad-chat-body').scrollTop() + this.el.find('.zammad-scroll-hint').outerHeight()); + }; + + ZammadChat.prototype.onScrollHintClick = function() { + return this.el.find('.zammad-chat-body').animate({ + scrollTop: this.el.find('.zammad-chat-body').prop('scrollHeight') + }, 300); + }; + + ZammadChat.prototype.scrollToBottom = function(arg) { + var showHint; + showHint = (arg != null ? arg : { + showHint: false + }).showHint; + if (this.scrolledToBottom) { + return this.el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')); + } else if (showHint) { + return this.showScrollHint(); + } }; ZammadChat.prototype.destroy = function(params) { @@ -1234,67 +1275,6 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); 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, '"'); - }; - } - (function() { - (function() { - if (this.agent.avatar) { - __out.push('\n\n'); - } - - __out.push('\n\n '); - - __out.push(__sanitize(this.agent.name)); - - __out.push('\n'); - - }).call(this); - - }).call(__obj); - __obj.safe = __objSafe, __obj.escape = __escape; - return __out.join(''); -}; - /*! * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): @@ -1380,6 +1360,67 @@ jQuery.fn.autoGrow = function(options) { }); }; +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, '"'); + }; + } + (function() { + (function() { + if (this.agent.avatar) { + __out.push('\n\n'); + } + + __out.push('\n\n '); + + __out.push(__sanitize(this.agent.name)); + + __out.push('\n'); + + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}; + if (!window.zammadChatTemplates) { window.zammadChatTemplates = {}; } @@ -1444,7 +1485,11 @@ window.zammadChatTemplates["chat"] = function (__obj) { __out.push(this.T(this.title)); - __out.push('\n \n \n
\n \n