chat.coffee 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713
  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. # ZAMMAD_TRANSLATIONS_START
  171. 'cs':
  172. '<strong>Chat</strong> with us!': '<strong>Chatujte</strong> s námi!'
  173. 'All colleagues are busy.': 'Všichni kolegové jsou vytíženi.'
  174. 'Chat closed by %s': '%s ukončil konverzaci'
  175. 'Compose your message…': 'Napište svou zprávu…'
  176. 'Connecting': 'Připojování'
  177. 'Connection lost': 'Připojení ztraceno'
  178. 'Connection re-established': 'Připojení obnoveno'
  179. 'Offline': 'Offline'
  180. 'Online': 'Online'
  181. 'Scroll down to see new messages': 'Srolujte dolů pro zobrazení nových zpráv'
  182. 'Send': 'Odeslat'
  183. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Jelikož jste nereagovali v posledních %s minutách, vaše konverzace byla uzavřena.'
  184. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Jelikož jste nereagovali v posledních %s minutách, vaše konverzace s <strong>%s</strong> byla uzavřena.'
  185. 'Start new conversation': 'Zahájit novou konverzaci'
  186. 'Today': 'Dnes'
  187. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Omlouváme se, že musíte čekat déle, než je vhodné pro získání slotu. Prosím, zkuste to později, případně nám napište e-mail. Děkujeme!'
  188. 'You are on waiting list position <strong>%s</strong>.': 'Jste <strong>%s</strong>. v pořadí na čekací listině.'
  189. 'da':
  190. '<strong>Chat</strong> with us!': '<strong>Chat</strong> med os!'
  191. 'All colleagues are busy.': 'Alle medarbejdere er optaget.'
  192. 'Chat closed by %s': 'Chat lukket af %s'
  193. 'Compose your message…': 'Skriv din besked…'
  194. 'Connecting': 'Forbinder'
  195. 'Connection lost': 'Forbindelse mistet'
  196. 'Connection re-established': 'Forbindelse genoprettet'
  197. 'Offline': 'Offline'
  198. 'Online': 'Online'
  199. 'Scroll down to see new messages': 'Scroll ned for at se nye beskeder'
  200. 'Send': 'Afsend'
  201. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': ''
  202. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': ''
  203. 'Start new conversation': 'Start en ny samtale'
  204. 'Today': 'I dag'
  205. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': ''
  206. 'You are on waiting list position <strong>%s</strong>.': 'Du er i kø som nummer <strong>%s</strong>.'
  207. 'de':
  208. '<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!'
  209. 'All colleagues are busy.': 'Alle Kollegen sind beschäftigt.'
  210. 'Chat closed by %s': 'Chat von %s geschlossen'
  211. 'Compose your message…': 'Verfassen Sie Ihre Nachricht…'
  212. 'Connecting': 'Verbinde'
  213. 'Connection lost': 'Verbindung verloren'
  214. 'Connection re-established': 'Verbindung wieder aufgebaut'
  215. 'Offline': 'Offline'
  216. 'Online': 'Online'
  217. 'Scroll down to see new messages': 'Nach unten scrollen um neue Nachrichten zu sehen'
  218. 'Send': 'Senden'
  219. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Da Sie innerhalb der letzten %s Minuten nicht reagiert haben, wurde Ihre Unterhaltung geschlossen.'
  220. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Da Sie innerhalb der letzten %s Minuten nicht reagiert haben, wurde Ihre Unterhaltung mit <strong>%s</strong> geschlossen.'
  221. 'Start new conversation': 'Neue Unterhaltung starten'
  222. 'Today': 'Heute'
  223. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Entschuldigung, es dauert länger als erwartet einen freien Platz zu bekommen. Versuchen Sie es später erneut oder senden Sie uns eine E-Mail. Vielen Dank!'
  224. 'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste auf Position <strong>%s</strong>.'
  225. 'es':
  226. '<strong>Chat</strong> with us!': '<strong>Chatee</strong> con nosotros!'
  227. 'All colleagues are busy.': 'Todos los colegas están ocupados.'
  228. 'Chat closed by %s': 'Chat cerrado por %s'
  229. 'Compose your message…': 'Escribe tu mensaje…'
  230. 'Connecting': 'Conectando'
  231. 'Connection lost': 'Conexión perdida'
  232. 'Connection re-established': 'Conexión reestablecida'
  233. 'Offline': 'Desconectado'
  234. 'Online': 'En línea'
  235. 'Scroll down to see new messages': 'Desplace hacia abajo para ver nuevos mensajes'
  236. 'Send': 'Enviar'
  237. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Debido a que usted no ha respondido en los últimos %s minutos, su conversación se ha cerrado.'
  238. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Debido a que usted no ha respondido en los últimos %s minutos, su conversación con <strong>%s</strong> se ha cerrado.'
  239. 'Start new conversation': 'Iniciar nueva conversación'
  240. 'Today': 'Hoy'
  241. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Lo sentimos, estamos tardando más de lo esperado para asignar un agente. Inténtelo de nuevo más tarde o envíenos un correo electrónico. ¡Gracias!'
  242. 'You are on waiting list position <strong>%s</strong>.': 'Usted está en la posición <strong>%s</strong> de la lista de espera.'
  243. 'fr':
  244. '<strong>Chat</strong> with us!': '<strong>Chattez</strong> avec nous !'
  245. 'All colleagues are busy.': 'Tous les agents sont occupés.'
  246. 'Chat closed by %s': 'Chat fermé par %s'
  247. 'Compose your message…': 'Écrivez votre message…'
  248. 'Connecting': 'Connexion'
  249. 'Connection lost': 'Connexion perdue'
  250. 'Connection re-established': 'Connexion ré-établie'
  251. 'Offline': 'Hors-ligne'
  252. 'Online': 'En ligne'
  253. 'Scroll down to see new messages': 'Défiler vers le bas pour voir les nouveaux messages'
  254. 'Send': 'Envoyer'
  255. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Sans réponse de votre part depuis %s minutes, votre conservation a été fermée.'
  256. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Sans réponse de votre part depuis %s minutes, votre conversation avec <strong>%s</strong> a été fermée.'
  257. 'Start new conversation': 'Démarrer une nouvelle conversation'
  258. 'Today': 'Aujourd\'hui'
  259. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Nous sommes désolés, trouver un agent disponible prend plus de temps que prévu. Réessayez plus tard ou envoyez-nous un mail. Merci !'
  260. 'You are on waiting list position <strong>%s</strong>.': 'Vous êtes actuellement en position <strong>%s</strong> dans la file d\'attente.'
  261. 'hr':
  262. '<strong>Chat</strong> with us!': '<strong>Čavrljajte</strong> sa nama!'
  263. 'All colleagues are busy.': 'Svi kolege su zauzeti.'
  264. 'Chat closed by %s': '%s zatvara chat'
  265. 'Compose your message…': 'Sastavite poruku…'
  266. 'Connecting': 'Povezivanje'
  267. 'Connection lost': 'Veza prekinuta'
  268. 'Connection re-established': 'Veza je ponovno uspostavljena'
  269. 'Offline': 'Odsutan'
  270. 'Online': 'Dostupan(a)'
  271. 'Scroll down to see new messages': 'Pomaknite se prema dolje da biste vidjeli nove poruke'
  272. 'Send': 'Šalji'
  273. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Budući da niste odgovorili u posljednjih %s minuta, Vaš je razgovor zatvoren.'
  274. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Budući da niste odgovorili u posljednjih %s minuta, Vaš je razgovor s <strong>%</strong>s zatvoren.'
  275. 'Start new conversation': 'Započni novi razgovor'
  276. 'Today': 'Danas'
  277. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Oprostite, proces traje duže nego što se očekivalo da biste dobili slobodan termin. Molimo, pokušajte ponovno kasnije ili nam pošaljite e-mail. Hvala!'
  278. 'You are on waiting list position <strong>%s</strong>.': 'Nalazite se u redu čekanja na poziciji <strong>%s</strong>.'
  279. 'hu':
  280. '<strong>Chat</strong> with us!': '<strong>Csevegjen</strong> velünk!'
  281. 'All colleagues are busy.': 'Minden munkatársunk foglalt.'
  282. 'Chat closed by %s': 'A csevegés %s által lezárva'
  283. 'Compose your message…': 'Fogalmazza meg üzenetét…'
  284. 'Connecting': 'Csatlakozás'
  285. 'Connection lost': 'A kapcsolat megszakadt'
  286. 'Connection re-established': 'A kapcsolat helyreállt'
  287. 'Offline': 'Offline'
  288. 'Online': 'Online'
  289. 'Scroll down to see new messages': 'Görgessen lefelé az új üzenetek megtekintéséhez'
  290. 'Send': 'Küldés'
  291. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Mivel az elmúlt %s percben nem válaszolt, a beszélgetése lezárásra került.'
  292. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Mivel az elmúlt %s percben nem válaszolt, <strong>%s</strong> munkatársunkkal folytatott beszélgetését lezártuk.'
  293. 'Start new conversation': 'Új beszélgetés indítása'
  294. 'Today': 'Ma'
  295. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Sajnáljuk, hogy a vártnál hosszabb ideig tart a helyfoglalás. Kérjük, próbálja meg később újra, vagy küldjön nekünk egy e-mailt. Köszönjük!'
  296. 'You are on waiting list position <strong>%s</strong>.': 'Ön a várólistán a <strong>%s</strong> helyen szerepel.'
  297. 'it':
  298. '<strong>Chat</strong> with us!': '<strong>Chatta</strong> con noi!'
  299. 'All colleagues are busy.': 'Tutti i colleghi sono occupati.'
  300. 'Chat closed by %s': 'Chat chiusa da %s'
  301. 'Compose your message…': 'Scrivi il tuo messaggio…'
  302. 'Connecting': 'Connessione in corso'
  303. 'Connection lost': 'Connessione persa'
  304. 'Connection re-established': 'Connessione ristabilita'
  305. 'Offline': 'Offline'
  306. 'Online': 'Online'
  307. 'Scroll down to see new messages': 'Scorri verso il basso per vedere i nuovi messaggi'
  308. 'Send': 'Invia'
  309. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Dato che non hai risposto negli ultimi %s minuti, la conversazione è stata chiusa.'
  310. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Dato che non hai risposto negli ultimi %s minuti, la conversazione con <strong>%s</strong> è stata chiusa.'
  311. 'Start new conversation': 'Avvia una nuova chat'
  312. 'Today': 'Oggi'
  313. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Siamo spiacenti, ci vuole più tempo del previsto per ottenere uno spazio libero. Riprova più tardi o inviaci un\'e-mail. Grazie!'
  314. 'You are on waiting list position <strong>%s</strong>.': 'Sei alla posizione <strong>%s</strong> della lista di attesa.'
  315. 'lt':
  316. '<strong>Chat</strong> with us!': '<strong>Kalbėkitės</strong> su mumis!'
  317. 'All colleagues are busy.': 'Visi kolegos užimti.'
  318. 'Chat closed by %s': '%s uždarė pokalbį'
  319. 'Compose your message…': 'Rašykite žinutę…'
  320. 'Connecting': 'Jungiamasi'
  321. 'Connection lost': 'Dingo ryšys'
  322. 'Connection re-established': 'Ryšys atnaujintas'
  323. 'Offline': 'Atsijungęs'
  324. 'Online': 'Prisijungęs'
  325. 'Scroll down to see new messages': 'Naujos žinutės žemiau'
  326. 'Send': 'Siųsti'
  327. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Jūsų pokalbis buvo uždarytas, nes nieko neatsakėte per %s minučių.'
  328. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Jūsų pokalbis su <strong>%s</strong> buvo uždarytas, nes nieko neatsakėte per %s minučių.'
  329. 'Start new conversation': 'Pradėti naują pokalbį'
  330. 'Today': 'Šiandien'
  331. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Atsiprašome, kad tenka laukti atskymo. Bandykite vėliau arba rašykite el. paštu. Ačiū!'
  332. 'You are on waiting list position <strong>%s</strong>.': 'Esate <strong>%s</strong> eilėje.'
  333. 'nl':
  334. '<strong>Chat</strong> with us!': '<strong>Chat</strong> met ons!'
  335. 'All colleagues are busy.': 'Alle collega\'s zijn bezet.'
  336. 'Chat closed by %s': 'Chat gesloten door %s'
  337. 'Compose your message…': 'Stel je bericht op…'
  338. 'Connecting': 'Verbinden'
  339. 'Connection lost': 'Verbinding verbroken'
  340. 'Connection re-established': 'Verbinding hersteld'
  341. 'Offline': 'Offline'
  342. 'Online': 'Online'
  343. 'Scroll down to see new messages': 'Scroll naar beneden om nieuwe tickets te bekijken'
  344. 'Send': 'Verstuur'
  345. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'De chat is afgesloten omdat je de laatste %s minuten niet hebt gereageerd.'
  346. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Je chat met <strong>%s</strong> is afgesloten omdat je niet hebt gereageerd in de laatste %s minuten.'
  347. 'Start new conversation': 'Nieuw gesprek starten'
  348. 'Today': 'Vandaag'
  349. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Het spijt ons, het duurt langer dan verwacht om een chat te starten. Probeer het later nog eens of stuur ons een e-mail. Bedankt!'
  350. 'You are on waiting list position <strong>%s</strong>.': 'Je bevindt zich op wachtlijstpositie <strong>%s</strong>.'
  351. 'pl':
  352. '<strong>Chat</strong> with us!': '<strong>Czatuj</strong> z nami!'
  353. 'All colleagues are busy.': 'Wszyscy agenci są zajęci.'
  354. 'Chat closed by %s': 'Chat zamknięty przez %s'
  355. 'Compose your message…': 'Skomponuj swoją wiadomość…'
  356. 'Connecting': 'Łączenie'
  357. 'Connection lost': 'Utracono połączenie'
  358. 'Connection re-established': 'Ponowne nawiązanie połączenia'
  359. 'Offline': 'Offline'
  360. 'Online': 'Online'
  361. 'Scroll down to see new messages': 'Skroluj w dół, aby zobaczyć wiadomości'
  362. 'Send': 'Wyślij'
  363. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Ponieważ nie odpowiedziałeś w ciągu ostatnich %s minut, Twoja rozmowa została zamknięta.'
  364. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Ponieważ nie odpowiedziałeś w ciągu ostatnich %s minut, Twoja rozmowa z <strong>%s</strong> została zamknięta.'
  365. 'Start new conversation': 'Rozpocznij nową rozmowę'
  366. 'Today': 'Dzisiaj'
  367. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Przepraszamy, znalezienie wolnego konsultanta zajmuje więcej czasu niż oczekiwano. Spróbuj ponownie później lub wyślij nam e-mail. Dziękujemy!'
  368. 'You are on waiting list position <strong>%s</strong>.': 'Jesteś na pozycji listy oczekujących <strong>%s</strong>.'
  369. 'pt-br':
  370. '<strong>Chat</strong> with us!': '<strong>Converse</strong> conosco!'
  371. 'All colleagues are busy.': 'Nossos atendentes estão ocupados.'
  372. 'Chat closed by %s': 'Chat encerrado por %s'
  373. 'Compose your message…': 'Escreva sua mensagem…'
  374. 'Connecting': 'Conectando'
  375. 'Connection lost': 'Conexão perdida'
  376. 'Connection re-established': 'Conexão restabelecida'
  377. 'Offline': 'Desconectado'
  378. 'Online': 'Online'
  379. 'Scroll down to see new messages': 'Rolar para baixo para ver novas mensagems'
  380. 'Send': 'Enviar'
  381. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Como você não respondeu nos últimos %s minutos, sua conversa foi encerrada.'
  382. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Como você não respondeu nos últimos %s minutos, sua conversa com <strong>%s</strong> foi encerrada.'
  383. 'Start new conversation': 'Iniciar uma nova conversa'
  384. 'Today': 'Hoje'
  385. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Lamentamos, está demorando mais do que o esperado para conseguir uma vaga. Tente novamente mais tarde ou envie-nos um e-mail. Obrigado!'
  386. 'You are on waiting list position <strong>%s</strong>.': 'Você está na posição <strong>%s</strong> da lista de espera.'
  387. 'ru':
  388. '<strong>Chat</strong> with us!': '<strong>Напишите</strong> нам!'
  389. 'All colleagues are busy.': 'Все коллеги заняты.'
  390. 'Chat closed by %s': 'Чат закрыт %s'
  391. 'Compose your message…': 'Составьте сообщение…'
  392. 'Connecting': 'Подключение'
  393. 'Connection lost': 'Подключение потеряно'
  394. 'Connection re-established': 'Подключение восстановлено'
  395. 'Offline': 'Оффлайн'
  396. 'Online': 'В сети'
  397. 'Scroll down to see new messages': 'Прокрутите вниз, чтобы увидеть новые сообщения'
  398. 'Send': 'Отправить'
  399. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Поскольку Вы не ответили в течение последних %s минут, Ваш разговор был закрыт.'
  400. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Поскольку Вы не ответили в течение последних %s минут, Ваш разговор с <strong>%s</strong> был закрыт.'
  401. 'Start new conversation': 'Начать новый разговор'
  402. 'Today': 'Сегодня'
  403. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Извините, получение свободного слота занимает больше времени, чем ожидалось. Пожалуйста, повторите попытку позже или отправьте нам электронное письмо. Благодарим Вас!'
  404. 'You are on waiting list position <strong>%s</strong>.': 'Вы находитесь в списке ожидания <strong>%s</strong>.'
  405. 'sr':
  406. '<strong>Chat</strong> with us!': '<strong>Ћаскајте</strong> са нама!'
  407. 'All colleagues are busy.': 'Све колеге су заузете.'
  408. 'Chat closed by %s': 'Ћаскање затворено од стране %s'
  409. 'Compose your message…': 'Напишите поруку…'
  410. 'Connecting': 'Повезивање'
  411. 'Connection lost': 'Веза је изгубљена'
  412. 'Connection re-established': 'Веза је поново успостављена'
  413. 'Offline': 'Одсутан(а)'
  414. 'Online': 'Доступан(а)'
  415. 'Scroll down to see new messages': 'Скролујте на доле за нове поруке'
  416. 'Send': 'Пошаљи'
  417. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Пошто нисте одговорили у последњих %s минут(a), ваш разговор је завршен.'
  418. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Пошто нисте одговорили у последњих %s минут(a), ваш разговор са <strong>%s</strong> је завршен.'
  419. 'Start new conversation': 'Започни нови разговор'
  420. 'Today': 'Данас'
  421. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Жао нам је, добијање празног термина траје дуже од очекиваног. Молимо покушајте поново касније или нам пошаљите имејл поруку. Хвала вам!'
  422. 'You are on waiting list position <strong>%s</strong>.': 'Ви сте тренутно <strong>%s.</strong> у реду за чекање.'
  423. 'sr-latn-rs':
  424. '<strong>Chat</strong> with us!': '<strong>Ćaskajte</strong> sa nama!'
  425. 'All colleagues are busy.': 'Sve kolege su zauzete.'
  426. 'Chat closed by %s': 'Ćaskanje zatvoreno od strane %s'
  427. 'Compose your message…': 'Napišite poruku…'
  428. 'Connecting': 'Povezivanje'
  429. 'Connection lost': 'Veza je izgubljena'
  430. 'Connection re-established': 'Veza je ponovo uspostavljena'
  431. 'Offline': 'Odsutan(a)'
  432. 'Online': 'Dostupan(a)'
  433. 'Scroll down to see new messages': 'Skrolujte na dole za nove poruke'
  434. 'Send': 'Pošalji'
  435. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Pošto niste odgovorili u poslednjih %s minut(a), vaš razgovor je završen.'
  436. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Pošto niste odgovorili u poslednjih %s minut(a), vaš razgovor sa <strong>%s</strong> je završen.'
  437. 'Start new conversation': 'Započni novi razgovor'
  438. 'Today': 'Danas'
  439. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Žao nam je, dobijanje praznog termina traje duže od očekivanog. Molimo pokušajte ponovo kasnije ili nam pošaljite imejl poruku. Hvala vam!'
  440. 'You are on waiting list position <strong>%s</strong>.': 'Vi ste trenutno <strong>%s.</strong> u redu za čekanje.'
  441. 'sv':
  442. '<strong>Chat</strong> with us!': '<strong>Chatta</strong> med oss!'
  443. 'All colleagues are busy.': 'Alla kollegor är upptagna.'
  444. 'Chat closed by %s': 'Chatt stängd av %s'
  445. 'Compose your message…': 'Skriv ditt meddelande …'
  446. 'Connecting': 'Ansluter'
  447. 'Connection lost': 'Anslutningen försvann'
  448. 'Connection re-established': 'Anslutningen återupprättas'
  449. 'Offline': 'Offline'
  450. 'Online': 'Online'
  451. 'Scroll down to see new messages': 'Bläddra ner för att se nya meddelanden'
  452. 'Send': 'Skicka'
  453. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Din chatt avslutades då du inte svarade inom %s minuter.'
  454. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Chatten stängdes eftersom du inte svarat inom %s minuter i din konversation med <strong>%s</strong>.'
  455. 'Start new conversation': 'Starta ny konversation'
  456. 'Today': 'Idag'
  457. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Det tar tyvärr längre tid än förväntat att få en ledig plats. Försök igen senare eller skicka ett mejl till oss. Tack!'
  458. 'You are on waiting list position <strong>%s</strong>.': 'Du är på väntelistan som position <strong>%s</strong>.'
  459. 'zh-cn':
  460. '<strong>Chat</strong> with us!': '发起<strong>即时对话</strong>!'
  461. 'All colleagues are busy.': '所有同事都很忙。'
  462. 'Chat closed by %s': '对话已被 %s 关闭'
  463. 'Compose your message…': '编辑您的信息…'
  464. 'Connecting': '连接中'
  465. 'Connection lost': '连接丢失'
  466. 'Connection re-established': '正在重新建立连接'
  467. 'Offline': '离线'
  468. 'Online': '在线'
  469. 'Scroll down to see new messages': '向下滚动以查看新消息'
  470. 'Send': '发送'
  471. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': '"由于您超过 %s 分钟没有任何回复'
  472. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': '"由于您超过 %s 分钟没有回复'
  473. 'Start new conversation': '开始新的会话'
  474. 'Today': '今天'
  475. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': ''
  476. 'You are on waiting list position <strong>%s</strong>.': '您目前的等候位置是第 <strong>%s</strong> 位.'
  477. # ZAMMAD_TRANSLATIONS_END
  478. sessionId: undefined
  479. scrolledToBottom: true
  480. scrollSnapTolerance: 10
  481. richTextFormatKey:
  482. 66: true # b
  483. 73: true # i
  484. 85: true # u
  485. 83: true # s
  486. T: (string, items...) =>
  487. if @options.lang && @options.lang isnt 'en'
  488. if !@translations[@options.lang]
  489. @log.notice "Translation '#{@options.lang}' needed!"
  490. else
  491. translations = @translations[@options.lang]
  492. if !translations[string]
  493. @log.notice "Translation needed for '#{string}'"
  494. string = translations[string] || string
  495. if items
  496. for item in items
  497. string = string.replace(/%s/, item)
  498. string
  499. view: (name) =>
  500. return (options) =>
  501. if !options
  502. options = {}
  503. options.T = @T
  504. options.background = @options.background
  505. options.flat = @options.flat
  506. options.fontSize = @options.fontSize
  507. return window.zammadChatTemplates[name](options)
  508. constructor: (options) ->
  509. @options = $.extend {}, @defaults, options
  510. super(@options)
  511. # fullscreen
  512. @isFullscreen = (window.matchMedia and window.matchMedia('(max-width: 768px)').matches)
  513. @scrollRoot = $(@getScrollRoot())
  514. # check prerequisites
  515. if !$
  516. @state = 'unsupported'
  517. @log.notice 'Chat: no jquery found!'
  518. return
  519. if !window.WebSocket or !sessionStorage
  520. @state = 'unsupported'
  521. @log.notice 'Chat: Browser not supported!'
  522. return
  523. if !@options.chatId
  524. @state = 'unsupported'
  525. @log.error 'Chat: need chatId as option!'
  526. return
  527. # detect language
  528. if !@options.lang
  529. @options.lang = $('html').attr('lang')
  530. if @options.lang
  531. if !@translations[@options.lang]
  532. @log.debug "lang: No #{@options.lang} found, try first two letters"
  533. @options.lang = @options.lang.replace(/-.+?$/, '') # replace "-xx" of xx-xx
  534. @log.debug "lang: #{@options.lang}"
  535. # detect host
  536. @detectHost() if !@options.host
  537. @loadCss()
  538. @io = new Io(@options)
  539. @io.set(
  540. onOpen: @render
  541. onClose: @onWebSocketClose
  542. onMessage: @onWebSocketMessage
  543. onError: @onError
  544. )
  545. @io.connect()
  546. getScrollRoot: ->
  547. return document.scrollingElement if 'scrollingElement' of document
  548. html = document.documentElement
  549. start = html.scrollTop
  550. html.scrollTop = start + 1
  551. end = html.scrollTop
  552. html.scrollTop = start
  553. return if end > start then html else document.body
  554. render: =>
  555. if !@el || !$('.zammad-chat').get(0)
  556. @renderBase()
  557. # disable open button
  558. $(".#{ @options.buttonClass }").addClass @options.inactiveClass
  559. @setAgentOnlineState 'online'
  560. @log.debug 'widget rendered'
  561. @startTimeoutObservers()
  562. @idleTimeout.start()
  563. # get current chat status
  564. @sessionId = sessionStorage.getItem('sessionId')
  565. @send 'chat_status_customer',
  566. session_id: @sessionId
  567. url: window.location.href
  568. renderBase: ->
  569. @el = $(@view('chat')(
  570. title: @options.title,
  571. scrollHint: @options.scrollHint
  572. ))
  573. @options.target.append @el
  574. @input = @el.find('.zammad-chat-input')
  575. # start bindings
  576. @el.find('.js-chat-open').on 'click', @open
  577. @el.find('.js-chat-toggle').on 'click', @toggle
  578. @el.find('.js-chat-status').on 'click', @stopPropagation
  579. @el.find('.zammad-chat-controls').on 'submit', @onSubmit
  580. @el.find('.zammad-chat-body').on 'scroll', @detectScrolledtoBottom
  581. @el.find('.zammad-scroll-hint').on 'click', @onScrollHintClick
  582. @input.on(
  583. keydown: @checkForEnter
  584. input: @onInput
  585. )
  586. @input.on('keydown', (e) =>
  587. richtTextControl = false
  588. if !e.altKey && !e.ctrlKey && e.metaKey
  589. richtTextControl = true
  590. else if !e.altKey && e.ctrlKey && !e.metaKey
  591. richtTextControl = true
  592. if richtTextControl && @richTextFormatKey[ e.keyCode ]
  593. e.preventDefault()
  594. if e.keyCode is 66
  595. document.execCommand('bold')
  596. return true
  597. if e.keyCode is 73
  598. document.execCommand('italic')
  599. return true
  600. if e.keyCode is 85
  601. document.execCommand('underline')
  602. return true
  603. if e.keyCode is 83
  604. document.execCommand('strikeThrough')
  605. return true
  606. )
  607. @input.on('paste', (e) =>
  608. e.stopPropagation()
  609. e.preventDefault()
  610. clipboardData
  611. if e.clipboardData
  612. clipboardData = e.clipboardData
  613. else if window.clipboardData
  614. clipboardData = window.clipboardData
  615. else if e.originalEvent.clipboardData
  616. clipboardData = e.originalEvent.clipboardData
  617. else
  618. throw 'No clipboardData support'
  619. imageInserted = false
  620. if clipboardData && clipboardData.items && clipboardData.items[0]
  621. item = clipboardData.items[0]
  622. if item.kind == 'file' && (item.type == 'image/png' || item.type == 'image/jpeg')
  623. imageFile = item.getAsFile()
  624. reader = new FileReader()
  625. reader.onload = (e) =>
  626. result = e.target.result
  627. img = document.createElement('img')
  628. img.src = result
  629. insert = (dataUrl, width, height, isRetina) =>
  630. # adapt image if we are on retina devices
  631. if @isRetina()
  632. width = width / 2
  633. height = height / 2
  634. result = dataUrl
  635. img = "<img style=\"width: 100%; max-width: #{width}px;\" src=\"#{result}\">"
  636. document.execCommand('insertHTML', false, img)
  637. # resize if to big
  638. @resizeImage(img.src, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
  639. reader.readAsDataURL(imageFile)
  640. imageInserted = true
  641. return if imageInserted
  642. # check existing + paste text for limit
  643. text = undefined
  644. docType = undefined
  645. try
  646. text = clipboardData.getData('text/html')
  647. docType = 'html'
  648. if !text || text.length is 0
  649. docType = 'text'
  650. text = clipboardData.getData('text/plain')
  651. if !text || text.length is 0
  652. docType = 'text2'
  653. text = clipboardData.getData('text')
  654. catch e
  655. console.log('Sorry, can\'t insert markup because browser is not supporting it.')
  656. docType = 'text3'
  657. text = clipboardData.getData('text')
  658. if docType is 'text' || docType is 'text2' || docType is 'text3'
  659. text = '<div>' + text.replace(/\n/g, '</div><div>') + '</div>'
  660. text = text.replace(/<div><\/div>/g, '<div><br></div>')
  661. console.log('p', docType, text)
  662. if docType is 'html'
  663. sanitized = DOMPurify.sanitize(text)
  664. @log.debug 'sanitized HTML clipboard', sanitized
  665. html = $("<div>#{sanitized}</div>")
  666. match = false
  667. htmlTmp = text
  668. regex = new RegExp('<(/w|w)\:[A-Za-z]')
  669. if htmlTmp.match(regex)
  670. match = true
  671. htmlTmp = htmlTmp.replace(regex, '')
  672. regex = new RegExp('<(/o|o)\:[A-Za-z]')
  673. if htmlTmp.match(regex)
  674. match = true
  675. htmlTmp = htmlTmp.replace(regex, '')
  676. if match
  677. html = @wordFilter(html)
  678. #html
  679. html = $(html)
  680. html.contents().each( ->
  681. if @nodeType == 8
  682. $(@).remove()
  683. )
  684. # remove tags, keep content
  685. html.find('a, font, small, time, form, label').replaceWith( ->
  686. $(@).contents()
  687. )
  688. # replace tags with generic div
  689. # New type of the tag
  690. replacementTag = 'div';
  691. # Replace all x tags with the type of replacementTag
  692. html.find('textarea').each( ->
  693. outer = @outerHTML
  694. # Replace opening tag
  695. regex = new RegExp('<' + @tagName, 'i')
  696. newTag = outer.replace(regex, '<' + replacementTag)
  697. # Replace closing tag
  698. regex = new RegExp('</' + @tagName, 'i')
  699. newTag = newTag.replace(regex, '</' + replacementTag)
  700. $(@).replaceWith(newTag)
  701. )
  702. # remove tags & content
  703. html.find('font, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head, fieldset').remove()
  704. @removeAttributes(html)
  705. text = html.html()
  706. # as fallback, insert html via pasteHtmlAtCaret (for IE 11 and lower)
  707. if docType is 'text3'
  708. @pasteHtmlAtCaret(text)
  709. else
  710. document.execCommand('insertHTML', false, text)
  711. true
  712. )
  713. @input.on('drop', (e) =>
  714. e.stopPropagation()
  715. e.preventDefault()
  716. dataTransfer
  717. if window.dataTransfer # ie
  718. dataTransfer = window.dataTransfer
  719. else if e.originalEvent.dataTransfer # other browsers
  720. dataTransfer = e.originalEvent.dataTransfer
  721. else
  722. throw 'No clipboardData support'
  723. x = e.clientX
  724. y = e.clientY
  725. file = dataTransfer.files[0]
  726. # look for images
  727. if file.type.match('image.*')
  728. reader = new FileReader()
  729. reader.onload = (e) =>
  730. result = e.target.result
  731. img = document.createElement('img')
  732. img.src = result
  733. # Insert the image at the carat
  734. insert = (dataUrl, width, height, isRetina) =>
  735. # adapt image if we are on retina devices
  736. if @isRetina()
  737. width = width / 2
  738. height = height / 2
  739. result = dataUrl
  740. img = $("<img style=\"width: 100%; max-width: #{width}px;\" src=\"#{result}\">")
  741. img = img.get(0)
  742. if document.caretPositionFromPoint
  743. pos = document.caretPositionFromPoint(x, y)
  744. range = document.createRange()
  745. range.setStart(pos.offsetNode, pos.offset)
  746. range.collapse()
  747. range.insertNode(img)
  748. else if document.caretRangeFromPoint
  749. range = document.caretRangeFromPoint(x, y)
  750. range.insertNode(img)
  751. else
  752. console.log('could not find carat')
  753. # resize if to big
  754. @resizeImage(img.src, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
  755. reader.readAsDataURL(file)
  756. )
  757. $(window).on('beforeunload', =>
  758. @onLeaveTemporary()
  759. )
  760. $(window).on('hashchange', =>
  761. if @isOpen
  762. if @sessionId
  763. @send 'chat_session_notice',
  764. session_id: @sessionId
  765. message: window.location.href
  766. return
  767. @idleTimeout.start()
  768. )
  769. if @isFullscreen
  770. @input.on
  771. focus: @onFocus
  772. focusout: @onFocusOut
  773. stopPropagation: (event) ->
  774. event.stopPropagation()
  775. checkForEnter: (event) =>
  776. if not @inputDisabled and not event.shiftKey and event.keyCode is 13
  777. event.preventDefault()
  778. @sendMessage()
  779. send: (event, data = {}) =>
  780. data.chat_id = @options.chatId
  781. @io.send(event, data)
  782. onWebSocketMessage: (pipes) =>
  783. for pipe in pipes
  784. @log.debug 'ws:onmessage', pipe
  785. switch pipe.event
  786. when 'chat_error'
  787. @log.notice pipe.data
  788. if pipe.data && pipe.data.state is 'chat_disabled'
  789. @destroy(remove: true)
  790. when 'chat_session_message'
  791. return if pipe.data.self_written
  792. @receiveMessage pipe.data
  793. when 'chat_session_typing'
  794. return if pipe.data.self_written
  795. @onAgentTypingStart()
  796. when 'chat_session_start'
  797. @onConnectionEstablished pipe.data
  798. when 'chat_session_queue'
  799. @onQueueScreen pipe.data
  800. when 'chat_session_closed'
  801. @onSessionClosed pipe.data
  802. when 'chat_session_left'
  803. @onSessionClosed pipe.data
  804. when 'chat_session_notice'
  805. @addStatus @T(pipe.data.message)
  806. when 'chat_status_customer'
  807. switch pipe.data.state
  808. when 'online'
  809. @sessionId = undefined
  810. if !@options.cssAutoload || @cssLoaded
  811. @onReady()
  812. else
  813. @socketReady = true
  814. when 'offline'
  815. @onError 'Zammad Chat: No agent online'
  816. when 'chat_disabled'
  817. @onError 'Zammad Chat: Chat is disabled'
  818. when 'no_seats_available'
  819. @onError "Zammad Chat: Too many clients in queue. Clients in queue: #{pipe.data.queue}"
  820. when 'reconnect'
  821. @onReopenSession pipe.data
  822. onReady: ->
  823. @log.debug 'widget ready for use'
  824. $(".#{ @options.buttonClass }").on('click', @open).removeClass(@options.inactiveClass)
  825. @options.onReady?()
  826. if @options.show
  827. @show()
  828. onError: (message) =>
  829. @log.debug message
  830. @addStatus(message)
  831. $(".#{ @options.buttonClass }").hide()
  832. if @isOpen
  833. @disableInput()
  834. @destroy(remove: false)
  835. else
  836. @destroy(remove: true)
  837. @options.onError?(message)
  838. onReopenSession: (data) =>
  839. @log.debug 'old messages', data.session
  840. @inactiveTimeout.start()
  841. unfinishedMessage = sessionStorage.getItem 'unfinished_message'
  842. # rerender chat history
  843. if data.agent
  844. @onConnectionEstablished(data)
  845. for message in data.session
  846. @renderMessage
  847. message: message.content
  848. id: message.id
  849. from: if message.created_by_id then 'agent' else 'customer'
  850. if unfinishedMessage
  851. @input.html(unfinishedMessage)
  852. # show wait list
  853. if data.position
  854. @onQueue data
  855. @show()
  856. @open()
  857. @scrollToBottom()
  858. if unfinishedMessage
  859. @input.trigger('focus')
  860. onInput: =>
  861. # remove unread-state from messages
  862. @el.find('.zammad-chat-message--unread')
  863. .removeClass 'zammad-chat-message--unread'
  864. sessionStorage.setItem 'unfinished_message', @input.html()
  865. @onTyping()
  866. onFocus: =>
  867. $(window).scrollTop(10)
  868. keyboardShown = $(window).scrollTop() > 0
  869. $(window).scrollTop(0)
  870. if keyboardShown
  871. @log.notice 'virtual keyboard shown'
  872. # on keyboard shown
  873. # can't measure visible area height :(
  874. onFocusOut: ->
  875. # on keyboard hidden
  876. onTyping: ->
  877. # send typing start event only every 1.5 seconds
  878. return if @isTyping && @isTyping > new Date(new Date().getTime() - 1500)
  879. @isTyping = new Date()
  880. @send 'chat_session_typing',
  881. session_id: @sessionId
  882. @inactiveTimeout.start()
  883. onSubmit: (event) =>
  884. event.preventDefault()
  885. @sendMessage()
  886. sendMessage: ->
  887. message = @input.html()
  888. return if !message
  889. @inactiveTimeout.start()
  890. sessionStorage.removeItem 'unfinished_message'
  891. messageElement = @view('message')
  892. message: message
  893. from: 'customer'
  894. id: @_messageCount++
  895. unreadClass: ''
  896. @maybeAddTimestamp()
  897. # add message before message typing loader
  898. if @el.find('.zammad-chat-message--typing').get(0)
  899. @lastAddedType = 'typing-placeholder'
  900. @el.find('.zammad-chat-message--typing').before messageElement
  901. else
  902. @lastAddedType = 'message--customer'
  903. @el.find('.zammad-chat-body').append messageElement
  904. @input.html('')
  905. @scrollToBottom()
  906. # send message event
  907. @send 'chat_session_message',
  908. content: message
  909. id: @_messageCount
  910. session_id: @sessionId
  911. receiveMessage: (data) =>
  912. @inactiveTimeout.start()
  913. # hide writing indicator
  914. @onAgentTypingEnd()
  915. @maybeAddTimestamp()
  916. @renderMessage
  917. message: data.message.content
  918. id: data.id
  919. from: 'agent'
  920. @scrollToBottom showHint: true
  921. renderMessage: (data) =>
  922. @lastAddedType = "message--#{ data.from }"
  923. data.unreadClass = if document.hidden then ' zammad-chat-message--unread' else ''
  924. @el.find('.zammad-chat-body').append @view('message')(data)
  925. open: =>
  926. if @isOpen
  927. @log.debug 'widget already open, block'
  928. return
  929. @isOpen = true
  930. @log.debug 'open widget'
  931. @show()
  932. if !@sessionId
  933. @showLoader()
  934. @el.addClass('zammad-chat-is-open')
  935. remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
  936. @el.css 'bottom', -remainerHeight
  937. if !@sessionId
  938. @el.animate { bottom: 0 }, 500, @onOpenAnimationEnd
  939. @send('chat_session_init'
  940. url: window.location.href
  941. )
  942. else
  943. @el.css 'bottom', 0
  944. @onOpenAnimationEnd()
  945. onOpenAnimationEnd: =>
  946. @idleTimeout.stop()
  947. if @isFullscreen
  948. @disableScrollOnRoot()
  949. @options.onOpenAnimationEnd?()
  950. sessionClose: =>
  951. # send close
  952. @send 'chat_session_close',
  953. session_id: @sessionId
  954. # stop timer
  955. @inactiveTimeout.stop()
  956. @waitingListTimeout.stop()
  957. # delete input store
  958. sessionStorage.removeItem 'unfinished_message'
  959. # stop delay of initial queue position
  960. if @onInitialQueueDelayId
  961. clearTimeout(@onInitialQueueDelayId)
  962. @setSessionId undefined
  963. toggle: (event) =>
  964. if @isOpen
  965. @close(event)
  966. else
  967. @open(event)
  968. close: (event) =>
  969. if !@isOpen
  970. @log.debug 'can\'t close widget, it\'s not open'
  971. return
  972. if @initDelayId
  973. clearTimeout(@initDelayId)
  974. if @sessionId
  975. @log.debug 'session close before widget close'
  976. @sessionClose()
  977. @log.debug 'close widget'
  978. event.stopPropagation() if event
  979. if @isFullscreen
  980. @enableScrollOnRoot()
  981. # close window
  982. remainerHeight = @el.height() - @el.find('.zammad-chat-header').outerHeight()
  983. @el.animate { bottom: -remainerHeight }, 500, @onCloseAnimationEnd
  984. onCloseAnimationEnd: =>
  985. @el.css 'bottom', ''
  986. @el.removeClass('zammad-chat-is-open')
  987. @showLoader()
  988. @el.find('.zammad-chat-welcome').removeClass('zammad-chat-is-hidden')
  989. @el.find('.zammad-chat-agent').addClass('zammad-chat-is-hidden')
  990. @el.find('.zammad-chat-agent-status').addClass('zammad-chat-is-hidden')
  991. @isOpen = false
  992. @options.onCloseAnimationEnd?()
  993. @io.reconnect()
  994. onWebSocketClose: =>
  995. return if @isOpen
  996. if @el
  997. @el.removeClass('zammad-chat-is-shown')
  998. @el.removeClass('zammad-chat-is-loaded')
  999. show: ->
  1000. return if @state is 'offline'
  1001. @el.addClass('zammad-chat-is-loaded')
  1002. @el.addClass('zammad-chat-is-shown')
  1003. disableInput: ->
  1004. @inputDisabled = true
  1005. @input.prop('contenteditable', false)
  1006. @el.find('.zammad-chat-send').prop('disabled', true)
  1007. @io.close()
  1008. enableInput: ->
  1009. @inputDisabled = false
  1010. @input.prop('contenteditable', true)
  1011. @el.find('.zammad-chat-send').prop('disabled', false)
  1012. hideModal: ->
  1013. @el.find('.zammad-chat-modal').html ''
  1014. onQueueScreen: (data) =>
  1015. @setSessionId data.session_id
  1016. # delay initial queue position, show connecting first
  1017. show = =>
  1018. @onQueue data
  1019. @waitingListTimeout.start()
  1020. if @initialQueueDelay && !@onInitialQueueDelayId
  1021. @onInitialQueueDelayId = setTimeout(show, @initialQueueDelay)
  1022. return
  1023. # stop delay of initial queue position
  1024. if @onInitialQueueDelayId
  1025. clearTimeout(@onInitialQueueDelayId)
  1026. # show queue position
  1027. show()
  1028. onQueue: (data) =>
  1029. @log.notice 'onQueue', data.position
  1030. @inQueue = true
  1031. @el.find('.zammad-chat-modal').html @view('waiting')
  1032. position: data.position
  1033. onAgentTypingStart: =>
  1034. if @stopTypingId
  1035. clearTimeout(@stopTypingId)
  1036. @stopTypingId = setTimeout(@onAgentTypingEnd, 3000)
  1037. # never display two typing indicators
  1038. return if @el.find('.zammad-chat-message--typing').get(0)
  1039. @maybeAddTimestamp()
  1040. @el.find('.zammad-chat-body').append @view('typingIndicator')()
  1041. # only if typing indicator is shown
  1042. return if !@isVisible(@el.find('.zammad-chat-message--typing'), true)
  1043. @scrollToBottom()
  1044. onAgentTypingEnd: =>
  1045. @el.find('.zammad-chat-message--typing').remove()
  1046. onLeaveTemporary: =>
  1047. return if !@sessionId
  1048. @send 'chat_session_leave_temporary',
  1049. session_id: @sessionId
  1050. maybeAddTimestamp: ->
  1051. timestamp = Date.now()
  1052. if !@lastTimestamp or (timestamp - @lastTimestamp) > @showTimeEveryXMinutes * 60000
  1053. label = @T('Today')
  1054. time = new Date().toTimeString().substr 0,5
  1055. if @lastAddedType is 'timestamp'
  1056. # update last time
  1057. @updateLastTimestamp label, time
  1058. @lastTimestamp = timestamp
  1059. else
  1060. # add new timestamp
  1061. @el.find('.zammad-chat-body').append @view('timestamp')
  1062. label: label
  1063. time: time
  1064. @lastTimestamp = timestamp
  1065. @lastAddedType = 'timestamp'
  1066. @scrollToBottom()
  1067. updateLastTimestamp: (label, time) ->
  1068. return if !@el
  1069. @el.find('.zammad-chat-body')
  1070. .find('.zammad-chat-timestamp')
  1071. .last()
  1072. .replaceWith @view('timestamp')
  1073. label: label
  1074. time: time
  1075. addStatus: (status) ->
  1076. return if !@el
  1077. @maybeAddTimestamp()
  1078. @el.find('.zammad-chat-body').append @view('status')
  1079. status: status
  1080. @scrollToBottom()
  1081. detectScrolledtoBottom: =>
  1082. scrollBottom = @el.find('.zammad-chat-body').scrollTop() + @el.find('.zammad-chat-body').outerHeight()
  1083. @scrolledToBottom = Math.abs(scrollBottom - @el.find('.zammad-chat-body').prop('scrollHeight')) <= @scrollSnapTolerance
  1084. @el.find('.zammad-scroll-hint').addClass('is-hidden') if @scrolledToBottom
  1085. showScrollHint: ->
  1086. @el.find('.zammad-scroll-hint').removeClass('is-hidden')
  1087. # compensate scroll
  1088. @el.find('.zammad-chat-body').scrollTop(@el.find('.zammad-chat-body').scrollTop() + @el.find('.zammad-scroll-hint').outerHeight())
  1089. onScrollHintClick: =>
  1090. # animate scroll
  1091. @el.find('.zammad-chat-body').animate({scrollTop: @el.find('.zammad-chat-body').prop('scrollHeight')}, 300)
  1092. scrollToBottom: ({ showHint } = { showHint: false }) ->
  1093. if @scrolledToBottom
  1094. @el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
  1095. else if showHint
  1096. @showScrollHint()
  1097. destroy: (params = {}) =>
  1098. @log.debug 'destroy widget', params
  1099. @setAgentOnlineState 'offline'
  1100. if params.remove && @el
  1101. @el.remove()
  1102. # Remove button, because it can no longer be used.
  1103. $(".#{ @options.buttonClass }").hide()
  1104. # stop all timer
  1105. if @waitingListTimeout
  1106. @waitingListTimeout.stop()
  1107. if @inactiveTimeout
  1108. @inactiveTimeout.stop()
  1109. if @idleTimeout
  1110. @idleTimeout.stop()
  1111. # stop ws connection
  1112. @io.close()
  1113. reconnect: =>
  1114. # set status to connecting
  1115. @log.notice 'reconnecting'
  1116. @disableInput()
  1117. @lastAddedType = 'status'
  1118. @setAgentOnlineState 'connecting'
  1119. @addStatus @T('Connection lost')
  1120. onConnectionReestablished: =>
  1121. # set status back to online
  1122. @lastAddedType = 'status'
  1123. @setAgentOnlineState 'online'
  1124. @addStatus @T('Connection re-established')
  1125. @options.onConnectionReestablished?()
  1126. onSessionClosed: (data) ->
  1127. @addStatus @T('Chat closed by %s', data.realname)
  1128. @disableInput()
  1129. @setAgentOnlineState 'offline'
  1130. @inactiveTimeout.stop()
  1131. @options.onSessionClosed?(data)
  1132. setSessionId: (id) =>
  1133. @sessionId = id
  1134. if id is undefined
  1135. sessionStorage.removeItem 'sessionId'
  1136. else
  1137. sessionStorage.setItem 'sessionId', id
  1138. onConnectionEstablished: (data) =>
  1139. # stop delay of initial queue position
  1140. if @onInitialQueueDelayId
  1141. clearTimeout @onInitialQueueDelayId
  1142. @inQueue = false
  1143. if data.agent
  1144. @agent = data.agent
  1145. if data.session_id
  1146. @setSessionId data.session_id
  1147. # empty old messages
  1148. @el.find('.zammad-chat-body').html('')
  1149. @el.find('.zammad-chat-agent').html @view('agent')
  1150. agent: @agent
  1151. @enableInput()
  1152. @hideModal()
  1153. @el.find('.zammad-chat-welcome').addClass('zammad-chat-is-hidden')
  1154. @el.find('.zammad-chat-agent').removeClass('zammad-chat-is-hidden')
  1155. @el.find('.zammad-chat-agent-status').removeClass('zammad-chat-is-hidden')
  1156. @input.trigger('focus') if not @isFullscreen
  1157. @setAgentOnlineState 'online'
  1158. @waitingListTimeout.stop()
  1159. @idleTimeout.stop()
  1160. @inactiveTimeout.start()
  1161. @options.onConnectionEstablished?(data)
  1162. showCustomerTimeout: ->
  1163. @el.find('.zammad-chat-modal').html @view('customer_timeout')
  1164. agent: @agent.name
  1165. delay: @options.inactiveTimeout
  1166. reload = ->
  1167. location.reload()
  1168. @el.find('.js-restart').on 'click', reload
  1169. @sessionClose()
  1170. showWaitingListTimeout: ->
  1171. @el.find('.zammad-chat-modal').html @view('waiting_list_timeout')
  1172. delay: @options.watingListTimeout
  1173. reload = ->
  1174. location.reload()
  1175. @el.find('.js-restart').on 'click', reload
  1176. @sessionClose()
  1177. showLoader: ->
  1178. @el.find('.zammad-chat-modal').html @view('loader')()
  1179. setAgentOnlineState: (state) =>
  1180. @state = state
  1181. return if !@el
  1182. capitalizedState = state.charAt(0).toUpperCase() + state.slice(1)
  1183. @el
  1184. .find('.zammad-chat-agent-status')
  1185. .attr('data-status', state)
  1186. .text @T(capitalizedState) # @T('Online') @T('Offline')
  1187. detectHost: ->
  1188. protocol = 'ws://'
  1189. if scriptProtocol is 'https'
  1190. protocol = 'wss://'
  1191. @options.host = "#{ protocol }#{ scriptHost }/ws"
  1192. loadCss: ->
  1193. return if !@options.cssAutoload
  1194. url = @options.cssUrl
  1195. if !url
  1196. url = @options.host
  1197. .replace(/^wss/i, 'https')
  1198. .replace(/^ws/i, 'http')
  1199. .replace(/\/ws$/i, '') # WebSocket may run on example.com/ws path
  1200. url += '/assets/chat/chat.css'
  1201. @log.debug "load css from '#{url}'"
  1202. styles = "@import url('#{url}');"
  1203. newSS = document.createElement('link')
  1204. newSS.onload = @onCssLoaded
  1205. newSS.rel = 'stylesheet'
  1206. newSS.href = 'data:text/css,' + escape(styles)
  1207. document.getElementsByTagName('head')[0].appendChild(newSS)
  1208. onCssLoaded: =>
  1209. @cssLoaded = true
  1210. if @socketReady
  1211. @onReady()
  1212. @options.onCssLoaded?()
  1213. startTimeoutObservers: =>
  1214. @idleTimeout = new Timeout(
  1215. logPrefix: 'idleTimeout'
  1216. debug: @options.debug
  1217. timeout: @options.idleTimeout
  1218. timeoutIntervallCheck: @options.idleTimeoutIntervallCheck
  1219. callback: =>
  1220. @log.debug 'Idle timeout reached, hide widget', new Date
  1221. @destroy(remove: true)
  1222. )
  1223. @inactiveTimeout = new Timeout(
  1224. logPrefix: 'inactiveTimeout'
  1225. debug: @options.debug
  1226. timeout: @options.inactiveTimeout
  1227. timeoutIntervallCheck: @options.inactiveTimeoutIntervallCheck
  1228. callback: =>
  1229. @log.debug 'Inactive timeout reached, show timeout screen.', new Date
  1230. @showCustomerTimeout()
  1231. @destroy(remove: false)
  1232. )
  1233. @waitingListTimeout = new Timeout(
  1234. logPrefix: 'waitingListTimeout'
  1235. debug: @options.debug
  1236. timeout: @options.waitingListTimeout
  1237. timeoutIntervallCheck: @options.waitingListTimeoutIntervallCheck
  1238. callback: =>
  1239. @log.debug 'Waiting list timeout reached, show timeout screen.', new Date
  1240. @showWaitingListTimeout()
  1241. @destroy(remove: false)
  1242. )
  1243. disableScrollOnRoot: ->
  1244. @rootScrollOffset = @scrollRoot.scrollTop()
  1245. @scrollRoot.css
  1246. overflow: 'hidden'
  1247. position: 'fixed'
  1248. enableScrollOnRoot: ->
  1249. @scrollRoot.scrollTop @rootScrollOffset
  1250. @scrollRoot.css
  1251. overflow: ''
  1252. position: ''
  1253. # based on https://github.com/customd/jquery-visible/blob/master/jquery.visible.js
  1254. # to have not dependency, port to coffeescript
  1255. isVisible: (el, partial, hidden, direction) ->
  1256. return if el.length < 1
  1257. $w = $(window)
  1258. $t = if el.length > 1 then el.eq(0) else el
  1259. t = $t.get(0)
  1260. vpWidth = $w.width()
  1261. vpHeight = $w.height()
  1262. direction = if direction then direction else 'both'
  1263. clientSize = if hidden is true then t.offsetWidth * t.offsetHeight else true
  1264. if typeof t.getBoundingClientRect is 'function'
  1265. # Use this native browser method, if available.
  1266. rec = t.getBoundingClientRect()
  1267. tViz = rec.top >= 0 && rec.top < vpHeight
  1268. bViz = rec.bottom > 0 && rec.bottom <= vpHeight
  1269. lViz = rec.left >= 0 && rec.left < vpWidth
  1270. rViz = rec.right > 0 && rec.right <= vpWidth
  1271. vVisible = if partial then tViz || bViz else tViz && bViz
  1272. hVisible = if partial then lViz || rViz else lViz && rViz
  1273. if direction is 'both'
  1274. return clientSize && vVisible && hVisible
  1275. else if direction is 'vertical'
  1276. return clientSize && vVisible
  1277. else if direction is 'horizontal'
  1278. return clientSize && hVisible
  1279. else
  1280. viewTop = $w.scrollTop()
  1281. viewBottom = viewTop + vpHeight
  1282. viewLeft = $w.scrollLeft()
  1283. viewRight = viewLeft + vpWidth
  1284. offset = $t.offset()
  1285. _top = offset.top
  1286. _bottom = _top + $t.height()
  1287. _left = offset.left
  1288. _right = _left + $t.width()
  1289. compareTop = if partial is true then _bottom else _top
  1290. compareBottom = if partial is true then _top else _bottom
  1291. compareLeft = if partial is true then _right else _left
  1292. compareRight = if partial is true then _left else _right
  1293. if direction is 'both'
  1294. return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop)) && ((compareRight <= viewRight) && (compareLeft >= viewLeft))
  1295. else if direction is 'vertical'
  1296. return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop))
  1297. else if direction is 'horizontal'
  1298. return !!clientSize && ((compareRight <= viewRight) && (compareLeft >= viewLeft))
  1299. isRetina: ->
  1300. if window.matchMedia
  1301. 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)')
  1302. return (mq && mq.matches || (window.devicePixelRatio > 1))
  1303. false
  1304. resizeImage: (dataURL, x = 'auto', y = 'auto', sizeFactor = 1, type, quallity, callback, force = true) ->
  1305. # load image from data url
  1306. imageObject = new Image()
  1307. imageObject.onload = ->
  1308. imageWidth = imageObject.width
  1309. imageHeight = imageObject.height
  1310. console.log('ImageService', 'current size', imageWidth, imageHeight)
  1311. if y is 'auto' && x is 'auto'
  1312. x = imageWidth
  1313. y = imageHeight
  1314. # get auto dimensions
  1315. if y is 'auto'
  1316. factor = imageWidth / x
  1317. y = imageHeight / factor
  1318. if x is 'auto'
  1319. factor = imageWidth / y
  1320. x = imageHeight / factor
  1321. # check if resize is needed
  1322. resize = false
  1323. if x < imageWidth || y < imageHeight
  1324. resize = true
  1325. x = x * sizeFactor
  1326. y = y * sizeFactor
  1327. else
  1328. x = imageWidth
  1329. y = imageHeight
  1330. # create canvas and set dimensions
  1331. canvas = document.createElement('canvas')
  1332. canvas.width = x
  1333. canvas.height = y
  1334. # draw image on canvas and set image dimensions
  1335. context = canvas.getContext('2d')
  1336. context.drawImage(imageObject, 0, 0, x, y)
  1337. # set quallity based on image size
  1338. if quallity == 'auto'
  1339. if x < 200 && y < 200
  1340. quallity = 1
  1341. else if x < 400 && y < 400
  1342. quallity = 0.9
  1343. else if x < 600 && y < 600
  1344. quallity = 0.8
  1345. else if x < 900 && y < 900
  1346. quallity = 0.7
  1347. else
  1348. quallity = 0.6
  1349. # execute callback with resized image
  1350. newDataUrl = canvas.toDataURL(type, quallity)
  1351. if resize
  1352. console.log('ImageService', 'resize', x/sizeFactor, y/sizeFactor, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
  1353. callback(newDataUrl, x/sizeFactor, y/sizeFactor, true)
  1354. return
  1355. console.log('ImageService', 'no resize', x, y, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
  1356. callback(newDataUrl, x, y, false)
  1357. # load image from data url
  1358. imageObject.src = dataURL
  1359. # taken from https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
  1360. pasteHtmlAtCaret: (html) ->
  1361. sel = undefined
  1362. range = undefined
  1363. if window.getSelection
  1364. sel = window.getSelection()
  1365. if sel.getRangeAt && sel.rangeCount
  1366. range = sel.getRangeAt(0)
  1367. range.deleteContents()
  1368. el = document.createElement('div')
  1369. el.innerHTML = html
  1370. frag = document.createDocumentFragment(node, lastNode)
  1371. while node = el.firstChild
  1372. lastNode = frag.appendChild(node)
  1373. range.insertNode(frag)
  1374. if lastNode
  1375. range = range.cloneRange()
  1376. range.setStartAfter(lastNode)
  1377. range.collapse(true)
  1378. sel.removeAllRanges()
  1379. sel.addRange(range)
  1380. else if document.selection && document.selection.type != 'Control'
  1381. document.selection.createRange().pasteHTML(html)
  1382. # (C) sbrin - https://github.com/sbrin
  1383. # https://gist.github.com/sbrin/6801034
  1384. wordFilter: (editor) ->
  1385. content = editor.html()
  1386. # Word comments like conditional comments etc
  1387. content = content.replace(/<!--[\s\S]+?-->/gi, '')
  1388. # Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
  1389. # MS Office namespaced tags, and a few other tags
  1390. content = content.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '')
  1391. # Convert <s> into <strike> for line-though
  1392. content = content.replace(/<(\/?)s>/gi, '<$1strike>')
  1393. # Replace nbsp entites to char since it's easier to handle
  1394. # content = content.replace(/&nbsp;/gi, "\u00a0")
  1395. content = content.replace(/&nbsp;/gi, ' ')
  1396. # Convert <span style="mso-spacerun:yes">___</span> to string of alternating
  1397. # breaking/non-breaking spaces of same length
  1398. #content = content.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, (str, spaces) ->
  1399. # return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ''
  1400. #)
  1401. editor.html(content)
  1402. # Parse out list indent level for lists
  1403. $('p', editor).each( ->
  1404. str = $(@).attr('style')
  1405. matches = /mso-list:\w+ \w+([0-9]+)/.exec(str)
  1406. if matches
  1407. $(@).data('_listLevel', parseInt(matches[1], 10))
  1408. )
  1409. # Parse Lists
  1410. last_level = 0
  1411. pnt = null
  1412. $('p', editor).each(->
  1413. cur_level = $(@).data('_listLevel')
  1414. if cur_level != undefined
  1415. txt = $(@).text()
  1416. list_tag = '<ul></ul>'
  1417. if (/^\s*\w+\./.test(txt))
  1418. matches = /([0-9])\./.exec(txt)
  1419. if matches
  1420. start = parseInt(matches[1], 10)
  1421. list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>'
  1422. else
  1423. list_tag = '<ol></ol>'
  1424. if cur_level > last_level
  1425. if last_level == 0
  1426. $(@).before(list_tag)
  1427. pnt = $(@).prev()
  1428. else
  1429. pnt = $(list_tag).appendTo(pnt)
  1430. if cur_level < last_level
  1431. for i in [i..last_level-cur_level]
  1432. pnt = pnt.parent()
  1433. $('span:first', @).remove()
  1434. pnt.append('<li>' + $(@).html() + '</li>')
  1435. $(@).remove()
  1436. last_level = cur_level
  1437. else
  1438. last_level = 0
  1439. )
  1440. $('[style]', editor).removeAttr('style')
  1441. $('[align]', editor).removeAttr('align')
  1442. $('span', editor).replaceWith(->
  1443. $(@).contents()
  1444. )
  1445. $('span:empty', editor).remove()
  1446. $("[class^='Mso']", editor).removeAttr('class')
  1447. $('p:empty', editor).remove()
  1448. editor
  1449. removeAttribute: (element) ->
  1450. return if !element
  1451. $element = $(element)
  1452. for att in element.attributes
  1453. if att && att.name
  1454. element.removeAttribute(att.name)
  1455. #$element.removeAttr(att.name)
  1456. $element.removeAttr('style')
  1457. .removeAttr('class')
  1458. .removeAttr('lang')
  1459. .removeAttr('type')
  1460. .removeAttr('align')
  1461. .removeAttr('id')
  1462. .removeAttr('wrap')
  1463. .removeAttr('title')
  1464. removeAttributes: (html, parent = true) =>
  1465. if parent
  1466. html.each((index, element) => @removeAttribute(element) )
  1467. html.find('*').each((index, element) => @removeAttribute(element) )
  1468. html
  1469. window.ZammadChat = ZammadChat