PageSourceSearch

https://www.salmantinos.se/wp-content/plugins/sd-chatbot/assets/inline.js

js salmantinos.se collected 2026-10-10 05:18:56 UTC 16,919 bytes, 258 lines download raw bytes

1(function(){
2'use strict';
3// Fråga-sektionen [sd_fraga]. Pratar med samma salm_chat-endpoint som widgeten.
4
5var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
6var SPARK='<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l1.8 5.6L19.5 9l-5.7 1.6L12 16l-1.8-5.4L4.5 9l5.7-1.4z"/><path d="M19 15l.9 2.3 2.1.7-2.1.7L19 21l-.9-2.3-2.1-.7 2.1-.7z" opacity=".7"/></svg>';
7
8function esc(t){var d=document.createElement('div');d.appendChild(document.createTextNode(t));return d.innerHTML;}
9function linkify(t){
10    var s=esc(t);
11    s=s.replace(/\*\*(.+?)\*\*/g,'$1');
12    s=s.replace(/(^|[\s,;:(])(www\.[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}[^\s<>"()]*)/g,'$1https://$2');
13    s=s.replace(/(https?:\/\/[^\s<>"()]+?)([.,;:!?)]?)(?=\s|$)/g,function(_,u,p){
14        var label=u.replace(/^https?:\/\/(www\.)?/,'').replace(/\/$/,'');
15        return '<a href="'+u+'">'+label+'</a>'+p;
16    });
17    s=s.replace(/([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})/g,'<a href="mailto:$1">$1</a>');
18    return s.split(/\n{2,}/).map(function(p){return '<p>'+p.replace(/\n/g,'<br>')+'</p>';}).join('');
19}
20function detectLang(t){return /\b(the|is|are|what|how|price|do|you|have|hello|hi|can|please|thank)\b/i.test(t)?'en':'sv';}
21
22function init(root){
23    var ajax=root.getAttribute('data-ajax'),cartUrl=root.getAttribute('data-cart'),cartPage=root.getAttribute('data-cartpage')||'/varukorg/';
24    var ui=root.getAttribute('data-ui')||'inline',fixedCtx=root.getAttribute('data-ctx')||'',isPP=root.classList.contains('sdf--pp');
25    /* Produktsidan i mobilen: rutan flyttas upp under pris och artikelnummer, så att den
26       inte hamnar efter hela produkttexten. PÃ¥ dator ligger den kvar under produktbilden. */
27    if(isPP&&window.matchMedia&&window.matchMedia('(max-width:767px)').matches){
28        var desc=document.querySelector('.summary .woocommerce-product-details__short-description');
29        if(desc&&desc.parentNode)desc.parentNode.insertBefore(root,desc);
30    }
31    var words=[];try{words=JSON.parse(root.getAttribute('data-words'))||[];}catch(e){}
32    var form=root.querySelector('.sdf-form'),inp=root.querySelector('.sdf-q'),go=root.querySelector('.sdf-go');
33    var ex=root.querySelector('.sdf-ex'),conv=root.querySelector('.sdf-conv'),rot=root.querySelector('.sdf-rot');
34    var history=[],busy=false,lang='sv',noted=false,lastPids=[];
35    var sessionId='i'+Date.now().toString(36)+Math.random().toString(36).slice(2,6);
36    var customer=null;try{customer=JSON.parse(localStorage.getItem('salm_customer')||'null');}catch(e){}
37
38    /* Ordplatsen får det längsta ordets bredd, så att "Fråga oss om" står still
39       när orden byts (annars flyttar centreringen hela raden vid varje byte). */
40    if(rot&&words.length>1){
41        var fit=function(){
42            var em=rot.querySelector('em');if(!em)return;
43            var probe=em.cloneNode(false);probe.className='';probe.style.cssText='position:absolute;visibility:hidden;white-space:nowrap;animation:none';
44            rot.appendChild(probe);var max=0;
45            words.forEach(function(w){probe.textContent=w;max=Math.max(max,probe.getBoundingClientRect().width);});
46            rot.removeChild(probe);rot.style.minWidth=Math.ceil(max)+'px';
47        };
48        fit();if(document.fonts&&document.fonts.ready)document.fonts.ready.then(fit);
49        window.addEventListener('resize',fit);
50    }
51    /* växlande ord, bara när sektionen syns */
52    if(rot&&words.length>1&&!reduce){
53        var wi=0,timer=null;
54        var swap=function(){var old=rot.querySelector('em');if(!old)return;old.className='out';
55            setTimeout(function(){wi=(wi+1)%words.length;rot.innerHTML='<em class="in">'+esc(words[wi])+'</em>';},330);};
56        var start=function(){if(!timer)timer=setInterval(swap,2600);},stop=function(){clearInterval(timer);timer=null;};
57        if('IntersectionObserver' in window){new IntersectionObserver(function(es){es[0].isIntersecting?start():stop();}).observe(root);}else start();
58    }
59
60    function post(data,cb){
61        var body=new FormData();body.append('action','salm_chat');
62        for(var k in data)body.append(k,data[k]);
63        fetch(ajax,{method:'POST',body:body,credentials:'same-origin'})
64            .then(function(r){return r.json();}).then(cb)
65            .catch(function(){cb({success:false,data:{}});});
66    }
67
68    // Mätning (metrics.php): klick och varukorg. sendBeacon överlever att sidan byts.
69    function track(ev,ref){
70        try{var f=new FormData();f.append('action','salm_track');f.append('sid',sessionId);f.append('ev',ev);f.append('ref',String(ref||''));
71            if(navigator.sendBeacon)navigator.sendBeacon(ajax,f);else fetch(ajax,{method:'POST',body:f,credentials:'same-origin',keepalive:true});}catch(e){}
72    }
73    // Reserv för beställningskopplingen om WooCommerce-sessionen inte följer med (30 dagar).
74    function rememberCart(id){
75        try{var m=document.cookie.match(/(?:^|; )salm_chat_p=([^;]*)/),ids=m?decodeURIComponent(m[1]).split('.'):[];
76            if(ids.indexOf(String(id))===-1)ids.push(String(id));
77            var exp='; max-age='+(30*86400)+'; path=/; SameSite=Lax';
78            document.cookie='salm_chat_p='+encodeURIComponent(ids.slice(-30).join('.'))+exp;
79            document.cookie='salm_chat_s='+sessionId+exp;}catch(e){}
80    }
81    conv.addEventListener('click',function(e){
82        var a=e.target.closest('a');if(!a)return;
83        if(a.classList.contains('sdf-rc')){track('click_recipe',a.getAttribute('data-id'));}
84        else if(a.closest('.sdf-pc')){track('click_product',a.closest('.sdf-pc').getAttribute('data-id'));}
85    });
86
87    function addToCart(id,btn,done,bulk){
88        var next=function(){if(done)done();};
89        if(!cartUrl){if(!bulk)location.href=btn.getAttribute('data-url');return;}
90        btn.classList.add('is-busy');
91        var body=new FormData();body.append('product_id',id);body.append('quantity','1');
92        fetch(cartUrl,{method:'POST',body:body,credentials:'same-origin'})
93            .then(function(r){return r.json();})
94            .then(function(res){
95                btn.classList.remove('is-busy');
96                if(!res||res.error){if(!bulk&&res&&res.product_url)location.href=res.product_url;next();return;}
97                btn.classList.add('is-done');btn.textContent='✓';btn.setAttribute('aria-label','Tillagd i varukorgen');
98                track('add_cart',id);rememberCart(id);
99                var tx=btn.closest('.sdf-tx');
100                if(tx&&!tx.querySelector('.sdf-cartlink')){var l=document.createElement('a');l.className='sdf-cartlink';l.href=cartPage;l.textContent='Till varukorgen →';
101                    var all=tx.querySelector('.sdf-all');if(all)all.insertAdjacentElement('afterend',l);else tx.appendChild(l);}
102                // Uppdatera korgen i menyn + låt spårningen (GA/Meta) se köpet. Deras lyssnare
103                // läser produktdata från knappen och får inte kunna stoppa "Lägg alla"-kedjan.
104                try{if(window.jQuery){window.jQuery(document.body).trigger('added_to_cart',[res.fragments,res.cart_hash,window.jQuery(btn)]);}}catch(e){}
105                next();
106            })
107            .catch(function(){btn.classList.remove('is-busy');next();});
108    }
109
110    function recipes(tx,list){
111        if(!list||!list.length)return;
112        var lb=document.createElement('div');lb.className='sdf-lbl';lb.textContent=list.length>1?'Recept':'Receptet';tx.appendChild(lb);
113        var w=document.createElement('div');w.className='sdf-rx';
114        list.forEach(function(r,j){
115            var a=document.createElement('a');a.className='sdf-rc';a.href=r.url;a.setAttribute('data-id',r.id);a.style.animationDelay=(j*70)+'ms';
116            a.innerHTML=(r.img?'<img src="'+esc(r.img)+'" alt="" loading="lazy">':'')+'<span><b>'+esc(r.title)+'</b>'+(r.meta?'<small>'+esc(r.meta)+'</small>':'')+'<em>Till receptet →</em></span>';
117            w.appendChild(a);
118        });
119        tx.appendChild(w);
120    }
121
122    function cards(tx,list,labelled){
123        if(!list||!list.length)return;
124        if(labelled){var lb=document.createElement('div');lb.className='sdf-lbl';lb.textContent='Varor';tx.appendChild(lb);}
125        // Rutnät: 4 i bredd och högst 2 rader på dator, 2 i bredd på mobil.
126        list=list.slice(0,8);
127        var row=document.createElement('div');row.className='sdf-row';
128        list.forEach(function(p,j){
129            var c=document.createElement('div');c.className='sdf-pc';c.setAttribute('data-id',p.id);c.style.animationDelay=(j*70)+'ms';
130            c.innerHTML='<a href="'+esc(p.url)+'"><img src="'+esc(p.img)+'" alt="" loading="lazy"><span>'+esc(p.name)+'</span></a>'+
131                (p.stock?'<span class="sdf-pr">'+esc(p.label)+'</span>':'<span class="sdf-oos">Slut i webblager</span>')+
132                (p.buy?'<button type="button" class="sdf-plus" aria-label="Lägg '+esc(p.name)+' i varukorgen" data-id="'+p.id+'" data-product_id="'+p.id+'" data-product_sku="'+esc(p.sku||'')+'" data-quantity="1" data-url="'+esc(p.url)+'">+</button>'
133                      :'<a class="sdf-view" href="'+esc(p.url)+'">Visa</a>');
134            row.appendChild(c);
135        });
136        tx.appendChild(row);
137        row.addEventListener('click',function(e){
138            var b=e.target.closest('.sdf-plus');if(!b||b.classList.contains('is-done')||b.classList.contains('is-busy'))return;
139            addToCart(b.getAttribute('data-id'),b);
140        });
141        var buy=list.filter(function(p){return p.buy;});
142        if(buy.length>1){
143            var sum=buy.reduce(function(s,p){return s+p.price;},0);
144            var all=document.createElement('button');all.type='button';all.className='sdf-all';
145            all.textContent='Lägg alla i varukorgen · '+Math.round(sum)+' kr';
146            all.onclick=function(){
147                all.disabled=true;all.textContent='Lägger i varukorgen…';
148                var btns=[].slice.call(row.querySelectorAll('.sdf-plus:not(.is-done)'));
149                (function next(){var b=btns.shift();if(!b){all.textContent='✓ Tillagt i varukorgen';return;}addToCart(b.getAttribute('data-id'),b,next,true);})();
150            };
151            tx.appendChild(all);
152        }
153    }
154
155    function reveal(tx,reply,after){
156        var html=linkify(reply);
157        if(reduce){tx.innerHTML=html;after();return;}
158        var parts=reply.split(/(\s+)/),i=0,acc='';
159        tx.innerHTML='<p></p>';var p=tx.firstChild;
160        (function step(){
161            if(i>=parts.length){tx.innerHTML=html;after();return;}
162            var n=Math.min(parts.length,i+4);for(;i<n;i++)acc+=parts[i];
163            p.textContent=acc;setTimeout(step,24);
164        })();
165    }
166
167    function stream(data,tx,onDone,onFallback){
168        if(!window.fetch||!window.TextDecoder||!window.ReadableStream){onFallback();return;}
169        var body=new FormData();body.append('action','salm_chat_stream');
170        for(var k in data)body.append(k,data[k]);
171        var text='',got=false,ended=false,live=null;
172        var paint=function(){
173            if(!live){tx.innerHTML='';live=document.createElement('div');tx.appendChild(live);}
174            live.innerHTML=text.replace(/^\s+/,'').split(/\n{2,}/).map(function(p){return '<p>'+esc(p).replace(/\n/g,'<br>')+'</p>';}).join('');
175            var last=live.lastChild;if(last){var c=document.createElement('span');c.className='sdf-caret';
175last.appendChild(c);}
176        };
177        var fail=function(){if(ended)return;ended=true;got?onDone({success:false,data:{message:'Svaret avbröts. Försök igen.'}}):onFallback();};
178        var handle=function(block){
179            var ev='message',d='';
180            block.split('\n').forEach(function(l){if(l.indexOf('event:')===0)ev=l.slice(6).trim();else if(l.indexOf('data:')===0)d+=l.slice(5).trim();});
181            if(!d)return;var j;try{j=JSON.parse(d);}catch(e){return;}
182            if(ev==='delta'){text+=j.t;got=true;paint();}
183            else if(ev==='done'){ended=true;onDone({success:true,data:j});}
184            else if(ev==='error'){if(j.partial){ended=true;onDone({success:false,data:{message:j.message}});}else fail();}
185        };
186        fetch(ajax,{method:'POST',body:body,credentials:'same-origin'}).then(function(r){
187            var ct=r.headers.get('content-type')||'';
188            if(!r.ok||ct.indexOf('text/event-stream')===-1||!r.body){
189                return r.json().then(function(j){if(j&&j.data&&j.data.fallback)fail();else{ended=true;onDone(j||{});}}).catch(fail);
190            }
191            var rd=r.body.getReader(),dec=new TextDecoder(),buf='';
192            (function pump(){rd.read().then(function(x){
193                if(x.done){fail();return;}
194                buf+=dec.decode(x.value,{stream:true});var i;
195                while((i=buf.indexOf('\n\n'))!==-1){handle(buf.slice(0,i));buf=buf.slice(i+2);}
196                pump();
197            }).catch(fail);})();
198        }).catch(fail);
199    }
200
201    function ask(text){
202        text=(text||'').trim();if(!text||busy)return;
203        busy=true;go.disabled=true;
204        if(detectLang(text)==='en')lang='en';
205        if(ex)ex.style.display='none';
206        conv.hidden=false;
207        // Nyaste frågan + svaret överst, direkt under skrivfältet.
208        var turn=document.createElement('div');turn.className='sdf-turn';
209        var u=document.createElement('div');u.className='sdf-u';u.textContent=text;turn.appendChild(u);
210        var b=document.createElement('div');b.className='sdf-b';
211        b.innerHTML='<div class="sdf-ic">'+SPARK+'</div><div class="sdf-tx"><div class="sdf-think">'+(lang==='en'?'Searching the range…':'Letar i sortimentet…')+'</div></div>';
212        turn.appendChild(b);
213        conv.insertBefore(turn,conv.firstChild);
214        try{form.scrollIntoView({block:'nearest',behavior:reduce?'auto':'smooth'});}catch(e){}
215        inp.value='';
216        history.push({role:'user',content:text});
217        var data={messages:JSON.stringify(history),session_id:sessionId,language:lang,ui:ui};
218        var ctxIds=(fixedCtx?[String(fixedCtx)]:[]).concat(lastPids.map(String)).filter(function(v,i,a){return a.indexOf(v)===i;});
219        if(ctxIds.length)data.ctx=ctxIds.join(',');
220        if(customer&&customer.name){data.customer_name=customer.name;data.customer_visits=customer.visits||1;}
221        var tx=b.querySelector('.sdf-tx');
222        var finish=function(res,streamed){
223            var done=function(){busy=false;go.disabled=false;};
224            if(res&&res.success&&res.data&&res.data.reply){
225                var reply=res.data.reply,contact=reply.indexOf('[CONTACT_FORM]')!==-1,resto=reply.indexOf('[RESTO_LEAD]')!==-1;
226                reply=reply.replace(/\[RESTO_LEAD\]|\[CONTACT_FORM\]/g,'').trim();
227                history.push({role:'assistant',content:reply});
228                var after=function(){
229                    if(res.data.products&&res.data.products.length)lastPids=res.data.products.map(function(p){return p.id;});
230                    recipes(tx,res.data.recipes);
231                    cards(tx,res.data.products,res.data.recipes&&res.data.recipes.length);
232                    if(contact&&document.getElementById('salm-contact-btn')){
233                        var c=document.createElement('button');c.type='button';c.className='sdf-act';c.textContent='Bli kontaktad';
234                        c.onclick=function(){document.getElementById('salm-contact-btn').click();};tx.appendChild(c);
235                    }
236                    if(resto){var a=document.createElement('a');a.className='sdf-act';a.href='/bli-kund/';a.textContent='Bli företagskund';tx.appendChild(a);}
237                    if(!noted){noted=true;var n=document.createElement('div');n.className='sdf-note';
238                        n.innerHTML='Svaren skrivs av AI. Är något oklart, ring oss på <a href="tel:086732024">08-673 20 24</a>.';conv.appendChild(n);}
239                    else{var nn=conv.querySelector('.sdf-note');
239if(nn)conv.appendChild(nn);}
240                    done();
241                };
242                if(streamed){tx.innerHTML=linkify(reply);after();}else reveal(tx,reply,after);
243            } else {
244                var msg=(res&&res.data&&res.data.message)||'Något gick fel. Försök igen eller ring oss på 08-673 20 24.';
245                tx.innerHTML='<p>'+esc(msg)+'</p>';history.pop();done();
246            }
247        };
248        // Svaret visas medan det skrivs (salm_chat_stream); går det inte används vanliga salm_chat.
249        stream(data,tx,function(res){finish(res,true);},function(){post(data,function(res){finish(res,false);});});
250    }
251
252    form.addEventListener('submit',function(e){e.preventDefault();ask(inp.value);});
253    if(ex)ex.addEventListener('click',function(e){var q=e.target.closest('[data-q]');if(q)ask(q.getAttribute('data-q'));});
254}
255
256function boot(){[].forEach.call(document.querySelectorAll('.sdf'),init);}
257if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot);else boot();
258})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.