From 485196b01321bda14310ea12545163f14c7f94ca Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Fri, 15 Oct 2021 11:26:31 +0200 Subject: [PATCH 01/90] Fixes #2471 - Chat can't be closed after timeout. --- public/assets/chat/chat-no-jquery.coffee | 8 +++----- public/assets/chat/chat-no-jquery.js | 7 +++---- public/assets/chat/chat-no-jquery.min.js | 2 +- public/assets/chat/chat.coffee | 8 +++----- public/assets/chat/chat.js | 7 +++---- public/assets/chat/chat.min.js | 2 +- spec/system/chat_spec.rb | 25 ++++++++++++++++++++++++ 7 files changed, 39 insertions(+), 20 deletions(-) diff --git a/public/assets/chat/chat-no-jquery.coffee b/public/assets/chat/chat-no-jquery.coffee index 7b3783778..21033ae09 100644 --- a/public/assets/chat/chat-no-jquery.coffee +++ b/public/assets/chat/chat-no-jquery.coffee @@ -1097,16 +1097,14 @@ do(window) -> return if @initDelayId clearTimeout(@initDelayId) - if !@sessionId - @log.debug 'can\'t close widget without sessionId' - return + if @sessionId + @log.debug 'session close before widget close' + @sessionClose() @log.debug 'close widget' event.stopPropagation() if event - @sessionClose() - if @isFullscreen @enableScrollOnRoot() diff --git a/public/assets/chat/chat-no-jquery.js b/public/assets/chat/chat-no-jquery.js index 843cf6eef..bb7c38869 100644 --- a/public/assets/chat/chat-no-jquery.js +++ b/public/assets/chat/chat-no-jquery.js @@ -1437,15 +1437,14 @@ var extend = function(child, parent) { for (var key in parent) { if (hasProp.cal if (this.initDelayId) { clearTimeout(this.initDelayId); } - if (!this.sessionId) { - this.log.debug('can\'t close widget without sessionId'); - return; + if (this.sessionId) { + this.log.debug('session close before widget close'); + this.sessionClose(); } this.log.debug('close widget'); if (event) { event.stopPropagation(); } - this.sessionClose(); if (this.isFullscreen) { this.enableScrollOnRoot(); } diff --git a/public/assets/chat/chat-no-jquery.min.js b/public/assets/chat/chat-no-jquery.min.js index 5e4a55a20..8c40ef151 100644 --- a/public/assets/chat/chat-no-jquery.min.js +++ b/public/assets/chat/chat-no-jquery.min.js @@ -1 +1 @@ -window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.agent=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?o(e):""},s=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){this.agent.avatar&&(t.push('\n\n')),t.push('\n\n '),t.push(n(this.agent.name)),t.push("\n")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,function(){"use strict";var s=Object.hasOwnProperty,i=Object.setPrototypeOf,a=Object.isFrozen,o=Object.getPrototypeOf,r=Object.getOwnPropertyDescriptor,Me=Object.freeze,e=Object.seal,l=Object.create,t="undefined"!=typeof Reflect&&Reflect,c=t.apply,d=t.construct;c||(c=function(e,t,n){return e.apply(t,n)}),Me||(Me=function(e){return e}),e||(e=function(e){return e}),d||(d=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/gm),rt=e(/^data-[\-\w.\u00B7-\uFFFF]/),lt=e(/^aria-[\-\w]+$/),ct=e(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),dt=e(/^(?:\w+script|data):/i),ut=e(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ht="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function mt(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/i,n))ze(l,e);else{V&&(n=Pe(n,_," "),n=Pe(n,D," "));var d=e.nodeName.toLowerCase();if(Ie(d,s,n))try{c?e.setAttributeNS(c,l,n):e.setAttribute(l,n),je(u.removed)}catch(e){}}}Oe("afterSanitizeAttributes",e,null)}},De=function e(t){var n=void 0,s=Le(t);for(Oe("beforeSanitizeShadowDOM",t,null);n=s.nextNode();)Oe("uponSanitizeShadowNode",n,null),Ee(n)||(n.content instanceof h&&e(n.content),_e(n));Oe("afterSanitizeShadowDOM",t,null)};return u.sanitize=function(e,t){var n=void 0,s=void 0,o=void 0,i=void 0,a=void 0;if((fe=!e)&&(e="\x3c!--\x3e"),"string"!=typeof e&&!xe(e)){if("function"!=typeof e.toString)throw Ue("toString is not a function");if("string"!=typeof(e=e.toString()))throw Ue("dirty is not a string, aborting")}if(!u.isSupported){if("object"===ht(c.toStaticHTML)||"function"==typeof c.toStaticHTML){if("string"==typeof e)return c.toStaticHTML(e);if(xe(e))return c.toStaticHTML(e.outerHTML)}return e}if(Z||be(t),u.removed=[],"string"==typeof e&&(oe=!1),oe);else if(e instanceof m)1===(s=(n=Ae("\x3c!----\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===s.nodeName||"HTML"===s.nodeName?n=s:n.appendChild(s);else{if(!$&&!V&&!G&&-1===e.indexOf("<"))return T&&te?T.createHTML(e):e;if(!(n=Ae(e)))return $?null:C}n&&X&&ke(n.firstChild);for(var r=Le(oe?e:n);o=r.nextNode();)3===o.nodeType&&o===i||Ee(o)||(o.content instanceof h&&De(o.content),_e(o),i=o);if(i=null,oe)return e;if($){if(J)for(a=L.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ee&&(a=O.call(d,a,!0)),a}var l=G?n.outerHTML:n.innerHTML;return V&&(l=Pe(l,_," "),l=Pe(l,D," ")),T&&te?T.createHTML(l):l},u.setConfig=function(e){be(e),Z=!0},u.clearConfig=function(){ye=null,Z=!1},u.isValidAttribute=function(e,t,n){ye||be({});var s=Ne(e),o=Ne(t);return Ie(s,o,n)},u.addHook=function(e,t){"function"==typeof t&&(I[e]=I[e]||[],He(I[e],t))},u.removeHook=function(e){I[e]&&je(I[e])},u.removeHooks=function(e){I[e]&&(I[e]=[])},u.removeAllHooks=function(){I={}},u}()});var extend=function(e,t){for(var n in t)hasProp.call(t,n)&&(e[n]=t[n]);function s(){this.constructor=e}return s.prototype=t.prototype,e.prototype=new s,e.__super__=t.prototype,e},hasProp={}.hasOwnProperty,bind=function(e,t){return function(){return e.apply(t,arguments)}},slice=[].slice;!function(O){var s,n,o,i,a,e,r,l,c,d;r=(d=document.getElementsByTagName("script"))[d.length-1],c=O.location.protocol.replace(":",""),r&&r.src&&(l=r.src.match(".*://([^:/]*).*")[1],c=r.src.match("(.*)://[^:/]*.*")[1]),n=function(){function e(e){var t,n,s;for(t in this.options={},n=this.defaults)s=n[t],this.options[t]=s;for(t in e)s=e[t],this.options[t]=s}return e.prototype.defaults={debug:!1},e}(),s=function(e){function t(e){t.__super__.constructor.call(this,e),this.log=new i({debug:this.options.debug,logPrefix:this.options.logPrefix||this.logPrefix})}return extend(t,n),t}(),i=function(e){function t(){return this.log=bind(this.log,this),this.error=bind(this.error,this),this.notice=bind(this.notice,this),this.debug=bind(this.debug,this),t.__super__.constructor.apply(this,arguments)}return extend(t,n),t.prototype.debug=function(){var e;if(e=1<=arguments.length?slice.call(arguments,0):[],this.options.debug)return this.log("debug",e)},t.prototype.notice=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("notice",e)},t.prototype.error=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("error",e)},t.prototype.log=function(e,t){var n,s,o,i,a;if(t.unshift("||"),t.unshift(e),t.unshift(this.options.logPrefix),console.log.apply(console,t),this.options.debug){for(a="",o=0,i=t.length;o"+a+""+n.innerHTML:void 0}},t}(),a=function(e){function t(){return this.stop=bind(this.stop,this),this.start=bind(this.start,this),t.__super__.constructor.apply(this,arguments)}return extend(t,s),t.prototype.timeoutStartedAt=null,t.prototype.logPrefix="timeout",t.prototype.defaults={debug:!1,timeout:4,timeoutIntervallCheck:.5},t.prototype.start=function(){var e,t,n;return this.stop(),t=new Date,e=function(){var e;if(e=new Date-new Date(t.getTime()+1e3*n.options.timeout*60),n.log.debug("Timeout check for "+n.options.timeout+" minutes (left "+e/1e3+" sec.)"),!(e<0))return n.stop(),n.options.callback()},(n=this).log.debug("Start timeout in "+this.options.timeout+" minutes"),this.intervallId=setInterval(e,1e3*this.options.timeoutIntervallCheck*60)},t.prototype.stop=function(){if(this.intervallId)return this.log.debug("Stop timeout of "+this.options.timeout+" minutes"),clearInterval(this.intervallId)},t}(),o=function(e){function t(){return this.ping=bind(this.ping,this),this.send=bind(this.send,this),this.reconnect=bind(this.reconnect,this),this.close=bind(this.close,this),this.connect=bind(this.connect,this),this.set=bind(this.set,this),t.__super__.constructor.apply(this,arguments)}return extend(t,s),t.prototype.logPrefix="io",t.prototype.set=function(e){var t,n,s;for(t in n=[],e)s=e[t],n.push(this.options[t]=s);return n},t.prototype.connect=function(){var t,o,n,s;return this.log.debug("Connecting to "+this.options.host),this.ws=new O.WebSocket(""+this.options.host),this.ws.onopen=(t=this,function(e){return t.log.debug("onOpen",e),t.options.onOpen(e),t.ping()}),this.ws.onmessage=(o=this,function(e){var t,n,s;for(s=JSON.parse(e.data),o.log.debug("onMessage",e.data),t=0,n=s.length;tChat with us!",scrollHint:"Scroll down to see new messages",idleTimeout:6,idleTimeoutIntervallCheck:.5,inactiveTimeout:8,inactiveTimeoutIntervallCheck:.5,waitingListTimeout:4,waitingListTimeoutIntervallCheck:.5,onReady:void 0,onCloseAnimationEnd:void 0,onError:void 0,onOpenAnimationEnd:void 0,onConnectionReestablished:void 0,onSessionClosed:void 0,onConnectionEstablished:void 0,onCssLoaded:void 0},n.prototype.logPrefix="chat",n.prototype._messageCount=0,n.prototype.isOpen=!1,n.prototype.blinkOnlineInterval=null,n.prototype.stopBlinOnlineStateTimeout=null,n.prototype.showTimeEveryXMinutes=2,n.prototype.lastTimestamp=null,n.prototype.lastAddedType=null,n.prototype.inputDisabled=!1,n.prototype.inputTimeout=null,n.prototype.isTyping=!1,n.prototype.state="offline",n.prototype.initialQueueDelay=1e4,n.prototype.translations={da:{"Chat with us!":"Chat med os!","Scroll down to see new messages":"Scroll ned for at se nye beskeder",Online:"Online",Offline:"Offline",Connecting:"Forbinder","Connection re-established":"Forbindelse genoprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat lukket af %s","Compose your message...":"Skriv en besked...","All colleagues are busy.":"Alle kollegaer er optaget.","You are on waiting list position %s.":"Du er i venteliste som nummer %s.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale med %s blevet lukket.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale blevet lukket.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, det tager længere end forventet at få en ledig plads. Prøv venligst igen senere eller send os en e-mail. På forhånd tak!"},de:{"Chat with us!":"Chatte mit uns!","Scroll down to see new messages":"Scrolle nach unten um neue Nachrichten zu sehen",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbindung wiederhergestellt",Today:"Heute",Send:"Senden","Chat closed by %s":"Chat beendet von %s","Compose your message...":"Ihre Nachricht...","All colleagues are busy.":"Alle Kollegen sind belegt.","You are on waiting list position %s.":"Sie sind in der Warteliste an der Position %s.","Start new conversation":"Neue Konversation starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit %s geschlossen.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Es tut uns leid, es dauert länger als erwartet, um einen freien Platz zu erhalten. Bitte versuchen Sie es zu einem späteren Zeitpunkt noch einmal oder schicken Sie uns eine E-Mail. Vielen Dank!"},es:{"Chat with us!":"Chatee con nosotros!","Scroll down to see new messages":"Haga scroll hacia abajo para ver nuevos mensajes",Online:"En linea",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexión restablecida",Today:"Hoy",Send:"Enviar","Chat closed by %s":"Chat cerrado por %s","Compose your message...":"Escriba su mensaje...","All colleagues are busy.":"Todos los agentes están ocupados.","You are on waiting list position %s.":"Usted está en la posición %s de la lista de espera.","Start new conversation":"Iniciar nueva conversación","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación con %s se ha cerrado.","Since you didn't respond in the last %s minutes your conversation got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación se ha cerrado.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Lo sentimos, se tarda más tiempo de lo esperado para ser atendido por un agente. Inténtelo de nuevo más tarde o envíenos un correo electrónico. ¡Gracias!"},fi:{"Chat with us!":"Keskustele kanssamme!","Scroll down to see new messages":"Rullaa alas nähdäksesi uudet viestit",Online:"Paikalla",Offline:"Poissa",Connecting:"Yhdistetään","Connection re-established":"Yhteys muodostettu uudelleen",Today:"Tänään",Send:"Lähetä","Chat closed by %s":"%s sulki keskustelun","Compose your message...":"Luo viestisi...","All colleagues are busy.":"Kaikki kollegat ovat varattuja.","You are on waiting list position %s.":"Olet odotuslistalla sijalla %s.","Start new conversation":"Aloita uusi keskustelu","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi %s kanssa suljettiin.","Since you didn't respond in the last %s minutes your conversation got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi suljettiin.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Olemme pahoillamme, tyhjän paikan vapautumisessa kestää odotettua pidempään. Ole hyvä ja yritä myöhemmin uudestaan tai lähetä meille sähköpostia. Kiitos!"},fr:{"Chat with us!":"Chattez avec nous!","Scroll down to see new messages":"Faites défiler pour lire les nouveaux messages",Online:"En-ligne",Offline:"Hors-ligne",Connecting:"Connexion en cours","Connection re-established":"Connexion rétablie",Today:"Aujourdhui",Send:"Envoyer","Chat closed by %s":"Chat fermé par %s","Compose your message...":"Composez votre message...","All colleagues are busy.":"Tous les collaborateurs sont occupés actuellement.","You are on waiting list position %s.":"Vous êtes actuellement en position %s dans la file d'attente.","Start new conversation":"Démarrer une nouvelle conversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation avec %s sera fermée.","Since you didn't respond in the last %s minutes your conversation got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation va être fermée.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Nous sommes désolés, il faut plus de temps que prévu pour obtenir un emplacement vide. Veuillez réessayer ultérieurement ou nous envoyer un courriel. Nous vous remercions!"},he:{"Chat with us!":"שוחחאיתנו!","Scroll down to see new messages":"גלול מטה כדי לראות הודעות חדשות",Online:"מחובר",Offline:"מנותק",Connecting:"מתחבר","Connection re-established":"החיבור שוחזר",Today:"היום",Send:"שלח","Chat closed by %s":'הצאט נסגר ע"י %s',"Compose your message...":"כתוב את ההודעה שלך ...","All colleagues are busy.":"כל הנציגים תפוסים","You are on waiting list position %s.":"מיקומך בתור %s.","Start new conversation":"התחל שיחה חדשה","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"מכיוון שלא הגבת במהלך %s דקות השיחה שלך עם %s נסגרה.","Since you didn't respond in the last %s minutes your conversation got closed.":"מכיוון שלא הגבת במהלך %s הדקות האחרונות השיחה שלך נסגרה.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":'מצטערים, הזמן לקבלת נציג ארוך מהרגיל. נסה שוב מאוחר יותר או שלח לנו דוא"ל. תודה!'},hu:{"Chat with us!":"Chatelj velünk!","Scroll down to see new messages":"Görgess lejjebb az újabb üzenetekért",Online:"Online",Offline:"Offline",Connecting:"Csatlakozás","Connection re-established":"Újracsatlakozás",Today:"Ma",Send:"Küldés","Chat closed by %s":"A beszélgetést lezárta %s","Compose your message...":"Írj üzenetet...","All colleagues are busy.":"Jelenleg minden kollégánk elfoglalt.","You are on waiting list position %s.":"A várólistán a %s. pozícióban várakozol.","Start new conversation":"Új beszélgetés indítása","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Mivel %s perce nem érkezett újabb üzenet, ezért a %s kollégával folytatott beszéletést lezártuk.","Since you didn't respond in the last %s minutes your conversation got closed.":"Mivel %s perce nem érkezett válasz, a beszélgetés lezárult.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Sajnáljuk, de a várakozási idő hosszabb a szokásosnál. Kérlek próbáld újra, vagy írd meg kérdésed emailben. Köszönjük!"},nl:{"Chat with us!":"Chat met ons!","Scroll down to see new messages":"Scrol naar beneden om nieuwe berichten te zien",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbinding herstelt",Today:"Vandaag",Send:"Verzenden","Chat closed by %s":"Chat gesloten door %s","Compose your message...":"Typ uw bericht...","All colleagues are busy.":"Alle medewerkers zijn bezet.","You are on waiting list position %s.":"U bent %s in de wachtrij.","Start new conversation":"Nieuwe conversatie starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft wordt de conversatie met %s gesloten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft is de conversatie gesloten.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Het spijt ons, het duurt langer dan verwacht om te antwoorden. Alstublieft probeer het later nogmaals of stuur ons een email. Hartelijk dank!"},it:{"Chat with us!":"Chatta con noi!","Scroll down to see new messages":"Scorrere verso il basso per vedere i nuovi messaggi",Online:"Online",Offline:"Offline",Connecting:"Collegamento","Connection re-established":"Collegamento ristabilito",Today:"Oggi",Send:"Invio","Chat closed by %s":"Conversazione chiusa da %s","Compose your message...":"Comporre il tuo messaggio...","All colleagues are busy.":"Tutti i colleghi sono occupati.","You are on waiting list position %s.":"Siete in posizione lista d' attesa %s.","Start new conversation":"Avviare una nuova conversazione","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua conversazione con %s si è chiusa.","Since you didn't respond in the last %s minutes your conversation got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua conversazione si è chiusa.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Ci dispiace, ci vuole più tempo come previsto per ottenere uno slot vuoto. Per favore riprova più tardi o inviaci un' e-mail. Grazie!"},pl:{"Chat with us!":"Czatuj z nami!","Scroll down to see new messages":"Przewiń w dół, aby wyświetlić nowe wiadomości",Online:"Online",Offline:"Offline",Connecting:"Łączenie","Connection re-established":"Ponowne nawiązanie połączenia",Today:"dzisiejszy",Send:"Wyślij","Chat closed by %s":"Czat zamknięty przez %s","Compose your message...":"Utwórz swoją wiadomość...","All colleagues are busy.":"Wszyscy koledzy są zajęci.","You are on waiting list position %s.":"Na liście oczekujących znajduje się pozycja %s.","Start new conversation":"Rozpoczęcie nowej konwersacji","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ponieważ w ciągu ostatnich %s minut nie odpowiedziałeś, Twoja rozmowa z %s została zamknięta.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ponieważ nie odpowiedziałeś w ciągu ostatnich %s minut, Twoja rozmowa została zamknięta.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Przykro nam, ale to trwa dłużej niż się spodziewamy. Spróbuj ponownie później lub wyślij nam wiadomość e-mail. Dziękuję!"},"pt-br":{"Chat with us!":"Chat fale conosco!","Scroll down to see new messages":"Role para baixo, para ver nosvas mensagens",Online:"Online",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexão restabelecida",Today:"Hoje",Send:"Enviar","Chat closed by %s":"Chat encerrado por %s","Compose your message...":"Escreva sua mensagem...","All colleagues are busy.":"Todos os agentes estão ocupados.","You are on waiting list position %s.":"Você está na posição %s na fila de espera.","Start new conversation":"Iniciar uma nova conversa","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Como você não respondeu nos últimos %s minutos sua conversa com %s foi encerrada.","Since you didn't respond in the last %s minutes your conversation got closed.":"Como você não respondeu nos últimos %s minutos sua conversa foi encerrada.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Desculpe, mas o tempo de espera por um agente foi excedido. Tente novamente mais tarde ou nós envie um email. Obrigado"},"zh-cn":{"Chat with us!":"发起即时对话!","Scroll down to see new messages":"向下滚动以查看新消息",Online:"在线",Offline:"离线",Connecting:"连接中","Connection re-established":"正在重新建立连接",Today:"今天",Send:"发送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在输入信息...","All colleagues are busy.":"所有工作人员都在忙碌中.","You are on waiting list position %s.":"您目前的等候位置是第 %s 位.","Start new conversation":"开始新的会话","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由于您超过 %s 分钟没有回复, 您与 %s 的会话已被关闭.","Since you didn't respond in the last %s minutes your conversation got closed.":"由于您超过 %s 分钟没有任何回复, 该对话已被关闭.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 目前需要等候更长的时间才能接入对话, 请稍后重试或向我们发送电子邮件. 谢谢!"},"zh-tw":{"Chat with us!":"開始即時對话!","Scroll down to see new messages":"向下滑動以查看新訊息",Online:"線上",Offline:"离线",Connecting:"連線中","Connection re-established":"正在重新建立連線中",Today:"今天",Send:"發送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在輸入訊息...","All colleagues are busy.":"所有服務人員都在忙碌中.","You are on waiting list position %s.":"你目前的等候位置是第 %s 順位.","Start new conversation":"開始新的對話","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由於你超過 %s 分鐘沒有回應, 你與 %s 的對話已被關閉.","Since you didn't respond in the last %s minutes your conversation got closed.":"由於你超過 %s 分鐘沒有任何回應, 該對話已被關閉.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 當前需要等候更長的時間方可排入對話程序, 請稍後重試或向我們寄送電子郵件. 謝謝!"},ru:{"Chat with us!":"Напишите нам!","Scroll down to see new messages":"Прокрутите, чтобы увидеть новые сообщения",Online:"Онлайн",Offline:"Оффлайн",Connecting:"Подключение","Connection re-established":"Подключение восстановлено",Today:"Сегодня",Send:"Отправить","Chat closed by %s":"%s закрыл чат","Compose your message...":"Напишите сообщение...","All colleagues are busy.":"Все сотрудники заняты","You are on waiting list position %s.":"Вы в списке ожидания под номером %s","Start new conversation":"Начать новую переписку.","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор с %s был закрыт.","Since you didn't respond in the last %s minutes your conversation got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор был закрыт.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"К сожалению, ожидание свободного места требует больше времени. Повторите попытку позже или отправьте нам электронное письмо. Спасибо!"},sv:{"Chat with us!":"Chatta med oss!","Scroll down to see new messages":"Rulla ner för att se nya meddelanden",Online:"Online",Offline:"Offline",Connecting:"Ansluter","Connection re-established":"Anslutningen återupprättas",Today:"I dag",Send:"Skicka","Chat closed by %s":"Chatt stängd av %s","Compose your message...":"Skriv ditt meddelande...","All colleagues are busy.":"Alla kollegor är upptagna.","You are on waiting list position %s.":"Du är på väntelistan som position %s.","Start new conversation":"Starta ny konversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Eftersom du inte svarat inom %s minuterna i din konversation med %s så stängdes chatten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Då du inte svarat inom de senaste %s minuterna så avslutades din chatt.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi är ledsna, det tar längre tid som förväntat att få en ledig plats. Försök igen senare eller skicka ett e-postmeddelande till oss. Tack!"},no:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},nb:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},el:{"Chat with us!":"Επικοινωνήστε μαζί μας!","Scroll down to see new messages":"Μεταβείτε κάτω για να δείτε τα νέα μηνύματα",Online:"Σε σύνδεση",Offline:"Αποσυνδεμένος",Connecting:"Σύνδεση","Connection re-established":"Η σύνδεση αποκαταστάθηκε",Today:"Σήμερα",Send:"Αποστολή","Chat closed by %s":"Η συνομιλία έκλεισε από τον/την %s","Compose your message...":"Γράψτε το μήνυμα σας...","All colleagues are busy.":"Όλοι οι συνάδελφοι μας είναι απασχολημένοι.","You are on waiting list position %s.":"Βρίσκεστε σε λίστα αναμονής στη θέση %s.","Start new conversation":"Έναρξη νέας συνομιλίας","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας με τον/την %s έκλεισε.","Since you didn't respond in the last %s minutes your conversation got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας έκλεισε.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Λυπούμαστε που χρειάζεται περισσότερος χρόνος από τον αναμενόμενο για να βρεθεί μία κενή θέση. Παρακαλούμε δοκιμάστε ξανά αργότερα ή στείλτε μας ένα email. Ευχαριστούμε!"}},n.prototype.sessionId=void 0,n.prototype.scrolledToBottom=!0,n.prototype.scrollSnapTolerance=10,n.prototype.richTextFormatKey={66:!0,73:!0,85:!0,83:!0},n.prototype.T=function(){var e,t,n,s,o,i;if(o=arguments[0],t=2<=arguments.length?slice.call(arguments,1):[],this.options.lang&&"en"!==this.options.lang&&(this.translations[this.options.lang]?((i=this.translations[this.options.lang])[o]||this.log.notice("Translation needed for '"+o+"'"),o=i[o]||o):this.log.notice("Translation '"+this.options.lang+"' needed!")),t)for(n=0,s=t.length;n"+L.replace(/\n/g,"
")+"
").replace(/
<\/div>/g,"

