chat client: port scroll-hint from customer chat

This commit is contained in:
Felix Niklas 2016-07-05 13:00:49 +02:00
parent 21224200b4
commit b7a2ba06f1
6 changed files with 186 additions and 74 deletions

View file

@ -158,6 +158,7 @@ do($ = window.jQuery, window) ->
buttonClass: 'open-zammad-chat' buttonClass: 'open-zammad-chat'
inactiveClass: 'is-inactive' inactiveClass: 'is-inactive'
title: '<strong>Chat</strong> with us!' title: '<strong>Chat</strong> with us!'
scrollHint: 'Scrolle nach unten um neue Nachrichten zu sehen'
idleTimeout: 6 idleTimeout: 6
idleTimeoutIntervallCheck: 0.5 idleTimeoutIntervallCheck: 0.5
inactiveTimeout: 8 inactiveTimeout: 8
@ -180,6 +181,7 @@ do($ = window.jQuery, window) ->
translations: translations:
de: de:
'<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!' '<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!'
'Scroll down to see new messages': 'Scrolle nach unten um neue Nachrichten zu sehen'
'Online': 'Online' 'Online': 'Online'
'Online': 'Online' 'Online': 'Online'
'Offline': 'Offline' 'Offline': 'Offline'
@ -194,6 +196,8 @@ do($ = window.jQuery, window) ->
'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit <strong>%s</strong> geschlossen.' 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit <strong>%s</strong> 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.' '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 sessionId: undefined
scrolledToBottom: true
scrollSnapTolerance: 10
T: (string, items...) => T: (string, items...) =>
if @options.lang && @options.lang isnt 'en' if @options.lang && @options.lang isnt 'en'
@ -295,7 +299,8 @@ do($ = window.jQuery, window) ->
renderBase: -> renderBase: ->
@el = $(@view('chat')( @el = $(@view('chat')(
title: @options.title title: @options.title,
scrollHint: @options.scrollHint
)) ))
@options.target.append @el @options.target.append @el
@ -305,6 +310,8 @@ do($ = window.jQuery, window) ->
@el.find('.js-chat-open').click @open @el.find('.js-chat-open').click @open
@el.find('.js-chat-toggle').click @toggle @el.find('.js-chat-toggle').click @toggle
@el.find('.zammad-chat-controls').on 'submit', @onSubmit @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 @input.on
keydown: @checkForEnter keydown: @checkForEnter
input: @onInput input: @onInput
@ -503,11 +510,12 @@ do($ = window.jQuery, window) ->
id: data.id id: data.id
from: 'agent' from: 'agent'
@scrollToBottom showHint: true
renderMessage: (data) => renderMessage: (data) =>
@lastAddedType = "message--#{ data.from }" @lastAddedType = "message--#{ data.from }"
data.unreadClass = if document.hidden then ' zammad-chat-message--unread' else '' data.unreadClass = if document.hidden then ' zammad-chat-message--unread' else ''
@el.find('.zammad-chat-body').append @view('message')(data) @el.find('.zammad-chat-body').append @view('message')(data)
@scrollToBottom()
open: => open: =>
if @isOpen if @isOpen
@ -717,8 +725,25 @@ do($ = window.jQuery, window) ->
@scrollToBottom() @scrollToBottom()
scrollToBottom: -> detectScrolledtoBottom: =>
@el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')) 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 = {}) => destroy: (params = {}) =>
@log.debug 'destroy widget', params @log.debug 'destroy widget', params

View file

