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.