chat.coffee 65 KB

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