customer chat built + sources
This commit is contained in:
parent
deded86b63
commit
67886a6af7
13 changed files with 1408 additions and 0 deletions
227
public/assets/chat/chat.coffee
Normal file
227
public/assets/chat/chat.coffee
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
do($ = window.jQuery, window) ->
|
||||||
|
|
||||||
|
# Define the plugin class
|
||||||
|
class ZammadChat
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
invitationPhrase: '<strong>Chat</strong> with us!'
|
||||||
|
agentPhrase: '%%agent%% is helping you'
|
||||||
|
show: true
|
||||||
|
target: $('body')
|
||||||
|
|
||||||
|
isOpen: false
|
||||||
|
blinkOnlineInterval: null
|
||||||
|
stopBlinOnlineStateTimeout: null
|
||||||
|
showTimeEveryXMinutes: 1
|
||||||
|
lastTimestamp: null
|
||||||
|
lastAddedType: null
|
||||||
|
inputTimeout: null
|
||||||
|
isTyping: false
|
||||||
|
isOnline: true
|
||||||
|
strings:
|
||||||
|
'Online': 'Online'
|
||||||
|
'Offline': 'Offline'
|
||||||
|
'Connecting': 'Connecting'
|
||||||
|
'Connection re-established': 'Connection re-established'
|
||||||
|
'Today': 'Today'
|
||||||
|
|
||||||
|
T: (string) ->
|
||||||
|
return @strings[string]
|
||||||
|
|
||||||
|
view: (name) ->
|
||||||
|
return window.zammadChatTemplates[name]
|
||||||
|
|
||||||
|
constructor: (el, options) ->
|
||||||
|
@options = $.extend {}, @defaults, options
|
||||||
|
@el = $(@view('chat')())
|
||||||
|
@options.target.append @el
|
||||||
|
|
||||||
|
@setAgentOnlineState @isOnline
|
||||||
|
|
||||||
|
@show() if @options.show
|
||||||
|
|
||||||
|
@el.find('.zammad-chat-header').click @toggle
|
||||||
|
@el.find('.zammad-chat-controls').on 'submit', @onSubmit
|
||||||
|
@el.find('.zammad-chat-input').on(
|
||||||
|
keydown: @checkForEnter
|
||||||
|
input: @onInput
|
||||||
|
).autoGrow { extraLine: false }
|
||||||
|
|
||||||
|
checkForEnter: (event) =>
|
||||||
|
if not event.shiftKey and event.keyCode is 13
|
||||||
|
event.preventDefault()
|
||||||
|
@sendMessage()
|
||||||
|
|
||||||
|
onInput: =>
|
||||||
|
# remove unread-state from messages
|
||||||
|
@el.find('.zammad-chat-message--unread')
|
||||||
|
.removeClass 'zammad-chat-message--unread'
|
||||||
|
|
||||||
|
clearTimeout(@inputTimeout) if @inputTimeout
|
||||||
|
|
||||||
|
# fire typingEnd after 5 seconds
|
||||||
|
@inputTimeout = setTimeout @onTypingEnd, 5000
|
||||||
|
|
||||||
|
@onTypingStart() if @isTyping
|
||||||
|
|
||||||
|
onTypingStart: ->
|
||||||
|
# send typing start event
|
||||||
|
@isTyping = true
|
||||||
|
|
||||||
|
onTypingEnd: =>
|
||||||
|
# send typing end event
|
||||||
|
@isTyping = false
|
||||||
|
|
||||||
|
onSubmit: (event) =>
|
||||||
|
event.preventDefault()
|
||||||
|
@sendMessage()
|
||||||
|
|
||||||
|
sendMessage: ->
|
||||||
|
message = @el.find('.zammad-chat-input').val()
|
||||||
|
|
||||||
|
if message
|
||||||
|
messageElement = @view('message') { message: message }
|
||||||
|
|
||||||
|
@maybeAddTimestamp()
|
||||||
|
|
||||||
|
# add message before message typing loader
|
||||||
|
if @el.find('.zammad-chat-message--typing').size()
|
||||||
|
@lastAddedType = 'typing-placeholder'
|
||||||
|
@el.find('.zammad-chat-message--typing').before messageElement
|
||||||
|
else
|
||||||
|
@lastAddedType = 'message--customer'
|
||||||
|
@el.find('.zammad-chat-body').append messageElement
|
||||||
|
|
||||||
|
@el.find('.zammad-chat-input').val('')
|
||||||
|
@scrollToBottom()
|
||||||
|
|
||||||
|
@isTyping = false
|
||||||
|
# send message event
|
||||||
|
|
||||||
|
receiveMessage: (message) =>
|
||||||
|
# hide writing indicator
|
||||||
|
@onAgentTypingEnd()
|
||||||
|
|
||||||
|
@maybeAddTimestamp()
|
||||||
|
|
||||||
|
@lastAddedType = 'message--agent'
|
||||||
|
unread = document.hidden ? " zammad-chat-message--unread" : ""
|
||||||
|
@el.find('.zammad-chat-body').append @view('message')({message: message})
|
||||||
|
@scrollToBottom()
|
||||||
|
|
||||||
|
toggle: =>
|
||||||
|
if @isOpen then @close() else @open()
|
||||||
|
|
||||||
|
open: ->
|
||||||
|
@el
|
||||||
|
.addClass('zammad-chat-is-open')
|
||||||
|
.animate { bottom: 0 }, 500, @onOpenAnimationEnd
|
||||||
|
|
||||||
|
onOpenAnimationEnd: =>
|
||||||
|
@isOpen = true
|
||||||
|
setTimeout @onConnectionEstablished, 1180
|
||||||
|
setTimeout @onAgentTypingStart, 2000
|
||||||
|
setTimeout @receiveMessage, 5000, "Hello! How can I help you?"
|
||||||
|
@connect()
|
||||||
|
|
||||||
|
close: ->
|
||||||
|
remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
|
||||||
|
@el.animate { bottom: -remainerHeight }, 500, onCloseAnimationEnd
|
||||||
|
|
||||||
|
onCloseAnimationEnd: =>
|
||||||
|
@el.removeClass('zammad-chat-is-open')
|
||||||
|
@disconnect()
|
||||||
|
@isOpen = false
|
||||||
|
|
||||||
|
hide: ->
|
||||||
|
@el.removeClass('zammad-chat-is-visible')
|
||||||
|
|
||||||
|
show: ->
|
||||||
|
@el.addClass('zammad-chat-is-visible')
|
||||||
|
|
||||||
|
remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
|
||||||
|
|
||||||
|
@el.css 'bottom', -remainerHeight
|
||||||
|
|
||||||
|
onAgentTypingStart: =>
|
||||||
|
# never display two loaders
|
||||||
|
return if @el.find('.zammad-chat-message--typing').size()
|
||||||
|
|
||||||
|
@maybeAddTimestamp()
|
||||||
|
|
||||||
|
@el.find('.zammad-chat-body').append @view('typingIndicator')()
|
||||||
|
|
||||||
|
@scrollToBottom()
|
||||||
|
|
||||||
|
onAgentTypingEnd: =>
|
||||||
|
@el.find('.zammad-chat-message--typing').remove()
|
||||||
|
|
||||||
|
maybeAddTimestamp: ->
|
||||||
|
timestamp = Date.now()
|
||||||
|
|
||||||
|
if !@lastTimestamp or timestamp - @lastTimestamp > @showTimeEveryXMinutes * 60000
|
||||||
|
label = @T('Today')
|
||||||
|
time = new Date().toTimeString().substr 0,5
|
||||||
|
if @lastAddedType is 'timestamp'
|
||||||
|
# update last time
|
||||||
|
@updateLastTimestamp label, time
|
||||||
|
@lastTimestamp = timestamp
|
||||||
|
else
|
||||||
|
# add new timestamp
|
||||||
|
@addStatus label, time
|
||||||
|
@lastTimestamp = timestamp
|
||||||
|
@lastAddedType = 'timestamp'
|
||||||
|
|
||||||
|
updateLastTimestamp: (label, time) ->
|
||||||
|
@el.find('.zammad-chat-body')
|
||||||
|
.find('.zammad-chat-status')
|
||||||
|
.last()
|
||||||
|
.replaceWith @view('status')
|
||||||
|
label: label
|
||||||
|
time: time
|
||||||
|
|
||||||
|
addStatus: (label, time) ->
|
||||||
|
@el.find('.zammad-chat-body').append( @view('status')
|
||||||
|
label: label
|
||||||
|
time: time
|
||||||
|
|
||||||
|
scrollToBottom: ->
|
||||||
|
@el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
|
||||||
|
|
||||||
|
connect: ->
|
||||||
|
|
||||||
|
|
||||||
|
reconnect: =>
|
||||||
|
# set status to connecting
|
||||||
|
@lastAddedType = 'status'
|
||||||
|
@el.find('.zammad-chat-agent-status').attr('data-status', 'connecting').text @T('Connecting')
|
||||||
|
@addStatus @T('Connection lost')
|
||||||
|
|
||||||
|
onConnectionReestablished: =>
|
||||||
|
# set status back to online
|
||||||
|
@lastAddedType = 'status'
|
||||||
|
@el.find('.zammad-chat-agent-status').attr('data-status', 'online').text @T('Online')
|
||||||
|
@addStatus @T('Connection re-established')
|
||||||
|
|
||||||
|
disconnect: ->
|
||||||
|
@el.find('.zammad-chat-loader').removeClass('zammad-chat-is-hidden');
|
||||||
|
@el.find('.zammad-chat-welcome').removeClass('zammad-chat-is-hidden');
|
||||||
|
@el.find('.zammad-chat-agent').addClass('zammad-chat-is-hidden');
|
||||||
|
@el.find('.zammad-chat-agent-status').addClass('zammad-chat-is-hidden');
|
||||||
|
|
||||||
|
onConnectionEstablished: =>
|
||||||
|
@el.find('.zammad-chat-loader').addClass('zammad-chat-is-hidden');
|
||||||
|
@el.find('.zammad-chat-welcome').addClass('zammad-chat-is-hidden');
|
||||||
|
@el.find('.zammad-chat-agent').removeClass('zammad-chat-is-hidden');
|
||||||
|
@el.find('.zammad-chat-agent-status').removeClass('zammad-chat-is-hidden');
|
||||||
|
@el.find('.zammad-chat-input').focus();
|
||||||
|
|
||||||
|
setAgentOnlineState: (state) =>
|
||||||
|
@isOnline = state
|
||||||
|
@el
|
||||||
|
.find('.zammad-chat-agent-status')
|
||||||
|
.toggleClass('zammad-chat-is-online', state)
|
||||||
|
.text if state then @T('Online') else @T('Offline')
|
||||||
|
|
||||||
|
$(document).ready ->
|
||||||
|
window.zammadChat = new ZammadChat()
|
292
public/assets/chat/chat.js
Normal file
292
public/assets/chat/chat.js
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
if (!window.zammadChatTemplates) {
|
||||||
|
window.zammadChatTemplates = {};
|
||||||
|
}
|
||||||
|
window.zammadChatTemplates["chat"] = 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() {
|
||||||
|
__out.push('<div class="zammad-chat">\n <div class="zammad-chat-header">\n <div class="zammad-chat-header-controls">\n <span class="zammad-chat-agent-status zammad-chat-is-hidden" data-status="online">Online</span>\n <span class="zammad-chat-toggle">\n <svg class="zammad-chat-toggle-icon-open" viewBox="0 0 13 7"><path d="M10.807 7l1.4-1.428-5-4.9L6.5-.02l-.7.7-4.9 4.9 1.414 1.413L6.5 2.886 10.807 7z" fill-rule="evenodd"/></svg>\n <svg class="zammad-chat-toggle-icon-close" viewBox="0 0 13 7"><path d="M6.554 4.214L2.246 0l-1.4 1.428 5 4.9.708.693.7-.7 4.9-4.9L10.74.008 6.553 4.214z" fill-rule="evenodd"/></svg>\n </span>\n </div>\n <div class="zammad-chat-agent zammad-chat-is-hidden">\n <img class="zammad-chat-agent-avatar" src="https://s3.amazonaws.com/uifaces/faces/twitter/joshaustin/128.jpg">\n <span class="zammad-chat-agent-sentence">\n <span class="zammad-chat-agent-name">Adam</span> is helping you.\n </span>\n </div>\n <div class="zammad-chat-welcome">\n <svg class="zammad-chat-icon" viewBox="0 0 24 24"><path d="M2 5C2 4 3 3 4 3h16c1 0 2 1 2 2v10C22 16 21 17 20 17H4C3 17 2 16 2 15V5zM12 17l6 4v-4h-6z" fill-rule="evenodd"/></svg>\n <span class="zammad-chat-welcome-text"><strong>Chat</strong> with us!</span>\n </div>\n </div>\n <div class="zammad-chat-loader">\n <span class="zammad-chat-loading-animation">\n <span class="zammad-chat-loading-circle"></span>\n <span class="zammad-chat-loading-circle"></span>\n <span class="zammad-chat-loading-circle"></span>\n </span>\n <span class="zammad-chat-loader-text">Connecting</span>\n </div>\n <div class="zammad-chat-body"></div>\n <form class="zammad-chat-controls">\n <textarea class="zammad-chat-input" rows="1" placeholder="Compose your message..."></textarea>\n <button type="submit" class="zammad-chat-send">Send</button>\n </form>\n</div>');
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
|
}).call(__obj);
|
||||||
|
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||||
|
return __out.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||||
|
* <jevin9@gmail.com> wrote this file. As long as you retain this notice you
|
||||||
|
* can do whatever you want with this stuff. If we meet some day, and you think
|
||||||
|
* this stuff is worth it, you can buy me a beer in return. Jevin O. Sewaruth
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Autogrow Textarea Plugin Version v3.0
|
||||||
|
* http://www.technoreply.com/autogrow-textarea-plugin-3-0
|
||||||
|
*
|
||||||
|
* THIS PLUGIN IS DELIVERD ON A PAY WHAT YOU WHANT BASIS. IF THE PLUGIN WAS USEFUL TO YOU, PLEASE CONSIDER BUYING THE PLUGIN HERE :
|
||||||
|
* https://sites.fastspring.com/technoreply/instant/autogrowtextareaplugin
|
||||||
|
*
|
||||||
|
* Date: October 15, 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
jQuery.fn.autoGrow = function(options) {
|
||||||
|
return this.each(function() {
|
||||||
|
var settings = jQuery.extend({
|
||||||
|
extraLine: true,
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
var createMirror = function(textarea) {
|
||||||
|
jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
|
||||||
|
return jQuery(textarea).next('.autogrow-textarea-mirror')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendContentToMirror = function (textarea) {
|
||||||
|
mirror.innerHTML = String(textarea.value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
.replace(/\n/g, '<br />') +
|
||||||
|
(settings.extraLine? '.<br/>.' : '')
|
||||||
|
;
|
||||||
|
|
||||||
|
if (jQuery(textarea).height() != jQuery(mirror).height())
|
||||||
|
jQuery(textarea).height(jQuery(mirror).height());
|
||||||
|
}
|
||||||
|
|
||||||
|
var growTextarea = function () {
|
||||||
|
sendContentToMirror(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a mirror
|
||||||
|
var mirror = createMirror(this);
|
||||||
|
|
||||||
|
// Style the mirror
|
||||||
|
mirror.style.display = 'none';
|
||||||
|
mirror.style.wordWrap = 'break-word';
|
||||||
|
mirror.style.whiteSpace = 'normal';
|
||||||
|
mirror.style.padding = jQuery(this).css('paddingTop') + ' ' +
|
||||||
|
jQuery(this).css('paddingRight') + ' ' +
|
||||||
|
jQuery(this).css('paddingBottom') + ' ' +
|
||||||
|
jQuery(this).css('paddingLeft');
|
||||||
|
|
||||||
|
mirror.style.width = jQuery(this).css('width');
|
||||||
|
mirror.style.fontFamily = jQuery(this).css('font-family');
|
||||||
|
mirror.style.fontSize = jQuery(this).css('font-size');
|
||||||
|
mirror.style.lineHeight = jQuery(this).css('line-height');
|
||||||
|
|
||||||
|
// Style the textarea
|
||||||
|
this.style.overflow = "hidden";
|
||||||
|
this.style.minHeight = this.rows+"em";
|
||||||
|
|
||||||
|
// Bind the textarea's event
|
||||||
|
this.onkeyup = growTextarea;
|
||||||
|
|
||||||
|
// Fire the event for text already present
|
||||||
|
sendContentToMirror(this);
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (!window.zammadChatTemplates) {
|
||||||
|
window.zammadChatTemplates = {};
|
||||||
|
}
|
||||||
|
window.zammadChatTemplates["message"] = 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() {
|
||||||
|
__out.push('<div class="zammad-chat-message zammad-chat-message--customer">\n <span class="zammad-chat-message-body">');
|
||||||
|
|
||||||
|
__out.push(message);
|
||||||
|
|
||||||
|
__out.push('</span>\n</div>');
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
|
}).call(__obj);
|
||||||
|
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||||
|
return __out.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!window.zammadChatTemplates) {
|
||||||
|
window.zammadChatTemplates = {};
|
||||||
|
}
|
||||||
|
window.zammadChatTemplates["status"] = 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() {
|
||||||
|
__out.push('<div class="zammad-chat-status"><strong>');
|
||||||
|
|
||||||
|
__out.push(__sanitize(label));
|
||||||
|
|
||||||
|
__out.push('</strong>');
|
||||||
|
|
||||||
|
__out.push(__sanitize(time));
|
||||||
|
|
||||||
|
__out.push('</div>');
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
|
}).call(__obj);
|
||||||
|
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||||
|
return __out.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!window.zammadChatTemplates) {
|
||||||
|
window.zammadChatTemplates = {};
|
||||||
|
}
|
||||||
|
window.zammadChatTemplates["typingIndicator"] = 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() {
|
||||||
|
__out.push('<div class="zammad-chat-message zammad-chat-message--typing zammad-chat-message--agent">\n <span class="zammad-chat-message-body">\n <span class="zammad-chat-loading-animation">\n <span class="zammad-chat-loading-circle"></span>\n <span class="zammad-chat-loading-circle"></span>\n <span class="zammad-chat-loading-circle"></span>\n </span>\n </span>\n</div>');
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
|
|
||||||
|
}).call(__obj);
|
||||||
|
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||||
|
return __out.join('');
|
||||||
|
};
|
1
public/assets/chat/chat.min.js
vendored
Normal file
1
public/assets/chat/chat.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
52
public/assets/chat/gulpfile.js
Normal file
52
public/assets/chat/gulpfile.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
var gulp = require('gulp');
|
||||||
|
var autoprefixer = require('gulp-autoprefixer');
|
||||||
|
var sass = require('gulp-sass');
|
||||||
|
var gutil = require('gulp-util');
|
||||||
|
var concat = require('gulp-concat');
|
||||||
|
var coffee = require('gulp-coffee');
|
||||||
|
var eco = require('gulp-eco');
|
||||||
|
var rename = require('gulp-rename');
|
||||||
|
var uglify = require('gulp-uglify');
|
||||||
|
var merge = require('merge-stream');
|
||||||
|
var plumber = require('gulp-plumber');
|
||||||
|
|
||||||
|
gulp.task('css', function(){
|
||||||
|
return gulp.src('style.scss')
|
||||||
|
.pipe(sass.sync().on('error', gutil.log))
|
||||||
|
.pipe(autoprefixer({
|
||||||
|
browsers: ['last 4 versions'],
|
||||||
|
cascade: false
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest('./'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('js', function(){
|
||||||
|
var templates = gulp.src('views/*.eco')
|
||||||
|
.pipe(eco({namespace: 'zammadChatTemplates'}));
|
||||||
|
|
||||||
|
var js = gulp.src('chat.coffee')
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(coffee({bare: true}).on('error', gutil.log));
|
||||||
|
|
||||||
|
var autoGrow = gulp.src('jquery.autoGrow.js');
|
||||||
|
|
||||||
|
return merge(templates, js, autoGrow)
|
||||||
|
.pipe(concat('chat.js'))
|
||||||
|
.pipe(gulp.dest('./'))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(rename({ extname: '.min.js' }))
|
||||||
|
.pipe(gulp.dest('./'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('default', function(){
|
||||||
|
var cssWatcher = gulp.watch('style.scss', ['css']);
|
||||||
|
cssWatcher.on('change', function(event) {
|
||||||
|
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
|
||||||
|
});
|
||||||
|
|
||||||
|
var jsWatcher = gulp.watch(['chat.coffee', 'views/*.eco'], ['js']);
|
||||||
|
jsWatcher.on('change', function(event) {
|
||||||
|
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
|
||||||
|
});
|
||||||
|
});
|
4
public/assets/chat/jquery-2.1.4.min.js
vendored
Normal file
4
public/assets/chat/jquery-2.1.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
77
public/assets/chat/jquery.autoGrow.js
Normal file
77
public/assets/chat/jquery.autoGrow.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*!
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||||
|
* <jevin9@gmail.com> wrote this file. As long as you retain this notice you
|
||||||
|
* can do whatever you want with this stuff. If we meet some day, and you think
|
||||||
|
* this stuff is worth it, you can buy me a beer in return. Jevin O. Sewaruth
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Autogrow Textarea Plugin Version v3.0
|
||||||
|
* http://www.technoreply.com/autogrow-textarea-plugin-3-0
|
||||||
|
*
|
||||||
|
* THIS PLUGIN IS DELIVERD ON A PAY WHAT YOU WHANT BASIS. IF THE PLUGIN WAS USEFUL TO YOU, PLEASE CONSIDER BUYING THE PLUGIN HERE :
|
||||||
|
* https://sites.fastspring.com/technoreply/instant/autogrowtextareaplugin
|
||||||
|
*
|
||||||
|
* Date: October 15, 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
jQuery.fn.autoGrow = function(options) {
|
||||||
|
return this.each(function() {
|
||||||
|
var settings = jQuery.extend({
|
||||||
|
extraLine: true,
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
var createMirror = function(textarea) {
|
||||||
|
jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
|
||||||
|
return jQuery(textarea).next('.autogrow-textarea-mirror')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendContentToMirror = function (textarea) {
|
||||||
|
mirror.innerHTML = String(textarea.value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
.replace(/\n/g, '<br />') +
|
||||||
|
(settings.extraLine? '.<br/>.' : '')
|
||||||
|
;
|
||||||
|
|
||||||
|
if (jQuery(textarea).height() != jQuery(mirror).height())
|
||||||
|
jQuery(textarea).height(jQuery(mirror).height());
|
||||||
|
}
|
||||||
|
|
||||||
|
var growTextarea = function () {
|
||||||
|
sendContentToMirror(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a mirror
|
||||||
|
var mirror = createMirror(this);
|
||||||
|
|
||||||
|
// Style the mirror
|
||||||
|
mirror.style.display = 'none';
|
||||||
|
mirror.style.wordWrap = 'break-word';
|
||||||
|
mirror.style.whiteSpace = 'normal';
|
||||||
|
mirror.style.padding = jQuery(this).css('paddingTop') + ' ' +
|
||||||
|
jQuery(this).css('paddingRight') + ' ' +
|
||||||
|
jQuery(this).css('paddingBottom') + ' ' +
|
||||||
|
jQuery(this).css('paddingLeft');
|
||||||
|
|
||||||
|
mirror.style.width = jQuery(this).css('width');
|
||||||
|
mirror.style.fontFamily = jQuery(this).css('font-family');
|
||||||
|
mirror.style.fontSize = jQuery(this).css('font-size');
|
||||||
|
mirror.style.lineHeight = jQuery(this).css('line-height');
|
||||||
|
|
||||||
|
// Style the textarea
|
||||||
|
this.style.overflow = "hidden";
|
||||||
|
this.style.minHeight = this.rows+"em";
|
||||||
|
|
||||||
|
// Bind the textarea's event
|
||||||
|
this.onkeyup = growTextarea;
|
||||||
|
|
||||||
|
// Fire the event for text already present
|
||||||
|
sendContentToMirror(this);
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
19
public/assets/chat/package.json
Normal file
19
public/assets/chat/package.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "zammad-chat",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Zammad Customer Chat Javascript Widget",
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp": "^3.9.0",
|
||||||
|
"gulp-autoprefixer": "^3.0.2",
|
||||||
|
"gulp-coffee": "^2.3.1",
|
||||||
|
"gulp-concat": "^2.6.0",
|
||||||
|
"gulp-eco": "0.0.2",
|
||||||
|
"gulp-plumber": "^1.0.1",
|
||||||
|
"gulp-rename": "^1.2.2",
|
||||||
|
"gulp-sass": "^2.0.4",
|
||||||
|
"gulp-uglify": "^1.4.2",
|
||||||
|
"gulp-util": "^3.0.6",
|
||||||
|
"merge-stream": "^1.0.0",
|
||||||
|
"sass.js": "^0.9.2"
|
||||||
|
}
|
||||||
|
}
|
329
public/assets/chat/style.css
Normal file
329
public/assets/chat/style.css
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
.zammad-chat {
|
||||||
|
color: black;
|
||||||
|
position: fixed;
|
||||||
|
right: 30px;
|
||||||
|
bottom: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 33em;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
will-change: bottom;
|
||||||
|
display: none;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column; }
|
||||||
|
|
||||||
|
.zammad-chat.zammad-chat-is-open {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex; }
|
||||||
|
|
||||||
|
.zammad-chat-icon {
|
||||||
|
height: 2em;
|
||||||
|
fill: currentColor;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: 4px; }
|
||||||
|
|
||||||
|
.zammad-chat-header {
|
||||||
|
padding: 0.5em 2.5em 0.5em 1em;
|
||||||
|
background: #379ad7;
|
||||||
|
color: white;
|
||||||
|
line-height: 2.5em;
|
||||||
|
box-shadow: 0 -1px #247fb7, 0 1px rgba(255, 255, 255, 0.3) inset, 0 -1px #247fb7 inset, 0 1px 1px rgba(0, 0, 0, 0.13);
|
||||||
|
position: relative;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer; }
|
||||||
|
|
||||||
|
.zammad-chat-welcome-text {
|
||||||
|
font-size: 1.2em; }
|
||||||
|
|
||||||
|
.zammad-chat-toggle {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 3.4em;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 3.5em; }
|
||||||
|
|
||||||
|
.zammad-chat-toggle-icon-open,
|
||||||
|
.zammad-chat-toggle-icon-close {
|
||||||
|
fill: currentColor;
|
||||||
|
height: 0.85em; }
|
||||||
|
|
||||||
|
.zammad-chat-toggle-icon-close,
|
||||||
|
.zammad-chat.zammad-chat-is-open .zammad-chat-toggle-icon-open {
|
||||||
|
display: none; }
|
||||||
|
|
||||||
|
.zammad-chat.zammad-chat-is-open .zammad-chat-toggle-icon-close {
|
||||||
|
display: inline; }
|
||||||
|
|
||||||
|
.zammad-chat-agent {
|
||||||
|
float: left; }
|
||||||
|
|
||||||
|
.zammad-chat-header-controls {
|
||||||
|
float: right; }
|
||||||
|
|
||||||
|
.zammad-chat-agent-avatar {
|
||||||
|
border-radius: 100%;
|
||||||
|
margin-right: 0.6em;
|
||||||
|
float: left;
|
||||||
|
width: 2.5em; }
|
||||||
|
|
||||||
|
.zammad-chat-agent-name {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
.zammad-chat-agent-status {
|
||||||
|
margin: 0 1em;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 2em;
|
||||||
|
padding: 0 0.7em;
|
||||||
|
border-radius: 1em;
|
||||||
|
background: #288ecc;
|
||||||
|
box-shadow: 0 0 0 1px #2582bb; }
|
||||||
|
|
||||||
|
.zammad-chat-agent-status:before {
|
||||||
|
content: "";
|
||||||
|
background: #f35912;
|
||||||
|
display: inline-block;
|
||||||
|
height: 0.9em;
|
||||||
|
width: 0.9em;
|
||||||
|
border-radius: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
box-shadow: 0 0 0 1px #2582bb; }
|
||||||
|
|
||||||
|
.zammad-chat-agent-status[data-status="online"]:before {
|
||||||
|
background: #52c782; }
|
||||||
|
|
||||||
|
.zammad-chat-agent-status[data-status="connecting"]:before {
|
||||||
|
-webkit-animation: linear connect-fade 600ms infinite alternate;
|
||||||
|
animation: linear connect-fade 600ms infinite alternate;
|
||||||
|
background: #faab00; }
|
||||||
|
|
||||||
|
@-webkit-keyframes connect-fade {
|
||||||
|
from {
|
||||||
|
opacity: .5;
|
||||||
|
-webkit-transform: scale(0.6);
|
||||||
|
transform: scale(0.6); }
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1); } }
|
||||||
|
|
||||||
|
@keyframes connect-fade {
|
||||||
|
from {
|
||||||
|
opacity: .5;
|
||||||
|
-webkit-transform: scale(0.6);
|
||||||
|
transform: scale(0.6); }
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1); } }
|
||||||
|
|
||||||
|
.zammad-chat-loader {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: 3.5em;
|
||||||
|
margin-top: 1px;
|
||||||
|
text-align: center;
|
||||||
|
background: radial-gradient(rgba(255, 255, 255, 0.75), white);
|
||||||
|
z-index: 1;
|
||||||
|
padding: 10px;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
-ms-flex-pack: center;
|
||||||
|
justify-content: center; }
|
||||||
|
|
||||||
|
.zammad-chat-loader-text {
|
||||||
|
font-size: 1.3em; }
|
||||||
|
|
||||||
|
.zammad-chat-loader .zammad-chat-loading-animation {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: -19px; }
|
||||||
|
|
||||||
|
.zammad-chat-body {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
background: white; }
|
||||||
|
|
||||||
|
.zammad-chat-status {
|
||||||
|
text-align: center;
|
||||||
|
color: #999999;
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin: 1em 0; }
|
||||||
|
|
||||||
|
.zammad-chat-message {
|
||||||
|
margin: 0.5em 0; }
|
||||||
|
|
||||||
|
.zammad-chat-message-body {
|
||||||
|
padding: 0.6em 1em;
|
||||||
|
border-radius: 1em;
|
||||||
|
background: #f6f8f9;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 70%;
|
||||||
|
white-space: pre;
|
||||||
|
box-shadow: 0 2px rgba(255, 255, 255, 0.15) inset, 0 0 0 1px rgba(0, 0, 0, 0.08) inset, 0 1px rgba(0, 0, 0, 0.02); }
|
||||||
|
|
||||||
|
.zammad-chat-message--customer {
|
||||||
|
text-align: right; }
|
||||||
|
|
||||||
|
.zammad-chat-message--customer + .zammad-chat-message--agent,
|
||||||
|
.zammad-chat-message--agent + .zammad-chat-message--customer {
|
||||||
|
margin-top: 1em; }
|
||||||
|
|
||||||
|
.zammad-chat-message--customer .zammad-chat-message-body {
|
||||||
|
background: #379ad7;
|
||||||
|
color: white; }
|
||||||
|
|
||||||
|
.zammad-chat-message--unread {
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
|
.zammad-chat-message--typing .zammad-chat-message-body {
|
||||||
|
white-space: normal; }
|
||||||
|
|
||||||
|
.zammad-chat-loading-circle {
|
||||||
|
background: #c5dded;
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 0.72em;
|
||||||
|
width: 0.72em;
|
||||||
|
display: inline-block;
|
||||||
|
-webkit-animation: ease-in-out load-fade 600ms infinite alternate;
|
||||||
|
animation: ease-in-out load-fade 600ms infinite alternate; }
|
||||||
|
|
||||||
|
.zammad-chat-loading-circle + .zammad-chat-loading-circle {
|
||||||
|
-webkit-animation-delay: .13s;
|
||||||
|
animation-delay: .13s; }
|
||||||
|
|
||||||
|
.zammad-chat-loading-circle + .zammad-chat-loading-circle + .zammad-chat-loading-circle {
|
||||||
|
-webkit-animation-delay: .26s;
|
||||||
|
animation-delay: .26s; }
|
||||||
|
|
||||||
|
@-webkit-keyframes load-fade {
|
||||||
|
from {
|
||||||
|
opacity: .5;
|
||||||
|
-webkit-transform: scale(0.6);
|
||||||
|
transform: scale(0.6); }
|
||||||
|
67% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1); } }
|
||||||
|
|
||||||
|
@keyframes load-fade {
|
||||||
|
from {
|
||||||
|
opacity: .5;
|
||||||
|
-webkit-transform: scale(0.6);
|
||||||
|
transform: scale(0.6); }
|
||||||
|
67% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1); } }
|
||||||
|
|
||||||
|
.zammad-chat-controls {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-align-items: flex-start;
|
||||||
|
-ms-flex-align: start;
|
||||||
|
align-items: flex-start;
|
||||||
|
border-top: 1px solid #e3f0f7;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.4em;
|
||||||
|
box-shadow: 0 1px rgba(0, 0, 0, 0.01), 0 -1px rgba(0, 0, 0, 0.02);
|
||||||
|
position: relative;
|
||||||
|
background: white; }
|
||||||
|
|
||||||
|
.zammad-chat-input {
|
||||||
|
float: left;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 1em 2em;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
-webkit-flex: 1;
|
||||||
|
-ms-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
max-height: 6em; }
|
||||||
|
|
||||||
|
.zammad-chat-input::-webkit-input-placeholder {
|
||||||
|
color: #bdc9d0; }
|
||||||
|
|
||||||
|
.zammad-chat-send {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
float: right;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
background: #379ad7;
|
||||||
|
color: white;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
margin: 0.5em 1em 0.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
box-shadow: 0 2px rgba(255, 255, 255, 0.25) inset, 0 0 0 1px #247fb7 inset, 0 1px rgba(0, 0, 0, 0.1);
|
||||||
|
outline: none; }
|
||||||
|
|
||||||
|
.zammad-chat-is-hidden {
|
||||||
|
display: none; }
|
||||||
|
|
||||||
|
.zammad-chat-is-visible {
|
||||||
|
display: block; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
# Flat Design
|
||||||
|
|
||||||
|
*/
|
||||||
|
.zammad-chat--flat .zammad-chat-header,
|
||||||
|
.zammad-chat--flat .zammad-chat-body {
|
||||||
|
border: none; }
|
||||||
|
|
||||||
|
.zammad-chat--flat .zammad-chat-header {
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.13); }
|
||||||
|
|
||||||
|
.zammad-chat--flat .zammad-chat-message-body {
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08) inset; }
|
||||||
|
|
||||||
|
.zammad-chat--flat .zammad-chat-agent-status,
|
||||||
|
.zammad-chat--flat .zammad-chat-send {
|
||||||
|
box-shadow: none; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Mobile Design
|
||||||
|
|
||||||
|
*/
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.zammad-chat {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: auto;
|
||||||
|
height: 100vh; }
|
||||||
|
.zammad-chat-body {
|
||||||
|
height: auto;
|
||||||
|
-webkit-flex: 1;
|
||||||
|
-ms-flex: 1;
|
||||||
|
flex: 1; }
|
||||||
|
.zammad-chat,
|
||||||
|
.zammad-chat-header {
|
||||||
|
border-radius: 0 !important; } }
|
360
public/assets/chat/style.scss
Normal file
360
public/assets/chat/style.scss
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
/// Returns the luminance of `$color` as a float (between 0 and 1)
|
||||||
|
/// 1 is pure white, 0 is pure black
|
||||||
|
/// @param {Color} $color - Color
|
||||||
|
/// @return {Number}
|
||||||
|
/// @link http://stackoverflow.com/a/596241/731172
|
||||||
|
@function luminance($color) {
|
||||||
|
@return (0.375 * red($color) + 0.5 * green($color) + 0.125 * blue($color)) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limits the lighten function to a maximum value
|
||||||
|
/// @param {Color} $color - Color
|
||||||
|
/// @param {Number} $amount - The amount to increase the lightness by, between 0% and 100%
|
||||||
|
/// @param {Number} $max - The maximal lightness amount
|
||||||
|
/// @return {Color}
|
||||||
|
@function lightenMax($color, $amount, $max) {
|
||||||
|
$enlightedColor: lighten($color, $amount);
|
||||||
|
@if lightness($enlightedColor) > $max {
|
||||||
|
@return change-color($color, $lightness: $max);
|
||||||
|
} @else {
|
||||||
|
@return $enlightedColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$themeColor: hsl(203,67,53);
|
||||||
|
$fontSize: 12px;
|
||||||
|
$borderRadius: 5px;
|
||||||
|
|
||||||
|
$luminance: luminance($themeColor);
|
||||||
|
$contrastTextColor: if($luminance > 0.5, inherit, white);
|
||||||
|
$baseTextColor: if($luminance < 0.2, white, black);
|
||||||
|
|
||||||
|
.zammad-chat {
|
||||||
|
color: $baseTextColor;
|
||||||
|
position: fixed;
|
||||||
|
right: 30px;
|
||||||
|
bottom: 0;
|
||||||
|
font-size: $fontSize;
|
||||||
|
width: 33em;
|
||||||
|
box-shadow: 0 3px 10px rgba(0,0,0,.3);
|
||||||
|
border-radius: $borderRadius $borderRadius 0 0;
|
||||||
|
will-change: bottom;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.zammad-chat.zammad-chat-is-open {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-icon {
|
||||||
|
height: 2em;
|
||||||
|
fill: currentColor;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-header {
|
||||||
|
padding: 0.5em 2.5em 0.5em 1em;
|
||||||
|
background: $themeColor;
|
||||||
|
color: $contrastTextColor;
|
||||||
|
line-height: 2.5em;
|
||||||
|
box-shadow:
|
||||||
|
0 -1px darken($themeColor, 10%),
|
||||||
|
0 1px rgba(255,255,255,.3) inset,
|
||||||
|
0 -1px darken($themeColor, 10%) inset,
|
||||||
|
0 1px 1px rgba(0,0,0,.13);
|
||||||
|
position: relative;
|
||||||
|
border-radius: $borderRadius $borderRadius 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-welcome-text {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-toggle {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 3.4em;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 3.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-toggle-icon-open,
|
||||||
|
.zammad-chat-toggle-icon-close {
|
||||||
|
fill: currentColor;
|
||||||
|
height: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-toggle-icon-close,
|
||||||
|
.zammad-chat.zammad-chat-is-open .zammad-chat-toggle-icon-open {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat.zammad-chat-is-open .zammad-chat-toggle-icon-close {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-agent {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-header-controls {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-agent-avatar {
|
||||||
|
border-radius: 100%;
|
||||||
|
margin-right: 0.6em;
|
||||||
|
float: left;
|
||||||
|
width: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-agent-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-agent-status {
|
||||||
|
margin: 0 1em;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 2em;
|
||||||
|
padding: 0 .7em;
|
||||||
|
border-radius: 1em;
|
||||||
|
background: darken($themeColor, 5%);
|
||||||
|
box-shadow: 0 0 0 1px darken($themeColor, 9%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-agent-status:before {
|
||||||
|
content: "";
|
||||||
|
background: hsl(19,90%,51%);
|
||||||
|
display: inline-block;
|
||||||
|
height: 0.9em;
|
||||||
|
width: 0.9em;
|
||||||
|
border-radius: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
box-shadow: 0 0 0 1px darken($themeColor, 9%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-agent-status[data-status="online"]:before {
|
||||||
|
background: hsl(145,51%,55%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-agent-status[data-status="connecting"]:before {
|
||||||
|
animation: linear connect-fade 600ms infinite alternate;
|
||||||
|
background: hsl(41,100%,49%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes connect-fade {
|
||||||
|
from { opacity: .5; transform: scale(0.6); }
|
||||||
|
to { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-loader {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: 3.5em;
|
||||||
|
margin-top: 1px;
|
||||||
|
text-align: center;
|
||||||
|
background: radial-gradient(rgba(255,255,255,.75), rgba(255,255,255,1));
|
||||||
|
z-index: 1;
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-loader-text {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-loader .zammad-chat-loading-animation {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: -19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-body {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-status {
|
||||||
|
text-align: center;
|
||||||
|
color: hsl(0,0%,60%);
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-message {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-message-body {
|
||||||
|
padding: 0.6em 1em;
|
||||||
|
border-radius: 1em;
|
||||||
|
background: desaturate(lightenMax($themeColor, 50%, 97%), 50%);
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 70%;
|
||||||
|
white-space: pre;
|
||||||
|
box-shadow:
|
||||||
|
0 2px rgba(255,255,255,.15) inset,
|
||||||
|
0 0 0 1px rgba(0,0,0,.08) inset,
|
||||||
|
0 1px rgba(0,0,0,.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-message--customer {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-message--customer + .zammad-chat-message--agent,
|
||||||
|
.zammad-chat-message--agent + .zammad-chat-message--customer {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-message--customer .zammad-chat-message-body {
|
||||||
|
background: $themeColor;
|
||||||
|
color: $contrastTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-message--unread {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-message--typing .zammad-chat-message-body {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-loading-circle {
|
||||||
|
background: desaturate(lightenMax($themeColor, 50%, 85%), 15%);
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 0.72em;
|
||||||
|
width: 0.72em;
|
||||||
|
display: inline-block;
|
||||||
|
animation: ease-in-out load-fade 600ms infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-loading-circle + .zammad-chat-loading-circle {
|
||||||
|
animation-delay: .13s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-loading-circle + .zammad-chat-loading-circle + .zammad-chat-loading-circle {
|
||||||
|
animation-delay: .26s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes load-fade {
|
||||||
|
from { opacity: .5; transform: scale(0.6); }
|
||||||
|
67% { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-controls {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
border-top: 1px solid desaturate(lightenMax($themeColor, 80%, 93%), 10%);
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.4em;
|
||||||
|
box-shadow:
|
||||||
|
0 1px rgba(0,0,0,.01),
|
||||||
|
0 -1px rgba(0,0,0,.02);
|
||||||
|
position: relative;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-input {
|
||||||
|
float: left;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 1em 2em;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
flex: 1;
|
||||||
|
max-height: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-input::-webkit-input-placeholder {
|
||||||
|
color: desaturate(lightenMax($themeColor, 25%, 85%), 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-send {
|
||||||
|
appearance: none;
|
||||||
|
float: right;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
background: $themeColor;
|
||||||
|
color: $contrastTextColor;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
margin: 0.5em 1em 0.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
box-shadow:
|
||||||
|
0 2px rgba(255,255,255,.25) inset,
|
||||||
|
0 0 0 1px darken($themeColor, 10%) inset,
|
||||||
|
0 1px rgba(0,0,0,.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zammad-chat-is-visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
# Flat Design
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
.zammad-chat--flat .zammad-chat-header,
|
||||||
|
.zammad-chat--flat .zammad-chat-body {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.zammad-chat--flat .zammad-chat-header {
|
||||||
|
box-shadow: 0 1px 1px rgba(0,0,0,.13);
|
||||||
|
}
|
||||||
|
.zammad-chat--flat .zammad-chat-message-body {
|
||||||
|
box-shadow: 0 0 0 1px rgba(0,0,0,.08) inset;
|
||||||
|
}
|
||||||
|
.zammad-chat--flat .zammad-chat-agent-status,
|
||||||
|
.zammad-chat--flat .zammad-chat-send {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Mobile Design
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.zammad-chat {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: auto;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.zammad-chat-body {
|
||||||
|
height: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.zammad-chat,
|
||||||
|
.zammad-chat-header {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
34
public/assets/chat/views/chat.eco
Normal file
34
public/assets/chat/views/chat.eco
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<div class="zammad-chat">
|
||||||
|
<div class="zammad-chat-header">
|
||||||
|
<div class="zammad-chat-header-controls">
|
||||||
|
<span class="zammad-chat-agent-status zammad-chat-is-hidden" data-status="online">Online</span>
|
||||||
|
<span class="zammad-chat-toggle">
|
||||||
|
<svg class="zammad-chat-toggle-icon-open" viewBox="0 0 13 7"><path d="M10.807 7l1.4-1.428-5-4.9L6.5-.02l-.7.7-4.9 4.9 1.414 1.413L6.5 2.886 10.807 7z" fill-rule="evenodd"/></svg>
|
||||||
|
<svg class="zammad-chat-toggle-icon-close" viewBox="0 0 13 7"><path d="M6.554 4.214L2.246 0l-1.4 1.428 5 4.9.708.693.7-.7 4.9-4.9L10.74.008 6.553 4.214z" fill-rule="evenodd"/></svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="zammad-chat-agent zammad-chat-is-hidden">
|
||||||
|
<img class="zammad-chat-agent-avatar" src="https://s3.amazonaws.com/uifaces/faces/twitter/joshaustin/128.jpg">
|
||||||
|
<span class="zammad-chat-agent-sentence">
|
||||||
|
<span class="zammad-chat-agent-name">Adam</span> is helping you.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="zammad-chat-welcome">
|
||||||
|
<svg class="zammad-chat-icon" viewBox="0 0 24 24"><path d="M2 5C2 4 3 3 4 3h16c1 0 2 1 2 2v10C22 16 21 17 20 17H4C3 17 2 16 2 15V5zM12 17l6 4v-4h-6z" fill-rule="evenodd"/></svg>
|
||||||
|
<span class="zammad-chat-welcome-text"><strong>Chat</strong> with us!</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="zammad-chat-loader">
|
||||||
|
<span class="zammad-chat-loading-animation">
|
||||||
|
<span class="zammad-chat-loading-circle"></span>
|
||||||
|
<span class="zammad-chat-loading-circle"></span>
|
||||||
|
<span class="zammad-chat-loading-circle"></span>
|
||||||
|
</span>
|
||||||
|
<span class="zammad-chat-loader-text">Connecting</span>
|
||||||
|
</div>
|
||||||
|
<div class="zammad-chat-body"></div>
|
||||||
|
<form class="zammad-chat-controls">
|
||||||
|
<textarea class="zammad-chat-input" rows="1" placeholder="Compose your message..."></textarea>
|
||||||
|
<button type="submit" class="zammad-chat-send">Send</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
3
public/assets/chat/views/message.eco
Normal file
3
public/assets/chat/views/message.eco
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="zammad-chat-message zammad-chat-message--customer">
|
||||||
|
<span class="zammad-chat-message-body"><%- message %></span>
|
||||||
|
</div>
|
1
public/assets/chat/views/status.eco
Normal file
1
public/assets/chat/views/status.eco
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<div class="zammad-chat-status"><strong><%= label %></strong><%= time %></div>
|
9
public/assets/chat/views/typingIndicator.eco
Normal file
9
public/assets/chat/views/typingIndicator.eco
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="zammad-chat-message zammad-chat-message--typing zammad-chat-message--agent">
|
||||||
|
<span class="zammad-chat-message-body">
|
||||||
|
<span class="zammad-chat-loading-animation">
|
||||||
|
<span class="zammad-chat-loading-circle"></span>
|
||||||
|
<span class="zammad-chat-loading-circle"></span>
|
||||||
|
<span class="zammad-chat-loading-circle"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
Loading…
Reference in a new issue