chat-no-jquery.coffee 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658
  1. do(window) ->
  2. scripts = document.getElementsByTagName('script')
  3. # search for script to get protocol and hostname for ws connection
  4. myScript = scripts[scripts.length - 1]
  5. scriptProtocol = window.location.protocol.replace(':', '') # set default protocol
  6. if myScript && myScript.src
  7. scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
  8. scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1]
  9. # Define the plugin class
  10. class Core
  11. defaults:
  12. debug: false
  13. constructor: (options) ->
  14. @options = {}
  15. for key, value of @defaults
  16. @options[key] = value
  17. for key, value of options
  18. @options[key] = value
  19. class Base extends Core
  20. constructor: (options) ->
  21. super(options)
  22. @log = new Log(debug: @options.debug, logPrefix: @options.logPrefix || @logPrefix)
  23. class Log extends Core
  24. debug: (items...) =>
  25. return if !@options.debug
  26. @log('debug', items)
  27. notice: (items...) =>
  28. @log('notice', items)
  29. error: (items...) =>
  30. @log('error', items)
  31. log: (level, items) =>
  32. items.unshift('||')
  33. items.unshift(level)
  34. items.unshift(@options.logPrefix)
  35. console.log.apply console, items
  36. return if !@options.debug
  37. logString = ''
  38. for item in items
  39. logString += ' '
  40. if typeof item is 'object'
  41. logString += JSON.stringify(item)
  42. else if item && item.toString
  43. logString += item.toString()
  44. else
  45. logString += item
  46. element = document.querySelector('.js-chatLogDisplay')
  47. if element
  48. element.innerHTML = '<div>' + logString + '</div>' + element.innerHTML
  49. class Timeout extends Base
  50. timeoutStartedAt: null
  51. logPrefix: 'timeout'
  52. defaults:
  53. debug: false
  54. timeout: 4
  55. timeoutIntervallCheck: 0.5
  56. start: =>
  57. @stop()
  58. timeoutStartedAt = new Date
  59. check = =>
  60. timeLeft = new Date - new Date(timeoutStartedAt.getTime() + @options.timeout * 1000 * 60)
  61. @log.debug "Timeout check for #{@options.timeout} minutes (left #{timeLeft/1000} sec.)"#, new Date
  62. return if timeLeft < 0
  63. @stop()
  64. @options.callback()
  65. @log.debug "Start timeout in #{@options.timeout} minutes"#, new Date
  66. @intervallId = setInterval(check, @options.timeoutIntervallCheck * 1000 * 60)
  67. stop: =>
  68. return if !@intervallId
  69. @log.debug "Stop timeout of #{@options.timeout} minutes"#, new Date
  70. clearInterval(@intervallId)
  71. class Io extends Base
  72. logPrefix: 'io'
  73. set: (params) =>
  74. for key, value of params
  75. @options[key] = value
  76. connect: =>
  77. @log.debug "Connecting to #{@options.host}"
  78. @ws = new window.WebSocket("#{@options.host}")
  79. @ws.onopen = (e) =>
  80. @log.debug 'onOpen', e
  81. @options.onOpen(e)
  82. @ping()
  83. @ws.onmessage = (e) =>
  84. pipes = JSON.parse(e.data)
  85. @log.debug 'onMessage', e.data
  86. for pipe in pipes
  87. if pipe.event is 'pong'
  88. @ping()
  89. if @options.onMessage
  90. @options.onMessage(pipes)
  91. @ws.onclose = (e) =>
  92. @log.debug 'close websocket connection', e
  93. if @pingDelayId
  94. clearTimeout(@pingDelayId)
  95. if @manualClose
  96. @log.debug 'manual close, onClose callback'
  97. @manualClose = false
  98. if @options.onClose
  99. @options.onClose(e)
  100. else
  101. @log.debug 'error close, onError callback'
  102. if @options.onError
  103. @options.onError('Connection lost...')
  104. @ws.onerror = (e) =>
  105. @log.debug 'onError', e
  106. if @options.onError
  107. @options.onError(e)
  108. close: =>
  109. @log.debug 'close websocket manually'
  110. @manualClose = true
  111. @ws.close()
  112. reconnect: =>
  113. @log.debug 'reconnect'
  114. @close()
  115. @connect()
  116. send: (event, data = {}) =>
  117. @log.debug 'send', event, data
  118. msg = JSON.stringify
  119. event: event
  120. data: data
  121. @ws.send msg
  122. ping: =>
  123. localPing = =>
  124. @send('ping')
  125. @pingDelayId = setTimeout(localPing, 29000)
  126. class ZammadChat extends Base
  127. defaults:
  128. chatId: undefined
  129. show: true
  130. target: document.querySelector('body')
  131. host: ''
  132. debug: false
  133. flat: false
  134. lang: undefined
  135. cssAutoload: true
  136. cssUrl: undefined
  137. fontSize: undefined
  138. buttonClass: 'open-zammad-chat'
  139. inactiveClass: 'is-inactive'
  140. title: '<strong>Chat</strong> with us!'
  141. scrollHint: 'Scroll down to see new messages'
  142. idleTimeout: 6
  143. idleTimeoutIntervallCheck: 0.5
  144. inactiveTimeout: 8
  145. inactiveTimeoutIntervallCheck: 0.5
  146. waitingListTimeout: 4
  147. waitingListTimeoutIntervallCheck: 0.5
  148. # Callbacks
  149. onReady: undefined
  150. onCloseAnimationEnd: undefined
  151. onError: undefined
  152. onOpenAnimationEnd: undefined
  153. onConnectionReestablished: undefined
  154. onSessionClosed: undefined
  155. onConnectionEstablished: undefined
  156. onCssLoaded: undefined
  157. logPrefix: 'chat'
  158. _messageCount: 0
  159. isOpen: false
  160. blinkOnlineInterval: null
  161. stopBlinOnlineStateTimeout: null
  162. showTimeEveryXMinutes: 2
  163. lastTimestamp: null
  164. lastAddedType: null
  165. inputDisabled: false
  166. inputTimeout: null
  167. isTyping: false
  168. state: 'offline'
  169. initialQueueDelay: 10000
  170. translations:
  171. 'da':
  172. '<strong>Chat</strong> with us!': '<strong>Chat</strong> med os!'
  173. 'Scroll down to see new messages': 'Scroll ned for at se nye beskeder'
  174. 'Online': 'Online'
  175. 'Offline': 'Offline'
  176. 'Connecting': 'Forbinder'
  177. 'Connection re-established': 'Forbindelse genoprettet'
  178. 'Today': 'I dag'
  179. 'Send': 'Send'
  180. 'Chat closed by %s': 'Chat lukket af %s'
  181. 'Compose your message…': 'Skriv en besked…'
  182. 'All colleagues are busy.': 'Alle kollegaer er optaget.'
  183. 'You are on waiting list position <strong>%s</strong>.': 'Du er i venteliste som nummer <strong>%s</strong>.'
  184. 'Start new conversation': 'Start en ny samtale'
  185. '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.'
  186. '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.'
  187. '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!'
  188. 'de':
  189. '<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!'
  190. 'Scroll down to see new messages': 'Scrolle nach unten um neue Nachrichten zu sehen'
  191. 'Online': 'Online'
  192. 'Offline': 'Offline'
  193. 'Connecting': 'Verbinden'
  194. 'Connection re-established': 'Verbindung wiederhergestellt'
  195. 'Today': 'Heute'
  196. 'Send': 'Senden'
  197. 'Chat closed by %s': 'Chat beendet von %s'
  198. 'Compose your message…': 'Ihre Nachricht…'
  199. 'All colleagues are busy.': 'Alle Kollegen sind belegt.'
  200. 'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.'
  201. 'Start new conversation': 'Neue Konversation starten'
  202. '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.'
  203. '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.'
  204. '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!'
  205. 'es':
  206. '<strong>Chat</strong> with us!': '<strong>Chatee</strong> con nosotros!'
  207. 'Scroll down to see new messages': 'Haga scroll hacia abajo para ver nuevos mensajes'
  208. 'Online': 'En linea'
  209. 'Offline': 'Desconectado'
  210. 'Connecting': 'Conectando'
  211. 'Connection re-established': 'Conexión restablecida'
  212. 'Today': 'Hoy'
  213. 'Send': 'Enviar'
  214. 'Chat closed by %s': 'Chat cerrado por %s'
  215. 'Compose your message…': 'Escriba su mensaje…'
  216. 'All colleagues are busy.': 'Todos los agentes están ocupados.'
  217. 'You are on waiting list position <strong>%s</strong>.': 'Usted está en la posición <strong>%s</strong> de la lista de espera.'
  218. 'Start new conversation': 'Iniciar nueva conversación'
  219. '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.'
  220. '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.'
  221. '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!'
  222. 'fi':
  223. '<strong>Chat</strong> with us!': '<strong>Keskustele</strong> kanssamme!'
  224. 'Scroll down to see new messages': 'Rullaa alas nähdäksesi uudet viestit'
  225. 'Online': 'Paikalla'
  226. 'Offline': 'Poissa'
  227. 'Connecting': 'Yhdistetään'
  228. 'Connection re-established': 'Yhteys muodostettu uudelleen'
  229. 'Today': 'Tänään'
  230. 'Send': 'Lähetä'
  231. 'Chat closed by %s': '%s sulki keskustelun'
  232. 'Compose your message…': 'Luo viestisi…'
  233. 'All colleagues are busy.': 'Kaikki kollegat ovat varattuja.'
  234. 'You are on waiting list position <strong>%s</strong>.': 'Olet odotuslistalla sijalla <strong>%s</strong>.'
  235. 'Start new conversation': 'Aloita uusi keskustelu'
  236. '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.'
  237. 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Koska et vastannut viimeiseen %s minuuttiin, keskustelusi suljettiin.'
  238. '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!'
  239. 'fr':
  240. '<strong>Chat</strong> with us!': '<strong>Chattez</strong> avec nous!'
  241. 'Scroll down to see new messages': 'Faites défiler pour lire les nouveaux messages'
  242. 'Online': 'En-ligne'
  243. 'Offline': 'Hors-ligne'
  244. 'Connecting': 'Connexion en cours'
  245. 'Connection re-established': 'Connexion rétablie'
  246. 'Today': 'Aujourdhui'
  247. 'Send': 'Envoyer'
  248. 'Chat closed by %s': 'Chat fermé par %s'
  249. 'Compose your message…': 'Composez votre message…'
  250. 'All colleagues are busy.': 'Tous les collaborateurs sont occupés actuellement.'
  251. 'You are on waiting list position <strong>%s</strong>.': 'Vous êtes actuellement en position <strong>%s</strong> dans la file d\'attente.'
  252. 'Start new conversation': 'Démarrer une nouvelle conversation'
  253. '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.'
  254. '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.'
  255. '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!'
  256. 'he':
  257. '<strong>Chat</strong> with us!': '<strong>שוחח</strong>איתנו!'
  258. 'Scroll down to see new messages': 'גלול מטה כדי לראות הודעות חדשות'
  259. 'Online': 'מחובר'
  260. 'Offline': 'מנותק'
  261. 'Connecting': 'מתחבר'
  262. 'Connection re-established': 'החיבור שוחזר'
  263. 'Today': 'היום'
  264. 'Send': 'שלח'
  265. 'Chat closed by %s': 'הצאט נסגר ע"י %s'
  266. 'Compose your message…': 'כתוב את ההודעה שלך …'
  267. 'All colleagues are busy.': 'כל הנציגים תפוסים'
  268. 'You are on waiting list position <strong>%s</strong>.': 'מיקומך בתור <strong>%s</strong>.'
  269. 'Start new conversation': 'התחל שיחה חדשה'
  270. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'מכיוון שלא הגבת במהלך %s דקות השיחה שלך עם <strong>%s</strong> נסגרה.'
  271. 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'מכיוון שלא הגבת במהלך %s הדקות האחרונות השיחה שלך נסגרה.'
  272. 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'מצטערים, הזמן לקבלת נציג ארוך מהרגיל. נסה שוב מאוחר יותר או שלח לנו דוא"ל. תודה!'
  273. 'hu':
  274. '<strong>Chat</strong> with us!': '<strong>Chatelj</strong> velünk!'
  275. 'Scroll down to see new messages': 'Görgess lejjebb az újabb üzenetekért'
  276. 'Online': 'Online'
  277. 'Offline': 'Offline'
  278. 'Connecting': 'Csatlakozás'
  279. 'Connection re-established': 'Újracsatlakozás'
  280. 'Today': 'Ma'
  281. 'Send': 'Küldés'
  282. 'Chat closed by %s': 'A beszélgetést lezárta %s'
  283. 'Compose your message…': 'Írj üzenetet…'
  284. 'All colleagues are busy.': 'Jelenleg minden kollégánk elfoglalt.'
  285. 'You are on waiting list position <strong>%s</strong>.': 'A várólistán a <strong>%s</strong>. pozícióban várakozol.'
  286. 'Start new conversation': 'Új beszélgetés indítása'
  287. '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.'
  288. '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.'
  289. '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!'
  290. 'nl':
  291. '<strong>Chat</strong> with us!': '<strong>Chat</strong> met ons!'
  292. 'Scroll down to see new messages': 'Scrol naar beneden om nieuwe berichten te zien'
  293. 'Online': 'Online'
  294. 'Offline': 'Offline'
  295. 'Connecting': 'Verbinden'
  296. 'Connection re-established': 'Verbinding herstelt'
  297. 'Today': 'Vandaag'
  298. 'Send': 'Verzenden'
  299. 'Chat closed by %s': 'Chat gesloten door %s'
  300. 'Compose your message…': 'Typ uw bericht…'
  301. 'All colleagues are busy.': 'Alle medewerkers zijn bezet.'
  302. 'You are on waiting list position <strong>%s</strong>.': 'U bent <strong>%s</strong> in de wachtrij.'
  303. 'Start new conversation': 'Nieuwe conversatie starten'
  304. '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.'
  305. '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.'
  306. '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!'
  307. 'it':
  308. '<strong>Chat</strong> with us!': '<strong>Chatta</strong> con noi!'
  309. 'Scroll down to see new messages': 'Scorrere verso il basso per vedere i nuovi messaggi'
  310. 'Online': 'Online'
  311. 'Offline': 'Offline'
  312. 'Connecting': 'Collegamento'
  313. 'Connection re-established': 'Collegamento ristabilito'
  314. 'Today': 'Oggi'
  315. 'Send': 'Invio'
  316. 'Chat closed by %s': 'Conversazione chiusa da %s'
  317. 'Compose your message…': 'Comporre il tuo messaggio…'
  318. 'All colleagues are busy.': 'Tutti i colleghi sono occupati.'
  319. 'You are on waiting list position <strong>%s</strong>.': 'Siete in posizione lista d\' attesa <strong>%s</strong>.'
  320. 'Start new conversation': 'Avviare una nuova conversazione'
  321. '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 conversazione con <strong>%s</strong> si è chiusa.'
  322. '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 conversazione si è chiusa.'
  323. '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 come previsto per ottenere uno slot vuoto. Per favore riprova più tardi o inviaci un\' e-mail. Grazie!'
  324. 'pl':
  325. '<strong>Chat</strong> with us!': '<strong>Czatuj</strong> z nami!'
  326. 'Scroll down to see new messages': 'Przewiń w dół, aby wyświetlić nowe wiadomości'
  327. 'Online': 'Online'
  328. 'Offline': 'Offline'
  329. 'Connecting': 'Łączenie'
  330. 'Connection re-established': 'Ponowne nawiązanie połączenia'
  331. 'Today': 'dzisiejszy'
  332. 'Send': 'Wyślij'
  333. 'Chat closed by %s': 'Czat zamknięty przez %s'
  334. 'Compose your message…': 'Utwórz swoją wiadomość…'
  335. 'All colleagues are busy.': 'Wszyscy konsultanci są zajęci.'
  336. 'You are on waiting list position <strong>%s</strong>.': 'Na liście oczekujących znajduje się pozycja <strong>%s</strong>.'
  337. 'Start new conversation': 'Rozpoczęcie nowej konwersacji'
  338. '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.'
  339. '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.'
  340. '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ę!'
  341. 'pt-br': {
  342. '<strong>Chat</strong> with us!': '<strong>Chat</strong> fale conosco!',
  343. 'Scroll down to see new messages': 'Role para baixo, para ver nosvas mensagens',
  344. 'Online': 'Online',
  345. 'Offline': 'Desconectado',
  346. 'Connecting': 'Conectando',
  347. 'Connection re-established': 'Conexão restabelecida',
  348. 'Today': 'Hoje',
  349. 'Send': 'Enviar',
  350. 'Chat closed by %s': 'Chat encerrado por %s',
  351. 'Compose your message…': 'Escreva sua mensagem…',
  352. 'All colleagues are busy.': 'Todos os agentes estão ocupados.',
  353. 'You are on waiting list position <strong>%s</strong>.': 'Você está na posição <strong>%s</strong> na fila de espera.',
  354. 'Start new conversation': 'Iniciar uma nova conversa',
  355. '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.',
  356. '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.',
  357. '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'
  358. },
  359. 'zh-cn':
  360. '<strong>Chat</strong> with us!': '发起<strong>即时对话</strong>!'
  361. 'Scroll down to see new messages': '向下滚动以查看新消息'
  362. 'Online': '在线'
  363. 'Offline': '离线'
  364. 'Connecting': '连接中'
  365. 'Connection re-established': '正在重新建立连接'
  366. 'Today': '今天'
  367. 'Send': '发送'
  368. 'Chat closed by %s': 'Chat closed by %s'
  369. 'Compose your message…': '正在输入信息…'
  370. 'All colleagues are busy.': '所有工作人员都在忙碌中.'
  371. 'You are on waiting list position <strong>%s</strong>.': '您目前的等候位置是第 <strong>%s</strong> 位.'
  372. 'Start new conversation': '开始新的会话'
  373. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': '由于您超过 %s 分钟没有回复, 您与 <strong>%s</strong> 的会话已被关闭.'
  374. 'Since you didn\'t respond in the last %s minutes your conversation got closed.': '由于您超过 %s 分钟没有任何回复, 该对话已被关闭.'
  375. 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': '非常抱歉, 目前需要等候更长的时间才能接入对话, 请稍后重试或向我们发送电子邮件. 谢谢!'
  376. 'zh-tw':
  377. '<strong>Chat</strong> with us!': '開始<strong>即時對话</strong>!'
  378. 'Scroll down to see new messages': '向下滑動以查看新訊息'
  379. 'Online': '線上'
  380. 'Offline': '离线'
  381. 'Connecting': '連線中'
  382. 'Connection re-established': '正在重新建立連線中'
  383. 'Today': '今天'
  384. 'Send': '發送'
  385. 'Chat closed by %s': 'Chat closed by %s'
  386. 'Compose your message…': '正在輸入訊息…'
  387. 'All colleagues are busy.': '所有服務人員都在忙碌中.'
  388. 'You are on waiting list position <strong>%s</strong>.': '你目前的等候位置是第 <strong>%s</strong> 順位.'
  389. 'Start new conversation': '開始新的對話'
  390. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': '由於你超過 %s 分鐘沒有回應, 你與 <strong>%s</strong> 的對話已被關閉.'
  391. 'Since you didn\'t respond in the last %s minutes your conversation got closed.': '由於你超過 %s 分鐘沒有任何回應, 該對話已被關閉.'
  392. 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': '非常抱歉, 當前需要等候更長的時間方可排入對話程序, 請稍後重試或向我們寄送電子郵件. 謝謝!'
  393. 'ru':
  394. '<strong>Chat</strong> with us!': 'Напишите нам!'
  395. 'Scroll down to see new messages': 'Прокрутите, чтобы увидеть новые сообщения'
  396. 'Online': 'Онлайн'
  397. 'Offline': 'Оффлайн'
  398. 'Connecting': 'Подключение'
  399. 'Connection re-established': 'Подключение восстановлено'
  400. 'Today': 'Сегодня'
  401. 'Send': 'Отправить'
  402. 'Chat closed by %s': '%s закрыл чат'
  403. 'Compose your message…': 'Напишите сообщение…'
  404. 'All colleagues are busy.': 'Все сотрудники заняты'
  405. 'You are on waiting list position <strong>%s</strong>.': 'Вы в списке ожидания под номером <strong>%s</strong>'
  406. 'Start new conversation': 'Начать новую переписку.'
  407. 'Since you didn\'t respond in the last %s minutes your conversation with %s got closed.': 'Поскольку вы не отвечали в течение последних %s минут, ваш разговор с %s был закрыт.'
  408. 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Поскольку вы не отвечали в течение последних %s минут, ваш разговор был закрыт.'
  409. 'We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!': 'К сожалению, ожидание свободного места требует больше времени. Повторите попытку позже или отправьте нам электронное письмо. Спасибо!'
  410. 'sv':
  411. '<strong>Chat</strong> with us!': '<strong>Chatta</strong> med oss!'
  412. 'Scroll down to see new messages': 'Rulla ner för att se nya meddelanden'
  413. 'Online': 'Online'
  414. 'Offline': 'Offline'
  415. 'Connecting': 'Ansluter'
  416. 'Connection re-established': 'Anslutningen återupprättas'
  417. 'Today': 'I dag'
  418. 'Send': 'Skicka'
  419. 'Chat closed by %s': 'Chatt stängd av %s'
  420. 'Compose your message…': 'Skriv ditt meddelande…'
  421. 'All colleagues are busy.': 'Alla kollegor är upptagna.'
  422. 'You are on waiting list position <strong>%s</strong>.': 'Du är på väntelistan som position <strong>%s</strong>.'
  423. 'Start new conversation': 'Starta ny konversation'
  424. '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.'
  425. '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.'
  426. '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!'
  427. 'no':
  428. '<strong>Chat</strong> with us!': '<strong>Chat</strong> med oss!'
  429. 'Scroll down to see new messages': 'Bla ned for å se nye meldinger'
  430. 'Online': 'Pålogget'
  431. 'Offline': 'Avlogget'
  432. 'Connecting': 'Koble til'
  433. 'Connection re-established': 'Tilkoblingen er gjenopprettet'
  434. 'Today': 'I dag'
  435. 'Send': 'Send'
  436. 'Chat closed by %s': 'Chat avsluttes om %s'
  437. 'Compose your message…': 'Skriv din melding…'
  438. 'All colleagues are busy.': 'Alle våre kolleger er for øyeblikket opptatt.'
  439. 'You are on waiting list position <strong>%s</strong>.': 'Du står nå i kø og er nr. <strong>%s</strong> på ventelisten.'
  440. 'Start new conversation': 'Start en ny samtale'
  441. '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.'
  442. '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.'
  443. '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!'
  444. 'nb':
  445. '<strong>Chat</strong> with us!': '<strong>Chat</strong> med oss!'
  446. 'Scroll down to see new messages': 'Bla ned for å se nye meldinger'
  447. 'Online': 'Pålogget'
  448. 'Offline': 'Avlogget'
  449. 'Connecting': 'Koble til'
  450. 'Connection re-established': 'Tilkoblingen er gjenopprettet'
  451. 'Today': 'I dag'
  452. 'Send': 'Send'
  453. 'Chat closed by %s': 'Chat avsluttes om %s'
  454. 'Compose your message…': 'Skriv din melding…'
  455. 'All colleagues are busy.': 'Alle våre kolleger er for øyeblikket opptatt.'
  456. 'You are on waiting list position <strong>%s</strong>.': 'Du står nå i kø og er nr. <strong>%s</strong> på ventelisten.'
  457. 'Start new conversation': 'Start en ny samtale'
  458. '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.'
  459. '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.'
  460. '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!'
  461. 'el':
  462. '<strong>Chat</strong> with us!': '<strong>Επικοινωνήστε</strong> μαζί μας!'
  463. 'Scroll down to see new messages': 'Μεταβείτε κάτω για να δείτε τα νέα μηνύματα'
  464. 'Online': 'Σε σύνδεση'
  465. 'Offline': 'Αποσυνδεμένος'
  466. 'Connecting': 'Σύνδεση'
  467. 'Connection re-established': 'Η σύνδεση αποκαταστάθηκε'
  468. 'Today': 'Σήμερα'
  469. 'Send': 'Αποστολή'
  470. 'Chat closed by %s': 'Η συνομιλία έκλεισε από τον/την %s'
  471. 'Compose your message…': 'Γράψτε το μήνυμα σας…'
  472. 'All colleagues are busy.': 'Όλοι οι συνάδελφοι μας είναι απασχολημένοι.'
  473. 'You are on waiting list position <strong>%s</strong>.': 'Βρίσκεστε σε λίστα αναμονής στη θέση <strong>%s</strong>.'
  474. 'Start new conversation': 'Έναρξη νέας συνομιλίας'
  475. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας με τον/την <strong>%s</strong> έκλεισε.'
  476. 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας έκλεισε.'
  477. '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. Ευχαριστούμε!'
  478. sessionId: undefined
  479. scrolledToBottom: true
  480. scrollSnapTolerance: 10
  481. richTextFormatKey:
  482. 66: true # b
  483. 73: true # i
  484. 85: true # u
  485. 83: true # s
  486. T: (string, items...) =>
  487. if @options.lang && @options.lang isnt 'en'
  488. if !@translations[@options.lang]
  489. @log.notice "Translation '#{@options.lang}' needed!"
  490. else
  491. translations = @translations[@options.lang]
  492. if !translations[string]
  493. @log.notice "Translation needed for '#{string}'"
  494. string = translations[string] || string
  495. if items
  496. for item in items
  497. string = string.replace(/%s/, item)
  498. string
  499. view: (name) =>
  500. return (options) =>
  501. if !options
  502. options = {}
  503. options.T = @T
  504. options.background = @options.background
  505. options.flat = @options.flat
  506. options.fontSize = @options.fontSize
  507. return window.zammadChatTemplates[name](options)
  508. constructor: (options) ->
  509. super(options)
  510. # jQuery migration
  511. if typeof jQuery != 'undefined' && @options.target instanceof jQuery
  512. @log.notice 'Chat: target option is a jQuery object. jQuery is not a requirement for the chat any more.'
  513. @options.target = @options.target.get(0)
  514. # fullscreen
  515. @isFullscreen = (window.matchMedia and window.matchMedia('(max-width: 768px)').matches)
  516. @scrollRoot = @getScrollRoot()
  517. # check prerequisites
  518. if !window.WebSocket or !sessionStorage
  519. @state = 'unsupported'
  520. @log.notice 'Chat: Browser not supported!'
  521. return
  522. if !@options.chatId
  523. @state = 'unsupported'
  524. @log.error 'Chat: need chatId as option!'
  525. return
  526. # detect language
  527. if !@options.lang
  528. @options.lang = document.documentElement.getAttribute('lang')
  529. if @options.lang
  530. if !@translations[@options.lang]
  531. @log.debug "lang: No #{@options.lang} found, try first two letters"
  532. @options.lang = @options.lang.replace(/-.+?$/, '') # replace "-xx" of xx-xx
  533. @log.debug "lang: #{@options.lang}"
  534. # detect host
  535. @detectHost() if !@options.host
  536. @loadCss()
  537. @io = new Io(@options)
  538. @io.set(
  539. onOpen: @render
  540. onClose: @onWebSocketClose
  541. onMessage: @onWebSocketMessage
  542. onError: @onError
  543. )
  544. @io.connect()
  545. getScrollRoot: ->
  546. return document.scrollingElement if 'scrollingElement' of document
  547. html = document.documentElement
  548. start = parseInt(html.pageYOffset, 10)
  549. html.pageYOffset = start + 1
  550. end = parseInt(html.pageYOffset, 10)
  551. html.pageYOffset = start
  552. return if end > start then html else document.body
  553. render: =>
  554. if !@el || !document.querySelector('.zammad-chat')
  555. @renderBase()
  556. # disable open button
  557. btn = document.querySelector(".#{ @options.buttonClass }")
  558. if btn
  559. btn.classList.add @options.inactiveClass
  560. @setAgentOnlineState 'online'
  561. @log.debug 'widget rendered'
  562. @startTimeoutObservers()
  563. @idleTimeout.start()
  564. # get current chat status
  565. @sessionId = sessionStorage.getItem('sessionId')
  566. @send 'chat_status_customer',
  567. session_id: @sessionId
  568. url: window.location.href
  569. renderBase: ->
  570. @el.remove() if @el
  571. @options.target.insertAdjacentHTML('beforeend', @view('chat')(
  572. title: @options.title,
  573. scrollHint: @options.scrollHint
  574. ))
  575. @el = @options.target.querySelector('.zammad-chat')
  576. @input = @el.querySelector('.zammad-chat-input')
  577. @body = @el.querySelector('.zammad-chat-body')
  578. # start bindings
  579. @el.querySelector('.js-chat-open').addEventListener('click', @open)
  580. @el.querySelector('.js-chat-toggle').addEventListener('click', @toggle)
  581. @el.querySelector('.js-chat-status').addEventListener('click', @stopPropagation)
  582. @el.querySelector('.zammad-chat-controls').addEventListener('submit', @onSubmit)
  583. @body.addEventListener('scroll', @detectScrolledtoBottom)
  584. @el.querySelector('.zammad-scroll-hint').addEventListener('click', @onScrollHintClick)
  585. @input.addEventListener('keydown', @onKeydown)
  586. @input.addEventListener('input', @onInput)
  587. @input.addEventListener('paste', @onPaste)
  588. @input.addEventListener('drop', @onDrop)
  589. window.addEventListener('beforeunload', @onLeaveTemporary)
  590. window.addEventListener('hashchange', =>
  591. if @isOpen
  592. if @sessionId
  593. @send 'chat_session_notice',
  594. session_id: @sessionId
  595. message: window.location.href
  596. return
  597. @idleTimeout.start()
  598. )
  599. stopPropagation: (event) ->
  600. event.stopPropagation()
  601. onDrop: (e) =>
  602. e.stopPropagation()
  603. e.preventDefault()
  604. if window.dataTransfer # ie
  605. dataTransfer = window.dataTransfer
  606. else if e.dataTransfer # other browsers
  607. dataTransfer = e.dataTransfer
  608. else
  609. throw 'No clipboardData support'
  610. x = e.clientX
  611. y = e.clientY
  612. file = dataTransfer.files[0]
  613. # look for images
  614. if file.type.match('image.*')
  615. reader = new FileReader()
  616. reader.onload = (e) =>
  617. # Insert the image at the carat
  618. insert = (dataUrl, width) =>
  619. # adapt image if we are on retina devices
  620. if @isRetina()
  621. width = width / 2
  622. result = dataUrl
  623. img = new Image()
  624. img.style.width = '100%'
  625. img.style.maxWidth = width + 'px'
  626. img.src = result
  627. if document.caretPositionFromPoint
  628. pos = document.caretPositionFromPoint(x, y)
  629. range = document.createRange()
  630. range.setStart(pos.offsetNode, pos.offset)
  631. range.collapse()
  632. range.insertNode(img)
  633. else if document.caretRangeFromPoint
  634. range = document.caretRangeFromPoint(x, y)
  635. range.insertNode(img)
  636. else
  637. console.log('could not find carat')
  638. # resize if to big
  639. @resizeImage(e.target.result, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
  640. reader.readAsDataURL(file)
  641. onPaste: (e) =>
  642. e.stopPropagation()
  643. e.preventDefault()
  644. if e.clipboardData
  645. clipboardData = e.clipboardData
  646. else if window.clipboardData
  647. clipboardData = window.clipboardData
  648. else if e.clipboardData
  649. clipboardData = e.clipboardData
  650. else
  651. throw 'No clipboardData support'
  652. imageInserted = false
  653. if clipboardData && clipboardData.items && clipboardData.items[0]
  654. item = clipboardData.items[0]
  655. if item.kind == 'file' && (item.type == 'image/png' || item.type == 'image/jpeg')
  656. imageFile = item.getAsFile()
  657. reader = new FileReader()
  658. reader.onload = (e) =>
  659. insert = (dataUrl, width) =>
  660. # adapt image if we are on retina devices
  661. if @isRetina()
  662. width = width / 2
  663. img = new Image()
  664. img.style.width = '100%'
  665. img.style.maxWidth = width + 'px'
  666. img.src = dataUrl
  667. document.execCommand('insertHTML', false, img)
  668. # resize if to big
  669. @resizeImage(e.target.result, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
  670. reader.readAsDataURL(imageFile)
  671. imageInserted = true
  672. return if imageInserted
  673. # check existing + paste text for limit
  674. text = undefined
  675. docType = undefined
  676. try
  677. text = clipboardData.getData('text/html')
  678. docType = 'html'
  679. if !text || text.length is 0
  680. docType = 'text'
  681. text = clipboardData.getData('text/plain')
  682. if !text || text.length is 0
  683. docType = 'text2'
  684. text = clipboardData.getData('text')
  685. catch e
  686. console.log('Sorry, can\'t insert markup because browser is not supporting it.')
  687. docType = 'text3'
  688. text = clipboardData.getData('text')
  689. if docType is 'text' || docType is 'text2' || docType is 'text3'
  690. text = '<div>' + text.replace(/\n/g, '</div><div>') + '</div>'
  691. text = text.replace(/<div><\/div>/g, '<div><br></div>')
  692. console.log('p', docType, text)
  693. if docType is 'html'
  694. html = document.createElement('div')
  695. # can't log because might contain malicious content
  696. # @log.debug 'HTML clipboard', text
  697. sanitized = DOMPurify.sanitize(text)
  698. @log.debug 'sanitized HTML clipboard', sanitized
  699. html.innerHTML = sanitized
  700. match = false
  701. htmlTmp = text
  702. regex = new RegExp('<(/w|w)\:[A-Za-z]')
  703. if htmlTmp.match(regex)
  704. match = true
  705. htmlTmp = htmlTmp.replace(regex, '')
  706. regex = new RegExp('<(/o|o)\:[A-Za-z]')
  707. if htmlTmp.match(regex)
  708. match = true
  709. htmlTmp = htmlTmp.replace(regex, '')
  710. if match
  711. html = @wordFilter(html)
  712. #html
  713. for node in html.childNodes
  714. if node.nodeType == 8
  715. node.remove()
  716. # remove tags, keep content
  717. for node in html.querySelectorAll('a, font, small, time, form, label')
  718. node.outerHTML = node.innerHTML
  719. # replace tags with generic div
  720. # New type of the tag
  721. replacementTag = 'div';
  722. # Replace all x tags with the type of replacementTag
  723. for node in html.querySelectorAll('textarea')
  724. outer = node.outerHTML
  725. # Replace opening tag
  726. regex = new RegExp('<' + node.tagName, 'i')
  727. newTag = outer.replace(regex, '<' + replacementTag)
  728. # Replace closing tag
  729. regex = new RegExp('</' + node.tagName, 'i')
  730. newTag = newTag.replace(regex, '</' + replacementTag)
  731. node.outerHTML = newTag
  732. # remove tags & content
  733. for node in html.querySelectorAll('font, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head, fieldset')
  734. node.remove()
  735. @removeAttributes(html)
  736. text = html.innerHTML
  737. # as fallback, insert html via pasteHtmlAtCaret (for IE 11 and lower)
  738. if docType is 'text3'
  739. @pasteHtmlAtCaret(text)
  740. else
  741. document.execCommand('insertHTML', false, text)
  742. true
  743. onKeydown: (e) =>
  744. # check for enter
  745. if not @inputDisabled and not e.shiftKey and e.keyCode is 13
  746. e.preventDefault()
  747. @sendMessage()
  748. richtTextControl = false
  749. if !e.altKey && !e.ctrlKey && e.metaKey
  750. richtTextControl = true
  751. else if !e.altKey && e.ctrlKey && !e.metaKey
  752. richtTextControl = true
  753. if richtTextControl && @richTextFormatKey[ e.keyCode ]
  754. e.preventDefault()
  755. if e.keyCode is 66
  756. document.execCommand('bold')
  757. return true
  758. if e.keyCode is 73
  759. document.execCommand('italic')
  760. return true
  761. if e.keyCode is 85
  762. document.execCommand('underline')
  763. return true
  764. if e.keyCode is 83
  765. document.execCommand('strikeThrough')
  766. return true
  767. send: (event, data = {}) =>
  768. data.chat_id = @options.chatId
  769. @io.send(event, data)
  770. onWebSocketMessage: (pipes) =>
  771. for pipe in pipes
  772. @log.debug 'ws:onmessage', pipe
  773. switch pipe.event
  774. when 'chat_error'
  775. @log.notice pipe.data
  776. if pipe.data && pipe.data.state is 'chat_disabled'
  777. @destroy(remove: true)
  778. when 'chat_session_message'
  779. return if pipe.data.self_written
  780. @receiveMessage pipe.data
  781. when 'chat_session_typing'
  782. return if pipe.data.self_written
  783. @onAgentTypingStart()
  784. when 'chat_session_start'
  785. @onConnectionEstablished pipe.data
  786. when 'chat_session_queue'
  787. @onQueueScreen pipe.data
  788. when 'chat_session_closed'
  789. @onSessionClosed pipe.data
  790. when 'chat_session_left'
  791. @onSessionClosed pipe.data
  792. when 'chat_status_customer'
  793. switch pipe.data.state
  794. when 'online'
  795. @sessionId = undefined
  796. if !@options.cssAutoload || @cssLoaded
  797. @onReady()
  798. else
  799. @socketReady = true
  800. when 'offline'
  801. @onError 'Zammad Chat: No agent online'
  802. when 'chat_disabled'
  803. @onError 'Zammad Chat: Chat is disabled'
  804. when 'no_seats_available'
  805. @onError "Zammad Chat: Too many clients in queue. Clients in queue: #{pipe.data.queue}"
  806. when 'reconnect'
  807. @onReopenSession pipe.data
  808. onReady: ->
  809. @log.debug 'widget ready for use'
  810. btn = document.querySelector(".#{ @options.buttonClass }")
  811. if btn
  812. btn.addEventListener('click', @open)
  813. btn.classList.remove(@options.inactiveClass)
  814. @options.onReady?()
  815. if @options.show
  816. @show()
  817. onError: (message) =>
  818. @log.debug message
  819. @addStatus(message)
  820. btn = document.querySelector(".#{ @options.buttonClass }")
  821. if btn
  822. btn.classList.add('zammad-chat-is-hidden')
  823. if @isOpen
  824. @disableInput()
  825. @destroy(remove: false)
  826. else
  827. @destroy(remove: true)
  828. @options.onError?(message)
  829. onReopenSession: (data) =>
  830. @log.debug 'old messages', data.session
  831. @inactiveTimeout.start()
  832. unfinishedMessage = sessionStorage.getItem 'unfinished_message'
  833. # rerender chat history
  834. if data.agent
  835. @onConnectionEstablished(data)
  836. for message in data.session
  837. @renderMessage
  838. message: message.content
  839. id: message.id
  840. from: if message.created_by_id then 'agent' else 'customer'
  841. if unfinishedMessage
  842. @input.innerHTML = unfinishedMessage
  843. # show wait list
  844. if data.position
  845. @onQueue data
  846. @show()
  847. @open()
  848. @scrollToBottom()
  849. if unfinishedMessage
  850. @input.focus()
  851. onInput: =>
  852. # remove unread-state from messages
  853. for message in @el.querySelectorAll('.zammad-chat-message--unread')
  854. message.classList.remove 'zammad-chat-message--unread'
  855. sessionStorage.setItem 'unfinished_message', @input.innerHTML
  856. @onTyping()
  857. onTyping: ->
  858. # send typing start event only every 1.5 seconds
  859. return if @isTyping && @isTyping > new Date(new Date().getTime() - 1500)
  860. @isTyping = new Date()
  861. @send 'chat_session_typing',
  862. session_id: @sessionId
  863. @inactiveTimeout.start()
  864. onSubmit: (event) =>
  865. event.preventDefault()
  866. @sendMessage()
  867. sendMessage: ->
  868. message = @input.innerHTML
  869. return if !message
  870. @inactiveTimeout.start()
  871. sessionStorage.removeItem 'unfinished_message'
  872. messageElement = @view('message')
  873. message: message
  874. from: 'customer'
  875. id: @_messageCount++
  876. unreadClass: ''
  877. @maybeAddTimestamp()
  878. # add message before message typing loader
  879. if @el.querySelector('.zammad-chat-message--typing')
  880. @lastAddedType = 'typing-placeholder'
  881. @el.querySelector('.zammad-chat-message--typing').insertAdjacentHTML('beforebegin', messageElement)
  882. else
  883. @lastAddedType = 'message--customer'
  884. @body.insertAdjacentHTML('beforeend', messageElement)
  885. @input.innerHTML = ''
  886. @scrollToBottom()
  887. # send message event
  888. @send 'chat_session_message',
  889. content: message
  890. id: @_messageCount
  891. session_id: @sessionId
  892. receiveMessage: (data) =>
  893. @inactiveTimeout.start()
  894. # hide writing indicator
  895. @onAgentTypingEnd()
  896. @maybeAddTimestamp()
  897. @renderMessage
  898. message: data.message.content
  899. id: data.id
  900. from: 'agent'
  901. @scrollToBottom showHint: true
  902. renderMessage: (data) =>
  903. @lastAddedType = "message--#{ data.from }"
  904. data.unreadClass = if document.hidden then ' zammad-chat-message--unread' else ''
  905. @body.insertAdjacentHTML('beforeend', @view('message')(data))
  906. open: =>
  907. if @isOpen
  908. @log.debug 'widget already open, block'
  909. return
  910. @isOpen = true
  911. @log.debug 'open widget'
  912. @show()
  913. if !@sessionId
  914. @showLoader()
  915. @el.classList.add 'zammad-chat-is-open'
  916. remainerHeight = @el.clientHeight - @el.querySelector('.zammad-chat-header').offsetHeight
  917. @el.style.transform = "translateY(#{remainerHeight}px)"
  918. # force redraw
  919. @el.clientHeight
  920. if !@sessionId
  921. @el.addEventListener 'transitionend', @onOpenAnimationEnd
  922. @el.classList.add 'zammad-chat--animate'
  923. # force redraw
  924. @el.clientHeight
  925. # start animation
  926. @el.style.transform = ''
  927. @send('chat_session_init'
  928. url: window.location.href
  929. )
  930. else
  931. @el.style.transform = ''
  932. @onOpenAnimationEnd()
  933. onOpenAnimationEnd: =>
  934. @el.removeEventListener 'transitionend', @onOpenAnimationEnd
  935. @el.classList.remove 'zammad-chat--animate'
  936. @idleTimeout.stop()
  937. if @isFullscreen
  938. @disableScrollOnRoot()
  939. @options.onOpenAnimationEnd?()
  940. sessionClose: =>
  941. # send close
  942. @send 'chat_session_close',
  943. session_id: @sessionId
  944. # stop timer
  945. @inactiveTimeout.stop()
  946. @waitingListTimeout.stop()
  947. # delete input store
  948. sessionStorage.removeItem 'unfinished_message'
  949. # stop delay of initial queue position
  950. if @onInitialQueueDelayId
  951. clearTimeout(@onInitialQueueDelayId)
  952. @setSessionId undefined
  953. toggle: (event) =>
  954. if @isOpen
  955. @close(event)
  956. else
  957. @open(event)
  958. close: (event) =>
  959. if !@isOpen
  960. @log.debug 'can\'t close widget, it\'s not open'
  961. return
  962. if @initDelayId
  963. clearTimeout(@initDelayId)
  964. if @sessionId
  965. @log.debug 'session close before widget close'
  966. @sessionClose()
  967. @log.debug 'close widget'
  968. event.stopPropagation() if event
  969. if @isFullscreen
  970. @enableScrollOnRoot()
  971. # close window
  972. remainerHeight = @el.clientHeight - @el.querySelector('.zammad-chat-header').offsetHeight
  973. @el.addEventListener 'transitionend', @onCloseAnimationEnd
  974. @el.classList.add 'zammad-chat--animate'
  975. # force redraw
  976. document.offsetHeight
  977. # animate out
  978. @el.style.transform = "translateY(#{remainerHeight}px)"
  979. onCloseAnimationEnd: =>
  980. @el.removeEventListener 'transitionend', @onCloseAnimationEnd
  981. @el.classList.remove 'zammad-chat-is-open', 'zammad-chat--animate'
  982. @el.style.transform = ''
  983. @showLoader()
  984. @el.querySelector('.zammad-chat-welcome').classList.remove('zammad-chat-is-hidden')
  985. @el.querySelector('.zammad-chat-agent').classList.add('zammad-chat-is-hidden')
  986. @el.querySelector('.zammad-chat-agent-status').classList.add('zammad-chat-is-hidden')
  987. @isOpen = false
  988. @options.onCloseAnimationEnd?()
  989. @io.reconnect()
  990. onWebSocketClose: =>
  991. return if @isOpen
  992. if @el
  993. @el.classList.remove('zammad-chat-is-shown')
  994. @el.classList.remove('zammad-chat-is-loaded')
  995. show: ->
  996. return if @state is 'offline'
  997. @el.classList.add('zammad-chat-is-loaded')
  998. @el.classList.add('zammad-chat-is-shown')
  999. disableInput: ->
  1000. @inputDisabled = true
  1001. @input.setAttribute('contenteditable', false)
  1002. @el.querySelector('.zammad-chat-send').disabled = true
  1003. @io.close()
  1004. enableInput: ->
  1005. @inputDisabled = false
  1006. @input.setAttribute('contenteditable', true)
  1007. @el.querySelector('.zammad-chat-send').disabled = false
  1008. hideModal: ->
  1009. @el.querySelector('.zammad-chat-modal').innerHTML = ''
  1010. onQueueScreen: (data) =>
  1011. @setSessionId data.session_id
  1012. # delay initial queue position, show connecting first
  1013. show = =>
  1014. @onQueue data
  1015. @waitingListTimeout.start()
  1016. if @initialQueueDelay && !@onInitialQueueDelayId
  1017. @onInitialQueueDelayId = setTimeout(show, @initialQueueDelay)
  1018. return
  1019. # stop delay of initial queue position
  1020. if @onInitialQueueDelayId
  1021. clearTimeout(@onInitialQueueDelayId)
  1022. # show queue position
  1023. show()
  1024. onQueue: (data) =>
  1025. @log.notice 'onQueue', data.position
  1026. @inQueue = true
  1027. @el.querySelector('.zammad-chat-modal').innerHTML = @view('waiting')
  1028. position: data.position
  1029. onAgentTypingStart: =>
  1030. if @stopTypingId
  1031. clearTimeout(@stopTypingId)
  1032. @stopTypingId = setTimeout(@onAgentTypingEnd, 3000)
  1033. # never display two typing indicators
  1034. return if @el.querySelector('.zammad-chat-message--typing')
  1035. @maybeAddTimestamp()
  1036. @body.insertAdjacentHTML('beforeend', @view('typingIndicator')())
  1037. # only if typing indicator is shown
  1038. return if !@isVisible(@el.querySelector('.zammad-chat-message--typing'), true)
  1039. @scrollToBottom()
  1040. onAgentTypingEnd: =>
  1041. @el.querySelector('.zammad-chat-message--typing').remove() if @el.querySelector('.zammad-chat-message--typing')
  1042. onLeaveTemporary: =>
  1043. return if !@sessionId
  1044. @send 'chat_session_leave_temporary',
  1045. session_id: @sessionId
  1046. maybeAddTimestamp: ->
  1047. timestamp = Date.now()
  1048. if !@lastTimestamp or (timestamp - @lastTimestamp) > @showTimeEveryXMinutes * 60000
  1049. label = @T('Today')
  1050. time = new Date().toTimeString().substr 0,5
  1051. if @lastAddedType is 'timestamp'
  1052. # update last time
  1053. @updateLastTimestamp label, time
  1054. @lastTimestamp = timestamp
  1055. else
  1056. # add new timestamp
  1057. @body.insertAdjacentHTML 'beforeend', @view('timestamp')
  1058. label: label
  1059. time: time
  1060. @lastTimestamp = timestamp
  1061. @lastAddedType = 'timestamp'
  1062. @scrollToBottom()
  1063. updateLastTimestamp: (label, time) ->
  1064. return if !@el
  1065. timestamps = @el.querySelectorAll('.zammad-chat-body .zammad-chat-timestamp')
  1066. return if !timestamps
  1067. timestamps[timestamps.length - 1].outerHTML = @view('timestamp')
  1068. label: label
  1069. time: time
  1070. addStatus: (status) ->
  1071. return if !@el
  1072. @maybeAddTimestamp()
  1073. @body.insertAdjacentHTML 'beforeend', @view('status')
  1074. status: status
  1075. @scrollToBottom()
  1076. detectScrolledtoBottom: =>
  1077. scrollBottom = @body.scrollTop + @body.offsetHeight
  1078. @scrolledToBottom = Math.abs(scrollBottom - @body.scrollHeight) <= @scrollSnapTolerance
  1079. @el.querySelector('.zammad-scroll-hint').classList.add('is-hidden') if @scrolledToBottom
  1080. showScrollHint: ->
  1081. @el.querySelector('.zammad-scroll-hint').classList.remove('is-hidden')
  1082. # compensate scroll
  1083. @body.scrollTop = @body.scrollTop + @el.querySelector('.zammad-scroll-hint').offsetHeight
  1084. onScrollHintClick: =>
  1085. # animate scroll
  1086. @body.scrollTo
  1087. top: @body.scrollHeight
  1088. behavior: 'smooth'
  1089. scrollToBottom: ({ showHint } = { showHint: false }) ->
  1090. if @scrolledToBottom
  1091. @body.scrollTop = @body.scrollHeight
  1092. else if showHint
  1093. @showScrollHint()
  1094. destroy: (params = {}) =>
  1095. @log.debug 'destroy widget', params
  1096. @setAgentOnlineState 'offline'
  1097. if params.remove && @el
  1098. @el.remove()
  1099. # Remove button, because it can no longer be used.
  1100. btn = document.querySelector(".#{ @options.buttonClass }")
  1101. if btn
  1102. btn.classList.add @options.inactiveClass
  1103. btn.style.display = 'none';
  1104. # stop all timer
  1105. if @waitingListTimeout
  1106. @waitingListTimeout.stop()
  1107. if @inactiveTimeout
  1108. @inactiveTimeout.stop()
  1109. if @idleTimeout
  1110. @idleTimeout.stop()
  1111. # stop ws connection
  1112. @io.close()
  1113. reconnect: =>
  1114. # set status to connecting
  1115. @log.notice 'reconnecting'
  1116. @disableInput()
  1117. @lastAddedType = 'status'
  1118. @setAgentOnlineState 'connecting'
  1119. @addStatus @T('Connection lost')
  1120. onConnectionReestablished: =>
  1121. # set status back to online
  1122. @lastAddedType = 'status'
  1123. @setAgentOnlineState 'online'
  1124. @addStatus @T('Connection re-established')
  1125. @options.onConnectionReestablished?()
  1126. onSessionClosed: (data) ->
  1127. @addStatus @T('Chat closed by %s', data.realname)
  1128. @disableInput()
  1129. @setAgentOnlineState 'offline'
  1130. @inactiveTimeout.stop()
  1131. @options.onSessionClosed?(data)
  1132. setSessionId: (id) =>
  1133. @sessionId = id
  1134. if id is undefined
  1135. sessionStorage.removeItem 'sessionId'
  1136. else
  1137. sessionStorage.setItem 'sessionId', id
  1138. onConnectionEstablished: (data) =>
  1139. # stop delay of initial queue position
  1140. if @onInitialQueueDelayId
  1141. clearTimeout @onInitialQueueDelayId
  1142. @inQueue = false
  1143. if data.agent
  1144. @agent = data.agent
  1145. if data.session_id
  1146. @setSessionId data.session_id
  1147. # empty old messages
  1148. @body.innerHTML = ''
  1149. @el.querySelector('.zammad-chat-agent').innerHTML = @view('agent')
  1150. agent: @agent
  1151. @enableInput()
  1152. @hideModal()
  1153. @el.querySelector('.zammad-chat-welcome').classList.add('zammad-chat-is-hidden')
  1154. @el.querySelector('.zammad-chat-agent').classList.remove('zammad-chat-is-hidden')
  1155. @el.querySelector('.zammad-chat-agent-status').classList.remove('zammad-chat-is-hidden')
  1156. @input.focus() if not @isFullscreen
  1157. @setAgentOnlineState 'online'
  1158. @waitingListTimeout.stop()
  1159. @idleTimeout.stop()
  1160. @inactiveTimeout.start()
  1161. @options.onConnectionEstablished?(data)
  1162. showCustomerTimeout: ->
  1163. @el.querySelector('.zammad-chat-modal').innerHTML = @view('customer_timeout')
  1164. agent: @agent.name
  1165. delay: @options.inactiveTimeout
  1166. @el.querySelector('.js-restart').addEventListener 'click', -> location.reload()
  1167. @sessionClose()
  1168. showWaitingListTimeout: ->
  1169. @el.querySelector('.zammad-chat-modal').innerHTML = @view('waiting_list_timeout')
  1170. delay: @options.watingListTimeout
  1171. @el.querySelector('.js-restart').addEventListener 'click', -> location.reload()
  1172. @sessionClose()
  1173. showLoader: ->
  1174. @el.querySelector('.zammad-chat-modal').innerHTML = @view('loader')()
  1175. setAgentOnlineState: (state) =>
  1176. @state = state
  1177. return if !@el
  1178. capitalizedState = state.charAt(0).toUpperCase() + state.slice(1)
  1179. @el.querySelector('.zammad-chat-agent-status').dataset.status = state
  1180. @el.querySelector('.zammad-chat-agent-status').textContent = @T(capitalizedState)
  1181. detectHost: ->
  1182. protocol = 'ws://'
  1183. if scriptProtocol is 'https'
  1184. protocol = 'wss://'
  1185. @options.host = "#{ protocol }#{ scriptHost }/ws"
  1186. loadCss: ->
  1187. return if !@options.cssAutoload
  1188. url = @options.cssUrl
  1189. if !url
  1190. url = @options.host
  1191. .replace(/^wss/i, 'https')
  1192. .replace(/^ws/i, 'http')
  1193. .replace(/\/ws$/i, '') # WebSocket may run on example.com/ws path
  1194. url += '/assets/chat/chat.css'
  1195. @log.debug "load css from '#{url}'"
  1196. styles = "@import url('#{url}');"
  1197. newSS = document.createElement('link')
  1198. newSS.onload = @onCssLoaded
  1199. newSS.rel = 'stylesheet'
  1200. newSS.href = 'data:text/css,' + escape(styles)
  1201. document.getElementsByTagName('head')[0].appendChild(newSS)
  1202. onCssLoaded: =>
  1203. @cssLoaded = true
  1204. if @socketReady
  1205. @onReady()
  1206. @options.onCssLoaded?()
  1207. startTimeoutObservers: =>
  1208. @idleTimeout = new Timeout(
  1209. logPrefix: 'idleTimeout'
  1210. debug: @options.debug
  1211. timeout: @options.idleTimeout
  1212. timeoutIntervallCheck: @options.idleTimeoutIntervallCheck
  1213. callback: =>
  1214. @log.debug 'Idle timeout reached, hide widget', new Date
  1215. @destroy(remove: true)
  1216. )
  1217. @inactiveTimeout = new Timeout(
  1218. logPrefix: 'inactiveTimeout'
  1219. debug: @options.debug
  1220. timeout: @options.inactiveTimeout
  1221. timeoutIntervallCheck: @options.inactiveTimeoutIntervallCheck
  1222. callback: =>
  1223. @log.debug 'Inactive timeout reached, show timeout screen.', new Date
  1224. @showCustomerTimeout()
  1225. @destroy(remove: false)
  1226. )
  1227. @waitingListTimeout = new Timeout(
  1228. logPrefix: 'waitingListTimeout'
  1229. debug: @options.debug
  1230. timeout: @options.waitingListTimeout
  1231. timeoutIntervallCheck: @options.waitingListTimeoutIntervallCheck
  1232. callback: =>
  1233. @log.debug 'Waiting list timeout reached, show timeout screen.', new Date
  1234. @showWaitingListTimeout()
  1235. @destroy(remove: false)
  1236. )
  1237. disableScrollOnRoot: ->
  1238. @rootScrollOffset = @scrollRoot.scrollTop
  1239. @scrollRoot.style.overflow = 'hidden'
  1240. @scrollRoot.style.position = 'fixed'
  1241. enableScrollOnRoot: ->
  1242. @scrollRoot.scrollTop = @rootScrollOffset
  1243. @scrollRoot.style.overflow = ''
  1244. @scrollRoot.style.position = ''
  1245. # based on https://github.com/customd/jquery-visible/blob/master/jquery.visible.js
  1246. # to have not dependency, port to coffeescript
  1247. isVisible: (el, partial, hidden, direction) ->
  1248. return if el.length < 1
  1249. vpWidth = window.innerWidth
  1250. vpHeight = window.innerHeight
  1251. direction = if direction then direction else 'both'
  1252. clientSize = if hidden is true then t.offsetWidth * t.offsetHeight else true
  1253. rec = el.getBoundingClientRect()
  1254. tViz = rec.top >= 0 && rec.top < vpHeight
  1255. bViz = rec.bottom > 0 && rec.bottom <= vpHeight
  1256. lViz = rec.left >= 0 && rec.left < vpWidth
  1257. rViz = rec.right > 0 && rec.right <= vpWidth
  1258. vVisible = if partial then tViz || bViz else tViz && bViz
  1259. hVisible = if partial then lViz || rViz else lViz && rViz
  1260. if direction is 'both'
  1261. return clientSize && vVisible && hVisible
  1262. else if direction is 'vertical'
  1263. return clientSize && vVisible
  1264. else if direction is 'horizontal'
  1265. return clientSize && hVisible
  1266. isRetina: ->
  1267. if window.matchMedia
  1268. 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)')
  1269. return (mq && mq.matches || (window.devicePixelRatio > 1))
  1270. false
  1271. resizeImage: (dataURL, x = 'auto', y = 'auto', sizeFactor = 1, type, quallity, callback, force = true) ->
  1272. # load image from data url
  1273. imageObject = new Image()
  1274. imageObject.onload = ->
  1275. imageWidth = imageObject.width
  1276. imageHeight = imageObject.height
  1277. console.log('ImageService', 'current size', imageWidth, imageHeight)
  1278. if y is 'auto' && x is 'auto'
  1279. x = imageWidth
  1280. y = imageHeight
  1281. # get auto dimensions
  1282. if y is 'auto'
  1283. factor = imageWidth / x
  1284. y = imageHeight / factor
  1285. if x is 'auto'
  1286. factor = imageWidth / y
  1287. x = imageHeight / factor
  1288. # check if resize is needed
  1289. resize = false
  1290. if x < imageWidth || y < imageHeight
  1291. resize = true
  1292. x = x * sizeFactor
  1293. y = y * sizeFactor
  1294. else
  1295. x = imageWidth
  1296. y = imageHeight
  1297. # create canvas and set dimensions
  1298. canvas = document.createElement('canvas')
  1299. canvas.width = x
  1300. canvas.height = y
  1301. # draw image on canvas and set image dimensions
  1302. context = canvas.getContext('2d')
  1303. context.drawImage(imageObject, 0, 0, x, y)
  1304. # set quallity based on image size
  1305. if quallity == 'auto'
  1306. if x < 200 && y < 200
  1307. quallity = 1
  1308. else if x < 400 && y < 400
  1309. quallity = 0.9
  1310. else if x < 600 && y < 600
  1311. quallity = 0.8
  1312. else if x < 900 && y < 900
  1313. quallity = 0.7
  1314. else
  1315. quallity = 0.6
  1316. # execute callback with resized image
  1317. newDataUrl = canvas.toDataURL(type, quallity)
  1318. if resize
  1319. console.log('ImageService', 'resize', x/sizeFactor, y/sizeFactor, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
  1320. callback(newDataUrl, x/sizeFactor, y/sizeFactor, true)
  1321. return
  1322. console.log('ImageService', 'no resize', x, y, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
  1323. callback(newDataUrl, x, y, false)
  1324. # load image from data url
  1325. imageObject.src = dataURL
  1326. # taken from https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
  1327. pasteHtmlAtCaret: (html) ->
  1328. sel = undefined
  1329. range = undefined
  1330. if window.getSelection
  1331. sel = window.getSelection()
  1332. if sel.getRangeAt && sel.rangeCount
  1333. range = sel.getRangeAt(0)
  1334. range.deleteContents()
  1335. el = document.createElement('div')
  1336. el.innerHTML = html
  1337. frag = document.createDocumentFragment(node, lastNode)
  1338. while node = el.firstChild
  1339. lastNode = frag.appendChild(node)
  1340. range.insertNode(frag)
  1341. if lastNode
  1342. range = range.cloneRange()
  1343. range.setStartAfter(lastNode)
  1344. range.collapse(true)
  1345. sel.removeAllRanges()
  1346. sel.addRange(range)
  1347. else if document.selection && document.selection.type != 'Control'
  1348. document.selection.createRange().pasteHTML(html)
  1349. # (C) sbrin - https://github.com/sbrin
  1350. # https://gist.github.com/sbrin/6801034
  1351. wordFilter: (editor) ->
  1352. content = editor.html()
  1353. # Word comments like conditional comments etc
  1354. content = content.replace(/<!--[\s\S]+?-->/gi, '')
  1355. # Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
  1356. # MS Office namespaced tags, and a few other tags
  1357. content = content.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '')
  1358. # Convert <s> into <strike> for line-though
  1359. content = content.replace(/<(\/?)s>/gi, '<$1strike>')
  1360. # Replace nbsp entites to char since it's easier to handle
  1361. # content = content.replace(/&nbsp;/gi, "\u00a0")
  1362. content = content.replace(/&nbsp;/gi, ' ')
  1363. # Convert <span style="mso-spacerun:yes">___</span> to string of alternating
  1364. # breaking/non-breaking spaces of same length
  1365. #content = content.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, (str, spaces) ->
  1366. # return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ''
  1367. #)
  1368. editor.innerHTML = content
  1369. # Parse out list indent level for lists
  1370. for p in editor.querySelectorAll('p')
  1371. str = p.getAttribute('style')
  1372. matches = /mso-list:\w+ \w+([0-9]+)/.exec(str)
  1373. if matches
  1374. p.dataset._listLevel = parseInt(matches[1], 10)
  1375. # Parse Lists
  1376. last_level = 0
  1377. pnt = null
  1378. for p in editor.querySelectorAll('p')
  1379. cur_level = p.dataset._listLevel
  1380. if cur_level != undefined
  1381. txt = p.textContent
  1382. list_tag = '<ul></ul>'
  1383. if (/^\s*\w+\./.test(txt))
  1384. matches = /([0-9])\./.exec(txt)
  1385. if matches
  1386. start = parseInt(matches[1], 10)
  1387. list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>'
  1388. else
  1389. list_tag = '<ol></ol>'
  1390. if cur_level > last_level
  1391. if last_level == 0
  1392. p.insertAdjacentHTML 'beforebegin', list_tag
  1393. pnt = p.previousElementSibling
  1394. else
  1395. pnt.insertAdjacentHTML 'beforeend', list_tag
  1396. if cur_level < last_level
  1397. for i in [i..last_level-cur_level]
  1398. pnt = pnt.parentNode
  1399. p.querySelector('span:first').remove() if p.querySelector('span:first')
  1400. pnt.insertAdjacentHTML 'beforeend', '<li>' + p.innerHTML + '</li>'
  1401. p.remove()
  1402. last_level = cur_level
  1403. else
  1404. last_level = 0
  1405. el.removeAttribute('style') for el in editor.querySelectorAll('[style]')
  1406. el.removeAttribute('align') for el in editor.querySelectorAll('[align]')
  1407. el.outerHTML = el.innerHTML for el in editor.querySelectorAll('span')
  1408. el.remove() for el in editor.querySelectorAll('span:empty')
  1409. el.removeAttribute('class') for el in editor.querySelectorAll("[class^='Mso']")
  1410. el.remove() for el in editor.querySelectorAll('p:empty')
  1411. editor
  1412. removeAttribute: (element) ->
  1413. return if !element
  1414. for att in element.attributes
  1415. element.removeAttribute(att.name)
  1416. removeAttributes: (html) =>
  1417. for node in html.querySelectorAll('*')
  1418. @removeAttribute node
  1419. html
  1420. window.ZammadChat = ZammadChat