2015-10-15 09:14:19 +00:00
do ($ = window.jQuery, window) ->
# Define the plugin class
class ZammadChat
defaults:
invitationPhrase: ' <strong>Chat</strong> with us! '
2015-11-02 15:48:16 +00:00
agentPhrase: ' is helping you '
2015-10-15 09:14:19 +00:00
show: true
target: $ ( ' body ' )
2015-11-02 15:48:16 +00:00
_messageCount: 0
2015-10-15 09:14:19 +00:00
isOpen: false
blinkOnlineInterval: null
stopBlinOnlineStateTimeout: null
showTimeEveryXMinutes: 1
lastTimestamp: null
lastAddedType: null
inputTimeout: null
isTyping: false
isOnline: true
2015-11-12 13:19:41 +00:00
initialQueueDelay: 10000 ,
2015-11-11 20:44:54 +00:00
debug: true
host: ' ws://localhost:6042 '
2015-11-12 23:00:13 +00:00
wsReconnectEnable: true
2015-10-15 09:14:19 +00:00
strings:
' Online ' : ' Online '
' Offline ' : ' Offline '
2015-11-11 20:44:54 +00:00
' Connecting ' : ' Verbinden '
2015-10-15 09:14:19 +00:00
' Connection re-established ' : ' Connection re-established '
2015-11-11 20:44:54 +00:00
' Today ' : ' Heute '
' Send ' : ' Senden '
' Compose your message... ' : ' Ihre Nachricht... '
' All colleges are busy. ' : ' Alle Kollegen sind belegt. '
' You are on waiting list position <strong>%s</strong>. ' : ' Sie sind in der Warteliste an der Position <strong>%s</strong>. '
2015-11-12 16:31:20 +00:00
' Start new conversation ' : ' Neue Konversation starten '
' Since you didn \' t respond in the last %s your conversation with <strong>%s</strong> got closed. ' : ' Da sie in den letzten %s nichts geschrieben haben wurde ihre Konversation mit <strong>%s</strong> geschlossen. '
' minutes ' : ' Minuten '
2015-11-12 10:44:37 +00:00
sessionId: undefined
2015-11-11 20:44:54 +00:00
T: (string, items...) =>
if ! @ strings [ string ]
@ log ' notice ' , " Translation needed for ' #{ string } ' "
translation = @ strings [ string ] || string
if items
for item in items
translation = translation . replace ( /%s/ , item )
translation
log: (level, string...) =>
return if ! @ debug && level is ' debug '
console . log level , string
2015-10-15 09:14:19 +00:00
2015-11-02 15:48:16 +00:00
view: (name) =>
return (options) =>
if ! options
options = { }
options.T = @ T
return window . zammadChatTemplates [ name ] ( options )
2015-10-15 09:14:19 +00:00
constructor: (el, options) ->
@options = $ . extend { } , @ defaults , options
2015-11-02 15:48:16 +00:00
@el = $ ( @ view ( ' chat ' ) ( @ options ) )
2015-10-15 09:14:19 +00:00
@ options . target . append @ el
2015-11-12 13:56:47 +00:00
@input = @ el . find ( ' .zammad-chat-input ' )
2015-11-12 14:24:19 +00:00
@ el . find ( ' .js-chat-open ' ) . click => @ open ( )
2015-11-11 10:44:10 +00:00
@ el . find ( ' .js-chat-close ' ) . click @ close
2015-10-15 09:14:19 +00:00
@ el . find ( ' .zammad-chat-controls ' ) . on ' submit ' , @ onSubmit
2015-11-12 13:56:47 +00:00
@ input . on
2015-10-15 09:14:19 +00:00
keydown: @ checkForEnter
input: @ onInput
2015-11-12 13:56:47 +00:00
if ! window . WebSocket or ! sessionStorage
2015-11-11 20:44:54 +00:00
@ log ' notice ' , ' Chat: Browser not supported! '
2015-10-15 15:07:18 +00:00
return
2015-11-12 23:00:13 +00:00
@ wsConnect ( )
2015-10-15 15:07:18 +00:00
2015-11-12 13:56:47 +00:00
#@onReady()
2015-11-11 10:44:10 +00:00
2015-10-15 09:14:19 +00:00
checkForEnter: (event) =>
if not event . shiftKey and event . keyCode is 13
event . preventDefault ( )
@ sendMessage ( )
2015-11-10 14:01:04 +00:00
send: (event, data) =>
2015-11-11 20:44:54 +00:00
@ log ' debug ' , ' ws:send ' , event , data
2015-11-02 15:48:16 +00:00
pipe = JSON . stringify
2015-11-10 14:01:04 +00:00
event: event
2015-11-02 15:48:16 +00:00
data: data
@ ws . send pipe
onWebSocketMessage: (e) =>
2015-11-10 14:01:04 +00:00
pipes = JSON . parse ( e . data )
for pipe in pipes
2015-11-12 13:19:41 +00:00
@ log ' debug ' , ' ws:onmessage ' , pipe
2015-11-10 14:01:04 +00:00
switch pipe . event
when ' chat_session_message '
return if pipe . data . self_written
@ receiveMessage pipe . data
when ' chat_session_typing '
return if pipe . data . self_written
@ onAgentTypingStart ( )
when ' chat_session_start '
2015-11-12 13:56:47 +00:00
@ onConnectionEstablished pipe . data
2015-11-12 10:44:37 +00:00
when ' chat_session_queue '
2015-11-12 15:16:01 +00:00
@ onQueueScreen pipe . data
2015-11-11 20:44:54 +00:00
when ' chat_session_closed '
@ onSessionClosed pipe . data
when ' chat_session_left '
@ onSessionClosed pipe . data
2015-11-11 10:24:19 +00:00
when ' chat_status_customer '
2015-11-10 14:01:04 +00:00
switch pipe . data . state
when ' online '
2015-11-12 23:00:13 +00:00
@sessionId = undefined
2015-11-10 14:01:04 +00:00
@ onReady ( )
2015-11-11 20:44:54 +00:00
@ log ' debug ' , ' Zammad Chat: ready '
2015-11-10 14:01:04 +00:00
when ' offline '
2015-11-11 20:44:54 +00:00
@ log ' debug ' , ' Zammad Chat: No agent online '
2015-11-12 23:00:13 +00:00
@ wsClose ( )
2015-11-10 14:01:04 +00:00
when ' chat_disabled '
2015-11-11 20:44:54 +00:00
@ log ' debug ' , ' Zammad Chat: Chat is disabled '
2015-11-12 23:00:13 +00:00
@ wsClose ( )
2015-11-10 14:01:04 +00:00
when ' no_seats_available '
2015-11-11 20:44:54 +00:00
@ log ' debug ' , ' Zammad Chat: Too many clients in queue. Clients in queue: ' , pipe . data . queue
2015-11-12 23:00:13 +00:00
@ wsClose ( )
2015-11-12 09:39:14 +00:00
when ' reconnect '
@ log ' debug ' , ' old messages ' , pipe . data . session
2015-11-12 15:16:01 +00:00
@ reopenSession pipe . data
2015-11-02 15:48:16 +00:00
onReady: =>
2015-11-11 09:48:54 +00:00
if @ options . show
@ show ( )
2015-11-12 13:56:47 +00:00
2015-11-12 15:16:01 +00:00
reopenSession: (data) =>
2015-11-12 14:05:43 +00:00
unfinishedMessage = sessionStorage . getItem ' unfinished_message '
2015-11-12 15:16:01 +00:00
@ onConnectionEstablished ( data )
for message in data . session
2015-11-12 13:56:47 +00:00
@ renderMessage
message: message . content
id: message . id
from: if message . created_by_id then ' agent ' else ' customer '
2015-11-12 14:05:43 +00:00
if unfinishedMessage
@ input . val unfinishedMessage
2015-11-12 13:56:47 +00:00
@ show ( )
2015-11-12 14:30:44 +00:00
@ open ( )
2015-11-12 16:15:58 +00:00
@ scrollToBottom ( )
2015-11-02 15:48:16 +00:00
2015-11-12 14:05:43 +00:00
if unfinishedMessage
@ input . focus ( )
2015-10-15 09:14:19 +00:00
onInput: =>
# remove unread-state from messages
@ el . find ( ' .zammad-chat-message--unread ' )
. removeClass ' zammad-chat-message--unread '
2015-11-12 13:56:47 +00:00
sessionStorage . setItem ' unfinished_message ' , @ input . val ( )
2015-11-10 14:01:04 +00:00
@ onTypingStart ( )
2015-10-15 09:14:19 +00:00
2015-11-10 14:01:04 +00:00
onTypingStart: ->
2015-10-15 09:14:19 +00:00
2015-11-10 14:01:04 +00:00
clearTimeout ( @ isTypingTimeout ) if @ isTypingTimeout
# fire typingEnd after 5 seconds
@isTypingTimeout = setTimeout @ onTypingEnd , 1500
2015-10-15 09:14:19 +00:00
# send typing start event
2015-11-10 14:01:04 +00:00
if ! @ isTyping
@isTyping = true
2015-11-12 09:39:14 +00:00
@ send ' chat_session_typing ' ,
2015-11-12 10:44:37 +00:00
session_id: @ sessionId
2015-10-15 09:14:19 +00:00
onTypingEnd: =>
@isTyping = false
onSubmit: (event) =>
event . preventDefault ( )
@ sendMessage ( )
sendMessage: ->
2015-11-12 13:56:47 +00:00
message = @ input . val ( )
2015-10-15 09:14:19 +00:00
2015-11-11 20:44:54 +00:00
return if ! message
2015-10-15 09:14:19 +00:00
2015-11-12 13:56:47 +00:00
sessionStorage . removeItem ' unfinished_message '
2015-11-02 15:48:16 +00:00
messageElement = @ view ( ' message ' )
message: message
from: ' customer '
id: @ _messageCount ++
2015-10-15 09:14:19 +00:00
2015-11-02 15:48:16 +00:00
@ maybeAddTimestamp ( )
2015-10-15 09:14:19 +00:00
2015-11-02 15:48:16 +00:00
# add message before message typing loader
if @ el . find ( ' .zammad-chat-message--typing ' ) . size ( )
@lastAddedType = ' typing-placeholder '
2015-11-11 10:24:19 +00:00
@ el . find ( ' .zammad-chat-message--typing ' ) . before messageElement
2015-11-02 15:48:16 +00:00
else
@lastAddedType = ' message--customer '
@ el . find ( ' .zammad-chat-body ' ) . append messageElement
2015-10-15 09:14:19 +00:00
2015-11-12 14:05:43 +00:00
@ input . val ( ' ' )
2015-11-02 15:48:16 +00:00
@ scrollToBottom ( )
2015-10-15 09:14:19 +00:00
2015-11-02 15:48:16 +00:00
@isTyping = false
# send message event
2015-11-10 14:01:04 +00:00
@ send ' chat_session_message ' ,
content: message
2015-11-02 15:48:16 +00:00
id: @ _messageCount
2015-11-12 10:44:37 +00:00
session_id: @ sessionId
2015-11-02 15:48:16 +00:00
receiveMessage: (data) =>
2015-10-15 09:14:19 +00:00
# hide writing indicator
@ onAgentTypingEnd ( )
@ maybeAddTimestamp ( )
2015-11-12 13:56:47 +00:00
@ renderMessage
2015-11-10 14:01:04 +00:00
message: data . message . content
2015-11-02 15:48:16 +00:00
id: data . id
2015-10-15 09:26:56 +00:00
from: ' agent '
2015-11-12 13:56:47 +00:00
renderMessage: (data) =>
@lastAddedType = " message-- #{ data . from } "
unread = document . hidden ? " zammad-chat-message--unread " : " "
@ el . find ( ' .zammad-chat-body ' ) . append @ view ( ' message ' ) ( data )
2015-10-15 09:14:19 +00:00
@ scrollToBottom ( )
2015-11-12 14:30:44 +00:00
open: =>
2015-11-12 16:31:20 +00:00
if @ isOpen
@ show ( )
2015-10-15 09:14:19 +00:00
2015-11-12 14:30:44 +00:00
if ! @ sessionId
2015-11-12 13:56:47 +00:00
@ showLoader ( )
2015-11-02 15:48:16 +00:00
2015-10-15 09:14:19 +00:00
@ el
. addClass ( ' zammad-chat-is-open ' )
2015-11-12 13:56:47 +00:00
2015-11-12 14:30:44 +00:00
if ! @ sessionId
2015-11-12 13:56:47 +00:00
@ el . animate { bottom: 0 } , 500 , @ onOpenAnimationEnd
else
@ el . css ' bottom ' , 0
@ onOpenAnimationEnd ( )
2015-10-15 09:14:19 +00:00
@isOpen = true
2015-11-11 10:44:10 +00:00
2015-11-12 14:30:44 +00:00
if ! @ sessionId
@ session_init ( )
2015-10-15 09:14:19 +00:00
2015-11-12 23:00:13 +00:00
onOpenAnimationEnd: =>
#@showTimeout()
2015-11-11 10:44:10 +00:00
close: (event) =>
event . stopPropagation ( ) if event
2015-11-12 13:56:47 +00:00
2015-11-12 23:00:13 +00:00
# only close if session_id exists
return if ! @ sessionId
# stop delay of initial queue position
if @ onInitialQueueDelayId
clearTimeout ( @ onInitialQueueDelayId )
2015-11-12 16:15:58 +00:00
#@ws.close()
2015-11-12 13:56:47 +00:00
sessionStorage . removeItem ' sessionId '
sessionStorage . removeItem ' unfinished_message '
@ closeWindow ( )
closeWindow: =>
2015-10-15 09:14:19 +00:00
remainerHeight = @ el . height ( ) - @ el . find ( ' .zammad-chat-header ' ) . outerHeight ( )
2015-10-15 13:28:30 +00:00
@ el . animate { bottom: - remainerHeight } , 500 , @ onCloseAnimationEnd
2015-10-15 09:14:19 +00:00
onCloseAnimationEnd: =>
@ el . removeClass ( ' zammad-chat-is-open ' )
@ disconnect ( )
@isOpen = false
2015-11-12 09:39:14 +00:00
@ send ' chat_session_close ' ,
2015-11-12 10:44:37 +00:00
session_id: @ sessionId
2015-11-11 20:44:54 +00:00
2015-11-12 16:15:58 +00:00
@ setSessionId undefined
2015-10-15 09:14:19 +00:00
hide: ->
@ el . removeClass ( ' zammad-chat-is-visible ' )
show: ->
@ el . addClass ( ' zammad-chat-is-visible ' )
2015-10-15 13:28:30 +00:00
remainerHeight = @ el . outerHeight ( ) - @ el . find ( ' .zammad-chat-header ' ) . outerHeight ( )
2015-10-15 09:14:19 +00:00
@ el . css ' bottom ' , - remainerHeight
2015-11-12 14:05:43 +00:00
@ input . autoGrow
2015-11-12 13:56:47 +00:00
extraLine: false
2015-11-11 20:44:54 +00:00
disableInput: ->
2015-11-12 14:05:43 +00:00
@ input . prop ( ' disabled ' , true )
2015-11-11 20:44:54 +00:00
@ el . find ( ' .zammad-chat-send ' ) . prop ( ' disabled ' , true )
enableInput: ->
2015-11-12 14:05:43 +00:00
@ input . prop ( ' disabled ' , false )
2015-11-11 20:44:54 +00:00
@ el . find ( ' .zammad-chat-send ' ) . prop ( ' disabled ' , false )
2015-11-12 13:19:41 +00:00
onQueueScreen: (data) =>
2015-11-12 23:00:13 +00:00
@ setSessionId data . session_id
2015-11-12 13:19:41 +00:00
# delay initial queue position, show connecting first
show = =>
2015-11-12 14:21:04 +00:00
@ onQueue data
2015-11-12 13:19:41 +00:00
if @ initialQueueDelay && ! @ onInitialQueueDelayId
@onInitialQueueDelayId = setTimeout ( show , @ initialQueueDelay )
return
# stop delay of initial queue position
if @ onInitialQueueDelayId
clearTimeout ( @ onInitialQueueDelayId )
# show queue position
show ( )
2015-11-12 14:20:07 +00:00
onQueue: (data) =>
@ log ' notice ' , ' onQueue ' , data . position
2015-11-02 15:48:16 +00:00
@inQueue = true
@ el . find ( ' .zammad-chat-body ' ) . html @ view ( ' waiting ' )
2015-11-12 13:56:47 +00:00
position: data . position
2015-11-02 15:48:16 +00:00
2015-10-15 09:14:19 +00:00
onAgentTypingStart: =>
2015-11-11 10:24:19 +00:00
if @ stopTypingId
clearTimeout ( @ stopTypingId )
@stopTypingId = setTimeout ( @ onAgentTypingEnd , 3000 )
2015-11-02 15:48:16 +00:00
# never display two typing indicators
2015-10-15 09:14:19 +00:00
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 ( )
2015-10-15 09:26:56 +00:00
if ! @ lastTimestamp or ( timestamp - @ lastTimestamp ) > @ showTimeEveryXMinutes * 60000
2015-10-15 09:14:19 +00:00
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
2015-11-12 15:19:47 +00:00
@ el . find ( ' .zammad-chat-body ' ) . append @ view ( ' timestamp ' )
label: label
time: time
2015-10-15 09:14:19 +00:00
@lastTimestamp = timestamp
@lastAddedType = ' timestamp '
2015-11-12 15:19:47 +00:00
@ scrollToBottom ( )
2015-10-15 09:14:19 +00:00
updateLastTimestamp: (label, time) ->
@ el . find ( ' .zammad-chat-body ' )
2015-11-12 15:19:47 +00:00
. find ( ' .zammad-chat-timestamp ' )
2015-10-15 09:14:19 +00:00
. last ( )
2015-11-12 15:19:47 +00:00
. replaceWith @ view ( ' timestamp ' )
2015-10-15 09:14:19 +00:00
label: label
time: time
2015-11-12 15:19:47 +00:00
addStatus: (status) ->
2015-11-12 16:31:20 +00:00
@ maybeAddTimestamp ( )
2015-10-15 09:26:56 +00:00
@ el . find ( ' .zammad-chat-body ' ) . append @ view ( ' status ' )
2015-11-12 15:19:47 +00:00
status: status
@ scrollToBottom ( )
2015-10-15 09:14:19 +00:00
scrollToBottom: ->
@ el . find ( ' .zammad-chat-body ' ) . scrollTop ( $ ( ' .zammad-chat-body ' ) . prop ( ' scrollHeight ' ) )
2015-11-11 20:44:54 +00:00
session_init: ->
2015-11-10 14:01:04 +00:00
@ send ( ' chat_session_init ' )
2015-10-15 09:14:19 +00:00
2015-11-12 23:00:13 +00:00
wsConnect: =>
2015-11-11 20:44:54 +00:00
@ log ' notice ' , " Connecting to #{ @ host } "
@ws = new window . WebSocket ( @ host )
2015-11-12 13:56:47 +00:00
@ws.onopen = @ onWebSocketOpen
2015-11-11 20:44:54 +00:00
@ws.onmessage = @ onWebSocketMessage
@ws.onclose = (e) =>
@ log ' debug ' , ' close websocket connection '
2015-11-12 23:00:13 +00:00
if @ wsReconnectEnable
@ reconnect ( )
2015-11-11 20:44:54 +00:00
@ setAgentOnlineState ( false )
@ws.onerror = (e) =>
@ log ' debug ' , ' ws:onerror ' , e
2015-11-12 23:00:13 +00:00
wsClose: =>
@wsReconnectEnable = false
@ ws . close ( )
wsReconnect: =>
if @ reconnectDelayId
clearTimeout ( @ reconnectDelayId )
@reconnectDelayId = setTimeout ( @ wsConnect , 5000 )
2015-11-12 13:56:47 +00:00
onWebSocketOpen: =>
@sessionId = sessionStorage . getItem ( ' sessionId ' )
@ log ' debug ' , ' ws connected '
@ send ' chat_status_customer ' ,
session_id: @ sessionId
@ setAgentOnlineState ( true )
2015-10-15 09:14:19 +00:00
reconnect: =>
# set status to connecting
2015-11-11 20:44:54 +00:00
@ log ' notice ' , ' reconnecting '
@ disableInput ( )
2015-10-15 09:14:19 +00:00
@lastAddedType = ' status '
2015-11-11 20:44:54 +00:00
@ el . find ( ' .zammad-chat-agent-status ' ) . attr ( ' data-status ' , ' connecting ' ) . text @ T ( ' Reconnecting ' )
2015-10-15 09:14:19 +00:00
@ addStatus @ T ( ' Connection lost ' )
2015-11-12 23:00:13 +00:00
@ wsReconnect ( )
2015-11-11 20:44:54 +00:00
2015-10-15 09:14:19 +00:00
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 ' )
2015-11-11 20:44:54 +00:00
onSessionClosed: (data) ->
@ addStatus @ T ( ' Chat closed by %s ' , data . realname )
@ disableInput ( )
2015-10-15 09:14:19 +00:00
disconnect: ->
2015-11-02 15:48:16 +00:00
@ showLoader ( )
@ 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 ' )
2015-11-12 14:05:43 +00:00
setSessionId: (id) =>
@sessionId = id
2015-11-12 16:15:58 +00:00
if id is undefined
sessionStorage . removeItem ' sessionId '
else
sessionStorage . setItem ' sessionId ' , id
2015-11-12 14:05:43 +00:00
onConnectionEstablished: (data) =>
2015-11-12 13:19:41 +00:00
# stop delay of initial queue position
if @ onInitialQueueDelayId
2015-11-12 14:05:43 +00:00
clearTimeout @ onInitialQueueDelayId
2015-11-12 13:56:47 +00:00
2015-11-02 15:48:16 +00:00
@inQueue = false
2015-11-12 15:16:01 +00:00
if data . agent
@agent = data . agent
if data . session_id
@ setSessionId data . session_id
2015-11-02 15:48:16 +00:00
@ el . find ( ' .zammad-chat-agent ' ) . html @ view ( ' agent ' )
2015-11-12 13:56:47 +00:00
agent: @ agent
2015-11-02 15:48:16 +00:00
2015-11-11 20:44:54 +00:00
@ enableInput ( )
2015-11-02 15:48:16 +00:00
@ el . find ( ' .zammad-chat-body ' ) . empty ( )
@ 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 ' )
2015-11-12 14:05:43 +00:00
@ input . focus ( )
2015-11-02 15:48:16 +00:00
2015-11-12 16:31:20 +00:00
showTimeout: ->
@ el . find ( ' .zammad-chat-body ' ) . html @ view ( ' timeout ' )
agent: @ agent . name
delay: 10
unit: @ T ( ' minutes ' )
2015-11-02 15:48:16 +00:00
showLoader: ->
@ el . find ( ' .zammad-chat-body ' ) . html @ view ( ' loader ' ) ( )
2015-10-15 09:14:19 +00:00
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 ( )