@ -191,6 +191,25 @@
margin-right: 8px; margin-right: 8px;
vertical-align: middle; } 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 { .zammad-chat-body {
padding: 0.5em 1em; padding: 0.5em 1em;
overflow: auto; overflow: auto;

View file

@ -271,6 +271,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
buttonClass: 'open-zammad-chat', buttonClass: 'open-zammad-chat',
inactiveClass: 'is-inactive', inactiveClass: 'is-inactive',
title: '<strong>Chat</strong> with us!', title: '<strong>Chat</strong> with us!',
scrollHint: 'Scrolle nach unten um neue Nachrichten zu sehen',
idleTimeout: 6, idleTimeout: 6,
idleTimeoutIntervallCheck: 0.5, idleTimeoutIntervallCheck: 0.5,
inactiveTimeout: 8, inactiveTimeout: 8,
@ -306,6 +307,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
ZammadChat.prototype.translations = { ZammadChat.prototype.translations = {
de: { de: {
'<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!', '<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!',
'Scroll down to see new messages': 'Scrolle nach unten um neue Nachrichten zu sehen',
'Online': 'Online', 'Online': 'Online',
'Online': 'Online', 'Online': 'Online',
'Offline': 'Offline', '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.sessionId = void 0;
ZammadChat.prototype.scrolledToBottom = true;
ZammadChat.prototype.scrollSnapTolerance = 10;
ZammadChat.prototype.T = function() { ZammadChat.prototype.T = function() {
var i, item, items, len, string, translations; var i, item, items, len, string, translations;
string = arguments[0], items = 2 <= arguments.length ? slice.call(arguments, 1) : []; 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.onConnectionReestablished = bind(this.onConnectionReestablished, this);
this.reconnect = bind(this.reconnect, this); this.reconnect = bind(this.reconnect, this);
this.destroy = bind(this.destroy, 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.onLeaveTemporary = bind(this.onLeaveTemporary, this);
this.onAgentTypingEnd = bind(this.onAgentTypingEnd, this); this.onAgentTypingEnd = bind(this.onAgentTypingEnd, this);
this.onAgentTypingStart = bind(this.onAgentTypingStart, 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() { ZammadChat.prototype.renderBase = function() {
this.el = $(this.view('chat')({ this.el = $(this.view('chat')({
title: this.options.title title: this.options.title,
scrollHint: this.options.scrollHint
})); }));
this.options.target.append(this.el); this.options.target.append(this.el);
this.input = this.el.find('.zammad-chat-input'); this.input = this.el.find('.zammad-chat-input');
this.el.find('.js-chat-open').click(this.open); this.el.find('.js-chat-open').click(this.open);
this.el.find('.js-chat-toggle').click(this.toggle); this.el.find('.js-chat-toggle').click(this.toggle);
this.el.find('.zammad-chat-controls').on('submit', this.onSubmit); 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({ this.input.on({
keydown: this.checkForEnter, keydown: this.checkForEnter,
input: this.onInput input: this.onInput
@ -712,18 +723,20 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
this.inactiveTimeout.start(); this.inactiveTimeout.start();
this.onAgentTypingEnd(); this.onAgentTypingEnd();
this.maybeAddTimestamp(); this.maybeAddTimestamp();
return this.renderMessage({ this.renderMessage({
message: data.message.content, message: data.message.content,
id: data.id, id: data.id,
from: 'agent' from: 'agent'
}); });
return this.scrollToBottom({
showHint: true
});
}; };
ZammadChat.prototype.renderMessage = function(data) { ZammadChat.prototype.renderMessage = function(data) {
this.lastAddedType = "message--" + data.from; this.lastAddedType = "message--" + data.from;
data.unreadClass = document.hidden ? ' zammad-chat-message--unread' : ''; data.unreadClass = document.hidden ? ' zammad-chat-message--unread' : '';
this.el.find('.zammad-chat-body').append(this.view('message')(data)); return this.el.find('.zammad-chat-body').append(this.view('message')(data));
return this.scrollToBottom();
}; };
ZammadChat.prototype.open = function() { ZammadChat.prototype.open = function() {
@ -955,8 +968,36 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
return this.scrollToBottom(); return this.scrollToBottom();
}; };
ZammadChat.prototype.scrollToBottom = function() { ZammadChat.prototype.detectScrolledtoBottom = function() {
return this.el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')); 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) { 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; 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, '&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): * "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, '&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('');
};
if (!window.zammadChatTemplates) { if (!window.zammadChatTemplates) {
window.zammadChatTemplates = {}; window.zammadChatTemplates = {};
} }
@ -1444,7 +1485,11 @@ window.zammadChatTemplates["chat"] = function (__obj) {
__out.push(this.T(this.title)); __out.push(this.T(this.title));
__out.push('</span>\n </div>\n </div>\n <div class="zammad-chat-modal"></div>\n <div class="zammad-chat-body"></div>\n <form class="zammad-chat-controls">\n <textarea class="zammad-chat-input" rows="1" placeholder="'); __out.push('</span>\n </div>\n </div>\n <div class="zammad-chat-modal"></div>\n <div class="zammad-scroll-hint is-hidden">\n <svg class="zammad-scroll-hint-icon" width="20" height="18" viewBox="0 0 20 18"><path d="M0,2.00585866 C0,0.898053512 0.898212381,0 1.99079514,0 L18.0092049,0 C19.1086907,0 20,0.897060126 20,2.00585866 L20,11.9941413 C20,13.1019465 19.1017876,14 18.0092049,14 L1.99079514,14 C0.891309342,14 0,13.1029399 0,11.9941413 L0,2.00585866 Z M10,14 L16,18 L16,14 L10,14 Z" fill-rule="evenodd"/></svg>\n ');
__out.push(this.T(this.scrollHint));
__out.push('\n </div>\n <div class="zammad-chat-body"></div>\n <form class="zammad-chat-controls">\n <textarea class="zammad-chat-input" rows="1" placeholder="');
__out.push(this.T('Compose your message...')); __out.push(this.T('Compose your message...'));

File diff suppressed because one or more lines are too long

View file

@ -200,6 +200,25 @@
vertical-align: middle; vertical-align: middle;
} }
.zammad-scroll-hint {
background: hsl(210,8%,98%);
display: flex;
align-items: center;
border-bottom: 1px solid hsl(0,0%,91%);
padding: 7px 10px 6px;
color: hsl(0,0%,60%);
cursor: pointer;
&.is-hidden {
display: none;
}
}
.zammad-scroll-hint-icon {
fill: hsl(210,5%,78%);
margin-right: 8px;
}
.zammad-chat-body { .zammad-chat-body {
padding: 0.5em 1em; padding: 0.5em 1em;
overflow: auto; overflow: auto;

View file

@ -15,6 +15,10 @@
</div> </div>
</div> </div>
<div class="zammad-chat-modal"></div> <div class="zammad-chat-modal"></div>
<div class="zammad-scroll-hint is-hidden">
<svg class="zammad-scroll-hint-icon" width="20" height="18" viewBox="0 0 20 18"><path d="M0,2.00585866 C0,0.898053512 0.898212381,0 1.99079514,0 L18.0092049,0 C19.1086907,0 20,0.897060126 20,2.00585866 L20,11.9941413 C20,13.1019465 19.1017876,14 18.0092049,14 L1.99079514,14 C0.891309342,14 0,13.1029399 0,11.9941413 L0,2.00585866 Z M10,14 L16,18 L16,14 L10,14 Z" fill-rule="evenodd"/></svg>
<%- @T(@scrollHint) %>
</div>
<div class="zammad-chat-body"></div> <div class="zammad-chat-body"></div>
<form class="zammad-chat-controls"> <form class="zammad-chat-controls">
<textarea class="zammad-chat-input" rows="1" placeholder="<%- @T('Compose your message...') %>"></textarea> <textarea class="zammad-chat-input" rows="1" placeholder="<%- @T('Compose your message...') %>"></textarea>