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 →</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.