chat-no-jquery.coffee 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695
  1. do(window) ->
  2. scripts = document.getElementsByTagName('script')
  3. # search for script to get protocol and hostname for ws connection
  4. myScript = scripts[scripts.length - 1]
  5. scriptProtocol = window.location.protocol.replace(':', '') # set default protocol
  6. if myScript && myScript.src
  7. scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
  8. scriptProtocol = myScript.src.match('(.*)://[^:/]*.*')[1]
  9. # Define the plugin class
  10. class Core
  11. defaults:
  12. debug: false
  13. constructor: (options) ->
  14. @options = {}
  15. for key, value of @defaults
  16. @options[key] = value
  17. for key, value of options
  18. @options[key] = value
  19. class Base extends Core
  20. constructor: (options) ->
  21. super(options)
  22. @log = new Log(debug: @options.debug, logPrefix: @options.logPrefix || @logPrefix)
  23. class Log extends Core
  24. debug: (items...) =>
  25. return if !@options.debug
  26. @log('debug', items)
  27. notice: (items...) =>
  28. @log('notice', items)
  29. error: (items...) =>
  30. @log('error', items)
  31. log: (level, items) =>
  32. items.unshift('||')
  33. items.unshift(level)
  34. items.unshift(@options.logPrefix)
  35. console.log.apply console, items
  36. return if !@options.debug
  37. logString = ''
  38. for item in items
  39. logString += ' '
  40. if typeof item is 'object'
  41. logString += JSON.stringify(item)
  42. else if item && item.toString
  43. logString += item.toString()
  44. else
  45. logString += item
  46. element = document.querySelector('.js-chatLogDisplay')
  47. if element
  48. element.innerHTML = '<div>' + logString + '</div>' + element.innerHTML
  49. class Timeout extends Base
  50. timeoutStartedAt: null
  51. logPrefix: 'timeout'
  52. defaults:
  53. debug: false
  54. timeout: 4
  55. timeoutIntervallCheck: 0.5
  56. start: =>
  57. @stop()
  58. timeoutStartedAt = new Date
  59. check = =>
  60. timeLeft = new Date - new Date(timeoutStartedAt.getTime() + @options.timeout * 1000 * 60)
  61. @log.debug "Timeout check for #{@options.timeout} minutes (left #{timeLeft/1000} sec.)"#, new Date
  62. return if timeLeft < 0
  63. @stop()
  64. @options.callback()
  65. @log.debug "Start timeout in #{@options.timeout} minutes"#, new Date
  66. @intervallId = setInterval(check, @options.timeoutIntervallCheck * 1000 * 60)
  67. stop: =>
  68. return if !@intervallId
  69. @log.debug "Stop timeout of #{@options.timeout} minutes"#, new Date
  70. clearInterval(@intervallId)
  71. class Io extends Base
  72. logPrefix: 'io'
  73. set: (params) =>
  74. for key, value of params
  75. @options[key] = value
  76. connect: =>
  77. @log.debug "Connecting to #{@options.host}"
  78. @ws = new window.WebSocket("#{@options.host}")
  79. @ws.onopen = (e) =>
  80. @log.debug 'onOpen', e
  81. @options.onOpen(e)
  82. @ping()
  83. @ws.onmessage = (e) =>
  84. pipes = JSON.parse(e.data)
  85. @log.debug 'onMessage', e.data
  86. for pipe in pipes
  87. if pipe.event is 'pong'
  88. @ping()
  89. if @options.onMessage
  90. @options.onMessage(pipes)
  91. @ws.onclose = (e) =>
  92. @log.debug 'close websocket connection', e
  93. if @pingDelayId
  94. clearTimeout(@pingDelayId)
  95. if @manualClose
  96. @log.debug 'manual close, onClose callback'
  97. @manualClose = false
  98. if @options.onClose
  99. @options.onClose(e)
  100. else
  101. @log.debug 'error close, onError callback'
  102. if @options.onError
  103. @options.onError('Connection lost...')
  104. @ws.onerror = (e) =>
  105. @log.debug 'onError', e
  106. if @options.onError
  107. @options.onError(e)
  108. close: =>
  109. @log.debug 'close websocket manually'
  110. @manualClose = true
  111. @ws.close()
  112. reconnect: =>
  113. @log.debug 'reconnect'
  114. @close()
  115. @connect()
  116. send: (event, data = {}) =>
  117. @log.debug 'send', event, data
  118. msg = JSON.stringify
  119. event: event
  120. data: data
  121. @ws.send msg
  122. ping: =>
  123. localPing = =>
  124. @send('ping')
  125. @pingDelayId = setTimeout(localPing, 29000)
  126. class ZammadChat extends Base
  127. defaults:
  128. chatId: undefined
  129. show: true
  130. target: document.querySelector('body')
  131. host: ''
  132. debug: false
  133. flat: false
  134. lang: undefined
  135. cssAutoload: true
  136. cssUrl: undefined
  137. fontSize: undefined
  138. buttonClass: 'open-zammad-chat'
  139. inactiveClass: 'is-inactive'
  140. title: '<strong>Chat</strong> with us!'
  141. scrollHint: 'Scroll down to see new messages'
  142. idleTimeout: 6
  143. idleTimeoutIntervallCheck: 0.5
  144. inactiveTimeout: 8
  145. inactiveTimeoutIntervallCheck: 0.5
  146. waitingListTimeout: 4
  147. waitingListTimeoutIntervallCheck: 0.5
  148. # Callbacks
  149. onReady: undefined
  150. onCloseAnimationEnd: undefined
  151. onError: undefined
  152. onOpenAnimationEnd: undefined
  153. onConnectionReestablished: undefined
  154. onSessionClosed: undefined
  155. onConnectionEstablished: undefined
  156. onCssLoaded: undefined
  157. logPrefix: 'chat'
  158. _messageCount: 0
  159. isOpen: false
  160. blinkOnlineInterval: null
  161. stopBlinOnlineStateTimeout: null
  162. showTimeEveryXMinutes: 2
  163. lastTimestamp: null
  164. lastAddedType: null
  165. inputDisabled: false
  166. inputTimeout: null
  167. isTyping: false
  168. state: 'offline'
  169. initialQueueDelay: 10000
  170. translations:
  171. # ZAMMAD_TRANSLATIONS_START
  172. 'cs':
  173. '<strong>Chat</strong> with us!': '<strong>Chatujte</strong> s námi!'
  174. 'All colleagues are busy.': 'Všichni kolegové jsou vytíženi.'
  175. 'Chat closed by %s': '%s ukončil konverzaci'
  176. 'Compose your message…': 'Napište svou zprávu…'
  177. 'Connecting': 'Připojování'
  178. 'Connection lost': 'Připojení ztraceno'
  179. 'Connection re-established': 'Připojení obnoveno'
  180. 'Offline': 'Offline'
  181. 'Online': 'Online'
  182. 'Scroll down to see new messages': 'Srolujte dolů pro zobrazení nových zpráv'
  183. 'Send': 'Odeslat'
  184. '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.'
  185. '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.'
  186. 'Start new conversation': 'Zahájit novou konverzaci'
  187. 'Today': 'Dnes'
  188. '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!'
  189. 'You are on waiting list position <strong>%s</strong>.': 'Jste <strong>%s</strong>. v pořadí na čekací listině.'
  190. 'da':
  191. '<strong>Chat</strong> with us!': '<strong>Chat</strong> med os!'
  192. 'All colleagues are busy.': 'Alle medarbejdere er optaget.'
  193. 'Chat closed by %s': 'Chat lukket af %s'
  194. 'Compose your message…': 'Skriv din besked…'
  195. 'Connecting': 'Forbinder'
  196. 'Connection lost': 'Forbindelse mistet'
  197. 'Connection re-established': 'Forbindelse genoprettet'
  198. 'Offline': 'Offline'
  199. 'Online': 'Online'
  200. 'Scroll down to see new messages': 'Scroll ned for at se nye beskeder'
  201. 'Send': 'Afsend'
  202. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': ''
  203. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': ''
  204. 'Start new conversation': 'Start en ny samtale'
  205. 'Today': 'I dag'
  206. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': ''
  207. 'You are on waiting list position <strong>%s</strong>.': 'Du er i kø som nummer <strong>%s</strong>.'
  208. 'de':
  209. '<strong>Chat</strong> with us!': '<strong>Chatte</strong> mit uns!'
  210. 'All colleagues are busy.': 'Alle Kollegen sind beschäftigt.'
  211. 'Chat closed by %s': 'Chat von %s geschlossen'
  212. 'Compose your message…': 'Verfassen Sie Ihre Nachricht…'
  213. 'Connecting': 'Verbinde'
  214. 'Connection lost': 'Verbindung verloren'
  215. 'Connection re-established': 'Verbindung wieder aufgebaut'
  216. 'Offline': 'Offline'
  217. 'Online': 'Online'
  218. 'Scroll down to see new messages': 'Nach unten scrollen um neue Nachrichten zu sehen'
  219. 'Send': 'Senden'
  220. '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.'
  221. '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.'
  222. 'Start new conversation': 'Neue Unterhaltung starten'
  223. 'Today': 'Heute'
  224. '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!'
  225. 'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste auf Position <strong>%s</strong>.'
  226. 'es':
  227. '<strong>Chat</strong> with us!': '<strong>Chatee</strong> con nosotros!'
  228. 'All colleagues are busy.': 'Todos los colegas están ocupados.'
  229. 'Chat closed by %s': 'Chat cerrado por %s'
  230. 'Compose your message…': 'Escribe tu mensaje…'
  231. 'Connecting': 'Conectando'
  232. 'Connection lost': 'Conexión perdida'
  233. 'Connection re-established': 'Conexión reestablecida'
  234. 'Offline': 'Desconectado'
  235. 'Online': 'En línea'
  236. 'Scroll down to see new messages': 'Desplace hacia abajo para ver nuevos mensajes'
  237. 'Send': 'Enviar'
  238. '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.'
  239. '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.'
  240. 'Start new conversation': 'Iniciar nueva conversación'
  241. 'Today': 'Hoy'
  242. '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!'
  243. 'You are on waiting list position <strong>%s</strong>.': 'Usted está en la posición <strong>%s</strong> de la lista de espera.'
  244. 'fr':
  245. '<strong>Chat</strong> with us!': '<strong>Chattez</strong> avec nous !'
  246. 'All colleagues are busy.': 'Tous les agents sont occupés.'
  247. 'Chat closed by %s': 'Chat fermé par %s'
  248. 'Compose your message…': 'Écrivez votre message…'
  249. 'Connecting': 'Connexion'
  250. 'Connection lost': 'Connexion perdue'
  251. 'Connection re-established': 'Connexion ré-établie'
  252. 'Offline': 'Hors-ligne'
  253. 'Online': 'En ligne'
  254. 'Scroll down to see new messages': 'Défiler vers le bas pour voir les nouveaux messages'
  255. 'Send': 'Envoyer'
  256. '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.'
  257. '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.'
  258. 'Start new conversation': 'Démarrer une nouvelle conversation'
  259. 'Today': 'Aujourd\'hui'
  260. '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 !'
  261. 'You are on waiting list position <strong>%s</strong>.': 'Vous êtes actuellement en position <strong>%s</strong> dans la file d\'attente.'
  262. 'hr':
  263. '<strong>Chat</strong> with us!': '<strong>Čavrljajte</strong> sa nama!'
  264. 'All colleagues are busy.': 'Svi agenti su zauzeti.'
  265. 'Chat closed by %s': '%s zatvara chat'
  266. 'Compose your message…': 'Sastavite poruku…'
  267. 'Connecting': 'Povezivanje'
  268. 'Connection lost': 'Veza prekinuta'
  269. 'Connection re-established': 'Veza je ponovno uspostavljena'
  270. 'Offline': 'Odsutan'
  271. 'Online': 'Dostupan(a)'
  272. 'Scroll down to see new messages': 'Pomaknite se prema dolje da biste vidjeli nove poruke'
  273. 'Send': 'Pošalji'
  274. '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.'
  275. '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.'
  276. 'Start new conversation': 'Započni novi razgovor'
  277. 'Today': 'Danas'
  278. '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, traje duže nego inače za dobiti slobodan termin. Molimo, pokušajte ponovno kasnije ili nam pošaljite e-mail. Hvala!'
  279. 'You are on waiting list position <strong>%s</strong>.': 'Nalazite se u redu čekanja na poziciji <strong>%s</strong>.'
  280. 'hu':
  281. '<strong>Chat</strong> with us!': '<strong>Csevegjen</strong> velünk!'
  282. 'All colleagues are busy.': 'Minden munkatársunk foglalt.'
  283. 'Chat closed by %s': 'A csevegés %s által lezárva'
  284. 'Compose your message…': 'Fogalmazza meg üzenetét…'
  285. 'Connecting': 'Csatlakozás'
  286. 'Connection lost': 'A kapcsolat megszakadt'
  287. 'Connection re-established': 'A kapcsolat helyreállt'
  288. 'Offline': 'Offline'
  289. 'Online': 'Online'
  290. 'Scroll down to see new messages': 'Görgessen lefelé az új üzenetek megtekintéséhez'
  291. 'Send': 'Küldés'
  292. '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.'
  293. '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.'
  294. 'Start new conversation': 'Új beszélgetés indítása'
  295. 'Today': 'Ma'
  296. '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!'
  297. 'You are on waiting list position <strong>%s</strong>.': 'Ön a várólistán a <strong>%s</strong> helyen szerepel.'
  298. 'it':
  299. '<strong>Chat</strong> with us!': '<strong>Chatta</strong> con noi!'
  300. 'All colleagues are busy.': 'Tutti i colleghi sono occupati.'
  301. 'Chat closed by %s': 'Chat chiusa da %s'
  302. 'Compose your message…': 'Scrivi il tuo messaggio…'
  303. 'Connecting': 'Connessione in corso'
  304. 'Connection lost': 'Connessione persa'
  305. 'Connection re-established': 'Connessione ristabilita'
  306. 'Offline': 'Offline'
  307. 'Online': 'Online'
  308. 'Scroll down to see new messages': 'Scorri verso il basso per vedere i nuovi messaggi'
  309. 'Send': 'Invia'
  310. '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.'
  311. '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.'
  312. 'Start new conversation': 'Avvia una nuova chat'
  313. 'Today': 'Oggi'
  314. '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!'
  315. 'You are on waiting list position <strong>%s</strong>.': 'Sei alla posizione <strong>%s</strong> della lista di attesa.'
  316. 'lt':
  317. '<strong>Chat</strong> with us!': '<strong>Kalbėkitės</strong> su mumis!'
  318. 'All colleagues are busy.': 'Visi kolegos užimti.'
  319. 'Chat closed by %s': '%s uždarė pokalbį'
  320. 'Compose your message…': 'Rašykite žinutę…'
  321. 'Connecting': 'Jungiamasi'
  322. 'Connection lost': 'Dingo ryšys'
  323. 'Connection re-established': 'Ryšys atnaujintas'
  324. 'Offline': 'Atsijungęs'
  325. 'Online': 'Prisijungęs'
  326. 'Scroll down to see new messages': 'Naujos žinutės žemiau'
  327. 'Send': 'Siųsti'
  328. '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ų.'
  329. '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ų.'
  330. 'Start new conversation': 'Pradėti naują pokalbį'
  331. 'Today': 'Šiandien'
  332. '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ū!'
  333. 'You are on waiting list position <strong>%s</strong>.': 'Esate <strong>%s</strong> eilėje.'
  334. 'nl':
  335. '<strong>Chat</strong> with us!': '<strong>Chat</strong> met ons!'
  336. 'All colleagues are busy.': 'Alle collega\'s zijn bezet.'
  337. 'Chat closed by %s': 'Chat gesloten door %s'
  338. 'Compose your message…': 'Stel je bericht op…'
  339. 'Connecting': 'Verbinden'
  340. 'Connection lost': 'Verbinding verbroken'
  341. 'Connection re-established': 'Verbinding hersteld'
  342. 'Offline': 'Offline'
  343. 'Online': 'Online'
  344. 'Scroll down to see new messages': 'Scroll naar beneden om nieuwe tickets te bekijken'
  345. 'Send': 'Verstuur'
  346. '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.'
  347. '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.'
  348. 'Start new conversation': 'Nieuw gesprek starten'
  349. 'Today': 'Vandaag'
  350. '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!'
  351. 'You are on waiting list position <strong>%s</strong>.': 'Je bevindt zich op wachtlijstpositie <strong>%s</strong>.'
  352. 'pl':
  353. '<strong>Chat</strong> with us!': '<strong>Czatuj</strong> z nami!'
  354. 'All colleagues are busy.': 'Wszyscy agenci są zajęci.'
  355. 'Chat closed by %s': 'Chat zamknięty przez %s'
  356. 'Compose your message…': 'Skomponuj swoją wiadomość…'
  357. 'Connecting': 'Łączenie'
  358. 'Connection lost': 'Utracono połączenie'
  359. 'Connection re-established': 'Ponowne nawiązanie połączenia'
  360. 'Offline': 'Offline'
  361. 'Online': 'Online'
  362. 'Scroll down to see new messages': 'Skroluj w dół, aby zobaczyć wiadomości'
  363. 'Send': 'Wyślij'
  364. '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.'
  365. '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.'
  366. 'Start new conversation': 'Rozpocznij nową rozmowę'
  367. 'Today': 'Dzisiaj'
  368. '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!'
  369. 'You are on waiting list position <strong>%s</strong>.': 'Jesteś na pozycji listy oczekujących <strong>%s</strong>.'
  370. 'pt-br':
  371. '<strong>Chat</strong> with us!': '<strong>Converse</strong> conosco!'
  372. 'All colleagues are busy.': 'Nossos atendentes estão ocupados.'
  373. 'Chat closed by %s': 'Chat encerrado por %s'
  374. 'Compose your message…': 'Escreva sua mensagem…'
  375. 'Connecting': 'Conectando'
  376. 'Connection lost': 'Conexão perdida'
  377. 'Connection re-established': 'Conexão restabelecida'
  378. 'Offline': 'Desconectado'
  379. 'Online': 'Online'
  380. 'Scroll down to see new messages': 'Rolar para baixo para ver novas mensagems'
  381. 'Send': 'Enviar'
  382. '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.'
  383. '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.'
  384. 'Start new conversation': 'Iniciar uma nova conversa'
  385. 'Today': 'Hoje'
  386. '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!'
  387. 'You are on waiting list position <strong>%s</strong>.': 'Você está na posição <strong>%s</strong> da lista de espera.'
  388. 'ro':
  389. '<strong>Chat</strong> with us!': '<strong>Comunică</strong> cu noi!'
  390. 'All colleagues are busy.': 'Toți colegii sunt ocupați momentan.'
  391. 'Chat closed by %s': 'Chat închis de către %s'
  392. 'Compose your message…': 'Compune-ți mesajul…'
  393. 'Connecting': 'Se conectează'
  394. 'Connection lost': 'Conexiune pierdută'
  395. 'Connection re-established': 'Conexiune restabilită'
  396. 'Offline': 'Offline'
  397. 'Online': 'Online'
  398. 'Scroll down to see new messages': 'Derulați în jos pentru a vedea mesajele noi'
  399. 'Send': 'Trimite'
  400. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Deoarece nu ai răspuns în ultimele %s minute, conversația ta a fost închisă.'
  401. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Deoarece nu ai răspuns în ultimele %s minute, conversația ta cu <strong>%s</strong> a fost închisă.'
  402. 'Start new conversation': 'Începe o conversație nouă'
  403. 'Today': 'Azi'
  404. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Ne pare rău, durează mai mult decât ne așteptam să obținem un loc. Te rugăm să încerci din nou mai târziu sau să ne trimiți un email. Mulțumim!'
  405. 'You are on waiting list position <strong>%s</strong>.': 'Aveți poziția <strong>%s</strong> în lista de așteptare.'
  406. 'ru':
  407. '<strong>Chat</strong> with us!': '<strong>Напишите</strong> нам!'
  408. 'All colleagues are busy.': 'Все коллеги заняты.'
  409. 'Chat closed by %s': 'Чат закрыт %s'
  410. 'Compose your message…': 'Составьте сообщение…'
  411. 'Connecting': 'Подключение'
  412. 'Connection lost': 'Подключение потеряно'
  413. 'Connection re-established': 'Подключение восстановлено'
  414. 'Offline': 'Оффлайн'
  415. 'Online': 'В сети'
  416. 'Scroll down to see new messages': 'Прокрутите вниз, чтобы увидеть новые сообщения'
  417. 'Send': 'Отправить'
  418. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Поскольку Вы не ответили в течение последних %s минут, Ваш разговор был закрыт.'
  419. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Поскольку Вы не ответили в течение последних %s минут, Ваш разговор с <strong>%s</strong> был закрыт.'
  420. 'Start new conversation': 'Начать новый разговор'
  421. 'Today': 'Сегодня'
  422. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Извините, получение свободного слота занимает больше времени, чем ожидалось. Пожалуйста, повторите попытку позже или отправьте нам электронное письмо. Благодарим Вас!'
  423. 'You are on waiting list position <strong>%s</strong>.': 'Вы находитесь в списке ожидания <strong>%s</strong>.'
  424. 'sk':
  425. '<strong>Chat</strong> with us!': '<strong>Napíšte</strong> nám cez chat!'
  426. 'All colleagues are busy.': 'Všetci kolegovia sú zaneprázdnení.'
  427. 'Chat closed by %s': 'Chat zatvoril(a) %s'
  428. 'Compose your message…': 'Napíšte vašu správu…'
  429. 'Connecting': 'Pripája sa'
  430. 'Connection lost': 'Spojenie prerušené'
  431. 'Connection re-established': 'Pripojenie obnovené'
  432. 'Offline': 'Offline'
  433. 'Online': 'Online'
  434. 'Scroll down to see new messages': 'Posuňte sa nadol, aby ste videli nové správy'
  435. 'Send': 'Odoslať'
  436. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Keďže ste neodpovedali v posledných %s minútach, vaša konverzácia bola uzavretá.'
  437. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Keďže ste v posledných %s minútach neodpovedali, vaša konverzácia s <strong>%s</strong> bola ukončená.'
  438. 'Start new conversation': 'Začať novú konverzáciu'
  439. 'Today': 'Dnes'
  440. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Je nám ľúto, že získanie slotu trvá dlhšie, než sme očakávali. Skúste to prosím neskôr alebo nám pošlite e-mail. Ďakujeme!'
  441. 'You are on waiting list position <strong>%s</strong>.': 'Na čakacej listine ste na pozícii <strong>%s</strong>.'
  442. 'sr':
  443. '<strong>Chat</strong> with us!': '<strong>Ћаскајте</strong> са нама!'
  444. 'All colleagues are busy.': 'Све колеге су заузете.'
  445. 'Chat closed by %s': 'Ћаскање затворено од стране %s'
  446. 'Compose your message…': 'Напишите поруку…'
  447. 'Connecting': 'Повезивање'
  448. 'Connection lost': 'Веза је изгубљена'
  449. 'Connection re-established': 'Веза је поново успостављена'
  450. 'Offline': 'Одсутан(а)'
  451. 'Online': 'Доступан(а)'
  452. 'Scroll down to see new messages': 'Скролујте на доле за нове поруке'
  453. 'Send': 'Пошаљи'
  454. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Пошто нисте одговорили у последњих %s минут(a), ваш разговор је завршен.'
  455. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': 'Пошто нисте одговорили у последњих %s минут(a), ваш разговор са <strong>%s</strong> је завршен.'
  456. 'Start new conversation': 'Започни нови разговор'
  457. 'Today': 'Данас'
  458. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': 'Жао нам је, добијање празног термина траје дуже од очекиваног. Молимо покушајте поново касније или нам пошаљите имејл поруку. Хвала вам!'
  459. 'You are on waiting list position <strong>%s</strong>.': 'Ви сте тренутно <strong>%s.</strong> у реду за чекање.'
  460. 'sr-latn-rs':
  461. '<strong>Chat</strong> with us!': '<strong>Ćaskajte</strong> sa nama!'
  462. 'All colleagues are busy.': 'Sve kolege su zauzete.'
  463. 'Chat closed by %s': 'Ćaskanje zatvoreno od strane %s'
  464. 'Compose your message…': 'Napišite poruku…'
  465. 'Connecting': 'Povezivanje'
  466. 'Connection lost': 'Veza je izgubljena'
  467. 'Connection re-established': 'Veza je ponovo uspostavljena'
  468. 'Offline': 'Odsutan(a)'
  469. 'Online': 'Dostupan(a)'
  470. 'Scroll down to see new messages': 'Skrolujte na dole za nove poruke'
  471. 'Send': 'Pošalji'
  472. '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.'
  473. '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.'
  474. 'Start new conversation': 'Započni novi razgovor'
  475. 'Today': 'Danas'
  476. '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!'
  477. 'You are on waiting list position <strong>%s</strong>.': 'Vi ste trenutno <strong>%s.</strong> u redu za čekanje.'
  478. 'sv':
  479. '<strong>Chat</strong> with us!': '<strong>Chatta</strong> med oss!'
  480. 'All colleagues are busy.': 'Alla kollegor är upptagna.'
  481. 'Chat closed by %s': 'Chatt stängd av %s'
  482. 'Compose your message…': 'Skriv ditt meddelande …'
  483. 'Connecting': 'Ansluter'
  484. 'Connection lost': 'Anslutningen försvann'
  485. 'Connection re-established': 'Anslutningen återupprättas'
  486. 'Offline': 'Offline'
  487. 'Online': 'Online'
  488. 'Scroll down to see new messages': 'Bläddra ner för att se nya meddelanden'
  489. 'Send': 'Skicka'
  490. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': 'Din chatt avslutades då du inte svarade inom %s minuter.'
  491. '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>.'
  492. 'Start new conversation': 'Starta ny konversation'
  493. 'Today': 'Idag'
  494. '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!'
  495. 'You are on waiting list position <strong>%s</strong>.': 'Du är på väntelistan som position <strong>%s</strong>.'
  496. 'zh-cn':
  497. '<strong>Chat</strong> with us!': '发起<strong>即时对话</strong>!'
  498. 'All colleagues are busy.': '所有同事都很忙。'
  499. 'Chat closed by %s': '对话已被 %s 关闭'
  500. 'Compose your message…': '编辑您的信息…'
  501. 'Connecting': '连接中'
  502. 'Connection lost': '连接丢失'
  503. 'Connection re-established': '正在重新建立连接'
  504. 'Offline': '离线'
  505. 'Online': '在线'
  506. 'Scroll down to see new messages': '向下滚动以查看新消息'
  507. 'Send': '发送'
  508. 'Since you didn\'t respond in the last %s minutes your conversation was closed.': '"由于您超过 %s 分钟没有任何回复'
  509. 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> was closed.': '"由于您超过 %s 分钟没有回复'
  510. 'Start new conversation': '开始新的会话'
  511. 'Today': '今天'
  512. 'We are sorry, it is taking longer than expected to get a slot. Please try again later or send us an email. Thank you!': ''
  513. 'You are on waiting list position <strong>%s</strong>.': '您目前的等候位置是第 <strong>%s</strong> 位.'
  514. # ZAMMAD_TRANSLATIONS_END
  515. sessionId: undefined
  516. scrolledToBottom: true
  517. scrollSnapTolerance: 10
  518. richTextFormatKey:
  519. 66: true # b
  520. 73: true # i
  521. 85: true # u
  522. 83: true # s
  523. T: (string, items...) =>
  524. if @options.lang && @options.lang isnt 'en'
  525. if !@translations[@options.lang]
  526. @log.notice "Translation '#{@options.lang}' needed!"
  527. else
  528. translations = @translations[@options.lang]
  529. if !translations[string]
  530. @log.notice "Translation needed for '#{string}'"
  531. string = translations[string] || string
  532. if items
  533. for item in items
  534. string = string.replace(/%s/, item)
  535. string
  536. view: (name) =>
  537. return (options) =>
  538. if !options
  539. options = {}
  540. options.T = @T
  541. options.background = @options.background
  542. options.flat = @options.flat
  543. options.fontSize = @options.fontSize
  544. return window.zammadChatTemplates[name](options)
  545. constructor: (options) ->
  546. super(options)
  547. # jQuery migration
  548. if typeof jQuery != 'undefined' && @options.target instanceof jQuery
  549. @log.notice 'Chat: target option is a jQuery object. jQuery is not a requirement for the chat any more.'
  550. @options.target = @options.target.get(0)
  551. # fullscreen
  552. @isFullscreen = (window.matchMedia and window.matchMedia('(max-width: 768px)').matches)
  553. @scrollRoot = @getScrollRoot()
  554. # check prerequisites
  555. if !window.WebSocket or !sessionStorage
  556. @state = 'unsupported'
  557. @log.notice 'Chat: Browser not supported!'
  558. return
  559. if !@options.chatId
  560. @state = 'unsupported'
  561. @log.error 'Chat: need chatId as option!'
  562. return
  563. # detect language
  564. if !@options.lang
  565. @options.lang = document.documentElement.getAttribute('lang')
  566. if @options.lang
  567. if !@translations[@options.lang]
  568. @log.debug "lang: No #{@options.lang} found, try first two letters"
  569. @options.lang = @options.lang.replace(/-.+?$/, '') # replace "-xx" of xx-xx
  570. @log.debug "lang: #{@options.lang}"
  571. # detect host
  572. @detectHost() if !@options.host
  573. @loadCss()
  574. @io = new Io(@options)
  575. @io.set(
  576. onOpen: @render
  577. onClose: @onWebSocketClose
  578. onMessage: @onWebSocketMessage
  579. onError: @onError
  580. )
  581. @io.connect()
  582. getScrollRoot: ->
  583. return document.scrollingElement if 'scrollingElement' of document
  584. html = document.documentElement
  585. start = parseInt(html.pageYOffset, 10)
  586. html.pageYOffset = start + 1
  587. end = parseInt(html.pageYOffset, 10)
  588. html.pageYOffset = start
  589. return if end > start then html else document.body
  590. render: =>
  591. if !@el || !document.querySelector('.zammad-chat')
  592. @renderBase()
  593. # disable open button
  594. btn = document.querySelector(".#{ @options.buttonClass }")
  595. if btn
  596. btn.classList.add @options.inactiveClass
  597. @setAgentOnlineState 'online'
  598. @log.debug 'widget rendered'
  599. @startTimeoutObservers()
  600. @idleTimeout.start()
  601. # get current chat status
  602. @sessionId = sessionStorage.getItem('sessionId')
  603. @send 'chat_status_customer',
  604. session_id: @sessionId
  605. url: window.location.href
  606. renderBase: ->
  607. @el.remove() if @el
  608. @options.target.insertAdjacentHTML('beforeend', @view('chat')(
  609. title: @options.title,
  610. scrollHint: @options.scrollHint
  611. ))
  612. @el = @options.target.querySelector('.zammad-chat')
  613. @input = @el.querySelector('.zammad-chat-input')
  614. @body = @el.querySelector('.zammad-chat-body')
  615. # start bindings
  616. @el.querySelector('.js-chat-open').addEventListener('click', @open)
  617. @el.querySelector('.js-chat-toggle').addEventListener('click', @toggle)
  618. @el.querySelector('.js-chat-status').addEventListener('click', @stopPropagation)
  619. @el.querySelector('.zammad-chat-controls').addEventListener('submit', @onSubmit)
  620. @body.addEventListener('scroll', @detectScrolledtoBottom)
  621. @el.querySelector('.zammad-scroll-hint').addEventListener('click', @onScrollHintClick)
  622. @input.addEventListener('keydown', @onKeydown)
  623. @input.addEventListener('input', @onInput)
  624. @input.addEventListener('paste', @onPaste)
  625. @input.addEventListener('drop', @onDrop)
  626. window.addEventListener('beforeunload', @onLeaveTemporary)
  627. window.addEventListener('hashchange', =>
  628. if @isOpen
  629. if @sessionId
  630. @send 'chat_session_notice',
  631. session_id: @sessionId
  632. message: window.location.href
  633. return
  634. @idleTimeout.start()
  635. )
  636. stopPropagation: (event) ->
  637. event.stopPropagation()
  638. onDrop: (e) =>
  639. e.stopPropagation()
  640. e.preventDefault()
  641. if window.dataTransfer # ie
  642. dataTransfer = window.dataTransfer
  643. else if e.dataTransfer # other browsers
  644. dataTransfer = e.dataTransfer
  645. else
  646. throw 'No clipboardData support'
  647. x = e.clientX
  648. y = e.clientY
  649. file = dataTransfer.files[0]
  650. # look for images
  651. if file.type.match('image.*')
  652. reader = new FileReader()
  653. reader.onload = (e) =>
  654. # Insert the image at the carat
  655. insert = (dataUrl, width) =>
  656. # adapt image if we are on retina devices
  657. if @isRetina()
  658. width = width / 2
  659. result = dataUrl
  660. img = new Image()
  661. img.style.width = '100%'
  662. img.style.maxWidth = width + 'px'
  663. img.src = result
  664. if document.caretPositionFromPoint
  665. pos = document.caretPositionFromPoint(x, y)
  666. range = document.createRange()
  667. range.setStart(pos.offsetNode, pos.offset)
  668. range.collapse()
  669. range.insertNode(img)
  670. else if document.caretRangeFromPoint
  671. range = document.caretRangeFromPoint(x, y)
  672. range.insertNode(img)
  673. else
  674. console.log('could not find carat')
  675. # resize if to big
  676. @resizeImage(e.target.result, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
  677. reader.readAsDataURL(file)
  678. onPaste: (e) =>
  679. e.stopPropagation()
  680. e.preventDefault()
  681. if e.clipboardData
  682. clipboardData = e.clipboardData
  683. else if window.clipboardData
  684. clipboardData = window.clipboardData
  685. else if e.clipboardData
  686. clipboardData = e.clipboardData
  687. else
  688. throw 'No clipboardData support'
  689. imageInserted = false
  690. if clipboardData && clipboardData.items && clipboardData.items[0]
  691. item = clipboardData.items[0]
  692. if item.kind == 'file' && (item.type == 'image/png' || item.type == 'image/jpeg')
  693. imageFile = item.getAsFile()
  694. reader = new FileReader()
  695. reader.onload = (e) =>
  696. insert = (dataUrl, width) =>
  697. # adapt image if we are on retina devices
  698. if @isRetina()
  699. width = width / 2
  700. img = new Image()
  701. img.style.width = '100%'
  702. img.style.maxWidth = width + 'px'
  703. img.src = dataUrl
  704. document.execCommand('insertHTML', false, img)
  705. # resize if to big
  706. @resizeImage(e.target.result, 460, 'auto', 2, 'image/jpeg', 'auto', insert)
  707. reader.readAsDataURL(imageFile)
  708. imageInserted = true
  709. return if imageInserted
  710. # check existing + paste text for limit
  711. text = undefined
  712. docType = undefined
  713. try
  714. text = clipboardData.getData('text/html')
  715. docType = 'html'
  716. if !text || text.length is 0
  717. docType = 'text'
  718. text = clipboardData.getData('text/plain')
  719. if !text || text.length is 0
  720. docType = 'text2'
  721. text = clipboardData.getData('text')
  722. catch e
  723. console.log('Sorry, can\'t insert markup because browser is not supporting it.')
  724. docType = 'text3'
  725. text = clipboardData.getData('text')
  726. if docType is 'text' || docType is 'text2' || docType is 'text3'
  727. text = '<div>' + text.replace(/\n/g, '</div><div>') + '</div>'
  728. text = text.replace(/<div><\/div>/g, '<div><br></div>')
  729. console.log('p', docType, text)
  730. if docType is 'html'
  731. html = document.createElement('div')
  732. # can't log because might contain malicious content
  733. # @log.debug 'HTML clipboard', text
  734. sanitized = DOMPurify.sanitize(text)
  735. @log.debug 'sanitized HTML clipboard', sanitized
  736. html.innerHTML = sanitized
  737. match = false
  738. htmlTmp = text
  739. regex = new RegExp('<(/w|w)\:[A-Za-z]')
  740. if htmlTmp.match(regex)
  741. match = true
  742. htmlTmp = htmlTmp.replace(regex, '')
  743. regex = new RegExp('<(/o|o)\:[A-Za-z]')
  744. if htmlTmp.match(regex)
  745. match = true
  746. htmlTmp = htmlTmp.replace(regex, '')
  747. if match
  748. html = @wordFilter(html)
  749. #html
  750. for node in html.childNodes
  751. if node.nodeType == 8
  752. node.remove()
  753. # remove tags, keep content
  754. for node in html.querySelectorAll('a, font, small, time, form, label')
  755. node.outerHTML = node.innerHTML
  756. # replace tags with generic div
  757. # New type of the tag
  758. replacementTag = 'div';
  759. # Replace all x tags with the type of replacementTag
  760. for node in html.querySelectorAll('textarea')
  761. outer = node.outerHTML
  762. # Replace opening tag
  763. regex = new RegExp('<' + node.tagName, 'i')
  764. newTag = outer.replace(regex, '<' + replacementTag)
  765. # Replace closing tag
  766. regex = new RegExp('</' + node.tagName, 'i')
  767. newTag = newTag.replace(regex, '</' + replacementTag)
  768. node.outerHTML = newTag
  769. # remove tags & content
  770. for node in html.querySelectorAll('font, img, svg, input, select, button, style, applet, embed, noframes, canvas, script, frame, iframe, meta, link, title, head, fieldset')
  771. node.remove()
  772. @removeAttributes(html)
  773. text = html.innerHTML
  774. # as fallback, insert html via pasteHtmlAtCaret (for IE 11 and lower)
  775. if docType is 'text3'
  776. @pasteHtmlAtCaret(text)
  777. else
  778. document.execCommand('insertHTML', false, text)
  779. true
  780. onKeydown: (e) =>
  781. # check for enter
  782. if not @inputDisabled and not e.shiftKey and e.keyCode is 13
  783. e.preventDefault()
  784. @sendMessage()
  785. richtTextControl = false
  786. if !e.altKey && !e.ctrlKey && e.metaKey
  787. richtTextControl = true
  788. else if !e.altKey && e.ctrlKey && !e.metaKey
  789. richtTextControl = true
  790. if richtTextControl && @richTextFormatKey[ e.keyCode ]
  791. e.preventDefault()
  792. if e.keyCode is 66
  793. document.execCommand('bold')
  794. return true
  795. if e.keyCode is 73
  796. document.execCommand('italic')
  797. return true
  798. if e.keyCode is 85
  799. document.execCommand('underline')
  800. return true
  801. if e.keyCode is 83
  802. document.execCommand('strikeThrough')
  803. return true
  804. send: (event, data = {}) =>
  805. data.chat_id = @options.chatId
  806. @io.send(event, data)
  807. onWebSocketMessage: (pipes) =>
  808. for pipe in pipes
  809. @log.debug 'ws:onmessage', pipe
  810. switch pipe.event
  811. when 'chat_error'
  812. @log.notice pipe.data
  813. if pipe.data && pipe.data.state is 'chat_disabled'
  814. @destroy(remove: true)
  815. when 'chat_session_message'
  816. return if pipe.data.self_written
  817. @receiveMessage pipe.data
  818. when 'chat_session_typing'
  819. return if pipe.data.self_written
  820. @onAgentTypingStart()
  821. when 'chat_session_start'
  822. @onConnectionEstablished pipe.data
  823. when 'chat_session_queue'
  824. @onQueueScreen pipe.data
  825. when 'chat_session_closed'
  826. @onSessionClosed pipe.data
  827. when 'chat_session_left'
  828. @onSessionClosed pipe.data
  829. when 'chat_status_customer'
  830. switch pipe.data.state
  831. when 'online'
  832. @sessionId = undefined
  833. if !@options.cssAutoload || @cssLoaded
  834. @onReady()
  835. else
  836. @socketReady = true
  837. when 'offline'
  838. @onError 'Zammad Chat: No agent online'
  839. when 'chat_disabled'
  840. @onError 'Zammad Chat: Chat is disabled'
  841. when 'no_seats_available'
  842. @onError "Zammad Chat: Too many clients in queue. Clients in queue: #{pipe.data.queue}"
  843. when 'reconnect'
  844. @onReopenSession pipe.data
  845. onReady: ->
  846. @log.debug 'widget ready for use'
  847. btn = document.querySelector(".#{ @options.buttonClass }")
  848. if btn
  849. btn.addEventListener('click', @open)
  850. btn.classList.remove(@options.inactiveClass)
  851. @options.onReady?()
  852. if @options.show
  853. @show()
  854. onError: (message) =>
  855. @log.debug message
  856. @addStatus(message)
  857. btn = document.querySelector(".#{ @options.buttonClass }")
  858. if btn
  859. btn.classList.add('zammad-chat-is-hidden')
  860. if @isOpen
  861. @disableInput()
  862. @destroy(remove: false)
  863. else
  864. @destroy(remove: true)
  865. @options.onError?(message)
  866. onReopenSession: (data) =>
  867. @log.debug 'old messages', data.session
  868. @inactiveTimeout.start()
  869. unfinishedMessage = sessionStorage.getItem 'unfinished_message'
  870. # rerender chat history
  871. if data.agent
  872. @onConnectionEstablished(data)
  873. for message in data.session
  874. @renderMessage
  875. message: message.content
  876. id: message.id
  877. from: if message.created_by_id then 'agent' else 'customer'
  878. if unfinishedMessage
  879. @input.innerHTML = unfinishedMessage
  880. # show wait list
  881. if data.position
  882. @onQueue data
  883. @show()
  884. @open()
  885. @scrollToBottom()
  886. if unfinishedMessage
  887. @input.focus()
  888. onInput: =>
  889. # remove unread-state from messages
  890. for message in @el.querySelectorAll('.zammad-chat-message--unread')
  891. message.classList.remove 'zammad-chat-message--unread'
  892. sessionStorage.setItem 'unfinished_message', @input.innerHTML
  893. @onTyping()
  894. onTyping: ->
  895. # send typing start event only every 1.5 seconds
  896. return if @isTyping && @isTyping > new Date(new Date().getTime() - 1500)
  897. @isTyping = new Date()
  898. @send 'chat_session_typing',
  899. session_id: @sessionId
  900. @inactiveTimeout.start()
  901. onSubmit: (event) =>
  902. event.preventDefault()
  903. @sendMessage()
  904. sendMessage: ->
  905. message = @input.innerHTML
  906. return if !message
  907. @inactiveTimeout.start()
  908. sessionStorage.removeItem 'unfinished_message'
  909. messageElement = @view('message')
  910. message: message
  911. from: 'customer'
  912. id: @_messageCount++
  913. unreadClass: ''
  914. @maybeAddTimestamp()
  915. # add message before message typing loader
  916. if @el.querySelector('.zammad-chat-message--typing')
  917. @lastAddedType = 'typing-placeholder'
  918. @el.querySelector('.zammad-chat-message--typing').insertAdjacentHTML('beforebegin', messageElement)
  919. else
  920. @lastAddedType = 'message--customer'
  921. @body.insertAdjacentHTML('beforeend', messageElement)
  922. @input.innerHTML = ''
  923. @scrollToBottom()
  924. # send message event
  925. @send 'chat_session_message',
  926. content: message
  927. id: @_messageCount
  928. session_id: @sessionId
  929. receiveMessage: (data) =>
  930. @inactiveTimeout.start()
  931. # hide writing indicator
  932. @onAgentTypingEnd()
  933. @maybeAddTimestamp()
  934. @renderMessage
  935. message: data.message.content
  936. id: data.id
  937. from: 'agent'
  938. @scrollToBottom showHint: true
  939. renderMessage: (data) =>
  940. @lastAddedType = "message--#{ data.from }"
  941. data.unreadClass = if document.hidden then ' zammad-chat-message--unread' else ''
  942. @body.insertAdjacentHTML('beforeend', @view('message')(data))
  943. open: =>
  944. if @isOpen
  945. @log.debug 'widget already open, block'
  946. return
  947. @isOpen = true
  948. @log.debug 'open widget'
  949. @show()
  950. if !@sessionId
  951. @showLoader()
  952. @el.classList.add 'zammad-chat-is-open'
  953. remainerHeight = @el.clientHeight - @el.querySelector('.zammad-chat-header').offsetHeight
  954. @el.style.transform = "translateY(#{remainerHeight}px)"
  955. # force redraw
  956. @el.clientHeight
  957. if !@sessionId
  958. @el.addEventListener 'transitionend', @onOpenAnimationEnd
  959. @el.classList.add 'zammad-chat--animate'
  960. # force redraw
  961. @el.clientHeight
  962. # start animation
  963. @el.style.transform = ''
  964. @send('chat_session_init'
  965. url: window.location.href
  966. )
  967. else
  968. @el.style.transform = ''
  969. @onOpenAnimationEnd()
  970. onOpenAnimationEnd: =>
  971. @el.removeEventListener 'transitionend', @onOpenAnimationEnd
  972. @el.classList.remove 'zammad-chat--animate'
  973. @idleTimeout.stop()
  974. if @isFullscreen
  975. @disableScrollOnRoot()
  976. @options.onOpenAnimationEnd?()
  977. sessionClose: =>
  978. # send close
  979. @send 'chat_session_close',
  980. session_id: @sessionId
  981. # stop timer
  982. @inactiveTimeout.stop()
  983. @waitingListTimeout.stop()
  984. # delete input store
  985. sessionStorage.removeItem 'unfinished_message'
  986. # stop delay of initial queue position
  987. if @onInitialQueueDelayId
  988. clearTimeout(@onInitialQueueDelayId)
  989. @setSessionId undefined
  990. toggle: (event) =>
  991. if @isOpen
  992. @close(event)
  993. else
  994. @open(event)
  995. close: (event) =>
  996. if !@isOpen
  997. @log.debug 'can\'t close widget, it\'s not open'
  998. return
  999. if @initDelayId
  1000. clearTimeout(@initDelayId)
  1001. if @sessionId
  1002. @log.debug 'session close before widget close'
  1003. @sessionClose()
  1004. @log.debug 'close widget'
  1005. event.stopPropagation() if event
  1006. if @isFullscreen
  1007. @enableScrollOnRoot()
  1008. # close window
  1009. remainerHeight = @el.clientHeight - @el.querySelector('.zammad-chat-header').offsetHeight
  1010. @el.addEventListener 'transitionend', @onCloseAnimationEnd
  1011. @el.classList.add 'zammad-chat--animate'
  1012. # force redraw
  1013. document.offsetHeight
  1014. # animate out
  1015. @el.style.transform = "translateY(#{remainerHeight}px)"
  1016. onCloseAnimationEnd: =>
  1017. @el.removeEventListener 'transitionend', @onCloseAnimationEnd
  1018. @el.classList.remove 'zammad-chat-is-open', 'zammad-chat--animate'
  1019. @el.style.transform = ''
  1020. @showLoader()
  1021. @el.querySelector('.zammad-chat-welcome').classList.remove('zammad-chat-is-hidden')
  1022. @el.querySelector('.zammad-chat-agent').classList.add('zammad-chat-is-hidden')
  1023. @el.querySelector('.zammad-chat-agent-status').classList.add('zammad-chat-is-hidden')
  1024. @isOpen = false
  1025. @options.onCloseAnimationEnd?()
  1026. @io.reconnect()
  1027. onWebSocketClose: =>
  1028. return if @isOpen
  1029. if @el
  1030. @el.classList.remove('zammad-chat-is-shown')
  1031. @el.classList.remove('zammad-chat-is-loaded')
  1032. show: ->
  1033. return if @state is 'offline'
  1034. @el.classList.add('zammad-chat-is-loaded')
  1035. @el.classList.add('zammad-chat-is-shown')
  1036. disableInput: ->
  1037. @inputDisabled = true
  1038. @input.setAttribute('contenteditable', false)
  1039. @el.querySelector('.zammad-chat-send').disabled = true
  1040. @io.close()
  1041. enableInput: ->
  1042. @inputDisabled = false
  1043. @input.setAttribute('contenteditable', true)
  1044. @el.querySelector('.zammad-chat-send').disabled = false
  1045. hideModal: ->
  1046. @el.querySelector('.zammad-chat-modal').innerHTML = ''
  1047. onQueueScreen: (data) =>
  1048. @setSessionId data.session_id
  1049. # delay initial queue position, show connecting first
  1050. show = =>
  1051. @onQueue data
  1052. @waitingListTimeout.start()
  1053. if @initialQueueDelay && !@onInitialQueueDelayId
  1054. @onInitialQueueDelayId = setTimeout(show, @initialQueueDelay)
  1055. return
  1056. # stop delay of initial queue position
  1057. if @onInitialQueueDelayId
  1058. clearTimeout(@onInitialQueueDelayId)
  1059. # show queue position
  1060. show()
  1061. onQueue: (data) =>
  1062. @log.notice 'onQueue', data.position
  1063. @inQueue = true
  1064. @el.querySelector('.zammad-chat-modal').innerHTML = @view('waiting')
  1065. position: data.position
  1066. onAgentTypingStart: =>
  1067. if @stopTypingId
  1068. clearTimeout(@stopTypingId)
  1069. @stopTypingId = setTimeout(@onAgentTypingEnd, 3000)
  1070. # never display two typing indicators
  1071. return if @el.querySelector('.zammad-chat-message--typing')
  1072. @maybeAddTimestamp()
  1073. @body.insertAdjacentHTML('beforeend', @view('typingIndicator')())
  1074. # only if typing indicator is shown
  1075. return if !@isVisible(@el.querySelector('.zammad-chat-message--typing'), true)
  1076. @scrollToBottom()
  1077. onAgentTypingEnd: =>
  1078. @el.querySelector('.zammad-chat-message--typing').remove() if @el.querySelector('.zammad-chat-message--typing')
  1079. onLeaveTemporary: =>
  1080. return if !@sessionId
  1081. @send 'chat_session_leave_temporary',
  1082. session_id: @sessionId
  1083. maybeAddTimestamp: ->
  1084. timestamp = Date.now()
  1085. if !@lastTimestamp or (timestamp - @lastTimestamp) > @showTimeEveryXMinutes * 60000
  1086. label = @T('Today')
  1087. time = new Date().toTimeString().substr 0,5
  1088. if @lastAddedType is 'timestamp'
  1089. # update last time
  1090. @updateLastTimestamp label, time
  1091. @lastTimestamp = timestamp
  1092. else
  1093. # add new timestamp
  1094. @body.insertAdjacentHTML 'beforeend', @view('timestamp')
  1095. label: label
  1096. time: time
  1097. @lastTimestamp = timestamp
  1098. @lastAddedType = 'timestamp'
  1099. @scrollToBottom()
  1100. updateLastTimestamp: (label, time) ->
  1101. return if !@el
  1102. timestamps = @el.querySelectorAll('.zammad-chat-body .zammad-chat-timestamp')
  1103. return if !timestamps
  1104. timestamps[timestamps.length - 1].outerHTML = @view('timestamp')
  1105. label: label
  1106. time: time
  1107. addStatus: (status) ->
  1108. return if !@el
  1109. @maybeAddTimestamp()
  1110. @body.insertAdjacentHTML 'beforeend', @view('status')
  1111. status: status
  1112. @scrollToBottom()
  1113. detectScrolledtoBottom: =>
  1114. scrollBottom = @body.scrollTop + @body.offsetHeight
  1115. @scrolledToBottom = Math.abs(scrollBottom - @body.scrollHeight) <= @scrollSnapTolerance
  1116. @el.querySelector('.zammad-scroll-hint').classList.add('is-hidden') if @scrolledToBottom
  1117. showScrollHint: ->
  1118. @el.querySelector('.zammad-scroll-hint').classList.remove('is-hidden')
  1119. # compensate scroll
  1120. @body.scrollTop = @body.scrollTop + @el.querySelector('.zammad-scroll-hint').offsetHeight
  1121. onScrollHintClick: =>
  1122. # animate scroll
  1123. @body.scrollTo
  1124. top: @body.scrollHeight
  1125. behavior: 'smooth'
  1126. scrollToBottom: ({ showHint } = { showHint: false }) ->
  1127. if @scrolledToBottom
  1128. @body.scrollTop = @body.scrollHeight
  1129. else if showHint
  1130. @showScrollHint()
  1131. destroy: (params = {}) =>
  1132. @log.debug 'destroy widget', params
  1133. @setAgentOnlineState 'offline'
  1134. if params.remove && @el
  1135. @el.remove()
  1136. # Remove button, because it can no longer be used.
  1137. btn = document.querySelector(".#{ @options.buttonClass }")
  1138. if btn
  1139. btn.classList.add @options.inactiveClass
  1140. btn.style.display = 'none';
  1141. # stop all timer
  1142. if @waitingListTimeout
  1143. @waitingListTimeout.stop()
  1144. if @inactiveTimeout
  1145. @inactiveTimeout.stop()
  1146. if @idleTimeout
  1147. @idleTimeout.stop()
  1148. # stop ws connection
  1149. @io.close()
  1150. reconnect: =>
  1151. # set status to connecting
  1152. @log.notice 'reconnecting'
  1153. @disableInput()
  1154. @lastAddedType = 'status'
  1155. @setAgentOnlineState 'connecting'
  1156. @addStatus @T('Connection lost')
  1157. onConnectionReestablished: =>
  1158. # set status back to online
  1159. @lastAddedType = 'status'
  1160. @setAgentOnlineState 'online'
  1161. @addStatus @T('Connection re-established')
  1162. @options.onConnectionReestablished?()
  1163. onSessionClosed: (data) ->
  1164. @addStatus @T('Chat closed by %s', data.realname)
  1165. @disableInput()
  1166. @setAgentOnlineState 'offline'
  1167. @inactiveTimeout.stop()
  1168. @options.onSessionClosed?(data)
  1169. setSessionId: (id) =>
  1170. @sessionId = id
  1171. if id is undefined
  1172. sessionStorage.removeItem 'sessionId'
  1173. else
  1174. sessionStorage.setItem 'sessionId', id
  1175. onConnectionEstablished: (data) =>
  1176. # stop delay of initial queue position
  1177. if @onInitialQueueDelayId
  1178. clearTimeout @onInitialQueueDelayId
  1179. @inQueue = false
  1180. if data.agent
  1181. @agent = data.agent
  1182. if data.session_id
  1183. @setSessionId data.session_id
  1184. # empty old messages
  1185. @body.innerHTML = ''
  1186. @el.querySelector('.zammad-chat-agent').innerHTML = @view('agent')
  1187. agent: @agent
  1188. @enableInput()
  1189. @hideModal()
  1190. @el.querySelector('.zammad-chat-welcome').classList.add('zammad-chat-is-hidden')
  1191. @el.querySelector('.zammad-chat-agent').classList.remove('zammad-chat-is-hidden')
  1192. @el.querySelector('.zammad-chat-agent-status').classList.remove('zammad-chat-is-hidden')
  1193. @input.focus() if not @isFullscreen
  1194. @setAgentOnlineState 'online'
  1195. @waitingListTimeout.stop()
  1196. @idleTimeout.stop()
  1197. @inactiveTimeout.start()
  1198. @options.onConnectionEstablished?(data)
  1199. showCustomerTimeout: ->
  1200. @el.querySelector('.zammad-chat-modal').innerHTML = @view('customer_timeout')
  1201. agent: @agent.name
  1202. delay: @options.inactiveTimeout
  1203. @el.querySelector('.js-restart').addEventListener 'click', -> location.reload()
  1204. @sessionClose()
  1205. showWaitingListTimeout: ->
  1206. @el.querySelector('.zammad-chat-modal').innerHTML = @view('waiting_list_timeout')
  1207. delay: @options.watingListTimeout
  1208. @el.querySelector('.js-restart').addEventListener 'click', -> location.reload()
  1209. @sessionClose()
  1210. showLoader: ->
  1211. @el.querySelector('.zammad-chat-modal').innerHTML = @view('loader')()
  1212. setAgentOnlineState: (state) =>
  1213. @state = state
  1214. return if !@el
  1215. capitalizedState = state.charAt(0).toUpperCase() + state.slice(1)
  1216. @el.querySelector('.zammad-chat-agent-status').dataset.status = state
  1217. @el.querySelector('.zammad-chat-agent-status').textContent = @T(capitalizedState)
  1218. detectHost: ->
  1219. protocol = 'ws://'
  1220. if scriptProtocol is 'https'
  1221. protocol = 'wss://'
  1222. @options.host = "#{ protocol }#{ scriptHost }/ws"
  1223. loadCss: ->
  1224. return if !@options.cssAutoload
  1225. url = @options.cssUrl
  1226. if !url
  1227. url = @options.host
  1228. .replace(/^wss/i, 'https')
  1229. .replace(/^ws/i, 'http')
  1230. .replace(/\/ws$/i, '') # WebSocket may run on example.com/ws path
  1231. url += '/assets/chat/chat.css'
  1232. @log.debug "load css from '#{url}'"
  1233. styles = "@import url('#{url}');"
  1234. newSS = document.createElement('link')
  1235. newSS.onload = @onCssLoaded
  1236. newSS.rel = 'stylesheet'
  1237. newSS.href = 'data:text/css,' + escape(styles)
  1238. document.getElementsByTagName('head')[0].appendChild(newSS)
  1239. onCssLoaded: =>
  1240. @cssLoaded = true
  1241. if @socketReady
  1242. @onReady()
  1243. @options.onCssLoaded?()
  1244. startTimeoutObservers: =>
  1245. @idleTimeout = new Timeout(
  1246. logPrefix: 'idleTimeout'
  1247. debug: @options.debug
  1248. timeout: @options.idleTimeout
  1249. timeoutIntervallCheck: @options.idleTimeoutIntervallCheck
  1250. callback: =>
  1251. @log.debug 'Idle timeout reached, hide widget', new Date
  1252. @destroy(remove: true)
  1253. )
  1254. @inactiveTimeout = new Timeout(
  1255. logPrefix: 'inactiveTimeout'
  1256. debug: @options.debug
  1257. timeout: @options.inactiveTimeout
  1258. timeoutIntervallCheck: @options.inactiveTimeoutIntervallCheck
  1259. callback: =>
  1260. @log.debug 'Inactive timeout reached, show timeout screen.', new Date
  1261. @showCustomerTimeout()
  1262. @destroy(remove: false)
  1263. )
  1264. @waitingListTimeout = new Timeout(
  1265. logPrefix: 'waitingListTimeout'
  1266. debug: @options.debug
  1267. timeout: @options.waitingListTimeout
  1268. timeoutIntervallCheck: @options.waitingListTimeoutIntervallCheck
  1269. callback: =>
  1270. @log.debug 'Waiting list timeout reached, show timeout screen.', new Date
  1271. @showWaitingListTimeout()
  1272. @destroy(remove: false)
  1273. )
  1274. disableScrollOnRoot: ->
  1275. @rootScrollOffset = @scrollRoot.scrollTop
  1276. @scrollRoot.style.overflow = 'hidden'
  1277. @scrollRoot.style.position = 'fixed'
  1278. enableScrollOnRoot: ->
  1279. @scrollRoot.scrollTop = @rootScrollOffset
  1280. @scrollRoot.style.overflow = ''
  1281. @scrollRoot.style.position = ''
  1282. # based on https://github.com/customd/jquery-visible/blob/master/jquery.visible.js
  1283. # to have not dependency, port to coffeescript
  1284. isVisible: (el, partial, hidden, direction) ->
  1285. return if el.length < 1
  1286. vpWidth = window.innerWidth
  1287. vpHeight = window.innerHeight
  1288. direction = if direction then direction else 'both'
  1289. clientSize = if hidden is true then t.offsetWidth * t.offsetHeight else true
  1290. rec = el.getBoundingClientRect()
  1291. tViz = rec.top >= 0 && rec.top < vpHeight
  1292. bViz = rec.bottom > 0 && rec.bottom <= vpHeight
  1293. lViz = rec.left >= 0 && rec.left < vpWidth
  1294. rViz = rec.right > 0 && rec.right <= vpWidth
  1295. vVisible = if partial then tViz || bViz else tViz && bViz
  1296. hVisible = if partial then lViz || rViz else lViz && rViz
  1297. if direction is 'both'
  1298. return clientSize && vVisible && hVisible
  1299. else if direction is 'vertical'
  1300. return clientSize && vVisible
  1301. else if direction is 'horizontal'
  1302. return clientSize && hVisible
  1303. isRetina: ->
  1304. if window.matchMedia
  1305. 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)')
  1306. return (mq && mq.matches || (window.devicePixelRatio > 1))
  1307. false
  1308. resizeImage: (dataURL, x = 'auto', y = 'auto', sizeFactor = 1, type, quallity, callback, force = true) ->
  1309. # load image from data url
  1310. imageObject = new Image()
  1311. imageObject.onload = ->
  1312. imageWidth = imageObject.width
  1313. imageHeight = imageObject.height
  1314. console.log('ImageService', 'current size', imageWidth, imageHeight)
  1315. if y is 'auto' && x is 'auto'
  1316. x = imageWidth
  1317. y = imageHeight
  1318. # get auto dimensions
  1319. if y is 'auto'
  1320. factor = imageWidth / x
  1321. y = imageHeight / factor
  1322. if x is 'auto'
  1323. factor = imageWidth / y
  1324. x = imageHeight / factor
  1325. # check if resize is needed
  1326. resize = false
  1327. if x < imageWidth || y < imageHeight
  1328. resize = true
  1329. x = x * sizeFactor
  1330. y = y * sizeFactor
  1331. else
  1332. x = imageWidth
  1333. y = imageHeight
  1334. # create canvas and set dimensions
  1335. canvas = document.createElement('canvas')
  1336. canvas.width = x
  1337. canvas.height = y
  1338. # draw image on canvas and set image dimensions
  1339. context = canvas.getContext('2d')
  1340. context.drawImage(imageObject, 0, 0, x, y)
  1341. # set quallity based on image size
  1342. if quallity == 'auto'
  1343. if x < 200 && y < 200
  1344. quallity = 1
  1345. else if x < 400 && y < 400
  1346. quallity = 0.9
  1347. else if x < 600 && y < 600
  1348. quallity = 0.8
  1349. else if x < 900 && y < 900
  1350. quallity = 0.7
  1351. else
  1352. quallity = 0.6
  1353. # execute callback with resized image
  1354. newDataUrl = canvas.toDataURL(type, quallity)
  1355. if resize
  1356. console.log('ImageService', 'resize', x/sizeFactor, y/sizeFactor, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
  1357. callback(newDataUrl, x/sizeFactor, y/sizeFactor, true)
  1358. return
  1359. console.log('ImageService', 'no resize', x, y, quallity, (newDataUrl.length * 0.75)/1024/1024, 'in mb')
  1360. callback(newDataUrl, x, y, false)
  1361. # load image from data url
  1362. imageObject.src = dataURL
  1363. # taken from https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
  1364. pasteHtmlAtCaret: (html) ->
  1365. sel = undefined
  1366. range = undefined
  1367. if window.getSelection
  1368. sel = window.getSelection()
  1369. if sel.getRangeAt && sel.rangeCount
  1370. range = sel.getRangeAt(0)
  1371. range.deleteContents()
  1372. el = document.createElement('div')
  1373. el.innerHTML = html
  1374. frag = document.createDocumentFragment(node, lastNode)
  1375. while node = el.firstChild
  1376. lastNode = frag.appendChild(node)
  1377. range.insertNode(frag)
  1378. if lastNode
  1379. range = range.cloneRange()
  1380. range.setStartAfter(lastNode)
  1381. range.collapse(true)
  1382. sel.removeAllRanges()
  1383. sel.addRange(range)
  1384. else if document.selection && document.selection.type != 'Control'
  1385. document.selection.createRange().pasteHTML(html)
  1386. # (C) sbrin - https://github.com/sbrin
  1387. # https://gist.github.com/sbrin/6801034
  1388. wordFilter: (editor) ->
  1389. content = editor.html()
  1390. # Word comments like conditional comments etc
  1391. content = content.replace(/<!--[\s\S]+?-->/gi, '')
  1392. # Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
  1393. # MS Office namespaced tags, and a few other tags
  1394. content = content.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '')
  1395. # Convert <s> into <strike> for line-though
  1396. content = content.replace(/<(\/?)s>/gi, '<$1strike>')
  1397. # Replace nbsp entites to char since it's easier to handle
  1398. # content = content.replace(/&nbsp;/gi, "\u00a0")
  1399. content = content.replace(/&nbsp;/gi, ' ')
  1400. # Convert <span style="mso-spacerun:yes">___</span> to string of alternating
  1401. # breaking/non-breaking spaces of same length
  1402. #content = content.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, (str, spaces) ->
  1403. # return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ''
  1404. #)
  1405. editor.innerHTML = content
  1406. # Parse out list indent level for lists
  1407. for p in editor.querySelectorAll('p')
  1408. str = p.getAttribute('style')
  1409. matches = /mso-list:\w+ \w+([0-9]+)/.exec(str)
  1410. if matches
  1411. p.dataset._listLevel = parseInt(matches[1], 10)
  1412. # Parse Lists
  1413. last_level = 0
  1414. pnt = null
  1415. for p in editor.querySelectorAll('p')
  1416. cur_level = p.dataset._listLevel
  1417. if cur_level != undefined
  1418. txt = p.textContent
  1419. list_tag = '<ul></ul>'
  1420. if (/^\s*\w+\./.test(txt))
  1421. matches = /([0-9])\./.exec(txt)
  1422. if matches
  1423. start = parseInt(matches[1], 10)
  1424. list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>'
  1425. else
  1426. list_tag = '<ol></ol>'
  1427. if cur_level > last_level
  1428. if last_level == 0
  1429. p.insertAdjacentHTML 'beforebegin', list_tag
  1430. pnt = p.previousElementSibling
  1431. else
  1432. pnt.insertAdjacentHTML 'beforeend', list_tag
  1433. if cur_level < last_level
  1434. for i in [i..last_level-cur_level]
  1435. pnt = pnt.parentNode
  1436. p.querySelector('span:first').remove() if p.querySelector('span:first')
  1437. pnt.insertAdjacentHTML 'beforeend', '<li>' + p.innerHTML + '</li>'
  1438. p.remove()
  1439. last_level = cur_level
  1440. else
  1441. last_level = 0
  1442. el.removeAttribute('style') for el in editor.querySelectorAll('[style]')
  1443. el.removeAttribute('align') for el in editor.querySelectorAll('[align]')
  1444. el.outerHTML = el.innerHTML for el in editor.querySelectorAll('span')
  1445. el.remove() for el in editor.querySelectorAll('span:empty')
  1446. el.removeAttribute('class') for el in editor.querySelectorAll("[class^='Mso']")
  1447. el.remove() for el in editor.querySelectorAll('p:empty')
  1448. editor
  1449. removeAttribute: (element) ->
  1450. return if !element
  1451. for att in element.attributes
  1452. element.removeAttribute(att.name)
  1453. removeAttributes: (html) =>
  1454. for node in html.querySelectorAll('*')
  1455. @removeAttribute node
  1456. html
  1457. window.ZammadChat = ZammadChat