PageSourceSearch

https://www.yate.cz/user/documents/upload/porovnani.js

js yate.cz collected 2026-10-07 22:59:03 UTC 12,045 bytes, 258 lines download raw bytes

1(function(){
2  "use strict";
3
4  var STORE_KEY = "yateCmpItems";
5  var FEED_KEY  = "yateCmpFeed";
6  var DOM_KEY   = "yateCmpData";   // záložní data produktů přečtená z DOM (i vyprodané)
7  var MAX       = 4;
8  var CACHE_H   = 6;
9  var FEED_URL  = "https://www.yate.cz/seznam/export/products.xml?hash=3GMTrnzhovXlt3kEei8MgdpI";
10  var HIDE      = ["EAN"];   // parametry, které se v porovnání nezobrazují
11
12  /* ---------- normalizace názvu parametru pro párování ---------- */
13  // klíč = malá písmena, bez koncové dvojtečky, bez přebytečných mezer
14  // → "Barva:", "barva", " Barva " se spárují do jednoho řádku
15  function paramKey(name){
16    return String(name||"")
17      .replace(/\s+/g," ")     // sjednotit mezery
18      .replace(/\s*:\s*$/,"")  // odstranit koncovou dvojtečku
19      .trim()
20      .toLowerCase();
21  }
22  // hezký název pro zobrazení (bez koncové dvojtečky, ořezané mezery)
23  function paramLabel(name){
24    return String(name||"").replace(/\s+/g," ").replace(/\s*:\s*$/,"").trim();
25  }
26
27  /* ---------- localStorage (výběr = pole SKU) ---------- */
28  function load(){ try{ return JSON.parse(localStorage.getItem(STORE_KEY))||[]; }catch(e){ return []; } }
29  function save(l){ try{ localStorage.setItem(STORE_KEY, JSON.stringify(l)); }catch(e){} }
30  function has(sku){ return load().indexOf(sku)>-1; }
31
32  /* ---------- DOM cache (data produktů uložená při kliknutí Porovnat) ---------- */
33  function loadDom(){ try{ return JSON.parse(localStorage.getItem(DOM_KEY))||{}; }catch(e){ return {}; } }
34  function saveDom(map){ try{ localStorage.setItem(DOM_KEY, JSON.stringify(map)); }catch(e){} }
35  function storeDomProduct(p){ if(!p||!p.sku) return; var m=loadDom(); m[p.sku]=p; saveDom(m); }
36
37  /* ---------- SKU aktuálního produktu ---------- */
38  function currentSku(){
39    var d=document.querySelector(".p-detail"); if(!d) return null;
40    var m=d.querySelector('meta[itemprop="sku"]');
41    return m ? m.getAttribute("content") : null;
42  }
43
44  /* ---------- čtení dat produktu z DOM (funguje i pro vyprodané, co nejsou ve feedu) ---------- */
45  function readProductFromDom(){
46    var d=document.querySelector(".p-detail"); if(!d) return null;
47    function meta(prop){ var m=d.querySelector('meta[itemprop="'+prop+'"]'); return m?m.getAttribute("content"):""; }
48    var sku=meta("sku"); if(!sku) return null;
49
50    // parametry z tabulky detail-parameters (mimo GPSR)
51    var params=[], seen={};
52    var tables=document.querySelectorAll(".p-param-block .detail-parameters:not
52(.detail-parameters__gpsr)");
53    tables.forEach(function(tbl){
54      tbl.querySelectorAll("tr").forEach(function(tr){
55        if(tr.classList.contains("duplicate-param")) return;
56        var th=tr.querySelector("th .row-header-label"), td=tr.querySelector("td");
57        if(!th||!td) return;
58        var name=paramLabel(th.textContent);
59        var val =td.textContent.replace(/\s+/g," ").trim();
60        if(!name||!val||seen[name]) return;
61        if(HIDE.indexOf(name)>-1) return;
62        seen[name]=true; params.push({name:name, value:val});
63      });
64    });
65
66    var priceEl=d.querySelector(".price-final");
67    return {
68      sku:   sku,
69      name:  (meta("name")||document.title).replace(/\s+/g," ").trim(),
70      url:   location.pathname,
71      img:   meta("image"),
72      price: priceEl? priceEl.textContent.replace(/\s+/g," ").replace(/\s*Kč\s*$/,"").trim() : "",
73      params: params
74    };
75  }
76
77  /* ---------- feed: načtení + cache ---------- */
78  function getFeed(cb){
79    try{
80      var c=JSON.parse(localStorage.getItem(FEED_KEY)||"null");
81      if(c && (Date.now()-c.t)<CACHE_H*3600*1000){ cb(c.d); return; }
82    }catch(e){}
83    fetch(FEED_URL).then(function(r){ return r.text(); }).then(function(xml){
84      var data=parseFeed(xml);
85      try{ localStorage.setItem(FEED_KEY, JSON.stringify({t:Date.now(), d:data})); }catch(e){}
86      cb(data);
87    }).catch(function(){ cb({}); });
88  }
89  function parseFeed(xml){
90    var doc=new DOMParser().parseFromString(xml,"text/xml");
91    var items=doc.getElementsByTagName("SHOPITEM"), map={};
92    for(var i=0;i<items.length;i++){
93      var it=items[i];
94      var get=function(tag){ var e=it.getElementsByTagName(tag)[0]; return e?e.textContent.trim():""; };
95      var sku=get("PRODUCTNO")||get("ITEM_ID");
96      if(!sku || map[sku]) continue;
97      var params=[], pel=it.getElementsByTagName("PARAM");
98      for(var j=0;j<pel.length;j++){
99        var n=pel[j].getElementsByTagName("PARAM_NAME")[0];
100        var v=pel[j].getElementsByTagName("VAL")[0];
101        if(n&&v){ var nm=paramLabel(n.textContent); if(nm && HIDE.indexOf(nm)===-1) params.push({name:nm, value:v.textContent.trim()}); }
102      }
103      map[sku]={ sku:sku, name:get("PRODUCTNAME"), url:get("URL"), img:get("IMGURL"), price:get("PRICE_VAT"), params:params };
104    }
105    return map;
106  }
107
108  /* ---------- tlačítko POROVNAT do .c-btns ---------- */
109  function addButton(){
110    var row=document.querySelector(".c-btns");
111    var sku=currentSku();
112    if(!row || !sku || row.querySelector(".yc-cmp")) return;
113
114    var b=document.createElement("button");
115    b.type="button";
116    b.className="c-btn yc-cmp"+(has(sku)?" yc-on":"");
117    // ŽÁDNÝ data-tab → tvůj switchTab ho ignoruje, i kdyby ho zachytil
118    b.textContent=has(sku)?"V POROVNÁNÍ ✓":"POROVNAT";
119    b.addEventListener("click", function(ev){
120      ev.stopPropagation();           // pojistka proti tab handleru
121      var l=load();
122      if(has(sku)){ save(l.filter(function(x){return x!==sku;})); }
123      else{
124        if(l.length>=MAX){ alert("Porovnat lze nejvýše "+MAX+" produkty."); return; }
125        // uložíme data z DOM (funguje i pro vyprodané, co nejsou ve feedu)
126        var dom=readProductFromDom();
127        if(dom){ storeDomProduct(dom); }
128        l.push(sku); save(l);
129      }
130      b.classList.toggle("yc-on", has(sku));
131      b.textContent=has(sku)?"V POROVNÁNÍ ✓":"POROVNAT";
132      renderBar();
133    });
134    row.appendChild(b);
135  }
136  function waitButtons(n){
137    if(document.querySelector(".c-btns")){ addButton(); return; }
138    if(n>0) setTimeout(function(){ waitButtons(n-1); }, 300);
139  }
140
141  /* ---------- resolver: vezmi data z feedu, jinak z DOM cache ---------- */
142  function resolveProduct(sku, feed){
143    if(feed && feed[sku]) return feed[sku];   // primárně feed (nejaktuálnější)
144    var dom=loadDom();
145    if(dom[sku]) return dom[sku];             // záloha z DOM (i vyprodané)
146    return null;
147  }
148
149  /* ---------- plovoucí lišta ---------- */
150  var bar;
151  function renderBar(){
152    var l=load();
153    if(!bar){ bar=document.createElement("div"); bar.className="yc-bar"; document.body.appendChild(bar); }
154    if(!l.length){ bar.classList.remove("yc-visible"); return; }
155    getFeed(function(feed){
156      bar.innerHTML=
157        '<div class="yc-bar__thumbs">'+
158          l.map(function(sku){ var p=resolveProduct(sku,feed)||{img:"",name:sku};
159            return '<div class="yc-bar__thumb"><img src="'+p.img+'" alt=""><button type="button" data-r="'+encodeURIComponent(sku)+'" aria-label="Odebrat">×</button></div>'; }).join("")+
160        '</div>'+
161        '<span class="yc-bar__count">'+l.length+'/'+MAX+'</span>'+
162        '<a class="yc-bar__go">Porovnat &rarr;</a>'+
163        '<button type="button" class="yc-bar__clear"><span class="yc-bar__clear-txt">Vymazat</span><span class="yc-bar__clear-ico">🗑</span></button>';
164      bar.querySelector(".yc-bar__go").addEventListener("click", function(){ openOverlay(feed); });
165      bar.querySelector(".yc-bar__clear").addEventListener("click", clearAll);
166      bar.querySelectorAll("[data-r]").forEach(function(x){
167        x.addEventListener("click", function(){ removeSku(decodeURIComponent(x.getAttribute("data-r"))); });
168      });
169      bar.classList.add("yc-visible");
170    });
171  }
172  function removeSku(sku){
173    save(load().filter(function(s){ return s!==sku; }));
174    renderBar(); syncButton();
175    var ov=document.querySelector(".yc-ov"); if(ov){ ov.remove(); getFeed(openOverlay); }
176  }
177  function clearAll(){
178    save([]); renderBar(); syncButton();
179    var ov=document.querySelector(".yc-ov"); if(ov) ov.remove();
180  }
181  function syncButton(){
182    var b=document.querySelector(".yc-cmp"); var sku=currentSku();
183    if(b&&sku){ b.classList.toggle("yc-on", has(sku)); b.textContent=has(sku)?"V POROVNÁNÍ ✓":"POROVNAT"; }
184  }
185
186  /* ---------- overlay tabulka ---------- */
187  var showDiffOnly=false;
188  function openOverlay(feed){
189    var l=load(); if(!l.length) return;
190    var old=document.querySelector(".yc-ov"); if(old) old.remove();
191
192    var prods=l.map(function(sku){ return resolveProduct(sku,feed); }
192).filter(Boolean);
193    var ov=document.createElement("div"); ov.className="yc-ov";
194
195    if(!prods.length){
196      ov.innerHTML='<div class="yc-ov__box"><div class="yc-ov__head"><h2>Porovnání produktů</h2><button class="yc-ov__close" aria-label="Zavřít">×</button></div><div class="yc-loading">Data se načítají z feedu, zkuste to prosím za okamžik.</div></div>';
197      ov.addEventListener("click", function(e){ if(e.target===ov||e.target.classList.contains("yc-ov__close")) ov.remove(); });
198      document.body.appendChild(ov);
199      return;
200    }
201
202    // sjednocení parametrů podle klíče (Barva: = barva = Barva)
203    var order=[];           // pořadí klíčů dle prvního výskytu
204    var labelByKey={};      // klíč → hezký název pro zobrazení
205    prods.forEach(function(p){
206      p.params.forEach(function(pr){
207        var k=paramKey(pr.name);
208        if(!k) return;
209        if(order.indexOf(k)===-1){ order.push(k); labelByKey[k]=paramLabel(pr.name); }
210      });
211    });
212    function val(p,k){
213      var f=p.params.filter(function(x){ return paramKey(x.name)===k; })[0];
214      return f?f.value:null;
215    }
216
217    var head='<tr><th class="yc-pname"></th>'+
218      prods.map(function(p){
219        return '<td><a class="yc-prodname" href="'+(p.url||"#")+'">'+p.name+'</a>'+
220               '<img src="'+p.img+'" alt="">'+
221               (p.price?'<div class="yc-price">'+p.price+' Kč</div>':'')+
222               '<button type="button" class="yc-remove" data-r="'+encodeURIComponent(p.sku)+'">Odebrat</button></td>';
223      }).join("")+'</tr>';
224
225    var rows="";
226    order.forEach(function(k){
227      var vals=prods.map(function(p){ return val(p,k); });
228      var ne=vals.filter(function(v){ return v!=null&&v!==""; });
229      var diff=ne.length>1 && !ne.every(function(v){ return v===ne[0]; });
230      if(showDiffOnly && !diff) return;
231      rows+='<tr class="'+(diff?"yc-diff":"")+'"><td class="yc-pname">'+labelByKey[k]+'</td>'+
232        vals.map(function(v){ return '<td>'+(v!=null&&v!==""?v:'<span class="yc-empty-val">–</span>')+'</td>'; }).join("")+'</tr>';
233    });
234
235    ov.innerHTML=
236      '<div class="yc-ov__box">'+
237        '<div class="yc-ov__head"><h2>Porovnání produktů</h2><button class="yc-ov__close" aria-label="Zavřít">×</button></div>'+
238        '<p class="yc-ov__sub">Porovnáváte '+prods.length+' '+(prods.length<5?"produkty":"produktů")+'. Odlišné hodnoty jsou zvýrazněné.</p>'+
239        '<label class="yc-toggle"><input type="checkbox" class="yc-diff-cb" '+(showDiffOnly?"checked":"")+'> Zobrazit jen rozdíly</label>'+
240        '<div class="yc-scroll"><table class="yc-table"><thead>'+head+'</thead><tbody>'+rows+'</tbody></table></div>'+
241      '</div>';
242
243    ov.addEventListener("click", function(e){ if(e.target===ov||e.target.classList.contains("yc-ov__close")) ov.remove(); });
244    ov.querySelectorAll("[data-r]").forEach(function(x){
245      x.addEventListener("click", function(){ removeSku(decodeURIComponent(x.getAttribute("data-r"))); });
246    });
247    var cb=ov.querySelector(".yc-diff-cb");
248    if(cb) cb.addEventListener("change", function(){ showDiffOnly=cb.checked; ov.remove(); getFeed(openOverlay); });
249
250    document.body.appendChild(ov);
251  }
252
253  /* ---------- init ---------- */
254  function init(){ waitButtons(20); renderBar(); }
255  if(document.readyState==="loading"){ document.addEventListener("DOMContentLoaded", init); }
256  else{ init(); }
257
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.