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