chat.coffee 69 KB

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