1/* 2 Store locator widget pro wBTCb.cz 3 âââââââââââââââââââââââââââââââââ 4 Automaticky naÄÃtá: 5 - Mapy.cz API klÃÄ z /api/mapy-key (z env promÄnné nastavené v Cloudflare) 6 - Prodejny GECO z /api/prodejny (z KV napumpovaného Workerem geco-sync) 7 - Vlastnà BTM ze statického /btm.json 8 9 Vyžaduje Leaflet + Leaflet.markercluster (naÄÃtajà se z CDN v <head>). 10*/ 11(function () { 12 "use strict"; 13 14 const URL_KEY = "/api/mapy-key"; 15 const URL_PRODEJNY = "/api/prodejny"; 16 const URL_BTM = "/btm.json"; 17 18 // i18n fallback â pokud stránka nemá window.LANG/I18N, použijà se defaulty 19 function t(key, fallback) { 20 try { 21 if (window.I18N && window.LANG && window.I18N[window.LANG] && window.I18N[window.LANG][key]) { 22 return window.I18N[window.LANG][key]; 23 } 24 } catch (_) {} 25 return fallback; 26 } 27 28 let map = null; 29 let clGeco = null; 30 let clBtm = null; 31 let loaded = false; 32 let apiKey = ""; 33 34 function esc(s) { 35 return String(s == null ? "" : s).replace(/[&<>"']/g, c => 36 ({ "&":"&", "<":"<", ">":">", '"':""", "'":"'" }[c])); 37 } 38 39 function makeIcon(kind) { 40 return L.divIcon({ 41 className: "", 42 html: `<div class="mmap-pin ${kind}"></div>`, 43 iconSize: [22, 22], 44 iconAnchor: [11, 22], 45 popupAnchor: [0, -22], 46 }); 47 } 48 49 function popupProdejna(p) { 50 return `<div class="mmap-pop-title">${esc(p.name)}</div> 51 <div class="mmap-pop-addr">${esc(p.street)}<br>${esc(p.zip)} ${esc(p.city)}</div> 52 ${p.phone ? `<div class="mmap-pop-tel"><a href="tel:${esc(p.phone)}">${esc(p.phone)}</a></div>` : ""}`; 53 } 54 55 function popupBtm(b) { 56 const note = b.note ? `<div class="mmap-pop-note mmap-lang-cs">${esc(b.note)}</div>` : ""; 57 const note_en = b.note_en ? `<div class="mmap-pop-note mmap-lang-en">${esc(b.note_en)}</div>` : ""; 58 return `<div class="mmap-pop-title">${esc(b.name || "BTM")}</div> 59 ${b.address ? `<div class="mmap-pop-addr">${esc(b.address)}</div>` : ""} 60 ${note}${note_en}`; 61 } 62 63 function showError(msg) { 64 const container = document.getElementById("mmap"); 65 if (!container) return; 66 container.innerHTML = ` 67 <div id="mmap-loading"> 68 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 9v4M12 17h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg> 69 <span>${esc(msg)}</span> 70 </div>`; 71 } 72 73 async function initMap() { 74 const container = document.getElementById("mmap"); 75 if (!container || !window.L) return; 76 77 // NaÄteme API klÃÄ nejdÅÃv â bez nÄj nejede ani dlaždice 78 try { 79 const r = await fetch(URL_KEY); 80 const data = await r.json(); 81 apiKey = data.key || ""; 82 } catch (e) { 83 console.error("mmap: failed to load api key", e); 84 } 85 86 if (!apiKey) { 87 showError(t("map_err_key", "Mapa nenà nakonfigurovaná. Nastavte MAPY_API_KEY v Cloudflare.")); 88 return; 89 } 90 91 // Vyprázdnit loading state a postavit kontejner pro Leaflet 92 container.innerHTML = ` 93 <div id="mmap-canvas" style="width:100%;height:100%;"></div> 94 95 `; 96 97 map = L.map("mmap-canvas", { 98 preferCanvas: true, 99 zoomControl: true, 100 attributionControl: true, 101 }).setView([49.8, 15.5], 7); 102 // Odstranà Leaflet prefix (vlajka je souÄástà Leaflet prefixu, ne Mapy.com) 103 map.attributionControl.setPrefix(''); 104 105 L.tileLayer( 106 `https://api.mapy.cz/v1/maptiles/basic/256/{z}/{x}/{y}?apikey=${apiKey}`, 107 { 108 minZoom: 0, 109 maxZoom: 19, 110 attribution: '<a href="https://mapy.com/" target="_blank">© Mapy.com</a>', 111 crossOrigin: true, 112 } 113 ).addTo(map); 114 115 clGeco = L.markerClusterGroup({ showCoverageOnHover: false, maxClusterRadius: 50 }); 116 clBtm = L.markerClusterGroup({ showCoverageOnHover: false, maxClusterRadius: 50 }); 117 118 loadData(); 119 // Attribution odkazy v nové záložce 120 setTimeout(function() { 121 document.querySelectorAll('.leaflet-control-attribution a').forEach(function(a) { 122 a.target = '_blank'; 123 a.rel = 'noopener'; 124 }); 125 }, 500); 126 127 } 128 129 async function loadData() { 130 const iconGeco = makeIcon("geco"); 131 const iconBtm = makeIcon("btm"); 132 133 let prodejny = [], btm = []; 134 try {
135 const [rP, rB] = await Promise.all([ 136 fetch(URL_PRODEJNY).then(r => r.ok ? r.json() : []), 137 fetch(URL_BTM).then(r => r.ok ? r.json() : []), 138 ]); 139 prodejny = Array.isArray(rP) ? rP : []; 140 btm = Array.isArray(rB) ? rB : []; 141 } catch (e) { 142 console.error("mmap: data load failed", e); 143 } 144 145 let pCount = 0, bCount = 0; 146 147 for (const p of prodejny) { 148 if (typeof p.lat !== "number" || typeof p.lng !== "number") continue; 149 L.marker([p.lat, p.lng], { icon: iconGeco }) 150 .bindPopup(popupProdejna(p)) 151 .addTo(clGeco); 152 pCount++; 153 } 154 for (const b of btm) { 155 if (typeof b.lat !== "number" || typeof b.lng !== "number") continue; 156 L.marker([b.lat, b.lng], { icon: iconBtm }) 157 .bindPopup(popupBtm(b)) 158 .addTo(clBtm); 159 bCount++; 160 } 161 162 loaded = true; 163 applyFilter("all"); 164 165 166 } 167 168 function applyFilter(which) { 169 if (!map || !loaded) return; 170 if (map.hasLayer(clGeco)) map.removeLayer(clGeco); 171 if (map.hasLayer(clBtm)) map.removeLayer(clBtm); 172 if (which === "all" || which === "geco") map.addLayer(clGeco); 173 if (which === "all" || which === "btm") map.addLayer(clBtm); 174 } 175 176 // Globálnà mf() â volá se z HTML onclick="mf(this, 'all|btm|geco')" 177 // PÅepÃná aktivnà stav tlaÄÃtek a filtruje mapu. 178 window.mf = function (btn, which) { 179 document.querySelectorAll(".mf").forEach(b => b.classList.remove("on")); 180 btn.classList.add("on"); 181 applyFilter(which || "all"); 182 }; 183 184 // Lazy-init: mapu inicializuj, až když sekce vstoupà do viewportu. 185 function setupLazyInit() { 186 const el = document.getElementById("mmap"); 187 if (!el) return; 188 189 function whenReady(cb) { 190 if (window.L && window.L.markerClusterGroup) { cb(); return; } 191 setTimeout(() => whenReady(cb), 50); 192 } 193 194 if ("IntersectionObserver" in window) { 195 const io = new IntersectionObserver((entries) => { 196 for (const e of entries) { 197 if (e.isIntersecting) { 198 io.disconnect(); 199 whenReady(initMap); 200 return; 201 } 202 } 203 }, { rootMargin: "200px" }); 204 io.observe(el); 205 } else { 206 whenReady(initMap); 207 } 208 } 209 210 if (document.readyState === "loading") { 211 document.addEventListener("DOMContentLoaded", setupLazyInit); 212 } else { 213 setupLazyInit(); 214 } 215})();
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.