12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712 |
- do($ = window.jQuery, window) ->
- scripts = document.getElementsByTagName('script')
- # search for script to get protocol and hostname for ws connection
- myScript = scripts[scripts.length - 1]
- scriptProtocol = window.location.protocol.replace(':', '') # set default protocol
- if myScript && myScript.src
- scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
- scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1]
- # Define the plugin class
- class Base
- defaults:
- debug: false
- constructor: (options) ->
- @options = $.extend {}, @defaults, options
- @log = new Log(debug: @options.debug, logPrefix: @options.logPrefix || @logPrefix)
- class Log
- defaults:
- debug: false
- constructor: (options) ->
- @options = $.extend {}, @defaults, options
- debug: (items...) =>
- return if !@options.debug
- @log('debug', items)
- notice: (items...) =>
- @log('notice', items)
- error: (items...) =>
- @log('error', items)
- log: (level, items) =>
- items.unshift('||')
- items.unshift(level)
- items.unshift(@options.logPrefix)
- console.log.apply console, items
- return if !@options.debug
- logString = ''
- for item in items
- logString += ' '
- if typeof item is 'object'
- logString += JSON.stringify(item)
- else if item && item.toString
- logString += item.toString()
- else
- logString += item
- $('.js-chatLogDisplay').prepend('<div>' + logString + '</div>')
- class Timeout extends Base
- timeoutStartedAt: null
- logPrefix: 'timeout'
- defaults:
- debug: false
- timeout: 4
- timeoutIntervallCheck: 0.5
- constructor: (options) ->
- super(options)
- start: =>
- @stop()
- timeoutStartedAt = new Date
- check = =>
- timeLeft = new Date - new Date(timeoutStartedAt.getTime() + @options.timeout * 1000 * 60)
- @log.debug "Timeout check for #{@options.timeout} minutes (left #{timeLeft/1000} sec.)"#, new Date
- return if timeLeft < 0
- @stop()
- @options.callback()
- @log.debug "Start timeout in #{@options.timeout} minutes"#, new Date
- @intervallId = setInterval(check, @options.timeoutIntervallCheck * 1000 * 60)
- stop: =>
- return if !@intervallId
- @log.debug "Stop timeout of #{@options.timeout} minutes"#, new Date
- clearInterval(@intervallId)
- class Io extends Base
- logPrefix: 'io'
- constructor: (options) ->
- super(options)
- set: (params) =>
- for key, value of params
- @options[key] = value
- connect: =>
- @log.debug "Connecting to #{@options.host}"
- @ws = new window.WebSocket("#{@options.host}")
- @ws.onopen = (e) =>
- @log.debug 'onOpen', e
- @options.onOpen(e)
- @ping()
- @ws.onmessage = (e) =>
- pipes = JSON.parse(e.data)
- @log.debug 'onMessage', e.data
- for pipe in pipes
- if pipe.event is 'pong'
- @ping()
- if @options.onMessage
- @options.onMessage(pipes)
- @ws.onclose = (e) =>
- @log.debug 'close websocket connection', e
- if @pingDelayId
- clearTimeout(@pingDelayId)
- if @manualClose
- @log.debug 'manual close, onClose callback'
- @manualClose = false
- if @options.onClose
- @options.onClose(e)
- else
- @log.debug 'error close, onError callback'
- if @options.onError
- @options.onError('Connection lost...')
- @ws.onerror = (e) =>
- @log.debug 'onError', e
- if @options.onError
- @options.onError(e)
- close: =>
- @log.debug 'close websocket manually'
- @manualClose = true
- @ws.close()
- reconnect: =>
- @log.debug 'reconnect'
- @close()
- @connect()
- send: (event, data = {}) =>
- @log.debug 'send', event, data
- msg = JSON.stringify
- event: event
- data: data
- @ws.send msg
- ping: =>
- localPing = =>
- @send('ping')
- @pingDelayId = setTimeout(localPing, 29000)
- class ZammadChat extends Base
- defaults:
- chatId: undefined
- show: true
- target: $('body')
- host: ''
- debug: false
- flat: false
- lang: undefined
- cssAutoload: true
- cssUrl: undefined
- fontSize: undefined
- buttonClass: 'open-zammad-chat'
- inactiveClass: 'is-inactive'
- title: '<strong>Chat</strong> with us!'
- scrollHint: 'Scroll down to see new messages'
- idleTimeout: 6
- idleTimeoutIntervallCheck: 0.5
- inactiveTimeout: 8
- inactiveTimeoutIntervallCheck: 0.5
- waitingListTimeout: 4
- waitingListTimeoutIntervallCheck: 0.5
- # Callbacks
- onReady: undefined
- onCloseAnimationEnd: undefined
- onError: undefined
- onOpenAnimationEnd: undefined
- onConnectionReestablished: undefined
- onSessionClosed: undefined
- onConnectionEstablished: undefined
- onCssLoaded: undefined
- logPrefix: 'chat'
- _messageCount: 0
- isOpen: false
- blinkOnlineInterval: null
- stopBlinOnlineStateTimeout: null
- showTimeEveryXMinutes: 2
- lastTimestamp: null
- lastAddedType: null
- inputDisabled: false
- inputTimeout: null
- isTyping: false
- state: 'offline'
- initialQueueDelay: 10000
- translations:
- 'da':
- '<strong>Chat</strong> with us!': '<strong>Chat</strong> med os!'
- 'Scroll down to see new messages': 'Scroll ned for at se nye beskeder'
- 'Online': 'Online'
- 'Offline': 'Offline'
- 'Connecting': 'Forbinder'
- 'Connection re-established': 'Forbindelse genoprettet'
- 'Today': 'I dag'
- 'Send': 'Send'
- 'Chat closed by %s': 'Chat lukket af %s'
- 'Compose your message...': 'Skriv en besked...'
- 'All colleagues are busy.': 'Alle kollegaer er optaget.'
- 'You are on waiting list position <strong>%s</strong>.': 'Du er i venteliste som nummer <strong>%s</strong>.'
- 'Start new conversation': 'Start en ny samtale'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da du ikke har svaret i de sidste %s minutter er din samtale med <strong>%s</strong> blevet lukket.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Da du ikke har svaret i de sidste %s minutter er din samtale blevet lukket.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Vi beklager, det tager længere end forventet at få en ledig plads. Prøv venligst igen senere eller send os en e-mail. På forhånd tak!'
- 'de':
- '<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!'
- 'Scroll down to see new messages': 'Scrolle nach unten um neue Nachrichten zu sehen'
- 'Online': 'Online'
- 'Offline': 'Offline'
- 'Connecting': 'Verbinden'
- 'Connection re-established': 'Verbindung wiederhergestellt'
- 'Today': 'Heute'
- 'Send': 'Senden'
- 'Chat closed by %s': 'Chat beendet von %s'
- 'Compose your message...': 'Ihre Nachricht...'
- 'All colleagues 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>.'
- 'Start new conversation': 'Neue Konversation starten'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit <strong>%s</strong> geschlossen.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Es tut uns leid, es dauert länger als erwartet, um einen freien Platz zu erhalten. Bitte versuchen Sie es zu einem späteren Zeitpunkt noch einmal oder schicken Sie uns eine E-Mail. Vielen Dank!'
- 'es':
- '<strong>Chat</strong> with us!': '<strong>Chatee</strong> con nosotros!'
- 'Scroll down to see new messages': 'Haga scroll hacia abajo para ver nuevos mensajes'
- 'Online': 'En linea'
- 'Offline': 'Desconectado'
- 'Connecting': 'Conectando'
- 'Connection re-established': 'Conexión restablecida'
- 'Today': 'Hoy'
- 'Send': 'Enviar'
- 'Chat closed by %s': 'Chat cerrado por %s'
- 'Compose your message...': 'Escriba su mensaje...'
- 'All colleagues are busy.': 'Todos los agentes están ocupados.'
- 'You are on waiting list position <strong>%s</strong>.': 'Usted está en la posición <strong>%s</strong> de la lista de espera.'
- 'Start new conversation': 'Iniciar nueva conversación'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Puesto que usted no respondió en los últimos %s minutos su conversación con <strong>%s</strong> se ha cerrado.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Puesto que usted no respondió en los últimos %s minutos su conversación se ha cerrado.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Lo sentimos, se tarda más tiempo de lo esperado para ser atendido por un agente. Inténtelo de nuevo más tarde o envíenos un correo electrónico. ¡Gracias!'
- 'fi':
- '<strong>Chat</strong> with us!': '<strong>Keskustele</strong> kanssamme!'
- 'Scroll down to see new messages': 'Rullaa alas nähdäksesi uudet viestit'
- 'Online': 'Paikalla'
- 'Offline': 'Poissa'
- 'Connecting': 'Yhdistetään'
- 'Connection re-established': 'Yhteys muodostettu uudelleen'
- 'Today': 'Tänään'
- 'Send': 'Lähetä'
- 'Chat closed by %s': '%s sulki keskustelun'
- 'Compose your message...': 'Luo viestisi...'
- 'All colleagues are busy.': 'Kaikki kollegat ovat varattuja.'
- 'You are on waiting list position <strong>%s</strong>.': 'Olet odotuslistalla sijalla <strong>%s</strong>.'
- 'Start new conversation': 'Aloita uusi keskustelu'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Koska et vastannut viimeiseen %s minuuttiin, keskustelusi <strong>%s</strong> kanssa suljettiin.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Koska et vastannut viimeiseen %s minuuttiin, keskustelusi suljettiin.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Olemme pahoillamme, tyhjän paikan vapautumisessa kestää odotettua pidempään. Ole hyvä ja yritä myöhemmin uudestaan tai lähetä meille sähköpostia. Kiitos!'
- 'fr':
- '<strong>Chat</strong> with us!': '<strong>Chattez</strong> avec nous!'
- 'Scroll down to see new messages': 'Faites défiler pour lire les nouveaux messages'
- 'Online': 'En-ligne'
- 'Offline': 'Hors-ligne'
- 'Connecting': 'Connexion en cours'
- 'Connection re-established': 'Connexion rétablie'
- 'Today': 'Aujourdhui'
- 'Send': 'Envoyer'
- 'Chat closed by %s': 'Chat fermé par %s'
- 'Compose your message...': 'Composez votre message...'
- 'All colleagues are busy.': 'Tous les collaborateurs sont occupés actuellement.'
- 'You are on waiting list position <strong>%s</strong>.': 'Vous êtes actuellement en position <strong>%s</strong> dans la file d\'attente.'
- 'Start new conversation': 'Démarrer une nouvelle conversation'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Si vous ne répondez pas dans les <strong>%s</strong> minutes, votre conversation avec %s sera fermée.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Si vous ne répondez pas dans les %s minutes, votre conversation va être fermée.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Nous sommes désolés, il faut plus de temps que prévu pour obtenir un emplacement vide. Veuillez réessayer ultérieurement ou nous envoyer un courriel. Nous vous remercions!'
- 'he':
- '<strong>Chat</strong> with us!': '<strong>שוחח</strong>איתנו!'
- 'Scroll down to see new messages': 'גלול מטה כדי לראות הודעות חדשות'
- 'Online': 'מחובר'
- 'Offline': 'מנותק'
- 'Connecting': 'מתחבר'
- 'Connection re-established': 'החיבור שוחזר'
- 'Today': 'היום'
- 'Send': 'שלח'
- 'Chat closed by %s': 'הצאט נסגר ע"י %s'
- 'Compose your message...': 'כתוב את ההודעה שלך ...'
- 'All colleagues are busy.': 'כל הנציגים תפוסים'
- 'You are on waiting list position <strong>%s</strong>.': 'מיקומך בתור <strong>%s</strong>.'
- 'Start new conversation': 'התחל שיחה חדשה'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'מכיוון שלא הגבת במהלך %s דקות השיחה שלך עם <strong>%s</strong> נסגרה.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'מכיוון שלא הגבת במהלך %s הדקות האחרונות השיחה שלך נסגרה.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'מצטערים, הזמן לקבלת נציג ארוך מהרגיל. נסה שוב מאוחר יותר או שלח לנו דוא"ל. תודה!'
- 'hu':
- '<strong>Chat</strong> with us!': '<strong>Chatelj</strong> velünk!'
- 'Scroll down to see new messages': 'Görgess lejjebb az újabb üzenetekért'
- 'Online': 'Online'
- 'Offline': 'Offline'
- 'Connecting': 'Csatlakozás'
- 'Connection re-established': 'Újracsatlakozás'
- 'Today': 'Ma'
- 'Send': 'Küldés'
- 'Chat closed by %s': 'A beszélgetést lezárta %s'
- 'Compose your message...': 'Írj üzenetet...'
- 'All colleagues are busy.': 'Jelenleg minden kollégánk elfoglalt.'
- 'You are on waiting list position <strong>%s</strong>.': 'A várólistán a <strong>%s</strong>. pozícióban várakozol.'
- 'Start new conversation': 'Új beszélgetés indítása'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Mivel %s perce nem érkezett újabb üzenet, ezért a <strong>%s</strong> kollégával folytatott beszéletést lezártuk.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Mivel %s perce nem érkezett válasz, a beszélgetés lezárult.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Sajnáljuk, de a várakozási idő hosszabb a szokásosnál. Kérlek próbáld újra, vagy írd meg kérdésed emailben. Köszönjük!'
- 'nl':
- '<strong>Chat</strong> with us!': '<strong>Chat</strong> met ons!'
- 'Scroll down to see new messages': 'Scrol naar beneden om nieuwe berichten te zien'
- 'Online': 'Online'
- 'Offline': 'Offline'
- 'Connecting': 'Verbinden'
- 'Connection re-established': 'Verbinding herstelt'
- 'Today': 'Vandaag'
- 'Send': 'Verzenden'
- 'Chat closed by %s': 'Chat gesloten door %s'
- 'Compose your message...': 'Typ uw bericht...'
- 'All colleagues are busy.': 'Alle medewerkers zijn bezet.'
- 'You are on waiting list position <strong>%s</strong>.': 'U bent <strong>%s</strong> in de wachtrij.'
- 'Start new conversation': 'Nieuwe conversatie starten'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Omdat u in de laatste %s minuten niets geschreven heeft wordt de conversatie met <strong>%s</strong> gesloten.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Omdat u in de laatste %s minuten niets geschreven heeft is de conversatie gesloten.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Het spijt ons, het duurt langer dan verwacht om te antwoorden. Alstublieft probeer het later nogmaals of stuur ons een email. Hartelijk dank!'
- 'it':
- '<strong>Chat</strong> with us!': '<strong>Chatta</strong> con noi!'
- 'Scroll down to see new messages': 'Scorri verso il basso per vedere i nuovi messaggi'
- 'Online': 'Online'
- 'Offline': 'Offline'
- 'Connecting': 'Collegamento in corso'
- 'Connection re-established': 'Collegamento ristabilito'
- 'Today': 'Oggi'
- 'Send': 'Invio'
- 'Chat closed by %s': 'Chat chiusa da %s'
- 'Compose your message...': 'Componi il tuo messaggio...'
- 'All colleagues are busy.': 'Tutti gli operatori sono occupati.'
- 'You are on waiting list position <strong>%s</strong>.': 'Sei in posizione <strong>%s</strong> nella lista d\'attesa.'
- 'Start new conversation': 'Avvia una nuova chat'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Dal momento che non hai risposto negli ultimi %s minuti la tua chat con <strong>%s</strong> è stata chiusa.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Dal momento che non hai risposto negli ultimi %s minuti la tua chat è stata chiusa.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Ci dispiace, ci vuole più tempo del previsto per arrivare al tuo turno. Per favore riprova più tardi o inviaci un\'email. Grazie!'
- 'pl':
- '<strong>Chat</strong> with us!': '<strong>Czatuj</strong> z nami!'
- 'Scroll down to see new messages': 'Przewiń w dół, aby wyświetlić nowe wiadomości'
- 'Online': 'Online'
- 'Offline': 'Offline'
- 'Connecting': 'Łączenie'
- 'Connection re-established': 'Ponowne nawiązanie połączenia'
- 'Today': 'dzisiejszy'
- 'Send': 'Wyślij'
- 'Chat closed by %s': 'Czat zamknięty przez %s'
- 'Compose your message...': 'Utwórz swoją wiadomość...'
- 'All colleagues are busy.': 'Wszyscy konsultanci są zajęci.'
- 'You are on waiting list position <strong>%s</strong>.': 'Na liście oczekujących znajduje się pozycja <strong>%s</strong>.'
- 'Start new conversation': 'Rozpoczęcie nowej konwersacji'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Ponieważ w ciągu ostatnich %s minut nie odpowiedziałeś, Twoja rozmowa z <strong>%s</strong> została zamknięta.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Ponieważ nie odpowiedziałeś w ciągu ostatnich %s minut, Twoja rozmowa została zamknięta.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Przykro nam, ale to trwa dłużej niż się spodziewamy. Spróbuj ponownie później lub wyślij nam wiadomość e-mail. Dziękuję!'
- 'pt-br': {
- '<strong>Chat</strong> with us!': '<strong>Chat</strong> fale conosco!',
- 'Scroll down to see new messages': 'Role para baixo, para ver nosvas mensagens',
- 'Online': 'Online',
- 'Offline': 'Desconectado',
- 'Connecting': 'Conectando',
- 'Connection re-established': 'Conexão restabelecida',
- 'Today': 'Hoje',
- 'Send': 'Enviar',
- 'Chat closed by %s': 'Chat encerrado por %s',
- 'Compose your message...': 'Escreva sua mensagem...',
- 'All colleagues are busy.': 'Todos os agentes estão ocupados.',
- 'You are on waiting list position <strong>%s</strong>.': 'Você está na posição <strong>%s</strong> na fila de espera.',
- 'Start new conversation': 'Iniciar uma nova conversa',
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Como você não respondeu nos últimos %s minutos sua conversa com <strong>%s</strong> foi encerrada.',
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Como você não respondeu nos últimos %s minutos sua conversa foi encerrada.',
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Desculpe, mas o tempo de espera por um agente foi excedido. Tente novamente mais tarde ou nós envie um email. Obrigado'
- },
- 'zh-cn':
- '<strong>Chat</strong> with us!': '发起<strong>即时对话</strong>!'
- 'Scroll down to see new messages': '向下滚动以查看新消息'
- 'Online': '在线'
- 'Offline': '离线'
- 'Connecting': '连接中'
- 'Connection re-established': '正在重新建立连接'
- 'Today': '今天'
- 'Send': '发送'
- 'Chat closed by %s': 'Chat closed by %s'
- 'Compose your message...': '正在输入信息...'
- 'All colleagues are busy.': '所有工作人员都在忙碌中.'
- 'You are on waiting list position <strong>%s</strong>.': '您目前的等候位置是第 <strong>%s</strong> 位.'
- 'Start new conversation': '开始新的会话'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': '由于您超过 %s 分钟没有回复, 您与 <strong>%s</strong> 的会话已被关闭.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': '由于您超过 %s 分钟没有任何回复, 该对话已被关闭.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': '非常抱歉, 目前需要等候更长的时间才能接入对话, 请稍后重试或向我们发送电子邮件. 谢谢!'
- 'zh-tw':
- '<strong>Chat</strong> with us!': '開始<strong>即時對话</strong>!'
- 'Scroll down to see new messages': '向下滑動以查看新訊息'
- 'Online': '線上'
- 'Offline': '离线'
- 'Connecting': '連線中'
- 'Connection re-established': '正在重新建立連線中'
- 'Today': '今天'
- 'Send': '發送'
- 'Chat closed by %s': 'Chat closed by %s'
- 'Compose your message...': '正在輸入訊息...'
- 'All colleagues are busy.': '所有服務人員都在忙碌中.'
- 'You are on waiting list position <strong>%s</strong>.': '你目前的等候位置是第 <strong>%s</strong> 順位.'
- 'Start new conversation': '開始新的對話'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': '由於你超過 %s 分鐘沒有回應, 你與 <strong>%s</strong> 的對話已被關閉.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': '由於你超過 %s 分鐘沒有任何回應, 該對話已被關閉.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': '非常抱歉, 當前需要等候更長的時間方可排入對話程序, 請稍後重試或向我們寄送電子郵件. 謝謝!'
- 'ru':
- '<strong>Chat</strong> with us!': 'Напишите нам!'
- 'Scroll down to see new messages': 'Прокрутите, чтобы увидеть новые сообщения'
- 'Online': 'Онлайн'
- 'Offline': 'Оффлайн'
- 'Connecting': 'Подключение'
- 'Connection re-established': 'Подключение восстановлено'
- 'Today': 'Сегодня'
- 'Send': 'Отправить'
- 'Chat closed by %s': '%s закрыл чат'
- 'Compose your message...': 'Напишите сообщение...'
- 'All colleagues are busy.': 'Все сотрудники заняты'
- 'You are on waiting list position %s.': 'Вы в списке ожидания под номером %s'
- 'Start new conversation': 'Начать новую переписку.'
- 'Since you didn\'t respond in the last %s minutes your conversation with %s got closed.': 'Поскольку вы не отвечали в течение последних %s минут, ваш разговор с %s был закрыт.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Поскольку вы не отвечали в течение последних %s минут, ваш разговор был закрыт.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'К сожалению, ожидание свободного места требует больше времени. Повторите попытку позже или отправьте нам электронное письмо. Спасибо!'
- 'sv':
- '<strong>Chat</strong> with us!': '<strong>Chatta</strong> med oss!'
- 'Scroll down to see new messages': 'Rulla ner för att se nya meddelanden'
- 'Online': 'Online'
- 'Offline': 'Offline'
- 'Connecting': 'Ansluter'
- 'Connection re-established': 'Anslutningen återupprättas'
- 'Today': 'I dag'
- 'Send': 'Skicka'
- 'Chat closed by %s': 'Chatt stängd av %s'
- 'Compose your message...': 'Skriv ditt meddelande...'
- 'All colleagues are busy.': 'Alla kollegor är upptagna.'
- 'You are on waiting list position <strong>%s</strong>.': 'Du är på väntelistan som position <strong>%s</strong>.'
- 'Start new conversation': 'Starta ny konversation'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Eftersom du inte svarat inom %s minuterna i din konversation med <strong>%s</strong> så stängdes chatten.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Då du inte svarat inom de senaste %s minuterna så avslutades din chatt.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Vi är ledsna, det tar längre tid som förväntat att få en ledig plats. Försök igen senare eller skicka ett e-postmeddelande till oss. Tack!'
- 'no':
- '<strong>Chat</strong> with us!': '<strong>Chat</strong> med oss!'
- 'Scroll down to see new messages': 'Bla ned for å se nye meldinger'
- 'Online': 'Pålogget'
- 'Offline': 'Avlogget'
- 'Connecting': 'Koble til'
- 'Connection re-established': 'Tilkoblingen er gjenopprettet'
- 'Today': 'I dag'
- 'Send': 'Send'
- 'Chat closed by %s': 'Chat avsluttes om %s'
- 'Compose your message...': 'Skriv din melding...'
- 'All colleagues are busy.': 'Alle våre kolleger er for øyeblikket opptatt.'
- 'You are on waiting list position <strong>%s</strong>.': 'Du står nå i kø og er nr. <strong>%s</strong> på ventelisten.'
- 'Start new conversation': 'Start en ny samtale'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med <strong>%s</strong> nå avsluttes.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!'
- 'nb':
- '<strong>Chat</strong> with us!': '<strong>Chat</strong> med oss!'
- 'Scroll down to see new messages': 'Bla ned for å se nye meldinger'
- 'Online': 'Pålogget'
- 'Offline': 'Avlogget'
- 'Connecting': 'Koble til'
- 'Connection re-established': 'Tilkoblingen er gjenopprettet'
- 'Today': 'I dag'
- 'Send': 'Send'
- 'Chat closed by %s': 'Chat avsluttes om %s'
- 'Compose your message...': 'Skriv din melding...'
- 'All colleagues are busy.': 'Alle våre kolleger er for øyeblikket opptatt.'
- 'You are on waiting list position <strong>%s</strong>.': 'Du står nå i kø og er nr. <strong>%s</strong> på ventelisten.'
- 'Start new conversation': 'Start en ny samtale'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med <strong>%s</strong> nå avsluttes.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!'
- 'el':
- '<strong>Chat</strong> with us!': '<strong>Επικοινωνήστε</strong> μαζί μας!'
- 'Scroll down to see new messages': 'Μεταβείτε κάτω για να δείτε τα νέα μηνύματα'
- 'Online': 'Σε σύνδεση'
- 'Offline': 'Αποσυνδεμένος'
- 'Connecting': 'Σύνδεση'
- 'Connection re-established': 'Η σύνδεση αποκαταστάθηκε'
- 'Today': 'Σήμερα'
- 'Send': 'Αποστολή'
- 'Chat closed by %s': 'Η συνομιλία έκλεισε από τον/την %s'
- 'Compose your message...': 'Γράψτε το μήνυμα σας...'
- 'All colleagues are busy.': 'Όλοι οι συνάδελφοι μας είναι απασχολημένοι.'
- 'You are on waiting list position <strong>%s</strong>.': 'Βρίσκεστε σε λίστα αναμονής στη θέση <strong>%s</strong>.'
- 'Start new conversation': 'Έναρξη νέας συνομιλίας'
- 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας με τον/την <strong>%s</strong> έκλεισε.'
- 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας έκλεισε.'
- 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'Λυπούμαστε που χρειάζεται περισσότερος χρόνος από τον αναμενόμενο για να βρεθεί μία κενή θέση. Παρακαλούμε δοκιμάστε ξανά αργότερα ή στείλτε μας ένα email. Ευχαριστούμε!'
- sessionId: undefined
- scrolledToBottom: true
- scrollSnapTolerance: 10
- richTextFormatKey:
- 66: true # b
- 73: true # i
- 85: true # u
- 83: true # s
- T: (string, items...) =>
- if @options.lang && @options.lang isnt 'en'
- if !@translations[@options.lang]
- @log.notice "Translation '#{@options.lang}' needed!"
- else
- translations = @translations[@options.lang]
- if !translations[string]
- @log.notice "Translation needed for '#{string}'"
- string = translations[string] || string
- if items
- for item in items
- string = string.replace(/%s/, item)
- string
- view: (name) =>
- return (options) =>
- if !options
- options = {}
- options.T = @T
- options.background = @options.background
- options.flat = @options.flat
- options.fontSize = @options.fontSize
- return window.zammadChatTemplates[name](options)
- constructor: (options) ->
- @options = $.extend {}, @defaults, options
- super(@options)
- # fullscreen
- @isFullscreen = (window.matchMedia and window.matchMedia('(max-width: 768px)').matches)
- @scrollRoot = $(@getScrollRoot())
- # check prerequisites
- if !$
- @state = 'unsupported'
- @log.notice 'Chat: no jquery found!'
- return
- if !window.WebSocket or !sessionStorage
- @state = 'unsupported'
- @log.notice 'Chat: Browser not supported!'
- return
- if !@options.chatId
- @state = 'unsupported'
- @log.error 'Chat: need chatId as option!'
- return
- # detect language
- if !@options.lang
- @options.lang = $('html').attr('lang')
- if @options.lang
- if !@translations[@options.lang]
- @log.debug "lang: No #{@options.lang} found, try first two letters"
- @options.lang = @options.lang.replace(/-.+?$/, '') # replace "-xx" of xx-xx
- @log.debug "lang: #{@options.lang}"
- # detect host
- @detectHost() if !@options.host
- @loadCss()
- @io = new Io(@options)
- @io.set(
- onOpen: @render
- onClose: @onWebSocketClose
- onMessage: @onWebSocketMessage
- onError: @onError
- )
- @io.connect()
- getScrollRoot: ->
- return document.scrollingElement if 'scrollingElement' of document
- html = document.documentElement
- start = html.scrollTop
- html.scrollTop = start + 1
- end = html.scrollTop
- html.scrollTop = start
- return if end > start then html else document.body
- render: =>
- if !@el || !$('.zammad-chat').get(0)
- @renderBase()
- # disable open button
- $(".#{ @options.buttonClass }").addClass @options.inactiveClass
- @setAgentOnlineState 'online'
- @log.debug 'widget rendered'
- @startTimeoutObservers()
- @idleTimeout.start()
- # get current chat status
- @sessionId = sessionStorage.getItem('sessionId')
- @send 'chat_status_customer',
- session_id: @sessionId
- url: window.location.href
- renderBase: ->
- @el = $(@view('chat')(
- title: @options.title,
- scrollHint: @options.scrollHint
- ))
- @options.target.append @el
- @input = @el.find('.zammad-chat-input')
- # start bindings
- @el.find('.js-chat-open').click @open
- @el.find('.js-chat-toggle').click @toggle
- @el.find('.js-chat-status').click @stopPropagation
- @el.find('.zammad-chat-controls').on 'submit', @onSubmit
- @el.find('.zammad-chat-body').on 'scroll', @detectScrolledtoBottom
- @el.find('.zammad-scroll-hint').click @onScrollHintClick
- @input.on(
- keydown: @checkForEnter
- input: @onInput
- )
- @input.on('keydown', (e) =>
- richtTextControl = false
- if !e.altKey && !e.ctrlKey && e.metaKey
- richtTextControl = true
- else if !e.altKey && e.ctrlKey && !e.metaKey
- richtTextControl = true
- if richtTextControl && @richTextFormatKey[ e.keyCode ]
- e.preventDefault()
- if e.keyCode is 66
- document.execCommand('bold')
- return true
- if e.keyCode is 73
- document.execCommand('italic')
- return true
- if e.keyCode is 85
- document.execCommand('underline')
- return true
- if e.keyCode is 83
- document.execCommand('strikeThrough')
- return true
- )
- @input.on('paste', (e) =>
- e.stopPropagation()
- e.preventDefault()
- clipboardData
- if e.clipboardData
- clipboardData = e.clipboardData
- else if window.clipboardData
- clipboardData = window.clipboardData
- else if e.originalEvent.clipboardData
- clipboardData = e.originalEvent.clipboardData
- else
- throw 'No clipboardData support'
- imageInserted = false
- if clipboardData && clipboardData.items && clipboardData.items[0]
- item = clipboardData.items[0]
- if item.kind == 'file' && (item.type == 'image/png' || item.type == 'image/jpeg')
- imageFile = item.getAsFile()
- reader = new FileReader()
- reader.onload = (e) =>
- result = e.target.result
- img = document.createElement('img')
- img.src = result
- insert = (dataUrl, width, height, isRetina) =>
- # adapt image if we are on retina devices
- if @isRetina()
- width = width / 2
- height = height / 2
- result = dataUrl
- img = "<img style=\"width: 100%; max-width: #{width}px;\" src=\"#{result}\">"
- document.execCommand('insertHTML', false, img)
- # resize if to big
- @resizeImage(img.src, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
- reader.readAsDataURL(imageFile)
- imageInserted = true
- return if imageInserted
- # check existing + paste text for limit
- text = undefined
- docType = undefined
- try
- text = clipboardData.getData('text/html')
- docType = 'html'
- if !text || text.length is 0
- docType = 'text'
- text = clipboardData.getData('text/plain')
- if !text || text.length is 0
- docType = 'text2'
- text = clipboardData.getData('text')
- catch e
- console.log('Sorry, can\'t insert markup because browser is not supporting it.')
- docType = 'text3'
- text = clipboardData.getData('text')
- if docType is 'text' || docType is 'text2' || docType is 'text3'
- text = '<div>' + text.replace(/\n/g, '</div><div>') + '</div>'
- text = text.replace(/<div><\/div>/g, '<div><br></div>')
- console.log('p', docType, text)
- if docType is 'html'
- sanitized = DOMPurify.sanitize(text)
- @log.debug 'sanitized HTML clipboard', sanitized
- html = $("<div>#{sanitized}</div>")
- match = false
- htmlTmp = text
- regex = new RegExp('<(/w|w)\:[A-Za-z]')
- if htmlTmp.match(regex)
- match = true
- htmlTmp = htmlTmp.replace(regex, '')
- regex = new RegExp('<(/o|o)\:[A-Za-z]')
- if htmlTmp.match(regex)
- match = true
- htmlTmp = htmlTmp.replace(regex, '')
- if match
- html = @wordFilter(html)
- #html
- html = $(html)
- html.contents().each( ->
- if @nodeType == 8
- $(@).remove()
- )
- # remove tags, keep content
- html.find('a, font, small, time, form, label').replaceWith( ->
- $(@).contents()
- )
- # replace tags with generic div
- # New type of the tag
- replacementTag = 'div';
- # Replace all x tags with the type of replacementTag
- html.find('textarea').each( ->
- outer = @outerHTML
- # Replace opening tag
- regex = new RegExp('<' + @tagName, 'i')
- newTag = outer.replace(regex, '<' + replacementTag)
- # Replace closing tag
- regex = new RegExp('</' + @tagName, 'i')
- newTag = newTag.replace(regex, '</' + replacementTag)
- $(@).replaceWith(newTag)
- )
- # remove tags & content
- html.find('font, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head, fieldset').remove()
- @removeAttributes(html)
- text = html.html()
- # as fallback, insert html via pasteHtmlAtCaret (for IE 11 and lower)
- if docType is 'text3'
- @pasteHtmlAtCaret(text)
- else
- document.execCommand('insertHTML', false, text)
- true
- )
- @input.on('drop', (e) =>
- e.stopPropagation()
- e.preventDefault()
- dataTransfer
- if window.dataTransfer # ie
- dataTransfer = window.dataTransfer
- else if e.originalEvent.dataTransfer # other browsers
- dataTransfer = e.originalEvent.dataTransfer
- else
- throw 'No clipboardData support'
- x = e.clientX
- y = e.clientY
- file = dataTransfer.files[0]
- # look for images
- if file.type.match('image.*')
- reader = new FileReader()
- reader.onload = (e) =>
- result = e.target.result
- img = document.createElement('img')
- img.src = result
- # Insert the image at the carat
- insert = (dataUrl, width, height, isRetina) =>
- # adapt image if we are on retina devices
- if @isRetina()
- width = width / 2
- height = height / 2
- result = dataUrl
- img = $("<img style=\"width: 100%; max-width: #{width}px;\" src=\"#{result}\">")
- img = img.get(0)
- if document.caretPositionFromPoint
- pos = document.caretPositionFromPoint(x, y)
- range = document.createRange()
- range.setStart(pos.offsetNode, pos.offset)
- range.collapse()
- range.insertNode(img)
- else if document.caretRangeFromPoint
- range = document.caretRangeFromPoint(x, y)
- range.insertNode(img)
- else
- console.log('could not find carat')
- # resize if to big
- @resizeImage(img.src, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
- reader.readAsDataURL(file)
- )
- $(window).on('beforeunload', =>
- @onLeaveTemporary()
- )
- $(window).bind('hashchange', =>
- if @isOpen
- if @sessionId
- @send 'chat_session_notice',
- session_id: @sessionId
- message: window.location.href
- return
- @idleTimeout.start()
- )
- if @isFullscreen
- @input.on
- focus: @onFocus
- focusout: @onFocusOut
- stopPropagation: (event) ->
- event.stopPropagation()
- checkForEnter: (event) =>
- if not @inputDisabled and not event.shiftKey and event.keyCode is 13
- event.preventDefault()
- @sendMessage()
- send: (event, data = {}) =>
- data.chat_id = @options.chatId
- @io.send(event, data)
- onWebSocketMessage: (pipes) =>
- for pipe in pipes
- @log.debug 'ws:onmessage', pipe
- switch pipe.event
- when 'chat_error'
- @log.notice pipe.data
- if pipe.data && pipe.data.state is 'chat_disabled'
- @destroy(remove: true)
- 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'
- @onConnectionEstablished pipe.data
- when 'chat_session_queue'
- @onQueueScreen pipe.data
- when 'chat_session_closed'
- @onSessionClosed pipe.data
- when 'chat_session_left'
- @onSessionClosed pipe.data
- when 'chat_session_notice'
- @addStatus @T(pipe.data.message)
- when 'chat_status_customer'
- switch pipe.data.state
- when 'online'
- @sessionId = undefined
- if !@options.cssAutoload || @cssLoaded
- @onReady()
- else
- @socketReady = true
- when 'offline'
- @onError 'Zammad Chat: No agent online'
- when 'chat_disabled'
- @onError 'Zammad Chat: Chat is disabled'
- when 'no_seats_available'
- @onError "Zammad Chat: Too many clients in queue. Clients in queue: #{pipe.data.queue}"
- when 'reconnect'
- @onReopenSession pipe.data
- onReady: ->
- @log.debug 'widget ready for use'
- $(".#{ @options.buttonClass }").click(@open).removeClass(@options.inactiveClass)
- @options.onReady?()
- if @options.show
- @show()
- onError: (message) =>
- @log.debug message
- @addStatus(message)
- $(".#{ @options.buttonClass }").hide()
- if @isOpen
- @disableInput()
- @destroy(remove: false)
- else
- @destroy(remove: true)
- @options.onError?(message)
- onReopenSession: (data) =>
- @log.debug 'old messages', data.session
- @inactiveTimeout.start()
- unfinishedMessage = sessionStorage.getItem 'unfinished_message'
- # rerender chat history
- if data.agent
- @onConnectionEstablished(data)
- for message in data.session
- @renderMessage
- message: message.content
- id: message.id
- from: if message.created_by_id then 'agent' else 'customer'
- if unfinishedMessage
- @input.html(unfinishedMessage)
- # show wait list
- if data.position
- @onQueue data
- @show()
- @open()
- @scrollToBottom()
- if unfinishedMessage
- @input.focus()
- onInput: =>
- # remove unread-state from messages
- @el.find('.zammad-chat-message--unread')
- .removeClass 'zammad-chat-message--unread'
- sessionStorage.setItem 'unfinished_message', @input.html()
- @onTyping()
- onFocus: =>
- $(window).scrollTop(10)
- keyboardShown = $(window).scrollTop() > 0
- $(window).scrollTop(0)
- if keyboardShown
- @log.notice 'virtual keyboard shown'
- # on keyboard shown
- # can't measure visible area height :(
- onFocusOut: ->
- # on keyboard hidden
- onTyping: ->
- # send typing start event only every 1.5 seconds
- return if @isTyping && @isTyping > new Date(new Date().getTime() - 1500)
- @isTyping = new Date()
- @send 'chat_session_typing',
- session_id: @sessionId
- @inactiveTimeout.start()
- onSubmit: (event) =>
- event.preventDefault()
- @sendMessage()
- sendMessage: ->
- message = @input.html()
- return if !message
- @inactiveTimeout.start()
- sessionStorage.removeItem 'unfinished_message'
- messageElement = @view('message')
- message: message
- from: 'customer'
- id: @_messageCount++
- unreadClass: ''
- @maybeAddTimestamp()
- # add message before message typing loader
- if @el.find('.zammad-chat-message--typing').get(0)
- @lastAddedType = 'typing-placeholder'
- @el.find('.zammad-chat-message--typing').before messageElement
- else
- @lastAddedType = 'message--customer'
- @el.find('.zammad-chat-body').append messageElement
- @input.html('')
- @scrollToBottom()
- # send message event
- @send 'chat_session_message',
- content: message
- id: @_messageCount
- session_id: @sessionId
- receiveMessage: (data) =>
- @inactiveTimeout.start()
- # hide writing indicator
- @onAgentTypingEnd()
- @maybeAddTimestamp()
- @renderMessage
- message: data.message.content
- id: data.id
- from: 'agent'
- @scrollToBottom showHint: true
- renderMessage: (data) =>
- @lastAddedType = "message--#{ data.from }"
- data.unreadClass = if document.hidden then ' zammad-chat-message--unread' else ''
- @el.find('.zammad-chat-body').append @view('message')(data)
- open: =>
- if @isOpen
- @log.debug 'widget already open, block'
- return
- @isOpen = true
- @log.debug 'open widget'
- @show()
- if !@sessionId
- @showLoader()
- @el.addClass('zammad-chat-is-open')
- remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
- @el.css 'bottom', -remainerHeight
- if !@sessionId
- @el.animate { bottom: 0 }, 500, @onOpenAnimationEnd
- @send('chat_session_init'
- url: window.location.href
- )
- else
- @el.css 'bottom', 0
- @onOpenAnimationEnd()
- onOpenAnimationEnd: =>
- @idleTimeout.stop()
- if @isFullscreen
- @disableScrollOnRoot()
- @options.onOpenAnimationEnd?()
- sessionClose: =>
- # send close
- @send 'chat_session_close',
- session_id: @sessionId
- # stop timer
- @inactiveTimeout.stop()
- @waitingListTimeout.stop()
- # delete input store
- sessionStorage.removeItem 'unfinished_message'
- # stop delay of initial queue position
- if @onInitialQueueDelayId
- clearTimeout(@onInitialQueueDelayId)
- @setSessionId undefined
- toggle: (event) =>
- if @isOpen
- @close(event)
- else
- @open(event)
- close: (event) =>
- if !@isOpen
- @log.debug 'can\'t close widget, it\'s not open'
- return
- if @initDelayId
- clearTimeout(@initDelayId)
- if @sessionId
- @log.debug 'session close before widget close'
- @sessionClose()
- @log.debug 'close widget'
- event.stopPropagation() if event
- if @isFullscreen
- @enableScrollOnRoot()
- # close window
- remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
- @el.animate { bottom: -remainerHeight }, 500, @onCloseAnimationEnd
- onCloseAnimationEnd: =>
- @el.css 'bottom', ''
- @el.removeClass('zammad-chat-is-open')
- @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')
- @isOpen = false
- @options.onCloseAnimationEnd?()
- @io.reconnect()
- onWebSocketClose: =>
- return if @isOpen
- if @el
- @el.removeClass('zammad-chat-is-shown')
- @el.removeClass('zammad-chat-is-loaded')
- show: ->
- return if @state is 'offline'
- @el.addClass('zammad-chat-is-loaded')
- @el.addClass('zammad-chat-is-shown')
- disableInput: ->
- @inputDisabled = true
- @input.prop('contenteditable', false)
- @el.find('.zammad-chat-send').prop('disabled', true)
- @io.close()
- enableInput: ->
- @inputDisabled = false
- @input.prop('contenteditable', true)
- @el.find('.zammad-chat-send').prop('disabled', false)
- hideModal: ->
- @el.find('.zammad-chat-modal').html ''
- onQueueScreen: (data) =>
- @setSessionId data.session_id
- # delay initial queue position, show connecting first
- show = =>
- @onQueue data
- @waitingListTimeout.start()
- if @initialQueueDelay && !@onInitialQueueDelayId
- @onInitialQueueDelayId = setTimeout(show, @initialQueueDelay)
- return
- # stop delay of initial queue position
- if @onInitialQueueDelayId
- clearTimeout(@onInitialQueueDelayId)
- # show queue position
- show()
- onQueue: (data) =>
- @log.notice 'onQueue', data.position
- @inQueue = true
- @el.find('.zammad-chat-modal').html @view('waiting')
- position: data.position
- onAgentTypingStart: =>
- if @stopTypingId
- clearTimeout(@stopTypingId)
- @stopTypingId = setTimeout(@onAgentTypingEnd, 3000)
- # never display two typing indicators
- return if @el.find('.zammad-chat-message--typing').get(0)
- @maybeAddTimestamp()
- @el.find('.zammad-chat-body').append @view('typingIndicator')()
- # only if typing indicator is shown
- return if !@isVisible(@el.find('.zammad-chat-message--typing'), true)
- @scrollToBottom()
- onAgentTypingEnd: =>
- @el.find('.zammad-chat-message--typing').remove()
- onLeaveTemporary: =>
- return if !@sessionId
- @send 'chat_session_leave_temporary',
- session_id: @sessionId
- 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
- @el.find('.zammad-chat-body').append @view('timestamp')
- label: label
- time: time
- @lastTimestamp = timestamp
- @lastAddedType = 'timestamp'
- @scrollToBottom()
- updateLastTimestamp: (label, time) ->
- return if !@el
- @el.find('.zammad-chat-body')
- .find('.zammad-chat-timestamp')
- .last()
- .replaceWith @view('timestamp')
- label: label
- time: time
- addStatus: (status) ->
- return if !@el
- @maybeAddTimestamp()
- @el.find('.zammad-chat-body').append @view('status')
- status: status
- @scrollToBottom()
- detectScrolledtoBottom: =>
- scrollBottom = @el.find('.zammad-chat-body').scrollTop() + @el.find('.zammad-chat-body').outerHeight()
- @scrolledToBottom = Math.abs(scrollBottom - @el.find('.zammad-chat-body').prop('scrollHeight')) <= @scrollSnapTolerance
- @el.find('.zammad-scroll-hint').addClass('is-hidden') if @scrolledToBottom
- showScrollHint: ->
- @el.find('.zammad-scroll-hint').removeClass('is-hidden')
- # compensate scroll
- @el.find('.zammad-chat-body').scrollTop(@el.find('.zammad-chat-body').scrollTop() + @el.find('.zammad-scroll-hint').outerHeight())
- onScrollHintClick: =>
- # animate scroll
- @el.find('.zammad-chat-body').animate({scrollTop: @el.find('.zammad-chat-body').prop('scrollHeight')}, 300)
- scrollToBottom: ({ showHint } = { showHint: false }) ->
- if @scrolledToBottom
- @el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
- else if showHint
- @showScrollHint()
- destroy: (params = {}) =>
- @log.debug 'destroy widget', params
- @setAgentOnlineState 'offline'
- if params.remove && @el
- @el.remove()
- # Remove button, because it can no longer be used.
- $(".#{ @options.buttonClass }").hide()
- # stop all timer
- if @waitingListTimeout
- @waitingListTimeout.stop()
- if @inactiveTimeout
- @inactiveTimeout.stop()
- if @idleTimeout
- @idleTimeout.stop()
- # stop ws connection
- @io.close()
- reconnect: =>
- # set status to connecting
- @log.notice 'reconnecting'
- @disableInput()
- @lastAddedType = 'status'
- @setAgentOnlineState 'connecting'
- @addStatus @T('Connection lost')
- onConnectionReestablished: =>
- # set status back to online
- @lastAddedType = 'status'
- @setAgentOnlineState 'online'
- @addStatus @T('Connection re-established')
- @options.onConnectionReestablished?()
- onSessionClosed: (data) ->
- @addStatus @T('Chat closed by %s', data.realname)
- @disableInput()
- @setAgentOnlineState 'offline'
- @inactiveTimeout.stop()
- @options.onSessionClosed?(data)
- setSessionId: (id) =>
- @sessionId = id
- if id is undefined
- sessionStorage.removeItem 'sessionId'
- else
- sessionStorage.setItem 'sessionId', id
- onConnectionEstablished: (data) =>
- # stop delay of initial queue position
- if @onInitialQueueDelayId
- clearTimeout @onInitialQueueDelayId
- @inQueue = false
- if data.agent
- @agent = data.agent
- if data.session_id
- @setSessionId data.session_id
- # empty old messages
- @el.find('.zammad-chat-body').html('')
- @el.find('.zammad-chat-agent').html @view('agent')
- agent: @agent
- @enableInput()
- @hideModal()
- @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')
- @input.focus() if not @isFullscreen
- @setAgentOnlineState 'online'
- @waitingListTimeout.stop()
- @idleTimeout.stop()
- @inactiveTimeout.start()
- @options.onConnectionEstablished?(data)
- showCustomerTimeout: ->
- @el.find('.zammad-chat-modal').html @view('customer_timeout')
- agent: @agent.name
- delay: @options.inactiveTimeout
- reload = ->
- location.reload()
- @el.find('.js-restart').click reload
- @sessionClose()
- showWaitingListTimeout: ->
- @el.find('.zammad-chat-modal').html @view('waiting_list_timeout')
- delay: @options.watingListTimeout
- reload = ->
- location.reload()
- @el.find('.js-restart').click reload
- @sessionClose()
- showLoader: ->
- @el.find('.zammad-chat-modal').html @view('loader')()
- setAgentOnlineState: (state) =>
- @state = state
- return if !@el
- capitalizedState = state.charAt(0).toUpperCase() + state.slice(1)
- @el
- .find('.zammad-chat-agent-status')
- .attr('data-status', state)
- .text @T(capitalizedState)
- detectHost: ->
- protocol = 'ws://'
- if scriptProtocol is 'https'
- protocol = 'wss://'
- @options.host = "#{ protocol }#{ scriptHost }/ws"
- loadCss: ->
- return if !@options.cssAutoload
- url = @options.cssUrl
- if !url
- url = @options.host
- .replace(/^wss/i, 'https')
- .replace(/^ws/i, 'http')
- .replace(/\/ws$/i, '') # WebSocket may run on example.com/ws path
- url += '/assets/chat/chat.css'
- @log.debug "load css from '#{url}'"
- styles = "@import url('#{url}');"
- newSS = document.createElement('link')
- newSS.onload = @onCssLoaded
- newSS.rel = 'stylesheet'
- newSS.href = 'data:text/css,' + escape(styles)
- document.getElementsByTagName('head')[0].appendChild(newSS)
- onCssLoaded: =>
- @cssLoaded = true
- if @socketReady
- @onReady()
- @options.onCssLoaded?()
- startTimeoutObservers: =>
- @idleTimeout = new Timeout(
- logPrefix: 'idleTimeout'
- debug: @options.debug
- timeout: @options.idleTimeout
- timeoutIntervallCheck: @options.idleTimeoutIntervallCheck
- callback: =>
- @log.debug 'Idle timeout reached, hide widget', new Date
- @destroy(remove: true)
- )
- @inactiveTimeout = new Timeout(
- logPrefix: 'inactiveTimeout'
- debug: @options.debug
- timeout: @options.inactiveTimeout
- timeoutIntervallCheck: @options.inactiveTimeoutIntervallCheck
- callback: =>
- @log.debug 'Inactive timeout reached, show timeout screen.', new Date
- @showCustomerTimeout()
- @destroy(remove: false)
- )
- @waitingListTimeout = new Timeout(
- logPrefix: 'waitingListTimeout'
- debug: @options.debug
- timeout: @options.waitingListTimeout
- timeoutIntervallCheck: @options.waitingListTimeoutIntervallCheck
- callback: =>
- @log.debug 'Waiting list timeout reached, show timeout screen.', new Date
- @showWaitingListTimeout()
- @destroy(remove: false)
- )
- disableScrollOnRoot: ->
- @rootScrollOffset = @scrollRoot.scrollTop()
- @scrollRoot.css
- overflow: 'hidden'
- position: 'fixed'
- enableScrollOnRoot: ->
- @scrollRoot.scrollTop @rootScrollOffset
- @scrollRoot.css
- overflow: ''
- position: ''
- # based on https://github.com/customd/jquery-visible/blob/master/jquery.visible.js
- # to have not dependency, port to coffeescript
- isVisible: (el, partial, hidden, direction) ->
- return if el.length < 1
- $w = $(window)
- $t = if el.length > 1 then el.eq(0) else el
- t = $t.get(0)
- vpWidth = $w.width()
- vpHeight = $w.height()
- direction = if direction then direction else 'both'
- clientSize = if hidden is true then t.offsetWidth * t.offsetHeight else true
- if typeof t.getBoundingClientRect is 'function'
- # Use this native browser method, if available.
- rec = t.getBoundingClientRect()
- tViz = rec.top >= 0 && rec.top < vpHeight
- bViz = rec.bottom > 0 && rec.bottom <= vpHeight
- lViz = rec.left >= 0 && rec.left < vpWidth
- rViz = rec.right > 0 && rec.right <= vpWidth
- vVisible = if partial then tViz || bViz else tViz && bViz
- hVisible = if partial then lViz || rViz else lViz && rViz
- if direction is 'both'
- return clientSize && vVisible && hVisible
- else if direction is 'vertical'
- return clientSize && vVisible
- else if direction is 'horizontal'
- return clientSize && hVisible
- else
- viewTop = $w.scrollTop()
- viewBottom = viewTop + vpHeight
- viewLeft = $w.scrollLeft()
- viewRight = viewLeft + vpWidth
- offset = $t.offset()
- _top = offset.top
- _bottom = _top + $t.height()
- _left = offset.left
- _right = _left + $t.width()
- compareTop = if partial is true then _bottom else _top
- compareBottom = if partial is true then _top else _bottom
- compareLeft = if partial is true then _right else _left
- compareRight = if partial is true then _left else _right
- if direction is 'both'
- return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop)) && ((compareRight <= viewRight) && (compareLeft >= viewLeft))
- else if direction is 'vertical'
- return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop))
- else if direction is 'horizontal'
- return !!clientSize && ((compareRight <= viewRight) && (compareLeft >= viewLeft))
- isRetina: ->
- if window.matchMedia
- mq = window.matchMedia('only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 2.6/2), only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (min-device-pixel-ratio: 1.3), only screen and (min-resolution: 1.3dppx)')
- return (mq && mq.matches || (window.devicePixelRatio > 1))
- false
- resizeImage: (dataURL, x = 'auto', y = 'auto', sizeFactor = 1, type, quallity, callback, force = true) ->
- # load image from data url
- imageObject = new Image()
- imageObject.onload = ->
- imageWidth = imageObject.width
- imageHeight = imageObject.height
- console.log('ImageService', 'current size', imageWidth, imageHeight)
- if y is 'auto' && x is 'auto'
- x = imageWidth
- y = imageHeight
- # get auto dimensions
- if y is 'auto'
- factor = imageWidth / x
- y = imageHeight / factor
- if x is 'auto'
- factor = imageWidth / y
- x = imageHeight / factor
- # check if resize is needed
- resize = false
- if x < imageWidth || y < imageHeight
- resize = true
- x = x * sizeFactor
- y = y * sizeFactor
- else
- x = imageWidth
- y = imageHeight
- # create canvas and set dimensions
- canvas = document.createElement('canvas')
- canvas.width = x
- canvas.height = y
- # draw image on canvas and set image dimensions
- context = canvas.getContext('2d')
- context.drawImage(imageObject, 0, 0, x, y)
- # set quallity based on image size
- if quallity == 'auto'
- if x < 200 && y < 200
- quallity = 1
- else if x < 400 && y < 400
- quallity = 0.9
- else if x < 600 && y < 600
- quallity = 0.8
- else if x < 900 && y < 900
- quallity = 0.7
- else
- quallity = 0.6
- # execute callback with resized image
- newDataUrl = canvas.toDataURL(type, quallity)
- if resize
- console.log('ImageService', 'resize', x/sizeFactor, y/sizeFactor, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
- callback(newDataUrl, x/sizeFactor, y/sizeFactor, true)
- return
- console.log('ImageService', 'no resize', x, y, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
- callback(newDataUrl, x, y, false)
- # load image from data url
- imageObject.src = dataURL
- # taken from https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
- pasteHtmlAtCaret: (html) ->
- sel = undefined
- range = undefined
- if window.getSelection
- sel = window.getSelection()
- if sel.getRangeAt && sel.rangeCount
- range = sel.getRangeAt(0)
- range.deleteContents()
- el = document.createElement('div')
- el.innerHTML = html
- frag = document.createDocumentFragment(node, lastNode)
- while node = el.firstChild
- lastNode = frag.appendChild(node)
- range.insertNode(frag)
- if lastNode
- range = range.cloneRange()
- range.setStartAfter(lastNode)
- range.collapse(true)
- sel.removeAllRanges()
- sel.addRange(range)
- else if document.selection && document.selection.type != 'Control'
- document.selection.createRange().pasteHTML(html)
- # (C) sbrin - https://github.com/sbrin
- # https://gist.github.com/sbrin/6801034
- wordFilter: (editor) ->
- content = editor.html()
- # Word comments like conditional comments etc
- content = content.replace(/<!--[\s\S]+?-->/gi, '')
- # Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
- # MS Office namespaced tags, and a few other tags
- content = content.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '')
- # Convert <s> into <strike> for line-though
- content = content.replace(/<(\/?)s>/gi, '<$1strike>')
- # Replace nbsp entites to char since it's easier to handle
- # content = content.replace(/ /gi, "\u00a0")
- content = content.replace(/ /gi, ' ')
- # Convert <span style="mso-spacerun:yes">___</span> to string of alternating
- # breaking/non-breaking spaces of same length
- #content = content.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, (str, spaces) ->
- # return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ''
- #)
- editor.html(content)
- # Parse out list indent level for lists
- $('p', editor).each( ->
- str = $(@).attr('style')
- matches = /mso-list:\w+ \w+([0-9]+)/.exec(str)
- if matches
- $(@).data('_listLevel', parseInt(matches[1], 10))
- )
- # Parse Lists
- last_level = 0
- pnt = null
- $('p', editor).each(->
- cur_level = $(@).data('_listLevel')
- if cur_level != undefined
- txt = $(@).text()
- list_tag = '<ul></ul>'
- if (/^\s*\w+\./.test(txt))
- matches = /([0-9])\./.exec(txt)
- if matches
- start = parseInt(matches[1], 10)
- list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>'
- else
- list_tag = '<ol></ol>'
- if cur_level > last_level
- if last_level == 0
- $(@).before(list_tag)
- pnt = $(@).prev()
- else
- pnt = $(list_tag).appendTo(pnt)
- if cur_level < last_level
- for i in [i..last_level-cur_level]
- pnt = pnt.parent()
- $('span:first', @).remove()
- pnt.append('<li>' + $(@).html() + '</li>')
- $(@).remove()
- last_level = cur_level
- else
- last_level = 0
- )
- $('[style]', editor).removeAttr('style')
- $('[align]', editor).removeAttr('align')
- $('span', editor).replaceWith(->
- $(@).contents()
- )
- $('span:empty', editor).remove()
- $("[class^='Mso']", editor).removeAttr('class')
- $('p:empty', editor).remove()
- editor
- removeAttribute: (element) ->
- return if !element
- $element = $(element)
- for att in element.attributes
- if att && att.name
- element.removeAttribute(att.name)
- #$element.removeAttr(att.name)
- $element.removeAttr('style')
- .removeAttr('class')
- .removeAttr('lang')
- .removeAttr('type')
- .removeAttr('align')
- .removeAttr('id')
- .removeAttr('wrap')
- .removeAttr('title')
- removeAttributes: (html, parent = true) =>
- if parent
- html.each((index, element) => @removeAttribute(element) )
- html.find('*').each((index, element) => @removeAttribute(element) )
- html
- window.ZammadChat = ZammadChat
|