")),console.log("p",s,L),"html"===s){for(e=document.createElement("div"),A=DOMPurify.sanitize(L),this.log.debug("sanitized HTML clipboard",A),e.innerHTML=A,f=!1,o=L,z=new RegExp("<(/w|w):[A-Za-z]"),o.match(z)&&(f=!0,o=o.replace(z,"")),z=new RegExp("<(/o|o):[A-Za-z]"),o.match(z)&&(f=!0,o=o.replace(z,"")),f&&(e=this.wordFilter(e)),l=0,u=(S=e.childNodes).length;lnew Date((new Date).getTime()-1500)))return this.isTyping=new Date,this.send("chat_session_typing",{session_id:this.sessionId}),this.inactiveTimeout.start()},n.prototype.onSubmit=function(e){return e.preventDefault(),this.sendMessage()},n.prototype.sendMessage=function(){var e,t;if(e=this.input.innerHTML)return this.inactiveTimeout.start(),sessionStorage.removeItem("unfinished_message"),t=this.view("message")({message:e,from:"customer",id:this._messageCount++,unreadClass:""}),this.maybeAddTimestamp(),this.el.querySelector(".zammad-chat-message--typing")?(this.lastAddedType="typing-placeholder",this.el.querySelector(".zammad-chat-message--typing").insertAdjacentHTML("beforebegin",t)):(this.lastAddedType="message--customer",this.body.insertAdjacentHTML("beforeend",t)),this.input.innerHTML="",this.scrollToBottom(),this.send("chat_session_message",{content:e,id:this._messageCount,session_id:this.sessionId})},n.prototype.receiveMessage=function(e){return this.inactiveTimeout.start(),this.onAgentTypingEnd(),this.maybeAddTimestamp(),this.renderMessage({message:e.message.content,id:e.id,from:"agent"}),this.scrollToBottom({showHint:!0})},n.prototype.renderMessage=function(e){return this.lastAddedType="message--"+e.from,e.unreadClass=document.hidden?" zammad-chat-message--unread":"",this.body.insertAdjacentHTML("beforeend",this.view("message")(e))},n.prototype.open=function(){var e;if(!this.isOpen)return this.isOpen=!0,this.log.debug("open widget"),this.show(),this.sessionId||this.showLoader(),this.el.classList.add("zammad-chat-is-open"),e=this.el.clientHeight-this.el.querySelector(".zammad-chat-header").offsetHeight,this.el.style.transform="translateY("+e+"px)",this.el.clientHeight,this.sessionId?(this.el.style.transform="",this.onOpenAnimationEnd()):(this.el.addEventListener("transitionend",this.onOpenAnimationEnd),this.el.classList.add("zammad-chat--animate"),this.el.clientHeight,this.el.style.transform="",this.send("chat_session_init",{url:O.location.href}));this.log.debug("widget already open, block")},n.prototype.onOpenAnimationEnd=function(){var e;return this.el.removeEventListener("transitionend",this.onOpenAnimationEnd),this.el.classList.remove("zammad-chat--animate"),this.idleTimeout.stop(),this.isFullscreen&&this.disableScrollOnRoot(),"function"==typeof(e=this.options).onOpenAnimationEnd?e.onOpenAnimationEnd():void 0},n.prototype.sessionClose=function(){return this.send("chat_session_close",{session_id:this.sessionId}),this.inactiveTimeout.stop(),this.waitingListTimeout.stop(),sessionStorage.removeItem("unfinished_message"),this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.setSessionId(void 0)},n.prototype.toggle=function(e){return this.isOpen?this.close(e):this.open(e)},n.prototype.close=function(e){var t;if(this.isOpen){if(this.initDelayId&&clearTimeout(this.initDelayId),this.sessionId)return this.log.debug("close widget"),e&&e.stopPropagation(),this.sessionClose(),this.isFullscreen&&this.enableScrollOnRoot(),t=this.el.clientHeight-this.el.querySelector(".zammad-chat-header").offsetHeight,this.el.addEventListener("transitionend",this.onCloseAnimationEnd),this.el.classList.add("zammad-chat--animate"),document.offsetHeight,this.el.style.transform="translateY("+t+"px)";this.log.debug("can't close widget without sessionId")}else this.log.debug("can't close widget, it's not open")},n.prototype.onCloseAnimationEnd=function(){var e;return this.el.removeEventListener("transitionend",this.onCloseAnimationEnd),this.el.classList.remove("zammad-chat-is-open","zammad-chat--animate"),this.el.style.transform="",this.showLoader(),this.el.querySelector(".zammad-chat-welcome").classList.remove("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent").classList.add("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent-status").classList.add("zammad-chat-is-hidden"),this.isOpen=!1,"function"==typeof(e=this.options).onCloseAnimationEnd&&e.onCloseAnimationEnd(),this.io.reconnect()},n.prototype.onWebSocketClose=function(){if(!this.isOpen)return this.el?(this.el.classList.remove("zammad-chat-is-shown"),this.el.classList.remove("zammad-chat-is-loaded")):void 0},n.prototype.show=function(){if("offline"!==this.state)return this.el.classList.add("zammad-chat-is-loaded"),this.el.classList.add("zammad-chat-is-shown")},n.prototype.disableInput=function(){return this.inputDisabled=!0,this.input.setAttribute("contenteditable",!1),this.el.querySelector(".zammad-chat-send").disabled=!0,this.io.close()},n.prototype.enableInput=function(){return this.inputDisabled=!1,this.input.setAttribute("contenteditable",!0),this.el.querySelector(".zammad-chat-send").disabled=!1},n.prototype.hideModal=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=""},n.prototype.onQueueScreen=function(e){var t,n;if(this.setSessionId(e.session_id),t=function(){return n.onQueue(e),n.waitingListTimeout.start()},!(n=this).initialQueueDelay||this.onInitialQueueDelayId)return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),t();this.onInitialQueueDelayId=setTimeout(t,this.initialQueueDelay)},n.prototype.onQueue=function(e){return this.log.notice("onQueue",e.position),this.inQueue=!0,this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("waiting")({position:e.position})},n.prototype.onAgentTypingStart=function(){if(this.stopTypingId&&clearTimeout(this.stopTypingId),this.stopTypingId=setTimeout(this.onAgentTypingEnd,3e3),!this.el.querySelector(".zammad-chat-message--typing")&&(this.maybeAddTimestamp(),this.body.insertAdjacentHTML("beforeend",this.view("typingIndicator")()),this.isVisible(this.el.querySelector(".zammad-chat-message--typing"),!0)))return this.scrollToBottom()},n.prototype.onAgentTypingEnd=function(){if(this.el.querySelector(".zammad-chat-message--typing"))return this.el.querySelector(".zammad-chat-message--typing").remove()},n.prototype.onLeaveTemporary=function(){if(this.sessionId)return this.send("chat_session_leave_temporary",{session_id:this.sessionId})},n.prototype.maybeAddTimestamp=function(){var e,t,n;if(n=Date.now(),!this.lastTimestamp||n-this.lastTimestamp>6e4*this.showTimeEveryXMinutes)return e=this.T("Today"),t=(new Date).toTimeString().substr(0,5),"timestamp"===this.lastAddedType?(this.updateLastTimestamp(e,t),this.lastTimestamp=n):(this.body.insertAdjacentHTML("beforeend",this.view("timestamp")({label:e,time:t})),this.lastTimestamp=n,this.lastAddedType="timestamp",this.scrollToBottom())},n.prototype.updateLastTimestamp=function(e,t){var n;if(this.el&&(n=this.el.querySelectorAll(".zammad-chat-body .zammad-chat-timestamp")))return n[n.length-1].outerHTML=this.view("timestamp")({label:e,time:t})},n.prototype.addStatus=function(e){if(this.el)return this.maybeAddTimestamp(),this.body.insertAdjacentHTML("beforeend",this.view("status")({status:e})),this.scrollToBottom()},n.prototype.detectScrolledtoBottom=function(){var e;if(e=this.body.scrollTop+this.body.offsetHeight,this.scrolledToBottom=Math.abs(e-this.body.scrollHeight)<=this.scrollSnapTolerance,this.scrolledToBottom)return this.el.querySelector(".zammad-scroll-hint").classList.add("is-hidden")},n.prototype.showScrollHint=function(){return this.el.querySelector(".zammad-scroll-hint").classList.remove("is-hidden"),this.body.scrollTop=this.body.scrollTop+this.el.querySelector(".zammad-scroll-hint").offsetHeight},n.prototype.onScrollHintClick=function(){return this.body.scrollTo({top:this.body.scrollHeight,behavior:"smooth"})},n.prototype.scrollToBottom=function(e){var t;return t=(null!=e?e:{showHint:!1}).showHint,this.scrolledToBottom?this.body.scrollTop=this.body.scrollHeight:t?this.showScrollHint():void 0},n.prototype.destroy=function(e){var t;return null==e&&(e={}),this.log.debug("destroy widget",e),this.setAgentOnlineState("offline"),e.remove&&this.el&&(this.el.remove(),(t=document.querySelector("."+this.options.buttonClass))&&(t.classList.add(this.options.inactiveClass),t.style.display="none")),this.waitingListTimeout&&this.waitingListTimeout.stop(),this.inactiveTimeout&&this.inactiveTimeout.stop(),this.idleTimeout&&this.idleTimeout.stop(),this.io.close()},n.prototype.reconnect=function(){return this.log.notice("reconnecting"),this.disableInput(),this.lastAddedType="status",this.setAgentOnlineState("connecting"),this.addStatus(this.T("Connection lost"))},n.prototype.onConnectionReestablished=function(){var e;return this.lastAddedType="status",this.setAgentOnlineState("online"),this.addStatus(this.T("Connection re-established")),"function"==typeof(e=this.options).onConnectionReestablished?e.onConnectionReestablished():void 0},n.prototype.onSessionClosed=function(e){var t;return this.addStatus(this.T("Chat closed by %s",e.realname)),this.disableInput(),this.setAgentOnlineState("offline"),this.inactiveTimeout.stop(),"function"==typeof(t=this.options).onSessionClosed?t.onSessionClosed(e):void 0},n.prototype.setSessionId=function(e){return void 0===(this.sessionId=e)?sessionStorage.removeItem("sessionId"):sessionStorage.setItem("sessionId",e)},n.prototype.onConnectionEstablished=function(e){var t;return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.inQueue=!1,e.agent&&(this.agent=e.agent),e.session_id&&this.setSessionId(e.session_id),this.body.innerHTML="",this.el.querySelector(".zammad-chat-agent").innerHTML=this.view("agent")({agent:this.agent}),this.enableInput(),this.hideModal(),this.el.querySelector(".zammad-chat-welcome").classList.add("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent").classList.remove("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent-status").classList.remove("zammad-chat-is-hidden"),this.isFullscreen||this.input.focus(),this.setAgentOnlineState("online"),this.waitingListTimeout.stop(),this.idleTimeout.stop(),this.inactiveTimeout.start(),"function"==typeof(t=this.options).onConnectionEstablished?t.onConnectionEstablished(e):void 0},n.prototype.showCustomerTimeout=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("customer_timeout")({agent:this.agent.name,delay:this.options.inactiveTimeout}),this.el.querySelector(".js-restart").addEventListener("click",function(){return location.reload()}),this.sessionClose()},n.prototype.showWaitingListTimeout=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("waiting_list_timeout")({delay:this.options.watingListTimeout}),this.el.querySelector(".js-restart").addEventListener("click",function(){return location.reload()}),this.sessionClose()},n.prototype.showLoader=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("loader")()},n.prototype.setAgentOnlineState=function(e){var t;if(this.state=e,this.el)return t=e.charAt(0).toUpperCase()+e.slice(1),this.el.querySelector(".zammad-chat-agent-status").dataset.status=e,this.el.querySelector(".zammad-chat-agent-status").textContent=this.T(t)},n.prototype.detectHost=function(){var e;return e="ws://","https"===c&&(e="wss://"),this.options.host=""+e+l+"/ws"},n.prototype.loadCss=function(){var e,t,n;if(this.options.cssAutoload)return(n=this.options.cssUrl)||(n=this.options.host.replace(/^wss/i,"https").replace(/^ws/i,"http").replace(/\/ws$/i,""),n+="/assets/chat/chat.css"),this.log.debug("load css from '"+n+"'"),t="@import url('"+n+"');",(e=document.createElement("link")).onload=this.onCssLoaded,e.rel="stylesheet",e.href="data:text/css,"+escape(t),document.getElementsByTagName("head")[0].appendChild(e)},n.prototype.onCssLoaded=function(){var e;return this.cssLoaded=!0,this.socketReady&&this.onReady(),"function"==typeof(e=this.options).onCssLoaded?e.onCssLoaded():void 0},n.prototype.startTimeoutObservers=function(){var e,t,n;return this.idleTimeout=new a({logPrefix:"idleTimeout",debug:this.options.debug,timeout:this.options.idleTimeout,timeoutIntervallCheck:this.options.idleTimeoutIntervallCheck,callback:(e=this,function(){return e.log.debug("Idle timeout reached, hide widget",new Date),e.destroy({remove:!0})})}),this.inactiveTimeout=new a({logPrefix:"inactiveTimeout",debug:this.options.debug,timeout:this.options.inactiveTimeout,timeoutIntervallCheck:this.options.inactiveTimeoutIntervallCheck,callback:(t=this,function(){return t.log.debug("Inactive timeout reached, show timeout screen.",new Date),t.showCustomerTimeout(),t.destroy({remove:!1})})}),this.waitingListTimeout=new a({logPrefix:"waitingListTimeout",debug:this.options.debug,timeout:this.options.waitingListTimeout,timeoutIntervallCheck:this.options.waitingListTimeoutIntervallCheck,callback:(n=this,function(){return n.log.debug("Waiting list timeout reached, show timeout screen.",new Date),n.showWaitingListTimeout(),n.destroy({remove:!1})})})},n.prototype.disableScrollOnRoot=function(){return this.rootScrollOffset=this.scrollRoot.scrollTop,this.scrollRoot.style.overflow="hidden",this.scrollRoot.style.position="fixed"},n.prototype.enableScrollOnRoot=function(){return this.scrollRoot.scrollTop=this.rootScrollOffset,this.scrollRoot.style.overflow="",this.scrollRoot.style.position=""},n.prototype.isVisible=function(e,n,s,o){var i,a,r,l,c,d,u,h,m,p;if(!(e.length<1))return p=O.innerWidth,m=O.innerHeight,o=o||"both",a=!0!==s||t.offsetWidth*t.offsetHeight,u=0<=(d=e.getBoundingClientRect()).top&&d.top/gi,"")).replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,"")).replace(/<(\/?)s>/gi,"<$1strike>")).replace(/ /gi," "),e.innerHTML=t,i=0,c=(A=e.querySelectorAll("p")).length;i",/^\s*\w+\./.test(P)&&(y=(b=/([0-9])\./.exec(P))?null!=(O=1<(N=parseInt(b[1],10)))?O:'
    ':"
      "}:"
        "),l"+T.innerHTML+""),T.remove(),l=n}else l=0;for(v=0,u=(_=e.querySelectorAll("[style]")).length;v/g,">").replace(/"/g,""")}),function(){(function(){t.push('
        \n
        \n
        \n \n \n \n \n \n
        \n
        \n
        \n
        \n \n '),t.push(this.T(this.title)),t.push('\n
        \n
        \n
        \n \n
        \n
        \n
        \n \n
        \n
        ")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.customer_timeout=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
        \n '),this.agent?(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation with %s got closed.",this.delay,this.agent))):(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation got closed.",this.delay))),t.push("\n "),t.push('\n
        \n
        "),t.push(this.T("Start new conversation")),t.push("
        \n
        ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.loader=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('\n \n \n \n\n'),t.push(this.T("Connecting")),t.push("")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.message=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?o(e):""},s=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
        \n "),t.push(this.message),t.push("\n
        ")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.status=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
        \n
        \n '),t.push(this.status),t.push("\n
        \n
        ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.timestamp=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?o(e):""},s=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
        '),t.push(n(this.label)),t.push(" "),t.push(n(this.time)),t.push("
        ")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.typingIndicator=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
        \n \n \n \n \n \n \n \n
        ')}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
        \n \n \n \n \n \n '),t.push(this.T("All colleagues are busy.")),t.push("
        \n "),t.push(this.T("You are on waiting list position %s.",this.position)),t.push("\n
        ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting_list_timeout=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
        \n '),t.push(this.T("We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!")),t.push('\n
        \n
        "),t.push(this.T("Start new conversation")),t.push("
        \n
        ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")}; \ No newline at end of file +window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.agent=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?o(e):""},s=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){this.agent.avatar&&(t.push('\n\n')),t.push('\n\n '),t.push(n(this.agent.name)),t.push("\n")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,function(){"use strict";var s=Object.hasOwnProperty,i=Object.setPrototypeOf,a=Object.isFrozen,o=Object.getPrototypeOf,r=Object.getOwnPropertyDescriptor,Me=Object.freeze,e=Object.seal,l=Object.create,t="undefined"!=typeof Reflect&&Reflect,c=t.apply,d=t.construct;c||(c=function(e,t,n){return e.apply(t,n)}),Me||(Me=function(e){return e}),e||(e=function(e){return e}),d||(d=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/gm),rt=e(/^data-[\-\w.\u00B7-\uFFFF]/),lt=e(/^aria-[\-\w]+$/),ct=e(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),dt=e(/^(?:\w+script|data):/i),ut=e(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ht="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function mt(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/i,n))ze(l,e);else{V&&(n=Pe(n,_," "),n=Pe(n,D," "));var d=e.nodeName.toLowerCase();if(Ie(d,s,n))try{c?e.setAttributeNS(c,l,n):e.setAttribute(l,n),je(u.removed)}catch(e){}}}Oe("afterSanitizeAttributes",e,null)}},De=function e(t){var n=void 0,s=Le(t);for(Oe("beforeSanitizeShadowDOM",t,null);n=s.nextNode();)Oe("uponSanitizeShadowNode",n,null),Ee(n)||(n.content instanceof h&&e(n.content),_e(n));Oe("afterSanitizeShadowDOM",t,null)};return u.sanitize=function(e,t){var n=void 0,s=void 0,o=void 0,i=void 0,a=void 0;if((fe=!e)&&(e="\x3c!--\x3e"),"string"!=typeof e&&!xe(e)){if("function"!=typeof e.toString)throw Ue("toString is not a function");if("string"!=typeof(e=e.toString()))throw Ue("dirty is not a string, aborting")}if(!u.isSupported){if("object"===ht(c.toStaticHTML)||"function"==typeof c.toStaticHTML){if("string"==typeof e)return c.toStaticHTML(e);if(xe(e))return c.toStaticHTML(e.outerHTML)}return e}if(Z||be(t),u.removed=[],"string"==typeof e&&(oe=!1),oe);else if(e instanceof m)1===(s=(n=Ae("\x3c!----\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===s.nodeName||"HTML"===s.nodeName?n=s:n.appendChild(s);else{if(!$&&!V&&!G&&-1===e.indexOf("<"))return T&&te?T.createHTML(e):e;if(!(n=Ae(e)))return $?null:C}n&&X&&ke(n.firstChild);for(var r=Le(oe?e:n);o=r.nextNode();)3===o.nodeType&&o===i||Ee(o)||(o.content instanceof h&&De(o.content),_e(o),i=o);if(i=null,oe)return e;if($){if(J)for(a=L.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ee&&(a=O.call(d,a,!0)),a}var l=G?n.outerHTML:n.innerHTML;return V&&(l=Pe(l,_," "),l=Pe(l,D," ")),T&&te?T.createHTML(l):l},u.setConfig=function(e){be(e),Z=!0},u.clearConfig=function(){ye=null,Z=!1},u.isValidAttribute=function(e,t,n){ye||be({});var s=Ne(e),o=Ne(t);return Ie(s,o,n)},u.addHook=function(e,t){"function"==typeof t&&(I[e]=I[e]||[],He(I[e],t))},u.removeHook=function(e){I[e]&&je(I[e])},u.removeHooks=function(e){I[e]&&(I[e]=[])},u.removeAllHooks=function(){I={}},u}()});var extend=function(e,t){for(var n in t)hasProp.call(t,n)&&(e[n]=t[n]);function s(){this.constructor=e}return s.prototype=t.prototype,e.prototype=new s,e.__super__=t.prototype,e},hasProp={}.hasOwnProperty,bind=function(e,t){return function(){return e.apply(t,arguments)}},slice=[].slice;!function(O){var s,n,o,i,a,e,r,l,c,d;r=(d=document.getElementsByTagName("script"))[d.length-1],c=O.location.protocol.replace(":",""),r&&r.src&&(l=r.src.match(".*://([^:/]*).*")[1],c=r.src.match("(.*)://[^:/]*.*")[1]),n=function(){function e(e){var t,n,s;for(t in this.options={},n=this.defaults)s=n[t],this.options[t]=s;for(t in e)s=e[t],this.options[t]=s}return e.prototype.defaults={debug:!1},e}(),s=function(e){function t(e){t.__super__.constructor.call(this,e),this.log=new i({debug:this.options.debug,logPrefix:this.options.logPrefix||this.logPrefix})}return extend(t,n),t}(),i=function(e){function t(){return this.log=bind(this.log,this),this.error=bind(this.error,this),this.notice=bind(this.notice,this),this.debug=bind(this.debug,this),t.__super__.constructor.apply(this,arguments)}return extend(t,n),t.prototype.debug=function(){var e;if(e=1<=arguments.length?slice.call(arguments,0):[],this.options.debug)return this.log("debug",e)},t.prototype.notice=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("notice",e)},t.prototype.error=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("error",e)},t.prototype.log=function(e,t){var n,s,o,i,a;if(t.unshift("||"),t.unshift(e),t.unshift(this.options.logPrefix),console.log.apply(console,t),this.options.debug){for(a="",o=0,i=t.length;o"+a+"
        "+n.innerHTML:void 0}},t}(),a=function(e){function t(){return this.stop=bind(this.stop,this),this.start=bind(this.start,this),t.__super__.constructor.apply(this,arguments)}return extend(t,s),t.prototype.timeoutStartedAt=null,t.prototype.logPrefix="timeout",t.prototype.defaults={debug:!1,timeout:4,timeoutIntervallCheck:.5},t.prototype.start=function(){var e,t,n;return this.stop(),t=new Date,e=function(){var e;if(e=new Date-new Date(t.getTime()+1e3*n.options.timeout*60),n.log.debug("Timeout check for "+n.options.timeout+" minutes (left "+e/1e3+" sec.)"),!(e<0))return n.stop(),n.options.callback()},(n=this).log.debug("Start timeout in "+this.options.timeout+" minutes"),this.intervallId=setInterval(e,1e3*this.options.timeoutIntervallCheck*60)},t.prototype.stop=function(){if(this.intervallId)return this.log.debug("Stop timeout of "+this.options.timeout+" minutes"),clearInterval(this.intervallId)},t}(),o=function(e){function t(){return this.ping=bind(this.ping,this),this.send=bind(this.send,this),this.reconnect=bind(this.reconnect,this),this.close=bind(this.close,this),this.connect=bind(this.connect,this),this.set=bind(this.set,this),t.__super__.constructor.apply(this,arguments)}return extend(t,s),t.prototype.logPrefix="io",t.prototype.set=function(e){var t,n,s;for(t in n=[],e)s=e[t],n.push(this.options[t]=s);return n},t.prototype.connect=function(){var t,o,n,s;return this.log.debug("Connecting to "+this.options.host),this.ws=new O.WebSocket(""+this.options.host),this.ws.onopen=(t=this,function(e){return t.log.debug("onOpen",e),t.options.onOpen(e),t.ping()}),this.ws.onmessage=(o=this,function(e){var t,n,s;for(s=JSON.parse(e.data),o.log.debug("onMessage",e.data),t=0,n=s.length;tChat with us!",scrollHint:"Scroll down to see new messages",idleTimeout:6,idleTimeoutIntervallCheck:.5,inactiveTimeout:8,inactiveTimeoutIntervallCheck:.5,waitingListTimeout:4,waitingListTimeoutIntervallCheck:.5,onReady:void 0,onCloseAnimationEnd:void 0,onError:void 0,onOpenAnimationEnd:void 0,onConnectionReestablished:void 0,onSessionClosed:void 0,onConnectionEstablished:void 0,onCssLoaded:void 0},n.prototype.logPrefix="chat",n.prototype._messageCount=0,n.prototype.isOpen=!1,n.prototype.blinkOnlineInterval=null,n.prototype.stopBlinOnlineStateTimeout=null,n.prototype.showTimeEveryXMinutes=2,n.prototype.lastTimestamp=null,n.prototype.lastAddedType=null,n.prototype.inputDisabled=!1,n.prototype.inputTimeout=null,n.prototype.isTyping=!1,n.prototype.state="offline",n.prototype.initialQueueDelay=1e4,n.prototype.translations={da:{"Chat with us!":"Chat med os!","Scroll down to see new messages":"Scroll ned for at se nye beskeder",Online:"Online",Offline:"Offline",Connecting:"Forbinder","Connection re-established":"Forbindelse genoprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat lukket af %s","Compose your message...":"Skriv en besked...","All colleagues are busy.":"Alle kollegaer er optaget.","You are on waiting list position %s.":"Du er i venteliste som nummer %s.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale med %s blevet lukket.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale blevet lukket.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, det tager længere end forventet at få en ledig plads. Prøv venligst igen senere eller send os en e-mail. På forhånd tak!"},de:{"Chat with us!":"Chatte mit uns!","Scroll down to see new messages":"Scrolle nach unten um neue Nachrichten zu sehen",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbindung wiederhergestellt",Today:"Heute",Send:"Senden","Chat closed by %s":"Chat beendet von %s","Compose your message...":"Ihre Nachricht...","All colleagues are busy.":"Alle Kollegen sind belegt.","You are on waiting list position %s.":"Sie sind in der Warteliste an der Position %s.","Start new conversation":"Neue Konversation starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit %s geschlossen.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Es tut uns leid, es dauert länger als erwartet, um einen freien Platz zu erhalten. Bitte versuchen Sie es zu einem späteren Zeitpunkt noch einmal oder schicken Sie uns eine E-Mail. Vielen Dank!"},es:{"Chat with us!":"Chatee con nosotros!","Scroll down to see new messages":"Haga scroll hacia abajo para ver nuevos mensajes",Online:"En linea",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexión restablecida",Today:"Hoy",Send:"Enviar","Chat closed by %s":"Chat cerrado por %s","Compose your message...":"Escriba su mensaje...","All colleagues are busy.":"Todos los agentes están ocupados.","You are on waiting list position %s.":"Usted está en la posición %s de la lista de espera.","Start new conversation":"Iniciar nueva conversación","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación con %s se ha cerrado.","Since you didn't respond in the last %s minutes your conversation got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación se ha cerrado.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Lo sentimos, se tarda más tiempo de lo esperado para ser atendido por un agente. Inténtelo de nuevo más tarde o envíenos un correo electrónico. ¡Gracias!"},fi:{"Chat with us!":"Keskustele kanssamme!","Scroll down to see new messages":"Rullaa alas nähdäksesi uudet viestit",Online:"Paikalla",Offline:"Poissa",Connecting:"Yhdistetään","Connection re-established":"Yhteys muodostettu uudelleen",Today:"Tänään",Send:"Lähetä","Chat closed by %s":"%s sulki keskustelun","Compose your message...":"Luo viestisi...","All colleagues are busy.":"Kaikki kollegat ovat varattuja.","You are on waiting list position %s.":"Olet odotuslistalla sijalla %s.","Start new conversation":"Aloita uusi keskustelu","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi %s kanssa suljettiin.","Since you didn't respond in the last %s minutes your conversation got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi suljettiin.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Olemme pahoillamme, tyhjän paikan vapautumisessa kestää odotettua pidempään. Ole hyvä ja yritä myöhemmin uudestaan tai lähetä meille sähköpostia. Kiitos!"},fr:{"Chat with us!":"Chattez avec nous!","Scroll down to see new messages":"Faites défiler pour lire les nouveaux messages",Online:"En-ligne",Offline:"Hors-ligne",Connecting:"Connexion en cours","Connection re-established":"Connexion rétablie",Today:"Aujourdhui",Send:"Envoyer","Chat closed by %s":"Chat fermé par %s","Compose your message...":"Composez votre message...","All colleagues are busy.":"Tous les collaborateurs sont occupés actuellement.","You are on waiting list position %s.":"Vous êtes actuellement en position %s dans la file d'attente.","Start new conversation":"Démarrer une nouvelle conversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation avec %s sera fermée.","Since you didn't respond in the last %s minutes your conversation got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation va être fermée.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Nous sommes désolés, il faut plus de temps que prévu pour obtenir un emplacement vide. Veuillez réessayer ultérieurement ou nous envoyer un courriel. Nous vous remercions!"},he:{"Chat with us!":"שוחחאיתנו!","Scroll down to see new messages":"גלול מטה כדי לראות הודעות חדשות",Online:"מחובר",Offline:"מנותק",Connecting:"מתחבר","Connection re-established":"החיבור שוחזר",Today:"היום",Send:"שלח","Chat closed by %s":'הצאט נסגר ע"י %s',"Compose your message...":"כתוב את ההודעה שלך ...","All colleagues are busy.":"כל הנציגים תפוסים","You are on waiting list position %s.":"מיקומך בתור %s.","Start new conversation":"התחל שיחה חדשה","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"מכיוון שלא הגבת במהלך %s דקות השיחה שלך עם %s נסגרה.","Since you didn't respond in the last %s minutes your conversation got closed.":"מכיוון שלא הגבת במהלך %s הדקות האחרונות השיחה שלך נסגרה.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":'מצטערים, הזמן לקבלת נציג ארוך מהרגיל. נסה שוב מאוחר יותר או שלח לנו דוא"ל. תודה!'},hu:{"Chat with us!":"Chatelj velünk!","Scroll down to see new messages":"Görgess lejjebb az újabb üzenetekért",Online:"Online",Offline:"Offline",Connecting:"Csatlakozás","Connection re-established":"Újracsatlakozás",Today:"Ma",Send:"Küldés","Chat closed by %s":"A beszélgetést lezárta %s","Compose your message...":"Írj üzenetet...","All colleagues are busy.":"Jelenleg minden kollégánk elfoglalt.","You are on waiting list position %s.":"A várólistán a %s. pozícióban várakozol.","Start new conversation":"Új beszélgetés indítása","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Mivel %s perce nem érkezett újabb üzenet, ezért a %s kollégával folytatott beszéletést lezártuk.","Since you didn't respond in the last %s minutes your conversation got closed.":"Mivel %s perce nem érkezett válasz, a beszélgetés lezárult.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Sajnáljuk, de a várakozási idő hosszabb a szokásosnál. Kérlek próbáld újra, vagy írd meg kérdésed emailben. Köszönjük!"},nl:{"Chat with us!":"Chat met ons!","Scroll down to see new messages":"Scrol naar beneden om nieuwe berichten te zien",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbinding herstelt",Today:"Vandaag",Send:"Verzenden","Chat closed by %s":"Chat gesloten door %s","Compose your message...":"Typ uw bericht...","All colleagues are busy.":"Alle medewerkers zijn bezet.","You are on waiting list position %s.":"U bent %s in de wachtrij.","Start new conversation":"Nieuwe conversatie starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft wordt de conversatie met %s gesloten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft is de conversatie gesloten.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Het spijt ons, het duurt langer dan verwacht om te antwoorden. Alstublieft probeer het later nogmaals of stuur ons een email. Hartelijk dank!"},it:{"Chat with us!":"Chatta con noi!","Scroll down to see new messages":"Scorrere verso il basso per vedere i nuovi messaggi",Online:"Online",Offline:"Offline",Connecting:"Collegamento","Connection re-established":"Collegamento ristabilito",Today:"Oggi",Send:"Invio","Chat closed by %s":"Conversazione chiusa da %s","Compose your message...":"Comporre il tuo messaggio...","All colleagues are busy.":"Tutti i colleghi sono occupati.","You are on waiting list position %s.":"Siete in posizione lista d' attesa %s.","Start new conversation":"Avviare una nuova conversazione","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua conversazione con %s si è chiusa.","Since you didn't respond in the last %s minutes your conversation got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua conversazione si è chiusa.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Ci dispiace, ci vuole più tempo come previsto per ottenere uno slot vuoto. Per favore riprova più tardi o inviaci un' e-mail. Grazie!"},pl:{"Chat with us!":"Czatuj z nami!","Scroll down to see new messages":"Przewiń w dół, aby wyświetlić nowe wiadomości",Online:"Online",Offline:"Offline",Connecting:"Łączenie","Connection re-established":"Ponowne nawiązanie połączenia",Today:"dzisiejszy",Send:"Wyślij","Chat closed by %s":"Czat zamknięty przez %s","Compose your message...":"Utwórz swoją wiadomość...","All colleagues are busy.":"Wszyscy koledzy są zajęci.","You are on waiting list position %s.":"Na liście oczekujących znajduje się pozycja %s.","Start new conversation":"Rozpoczęcie nowej konwersacji","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ponieważ w ciągu ostatnich %s minut nie odpowiedziałeś, Twoja rozmowa z %s została zamknięta.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ponieważ nie odpowiedziałeś w ciągu ostatnich %s minut, Twoja rozmowa została zamknięta.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Przykro nam, ale to trwa dłużej niż się spodziewamy. Spróbuj ponownie później lub wyślij nam wiadomość e-mail. Dziękuję!"},"pt-br":{"Chat with us!":"Chat fale conosco!","Scroll down to see new messages":"Role para baixo, para ver nosvas mensagens",Online:"Online",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexão restabelecida",Today:"Hoje",Send:"Enviar","Chat closed by %s":"Chat encerrado por %s","Compose your message...":"Escreva sua mensagem...","All colleagues are busy.":"Todos os agentes estão ocupados.","You are on waiting list position %s.":"Você está na posição %s na fila de espera.","Start new conversation":"Iniciar uma nova conversa","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Como você não respondeu nos últimos %s minutos sua conversa com %s foi encerrada.","Since you didn't respond in the last %s minutes your conversation got closed.":"Como você não respondeu nos últimos %s minutos sua conversa foi encerrada.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Desculpe, mas o tempo de espera por um agente foi excedido. Tente novamente mais tarde ou nós envie um email. Obrigado"},"zh-cn":{"Chat with us!":"发起即时对话!","Scroll down to see new messages":"向下滚动以查看新消息",Online:"在线",Offline:"离线",Connecting:"连接中","Connection re-established":"正在重新建立连接",Today:"今天",Send:"发送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在输入信息...","All colleagues are busy.":"所有工作人员都在忙碌中.","You are on waiting list position %s.":"您目前的等候位置是第 %s 位.","Start new conversation":"开始新的会话","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由于您超过 %s 分钟没有回复, 您与 %s 的会话已被关闭.","Since you didn't respond in the last %s minutes your conversation got closed.":"由于您超过 %s 分钟没有任何回复, 该对话已被关闭.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 目前需要等候更长的时间才能接入对话, 请稍后重试或向我们发送电子邮件. 谢谢!"},"zh-tw":{"Chat with us!":"開始即時對话!","Scroll down to see new messages":"向下滑動以查看新訊息",Online:"線上",Offline:"离线",Connecting:"連線中","Connection re-established":"正在重新建立連線中",Today:"今天",Send:"發送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在輸入訊息...","All colleagues are busy.":"所有服務人員都在忙碌中.","You are on waiting list position %s.":"你目前的等候位置是第 %s 順位.","Start new conversation":"開始新的對話","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由於你超過 %s 分鐘沒有回應, 你與 %s 的對話已被關閉.","Since you didn't respond in the last %s minutes your conversation got closed.":"由於你超過 %s 分鐘沒有任何回應, 該對話已被關閉.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 當前需要等候更長的時間方可排入對話程序, 請稍後重試或向我們寄送電子郵件. 謝謝!"},ru:{"Chat with us!":"Напишите нам!","Scroll down to see new messages":"Прокрутите, чтобы увидеть новые сообщения",Online:"Онлайн",Offline:"Оффлайн",Connecting:"Подключение","Connection re-established":"Подключение восстановлено",Today:"Сегодня",Send:"Отправить","Chat closed by %s":"%s закрыл чат","Compose your message...":"Напишите сообщение...","All colleagues are busy.":"Все сотрудники заняты","You are on waiting list position %s.":"Вы в списке ожидания под номером %s","Start new conversation":"Начать новую переписку.","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор с %s был закрыт.","Since you didn't respond in the last %s minutes your conversation got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор был закрыт.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"К сожалению, ожидание свободного места требует больше времени. Повторите попытку позже или отправьте нам электронное письмо. Спасибо!"},sv:{"Chat with us!":"Chatta med oss!","Scroll down to see new messages":"Rulla ner för att se nya meddelanden",Online:"Online",Offline:"Offline",Connecting:"Ansluter","Connection re-established":"Anslutningen återupprättas",Today:"I dag",Send:"Skicka","Chat closed by %s":"Chatt stängd av %s","Compose your message...":"Skriv ditt meddelande...","All colleagues are busy.":"Alla kollegor är upptagna.","You are on waiting list position %s.":"Du är på väntelistan som position %s.","Start new conversation":"Starta ny konversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Eftersom du inte svarat inom %s minuterna i din konversation med %s så stängdes chatten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Då du inte svarat inom de senaste %s minuterna så avslutades din chatt.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi är ledsna, det tar längre tid som förväntat att få en ledig plats. Försök igen senare eller skicka ett e-postmeddelande till oss. Tack!"},no:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},nb:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},el:{"Chat with us!":"Επικοινωνήστε μαζί μας!","Scroll down to see new messages":"Μεταβείτε κάτω για να δείτε τα νέα μηνύματα",Online:"Σε σύνδεση",Offline:"Αποσυνδεμένος",Connecting:"Σύνδεση","Connection re-established":"Η σύνδεση αποκαταστάθηκε",Today:"Σήμερα",Send:"Αποστολή","Chat closed by %s":"Η συνομιλία έκλεισε από τον/την %s","Compose your message...":"Γράψτε το μήνυμα σας...","All colleagues are busy.":"Όλοι οι συνάδελφοι μας είναι απασχολημένοι.","You are on waiting list position %s.":"Βρίσκεστε σε λίστα αναμονής στη θέση %s.","Start new conversation":"Έναρξη νέας συνομιλίας","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας με τον/την %s έκλεισε.","Since you didn't respond in the last %s minutes your conversation got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας έκλεισε.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Λυπούμαστε που χρειάζεται περισσότερος χρόνος από τον αναμενόμενο για να βρεθεί μία κενή θέση. Παρακαλούμε δοκιμάστε ξανά αργότερα ή στείλτε μας ένα email. Ευχαριστούμε!"}},n.prototype.sessionId=void 0,n.prototype.scrolledToBottom=!0,n.prototype.scrollSnapTolerance=10,n.prototype.richTextFormatKey={66:!0,73:!0,85:!0,83:!0},n.prototype.T=function(){var e,t,n,s,o,i;if(o=arguments[0],t=2<=arguments.length?slice.call(arguments,1):[],this.options.lang&&"en"!==this.options.lang&&(this.translations[this.options.lang]?((i=this.translations[this.options.lang])[o]||this.log.notice("Translation needed for '"+o+"'"),o=i[o]||o):this.log.notice("Translation '"+this.options.lang+"' needed!")),t)for(n=0,s=t.length;n"+L.replace(/\n/g,"
        ")+"
        ").replace(/
        <\/div>/g,"

        ")),console.log("p",s,L),"html"===s){for(e=document.createElement("div"),A=DOMPurify.sanitize(L),this.log.debug("sanitized HTML clipboard",A),e.innerHTML=A,f=!1,o=L,z=new RegExp("<(/w|w):[A-Za-z]"),o.match(z)&&(f=!0,o=o.replace(z,"")),z=new RegExp("<(/o|o):[A-Za-z]"),o.match(z)&&(f=!0,o=o.replace(z,"")),f&&(e=this.wordFilter(e)),l=0,u=(S=e.childNodes).length;lnew Date((new Date).getTime()-1500)))return this.isTyping=new Date,this.send("chat_session_typing",{session_id:this.sessionId}),this.inactiveTimeout.start()},n.prototype.onSubmit=function(e){return e.preventDefault(),this.sendMessage()},n.prototype.sendMessage=function(){var e,t;if(e=this.input.innerHTML)return this.inactiveTimeout.start(),sessionStorage.removeItem("unfinished_message"),t=this.view("message")({message:e,from:"customer",id:this._messageCount++,unreadClass:""}),this.maybeAddTimestamp(),this.el.querySelector(".zammad-chat-message--typing")?(this.lastAddedType="typing-placeholder",this.el.querySelector(".zammad-chat-message--typing").insertAdjacentHTML("beforebegin",t)):(this.lastAddedType="message--customer",this.body.insertAdjacentHTML("beforeend",t)),this.input.innerHTML="",this.scrollToBottom(),this.send("chat_session_message",{content:e,id:this._messageCount,session_id:this.sessionId})},n.prototype.receiveMessage=function(e){return this.inactiveTimeout.start(),this.onAgentTypingEnd(),this.maybeAddTimestamp(),this.renderMessage({message:e.message.content,id:e.id,from:"agent"}),this.scrollToBottom({showHint:!0})},n.prototype.renderMessage=function(e){return this.lastAddedType="message--"+e.from,e.unreadClass=document.hidden?" zammad-chat-message--unread":"",this.body.insertAdjacentHTML("beforeend",this.view("message")(e))},n.prototype.open=function(){var e;if(!this.isOpen)return this.isOpen=!0,this.log.debug("open widget"),this.show(),this.sessionId||this.showLoader(),this.el.classList.add("zammad-chat-is-open"),e=this.el.clientHeight-this.el.querySelector(".zammad-chat-header").offsetHeight,this.el.style.transform="translateY("+e+"px)",this.el.clientHeight,this.sessionId?(this.el.style.transform="",this.onOpenAnimationEnd()):(this.el.addEventListener("transitionend",this.onOpenAnimationEnd),this.el.classList.add("zammad-chat--animate"),this.el.clientHeight,this.el.style.transform="",this.send("chat_session_init",{url:O.location.href}));this.log.debug("widget already open, block")},n.prototype.onOpenAnimationEnd=function(){var e;return this.el.removeEventListener("transitionend",this.onOpenAnimationEnd),this.el.classList.remove("zammad-chat--animate"),this.idleTimeout.stop(),this.isFullscreen&&this.disableScrollOnRoot(),"function"==typeof(e=this.options).onOpenAnimationEnd?e.onOpenAnimationEnd():void 0},n.prototype.sessionClose=function(){return this.send("chat_session_close",{session_id:this.sessionId}),this.inactiveTimeout.stop(),this.waitingListTimeout.stop(),sessionStorage.removeItem("unfinished_message"),this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.setSessionId(void 0)},n.prototype.toggle=function(e){return this.isOpen?this.close(e):this.open(e)},n.prototype.close=function(e){var t;if(this.isOpen)return this.initDelayId&&clearTimeout(this.initDelayId),this.sessionId&&(this.log.debug("session close before widget close"),this.sessionClose()),this.log.debug("close widget"),e&&e.stopPropagation(),this.isFullscreen&&this.enableScrollOnRoot(),t=this.el.clientHeight-this.el.querySelector(".zammad-chat-header").offsetHeight,this.el.addEventListener("transitionend",this.onCloseAnimationEnd),this.el.classList.add("zammad-chat--animate"),document.offsetHeight,this.el.style.transform="translateY("+t+"px)";this.log.debug("can't close widget, it's not open")},n.prototype.onCloseAnimationEnd=function(){var e;return this.el.removeEventListener("transitionend",this.onCloseAnimationEnd),this.el.classList.remove("zammad-chat-is-open","zammad-chat--animate"),this.el.style.transform="",this.showLoader(),this.el.querySelector(".zammad-chat-welcome").classList.remove("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent").classList.add("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent-status").classList.add("zammad-chat-is-hidden"),this.isOpen=!1,"function"==typeof(e=this.options).onCloseAnimationEnd&&e.onCloseAnimationEnd(),this.io.reconnect()},n.prototype.onWebSocketClose=function(){if(!this.isOpen)return this.el?(this.el.classList.remove("zammad-chat-is-shown"),this.el.classList.remove("zammad-chat-is-loaded")):void 0},n.prototype.show=function(){if("offline"!==this.state)return this.el.classList.add("zammad-chat-is-loaded"),this.el.classList.add("zammad-chat-is-shown")},n.prototype.disableInput=function(){return this.inputDisabled=!0,this.input.setAttribute("contenteditable",!1),this.el.querySelector(".zammad-chat-send").disabled=!0,this.io.close()},n.prototype.enableInput=function(){return this.inputDisabled=!1,this.input.setAttribute("contenteditable",!0),this.el.querySelector(".zammad-chat-send").disabled=!1},n.prototype.hideModal=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=""},n.prototype.onQueueScreen=function(e){var t,n;if(this.setSessionId(e.session_id),t=function(){return n.onQueue(e),n.waitingListTimeout.start()},!(n=this).initialQueueDelay||this.onInitialQueueDelayId)return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),t();this.onInitialQueueDelayId=setTimeout(t,this.initialQueueDelay)},n.prototype.onQueue=function(e){return this.log.notice("onQueue",e.position),this.inQueue=!0,this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("waiting")({position:e.position})},n.prototype.onAgentTypingStart=function(){if(this.stopTypingId&&clearTimeout(this.stopTypingId),this.stopTypingId=setTimeout(this.onAgentTypingEnd,3e3),!this.el.querySelector(".zammad-chat-message--typing")&&(this.maybeAddTimestamp(),this.body.insertAdjacentHTML("beforeend",this.view("typingIndicator")()),this.isVisible(this.el.querySelector(".zammad-chat-message--typing"),!0)))return this.scrollToBottom()},n.prototype.onAgentTypingEnd=function(){if(this.el.querySelector(".zammad-chat-message--typing"))return this.el.querySelector(".zammad-chat-message--typing").remove()},n.prototype.onLeaveTemporary=function(){if(this.sessionId)return this.send("chat_session_leave_temporary",{session_id:this.sessionId})},n.prototype.maybeAddTimestamp=function(){var e,t,n;if(n=Date.now(),!this.lastTimestamp||n-this.lastTimestamp>6e4*this.showTimeEveryXMinutes)return e=this.T("Today"),t=(new Date).toTimeString().substr(0,5),"timestamp"===this.lastAddedType?(this.updateLastTimestamp(e,t),this.lastTimestamp=n):(this.body.insertAdjacentHTML("beforeend",this.view("timestamp")({label:e,time:t})),this.lastTimestamp=n,this.lastAddedType="timestamp",this.scrollToBottom())},n.prototype.updateLastTimestamp=function(e,t){var n;if(this.el&&(n=this.el.querySelectorAll(".zammad-chat-body .zammad-chat-timestamp")))return n[n.length-1].outerHTML=this.view("timestamp")({label:e,time:t})},n.prototype.addStatus=function(e){if(this.el)return this.maybeAddTimestamp(),this.body.insertAdjacentHTML("beforeend",this.view("status")({status:e})),this.scrollToBottom()},n.prototype.detectScrolledtoBottom=function(){var e;if(e=this.body.scrollTop+this.body.offsetHeight,this.scrolledToBottom=Math.abs(e-this.body.scrollHeight)<=this.scrollSnapTolerance,this.scrolledToBottom)return this.el.querySelector(".zammad-scroll-hint").classList.add("is-hidden")},n.prototype.showScrollHint=function(){return this.el.querySelector(".zammad-scroll-hint").classList.remove("is-hidden"),this.body.scrollTop=this.body.scrollTop+this.el.querySelector(".zammad-scroll-hint").offsetHeight},n.prototype.onScrollHintClick=function(){return this.body.scrollTo({top:this.body.scrollHeight,behavior:"smooth"})},n.prototype.scrollToBottom=function(e){var t;return t=(null!=e?e:{showHint:!1}).showHint,this.scrolledToBottom?this.body.scrollTop=this.body.scrollHeight:t?this.showScrollHint():void 0},n.prototype.destroy=function(e){var t;return null==e&&(e={}),this.log.debug("destroy widget",e),this.setAgentOnlineState("offline"),e.remove&&this.el&&(this.el.remove(),(t=document.querySelector("."+this.options.buttonClass))&&(t.classList.add(this.options.inactiveClass),t.style.display="none")),this.waitingListTimeout&&this.waitingListTimeout.stop(),this.inactiveTimeout&&this.inactiveTimeout.stop(),this.idleTimeout&&this.idleTimeout.stop(),this.io.close()},n.prototype.reconnect=function(){return this.log.notice("reconnecting"),this.disableInput(),this.lastAddedType="status",this.setAgentOnlineState("connecting"),this.addStatus(this.T("Connection lost"))},n.prototype.onConnectionReestablished=function(){var e;return this.lastAddedType="status",this.setAgentOnlineState("online"),this.addStatus(this.T("Connection re-established")),"function"==typeof(e=this.options).onConnectionReestablished?e.onConnectionReestablished():void 0},n.prototype.onSessionClosed=function(e){var t;return this.addStatus(this.T("Chat closed by %s",e.realname)),this.disableInput(),this.setAgentOnlineState("offline"),this.inactiveTimeout.stop(),"function"==typeof(t=this.options).onSessionClosed?t.onSessionClosed(e):void 0},n.prototype.setSessionId=function(e){return void 0===(this.sessionId=e)?sessionStorage.removeItem("sessionId"):sessionStorage.setItem("sessionId",e)},n.prototype.onConnectionEstablished=function(e){var t;return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.inQueue=!1,e.agent&&(this.agent=e.agent),e.session_id&&this.setSessionId(e.session_id),this.body.innerHTML="",this.el.querySelector(".zammad-chat-agent").innerHTML=this.view("agent")({agent:this.agent}),this.enableInput(),this.hideModal(),this.el.querySelector(".zammad-chat-welcome").classList.add("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent").classList.remove("zammad-chat-is-hidden"),this.el.querySelector(".zammad-chat-agent-status").classList.remove("zammad-chat-is-hidden"),this.isFullscreen||this.input.focus(),this.setAgentOnlineState("online"),this.waitingListTimeout.stop(),this.idleTimeout.stop(),this.inactiveTimeout.start(),"function"==typeof(t=this.options).onConnectionEstablished?t.onConnectionEstablished(e):void 0},n.prototype.showCustomerTimeout=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("customer_timeout")({agent:this.agent.name,delay:this.options.inactiveTimeout}),this.el.querySelector(".js-restart").addEventListener("click",function(){return location.reload()}),this.sessionClose()},n.prototype.showWaitingListTimeout=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("waiting_list_timeout")({delay:this.options.watingListTimeout}),this.el.querySelector(".js-restart").addEventListener("click",function(){return location.reload()}),this.sessionClose()},n.prototype.showLoader=function(){return this.el.querySelector(".zammad-chat-modal").innerHTML=this.view("loader")()},n.prototype.setAgentOnlineState=function(e){var t;if(this.state=e,this.el)return t=e.charAt(0).toUpperCase()+e.slice(1),this.el.querySelector(".zammad-chat-agent-status").dataset.status=e,this.el.querySelector(".zammad-chat-agent-status").textContent=this.T(t)},n.prototype.detectHost=function(){var e;return e="ws://","https"===c&&(e="wss://"),this.options.host=""+e+l+"/ws"},n.prototype.loadCss=function(){var e,t,n;if(this.options.cssAutoload)return(n=this.options.cssUrl)||(n=this.options.host.replace(/^wss/i,"https").replace(/^ws/i,"http").replace(/\/ws$/i,""),n+="/assets/chat/chat.css"),this.log.debug("load css from '"+n+"'"),t="@import url('"+n+"');",(e=document.createElement("link")).onload=this.onCssLoaded,e.rel="stylesheet",e.href="data:text/css,"+escape(t),document.getElementsByTagName("head")[0].appendChild(e)},n.prototype.onCssLoaded=function(){var e;return this.cssLoaded=!0,this.socketReady&&this.onReady(),"function"==typeof(e=this.options).onCssLoaded?e.onCssLoaded():void 0},n.prototype.startTimeoutObservers=function(){var e,t,n;return this.idleTimeout=new a({logPrefix:"idleTimeout",debug:this.options.debug,timeout:this.options.idleTimeout,timeoutIntervallCheck:this.options.idleTimeoutIntervallCheck,callback:(e=this,function(){return e.log.debug("Idle timeout reached, hide widget",new Date),e.destroy({remove:!0})})}),this.inactiveTimeout=new a({logPrefix:"inactiveTimeout",debug:this.options.debug,timeout:this.options.inactiveTimeout,timeoutIntervallCheck:this.options.inactiveTimeoutIntervallCheck,callback:(t=this,function(){return t.log.debug("Inactive timeout reached, show timeout screen.",new Date),t.showCustomerTimeout(),t.destroy({remove:!1})})}),this.waitingListTimeout=new a({logPrefix:"waitingListTimeout",debug:this.options.debug,timeout:this.options.waitingListTimeout,timeoutIntervallCheck:this.options.waitingListTimeoutIntervallCheck,callback:(n=this,function(){return n.log.debug("Waiting list timeout reached, show timeout screen.",new Date),n.showWaitingListTimeout(),n.destroy({remove:!1})})})},n.prototype.disableScrollOnRoot=function(){return this.rootScrollOffset=this.scrollRoot.scrollTop,this.scrollRoot.style.overflow="hidden",this.scrollRoot.style.position="fixed"},n.prototype.enableScrollOnRoot=function(){return this.scrollRoot.scrollTop=this.rootScrollOffset,this.scrollRoot.style.overflow="",this.scrollRoot.style.position=""},n.prototype.isVisible=function(e,n,s,o){var i,a,r,l,c,d,u,h,m,p;if(!(e.length<1))return p=O.innerWidth,m=O.innerHeight,o=o||"both",a=!0!==s||t.offsetWidth*t.offsetHeight,u=0<=(d=e.getBoundingClientRect()).top&&d.top/gi,"")).replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,"")).replace(/<(\/?)s>/gi,"<$1strike>")).replace(/ /gi," "),e.innerHTML=t,i=0,c=(A=e.querySelectorAll("p")).length;i",/^\s*\w+\./.test(P)&&(y=(b=/([0-9])\./.exec(P))?null!=(O=1<(N=parseInt(b[1],10)))?O:'
          ':"
            "}:"
              "),l"+T.innerHTML+""),T.remove(),l=n}else l=0;for(v=0,u=(_=e.querySelectorAll("[style]")).length;v/g,">").replace(/"/g,""")}),function(){(function(){t.push('
              \n
              \n
              \n \n \n \n \n \n
              \n
              \n
              \n
              \n \n '),t.push(this.T(this.title)),t.push('\n
              \n
              \n
              \n \n
              \n
              \n
              \n \n
              \n
              ")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.customer_timeout=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
              \n '),this.agent?(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation with %s got closed.",this.delay,this.agent))):(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation got closed.",this.delay))),t.push("\n "),t.push('\n
              \n
              "),t.push(this.T("Start new conversation")),t.push("
              \n
              ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.loader=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('\n \n \n \n\n'),t.push(this.T("Connecting")),t.push("")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.message=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?o(e):""},s=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
              \n "),t.push(this.message),t.push("\n
              ")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.status=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
              \n
              \n '),t.push(this.status),t.push("\n
              \n
              ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.timestamp=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?o(e):""},s=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
              '),t.push(n(this.label)),t.push(" "),t.push(n(this.time)),t.push("
              ")}).call(this)}.call(e),e.safe=s,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.typingIndicator=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
              \n \n \n \n \n \n \n \n
              ')}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
              \n \n \n \n \n \n '),t.push(this.T("All colleagues are busy.")),t.push("
              \n "),t.push(this.T("You are on waiting list position %s.",this.position)),t.push("\n
              ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting_list_timeout=function(e){e||(e={});var t=[],n=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
              \n '),t.push(this.T("We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!")),t.push('\n
              \n
              "),t.push(this.T("Start new conversation")),t.push("
              \n
              ")}).call(this)}.call(e),e.safe=n,e.escape=s,t.join("")}; \ No newline at end of file diff --git a/public/assets/chat/chat.coffee b/public/assets/chat/chat.coffee index 574d17483..d8663b54f 100644 --- a/public/assets/chat/chat.coffee +++ b/public/assets/chat/chat.coffee @@ -1108,16 +1108,14 @@ do($ = window.jQuery, window) -> return if @initDelayId clearTimeout(@initDelayId) - if !@sessionId - @log.debug 'can\'t close widget without sessionId' - return + if @sessionId + @log.debug 'session close before widget close' + @sessionClose() @log.debug 'close widget' event.stopPropagation() if event - @sessionClose() - if @isFullscreen @enableScrollOnRoot() diff --git a/public/assets/chat/chat.js b/public/assets/chat/chat.js index 389694bcd..8b78fe848 100644 --- a/public/assets/chat/chat.js +++ b/public/assets/chat/chat.js @@ -1416,15 +1416,14 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); if (this.initDelayId) { clearTimeout(this.initDelayId); } - if (!this.sessionId) { - this.log.debug('can\'t close widget without sessionId'); - return; + if (this.sessionId) { + this.log.debug('session close before widget close'); + this.sessionClose(); } this.log.debug('close widget'); if (event) { event.stopPropagation(); } - this.sessionClose(); if (this.isFullscreen) { this.enableScrollOnRoot(); } diff --git a/public/assets/chat/chat.min.js b/public/assets/chat/chat.min.js index a38a39174..d0a7c4477 100644 --- a/public/assets/chat/chat.min.js +++ b/public/assets/chat/chat.min.js @@ -1 +1 @@ -window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.agent=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?s(e):""},o=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){this.agent.avatar&&(t.push('\n\n')),t.push('\n\n '),t.push(n(this.agent.name)),t.push("\n")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,function(){"use strict";var o=Object.hasOwnProperty,i=Object.setPrototypeOf,a=Object.isFrozen,s=Object.getPrototypeOf,r=Object.getOwnPropertyDescriptor,Le=Object.freeze,e=Object.seal,l=Object.create,t="undefined"!=typeof Reflect&&Reflect,d=t.apply,c=t.construct;d||(d=function(e,t,n){return e.apply(t,n)}),Le||(Le=function(e){return e}),e||(e=function(e){return e}),c||(c=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/gm),rt=e(/^data-[\-\w.\u00B7-\uFFFF]/),lt=e(/^aria-[\-\w]+$/),dt=e(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ct=e(/^(?:\w+script|data):/i),ut=e(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ht="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function mt(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/i,n))ze(l,e);else{V&&(n=He(n,D," "),n=He(n,R," "));var c=e.nodeName.toLowerCase();if(_e(c,o,n))try{d?e.setAttributeNS(d,l,n):e.setAttribute(l,n),je(u.removed)}catch(e){}}}Ie("afterSanitizeAttributes",e,null)}},Re=function e(t){var n=void 0,o=xe(t);for(Ie("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)Ie("uponSanitizeShadowNode",n,null),Ee(n)||(n.content instanceof h&&e(n.content),De(n));Ie("afterSanitizeShadowDOM",t,null)};return u.sanitize=function(e,t){var n=void 0,o=void 0,s=void 0,i=void 0,a=void 0;if((fe=!e)&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Oe(e)){if("function"!=typeof e.toString)throw qe("toString is not a function");if("string"!=typeof(e=e.toString()))throw qe("dirty is not a string, aborting")}if(!u.isSupported){if("object"===ht(d.toStaticHTML)||"function"==typeof d.toStaticHTML){if("string"==typeof e)return d.toStaticHTML(e);if(Oe(e))return d.toStaticHTML(e.outerHTML)}return e}if(Z||be(t),u.removed=[],"string"==typeof e&&(se=!1),se);else if(e instanceof m)1===(o=(n=Ae("\x3c!----\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===o.nodeName||"HTML"===o.nodeName?n=o:n.appendChild(o);else{if(!$&&!V&&!G&&-1===e.indexOf("<"))return C&&te?C.createHTML(e):e;if(!(n=Ae(e)))return $?null:S}n&&X&&ke(n.firstChild);for(var r=xe(se?e:n);s=r.nextNode();)3===s.nodeType&&s===i||Ee(s)||(s.content instanceof h&&Re(s.content),De(s),i=s);if(i=null,se)return e;if($){if(J)for(a=x.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ee&&(a=I.call(c,a,!0)),a}var l=G?n.outerHTML:n.innerHTML;return V&&(l=He(l,D," "),l=He(l,R," ")),C&&te?C.createHTML(l):l},u.setConfig=function(e){be(e),Z=!0},u.clearConfig=function(){ye=null,Z=!1},u.isValidAttribute=function(e,t,n){ye||be({});var o=Pe(e),s=Pe(t);return _e(o,s,n)},u.addHook=function(e,t){"function"==typeof t&&(_[e]=_[e]||[],Ne(_[e],t))},u.removeHook=function(e){_[e]&&je(_[e])},u.removeHooks=function(e){_[e]&&(_[e]=[])},u.removeAllHooks=function(){_={}},u}()});var bind=function(e,t){return function(){return e.apply(t,arguments)}},slice=[].slice,extend=function(e,t){for(var n in t)hasProp.call(t,n)&&(e[n]=t[n]);function o(){this.constructor=e}return o.prototype=t.prototype,e.prototype=new o,e.__super__=t.prototype,e},hasProp={}.hasOwnProperty;!function(E,_){var n,o,t,s,e,i,a,r,l;i=(l=document.getElementsByTagName("script"))[l.length-1],r=_.location.protocol.replace(":",""),i&&i.src&&(a=i.src.match(".*://([^:/]*).*")[1],r=i.src.match("(.*)://[^:/]*.*")[1]),n=function(){function e(e){this.options=E.extend({},this.defaults,e),this.log=new t({debug:this.options.debug,logPrefix:this.options.logPrefix||this.logPrefix})}return e.prototype.defaults={debug:!1},e}(),t=function(){function e(e){this.log=bind(this.log,this),this.error=bind(this.error,this),this.notice=bind(this.notice,this),this.debug=bind(this.debug,this),this.options=E.extend({},this.defaults,e)}return e.prototype.defaults={debug:!1},e.prototype.debug=function(){var e;if(e=1<=arguments.length?slice.call(arguments,0):[],this.options.debug)return this.log("debug",e)},e.prototype.notice=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("notice",e)},e.prototype.error=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("error",e)},e.prototype.log=function(e,t){var n,o,s,i;if(t.unshift("||"),t.unshift(e),t.unshift(this.options.logPrefix),console.log.apply(console,t),this.options.debug){for(i="",o=0,s=t.length;o"+i+"
              ")}},e}(),s=function(e){function t(e){this.stop=bind(this.stop,this),this.start=bind(this.start,this),t.__super__.constructor.call(this,e)}return extend(t,n),t.prototype.timeoutStartedAt=null,t.prototype.logPrefix="timeout",t.prototype.defaults={debug:!1,timeout:4,timeoutIntervallCheck:.5},t.prototype.start=function(){var e,t,n;return this.stop(),t=new Date,e=function(){var e;if(e=new Date-new Date(t.getTime()+1e3*n.options.timeout*60),n.log.debug("Timeout check for "+n.options.timeout+" minutes (left "+e/1e3+" sec.)"),!(e<0))return n.stop(),n.options.callback()},(n=this).log.debug("Start timeout in "+this.options.timeout+" minutes"),this.intervallId=setInterval(e,1e3*this.options.timeoutIntervallCheck*60)},t.prototype.stop=function(){if(this.intervallId)return this.log.debug("Stop timeout of "+this.options.timeout+" minutes"),clearInterval(this.intervallId)},t}(),o=function(e){function t(e){this.ping=bind(this.ping,this),this.send=bind(this.send,this),this.reconnect=bind(this.reconnect,this),this.close=bind(this.close,this),this.connect=bind(this.connect,this),this.set=bind(this.set,this),t.__super__.constructor.call(this,e)}return extend(t,n),t.prototype.logPrefix="io",t.prototype.set=function(e){var t,n,o;for(t in n=[],e)o=e[t],n.push(this.options[t]=o);return n},t.prototype.connect=function(){var t,s,n,o;return this.log.debug("Connecting to "+this.options.host),this.ws=new _.WebSocket(""+this.options.host),this.ws.onopen=(t=this,function(e){return t.log.debug("onOpen",e),t.options.onOpen(e),t.ping()}),this.ws.onmessage=(s=this,function(e){var t,n,o;for(o=JSON.parse(e.data),s.log.debug("onMessage",e.data),t=0,n=o.length;tChat with us!",scrollHint:"Scroll down to see new messages",idleTimeout:6,idleTimeoutIntervallCheck:.5,inactiveTimeout:8,inactiveTimeoutIntervallCheck:.5,waitingListTimeout:4,waitingListTimeoutIntervallCheck:.5,onReady:void 0,onCloseAnimationEnd:void 0,onError:void 0,onOpenAnimationEnd:void 0,onConnectionReestablished:void 0,onSessionClosed:void 0,onConnectionEstablished:void 0,onCssLoaded:void 0},t.prototype.logPrefix="chat",t.prototype._messageCount=0,t.prototype.isOpen=!1,t.prototype.blinkOnlineInterval=null,t.prototype.stopBlinOnlineStateTimeout=null,t.prototype.showTimeEveryXMinutes=2,t.prototype.lastTimestamp=null,t.prototype.lastAddedType=null,t.prototype.inputDisabled=!1,t.prototype.inputTimeout=null,t.prototype.isTyping=!1,t.prototype.state="offline",t.prototype.initialQueueDelay=1e4,t.prototype.translations={da:{"Chat with us!":"Chat med os!","Scroll down to see new messages":"Scroll ned for at se nye beskeder",Online:"Online",Offline:"Offline",Connecting:"Forbinder","Connection re-established":"Forbindelse genoprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat lukket af %s","Compose your message...":"Skriv en besked...","All colleagues are busy.":"Alle kollegaer er optaget.","You are on waiting list position %s.":"Du er i venteliste som nummer %s.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale med %s blevet lukket.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale blevet lukket.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, det tager længere end forventet at få en ledig plads. Prøv venligst igen senere eller send os en e-mail. På forhånd tak!"},de:{"Chat with us!":"Chatte mit uns!","Scroll down to see new messages":"Scrolle nach unten um neue Nachrichten zu sehen",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbindung wiederhergestellt",Today:"Heute",Send:"Senden","Chat closed by %s":"Chat beendet von %s","Compose your message...":"Ihre Nachricht...","All colleagues are busy.":"Alle Kollegen sind belegt.","You are on waiting list position %s.":"Sie sind in der Warteliste an der Position %s.","Start new conversation":"Neue Konversation starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit %s geschlossen.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Es tut uns leid, es dauert länger als erwartet, um einen freien Platz zu erhalten. Bitte versuchen Sie es zu einem späteren Zeitpunkt noch einmal oder schicken Sie uns eine E-Mail. Vielen Dank!"},es:{"Chat with us!":"Chatee con nosotros!","Scroll down to see new messages":"Haga scroll hacia abajo para ver nuevos mensajes",Online:"En linea",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexión restablecida",Today:"Hoy",Send:"Enviar","Chat closed by %s":"Chat cerrado por %s","Compose your message...":"Escriba su mensaje...","All colleagues are busy.":"Todos los agentes están ocupados.","You are on waiting list position %s.":"Usted está en la posición %s de la lista de espera.","Start new conversation":"Iniciar nueva conversación","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación con %s se ha cerrado.","Since you didn't respond in the last %s minutes your conversation got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación se ha cerrado.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Lo sentimos, se tarda más tiempo de lo esperado para ser atendido por un agente. Inténtelo de nuevo más tarde o envíenos un correo electrónico. ¡Gracias!"},fi:{"Chat with us!":"Keskustele kanssamme!","Scroll down to see new messages":"Rullaa alas nähdäksesi uudet viestit",Online:"Paikalla",Offline:"Poissa",Connecting:"Yhdistetään","Connection re-established":"Yhteys muodostettu uudelleen",Today:"Tänään",Send:"Lähetä","Chat closed by %s":"%s sulki keskustelun","Compose your message...":"Luo viestisi...","All colleagues are busy.":"Kaikki kollegat ovat varattuja.","You are on waiting list position %s.":"Olet odotuslistalla sijalla %s.","Start new conversation":"Aloita uusi keskustelu","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi %s kanssa suljettiin.","Since you didn't respond in the last %s minutes your conversation got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi suljettiin.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Olemme pahoillamme, tyhjän paikan vapautumisessa kestää odotettua pidempään. Ole hyvä ja yritä myöhemmin uudestaan tai lähetä meille sähköpostia. Kiitos!"},fr:{"Chat with us!":"Chattez avec nous!","Scroll down to see new messages":"Faites défiler pour lire les nouveaux messages",Online:"En-ligne",Offline:"Hors-ligne",Connecting:"Connexion en cours","Connection re-established":"Connexion rétablie",Today:"Aujourdhui",Send:"Envoyer","Chat closed by %s":"Chat fermé par %s","Compose your message...":"Composez votre message...","All colleagues are busy.":"Tous les collaborateurs sont occupés actuellement.","You are on waiting list position %s.":"Vous êtes actuellement en position %s dans la file d'attente.","Start new conversation":"Démarrer une nouvelle conversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation avec %s sera fermée.","Since you didn't respond in the last %s minutes your conversation got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation va être fermée.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Nous sommes désolés, il faut plus de temps que prévu pour obtenir un emplacement vide. Veuillez réessayer ultérieurement ou nous envoyer un courriel. Nous vous remercions!"},he:{"Chat with us!":"שוחחאיתנו!","Scroll down to see new messages":"גלול מטה כדי לראות הודעות חדשות",Online:"מחובר",Offline:"מנותק",Connecting:"מתחבר","Connection re-established":"החיבור שוחזר",Today:"היום",Send:"שלח","Chat closed by %s":'הצאט נסגר ע"י %s',"Compose your message...":"כתוב את ההודעה שלך ...","All colleagues are busy.":"כל הנציגים תפוסים","You are on waiting list position %s.":"מיקומך בתור %s.","Start new conversation":"התחל שיחה חדשה","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"מכיוון שלא הגבת במהלך %s דקות השיחה שלך עם %s נסגרה.","Since you didn't respond in the last %s minutes your conversation got closed.":"מכיוון שלא הגבת במהלך %s הדקות האחרונות השיחה שלך נסגרה.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":'מצטערים, הזמן לקבלת נציג ארוך מהרגיל. נסה שוב מאוחר יותר או שלח לנו דוא"ל. תודה!'},hu:{"Chat with us!":"Chatelj velünk!","Scroll down to see new messages":"Görgess lejjebb az újabb üzenetekért",Online:"Online",Offline:"Offline",Connecting:"Csatlakozás","Connection re-established":"Újracsatlakozás",Today:"Ma",Send:"Küldés","Chat closed by %s":"A beszélgetést lezárta %s","Compose your message...":"Írj üzenetet...","All colleagues are busy.":"Jelenleg minden kollégánk elfoglalt.","You are on waiting list position %s.":"A várólistán a %s. pozícióban várakozol.","Start new conversation":"Új beszélgetés indítása","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Mivel %s perce nem érkezett újabb üzenet, ezért a %s kollégával folytatott beszéletést lezártuk.","Since you didn't respond in the last %s minutes your conversation got closed.":"Mivel %s perce nem érkezett válasz, a beszélgetés lezárult.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Sajnáljuk, de a várakozási idő hosszabb a szokásosnál. Kérlek próbáld újra, vagy írd meg kérdésed emailben. Köszönjük!"},nl:{"Chat with us!":"Chat met ons!","Scroll down to see new messages":"Scrol naar beneden om nieuwe berichten te zien",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbinding herstelt",Today:"Vandaag",Send:"Verzenden","Chat closed by %s":"Chat gesloten door %s","Compose your message...":"Typ uw bericht...","All colleagues are busy.":"Alle medewerkers zijn bezet.","You are on waiting list position %s.":"U bent %s in de wachtrij.","Start new conversation":"Nieuwe conversatie starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft wordt de conversatie met %s gesloten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft is de conversatie gesloten.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Het spijt ons, het duurt langer dan verwacht om te antwoorden. Alstublieft probeer het later nogmaals of stuur ons een email. Hartelijk dank!"},it:{"Chat with us!":"Chatta con noi!","Scroll down to see new messages":"Scorri verso il basso per vedere i nuovi messaggi",Online:"Online",Offline:"Offline",Connecting:"Collegamento in corso","Connection re-established":"Collegamento ristabilito",Today:"Oggi",Send:"Invio","Chat closed by %s":"Chat chiusa da %s","Compose your message...":"Componi il tuo messaggio...","All colleagues are busy.":"Tutti gli operatori sono occupati.","You are on waiting list position %s.":"Sei in posizione %s nella lista d'attesa.","Start new conversation":"Avvia una nuova chat","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua chat con %s è stata chiusa.","Since you didn't respond in the last %s minutes your conversation got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua chat è stata chiusa.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Ci dispiace, ci vuole più tempo del previsto per arrivare al tuo turno. Per favore riprova più tardi o inviaci un'email. Grazie!"},pl:{"Chat with us!":"Czatuj z nami!","Scroll down to see new messages":"Przewiń w dół, aby wyświetlić nowe wiadomości",Online:"Online",Offline:"Offline",Connecting:"Łączenie","Connection re-established":"Ponowne nawiązanie połączenia",Today:"dzisiejszy",Send:"Wyślij","Chat closed by %s":"Czat zamknięty przez %s","Compose your message...":"Utwórz swoją wiadomość...","All colleagues are busy.":"Wszyscy koledzy są zajęci.","You are on waiting list position %s.":"Na liście oczekujących znajduje się pozycja %s.","Start new conversation":"Rozpoczęcie nowej konwersacji","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ponieważ w ciągu ostatnich %s minut nie odpowiedziałeś, Twoja rozmowa z %s została zamknięta.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ponieważ nie odpowiedziałeś w ciągu ostatnich %s minut, Twoja rozmowa została zamknięta.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Przykro nam, ale to trwa dłużej niż się spodziewamy. Spróbuj ponownie później lub wyślij nam wiadomość e-mail. Dziękuję!"},"pt-br":{"Chat with us!":"Chat fale conosco!","Scroll down to see new messages":"Role para baixo, para ver nosvas mensagens",Online:"Online",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexão restabelecida",Today:"Hoje",Send:"Enviar","Chat closed by %s":"Chat encerrado por %s","Compose your message...":"Escreva sua mensagem...","All colleagues are busy.":"Todos os agentes estão ocupados.","You are on waiting list position %s.":"Você está na posição %s na fila de espera.","Start new conversation":"Iniciar uma nova conversa","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Como você não respondeu nos últimos %s minutos sua conversa com %s foi encerrada.","Since you didn't respond in the last %s minutes your conversation got closed.":"Como você não respondeu nos últimos %s minutos sua conversa foi encerrada.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Desculpe, mas o tempo de espera por um agente foi excedido. Tente novamente mais tarde ou nós envie um email. Obrigado"},"zh-cn":{"Chat with us!":"发起即时对话!","Scroll down to see new messages":"向下滚动以查看新消息",Online:"在线",Offline:"离线",Connecting:"连接中","Connection re-established":"正在重新建立连接",Today:"今天",Send:"发送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在输入信息...","All colleagues are busy.":"所有工作人员都在忙碌中.","You are on waiting list position %s.":"您目前的等候位置是第 %s 位.","Start new conversation":"开始新的会话","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由于您超过 %s 分钟没有回复, 您与 %s 的会话已被关闭.","Since you didn't respond in the last %s minutes your conversation got closed.":"由于您超过 %s 分钟没有任何回复, 该对话已被关闭.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 目前需要等候更长的时间才能接入对话, 请稍后重试或向我们发送电子邮件. 谢谢!"},"zh-tw":{"Chat with us!":"開始即時對话!","Scroll down to see new messages":"向下滑動以查看新訊息",Online:"線上",Offline:"离线",Connecting:"連線中","Connection re-established":"正在重新建立連線中",Today:"今天",Send:"發送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在輸入訊息...","All colleagues are busy.":"所有服務人員都在忙碌中.","You are on waiting list position %s.":"你目前的等候位置是第 %s 順位.","Start new conversation":"開始新的對話","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由於你超過 %s 分鐘沒有回應, 你與 %s 的對話已被關閉.","Since you didn't respond in the last %s minutes your conversation got closed.":"由於你超過 %s 分鐘沒有任何回應, 該對話已被關閉.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 當前需要等候更長的時間方可排入對話程序, 請稍後重試或向我們寄送電子郵件. 謝謝!"},ru:{"Chat with us!":"Напишите нам!","Scroll down to see new messages":"Прокрутите, чтобы увидеть новые сообщения",Online:"Онлайн",Offline:"Оффлайн",Connecting:"Подключение","Connection re-established":"Подключение восстановлено",Today:"Сегодня",Send:"Отправить","Chat closed by %s":"%s закрыл чат","Compose your message...":"Напишите сообщение...","All colleagues are busy.":"Все сотрудники заняты","You are on waiting list position %s.":"Вы в списке ожидания под номером %s","Start new conversation":"Начать новую переписку.","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор с %s был закрыт.","Since you didn't respond in the last %s minutes your conversation got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор был закрыт.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"К сожалению, ожидание свободного места требует больше времени. Повторите попытку позже или отправьте нам электронное письмо. Спасибо!"},sv:{"Chat with us!":"Chatta med oss!","Scroll down to see new messages":"Rulla ner för att se nya meddelanden",Online:"Online",Offline:"Offline",Connecting:"Ansluter","Connection re-established":"Anslutningen återupprättas",Today:"I dag",Send:"Skicka","Chat closed by %s":"Chatt stängd av %s","Compose your message...":"Skriv ditt meddelande...","All colleagues are busy.":"Alla kollegor är upptagna.","You are on waiting list position %s.":"Du är på väntelistan som position %s.","Start new conversation":"Starta ny konversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Eftersom du inte svarat inom %s minuterna i din konversation med %s så stängdes chatten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Då du inte svarat inom de senaste %s minuterna så avslutades din chatt.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi är ledsna, det tar längre tid som förväntat att få en ledig plats. Försök igen senare eller skicka ett e-postmeddelande till oss. Tack!"},no:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},nb:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},el:{"Chat with us!":"Επικοινωνήστε μαζί μας!","Scroll down to see new messages":"Μεταβείτε κάτω για να δείτε τα νέα μηνύματα",Online:"Σε σύνδεση",Offline:"Αποσυνδεμένος",Connecting:"Σύνδεση","Connection re-established":"Η σύνδεση αποκαταστάθηκε",Today:"Σήμερα",Send:"Αποστολή","Chat closed by %s":"Η συνομιλία έκλεισε από τον/την %s","Compose your message...":"Γράψτε το μήνυμα σας...","All colleagues are busy.":"Όλοι οι συνάδελφοι μας είναι απασχολημένοι.","You are on waiting list position %s.":"Βρίσκεστε σε λίστα αναμονής στη θέση %s.","Start new conversation":"Έναρξη νέας συνομιλίας","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας με τον/την %s έκλεισε.","Since you didn't respond in the last %s minutes your conversation got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας έκλεισε.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Λυπούμαστε που χρειάζεται περισσότερος χρόνος από τον αναμενόμενο για να βρεθεί μία κενή θέση. Παρακαλούμε δοκιμάστε ξανά αργότερα ή στείλτε μας ένα email. Ευχαριστούμε!"}},t.prototype.sessionId=void 0,t.prototype.scrolledToBottom=!0,t.prototype.scrollSnapTolerance=10,t.prototype.richTextFormatKey={66:!0,73:!0,85:!0,83:!0},t.prototype.T=function(){var e,t,n,o,s,i;if(s=arguments[0],t=2<=arguments.length?slice.call(arguments,1):[],this.options.lang&&"en"!==this.options.lang&&(this.translations[this.options.lang]?((i=this.translations[this.options.lang])[s]||this.log.notice("Translation needed for '"+s+"'"),s=i[s]||s):this.log.notice("Translation '"+this.options.lang+"' needed!")),t)for(n=0,o=t.length;n',document.execCommand("insertHTML",!1,s)},m.resizeImage(s.src,460,"auto",2,"image/jpeg","auto",t)},d.readAsDataURL(i),a=!0)),!a){o=h=void 0;try{h=n.getData("text/html"),o="html",h&&0!==h.length||(o="text",h=n.getData("text/plain")),h&&0!==h.length||(o="text2",h=n.getData("text"))}catch(e){t=e,console.log("Sorry, can't insert markup because browser is not supporting it."),o="text3",h=n.getData("text")}return"text"!==o&&"text2"!==o&&"text3"!==o||(h=(h="
              "+h.replace(/\n/g,"
              ")+"
              ").replace(/
              <\/div>/g,"

              ")),console.log("p",o,h),"html"===o&&(u=DOMPurify.sanitize(h),m.log.debug("sanitized HTML clipboard",u),e=E("
              "+u+"
              "),l=!1,s=h,c=new RegExp("<(/w|w):[A-Za-z]"),s.match(c)&&(l=!0,s=s.replace(c,"")),c=new RegExp("<(/o|o):[A-Za-z]"),s.match(c)&&(l=!0,s=s.replace(c,"")),l&&(e=m.wordFilter(e)),(e=E(e)).contents().each(function(){if(8===this.nodeType)return E(this).remove()}),e.find("a, font, small, time, form, label").replaceWith(function(){return E(this).contents()}),e.find("textarea").each(function(){var e,t;return t=this.outerHTML,c=new RegExp("<"+this.tagName,"i"),e=t.replace(c,"')).get(0),document.caretPositionFromPoint?(s=document.caretPositionFromPoint(l,d),(i=document.createRange()).setStart(s.offsetNode,s.offset),i.collapse(),i.insertNode(a)):document.caretRangeFromPoint?(i=document.caretRangeFromPoint(l,d)).insertNode(a):console.log("could not find carat")},c.resizeImage(a.src,460,"auto",2,"image/jpeg","auto",t)},o.readAsDataURL(n)})),E(_).on("beforeunload",(e=this,function(){return e.onLeaveTemporary()})),E(_).bind("hashchange",(t=this,function(){if(!t.isOpen)return t.idleTimeout.start();t.sessionId&&t.send("chat_session_notice",{session_id:t.sessionId,message:_.location.href})})),this.isFullscreen)return this.input.on({focus:this.onFocus,focusout:this.onFocusOut})},t.prototype.stopPropagation=function(e){return e.stopPropagation()},t.prototype.checkForEnter=function(e){if(!this.inputDisabled&&!e.shiftKey&&13===e.keyCode)return e.preventDefault(),this.sendMessage()},t.prototype.send=function(e,t){return null==t&&(t={}),t.chat_id=this.options.chatId,this.io.send(e,t)},t.prototype.onWebSocketMessage=function(e){var t,n,o;for(t=0,n=e.length;tnew Date((new Date).getTime()-1500)))return this.isTyping=new Date,this.send("chat_session_typing",{session_id:this.sessionId}),this.inactiveTimeout.start()},t.prototype.onSubmit=function(e){return e.preventDefault(),this.sendMessage()},t.prototype.sendMessage=function(){var e,t;if(e=this.input.html())return this.inactiveTimeout.start(),sessionStorage.removeItem("unfinished_message"),t=this.view("message")({message:e,from:"customer",id:this._messageCount++,unreadClass:""}),this.maybeAddTimestamp(),this.el.find(".zammad-chat-message--typing").get(0)?(this.lastAddedType="typing-placeholder",this.el.find(".zammad-chat-message--typing").before(t)):(this.lastAddedType="message--customer",this.el.find(".zammad-chat-body").append(t)),this.input.html(""),this.scrollToBottom(),this.send("chat_session_message",{content:e,id:this._messageCount,session_id:this.sessionId})},t.prototype.receiveMessage=function(e){return this.inactiveTimeout.start(),this.onAgentTypingEnd(),this.maybeAddTimestamp(),this.renderMessage({message:e.message.content,id:e.id,from:"agent"}),this.scrollToBottom({showHint:!0})},t.prototype.renderMessage=function(e){return this.lastAddedType="message--"+e.from,e.unreadClass=document.hidden?" zammad-chat-message--unread":"",this.el.find(".zammad-chat-body").append(this.view("message")(e))},t.prototype.open=function(){var e;if(!this.isOpen)return this.isOpen=!0,this.log.debug("open widget"),this.show(),this.sessionId||this.showLoader(),this.el.addClass("zammad-chat-is-open"),e=this.el.height()-this.el.find(".zammad-chat-header").outerHeight(),this.el.css("bottom",-e),this.sessionId?(this.el.css("bottom",0),this.onOpenAnimationEnd()):(this.el.animate({bottom:0},500,this.onOpenAnimationEnd),this.send("chat_session_init",{url:_.location.href}));this.log.debug("widget already open, block")},t.prototype.onOpenAnimationEnd=function(){var e;return this.idleTimeout.stop(),this.isFullscreen&&this.disableScrollOnRoot(),"function"==typeof(e=this.options).onOpenAnimationEnd?e.onOpenAnimationEnd():void 0},t.prototype.sessionClose=function(){return this.send("chat_session_close",{session_id:this.sessionId}),this.inactiveTimeout.stop(),this.waitingListTimeout.stop(),sessionStorage.removeItem("unfinished_message"),this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.setSessionId(void 0)},t.prototype.toggle=function(e){return this.isOpen?this.close(e):this.open(e)},t.prototype.close=function(e){var t;if(this.isOpen){if(this.initDelayId&&clearTimeout(this.initDelayId),this.sessionId)return this.log.debug("close widget"),e&&e.stopPropagation(),this.sessionClose(),this.isFullscreen&&this.enableScrollOnRoot(),t=this.el.height()-this.el.find(".zammad-chat-header").outerHeight(),this.el.animate({bottom:-t},500,this.onCloseAnimationEnd);this.log.debug("can't close widget without sessionId")}else this.log.debug("can't close widget, it's not open")},t.prototype.onCloseAnimationEnd=function(){var e;return this.el.css("bottom",""),this.el.removeClass("zammad-chat-is-open"),this.showLoader(),this.el.find(".zammad-chat-welcome").removeClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent").addClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent-status").addClass("zammad-chat-is-hidden"),this.isOpen=!1,"function"==typeof(e=this.options).onCloseAnimationEnd&&e.onCloseAnimationEnd(),this.io.reconnect()},t.prototype.onWebSocketClose=function(){if(!this.isOpen)return this.el?(this.el.removeClass("zammad-chat-is-shown"),this.el.removeClass("zammad-chat-is-loaded")):void 0},t.prototype.show=function(){if("offline"!==this.state)return this.el.addClass("zammad-chat-is-loaded"),this.el.addClass("zammad-chat-is-shown")},t.prototype.disableInput=function(){return this.inputDisabled=!0,this.input.prop("contenteditable",!1),this.el.find(".zammad-chat-send").prop("disabled",!0),this.io.close()},t.prototype.enableInput=function(){return this.inputDisabled=!1,this.input.prop("contenteditable",!0),this.el.find(".zammad-chat-send").prop("disabled",!1)},t.prototype.hideModal=function(){return this.el.find(".zammad-chat-modal").html("")},t.prototype.onQueueScreen=function(e){var t,n;if(this.setSessionId(e.session_id),t=function(){return n.onQueue(e),n.waitingListTimeout.start()},!(n=this).initialQueueDelay||this.onInitialQueueDelayId)return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),t();this.onInitialQueueDelayId=setTimeout(t,this.initialQueueDelay)},t.prototype.onQueue=function(e){return this.log.notice("onQueue",e.position),this.inQueue=!0,this.el.find(".zammad-chat-modal").html(this.view("waiting")({position:e.position}))},t.prototype.onAgentTypingStart=function(){if(this.stopTypingId&&clearTimeout(this.stopTypingId),this.stopTypingId=setTimeout(this.onAgentTypingEnd,3e3),!this.el.find(".zammad-chat-message--typing").get(0)&&(this.maybeAddTimestamp(),this.el.find(".zammad-chat-body").append(this.view("typingIndicator")()),this.isVisible(this.el.find(".zammad-chat-message--typing"),!0)))return this.scrollToBottom()},t.prototype.onAgentTypingEnd=function(){return this.el.find(".zammad-chat-message--typing").remove()},t.prototype.onLeaveTemporary=function(){if(this.sessionId)return this.send("chat_session_leave_temporary",{session_id:this.sessionId})},t.prototype.maybeAddTimestamp=function(){var e,t,n;if(n=Date.now(),!this.lastTimestamp||n-this.lastTimestamp>6e4*this.showTimeEveryXMinutes)return e=this.T("Today"),t=(new Date).toTimeString().substr(0,5),"timestamp"===this.lastAddedType?(this.updateLastTimestamp(e,t),this.lastTimestamp=n):(this.el.find(".zammad-chat-body").append(this.view("timestamp")({label:e,time:t})),this.lastTimestamp=n,this.lastAddedType="timestamp",this.scrollToBottom())},t.prototype.updateLastTimestamp=function(e,t){if(this.el)return this.el.find(".zammad-chat-body").find(".zammad-chat-timestamp").last().replaceWith(this.view("timestamp")({label:e,time:t}))},t.prototype.addStatus=function(e){if(this.el)return this.maybeAddTimestamp(),this.el.find(".zammad-chat-body").append(this.view("status")({status:e})),this.scrollToBottom()},t.prototype.detectScrolledtoBottom=function(){var e;if(e=this.el.find(".zammad-chat-body").scrollTop()+this.el.find(".zammad-chat-body").outerHeight(),this.scrolledToBottom=Math.abs(e-this.el.find(".zammad-chat-body").prop("scrollHeight"))<=this.scrollSnapTolerance,this.scrolledToBottom)return this.el.find(".zammad-scroll-hint").addClass("is-hidden")},t.prototype.showScrollHint=function(){return this.el.find(".zammad-scroll-hint").removeClass("is-hidden"),this.el.find(".zammad-chat-body").scrollTop(this.el.find(".zammad-chat-body").scrollTop()+this.el.find(".zammad-scroll-hint").outerHeight())},t.prototype.onScrollHintClick=function(){return this.el.find(".zammad-chat-body").animate({scrollTop:this.el.find(".zammad-chat-body").prop("scrollHeight")},300)},t.prototype.scrollToBottom=function(e){var t;return t=(null!=e?e:{showHint:!1}).showHint,this.scrolledToBottom?this.el.find(".zammad-chat-body").scrollTop(E(".zammad-chat-body").prop("scrollHeight")):t?this.showScrollHint():void 0},t.prototype.destroy=function(e){return null==e&&(e={}),this.log.debug("destroy widget",e),this.setAgentOnlineState("offline"),e.remove&&this.el&&(this.el.remove(),E("."+this.options.buttonClass).hide()),this.waitingListTimeout&&this.waitingListTimeout.stop(),this.inactiveTimeout&&this.inactiveTimeout.stop(),this.idleTimeout&&this.idleTimeout.stop(),this.io.close()},t.prototype.reconnect=function(){return this.log.notice("reconnecting"),this.disableInput(),this.lastAddedType="status",this.setAgentOnlineState("connecting"),this.addStatus(this.T("Connection lost"))},t.prototype.onConnectionReestablished=function(){var e;return this.lastAddedType="status",this.setAgentOnlineState("online"),this.addStatus(this.T("Connection re-established")),"function"==typeof(e=this.options).onConnectionReestablished?e.onConnectionReestablished():void 0},t.prototype.onSessionClosed=function(e){var t;return this.addStatus(this.T("Chat closed by %s",e.realname)),this.disableInput(),this.setAgentOnlineState("offline"),this.inactiveTimeout.stop(),"function"==typeof(t=this.options).onSessionClosed?t.onSessionClosed(e):void 0},t.prototype.setSessionId=function(e){return void 0===(this.sessionId=e)?sessionStorage.removeItem("sessionId"):sessionStorage.setItem("sessionId",e)},t.prototype.onConnectionEstablished=function(e){var t;return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.inQueue=!1,e.agent&&(this.agent=e.agent),e.session_id&&this.setSessionId(e.session_id),this.el.find(".zammad-chat-body").html(""),this.el.find(".zammad-chat-agent").html(this.view("agent")({agent:this.agent})),this.enableInput(),this.hideModal(),this.el.find(".zammad-chat-welcome").addClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent").removeClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent-status").removeClass("zammad-chat-is-hidden"),this.isFullscreen||this.input.focus(),this.setAgentOnlineState("online"),this.waitingListTimeout.stop(),this.idleTimeout.stop(),this.inactiveTimeout.start(),"function"==typeof(t=this.options).onConnectionEstablished?t.onConnectionEstablished(e):void 0},t.prototype.showCustomerTimeout=function(){var e;return this.el.find(".zammad-chat-modal").html(this.view("customer_timeout")({agent:this.agent.name,delay:this.options.inactiveTimeout})),e=function(){return location.reload()},this.el.find(".js-restart").click(e),this.sessionClose()},t.prototype.showWaitingListTimeout=function(){var e;return this.el.find(".zammad-chat-modal").html(this.view("waiting_list_timeout")({delay:this.options.watingListTimeout})),e=function(){return location.reload()},this.el.find(".js-restart").click(e),this.sessionClose()},t.prototype.showLoader=function(){return this.el.find(".zammad-chat-modal").html(this.view("loader")())},t.prototype.setAgentOnlineState=function(e){var t;if(this.state=e,this.el)return t=e.charAt(0).toUpperCase()+e.slice(1),this.el.find(".zammad-chat-agent-status").attr("data-status",e).text(this.T(t))},t.prototype.detectHost=function(){var e;return e="ws://","https"===r&&(e="wss://"),this.options.host=""+e+a+"/ws"},t.prototype.loadCss=function(){var e,t,n;if(this.options.cssAutoload)return(n=this.options.cssUrl)||(n=this.options.host.replace(/^wss/i,"https").replace(/^ws/i,"http").replace(/\/ws$/i,""),n+="/assets/chat/chat.css"),this.log.debug("load css from '"+n+"'"),t="@import url('"+n+"');",(e=document.createElement("link")).onload=this.onCssLoaded,e.rel="stylesheet",e.href="data:text/css,"+escape(t),document.getElementsByTagName("head")[0].appendChild(e)},t.prototype.onCssLoaded=function(){var e;return this.cssLoaded=!0,this.socketReady&&this.onReady(),"function"==typeof(e=this.options).onCssLoaded?e.onCssLoaded():void 0},t.prototype.startTimeoutObservers=function(){var e,t,n;return this.idleTimeout=new s({logPrefix:"idleTimeout",debug:this.options.debug,timeout:this.options.idleTimeout,timeoutIntervallCheck:this.options.idleTimeoutIntervallCheck,callback:(e=this,function(){return e.log.debug("Idle timeout reached, hide widget",new Date),e.destroy({remove:!0})})}),this.inactiveTimeout=new s({logPrefix:"inactiveTimeout",debug:this.options.debug,timeout:this.options.inactiveTimeout,timeoutIntervallCheck:this.options.inactiveTimeoutIntervallCheck,callback:(t=this,function(){return t.log.debug("Inactive timeout reached, show timeout screen.",new Date),t.showCustomerTimeout(),t.destroy({remove:!1})})}),this.waitingListTimeout=new s({logPrefix:"waitingListTimeout",debug:this.options.debug,timeout:this.options.waitingListTimeout,timeoutIntervallCheck:this.options.waitingListTimeoutIntervallCheck,callback:(n=this,function(){return n.log.debug("Waiting list timeout reached, show timeout screen.",new Date),n.showWaitingListTimeout(),n.destroy({remove:!1})})})},t.prototype.disableScrollOnRoot=function(){return this.rootScrollOffset=this.scrollRoot.scrollTop(),this.scrollRoot.css({overflow:"hidden",position:"fixed"})},t.prototype.enableScrollOnRoot=function(){return this.scrollRoot.scrollTop(this.rootScrollOffset),this.scrollRoot.css({overflow:"",position:""})},t.prototype.isVisible=function(e,t,n,o){var s,i,a,r,l,d,c,u,h,m,p,g,f,y,v,b,w,T,C,S,k,z,A,x,O,I;if(!(e.length<1))if(i=E(_),T=(s=1/gi,"")).replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,"")).replace(/<(\/?)s>/gi,"<$1strike>")).replace(/ /gi," "),e.html(t),E("p",e).each(function(){var e,t;if(t=E(this).attr("style"),e=/mso-list:\w+ \w+([0-9]+)/.exec(t))return E(this).data("_listLevel",parseInt(e[1],10))}),c=0,u=null,E("p",e).each(function(){var e,t,n,o,s,i,a,r,l,d;if(void 0!==(e=E(this).data("_listLevel"))){if(d=E(this).text(),o="
                ",/^\s*\w+\./.test(d)&&(o=(s=/([0-9])\./.exec(d))?null!=(i=1<(l=parseInt(s[1],10)))?i:'
                  ':"
                    "}:"
                      "),c"+E(this).html()+""),E(this).remove(),c=e}return c=0}),E("[style]",e).removeAttr("style"),E("[align]",e).removeAttr("align"),E("span",e).replaceWith(function(){return E(this).contents()}),E("span:empty",e).remove(),E("[class^='Mso']",e).removeAttr("class"),E("p:empty",e).remove(),e},t.prototype.removeAttribute=function(e){var t,n,o,s,i;if(e){for(t=E(e),o=0,s=(i=e.attributes).length;o/g,">").replace(/"/g,""")}),function(){(function(){t.push('
                      \n
                      \n
                      \n \n \n \n \n \n
                      \n
                      \n
                      \n
                      \n \n '),t.push(this.T(this.title)),t.push('\n
                      \n
                      \n
                      \n \n
                      \n
                      \n
                      \n \n
                      \n
                      ")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.customer_timeout=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
                      \n '),this.agent?(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation with %s got closed.",this.delay,this.agent))):(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation got closed.",this.delay))),t.push("\n "),t.push('\n
                      \n
                      "),t.push(this.T("Start new conversation")),t.push("
                      \n
                      ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.loader=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('\n \n \n \n\n'),t.push(this.T("Connecting")),t.push("")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.message=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?s(e):""},o=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                      \n "),t.push(this.message),t.push("\n
                      ")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.status=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                      \n
                      \n '),t.push(this.status),t.push("\n
                      \n
                      ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.timestamp=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?s(e):""},o=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                      '),t.push(n(this.label)),t.push(" "),t.push(n(this.time)),t.push("
                      ")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.typingIndicator=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                      \n \n \n \n \n \n \n \n
                      ')}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                      \n \n \n \n \n \n '),t.push(this.T("All colleagues are busy.")),t.push("
                      \n "),t.push(this.T("You are on waiting list position %s.",this.position)),t.push("\n
                      ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting_list_timeout=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
                      \n '),t.push(this.T("We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!")),t.push('\n
                      \n
                      "),t.push(this.T("Start new conversation")),t.push("
                      \n
                      ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")}; \ No newline at end of file +window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.agent=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?s(e):""},o=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){this.agent.avatar&&(t.push('\n\n')),t.push('\n\n '),t.push(n(this.agent.name)),t.push("\n")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,function(){"use strict";var o=Object.hasOwnProperty,i=Object.setPrototypeOf,a=Object.isFrozen,s=Object.getPrototypeOf,r=Object.getOwnPropertyDescriptor,Le=Object.freeze,e=Object.seal,l=Object.create,t="undefined"!=typeof Reflect&&Reflect,d=t.apply,c=t.construct;d||(d=function(e,t,n){return e.apply(t,n)}),Le||(Le=function(e){return e}),e||(e=function(e){return e}),c||(c=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/gm),rt=e(/^data-[\-\w.\u00B7-\uFFFF]/),lt=e(/^aria-[\-\w]+$/),dt=e(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ct=e(/^(?:\w+script|data):/i),ut=e(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ht="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function mt(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t/i,n))ze(l,e);else{V&&(n=He(n,D," "),n=He(n,R," "));var c=e.nodeName.toLowerCase();if(_e(c,o,n))try{d?e.setAttributeNS(d,l,n):e.setAttribute(l,n),je(u.removed)}catch(e){}}}Ie("afterSanitizeAttributes",e,null)}},Re=function e(t){var n=void 0,o=xe(t);for(Ie("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)Ie("uponSanitizeShadowNode",n,null),Ee(n)||(n.content instanceof h&&e(n.content),De(n));Ie("afterSanitizeShadowDOM",t,null)};return u.sanitize=function(e,t){var n=void 0,o=void 0,s=void 0,i=void 0,a=void 0;if((fe=!e)&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Oe(e)){if("function"!=typeof e.toString)throw qe("toString is not a function");if("string"!=typeof(e=e.toString()))throw qe("dirty is not a string, aborting")}if(!u.isSupported){if("object"===ht(d.toStaticHTML)||"function"==typeof d.toStaticHTML){if("string"==typeof e)return d.toStaticHTML(e);if(Oe(e))return d.toStaticHTML(e.outerHTML)}return e}if(Z||be(t),u.removed=[],"string"==typeof e&&(se=!1),se);else if(e instanceof m)1===(o=(n=Ae("\x3c!----\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===o.nodeName||"HTML"===o.nodeName?n=o:n.appendChild(o);else{if(!$&&!V&&!G&&-1===e.indexOf("<"))return C&&te?C.createHTML(e):e;if(!(n=Ae(e)))return $?null:S}n&&X&&ke(n.firstChild);for(var r=xe(se?e:n);s=r.nextNode();)3===s.nodeType&&s===i||Ee(s)||(s.content instanceof h&&Re(s.content),De(s),i=s);if(i=null,se)return e;if($){if(J)for(a=x.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ee&&(a=I.call(c,a,!0)),a}var l=G?n.outerHTML:n.innerHTML;return V&&(l=He(l,D," "),l=He(l,R," ")),C&&te?C.createHTML(l):l},u.setConfig=function(e){be(e),Z=!0},u.clearConfig=function(){ye=null,Z=!1},u.isValidAttribute=function(e,t,n){ye||be({});var o=Pe(e),s=Pe(t);return _e(o,s,n)},u.addHook=function(e,t){"function"==typeof t&&(_[e]=_[e]||[],Ne(_[e],t))},u.removeHook=function(e){_[e]&&je(_[e])},u.removeHooks=function(e){_[e]&&(_[e]=[])},u.removeAllHooks=function(){_={}},u}()});var bind=function(e,t){return function(){return e.apply(t,arguments)}},slice=[].slice,extend=function(e,t){for(var n in t)hasProp.call(t,n)&&(e[n]=t[n]);function o(){this.constructor=e}return o.prototype=t.prototype,e.prototype=new o,e.__super__=t.prototype,e},hasProp={}.hasOwnProperty;!function(E,_){var n,o,t,s,e,i,a,r,l;i=(l=document.getElementsByTagName("script"))[l.length-1],r=_.location.protocol.replace(":",""),i&&i.src&&(a=i.src.match(".*://([^:/]*).*")[1],r=i.src.match("(.*)://[^:/]*.*")[1]),n=function(){function e(e){this.options=E.extend({},this.defaults,e),this.log=new t({debug:this.options.debug,logPrefix:this.options.logPrefix||this.logPrefix})}return e.prototype.defaults={debug:!1},e}(),t=function(){function e(e){this.log=bind(this.log,this),this.error=bind(this.error,this),this.notice=bind(this.notice,this),this.debug=bind(this.debug,this),this.options=E.extend({},this.defaults,e)}return e.prototype.defaults={debug:!1},e.prototype.debug=function(){var e;if(e=1<=arguments.length?slice.call(arguments,0):[],this.options.debug)return this.log("debug",e)},e.prototype.notice=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("notice",e)},e.prototype.error=function(){var e;return e=1<=arguments.length?slice.call(arguments,0):[],this.log("error",e)},e.prototype.log=function(e,t){var n,o,s,i;if(t.unshift("||"),t.unshift(e),t.unshift(this.options.logPrefix),console.log.apply(console,t),this.options.debug){for(i="",o=0,s=t.length;o"+i+"
                      ")}},e}(),s=function(e){function t(e){this.stop=bind(this.stop,this),this.start=bind(this.start,this),t.__super__.constructor.call(this,e)}return extend(t,n),t.prototype.timeoutStartedAt=null,t.prototype.logPrefix="timeout",t.prototype.defaults={debug:!1,timeout:4,timeoutIntervallCheck:.5},t.prototype.start=function(){var e,t,n;return this.stop(),t=new Date,e=function(){var e;if(e=new Date-new Date(t.getTime()+1e3*n.options.timeout*60),n.log.debug("Timeout check for "+n.options.timeout+" minutes (left "+e/1e3+" sec.)"),!(e<0))return n.stop(),n.options.callback()},(n=this).log.debug("Start timeout in "+this.options.timeout+" minutes"),this.intervallId=setInterval(e,1e3*this.options.timeoutIntervallCheck*60)},t.prototype.stop=function(){if(this.intervallId)return this.log.debug("Stop timeout of "+this.options.timeout+" minutes"),clearInterval(this.intervallId)},t}(),o=function(e){function t(e){this.ping=bind(this.ping,this),this.send=bind(this.send,this),this.reconnect=bind(this.reconnect,this),this.close=bind(this.close,this),this.connect=bind(this.connect,this),this.set=bind(this.set,this),t.__super__.constructor.call(this,e)}return extend(t,n),t.prototype.logPrefix="io",t.prototype.set=function(e){var t,n,o;for(t in n=[],e)o=e[t],n.push(this.options[t]=o);return n},t.prototype.connect=function(){var t,s,n,o;return this.log.debug("Connecting to "+this.options.host),this.ws=new _.WebSocket(""+this.options.host),this.ws.onopen=(t=this,function(e){return t.log.debug("onOpen",e),t.options.onOpen(e),t.ping()}),this.ws.onmessage=(s=this,function(e){var t,n,o;for(o=JSON.parse(e.data),s.log.debug("onMessage",e.data),t=0,n=o.length;tChat with us!",scrollHint:"Scroll down to see new messages",idleTimeout:6,idleTimeoutIntervallCheck:.5,inactiveTimeout:8,inactiveTimeoutIntervallCheck:.5,waitingListTimeout:4,waitingListTimeoutIntervallCheck:.5,onReady:void 0,onCloseAnimationEnd:void 0,onError:void 0,onOpenAnimationEnd:void 0,onConnectionReestablished:void 0,onSessionClosed:void 0,onConnectionEstablished:void 0,onCssLoaded:void 0},t.prototype.logPrefix="chat",t.prototype._messageCount=0,t.prototype.isOpen=!1,t.prototype.blinkOnlineInterval=null,t.prototype.stopBlinOnlineStateTimeout=null,t.prototype.showTimeEveryXMinutes=2,t.prototype.lastTimestamp=null,t.prototype.lastAddedType=null,t.prototype.inputDisabled=!1,t.prototype.inputTimeout=null,t.prototype.isTyping=!1,t.prototype.state="offline",t.prototype.initialQueueDelay=1e4,t.prototype.translations={da:{"Chat with us!":"Chat med os!","Scroll down to see new messages":"Scroll ned for at se nye beskeder",Online:"Online",Offline:"Offline",Connecting:"Forbinder","Connection re-established":"Forbindelse genoprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat lukket af %s","Compose your message...":"Skriv en besked...","All colleagues are busy.":"Alle kollegaer er optaget.","You are on waiting list position %s.":"Du er i venteliste som nummer %s.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale med %s blevet lukket.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da du ikke har svaret i de sidste %s minutter er din samtale blevet lukket.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, det tager længere end forventet at få en ledig plads. Prøv venligst igen senere eller send os en e-mail. På forhånd tak!"},de:{"Chat with us!":"Chatte mit uns!","Scroll down to see new messages":"Scrolle nach unten um neue Nachrichten zu sehen",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbindung wiederhergestellt",Today:"Heute",Send:"Senden","Chat closed by %s":"Chat beendet von %s","Compose your message...":"Ihre Nachricht...","All colleagues are busy.":"Alle Kollegen sind belegt.","You are on waiting list position %s.":"Sie sind in der Warteliste an der Position %s.","Start new conversation":"Neue Konversation starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit %s geschlossen.","Since you didn't respond in the last %s minutes your conversation got closed.":"Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Es tut uns leid, es dauert länger als erwartet, um einen freien Platz zu erhalten. Bitte versuchen Sie es zu einem späteren Zeitpunkt noch einmal oder schicken Sie uns eine E-Mail. Vielen Dank!"},es:{"Chat with us!":"Chatee con nosotros!","Scroll down to see new messages":"Haga scroll hacia abajo para ver nuevos mensajes",Online:"En linea",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexión restablecida",Today:"Hoy",Send:"Enviar","Chat closed by %s":"Chat cerrado por %s","Compose your message...":"Escriba su mensaje...","All colleagues are busy.":"Todos los agentes están ocupados.","You are on waiting list position %s.":"Usted está en la posición %s de la lista de espera.","Start new conversation":"Iniciar nueva conversación","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación con %s se ha cerrado.","Since you didn't respond in the last %s minutes your conversation got closed.":"Puesto que usted no respondió en los últimos %s minutos su conversación se ha cerrado.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Lo sentimos, se tarda más tiempo de lo esperado para ser atendido por un agente. Inténtelo de nuevo más tarde o envíenos un correo electrónico. ¡Gracias!"},fi:{"Chat with us!":"Keskustele kanssamme!","Scroll down to see new messages":"Rullaa alas nähdäksesi uudet viestit",Online:"Paikalla",Offline:"Poissa",Connecting:"Yhdistetään","Connection re-established":"Yhteys muodostettu uudelleen",Today:"Tänään",Send:"Lähetä","Chat closed by %s":"%s sulki keskustelun","Compose your message...":"Luo viestisi...","All colleagues are busy.":"Kaikki kollegat ovat varattuja.","You are on waiting list position %s.":"Olet odotuslistalla sijalla %s.","Start new conversation":"Aloita uusi keskustelu","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi %s kanssa suljettiin.","Since you didn't respond in the last %s minutes your conversation got closed.":"Koska et vastannut viimeiseen %s minuuttiin, keskustelusi suljettiin.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Olemme pahoillamme, tyhjän paikan vapautumisessa kestää odotettua pidempään. Ole hyvä ja yritä myöhemmin uudestaan tai lähetä meille sähköpostia. Kiitos!"},fr:{"Chat with us!":"Chattez avec nous!","Scroll down to see new messages":"Faites défiler pour lire les nouveaux messages",Online:"En-ligne",Offline:"Hors-ligne",Connecting:"Connexion en cours","Connection re-established":"Connexion rétablie",Today:"Aujourdhui",Send:"Envoyer","Chat closed by %s":"Chat fermé par %s","Compose your message...":"Composez votre message...","All colleagues are busy.":"Tous les collaborateurs sont occupés actuellement.","You are on waiting list position %s.":"Vous êtes actuellement en position %s dans la file d'attente.","Start new conversation":"Démarrer une nouvelle conversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation avec %s sera fermée.","Since you didn't respond in the last %s minutes your conversation got closed.":"Si vous ne répondez pas dans les %s minutes, votre conversation va être fermée.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Nous sommes désolés, il faut plus de temps que prévu pour obtenir un emplacement vide. Veuillez réessayer ultérieurement ou nous envoyer un courriel. Nous vous remercions!"},he:{"Chat with us!":"שוחחאיתנו!","Scroll down to see new messages":"גלול מטה כדי לראות הודעות חדשות",Online:"מחובר",Offline:"מנותק",Connecting:"מתחבר","Connection re-established":"החיבור שוחזר",Today:"היום",Send:"שלח","Chat closed by %s":'הצאט נסגר ע"י %s',"Compose your message...":"כתוב את ההודעה שלך ...","All colleagues are busy.":"כל הנציגים תפוסים","You are on waiting list position %s.":"מיקומך בתור %s.","Start new conversation":"התחל שיחה חדשה","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"מכיוון שלא הגבת במהלך %s דקות השיחה שלך עם %s נסגרה.","Since you didn't respond in the last %s minutes your conversation got closed.":"מכיוון שלא הגבת במהלך %s הדקות האחרונות השיחה שלך נסגרה.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":'מצטערים, הזמן לקבלת נציג ארוך מהרגיל. נסה שוב מאוחר יותר או שלח לנו דוא"ל. תודה!'},hu:{"Chat with us!":"Chatelj velünk!","Scroll down to see new messages":"Görgess lejjebb az újabb üzenetekért",Online:"Online",Offline:"Offline",Connecting:"Csatlakozás","Connection re-established":"Újracsatlakozás",Today:"Ma",Send:"Küldés","Chat closed by %s":"A beszélgetést lezárta %s","Compose your message...":"Írj üzenetet...","All colleagues are busy.":"Jelenleg minden kollégánk elfoglalt.","You are on waiting list position %s.":"A várólistán a %s. pozícióban várakozol.","Start new conversation":"Új beszélgetés indítása","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Mivel %s perce nem érkezett újabb üzenet, ezért a %s kollégával folytatott beszéletést lezártuk.","Since you didn't respond in the last %s minutes your conversation got closed.":"Mivel %s perce nem érkezett válasz, a beszélgetés lezárult.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Sajnáljuk, de a várakozási idő hosszabb a szokásosnál. Kérlek próbáld újra, vagy írd meg kérdésed emailben. Köszönjük!"},nl:{"Chat with us!":"Chat met ons!","Scroll down to see new messages":"Scrol naar beneden om nieuwe berichten te zien",Online:"Online",Offline:"Offline",Connecting:"Verbinden","Connection re-established":"Verbinding herstelt",Today:"Vandaag",Send:"Verzenden","Chat closed by %s":"Chat gesloten door %s","Compose your message...":"Typ uw bericht...","All colleagues are busy.":"Alle medewerkers zijn bezet.","You are on waiting list position %s.":"U bent %s in de wachtrij.","Start new conversation":"Nieuwe conversatie starten","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft wordt de conversatie met %s gesloten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Omdat u in de laatste %s minuten niets geschreven heeft is de conversatie gesloten.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Het spijt ons, het duurt langer dan verwacht om te antwoorden. Alstublieft probeer het later nogmaals of stuur ons een email. Hartelijk dank!"},it:{"Chat with us!":"Chatta con noi!","Scroll down to see new messages":"Scorri verso il basso per vedere i nuovi messaggi",Online:"Online",Offline:"Offline",Connecting:"Collegamento in corso","Connection re-established":"Collegamento ristabilito",Today:"Oggi",Send:"Invio","Chat closed by %s":"Chat chiusa da %s","Compose your message...":"Componi il tuo messaggio...","All colleagues are busy.":"Tutti gli operatori sono occupati.","You are on waiting list position %s.":"Sei in posizione %s nella lista d'attesa.","Start new conversation":"Avvia una nuova chat","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua chat con %s è stata chiusa.","Since you didn't respond in the last %s minutes your conversation got closed.":"Dal momento che non hai risposto negli ultimi %s minuti la tua chat è stata chiusa.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Ci dispiace, ci vuole più tempo del previsto per arrivare al tuo turno. Per favore riprova più tardi o inviaci un'email. Grazie!"},pl:{"Chat with us!":"Czatuj z nami!","Scroll down to see new messages":"Przewiń w dół, aby wyświetlić nowe wiadomości",Online:"Online",Offline:"Offline",Connecting:"Łączenie","Connection re-established":"Ponowne nawiązanie połączenia",Today:"dzisiejszy",Send:"Wyślij","Chat closed by %s":"Czat zamknięty przez %s","Compose your message...":"Utwórz swoją wiadomość...","All colleagues are busy.":"Wszyscy koledzy są zajęci.","You are on waiting list position %s.":"Na liście oczekujących znajduje się pozycja %s.","Start new conversation":"Rozpoczęcie nowej konwersacji","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ponieważ w ciągu ostatnich %s minut nie odpowiedziałeś, Twoja rozmowa z %s została zamknięta.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ponieważ nie odpowiedziałeś w ciągu ostatnich %s minut, Twoja rozmowa została zamknięta.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Przykro nam, ale to trwa dłużej niż się spodziewamy. Spróbuj ponownie później lub wyślij nam wiadomość e-mail. Dziękuję!"},"pt-br":{"Chat with us!":"Chat fale conosco!","Scroll down to see new messages":"Role para baixo, para ver nosvas mensagens",Online:"Online",Offline:"Desconectado",Connecting:"Conectando","Connection re-established":"Conexão restabelecida",Today:"Hoje",Send:"Enviar","Chat closed by %s":"Chat encerrado por %s","Compose your message...":"Escreva sua mensagem...","All colleagues are busy.":"Todos os agentes estão ocupados.","You are on waiting list position %s.":"Você está na posição %s na fila de espera.","Start new conversation":"Iniciar uma nova conversa","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Como você não respondeu nos últimos %s minutos sua conversa com %s foi encerrada.","Since you didn't respond in the last %s minutes your conversation got closed.":"Como você não respondeu nos últimos %s minutos sua conversa foi encerrada.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Desculpe, mas o tempo de espera por um agente foi excedido. Tente novamente mais tarde ou nós envie um email. Obrigado"},"zh-cn":{"Chat with us!":"发起即时对话!","Scroll down to see new messages":"向下滚动以查看新消息",Online:"在线",Offline:"离线",Connecting:"连接中","Connection re-established":"正在重新建立连接",Today:"今天",Send:"发送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在输入信息...","All colleagues are busy.":"所有工作人员都在忙碌中.","You are on waiting list position %s.":"您目前的等候位置是第 %s 位.","Start new conversation":"开始新的会话","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由于您超过 %s 分钟没有回复, 您与 %s 的会话已被关闭.","Since you didn't respond in the last %s minutes your conversation got closed.":"由于您超过 %s 分钟没有任何回复, 该对话已被关闭.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 目前需要等候更长的时间才能接入对话, 请稍后重试或向我们发送电子邮件. 谢谢!"},"zh-tw":{"Chat with us!":"開始即時對话!","Scroll down to see new messages":"向下滑動以查看新訊息",Online:"線上",Offline:"离线",Connecting:"連線中","Connection re-established":"正在重新建立連線中",Today:"今天",Send:"發送","Chat closed by %s":"Chat closed by %s","Compose your message...":"正在輸入訊息...","All colleagues are busy.":"所有服務人員都在忙碌中.","You are on waiting list position %s.":"你目前的等候位置是第 %s 順位.","Start new conversation":"開始新的對話","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"由於你超過 %s 分鐘沒有回應, 你與 %s 的對話已被關閉.","Since you didn't respond in the last %s minutes your conversation got closed.":"由於你超過 %s 分鐘沒有任何回應, 該對話已被關閉.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"非常抱歉, 當前需要等候更長的時間方可排入對話程序, 請稍後重試或向我們寄送電子郵件. 謝謝!"},ru:{"Chat with us!":"Напишите нам!","Scroll down to see new messages":"Прокрутите, чтобы увидеть новые сообщения",Online:"Онлайн",Offline:"Оффлайн",Connecting:"Подключение","Connection re-established":"Подключение восстановлено",Today:"Сегодня",Send:"Отправить","Chat closed by %s":"%s закрыл чат","Compose your message...":"Напишите сообщение...","All colleagues are busy.":"Все сотрудники заняты","You are on waiting list position %s.":"Вы в списке ожидания под номером %s","Start new conversation":"Начать новую переписку.","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор с %s был закрыт.","Since you didn't respond in the last %s minutes your conversation got closed.":"Поскольку вы не отвечали в течение последних %s минут, ваш разговор был закрыт.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"К сожалению, ожидание свободного места требует больше времени. Повторите попытку позже или отправьте нам электронное письмо. Спасибо!"},sv:{"Chat with us!":"Chatta med oss!","Scroll down to see new messages":"Rulla ner för att se nya meddelanden",Online:"Online",Offline:"Offline",Connecting:"Ansluter","Connection re-established":"Anslutningen återupprättas",Today:"I dag",Send:"Skicka","Chat closed by %s":"Chatt stängd av %s","Compose your message...":"Skriv ditt meddelande...","All colleagues are busy.":"Alla kollegor är upptagna.","You are on waiting list position %s.":"Du är på väntelistan som position %s.","Start new conversation":"Starta ny konversation","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Eftersom du inte svarat inom %s minuterna i din konversation med %s så stängdes chatten.","Since you didn't respond in the last %s minutes your conversation got closed.":"Då du inte svarat inom de senaste %s minuterna så avslutades din chatt.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi är ledsna, det tar längre tid som förväntat att få en ledig plats. Försök igen senare eller skicka ett e-postmeddelande till oss. Tack!"},no:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},nb:{"Chat with us!":"Chat med oss!","Scroll down to see new messages":"Bla ned for å se nye meldinger",Online:"Pålogget",Offline:"Avlogget",Connecting:"Koble til","Connection re-established":"Tilkoblingen er gjenopprettet",Today:"I dag",Send:"Send","Chat closed by %s":"Chat avsluttes om %s","Compose your message...":"Skriv din melding...","All colleagues are busy.":"Alle våre kolleger er for øyeblikket opptatt.","You are on waiting list position %s.":"Du står nå i kø og er nr. %s på ventelisten.","Start new conversation":"Start en ny samtale","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene av samtalen, vil samtalen med %s nå avsluttes.","Since you didn't respond in the last %s minutes your conversation got closed.":"Ettersom du ikke har respondert i løpet av de siste %s minuttene, har samtalen nå blitt avsluttet.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Vi beklager, men det tar lengre tid enn vanlig å få en ledig plass i vår chat. Vennligst prøv igjen på et senere tidspunkt eller send oss en e-post. Tusen takk!"},el:{"Chat with us!":"Επικοινωνήστε μαζί μας!","Scroll down to see new messages":"Μεταβείτε κάτω για να δείτε τα νέα μηνύματα",Online:"Σε σύνδεση",Offline:"Αποσυνδεμένος",Connecting:"Σύνδεση","Connection re-established":"Η σύνδεση αποκαταστάθηκε",Today:"Σήμερα",Send:"Αποστολή","Chat closed by %s":"Η συνομιλία έκλεισε από τον/την %s","Compose your message...":"Γράψτε το μήνυμα σας...","All colleagues are busy.":"Όλοι οι συνάδελφοι μας είναι απασχολημένοι.","You are on waiting list position %s.":"Βρίσκεστε σε λίστα αναμονής στη θέση %s.","Start new conversation":"Έναρξη νέας συνομιλίας","Since you didn't respond in the last %s minutes your conversation with %s got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας με τον/την %s έκλεισε.","Since you didn't respond in the last %s minutes your conversation got closed.":"Από τη στιγμή που δεν απαντήσατε τα τελευταία %s λεπτά η συνομιλία σας έκλεισε.","We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!":"Λυπούμαστε που χρειάζεται περισσότερος χρόνος από τον αναμενόμενο για να βρεθεί μία κενή θέση. Παρακαλούμε δοκιμάστε ξανά αργότερα ή στείλτε μας ένα email. Ευχαριστούμε!"}},t.prototype.sessionId=void 0,t.prototype.scrolledToBottom=!0,t.prototype.scrollSnapTolerance=10,t.prototype.richTextFormatKey={66:!0,73:!0,85:!0,83:!0},t.prototype.T=function(){var e,t,n,o,s,i;if(s=arguments[0],t=2<=arguments.length?slice.call(arguments,1):[],this.options.lang&&"en"!==this.options.lang&&(this.translations[this.options.lang]?((i=this.translations[this.options.lang])[s]||this.log.notice("Translation needed for '"+s+"'"),s=i[s]||s):this.log.notice("Translation '"+this.options.lang+"' needed!")),t)for(n=0,o=t.length;n',document.execCommand("insertHTML",!1,s)},m.resizeImage(s.src,460,"auto",2,"image/jpeg","auto",t)},d.readAsDataURL(i),a=!0)),!a){o=h=void 0;try{h=n.getData("text/html"),o="html",h&&0!==h.length||(o="text",h=n.getData("text/plain")),h&&0!==h.length||(o="text2",h=n.getData("text"))}catch(e){t=e,console.log("Sorry, can't insert markup because browser is not supporting it."),o="text3",h=n.getData("text")}return"text"!==o&&"text2"!==o&&"text3"!==o||(h=(h="
                      "+h.replace(/\n/g,"
                      ")+"
                      ").replace(/
                      <\/div>/g,"

                      ")),console.log("p",o,h),"html"===o&&(u=DOMPurify.sanitize(h),m.log.debug("sanitized HTML clipboard",u),e=E("
                      "+u+"
                      "),l=!1,s=h,c=new RegExp("<(/w|w):[A-Za-z]"),s.match(c)&&(l=!0,s=s.replace(c,"")),c=new RegExp("<(/o|o):[A-Za-z]"),s.match(c)&&(l=!0,s=s.replace(c,"")),l&&(e=m.wordFilter(e)),(e=E(e)).contents().each(function(){if(8===this.nodeType)return E(this).remove()}),e.find("a, font, small, time, form, label").replaceWith(function(){return E(this).contents()}),e.find("textarea").each(function(){var e,t;return t=this.outerHTML,c=new RegExp("<"+this.tagName,"i"),e=t.replace(c,"')).get(0),document.caretPositionFromPoint?(s=document.caretPositionFromPoint(l,d),(i=document.createRange()).setStart(s.offsetNode,s.offset),i.collapse(),i.insertNode(a)):document.caretRangeFromPoint?(i=document.caretRangeFromPoint(l,d)).insertNode(a):console.log("could not find carat")},c.resizeImage(a.src,460,"auto",2,"image/jpeg","auto",t)},o.readAsDataURL(n)})),E(_).on("beforeunload",(e=this,function(){return e.onLeaveTemporary()})),E(_).bind("hashchange",(t=this,function(){if(!t.isOpen)return t.idleTimeout.start();t.sessionId&&t.send("chat_session_notice",{session_id:t.sessionId,message:_.location.href})})),this.isFullscreen)return this.input.on({focus:this.onFocus,focusout:this.onFocusOut})},t.prototype.stopPropagation=function(e){return e.stopPropagation()},t.prototype.checkForEnter=function(e){if(!this.inputDisabled&&!e.shiftKey&&13===e.keyCode)return e.preventDefault(),this.sendMessage()},t.prototype.send=function(e,t){return null==t&&(t={}),t.chat_id=this.options.chatId,this.io.send(e,t)},t.prototype.onWebSocketMessage=function(e){var t,n,o;for(t=0,n=e.length;tnew Date((new Date).getTime()-1500)))return this.isTyping=new Date,this.send("chat_session_typing",{session_id:this.sessionId}),this.inactiveTimeout.start()},t.prototype.onSubmit=function(e){return e.preventDefault(),this.sendMessage()},t.prototype.sendMessage=function(){var e,t;if(e=this.input.html())return this.inactiveTimeout.start(),sessionStorage.removeItem("unfinished_message"),t=this.view("message")({message:e,from:"customer",id:this._messageCount++,unreadClass:""}),this.maybeAddTimestamp(),this.el.find(".zammad-chat-message--typing").get(0)?(this.lastAddedType="typing-placeholder",this.el.find(".zammad-chat-message--typing").before(t)):(this.lastAddedType="message--customer",this.el.find(".zammad-chat-body").append(t)),this.input.html(""),this.scrollToBottom(),this.send("chat_session_message",{content:e,id:this._messageCount,session_id:this.sessionId})},t.prototype.receiveMessage=function(e){return this.inactiveTimeout.start(),this.onAgentTypingEnd(),this.maybeAddTimestamp(),this.renderMessage({message:e.message.content,id:e.id,from:"agent"}),this.scrollToBottom({showHint:!0})},t.prototype.renderMessage=function(e){return this.lastAddedType="message--"+e.from,e.unreadClass=document.hidden?" zammad-chat-message--unread":"",this.el.find(".zammad-chat-body").append(this.view("message")(e))},t.prototype.open=function(){var e;if(!this.isOpen)return this.isOpen=!0,this.log.debug("open widget"),this.show(),this.sessionId||this.showLoader(),this.el.addClass("zammad-chat-is-open"),e=this.el.height()-this.el.find(".zammad-chat-header").outerHeight(),this.el.css("bottom",-e),this.sessionId?(this.el.css("bottom",0),this.onOpenAnimationEnd()):(this.el.animate({bottom:0},500,this.onOpenAnimationEnd),this.send("chat_session_init",{url:_.location.href}));this.log.debug("widget already open, block")},t.prototype.onOpenAnimationEnd=function(){var e;return this.idleTimeout.stop(),this.isFullscreen&&this.disableScrollOnRoot(),"function"==typeof(e=this.options).onOpenAnimationEnd?e.onOpenAnimationEnd():void 0},t.prototype.sessionClose=function(){return this.send("chat_session_close",{session_id:this.sessionId}),this.inactiveTimeout.stop(),this.waitingListTimeout.stop(),sessionStorage.removeItem("unfinished_message"),this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.setSessionId(void 0)},t.prototype.toggle=function(e){return this.isOpen?this.close(e):this.open(e)},t.prototype.close=function(e){var t;if(this.isOpen)return this.initDelayId&&clearTimeout(this.initDelayId),this.sessionId&&(this.log.debug("session close before widget close"),this.sessionClose()),this.log.debug("close widget"),e&&e.stopPropagation(),this.isFullscreen&&this.enableScrollOnRoot(),t=this.el.height()-this.el.find(".zammad-chat-header").outerHeight(),this.el.animate({bottom:-t},500,this.onCloseAnimationEnd);this.log.debug("can't close widget, it's not open")},t.prototype.onCloseAnimationEnd=function(){var e;return this.el.css("bottom",""),this.el.removeClass("zammad-chat-is-open"),this.showLoader(),this.el.find(".zammad-chat-welcome").removeClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent").addClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent-status").addClass("zammad-chat-is-hidden"),this.isOpen=!1,"function"==typeof(e=this.options).onCloseAnimationEnd&&e.onCloseAnimationEnd(),this.io.reconnect()},t.prototype.onWebSocketClose=function(){if(!this.isOpen)return this.el?(this.el.removeClass("zammad-chat-is-shown"),this.el.removeClass("zammad-chat-is-loaded")):void 0},t.prototype.show=function(){if("offline"!==this.state)return this.el.addClass("zammad-chat-is-loaded"),this.el.addClass("zammad-chat-is-shown")},t.prototype.disableInput=function(){return this.inputDisabled=!0,this.input.prop("contenteditable",!1),this.el.find(".zammad-chat-send").prop("disabled",!0),this.io.close()},t.prototype.enableInput=function(){return this.inputDisabled=!1,this.input.prop("contenteditable",!0),this.el.find(".zammad-chat-send").prop("disabled",!1)},t.prototype.hideModal=function(){return this.el.find(".zammad-chat-modal").html("")},t.prototype.onQueueScreen=function(e){var t,n;if(this.setSessionId(e.session_id),t=function(){return n.onQueue(e),n.waitingListTimeout.start()},!(n=this).initialQueueDelay||this.onInitialQueueDelayId)return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),t();this.onInitialQueueDelayId=setTimeout(t,this.initialQueueDelay)},t.prototype.onQueue=function(e){return this.log.notice("onQueue",e.position),this.inQueue=!0,this.el.find(".zammad-chat-modal").html(this.view("waiting")({position:e.position}))},t.prototype.onAgentTypingStart=function(){if(this.stopTypingId&&clearTimeout(this.stopTypingId),this.stopTypingId=setTimeout(this.onAgentTypingEnd,3e3),!this.el.find(".zammad-chat-message--typing").get(0)&&(this.maybeAddTimestamp(),this.el.find(".zammad-chat-body").append(this.view("typingIndicator")()),this.isVisible(this.el.find(".zammad-chat-message--typing"),!0)))return this.scrollToBottom()},t.prototype.onAgentTypingEnd=function(){return this.el.find(".zammad-chat-message--typing").remove()},t.prototype.onLeaveTemporary=function(){if(this.sessionId)return this.send("chat_session_leave_temporary",{session_id:this.sessionId})},t.prototype.maybeAddTimestamp=function(){var e,t,n;if(n=Date.now(),!this.lastTimestamp||n-this.lastTimestamp>6e4*this.showTimeEveryXMinutes)return e=this.T("Today"),t=(new Date).toTimeString().substr(0,5),"timestamp"===this.lastAddedType?(this.updateLastTimestamp(e,t),this.lastTimestamp=n):(this.el.find(".zammad-chat-body").append(this.view("timestamp")({label:e,time:t})),this.lastTimestamp=n,this.lastAddedType="timestamp",this.scrollToBottom())},t.prototype.updateLastTimestamp=function(e,t){if(this.el)return this.el.find(".zammad-chat-body").find(".zammad-chat-timestamp").last().replaceWith(this.view("timestamp")({label:e,time:t}))},t.prototype.addStatus=function(e){if(this.el)return this.maybeAddTimestamp(),this.el.find(".zammad-chat-body").append(this.view("status")({status:e})),this.scrollToBottom()},t.prototype.detectScrolledtoBottom=function(){var e;if(e=this.el.find(".zammad-chat-body").scrollTop()+this.el.find(".zammad-chat-body").outerHeight(),this.scrolledToBottom=Math.abs(e-this.el.find(".zammad-chat-body").prop("scrollHeight"))<=this.scrollSnapTolerance,this.scrolledToBottom)return this.el.find(".zammad-scroll-hint").addClass("is-hidden")},t.prototype.showScrollHint=function(){return this.el.find(".zammad-scroll-hint").removeClass("is-hidden"),this.el.find(".zammad-chat-body").scrollTop(this.el.find(".zammad-chat-body").scrollTop()+this.el.find(".zammad-scroll-hint").outerHeight())},t.prototype.onScrollHintClick=function(){return this.el.find(".zammad-chat-body").animate({scrollTop:this.el.find(".zammad-chat-body").prop("scrollHeight")},300)},t.prototype.scrollToBottom=function(e){var t;return t=(null!=e?e:{showHint:!1}).showHint,this.scrolledToBottom?this.el.find(".zammad-chat-body").scrollTop(E(".zammad-chat-body").prop("scrollHeight")):t?this.showScrollHint():void 0},t.prototype.destroy=function(e){return null==e&&(e={}),this.log.debug("destroy widget",e),this.setAgentOnlineState("offline"),e.remove&&this.el&&(this.el.remove(),E("."+this.options.buttonClass).hide()),this.waitingListTimeout&&this.waitingListTimeout.stop(),this.inactiveTimeout&&this.inactiveTimeout.stop(),this.idleTimeout&&this.idleTimeout.stop(),this.io.close()},t.prototype.reconnect=function(){return this.log.notice("reconnecting"),this.disableInput(),this.lastAddedType="status",this.setAgentOnlineState("connecting"),this.addStatus(this.T("Connection lost"))},t.prototype.onConnectionReestablished=function(){var e;return this.lastAddedType="status",this.setAgentOnlineState("online"),this.addStatus(this.T("Connection re-established")),"function"==typeof(e=this.options).onConnectionReestablished?e.onConnectionReestablished():void 0},t.prototype.onSessionClosed=function(e){var t;return this.addStatus(this.T("Chat closed by %s",e.realname)),this.disableInput(),this.setAgentOnlineState("offline"),this.inactiveTimeout.stop(),"function"==typeof(t=this.options).onSessionClosed?t.onSessionClosed(e):void 0},t.prototype.setSessionId=function(e){return void 0===(this.sessionId=e)?sessionStorage.removeItem("sessionId"):sessionStorage.setItem("sessionId",e)},t.prototype.onConnectionEstablished=function(e){var t;return this.onInitialQueueDelayId&&clearTimeout(this.onInitialQueueDelayId),this.inQueue=!1,e.agent&&(this.agent=e.agent),e.session_id&&this.setSessionId(e.session_id),this.el.find(".zammad-chat-body").html(""),this.el.find(".zammad-chat-agent").html(this.view("agent")({agent:this.agent})),this.enableInput(),this.hideModal(),this.el.find(".zammad-chat-welcome").addClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent").removeClass("zammad-chat-is-hidden"),this.el.find(".zammad-chat-agent-status").removeClass("zammad-chat-is-hidden"),this.isFullscreen||this.input.focus(),this.setAgentOnlineState("online"),this.waitingListTimeout.stop(),this.idleTimeout.stop(),this.inactiveTimeout.start(),"function"==typeof(t=this.options).onConnectionEstablished?t.onConnectionEstablished(e):void 0},t.prototype.showCustomerTimeout=function(){var e;return this.el.find(".zammad-chat-modal").html(this.view("customer_timeout")({agent:this.agent.name,delay:this.options.inactiveTimeout})),e=function(){return location.reload()},this.el.find(".js-restart").click(e),this.sessionClose()},t.prototype.showWaitingListTimeout=function(){var e;return this.el.find(".zammad-chat-modal").html(this.view("waiting_list_timeout")({delay:this.options.watingListTimeout})),e=function(){return location.reload()},this.el.find(".js-restart").click(e),this.sessionClose()},t.prototype.showLoader=function(){return this.el.find(".zammad-chat-modal").html(this.view("loader")())},t.prototype.setAgentOnlineState=function(e){var t;if(this.state=e,this.el)return t=e.charAt(0).toUpperCase()+e.slice(1),this.el.find(".zammad-chat-agent-status").attr("data-status",e).text(this.T(t))},t.prototype.detectHost=function(){var e;return e="ws://","https"===r&&(e="wss://"),this.options.host=""+e+a+"/ws"},t.prototype.loadCss=function(){var e,t,n;if(this.options.cssAutoload)return(n=this.options.cssUrl)||(n=this.options.host.replace(/^wss/i,"https").replace(/^ws/i,"http").replace(/\/ws$/i,""),n+="/assets/chat/chat.css"),this.log.debug("load css from '"+n+"'"),t="@import url('"+n+"');",(e=document.createElement("link")).onload=this.onCssLoaded,e.rel="stylesheet",e.href="data:text/css,"+escape(t),document.getElementsByTagName("head")[0].appendChild(e)},t.prototype.onCssLoaded=function(){var e;return this.cssLoaded=!0,this.socketReady&&this.onReady(),"function"==typeof(e=this.options).onCssLoaded?e.onCssLoaded():void 0},t.prototype.startTimeoutObservers=function(){var e,t,n;return this.idleTimeout=new s({logPrefix:"idleTimeout",debug:this.options.debug,timeout:this.options.idleTimeout,timeoutIntervallCheck:this.options.idleTimeoutIntervallCheck,callback:(e=this,function(){return e.log.debug("Idle timeout reached, hide widget",new Date),e.destroy({remove:!0})})}),this.inactiveTimeout=new s({logPrefix:"inactiveTimeout",debug:this.options.debug,timeout:this.options.inactiveTimeout,timeoutIntervallCheck:this.options.inactiveTimeoutIntervallCheck,callback:(t=this,function(){return t.log.debug("Inactive timeout reached, show timeout screen.",new Date),t.showCustomerTimeout(),t.destroy({remove:!1})})}),this.waitingListTimeout=new s({logPrefix:"waitingListTimeout",debug:this.options.debug,timeout:this.options.waitingListTimeout,timeoutIntervallCheck:this.options.waitingListTimeoutIntervallCheck,callback:(n=this,function(){return n.log.debug("Waiting list timeout reached, show timeout screen.",new Date),n.showWaitingListTimeout(),n.destroy({remove:!1})})})},t.prototype.disableScrollOnRoot=function(){return this.rootScrollOffset=this.scrollRoot.scrollTop(),this.scrollRoot.css({overflow:"hidden",position:"fixed"})},t.prototype.enableScrollOnRoot=function(){return this.scrollRoot.scrollTop(this.rootScrollOffset),this.scrollRoot.css({overflow:"",position:""})},t.prototype.isVisible=function(e,t,n,o){var s,i,a,r,l,d,c,u,h,m,p,g,f,y,v,b,w,T,C,S,k,z,A,x,O,I;if(!(e.length<1))if(i=E(_),T=(s=1/gi,"")).replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,"")).replace(/<(\/?)s>/gi,"<$1strike>")).replace(/ /gi," "),e.html(t),E("p",e).each(function(){var e,t;if(t=E(this).attr("style"),e=/mso-list:\w+ \w+([0-9]+)/.exec(t))return E(this).data("_listLevel",parseInt(e[1],10))}),c=0,u=null,E("p",e).each(function(){var e,t,n,o,s,i,a,r,l,d;if(void 0!==(e=E(this).data("_listLevel"))){if(d=E(this).text(),o="
                        ",/^\s*\w+\./.test(d)&&(o=(s=/([0-9])\./.exec(d))?null!=(i=1<(l=parseInt(s[1],10)))?i:'
                          ':"
                            "}:"
                              "),c"+E(this).html()+""),E(this).remove(),c=e}return c=0}),E("[style]",e).removeAttr("style"),E("[align]",e).removeAttr("align"),E("span",e).replaceWith(function(){return E(this).contents()}),E("span:empty",e).remove(),E("[class^='Mso']",e).removeAttr("class"),E("p:empty",e).remove(),e},t.prototype.removeAttribute=function(e){var t,n,o,s,i;if(e){for(t=E(e),o=0,s=(i=e.attributes).length;o/g,">").replace(/"/g,""")}),function(){(function(){t.push('
                              \n
                              \n
                              \n \n \n \n \n \n
                              \n
                              \n
                              \n
                              \n \n '),t.push(this.T(this.title)),t.push('\n
                              \n
                              \n
                              \n \n
                              \n
                              \n
                              \n \n
                              \n
                              ")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.customer_timeout=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
                              \n '),this.agent?(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation with %s got closed.",this.delay,this.agent))):(t.push("\n "),t.push(this.T("Since you didn't respond in the last %s minutes your conversation got closed.",this.delay))),t.push("\n "),t.push('\n
                              \n
                              "),t.push(this.T("Start new conversation")),t.push("
                              \n
                              ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.loader=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('\n \n \n \n\n'),t.push(this.T("Connecting")),t.push("")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.message=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?s(e):""},o=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                              \n "),t.push(this.message),t.push("\n
                              ")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.status=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                              \n
                              \n '),t.push(this.status),t.push("\n
                              \n
                              ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.timestamp=function(e){e||(e={});var t=[],n=function(e){return e&&e.ecoSafe?e:void 0!==e&&null!=e?s(e):""},o=e.safe,s=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},s||(s=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                              '),t.push(n(this.label)),t.push(" "),t.push(n(this.time)),t.push("
                              ")}).call(this)}.call(e),e.safe=o,e.escape=s,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.typingIndicator=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                              \n \n \n \n \n \n \n \n
                              ')}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){t.push('
                              \n \n \n \n \n \n '),t.push(this.T("All colleagues are busy.")),t.push("
                              \n "),t.push(this.T("You are on waiting list position %s.",this.position)),t.push("\n
                              ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")},window.zammadChatTemplates||(window.zammadChatTemplates={}),window.zammadChatTemplates.waiting_list_timeout=function(e){e||(e={});var t=[],n=e.safe,o=e.escape;return e.safe=function(e){if(e&&e.ecoSafe)return e;void 0!==e&&null!=e||(e="");var t=new String(e);return t.ecoSafe=!0,t},o||(o=e.escape=function(e){return(""+e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}),function(){(function(){var e;t.push('
                              \n '),t.push(this.T("We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!")),t.push('\n
                              \n
                              "),t.push(this.T("Start new conversation")),t.push("
                              \n
                              ")}).call(this)}.call(e),e.safe=n,e.escape=o,t.join("")}; \ No newline at end of file diff --git a/spec/system/chat_spec.rb b/spec/system/chat_spec.rb index 3dbaad633..6cdef49f9 100644 --- a/spec/system/chat_spec.rb +++ b/spec/system/chat_spec.rb @@ -388,4 +388,29 @@ RSpec.describe 'Chat Handling', type: :system do include_examples 'chat button is hidden after idle timeout' end end + + describe "Chat can't be closed after timeout #2471", authenticated_as: :authenticate do + shared_examples 'test issue #2471' do + it 'is able to close to the dialog after a idleTimeout happened' do + click agent_chat_switch_selector + open_window_and_switch + + visit chat_url + click '.zammad-chat .js-chat-open' + expect(page).to have_selector('.js-restart', wait: 60) + click '.js-chat-toggle .zammad-chat-header-icon' + expect(page).to have_no_selector('zammad-chat-is-open', wait: 60) + end + end + + context 'with jquery' do + include_examples 'test issue #2471' + end + + context 'wihtout jquery' do + let(:chat_url_type) { 'znuny-no-jquery' } + + include_examples 'test issue #2471' + end + end end From 0f4c6ddda3de850055c6fb3d3e8a484a207176a9 Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Fri, 15 Oct 2021 11:08:35 +0200 Subject: [PATCH 02/90] Fixes #3809 - Ticket owner selection is not updated if owner selection should be empty. --- app/models/core_workflow/attributes/user.rb | 12 +++++-- spec/models/core_workflow_spec.rb | 35 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/app/models/core_workflow/attributes/user.rb b/app/models/core_workflow/attributes/user.rb index 4700c0e46..36d3aee11 100644 --- a/app/models/core_workflow/attributes/user.rb +++ b/app/models/core_workflow/attributes/user.rb @@ -3,8 +3,11 @@ class CoreWorkflow::Attributes::User < CoreWorkflow::Attributes::Base def values - return ticket_owner_id_bulk if @attributes.payload['screen'] == 'overview_bulk' - return ticket_owner_id if @attributes.payload['class_name'] == 'Ticket' && @attribute[:name] == 'owner_id' + if @attribute[:name] == 'owner_id' && @attributes.payload['class_name'] == 'Ticket' + return ticket_owner_id_bulk if @attributes.payload['screen'] == 'overview_bulk' + + return ticket_owner_id + end [] end @@ -35,7 +38,10 @@ class CoreWorkflow::Attributes::User < CoreWorkflow::Attributes::Base def ticket_owner_id return [''] if @attributes.selected_only.group_id.blank? - group_owner_ids + owner_ids = group_owner_ids + return [''] if owner_ids.blank? + + owner_ids end def group_owner_ids diff --git a/spec/models/core_workflow_spec.rb b/spec/models/core_workflow_spec.rb index a98241f7a..711f582a0 100644 --- a/spec/models/core_workflow_spec.rb +++ b/spec/models/core_workflow_spec.rb @@ -207,6 +207,24 @@ RSpec.describe CoreWorkflow, type: :model do expect(result[:restrict_values]['owner_id']).to eq(['', action_user.id.to_s]) end end + + describe 'Ticket owner selection is not updated if owner selection should be empty #3809' do + let(:group_no_owners) { create(:group) } + let(:ticket2) { create(:ticket, group: group_no_owners) } + let(:payload) do + base_payload.merge('screen' => 'overview_bulk', 'params' => { 'ticket_ids' => ticket2.id.to_s }) + end + + before do + action_user.group_names_access_map = { + group_no_owners.name => %w[create read change overview], + } + end + + it 'does not show any owners for group with no full permitted users' do + expect(result[:restrict_values]['owner_id']).to eq(['']) + end + end end describe '.perform - Default - State' do @@ -1717,4 +1735,21 @@ RSpec.describe CoreWorkflow, type: :model do end end end + + describe 'Ticket owner selection is not updated if owner selection should be empty #3809' do + let(:group_no_owners) { create(:group) } + let(:payload) do + base_payload.merge('params' => { 'group_id' => group_no_owners.id }) + end + + before do + action_user.group_names_access_map = { + group_no_owners.name => %w[create read change overview], + } + end + + it 'does not show any owners because no one has full permissions' do + expect(result[:restrict_values]['owner_id']).to eq(['']) + end + end end From 6de1053d4b7cb2f108b1d2c7548c2e2f8b79d9e0 Mon Sep 17 00:00:00 2001 From: Mantas Masalskis Date: Tue, 12 Oct 2021 15:46:46 +0200 Subject: [PATCH 03/90] Fixes #3800 - Sort order group_by broken (alphabetical) --- .../_application_controller/table.coffee | 4 +- spec/system/overview_spec.rb | 88 +++++++++++-------- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller/table.coffee b/app/assets/javascripts/app/controllers/_application_controller/table.coffee index 7304710a0..3056a6a32 100644 --- a/app/assets/javascripts/app/controllers/_application_controller/table.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller/table.coffee @@ -497,7 +497,9 @@ class App.ControllerTable extends App.Controller reference_key = groupBy + '_id' if reference_key of object - return reference_key + attribute = _.findWhere(object.constructor.configure_attributes, { name: reference_key }) + + return App[attribute.relation]?.find(object[reference_key])?.displayName() || reference_key groupBy diff --git a/spec/system/overview_spec.rb b/spec/system/overview_spec.rb index 763619ec2..b255844c1 100644 --- a/spec/system/overview_spec.rb +++ b/spec/system/overview_spec.rb @@ -66,13 +66,19 @@ RSpec.describe 'Overview', type: :system do end end - context 'sorting when group by is set' do - let(:user) { create(:customer) } - let(:ticket1) { create(:ticket, group: Group.find_by(name: 'Users'), priority_id: 1, customer: user) } - let(:ticket2) { create(:ticket, group: Group.find_by(name: 'Users'), priority_id: 2, customer: user) } - let(:ticket3) { create(:ticket, group: Group.find_by(name: 'Users'), priority_id: 3, customer: user) } + context 'sorting when group by is set', authenticated_as: :user do + let(:user) { create(:agent, groups: [group_c, group_a, group_b]) } + + let(:group_a) { create(:group, name: 'aaa') } + let(:group_b) { create(:group, name: 'bbb') } + let(:group_c) { create(:group, name: 'ccc') } + + let(:ticket1) { create(:ticket, group: group_a, priority_id: 1, customer: user) } + let(:ticket2) { create(:ticket, group: group_c, priority_id: 2, customer: user) } + let(:ticket3) { create(:ticket, group: group_b, priority_id: 3, customer: user) } + let(:overview) do - create(:overview, group_by: 'priority', group_direction: group_direction, condition: { + create(:overview, group_by: group_key, group_direction: group_direction, condition: { 'ticket.customer_id' => { operator: 'is', value: user.id @@ -86,48 +92,60 @@ RSpec.describe 'Overview', type: :system do visit "ticket/view/#{overview.link}" end - context 'when group direction is default' do - let(:group_direction) { nil } + context 'when grouping by priority' do + let(:group_key) { 'priority' } - it 'sorts groups 1 > 3' do - within :active_content do - expect(find('.table-overview table tbody tr:first-child td:nth-child(1)').text).to match('1 low') - expect(find('.table-overview table tbody tr:nth-child(5) td:nth-child(1)').text).to match('3 high') + context 'when group direction is default' do + let(:group_direction) { nil } + + it 'sorts groups 1 > 3' do + within :active_content do + expect(all('.table-overview table b').map(&:text)).to eq ['1 low', '2 normal', '3 high'] + end + end + + it 'does not show duplicates when any ticket attribute is updated using bulk update' do + find("tr[data-id='#{ticket3.id}']").check('bulk', allow_label_click: true) + select '2 normal', from: 'priority_id' + + click '.js-confirm' + find('.js-confirm-step textarea').fill_in with: 'test tickets ordering' + click '.js-submit' + + within :active_content do + expect(page).to have_css("tr[data-id='#{ticket3.id}']", count: 1) + end end end - it 'does not show duplicates when any ticket attribute is updated using bulk update' do - find("tr[data-id='#{ticket3.id}']").check('bulk', allow_label_click: true) - select '2 normal', from: 'priority_id' + context 'when group direction is ASC' do + let(:group_direction) { 'ASC' } - click '.js-confirm' - find('.js-confirm-step textarea').fill_in with: 'test tickets ordering' - click '.js-submit' + it 'sorts groups 1 > 3' do + within :active_content do + expect(all('.table-overview table b').map(&:text)).to eq ['1 low', '2 normal', '3 high'] + end + end + end - within :active_content do - expect(page).to have_css("tr[data-id='#{ticket3.id}']", count: 1) + context 'when group direction is DESC' do + let(:group_direction) { 'DESC' } + + it 'sorts groups 3 > 1' do + within :active_content do + expect(all('.table-overview table b').map(&:text)).to eq ['3 high', '2 normal', '1 low'] + end end end end - context 'when group direction is ASC' do + context 'when grouping by groups' do + let(:group_key) { 'group' } let(:group_direction) { 'ASC' } - it 'sorts groups 1 > 3' do + it 'sorts groups a > b > c' do within :active_content do - expect(find('.table-overview table tbody tr:first-child td:nth-child(1)').text).to match('1 low') - expect(find('.table-overview table tbody tr:nth-child(5) td:nth-child(1)').text).to match('3 high') - end - end - end - - context 'when group direction is DESC' do - let(:group_direction) { 'DESC' } - - it 'sorts groups 3 > 1' do - within :active_content do - expect(find('.table-overview table tbody tr:first-child td:nth-child(1)').text).to match('3 high') - expect(find('.table-overview table tbody tr:nth-child(5) td:nth-child(1)').text).to match('1 low') + expect(all('.table-overview table b').map(&:text)).to eq %w[aaa bbb ccc] end end end From cb48cd2dcd0410240aca43f0b439853aa48c1b8d Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 19 Oct 2021 10:38:20 +0200 Subject: [PATCH 04/90] Fixes #3811 - Able to create custom fields for existing relation (e. g. ticket.state) - will lead to non bootable Zammad. --- app/models/object_manager/attribute.rb | 2 +- spec/models/object_manager/attribute_spec.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/models/object_manager/attribute.rb b/app/models/object_manager/attribute.rb index 404b5b3d1..d3071e5d0 100644 --- a/app/models/object_manager/attribute.rb +++ b/app/models/object_manager/attribute.rb @@ -820,7 +820,7 @@ is certain attribute used by triggers, overviews or schedulers end record = object_lookup.name.constantize.new - if record.respond_to?(name.to_sym) && record.attributes.key?(name) && new_record? + if new_record? && (record.respond_to?(name.to_sym) || record.attributes.key?(name)) errors.add(:name, "#{name} already exists!") end diff --git a/spec/models/object_manager/attribute_spec.rb b/spec/models/object_manager/attribute_spec.rb index 4e71f5b58..e283a8183 100644 --- a/spec/models/object_manager/attribute_spec.rb +++ b/spec/models/object_manager/attribute_spec.rb @@ -76,6 +76,22 @@ RSpec.describe ObjectManager::Attribute, type: :model do end end + %w[title tags].each do |not_editable_attribute| + it "rejects '#{not_editable_attribute}' which is used" do + expect do + described_class.add attributes_for :object_manager_attribute_text, name: not_editable_attribute + end.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Name Attribute not editable!') + end + end + + %w[priority state note number].each do |existing_attribute| + it "rejects '#{existing_attribute}' which is used" do + expect do + described_class.add attributes_for :object_manager_attribute_text, name: existing_attribute + end.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name #{existing_attribute} already exists!") + end + end + it 'rejects duplicate attribute name of conflicting types' do attribute = attributes_for :object_manager_attribute_text described_class.add attribute From 274e5a849f497652096e573870eab8118820f72e Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Wed, 20 Oct 2021 13:04:31 +0200 Subject: [PATCH 05/90] Fixes #3727 - Adding private keys allows adding certificates. --- .../integration/smime_controller.rb | 14 +-- app/models/smime_certificate.rb | 22 +++++ spec/fixtures/smime/issue_3727.key | 87 +++++++++++++++++++ spec/system/system/integration/smime_spec.rb | 83 +++++++++++------- 4 files changed, 164 insertions(+), 42 deletions(-) create mode 100644 spec/fixtures/smime/issue_3727.key diff --git a/app/controllers/integration/smime_controller.rb b/app/controllers/integration/smime_controller.rb index ae7a6c621..7204cc842 100644 --- a/app/controllers/integration/smime_controller.rb +++ b/app/controllers/integration/smime_controller.rb @@ -42,9 +42,7 @@ class Integration::SMIMEController < ApplicationController string = params[:file].read.force_encoding('utf-8') end - items = string.scan(%r{.+?-+END(?: TRUSTED)? CERTIFICATE-+}mi).each_with_object([]) do |cert, result| - result << SMIMECertificate.create!(public_key: cert) - end + items = SMIMECertificate.create_certificates(string) render json: { result: 'ok', @@ -73,14 +71,8 @@ class Integration::SMIMEController < ApplicationController raise "Parameter 'data' or 'file' required." if string.blank? - private_key = OpenSSL::PKey.read(string, params[:secret]) - modulus = private_key.public_key.n.to_s(16) - - certificate = SMIMECertificate.find_by(modulus: modulus) - - raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate - - certificate.update!(private_key: string, private_key_secret: params[:secret]) + SMIMECertificate.create_certificates(string) + SMIMECertificate.create_private_keys(string, params[:secret]) render json: { result: 'ok', diff --git a/app/models/smime_certificate.rb b/app/models/smime_certificate.rb index 932e79632..6d62e968d 100644 --- a/app/models/smime_certificate.rb +++ b/app/models/smime_certificate.rb @@ -3,6 +3,28 @@ class SMIMECertificate < ApplicationModel validates :fingerprint, uniqueness: { case_sensitive: true } + def self.parts(raw) + raw.scan(%r{-----BEGIN[^-]+-----.+?-----END[^-]+-----}m) + end + + def self.create_private_keys(raw, secret) + parts(raw).select { |part| part.include?('PRIVATE KEY') }.each do |part| + private_key = OpenSSL::PKey.read(part, secret) + modulus = private_key.public_key.n.to_s(16) + certificate = find_by(modulus: modulus) + + raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate + + certificate.update!(private_key: part, private_key_secret: secret) + end + end + + def self.create_certificates(raw) + parts(raw).select { |part| part.include?('CERTIFICATE') }.each_with_object([]) do |part, result| + result << create!(public_key: part) + end + end + def self.parse(raw) OpenSSL::X509::Certificate.new(raw.gsub(%r{(?:TRUSTED\s)?(CERTIFICATE---)}, '\1')) end diff --git a/spec/fixtures/smime/issue_3727.key b/spec/fixtures/smime/issue_3727.key new file mode 100644 index 000000000..2609a610c --- /dev/null +++ b/spec/fixtures/smime/issue_3727.key @@ -0,0 +1,87 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEA5Uos3YnMM4DLyO57KSshsUFC71+OqGdmVdWRln5P5R8IFR1D +9clNsz4o/SfJQVsI2WjnHtJs/2y3GMilTm56tOChBiwY0EiB7icy5t+BTqXigZJf +3PtK2zpHe6wU6w3mO2h1DLCP+dwuiCSYLkq+Gvdb+Q3xs6O3rDw+uX868MTwMde6 +Id6q5bq8po8PCdqrSLNBXBas5k8ixsMzKTDwv4DzPdh4xmml+4qfOC1GgC+27iSc +RJGNYpfXqJzoMdpXF379YAEEbHaLWr5GXQ7uHUlNiaE0fFv8pcm5OONQDn88KzKx +/24X31zyIcnAacwHyVQ1ueqP9VDK5zHV3vVVHWKqhoQC/YRy8CnjHim9NbL0pFyk +BrYbVa49bsZ39pRmljTSRxv8osoOn5NPqOnuR7EjTyOdltJvXs8R4IB3TTqNchf7 +2MemJRPgyn0Dj1NOHaJ+9K1w73vy3CNhTb3VijHTT0zgJuAuaxgjD7EHL0VFnVIl +yX2kaXquhBIi1naZPA1a1acXlxYrbAWqf97BF5B95Pi0afIHdOqeFS3RD3Fpj4jK +i2HdAEzYBA7r56qpsSJNxW0eBxqUlTu4g+B37fRijyZXAW74a2yS1B530OTEzzvv +GblaaM1pb4LoeT0dvqnoq7/0J2C+ZkeWXgTH3oobcc73CdROhszSJUHmT+kCAwEA +AQKCAgA+C3JUiGMvVJzQRGgjXb6CPoykRZFO1JwGggIhXRC1iU4gmIi5S72w0RM6 +XbfB7aZZXl+cIYjJHVv2YuUIcjDWHSq1ht04D0bJcOX/P1+4Ln86XKeAHqfE5uJM +/uWyLVKtpLh3tJdhH0mgIXbkn+kNVv4WSMWsvJKJEsxOWbVTTZdJhXyiiaRpAbzm +vTNukTNkOs1m4+PpdmSMsGl5rfqXd4dapucXmaMGjB5Fj0rSiRbRHisDCvfdRAVh +ZQQX6WNDwmNBxUSzLOjMp0xXBiE834cRxQN021dkbU+nqysQoTFg5xjva5UeJgKH +Tha5CjLZMeZP5r8JvNEK+ptK98wN4nLDqTlvgFr/L7RSrlj6KUWnM8e+cOMo/2T9 +VQ/7XQhZ0lxvVr9xEW5pkKqlq4Tyd4YwniU/rNNJV7cyqacpDSN4go/kYFsLCb5F +wY8CIBg/jLM4a7i7nt7UXcNVh3iRvhq8CduG4paQeJbxTSmzge4VAFwKO0TXotvN +V2Um+ibFc7S/9Tzy1BeTfUUE9nAWP9sXomBBXwG59Au9graorzuAUv+y2XfzTqK+ +QNtrHzL0VCODXAjOoSTr2Fhic+VR4lpxpZAzHxaOVKMHNW9+Z/QEFdgEjC4dvWXc +7geWNcrzjspz918j1CKihaVFX1tamDPSb1gk1+kgsDrwy3lI+QKCAQEA9KAxZ35/ ++bJbYXFzQFlaFyBjny5VGzchfhydj66S01NofmQLdyGHcWwqVf1DdL8dWmJC0eaP +pkIzLj/F6uvowASUk80MNARkVLMRr0M2y1p+MkLHy2eabaHF8S8AuaHV1bZtchCw +L00uPM5XECFqzctJv2Rcx+TAgDz0DfLDH/+Z4qhxYP93a/4aQnEMCrLPB8lJtGu9 +dzilzm7UKHddTSudX5N9XHQmwLe6lsaXaJ8F5wocRyw6/Ay3yesluNRAZ2BYGDfc +8Vh3tV9bfw8e1v8xgT3KyHSBo5kKxlHmQATBDTV490sLmK8U37my4UYeZYST4ag3 +4CnX1E5A0RhVxwKCAQEA7/NvpOOGCHqL6NDK1VvEP5axX9iS9rUPHAi6bxhf9RrR +K6S+9iVHgaOZMoVguHHqYfJUO3UPZlZ7h2gGppwgp/he2743mSeURiHOK7c6JIsH +KJhCCCiUjJeonykLHvbMQV8Nf9Ci+j/N3A+pwiR+ogLJY8vPSS2Hx+X/LoF1MUX/ +54LJwYFM1T8yVrDNXY29QrtBFMN+usyZ2KFyEPukfLNI0yZl+XlpHWedWHqZWcXK +DNAgOBoe5LVaAUnKatrEZH9t0tKYax+DTaDz3eA5YW8s7pKOFEjRBrZnoBW/dYfz +oF3QzEDlOb9dS+rx6+tj/ifn9RDcLFsRZs7ieK9szwKCAQEAwraDvJI3QURTckOA +bjbw+7l/MmQJwAjo8t3KGGTnX6hjYz801RVuHrzvEdTujY3VymyuLS8tJjRJUsXW +PsCaWcULkn3C+eCJD9Yc/HkuszyLeGwpZeFITX1X9jrog9mqQFrd0M4xvuTbKfE/ +4YoH3liykdJL+5w8EZby1+tknyKvlXdoD8Ioh2AR/NLIt/dNzS/OJ/seKzh+2crj +unYQYO2XbU0TmrSlZ/6WWY8nU1JIu3cTvR8asCdbXzB5rR3dSaupU1Wb2ssFNev6 +Ay/A53bnK61IrLf3vIWDywnDkS93jpECgSxNxbGOlunT1XYfmcSmhRaFqzsDHW1Q +MF8DXwKCAQB+S/TEpllDFzWTCmromE+YZLnhx/26yxwz1khC92JygXX9cc5tgru7 +eZ/GHrwE+Tiz6zf4v6mmZPjKEbAGfAEYSDutj9Z1z4ZUz7BUBDIfT+oprNJ8ttdR +lPXVKGZJGv/xnJVfZDKUY4b4QGpK3Kimn67ez0TAsK1aQy3ojY1grQaAFbAaIPOO +/p+BT7gYeOVYPXWI90k6Cz0i7/84/yrZ1AgN05UzFXuFVadVDdqvjNLHobiDrwP5 +v5arPOrFCXb7qrLkl6JQKBsVfhU+AKpTJZBR1mPgO1+CF/o9IZVPyIosK5UeHT5K +AfaaYgSJ97D+8oQ90m0BD8H+CgDcIwGzAoIBAC4KznWcLMOs7IIWu0OIfK1VWHdc +dcKwJOs+jGM4SaGWDTBUdVU2NE43fyphwJjBmj+efogeRW1JRBJJ3FNE8YULDmxO +UJEYyYvmuzsWNoF6e0uqLmn+aO6m1kbVnkF/YA5MR3ovKy6YAIVXIhGKPv8SHfea +nyLMORaVy0R8o3fyq6FMKoEf1p+2Gx3mMeKOIghhB/7clHWGZO8igKab+juGZPiK +3vd9Mg00GugWQZpIoyOIQvbdtNARVOVRCWDdtrdyb5UsfZx7fJBWnDiGhXjsGDYU +7bg30nj69KxQTuWR64tY4bBrTIifLWUP4YRm//lljcyjwzZUNP63ujD2eHI= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIGTTCCBDWgAwIBAgIBKDANBgkqhkiG9w0BAQ0FADCBjjELMAkGA1UEBhMCREUx +DzANBgNVBAgTBkJlcmxpbjEPMA0GA1UEBxMGQmVybGluMRUwEwYDVQQKEwxTZWNy +ZXQgQ29ycC4xCzAJBgNVBAsTAklUMRMwEQYDVQQDEwpFeGFtcGxlLUNBMSQwIgYJ +KoZIhvcNAQkBFhVjYS1pc3N1ZXNAZXhhbXBsZS5jb20wHhcNMjEwOTA3MTQxNDAw +WhcNMzAwNjAyMTQwMDAwWjCBkzELMAkGA1UEBhMCREUxDzANBgNVBAgTBkJlcmxp +bjEQMA4GA1UEBxMHR2VybWFueTEUMBIGA1UEChMLWmFtbWFkIEdtYkgxCzAJBgNV +BAsTAklUMRswGQYDVQQDDBJzYW1wbGVAZXhhbXBsZS5jb20xITAfBgkqhkiG9w0B +CQEWEnNhbXBsZUBleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAOVKLN2JzDOAy8jueykrIbFBQu9fjqhnZlXVkZZ+T+UfCBUdQ/XJTbM+ +KP0nyUFbCNlo5x7SbP9stxjIpU5uerTgoQYsGNBIge4nMubfgU6l4oGSX9z7Sts6 +R3usFOsN5jtodQywj/ncLogkmC5Kvhr3W/kN8bOjt6w8Prl/OvDE8DHXuiHequW6 +vKaPDwnaq0izQVwWrOZPIsbDMykw8L+A8z3YeMZppfuKnzgtRoAvtu4knESRjWKX +16ic6DHaVxd+/WABBGx2i1q+Rl0O7h1JTYmhNHxb/KXJuTjjUA5/PCsysf9uF99c +8iHJwGnMB8lUNbnqj/VQyucx1d71VR1iqoaEAv2EcvAp4x4pvTWy9KRcpAa2G1Wu +PW7Gd/aUZpY00kcb/KLKDp+TT6jp7kexI08jnZbSb17PEeCAd006jXIX+9jHpiUT +4Mp9A49TTh2ifvStcO978twjYU291Yox009M4CbgLmsYIw+xBy9FRZ1SJcl9pGl6 +roQSItZ2mTwNWtWnF5cWK2wFqn/ewReQfeT4tGnyB3TqnhUt0Q9xaY+Iyoth3QBM +2AQO6+eqqbEiTcVtHgcalJU7uIPgd+30Yo8mVwFu+GtsktQed9DkxM877xm5WmjN +aW+C6Hk9Hb6p6Ku/9CdgvmZHll4Ex96KG3HO9wnUTobM0iVB5k/pAgMBAAGjga4w +gaswDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUeecOtMgq/dUjz9DSBv9Zv/z5gE8w +CwYDVR0PBAQDAgPoMB0GA1UdJQQWMBQGCCsGAQUFBwMDBggrBgEFBQcDBDAdBgNV +HREEFjAUgRJzYW1wbGVAZXhhbXBsZS5jb20wEQYJYIZIAYb4QgEBBAQDAgUgMB4G +CWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQENBQADggIB +ALT3Mfzak7PHC1bmbHN58dhxaUdlhzX1u3UlDp7vPnm/7lKu4fw127qEQY186tni +Krn4bWYeYYo78pBmejzarkaA6UKOXtFC0IEepehNsCPcwjkxSp7FFqpjZ+krWwbU +wD8Ou3bXJVBsMvCZ/ucJc8ThOGqF4Lgeyvy4mw75WJtFe304fAhDTLedRyXqjhLX +4t0UT9cvKLfkqJCu051nDOlQs58stq63beUCZ1vIDu8jJNZ7PzT1F21AxMpYY6uq +0Cyb9qdMi3VNudbD62Ze85+qh4nunWIvBTGBZab3JhFvCRzafYiJwo6xT1+cFsiF +ZaUejtUPQNg0T6gOZIAu5tb1cgwBMBX0uD2gl7NPbqXsLET5U30a0AbGbM1p61H3 +EaBcl4MvtC4yUGfV/HrY6ZtzzYaKNKuflONS11GuzVIJA4noRExY+aYCLuWDN2Hj +wIpSzfA9uk5P13sydtiIstrfQrI5bHXMT8vCOe2vugIm/dTcRGkn65OlUiQYRhI/ +0/oef9ulnpTmZa0sJ2LPGiUkbRNRsw1imIpzy0F3CdIBfVzlEri+wbIth3Ufaeuk +LelKitEXM+BeuCfAJGzBENzOL3RdSP6LwM6oDFxZxTu6nFJ+Kjx7CZeJECEGBZHj +fnZoZ5X9LSfNMYH4TZG0jkH2Sm7L0OQmPOqViZ5NB3bw +-----END CERTIFICATE----- diff --git a/spec/system/system/integration/smime_spec.rb b/spec/system/system/integration/smime_spec.rb index 0d1ad99ce..13c5dab8c 100644 --- a/spec/system/system/integration/smime_spec.rb +++ b/spec/system/system/integration/smime_spec.rb @@ -23,44 +23,65 @@ RSpec.describe 'Manage > Integration > S/MIME', type: :system do click 'label[for=setting-switch]' end - it 'enabling and adding of public and private key' do + context 'when doing basic tests' do + it 'enabling and adding of public and private key' do - # add cert - click '.js-addCertificate' - fill_in 'Paste Certificate', with: certificate - click '.js-submit' + # add cert + click '.js-addCertificate' + fill_in 'Paste Certificate', with: certificate + click '.js-submit' - # add private key - click '.js-addPrivateKey' - fill_in 'Paste Private Key', with: private_key - fill_in 'Enter Private Key secret', with: private_key_secret - click '.js-submit' + # add private key + click '.js-addPrivateKey' + fill_in 'Paste Private Key', with: private_key + fill_in 'Enter Private Key secret', with: private_key_secret + click '.js-submit' - # wait for ajax - expect(page).to have_css('td', text: 'Including private key') + # check result + expect(Setting.get('smime_integration')).to be true + expect(SMIMECertificate.last.fingerprint).to be_present + expect(SMIMECertificate.last.raw).to be_present + expect(SMIMECertificate.last.private_key).to be_present + end - # check result - expect(Setting.get('smime_integration')).to be true - expect(SMIMECertificate.last.fingerprint).to be_present - expect(SMIMECertificate.last.raw).to be_present - expect(SMIMECertificate.last.private_key).to be_present + it 'adding of multiple certificates at once' do + multiple_certificates = [ + File.read(Rails.root.join('spec/fixtures/smime/ChainCA.crt')), + File.read(Rails.root.join('spec/fixtures/smime/IntermediateCA.crt')), + File.read(Rails.root.join('spec/fixtures/smime/RootCA.crt')), + ].join + + # add cert + click '.js-addCertificate' + fill_in 'Paste Certificate', with: multiple_certificates + click '.js-submit' + + # wait for ajax + expect(page).to have_text('ChainCA') + expect(page).to have_text('IntermediateCA') + expect(page).to have_text('RootCA') + end end - it 'adding of multiple certificates at once' do - multiple_certificates = [ - File.read(Rails.root.join('spec/fixtures/smime/ChainCA.crt')), - File.read(Rails.root.join('spec/fixtures/smime/IntermediateCA.crt')), - File.read(Rails.root.join('spec/fixtures/smime/RootCA.crt')), - ].join + context 'Adding private keys allows adding certificates #3727' do + let!(:private_key) do + File.read(Rails.root.join('spec/fixtures/smime/issue_3727.key')) + end - # add cert - click '.js-addCertificate' - fill_in 'Paste Certificate', with: multiple_certificates - click '.js-submit' + it 'does add public and private key in one file' do - # wait for ajax - expect(page).to have_text('ChainCA') - expect(page).to have_text('IntermediateCA') - expect(page).to have_text('RootCA') + # add private key + click '.js-addPrivateKey' + fill_in 'Paste Private Key', with: private_key + click '.js-submit' + + # check result + expect(Setting.get('smime_integration')).to be true + expect(SMIMECertificate.last.fingerprint).to eq('db49277070afcfc657bd71d04be4dd7e28f1685a') + expect(SMIMECertificate.last.raw).to include('CERTIFICATE') + expect(SMIMECertificate.last.raw).not_to include('PRIVATE') + expect(SMIMECertificate.last.private_key).to include('PRIVATE') + expect(SMIMECertificate.last.private_key).not_to include('CERTIFICATE') + end end end From b9bb88e96d06912ac9713d7bb479e841f50946b8 Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Wed, 20 Oct 2021 13:01:45 +0200 Subject: [PATCH 06/90] Fixes #3801 - Remote change of the group id does show it falsly as user change and not render the new value to the ticket. --- .../app/controllers/ticket_zoom.coffee | 9 +++ .../ticket_zoom/sidebar_ticket.coffee | 60 ++++++++----------- spec/system/ticket/zoom_spec.rb | 7 +++ 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.coffee index 6b560691b..0698fda57 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.coffee @@ -641,6 +641,7 @@ class App.TicketZoom extends App.Controller # update changes in ui currentStore = @currentStore() modelDiff = @formDiff(currentParams, currentStore) + return if _.isEmpty(modelDiff) # set followup state if needed @setDefaultFollowUpState(modelDiff, currentStore) @@ -735,6 +736,14 @@ class App.TicketZoom extends App.Controller # do not compare null or undefined value if currentStore.ticket + + # make sure that the compared state is same in local storage and + # rendered html. Else we could have race conditions of data + # which is not rendered yet + renderedUpdatedAt = @el.find('.edit').attr('data-ticket-updated-at') + return if !renderedUpdatedAt + return if currentStore.ticket.updated_at.toString() isnt renderedUpdatedAt + for key, value of currentStore.ticket if value is null || value is undefined currentStore.ticket[key] = '' diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee index bdb18819b..4b516e209 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee @@ -29,41 +29,33 @@ class Edit extends App.Controller # for the new ticket + eventually changed task state @formMeta.core_workflow = undefined - if followUpPossible == 'new_ticket' && ticketState != 'closed' || - followUpPossible != 'new_ticket' || - @permissionCheck('admin') || @ticket.currentView() is 'agent' - @controllerFormSidebarTicket = new App.ControllerForm( - elReplace: @el - model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes } - screen: 'edit' - handlersConfig: handlers - filter: @formMeta.filter - formMeta: @formMeta - params: defaults - isDisabled: !@ticket.editable() - taskKey: @taskKey - core_workflow: { - callbacks: [@markForm] - } - #bookmarkable: true - ) - else - @controllerFormSidebarTicket = new App.ControllerForm( - elReplace: @el - model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes } - screen: 'edit' - handlersConfig: handlers - filter: @formMeta.filter - formMeta: @formMeta - params: defaults - isDisabled: @ticket.editable() - taskKey: @taskKey - core_workflow: { - callbacks: [@markForm] - } - #bookmarkable: true - ) + editable = @ticket.editable() + if followUpPossible == 'new_ticket' && ticketState != 'closed' || followUpPossible != 'new_ticket' || @permissionCheck('admin') || @ticket.currentView() is 'agent' + editable = !editable + # reset updated_at for the sidbar because we render a new state + # it is used to compare the ticket with the rendered data later + # and needed to prevent race conditions + @el.removeAttr('data-ticket-updated-at') + + @controllerFormSidebarTicket = new App.ControllerForm( + elReplace: @el + model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes } + screen: 'edit' + handlersConfig: handlers + filter: @formMeta.filter + formMeta: @formMeta + params: defaults + isDisabled: editable + taskKey: @taskKey + core_workflow: { + callbacks: [@markForm] + } + #bookmarkable: true + ) + + # set updated_at for the sidbar because we render a new state + @el.attr('data-ticket-updated-at', defaults.updated_at) @markForm(true) return if @resetBind diff --git a/spec/system/ticket/zoom_spec.rb b/spec/system/ticket/zoom_spec.rb index 07b57b31d..7f48eb9db 100644 --- a/spec/system/ticket/zoom_spec.rb +++ b/spec/system/ticket/zoom_spec.rb @@ -2149,6 +2149,13 @@ RSpec.describe 'Ticket zoom', type: :system do expect(page.find("select[name='priority_id']").value).to eq(high_prio.id.to_s) end + it 'does show up the new group (different case because it will also trigger a full rerender because of potential permission changes)' do + group = Group.find_by(name: 'some group1') + ticket.update(group: group) + wait(10, interval: 0.5).until { page.find("select[name='group_id']").value == group.id.to_s } + expect(page.find("select[name='group_id']").value).to eq(group.id.to_s) + end + it 'does show up the new state and pending time' do pending_state = Ticket::State.find_by(name: 'pending reminder') ticket.update(state: pending_state, pending_time: 1.day.from_now) From 4ca7c254800cafdb9f403c1637ccb31b0058a8ae Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Wed, 20 Oct 2021 15:48:33 +0100 Subject: [PATCH 07/90] Fixes #3807 - Remove changes of 05a471f90ddf40bbe3d7693cde3f0be7ab5a6ed4 because they are not working as expected. --- .../app/controllers/ticket_overview.coffee | 31 ++----------------- .../javascripts/app/lib/spine/ajax.coffee | 14 ++++----- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/app/controllers/ticket_overview.coffee b/app/assets/javascripts/app/controllers/ticket_overview.coffee index aa254c6e3..e017c7f82 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.coffee @@ -206,22 +206,13 @@ class App.TicketOverview extends App.Controller article: article ) ticket.article = article - ticket.ajax().update( - ticket.attributes() - # this option will prevent callbacks and invalid data states in case of an error - failResponseNoTrigger: true + ticket.save( done: (r) => @batchCountIndex++ # refresh view after all tickets are proceeded if @batchCountIndex == @batchCount App.Event.trigger('overview:fetch') - fail: (record, settings, details) -> - console.log('record, settings, details', record, settings, details) - App.Event.trigger('notify', { - type: 'error' - msg: App.i18n.translateContent('Bulk action stopped %s!', error) - }) ) return @@ -234,21 +225,13 @@ class App.TicketOverview extends App.Controller ticket.owner_id = id if !_.isEmpty(groupId) ticket.group_id = groupId - ticket.ajax().update( - ticket.attributes() - # this option will prevent callbacks and invalid data states in case of an error - failResponseNoTrigger: true + ticket.save( done: (r) => @batchCountIndex++ # refresh view after all tickets are proceeded if @batchCountIndex == @batchCount App.Event.trigger('overview:fetch') - fail: (record, settings, details) -> - App.Event.trigger('notify', { - type: 'error' - msg: App.i18n.translateContent('Bulk action stopped %s!', settings.error) - }) ) return @@ -259,21 +242,13 @@ class App.TicketOverview extends App.Controller #console.log "perform action #{action} with id #{id} on ", $(item).val() ticket = App.Ticket.find($(item).val()) ticket.group_id = id - ticket.ajax().update( - ticket.attributes() - # this option will prevent callbacks and invalid data states in case of an error - failResponseNoTrigger: true + ticket.save( done: (r) => @batchCountIndex++ # refresh view after all tickets are proceeded if @batchCountIndex == @batchCount App.Event.trigger('overview:fetch') - fail: (record, settings, details) -> - App.Event.trigger('notify', { - type: 'error' - msg: App.i18n.translateContent('Bulk action stopped %s!', error) - }) ) return diff --git a/app/assets/javascripts/app/lib/spine/ajax.coffee b/app/assets/javascripts/app/lib/spine/ajax.coffee index f04409b71..491bea7ea 100644 --- a/app/assets/javascripts/app/lib/spine/ajax.coffee +++ b/app/assets/javascripts/app/lib/spine/ajax.coffee @@ -234,12 +234,11 @@ class Singleton extends Base failResponse: (options) => (xhr, statusText, error, settings) => - if options.failResponseNoTrigger isnt true - switch settings.type - when 'POST' then @createFailed() - when 'DELETE' then @destroyFailed() - # add errors to calllback - @record.trigger('ajaxError', @record, xhr, statusText, error, settings) + switch settings.type + when 'POST' then @createFailed() + when 'DELETE' then @destroyFailed() + # add errors to calllback + @record.trigger('ajaxError', @record, xhr, statusText, error, settings) #options.fail?.call(@record, settings) detailsRaw = xhr.responseText @@ -247,8 +246,7 @@ class Singleton extends Base details = JSON.parse(detailsRaw) options.fail?.call(@record, settings, details) - if options.failResponseNoTrigger isnt true - @record.trigger('destroy', @record) + @record.trigger('destroy', @record) # /add errors to calllback createFailed: -> From 678b5ada31af9aaafe8fbd741be9cabcac82886c Mon Sep 17 00:00:00 2001 From: Mantas Masalskis Date: Wed, 20 Oct 2021 18:54:33 +0200 Subject: [PATCH 08/90] Fixes #3810 - Custom date and datetime attributes are filled with dates on creation of tickets/users after update from 4.1 to 5.x. --- .../object_manager_attribute.coffee | 4 +- app/models/object_manager/attribute.rb | 7 +-- .../object_manager/attribute/set_defaults.rb | 25 ++++++----- ...e_3810_custom_date_attribute_no_default.rb | 18 ++++++++ public/assets/tests/form.js | 2 +- ...0_custom_date_attribute_no_default_spec.rb | 21 +++++++++ spec/factories/object_manager_attribute.rb | 26 ++++++++--- .../attribute/set_defaults_spec.rb | 44 +++++++++++++++---- spec/system/system/object_manager_spec.rb | 26 +++++++++++ 9 files changed, 139 insertions(+), 34 deletions(-) create mode 100644 db/migrate/20211020131134_issue_3810_custom_date_attribute_no_default.rb create mode 100644 spec/db/migrate/issue_3810_custom_date_attribute_no_default_spec.rb diff --git a/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee index 316515828..5212bc356 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/object_manager_attribute.coffee @@ -244,7 +244,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi params: params ) configureAttributes = [ - { name: 'data_option::diff', display: 'Default time Diff (minutes)', tag: 'integer', null: false, default: 24 }, + { name: 'data_option::diff', display: 'Default time Diff (minutes)', tag: 'integer', null: true }, ] datetimeDiff = new App.ControllerForm( model: @@ -258,7 +258,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi @date: (item, localParams, params) -> configureAttributes = [ - { name: 'data_option::diff', display: 'Default time Diff (hours)', tag: 'integer', null: false, default: 24 }, + { name: 'data_option::diff', display: 'Default time Diff (hours)', tag: 'integer', null: true }, ] dateDiff = new App.ControllerForm( model: diff --git a/app/models/object_manager/attribute.rb b/app/models/object_manager/attribute.rb index d3071e5d0..503a2e9f0 100644 --- a/app/models/object_manager/attribute.rb +++ b/app/models/object_manager/attribute.rb @@ -932,12 +932,7 @@ is certain attribute used by triggers, overviews or schedulers [{ failed: local_data_option[:future].nil?, message: 'must have boolean value for :future' }, { failed: local_data_option[:past].nil?, - message: 'must have boolean value for :past' }, - { failed: local_data_option[:diff].nil?, - message: 'must have integer value for :diff (in hours)' }] - when 'date' - [{ failed: local_data_option[:diff].nil?, - message: 'must have integer value for :diff (in days)' }] + message: 'must have boolean value for :past' }] else [] end diff --git a/app/models/object_manager/attribute/set_defaults.rb b/app/models/object_manager/attribute/set_defaults.rb index e8fb41a78..915033565 100644 --- a/app/models/object_manager/attribute/set_defaults.rb +++ b/app/models/object_manager/attribute/set_defaults.rb @@ -15,25 +15,30 @@ class ObjectManager method_name = "#{attr}=" return if !record.respond_to? method_name - return if record.send(attr).present? + return if record.send("#{attr}_came_from_user?") record.send method_name, build_value(config) end def build_value(config) - case config[:data_type] - when 'date' - config[:diff].days.from_now - when 'datetime' - config[:diff].hours.from_now - else - config[:default] - end + method_name = "build_value_#{config[:data_type]}" + + return send(method_name, config) if respond_to?(method_name, true) + + config[:default] + end + + def build_value_date(config) + config[:diff]&.days&.from_now + end + + def build_value_datetime(config) + config[:diff]&.hours&.from_now&.change(usec: 0, sec: 0) end def attributes_for(record) query = ObjectManager::Attribute.active.editable.for_object(record.class) - cache_key = "#{query.cache_key}/attribute_defaults" + cache_key = "#{query.cache_key_with_version}/attribute_defaults" Rails.cache.fetch cache_key do query diff --git a/db/migrate/20211020131134_issue_3810_custom_date_attribute_no_default.rb b/db/migrate/20211020131134_issue_3810_custom_date_attribute_no_default.rb new file mode 100644 index 000000000..edde66611 --- /dev/null +++ b/db/migrate/20211020131134_issue_3810_custom_date_attribute_no_default.rb @@ -0,0 +1,18 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +class Issue3810CustomDateAttributeNoDefault < ActiveRecord::Migration[6.0] + def up + return if !Setting.exists?(name: 'system_init_done') + + ObjectManager::Attribute + .where(data_type: %i[date datetime]) + .each { |elem| update_single(elem) } + end + + def update_single(elem) + elem.data_option[:diff] = nil + elem.save! + rescue => e + Rails.logger.error e + end +end diff --git a/public/assets/tests/form.js b/public/assets/tests/form.js index 3eb0468d9..544010390 100644 --- a/public/assets/tests/form.js +++ b/public/assets/tests/form.js @@ -1125,7 +1125,7 @@ test("object manager form 1", function() { params = App.ControllerForm.params(el) var test_params = { data_option: { - diff: 24, + diff: null, future: true, past: true }, diff --git a/spec/db/migrate/issue_3810_custom_date_attribute_no_default_spec.rb b/spec/db/migrate/issue_3810_custom_date_attribute_no_default_spec.rb new file mode 100644 index 000000000..46cefe948 --- /dev/null +++ b/spec/db/migrate/issue_3810_custom_date_attribute_no_default_spec.rb @@ -0,0 +1,21 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +require 'rails_helper' + +RSpec.describe Issue3810CustomDateAttributeNoDefault, type: :db_migration, db_strategy: :reset_all do + before :all do # rubocop:disable RSpec/BeforeAfterAll + create('object_manager_attribute_date', name: 'rspec_date', default: 24) + create('object_manager_attribute_datetime', name: 'rspec_datetime', default: 24) + + ObjectManager::Attribute.migration_execute + end + + after :all do # rubocop:disable RSpec/BeforeAfterAll + ObjectManager::Attribute.where('name LIKE ?', 'rspec_%').destroy_all + end + + it 'unsets diff migration' do + migrate + expect(create(:ticket)).to have_attributes(rspec_date: nil, rspec_datetime: nil) + end +end diff --git a/spec/factories/object_manager_attribute.rb b/spec/factories/object_manager_attribute.rb index 02732b88e..f7847ed50 100644 --- a/spec/factories/object_manager_attribute.rb +++ b/spec/factories/object_manager_attribute.rb @@ -41,6 +41,8 @@ FactoryBot.define do end factory :object_manager_attribute_text, parent: :object_manager_attribute do + default { '' } + data_type { 'input' } data_option do { @@ -48,7 +50,7 @@ FactoryBot.define do 'maxlength' => 200, 'null' => true, 'translate' => false, - 'default' => default || '', + 'default' => default, 'options' => {}, 'relation' => '', } @@ -56,10 +58,12 @@ FactoryBot.define do end factory :object_manager_attribute_integer, parent: :object_manager_attribute do + default { 0 } + data_type { 'integer' } data_option do { - 'default' => default || 0, + 'default' => default, 'min' => 0, 'max' => 9999, } @@ -67,10 +71,12 @@ FactoryBot.define do end factory :object_manager_attribute_boolean, parent: :object_manager_attribute do + default { false } + data_type { 'boolean' } data_option do { - default: default || false, + default: default, options: { true => 'yes', false => 'no', @@ -80,34 +86,40 @@ FactoryBot.define do end factory :object_manager_attribute_date, parent: :object_manager_attribute do + default { 24 } + name { 'date_attribute' } data_type { 'date' } data_option do { - 'diff' => default || 24, + 'diff' => default, 'null' => true, } end end factory :object_manager_attribute_datetime, parent: :object_manager_attribute do + default { 24 } + name { 'datetime_attribute' } data_type { 'datetime' } data_option do { 'future' => true, 'past' => true, - 'diff' => default || 24, + 'diff' => default, 'null' => true, } end end factory :object_manager_attribute_select, parent: :object_manager_attribute do + default { '' } + data_type { 'select' } data_option do { - 'default' => default || '', + 'default' => default, 'options' => { 'key_1' => 'value_1', 'key_2' => 'value_2', @@ -124,6 +136,8 @@ FactoryBot.define do end factory :object_manager_attribute_tree_select, parent: :object_manager_attribute do + default { '' } + data_type { 'tree_select' } data_option do { diff --git a/spec/models/object_manager/attribute/set_defaults_spec.rb b/spec/models/object_manager/attribute/set_defaults_spec.rb index 9e5a1cd3e..7fb9a98fe 100644 --- a/spec/models/object_manager/attribute/set_defaults_spec.rb +++ b/spec/models/object_manager/attribute/set_defaults_spec.rb @@ -2,18 +2,21 @@ require 'rails_helper' +DEFAULT_VALUES = { + text: 'rspec', + boolean: true, + date: 1, + datetime: 12, + integer: 123, + select: 'key_1' +}.freeze + RSpec.describe ObjectManager::Attribute::SetDefaults, type: :model do describe 'setting default', db_strategy: :reset_all do before :all do # rubocop:disable RSpec/BeforeAfterAll - { - text: 'rspec', - boolean: true, - date: 1, - datetime: 12, - integer: 123, - select: 'key_1' - }.each do |key, value| + DEFAULT_VALUES.each do |key, value| create("object_manager_attribute_#{key}", name: "rspec_#{key}", default: value) + create("object_manager_attribute_#{key}", name: "rspec_#{key}_no_default", default: nil) end create('object_manager_attribute_text', name: 'rspec_empty', default: '') @@ -77,7 +80,7 @@ RSpec.describe ObjectManager::Attribute::SetDefaults, type: :model do end it 'datetime is set' do - freeze_time + travel_to Time.current.change(usec: 0, sec: 0) expect(example.rspec_datetime).to eq 12.hours.from_now end @@ -89,5 +92,28 @@ RSpec.describe ObjectManager::Attribute::SetDefaults, type: :model do expect(example.rspec_select).to eq 'key_1' end end + + context 'when overriding default to empty value' do + subject(:example) do + params = DEFAULT_VALUES.keys.each_with_object({}) { |elem, memo| memo["rspec_#{elem}"] = nil } + create :ticket, params + end + + DEFAULT_VALUES.each_key do |elem| + it "#{elem} is empty" do + expect(example.send("rspec_#{elem}")).to be_nil + end + end + end + + context 'when default is not set' do + subject(:example) { create :ticket } + + DEFAULT_VALUES.each_key do |elem| + it "#{elem} is empty" do + expect(example.send("rspec_#{elem}_no_default")).to be_nil + end + end + end end end diff --git a/spec/system/system/object_manager_spec.rb b/spec/system/system/object_manager_spec.rb index 3fa859bdc..d69ef5eb1 100644 --- a/spec/system/system/object_manager_spec.rb +++ b/spec/system/system/object_manager_spec.rb @@ -489,4 +489,30 @@ RSpec.describe 'System > Objects', type: :system do end end end + + context 'when creating with no diff' do + before do + visit '/#system/object_manager' + page.find('.js-new').click + + in_modal disappears: false do + fill_in 'Name', with: 'nodiff' + fill_in 'Display', with: 'NoDiff' + end + end + + it 'date attribute' do + page.find('select[name=data_type]').select('Date') + fill_in 'Default time Diff (hours)', with: '' + + expect { page.find('.js-submit').click }.to change(ObjectManager::Attribute, :count).by(1) + end + + it 'datetime attribute' do + page.find('select[name=data_type]').select('Datetime') + fill_in 'Default time Diff (minutes)', with: '' + + expect { page.find('.js-submit').click }.to change(ObjectManager::Attribute, :count).by(1) + end + end end From 4f94b01d56f7c9d926c0c4dcf87c2361dda2e69b Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Thu, 21 Oct 2021 13:22:06 +0200 Subject: [PATCH 09/90] Fixes #3818 - Owner should get cleared if not listed in changed group. --- app/models/core_workflow/attributes.rb | 7 ++---- spec/system/ticket/update_spec.rb | 4 ++-- spec/system/ticket/zoom_spec.rb | 32 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/app/models/core_workflow/attributes.rb b/app/models/core_workflow/attributes.rb index 7005155af..5640cf851 100644 --- a/app/models/core_workflow/attributes.rb +++ b/app/models/core_workflow/attributes.rb @@ -238,13 +238,10 @@ class CoreWorkflow::Attributes end def saved_attribute_value(attribute) - saved_attribute_value = saved_only&.try(attribute[:name]) # special case for owner_id - if saved_only&.class == Ticket && attribute[:name] == 'owner_id' && saved_attribute_value == 1 - saved_attribute_value = nil - end + return if saved_only&.class == Ticket && attribute[:name] == 'owner_id' - saved_attribute_value + saved_only&.try(attribute[:name]) end end diff --git a/spec/system/ticket/update_spec.rb b/spec/system/ticket/update_spec.rb index 09cb8cfe1..3f56e6420 100644 --- a/spec/system/ticket/update_spec.rb +++ b/spec/system/ticket/update_spec.rb @@ -322,10 +322,10 @@ RSpec.describe 'Ticket Update', type: :system do end context 'when group will be changed' do - let(:user) { create(:user) } + let(:user) { User.find_by(email: 'agent1@example.com') } let(:ticket) { create(:ticket, group: group, owner: user) } - it 'check that owner is resetet after group change' do + it 'check that owner resets after group change' do visit "#ticket/zoom/#{ticket.id}" expect(page).to have_field('owner_id', with: user.id) diff --git a/spec/system/ticket/zoom_spec.rb b/spec/system/ticket/zoom_spec.rb index 7f48eb9db..53199f58d 100644 --- a/spec/system/ticket/zoom_spec.rb +++ b/spec/system/ticket/zoom_spec.rb @@ -2235,4 +2235,36 @@ RSpec.describe 'Ticket zoom', type: :system do expect(page).to have_selector('form.article-add.is-open') end end + + context 'Owner should get cleared if not listed in changed group #3818', authenticated_as: :authenticate do + let(:group1) { create(:group) } + let(:group2) { create(:group) } + let(:agent1) { create(:agent) } + let(:agent2) { create(:agent) } + let(:ticket) { create(:ticket, group: group1, owner: agent1) } + + def authenticate + agent1.group_names_access_map = { + group1.name => 'full', + group2.name => %w[read change overview] + } + agent2.group_names_access_map = { + group1.name => 'full', + group2.name => 'full', + } + agent1 + end + + before do + visit "#ticket/zoom/#{ticket.id}" + end + + it 'does clear agent1 on select of group 2' do + select group2.name, from: 'Group' + wait(5).until { page.find('select[name=owner_id]').value != agent1.id.to_s } + expect(page.find('select[name=owner_id]').value).to eq('') + expect(page.all('select[name=owner_id] option').map(&:value)).not_to include(agent1.id.to_s) + expect(page.all('select[name=owner_id] option').map(&:value)).to include(agent2.id.to_s) + end + end end From d9df6de2343cb39b5a8f33bcabc65b308061d81b Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Tue, 26 Oct 2021 12:14:35 +0200 Subject: [PATCH 10/90] Fixes #3815 - When looking for customers, it is no longer possible to change into organizations. --- .../controllers/organization_profile.coffee | 2 +- .../controllers/widget/organization.coffee | 2 +- ..._object_organization_autocompletion.coffee | 42 ++++++++++++--- .../app/models/organization.coffee | 2 +- .../item_organization_members.jst.eco | 7 ++- .../app/views/popover/organization.jst.eco | 2 +- spec/system/ticket/create_spec.rb | 52 +++++++++++++++++++ 7 files changed, 97 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/app/controllers/organization_profile.coffee b/app/assets/javascripts/app/controllers/organization_profile.coffee index 65db8b5c1..ad7890c68 100644 --- a/app/assets/javascripts/app/controllers/organization_profile.coffee +++ b/app/assets/javascripts/app/controllers/organization_profile.coffee @@ -153,7 +153,7 @@ class Object extends App.ControllerObserver elLocal.find('.js-userList').html(members) ) - if @organization.member_ids.length < @memberLimit + if @organization.member_ids.length <= @memberLimit @el.find('.js-showMoreMembers').parent().addClass('hidden') else @el.find('.js-showMoreMembers').parent().removeClass('hidden') diff --git a/app/assets/javascripts/app/controllers/widget/organization.coffee b/app/assets/javascripts/app/controllers/widget/organization.coffee index dc90b7611..574490cde 100644 --- a/app/assets/javascripts/app/controllers/widget/organization.coffee +++ b/app/assets/javascripts/app/controllers/widget/organization.coffee @@ -33,7 +33,7 @@ class App.WidgetOrganization extends App.Controller elLocal.find('.js-userList').html(members) ) - if @organization.member_ids.length < @memberLimit + if @organization.member_ids.length <= @memberLimit @el.find('.js-showMoreMembers').parent().addClass('hidden') else @el.find('.js-showMoreMembers').parent().removeClass('hidden') diff --git a/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee b/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee index ec51e6ca0..fa1cf4f2c 100644 --- a/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee +++ b/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee @@ -14,6 +14,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller 'click': 'stopPropagation' 'change .js-objectId': 'executeCallback' 'click .js-remove': 'removeThisToken' + 'click .js-showMoreMembers': 'showMoreMembers' elements: '.recipientList': 'recipientList' @@ -251,14 +252,42 @@ class App.ObjectOrganizationAutocompletion extends App.Controller objectCount: objectCount ) + showMoreMembers: (e) -> + @preventDefaultAndStopPropagation(e) + + memberElement = $(e.target).closest('.js-showMoreMembers') + oldMemberLimit = memberElement.attr('organization-member-limit') + newMemberLimit = (parseInt(oldMemberLimit / 25) + 1) * 25 + memberElement.attr('organization-member-limit', newMemberLimit) + + @renderMembers(memberElement, oldMemberLimit, newMemberLimit) + + renderMembers: (element, fromMemberLimit, toMemberLimit) -> + id = element.closest('.recipientList-organizationMembers').attr('organization-id') + organization = App.Organization.find(id) + + # only first 10 members else we would need more ajax requests + organization.members(fromMemberLimit, toMemberLimit, (users) => + for user in users + element.before(@buildObjectItem(user)) + + if element.closest('ul').hasClass('is-shown') + @showOrganizationMembers(undefined, element.closest('ul')) + ) + + if organization.member_ids.length <= toMemberLimit + element.addClass('hidden') + else + element.removeClass('hidden') + buildOrganizationMembers: (organization) => - organizationMemebers = $( App.view(@templateOrganizationItemMembers)( + organizationMembers = $( App.view(@templateOrganizationItemMembers)( organization: organization ) ) - if organization[@referenceAttribute] - for objectId in organization[@referenceAttribute] - object = App[@objectSingle].fullLocal(objectId) - organizationMemebers.append(@buildObjectItem(object)) + + @renderMembers(organizationMembers.find('.js-showMoreMembers'), 0, 10) + + organizationMembers buildObjectItem: (object) => icon = @objectIcon @@ -404,8 +433,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller e.stopPropagation() listEntry = $(e.currentTarget) - organizationId = listEntry.data('organization-id') - + organizationId = listEntry.data('organization-id') || listEntry.attr('organization-id') @organizationList = @$("[organization-id=#{ organizationId }]") return if !@organizationList.get(0) diff --git a/app/assets/javascripts/app/models/organization.coffee b/app/assets/javascripts/app/models/organization.coffee index 2189289f7..867189ba2 100644 --- a/app/assets/javascripts/app/models/organization.coffee +++ b/app/assets/javascripts/app/models/organization.coffee @@ -36,7 +36,7 @@ Using **Organisations** you can **group** customers. This has among others two i userResult = -> users = [] for user_id in member_ids - user = App.User.find(user_id) + user = App.User.fullLocal(user_id) continue if !user users.push(user) return users diff --git a/app/assets/javascripts/app/views/generic/object_search/item_organization_members.jst.eco b/app/assets/javascripts/app/views/generic/object_search/item_organization_members.jst.eco index eb5c080d6..3af3aba7c 100644 --- a/app/assets/javascripts/app/views/generic/object_search/item_organization_members.jst.eco +++ b/app/assets/javascripts/app/views/generic/object_search/item_organization_members.jst.eco @@ -4,4 +4,9 @@ <%- @Icon('arrow-left') %> <%- @T('Back') %>
                              - \ No newline at end of file + +
                            1. +
                              + <%- @T('show more') %> +
                              + diff --git a/app/assets/javascripts/app/views/popover/organization.jst.eco b/app/assets/javascripts/app/views/popover/organization.jst.eco index a8f40ba47..02572e6c3 100644 --- a/app/assets/javascripts/app/views/popover/organization.jst.eco +++ b/app/assets/javascripts/app/views/popover/organization.jst.eco @@ -3,7 +3,7 @@ <% if @object.member_ids: %>
                              - +
                              <% end %> diff --git a/spec/system/ticket/create_spec.rb b/spec/system/ticket/create_spec.rb index 021661059..57fbbc787 100644 --- a/spec/system/ticket/create_spec.rb +++ b/spec/system/ticket/create_spec.rb @@ -665,4 +665,56 @@ RSpec.describe 'Ticket Create', type: :system do expect(Ticket.last.pending_time).to be nil end end + + describe 'When looking for customers, it is no longer possible to change into organizations #3815' do + before do + visit 'ticket/create' + + # modal reaper ;) + sleep 3 + end + + context 'when less than 10 customers' do + let(:organization) { Organization.first } + + it 'has no show more option' do + find('[name=customer_id_completion]').fill_in with: 'zam' + expect(page).to have_selector("li.js-organization[data-organization-id='#{organization.id}']") + page.find("li.js-organization[data-organization-id='#{organization.id}']").click + expect(page).to have_selector("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li.js-showMoreMembers.hidden", visible: :all) + end + end + + context 'when more than 10 customers', authenticated_as: :authenticate do + def authenticate + customers + true + end + + let(:organization) { create(:organization, name: 'Zammed') } + let(:customers) do + create_list(:customer, 50, organization: organization) + end + + it 'does paginate through organization' do + find('[name=customer_id_completion]').fill_in with: 'zam' + expect(page).to have_selector("li.js-organization[data-organization-id='#{organization.id}']") + page.find("li.js-organization[data-organization-id='#{organization.id}']").click + wait(5).until { page.all("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li", visible: :all).count == 12 } # 10 users + back + show more button + + expect(page).to have_selector("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li.js-showMoreMembers[organization-member-limit='10']") + scroll_into_view('li.js-showMoreMembers') + page.find("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li.js-showMoreMembers").click + wait(5).until { page.all("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li", visible: :all).count == 27 } # 25 users + back + show more button + + expect(page).to have_selector("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li.js-showMoreMembers[organization-member-limit='25']") + scroll_into_view('li.js-showMoreMembers') + page.find("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li.js-showMoreMembers").click + wait(5).until { page.all("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li", visible: :all).count == 52 } # 50 users + back + show more button + + scroll_into_view('li.js-showMoreMembers') + expect(page).to have_selector("ul.recipientList-organizationMembers[organization-id='#{organization.id}'] li.js-showMoreMembers.hidden", visible: :all, wait: 20) + end + end + end end From 59cfc7c69adc57977038192459120433546679ec Mon Sep 17 00:00:00 2001 From: Dominik Klein Date: Wed, 27 Oct 2021 20:10:17 +0200 Subject: [PATCH 11/90] Maintenance: Improved authentication helper for developers. --- ...maintenance_remove_active_ldap_sessions.rb | 12 ++++++++ lib/auth/backend/base.rb | 5 ++++ lib/auth/backend/developer.rb | 7 +++++ lib/auth/backend/internal.rb | 1 - ...enance_remove_active_ldap_sessions_spec.rb | 30 +++++++++++++++++++ spec/lib/auth_spec.rb | 28 +++++++++++++++++ 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20211028072158_maintenance_remove_active_ldap_sessions.rb create mode 100644 spec/db/migrate/maintenance_remove_active_ldap_sessions_spec.rb diff --git a/db/migrate/20211028072158_maintenance_remove_active_ldap_sessions.rb b/db/migrate/20211028072158_maintenance_remove_active_ldap_sessions.rb new file mode 100644 index 000000000..b88b561d9 --- /dev/null +++ b/db/migrate/20211028072158_maintenance_remove_active_ldap_sessions.rb @@ -0,0 +1,12 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +class MaintenanceRemoveActiveLdapSessions < ActiveRecord::Migration[6.0] + def change + return if !Setting.exists?(name: 'system_init_done') + + # Only relevant for when ldap integration is used. + return if !Setting.get('ldap_integration') + + ActiveRecord::SessionStore::Session.destroy_all + end +end diff --git a/lib/auth/backend/base.rb b/lib/auth/backend/base.rb index ff3c584c1..347dfa75f 100644 --- a/lib/auth/backend/base.rb +++ b/lib/auth/backend/base.rb @@ -21,6 +21,7 @@ class Auth end def valid? + return false if password.blank? && password_required? return false if !perform? authenticated? @@ -28,6 +29,10 @@ class Auth private + def password_required? + true + end + def perform? raise NotImplementedError end diff --git a/lib/auth/backend/developer.rb b/lib/auth/backend/developer.rb index 4c63b5a83..4f6637fad 100644 --- a/lib/auth/backend/developer.rb +++ b/lib/auth/backend/developer.rb @@ -20,6 +20,13 @@ class Auth false end + # No password required for developer mode and test environment. + # + # @returns [Boolean] false + def password_required? + false + end + # Overwrites the default behaviour to check for a allowed environment. # # @returns [Boolean] true if the environment is development or test. diff --git a/lib/auth/backend/internal.rb b/lib/auth/backend/internal.rb index 459c45c13..e398a1be0 100644 --- a/lib/auth/backend/internal.rb +++ b/lib/auth/backend/internal.rb @@ -21,7 +21,6 @@ class Auth # # @returns [Boolean] true if a internal password for the user is present. def perform? - return false if password.blank? return false if !user.verified && user.source == 'signup' user.password.present? diff --git a/spec/db/migrate/maintenance_remove_active_ldap_sessions_spec.rb b/spec/db/migrate/maintenance_remove_active_ldap_sessions_spec.rb new file mode 100644 index 000000000..0d4a81bff --- /dev/null +++ b/spec/db/migrate/maintenance_remove_active_ldap_sessions_spec.rb @@ -0,0 +1,30 @@ +# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ + +require 'rails_helper' + +RSpec.describe MaintenanceRemoveActiveLdapSessions, type: :db_migration do + before do + 5.times do + ActiveRecord::SessionStore::Session.create( + session_id: SecureRandom.hex(16), + data: SecureRandom.base64(10) + ) + end + end + + context 'without ldap integration' do + before { Setting.set('ldap_integration', false) } + + it 'does not delete existing sessions' do + expect { migrate }.not_to change(ActiveRecord::SessionStore::Session, :count) + end + end + + context 'with ldap integration' do + before { Setting.set('ldap_integration', true) } + + it 'deletes all existing sessions' do + expect { migrate }.to change(ActiveRecord::SessionStore::Session, :count).to(0) + end + end +end diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb index 7bedbcefb..a95c26acd 100644 --- a/spec/lib/auth_spec.rb +++ b/spec/lib/auth_spec.rb @@ -168,6 +168,30 @@ RSpec.describe Auth do allow(Ldap::User).to receive(:new).with(any_args).and_return(ldap_user) end + shared_examples 'check empty password' do + before do + # Remove adapter from auth developer setting, to avoid execution for this test case, because of special empty + # password handling in adapter. + Setting.set('auth_developer', {}) + end + + context 'with empty password string' do + let(:password) { '' } + + it 'returns false' do + expect(instance.valid?).to be false + end + end + + context 'when password is nil' do + let(:password) { nil } + + it 'returns false' do + expect(instance.valid?).to be false + end + end + end + context 'with a ldap user without internal password' do let(:user) { create(:user, source: 'Ldap') } let(:password) { password_ldap } @@ -197,6 +221,8 @@ RSpec.describe Auth do expect { instance.valid? }.not_to change { user.reload.login_failed } end end + + include_examples 'check empty password' end context 'with a ldap user which also has a internal password' do @@ -238,6 +264,8 @@ RSpec.describe Auth do expect(instance.valid?).to be true end end + + include_examples 'check empty password' end end end From ce5b9bd9dee967c5bc351ffac885680f71d24f7a Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 28 Oct 2021 11:17:24 +0200 Subject: [PATCH 12/90] Updated to 5.0.2 --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d4dfd0dc..91951ea4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Change Log +## [5.0.2](https://github.com/zammad/zammad/tree/5.0.2) (2021-10-28) +[Full Changelog](https://github.com/zammad/zammad/compare/5.0.1...5.0.2) + +**Implemented enhancements:** + +**Fixed bugs:** +- When looking for customers, it is no longer possible to change into organizations [3815](https://github.com/zammad/zammad/issues/3815) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] +- Owner should get cleared if not listed in changed group [3818](https://github.com/zammad/zammad/issues/3818) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)] +- Custom date and datetime attributes are filled with dates on creation of tickets/users after update from 4.1 to 5.x [3810](https://github.com/zammad/zammad/issues/3810) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] +- ActionController::UnknownHttpMethod FRAGE [3807](https://github.com/zammad/zammad/issues/3807) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[macros](https://github.com/zammad/zammad/labels/macros)] +- Remote change of the group id does show it falsly as user change and not render the new value to the ticket [3801](https://github.com/zammad/zammad/issues/3801) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)] +- Adding private keys allows adding certificates [3727](https://github.com/zammad/zammad/issues/3727) [[bug](https://github.com/zammad/zammad/labels/bug)] +- Able to create custom fields for existing relation (e. g. ticket.state) - will lead to non bootable Zammad [3811](https://github.com/zammad/zammad/issues/3811) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)] +- Sort order group_by broken (alphabetical) [3800](https://github.com/zammad/zammad/issues/3800) [[bug](https://github.com/zammad/zammad/labels/bug)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[regression](https://github.com/zammad/zammad/labels/regression)] +- Ticket owner selection is not updated if owner selection should be empty [3809](https://github.com/zammad/zammad/issues/3809) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)] +- Chat can't be closed after timeout [2471](https://github.com/zammad/zammad/issues/2471) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)] +- Support workflow mechanism to do pending reminder state hide pending time use case [3790](https://github.com/zammad/zammad/issues/3790) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] +- Cache.clear in postinstall.sh throws ugly errors on fresh installations [3808](https://github.com/zammad/zammad/issues/3808) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[regression](https://github.com/zammad/zammad/labels/regression)] +- Example payload in webhook view leads to 500 error [3794](https://github.com/zammad/zammad/issues/3794) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)] +- OS package upgrade fails (activity_stream_object_id) [3797](https://github.com/zammad/zammad/issues/3797) [[bug](https://github.com/zammad/zammad/labels/bug)] + + ## [5.0.1](https://github.com/zammad/zammad/tree/5.0.1) (2021-10-08) [Full Changelog](https://github.com/zammad/zammad/compare/5.0.0...5.0.1) From ad12ad4e01f5e6d1d58da019107b66e562ae463c Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 28 Oct 2021 11:19:02 +0200 Subject: [PATCH 13/90] Removed not needed headline. --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91951ea4b..91dc2b0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,6 @@ ## [5.0.2](https://github.com/zammad/zammad/tree/5.0.2) (2021-10-28) [Full Changelog](https://github.com/zammad/zammad/compare/5.0.1...5.0.2) -**Implemented enhancements:** - **Fixed bugs:** - When looking for customers, it is no longer possible to change into organizations [3815](https://github.com/zammad/zammad/issues/3815) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] - Owner should get cleared if not listed in changed group [3818](https://github.com/zammad/zammad/issues/3818) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)] From 3f1057c1905f987ff42233e85a07fbb5c1ffbfdf Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Fri, 29 Oct 2021 14:46:14 +0200 Subject: [PATCH 14/90] Fixes #3827 - Ticket create screen will loose attachments by time. --- .../controllers/_ui_element/richtext.coffee | 4 +++ .../controllers/agent_ticket_create.coffee | 9 +++++ spec/system/ticket/create_spec.rb | 35 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee b/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee index b7b027ae7..757ebaa03 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/richtext.coffee @@ -27,6 +27,8 @@ class App.UiElement.richtext renderFile = (file) -> item.find('.attachments').append(App.view('generic/attachment_item')(file)) attachments.push file + if form.richTextUploadRenderCallback + form.richTextUploadRenderCallback(attribute, attachments) if params && params.attachments for file in params.attachments @@ -54,6 +56,8 @@ class App.UiElement.richtext return if item.id.toString() is id.toString() item ) + if form.richTextUploadDeleteCallback + form.richTextUploadDeleteCallback(attribute, attachments) form_id = item.closest('form').find('[name=form_id]').val() diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee index 954aa8859..a720ce630 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee @@ -282,6 +282,13 @@ class App.TicketCreate extends App.Controller return if !@formMeta App.QueueManager.run(@queueKey) + updateTaskManagerAttachments: (attribute, attachments) => + taskData = App.TaskManager.get(@taskKey) + return if _.isEmpty(taskData) + + taskData.attachments = attachments + App.TaskManager.update(@taskKey, taskData) + render: (template = {}) -> return if !@formMeta # get params @@ -376,6 +383,8 @@ class App.TicketCreate extends App.Controller handlersConfig: handlersTunnel params: params taskKey: @taskKey + richTextUploadRenderCallback: @updateTaskManagerAttachments + richTextUploadDeleteCallback: @updateTaskManagerAttachments ) @controllerFormCreateBottom = new App.ControllerForm( el: @$('.ticket-form-bottom') diff --git a/spec/system/ticket/create_spec.rb b/spec/system/ticket/create_spec.rb index 57fbbc787..46a002012 100644 --- a/spec/system/ticket/create_spec.rb +++ b/spec/system/ticket/create_spec.rb @@ -717,4 +717,39 @@ RSpec.describe 'Ticket Create', type: :system do end end end + + describe 'Ticket create screen will loose attachments by time #3827' do + before do + visit 'ticket/create' + end + + it 'does not loose attachments on rerender of the ui' do + # upload two files + page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box')) + await_empty_ajax_queue + page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail002.box')) + await_empty_ajax_queue + wait(5).until { page.all('div.attachment-delete.js-delete', visible: :all).count == 2 } + expect(page).to have_text('mail001.box') + expect(page).to have_text('mail002.box') + + # remove last file + begin + page.evaluate_script("$('div.attachment-delete.js-delete:last').click()") # not interactable + rescue # Lint/SuppressedException + # because its not interactable it also + # returns this weird exception for the jquery + # even tho it worked fine + end + await_empty_ajax_queue + wait(5).until { page.all('div.attachment-delete.js-delete', visible: :all).count == 1 } + expect(page).to have_text('mail001.box') + expect(page).to have_no_text('mail002.box') + + # simulate rerender b + page.evaluate_script("App.Event.trigger('ui:rerender')") + expect(page).to have_text('mail001.box') + expect(page).to have_no_text('mail002.box') + end + end end From 2c26e82354db1261cacf6b087cabe5c5589e204b Mon Sep 17 00:00:00 2001 From: Mantas Masalskis Date: Tue, 2 Nov 2021 12:33:18 +0100 Subject: [PATCH 15/90] Fixes #3140 - Update time SLAs escalates tickets with agent response. --- .../_application_controller/form.coffee | 8 +- .../controllers/_ui_element/sla_times.coffee | 61 ++++- .../javascripts/app/controllers/sla.coffee | 19 -- .../app/lib/mixins/view_helpers.coffee | 4 +- app/assets/javascripts/app/models/sla.coffee | 3 +- .../app/views/generic/sla_times.jst.eco | 46 +++- .../javascripts/app/views/sla/index.jst.eco | 12 +- app/assets/stylesheets/zammad.scss | 7 + app/models/core_workflow/custom/admin_sla.rb | 9 +- app/models/sla.rb | 10 + db/migrate/20120101000010_create_ticket.rb | 1 + .../20210614063039_sla_add_response_time.rb | 13 + lib/escalation.rb | 90 ++++++- public/assets/tests/form_extended.js | 78 ++++++ public/assets/tests/form_sla_times.js | 24 ++ spec/lib/escalation_spec.rb | 237 +++++++++++++++--- spec/models/calendar_spec.rb | 4 +- spec/models/core_workflow_spec.rb | 2 +- ..._escalation_calculation_impact_examples.rb | 12 +- spec/models/sla_spec.rb | 22 ++ ...cket_contact_attributes_impact_examples.rb | 6 +- spec/models/ticket/escalation_examples.rb | 16 +- spec/models/ticket_spec.rb | 8 +- spec/requests/ticket/escalation_spec.rb | 6 +- spec/system/manage/sla_spec.rb | 1 + 25 files changed, 585 insertions(+), 114 deletions(-) create mode 100644 db/migrate/20210614063039_sla_add_response_time.rb diff --git a/app/assets/javascripts/app/controllers/_application_controller/form.coffee b/app/assets/javascripts/app/controllers/_application_controller/form.coffee index 9d968be2e..eb9d6e69d 100644 --- a/app/assets/javascripts/app/controllers/_application_controller/form.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller/form.coffee @@ -418,11 +418,11 @@ class App.ControllerForm extends App.Controller if !@constructor.fieldIsMandatory(field_by_name) field_by_name.attr('required', true) - field_by_name.parents('.form-group').find('label span').html('*') + field_by_name.closest('.form-group').find('label span').html('*') field_by_name.closest('.form-group').addClass('is-required') if !@constructor.fieldIsMandatory(field_by_data) field_by_data.attr('required', true) - field_by_data.parents('.form-group').find('label span').html('*') + field_by_data.closest('.form-group').find('label span').html('*') field_by_data.closest('.form-group').addClass('is-required') optional: (name, el = @form) -> @@ -434,11 +434,11 @@ class App.ControllerForm extends App.Controller if @constructor.fieldIsMandatory(field_by_name) field_by_name.attr('required', false) - field_by_name.parents('.form-group').find('label span').html('') + field_by_name.closest('.form-group').find('label span').html('') field_by_name.closest('.form-group').removeClass('is-required') if @constructor.fieldIsMandatory(field_by_data) field_by_data.attr('required', false) - field_by_data.parents('.form-group').find('label span').html('') + field_by_data.closest('.form-group').find('label span').html('') field_by_data.closest('.form-group').removeClass('is-required') readonly: (name, el = @form) -> diff --git a/app/assets/javascripts/app/controllers/_ui_element/sla_times.coffee b/app/assets/javascripts/app/controllers/_ui_element/sla_times.coffee index 1a3fec95c..2bf72247d 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/sla_times.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/sla_times.coffee @@ -8,9 +8,11 @@ class App.UiElement.sla_times item = $( App.view('generic/sla_times')( attribute: attribute first_response_time: params.first_response_time + response_time: params.response_time update_time: params.update_time solution_time: params.solution_time first_response_time_in_text: @toText(params.first_response_time) + response_time_in_text: @toText(params.response_time) update_time_in_text: @toText(params.update_time) solution_time_in_text: @toText(params.solution_time) ) ) @@ -26,12 +28,16 @@ class App.UiElement.sla_times row = element.closest('tr') if element.prop('checked') row.addClass('is-active') + + if row.has('.js-updateTypeSelector').length > 0 && row.has('.js-updateTypeSelector:checked').length == 0 + row.find('.js-updateTypeSelector:first').prop('checked', true) else row.removeClass('is-active') # reset data item row.find('.js-timeConvertFrom').val('') row.find('.js-timeConvertTo').val('') + row.find('.js-updateTypeSelector').attr('checked', false) row.find('.help-inline').empty() row.removeClass('has-error') ) @@ -42,12 +48,16 @@ class App.UiElement.sla_times inText = element.val() row = element.closest('tr') - row.find('.js-activateRow').prop('checked', true) + + row + .find('.js-activateRow') + .prop('checked', true) + .trigger('change') + row.addClass('is-active') - element - .closest('td') - .find('.js-timeConvertTo') + row + .find("[name='#{element.data('name')}']") .val(@toMinutes(inText) || '') ) @@ -56,9 +66,19 @@ class App.UiElement.sla_times $(e.currentTarget).closest('tr').find('.checkbox-replacement').click() ) + # toggle update type on clicking around the element + item.find('.js-forward-radio').bind('click', (e) -> + elem = $(e.currentTarget).closest('p').find('.js-updateTypeSelector') + + elem.prop('checked', true) + elem.trigger('change') + ) + # focus time input on clicking surrounding cell item.find('.js-focus-input').bind('click', (e) -> - $(e.currentTarget).find('.form-control').focus() + $(e.currentTarget) + .find('.form-control:visible') + .focus() ) # show placeholder instead of 00:00 @@ -67,15 +87,36 @@ class App.UiElement.sla_times $(e.currentTarget).val('') ) + # switch update/response times when type is selected accordingly + item.find('.js-updateTypeSelector').bind('change', (e) -> + element = $(e.target) + row = element.closest('tr') + row.find('.js-activateRow').prop('checked', true) + row.addClass('is-active') + + row + .find('.js-timeConvertFrom') + .addClass('hidden') + .val('') + + row + .find('.js-timeConvertTo') + .val('') + + row + .find("[data-name='#{element.val()}_time']") + .removeClass('hidden') + ) + # set initial active/inactive rows item.find('.js-timeConvertFrom').each(-> row = $(@).closest('tr') checkbox = row.find('.js-activateRow') - if $(@).val() - checkbox.prop('checked', true) - row.addClass('is-active') - else - checkbox.prop('checked', false) + + return if !$(@).val() + + checkbox.prop('checked', true) + row.addClass('is-active') ) item diff --git a/app/assets/javascripts/app/controllers/sla.coffee b/app/assets/javascripts/app/controllers/sla.coffee index b3f50e1d3..1ab73b9d8 100644 --- a/app/assets/javascripts/app/controllers/sla.coffee +++ b/app/assets/javascripts/app/controllers/sla.coffee @@ -26,12 +26,6 @@ class Sla extends App.ControllerSubContent sortBy: 'name' ) for sla in slas - if sla.first_response_time - sla.first_response_time_in_text = @toText(sla.first_response_time) - if sla.update_time - sla.update_time_in_text = @toText(sla.update_time) - if sla.solution_time - sla.solution_time_in_text = @toText(sla.solution_time) sla.rules = App.UiElement.ticket_selector.humanText(sla.condition) sla.calendar = App.Calendar.find(sla.calendar_id) @@ -99,17 +93,4 @@ class Sla extends App.ControllerSubContent container: @el.closest('.content') ) - toText: (m) -> - m = parseInt(m) - return if !m - minutes = m % 60 - hours = Math.floor(m / 60) - - if minutes < 10 - minutes = "0#{minutes}" - if hours < 10 - hours = "0#{hours}" - - "#{hours}:#{minutes}" - App.Config.set('Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Sla, permission: ['admin.sla'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/lib/mixins/view_helpers.coffee b/app/assets/javascripts/app/lib/mixins/view_helpers.coffee index 64686fd3c..1ef955244 100644 --- a/app/assets/javascripts/app/lib/mixins/view_helpers.coffee +++ b/app/assets/javascripts/app/lib/mixins/view_helpers.coffee @@ -36,7 +36,7 @@ App.ViewHelpers = App.Utils.decimal(data, positions) # define time_duration / mm:ss / hh:mm:ss format helper - time_duration: (time) -> + time_duration: (time, show_seconds = true) -> return '' if !time return '' if isNaN(parseInt(time)) @@ -48,7 +48,7 @@ App.ViewHelpers = # Output like "1:01" or "4:03:59" or "123:03:59" mins = "0#{mins}" if mins < 10 secs = "0#{secs}" if secs < 10 - if hrs > 0 + if hrs > 0 && show_seconds return "#{hrs}:#{mins}:#{secs}" "#{mins}:#{secs}" diff --git a/app/assets/javascripts/app/models/sla.coffee b/app/assets/javascripts/app/models/sla.coffee index 824db43cf..5724ada1f 100644 --- a/app/assets/javascripts/app/models/sla.coffee +++ b/app/assets/javascripts/app/models/sla.coffee @@ -1,5 +1,5 @@ class App.Sla extends App.Model - @configure 'Sla', 'name', 'first_response_time', 'update_time', 'solution_time', 'condition', 'calendar_id' + @configure 'Sla', 'name', 'first_response_time', 'response_time', 'update_time', 'solution_time', 'condition', 'calendar_id' @extend Spine.Model.Ajax @url: @apiPath + '/slas' @configure_attributes = [ @@ -12,6 +12,7 @@ class App.Sla extends App.Model { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, { name: 'first_response_time',skipRendering: true }, + { name: 'response_time', skipRendering: true }, { name: 'update_time', skipRendering: true }, { name: 'solution_time', skipRendering: true }, ] diff --git a/app/assets/javascripts/app/views/generic/sla_times.jst.eco b/app/assets/javascripts/app/views/generic/sla_times.jst.eco index 98fe6ea21..0c4fcda89 100644 --- a/app/assets/javascripts/app/views/generic/sla_times.jst.eco +++ b/app/assets/javascripts/app/views/generic/sla_times.jst.eco @@ -6,7 +6,7 @@ <%- @T('Time') %> <%- @T('in hours') %> - +