1console.log("PSC script LOADED (TXT lists + cache)"); 2(function () { 3 // ================================================== 4 // OMEZENÃ: bÄžà pouze v ADMIN režimu (admin-bar) 5 // ================================================== 6 const ADMIN_ONLY = false; // <- pro zákaznÃky pÅepni na false 7 if (ADMIN_ONLY && !document.querySelector(".admin-bar")) return; 8 9 const PSC_STORAGE_KEY = "shoptet_psc_filter_v1"; 10 11 function savePscToStorage(psc) { 12 try { localStorage.setItem(PSC_STORAGE_KEY, psc); } catch (e) {} 13 } 14 function loadPscFromStorage() { 15 try { return localStorage.getItem(PSC_STORAGE_KEY) || ""; } catch (e) { return ""; } 16 } 17 18 const ALWAYS_AFTER_PSC = [445, 253, 409, 490, 228, 225, 510, 424, 436, 415, 231, 418, 451, 448, 507, 412, 526]; 19 20 const PSC_DEPENDENT_SHIPPINGS = { 21 // PÅÃKLADY: 22 251: { file: true }, // naÄte /psc/251.txt 23 77: { file: true } 24 }; 25 26 const ZONE_PRIORITY = []; // prázdné = nevynucuje auto-výbÄr zónové dopravy 27 28 const PSC_FILES_BASE_URL = "/user/documents/upload/AZStudio/scripts/PSCModul_Doprava/PSC/"; // napÅ. https://www.domena.cz/psc/ (může být i absolutnà URL) 29 30 31 const PSC_FILE_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h 32 const PSC_FILE_CACHE_PREFIX = "psc_shipping_list_v1:"; // localStorage prefix 33 34 // ================================================== 35 // HELPERS 36 // ================================================== 37 function normalizePsc(raw) { 38 if (!raw) return ""; 39 const digits = String(raw).replace(/\s+/g, "").replace(/\D+/g, ""); 40 return digits.length === 5 ? digits : ""; 41 } 42 43 function debounce(fn, ms) { 44 let t; 45 return function (...args) { 46 clearTimeout(t); 47 t = setTimeout(() => fn.apply(this, args), ms); 48 }; 49 } 50 51 function getShippingWrappers() { 52 const c = document.querySelector("#order-shipping-methods"); 53 if (!c) return []; 54 return Array.from(c.querySelectorAll(".radio-wrapper")); 55 } 56 57 function getShippingId(wrap) { 58 const input = wrap.querySelector("input[name='shippingId']"); 59 return input ? parseInt(input.value, 10) : null; 60 } 61 62 function getRadioById(id) { 63 return document.querySelector(`input[name="shippingId"][value="${id}"]`); 64 } 65 66 function getWrapperById(id) { 67 const r = getRadioById(id); 68 return r ? r.closest(".radio-wrapper") : null; 69 } 70 71 function isVisible(el) { 72 return !!el && el.style.display !== "none"; 73 } 74 75 function isPscInRanges(psc, ranges) { 76 return (ranges || []).some(([a, b]) => psc >= a && psc <= b); 77 } 78 79 // ================================================== 80 // TXT LIST CACHE (in-memory + localStorage TTL) 81 // ================================================== 82 const inMemoryListCache = new Map(); // id -> Promise<Set> 83 84 function storageKeyForId(id) { 85 return `${PSC_FILE_CACHE_PREFIX}${id}`; 86 } 87 88 function getCachedFromLocalStorage(id) { 89 try { 90 const raw = localStorage.getItem(storageKeyForId(id)); 91 if (!raw) return null; 92 const parsed = JSON.parse(raw); 93 if (!parsed || !parsed.ts || !Array.isArray(parsed.list)) return null; 94 if (Date.now() - parsed.ts > PSC_FILE_CACHE_TTL_MS) return null; 95 return new Set(parsed.list); 96 } catch (e) { 97 return null; 98 } 99 } 100 101 function saveCachedToLocalStorage(id, set) { 102 try { 103 const payload = { ts: Date.now(), list: Array.from(set) }; 104 localStorage.setItem(storageKeyForId(id), JSON.stringify(payload)); 105 } catch (e) {} 106 } 107 108 function resolveFileUrl(id, conf) { 109 if (conf && typeof conf.file === "string") return conf.file; 110 if (conf && conf.file === true) return `${PSC_FILES_BASE_URL}${id}.txt`; 111 // default: pokud file nenà definované, tak null 112 return null; 113 } 114 115 async function loadPscSetFromFile(id, conf) { 116 const url = resolveFileUrl(id, conf); 117 if (!url) return new Set(); 118 119 // localStorage cache 120 const cached = getCachedFromLocalStorage(id); 121 if (cached) return cached; 122 123 // fetch 124 const res = await fetch(url, { cache: "force-cache" }); 125 if (!res.ok) throw new Error(`PSC file ${url} HTTP ${res.status}`); 126 const text = await res.text(); 127 128 const set = new Set( 129 text 130 .split(/\r?\n/) 131 .map(x => x.trim()) 132 .filter(x => /^\d{5}
132$/.test(x)) 133 ); 134 135 saveCachedToLocalStorage(id, set); 136 return set; 137 } 138 139 function getPscSetForShipping(id) { 140 const conf = PSC_DEPENDENT_SHIPPINGS[id] || {}; 141 const url = resolveFileUrl(id, conf); 142 if (!url) return Promise.resolve(new Set()); // žádný soubor 143 144 if (inMemoryListCache.has(id)) return inMemoryListCache.get(id); 145 146 const p = loadPscSetFromFile(id, conf) 147 .catch(err => { 148 console.warn("PSC file load failed for shipping", id, err); 149 return new Set(); 150 }); 151 152 inMemoryListCache.set(id, p); 153 return p; 154 } 155 156 // ================================================== 157 // UI 158 // ================================================== 159 function ensureUI() { 160 if (document.getElementById("pscFilter")) return; 161 162 const methods = document.querySelector("#order-shipping-methods"); 163 if (!methods) return; 164 165 const d = document.createElement("div"); 166 d.className = "psc-filter-wrap"; 167 d.style.margin = "10px 0 15px"; 168 d.innerHTML = ` 169 <div style="padding:10px 12px;border:1px solid rgba(0,0,0,.08);border-radius:6px;background:#f7f7f7"> 170 <label for="pscFilter" style="font-weight:600;display:block;margin-bottom:6px"> 171 Zadejte PSÄ pro zobrazenà možnostà dopravy 172 </label> 173 <input id="pscFilter" type="text" inputmode="numeric" autocomplete="postal-code" 174 placeholder="110 00 / 11000" maxlength="6" 175 style="max-width:220px;width:100%;padding:8px 10px;border-radius:6px;border:1px solid #ccc"> 176 <div id="pscHint" style="margin-top:6px;font-size:12px;opacity:.75"></div> 177 </div> 178 `; 179 180 methods.parentNode.insertBefore(d, methods); 181 } 182 183 // ================================================== 184 // POKRAÄOVAT button lock + uloženà 185 // ================================================== 186 function setNextButtonEnabled(enabled) { 187 const btn = document.getElementById("orderFormButton"); 188 if (!btn) return; 189 190 btn.disabled = !enabled; 191 btn.style.pointerEvents = enabled ? "" : "none"; 192 btn.style.opacity = enabled ? "" : "0.45"; 193 btn.style.filter = enabled ? "" : "grayscale(1)"; 194 btn.setAttribute("aria-disabled", enabled ? "false" : "true"); 195 } 196 197 function hookNextButtonSave(inputEl) { 198 const btn = document.getElementById("orderFormButton"); 199 if (!btn || btn.dataset.pscHooked) return; 200 201 btn.addEventListener("click", () => { 202 const psc = normalizePsc(inputEl?.value || ""); 203 if (psc) savePscToStorage(psc); 204 }, { capture: true }); 205 206 btn.dataset.pscHooked = "1"; 207 } 208 209 // ================================================== 210 // Pravidla dostupnosti dopravy pro PSÄ 211 // ================================================== 212 async function isShippingAllowedForPsc(id, psc) { 213 const conf = PSC_DEPENDENT_SHIPPINGS[id]; 214 if (!conf) return false; 215 216 // exact list in config 217 if (Array.isArray(conf.exact) && conf.exact.includes(psc)) return true; 218 219 // ranges in config 220 if (Array.isArray(conf.ranges) && isPscInRanges(psc, conf.ranges)) return true; 221 222 // file list 223 const url = resolveFileUrl(id, conf); 224 if (url) { 225 const set = await getPscSetForShipping(id); 226 return set.has(psc); 227 } 228 229 return false; 230 } 231 232 function getAllowedZoneIdsForPscSync(psc) { 233 // sync pokus pouze pro range/exact (bez file) â pro auto-select to staÄÃ, file varianty budou vyÅeÅ¡eny nÞe v async Äásti 234 if (!psc) return []; 235 return ZONE_PRIORITY.filter(id => { 236 const conf = PSC_DEPENDENT_SHIPPINGS[id]; 237 if (!conf) return false; 238 if (Array.isArray(conf.exact) && conf.exact.includes(psc)) return true; 239 if (Array.isArray(conf.ranges) && isPscInRanges(psc, conf.ranges)) return true; 240 return false; 241 }); 242 } 243 244 function autoSelectZoneIfAvailableSync(psc) { 245 if (!ZONE_PRIORITY.length) return false; 246 const zoneIds = getAllowedZoneIdsForPscSync(psc); 247 if (!zoneIds.length) return false; 248 249 for (const id of zoneIds) { 250 const w = getWrapperById(id); 251 if (isVisible(w)) { 252 const r = getRadioById(id); 253 if (r && !r.checked) { 254 r.checked = true; 255 r.dispatchEvent(new Event("change", { bubbles: true })); 256 r.dispatchEvent(new Event("click", { bubbles: true })); 257 } 258 return true; 259 } 260 } 261 return false;
262 } 263 264 // ================================================== 265 // FILTRACE (async, kvůli TXT file) 266 // ================================================== 267 let applyToken = 0; 268 269 async function applyFilter(psc) { 270 const token = ++applyToken; 271 const wrappers = getShippingWrappers(); 272 273 if (!psc) { 274 wrappers.forEach(w => (w.style.display = "none")); 275 setNextButtonEnabled(false); 276 return; 277 } 278 279 setNextButtonEnabled(true); 280 281 // Nejprve ukaž "always" a schovej ostatnà (aby UI nereagovalo pozdÄ) 282 wrappers.forEach(w => { 283 const id = getShippingId(w); 284 if (!id) return; 285 286 if (ALWAYS_AFTER_PSC.includes(id)) { 287 w.style.display = ""; 288 return; 289 } 290 291 if (PSC_DEPENDENT_SHIPPINGS[id]) { 292 // doÄasnÄ schovat, dokud nevyhodnotÃme (kvůli async) 293 w.style.display = "none"; 294 return; 295 } 296 297 w.style.display = "none"; 298 }); 299 300 // auto-select zónové (range/exact) jeÅ¡tÄ pÅed file naÄtenÃm 301 const zoneSelectedSync = autoSelectZoneIfAvailableSync(psc); 302 303 // VyhodnoÅ¥ PSÄ-ÅÃzené dopravy (range/exact/file) 304 const dependentIds = Object.keys(PSC_DEPENDENT_SHIPPINGS).map(x => parseInt(x, 10)).filter(Number.isFinite); 305 306 // Preload file seznamy paralelnÄ (jen ty, co majà file) 307 await Promise.all(dependentIds.map(id => getPscSetForShipping(id).catch(() => new Set()))); 308 309 // Pokud mezitÃm pÅiÅ¡el nový input, skonÄi (race protection) 310 if (token !== applyToken) return; 311 312 for (const id of dependentIds) { 313 const wrap = getWrapperById(id); 314 if (!wrap) continue; 315 316 const allowed = await isShippingAllowedForPsc(id, psc); 317 318 // Pokud mezitÃm pÅiÅ¡el nový input, skonÄi 319 if (token !== applyToken) return; 320 321 wrap.style.display = allowed ? "" : "none"; 322 } 323 324 // auto-select zónové i pro "file" variantu (pokud je nastavená priority) 325 if (ZONE_PRIORITY.length) { 326 for (const id of ZONE_PRIORITY) { 327 const wrap = getWrapperById(id); 328 if (isVisible(wrap)) { 329 const allowed = await isShippingAllowedForPsc(id, psc); 330 if (token !== applyToken) return; 331 if (allowed) { 332 const r = getRadioById(id); 333 if (r && !r.checked) { 334 r.checked = true; 335 r.dispatchEvent(new Event("change", { bubbles: true })); 336 r.dispatchEvent(new Event("click", { bubbles: true })); 337 } 338 break; 339 } 340 } 341 } 342 } 343 344 // Pokud je vybraná doprava skrytá, pÅepni na prvnà viditelnou 345 const selected = document.querySelector('input[name="shippingId"]:checked'); 346 if (selected) { 347 const wrap = selected.closest(".radio-wrapper"); 348 if (wrap && wrap.style.display === "none") { 349 const firstVisible = wrappers.find(w => w.style.display !== "none"); 350 const r = firstVisible?.querySelector('input[name="shippingId"]'); 351 if (r) { 352 r.checked = true; 353 r.dispatchEvent(new Event("change", { bubbles: true })); 354 r.dispatchEvent(new Event("click", { bubbles: true })); 355 } 356 } 357 } else if (!zoneSelectedSync) { 358 const firstVisible = wrappers.find(w => w.style.display !== "none"); 359 const r = firstVisible?.querySelector('input[name="shippingId"]'); 360 if (r) { 361 r.checked = true; 362 r.dispatchEvent(new Event("change", { bubbles: true })); 363 r.dispatchEvent(new Event("click", { bubbles: true })); 364 } 365 } 366 } 367 368 // ================================================== 369 // INIT 370 // ================================================== 371 function init() { 372 ensureUI(); 373 374 const input = document.getElementById("pscFilter"); 375 const hint = document.getElementById("pscHint"); 376 if (!input) return; 377 378 hookNextButtonSave(input); 379 380 const onInput = debounce(() => { 381 const psc = normalizePsc(input.value); 382 383 if (hint) { 384 if (!input.value) hint.textContent = "Zadejte PSÄ â bez PSÄ nejdou zobrazit dopravy ani pokraÄovat."; 385 else if (!psc) hint.textContent = "Neplatné PSÄ â zadejte 5 ÄÃslic (napÅ. 11000 nebo 110 00)."; 386 else hint.textContent = "Zobrazeny dopravy pro PSÄ: " + psc; 387 } 388 389 if (psc) savePscToStorage(psc); 390 391 applyFilter(psc); 392 }, 250); 393 394 if (!input.dataset.listenerAttached) { 395 input.addEventListener("input", onInput); 396 input.dataset.listenerAttached = "1"; 397 } 398 399// ZABRÃNIT potvrzenà ENTEREM v poli PSÄ 400input.addEventListener("keydown", function (e) { 401 if (e.key === "Enter") { 402 e.preventDefault(); 403 e.stopPropagation(); 404 return false;
405 } 406}); 407 408 // PÅedvyplnÄnà z pamÄti â JEN JEDNOU 409if (!input.dataset.prefilledOnce) { 410 const stored = loadPscFromStorage(); 411 if (stored && !input.value) { 412 input.value = stored; 413 } 414 input.dataset.prefilledOnce = "1"; 415} 416 417 // aplikace stavu pÅi startu 418 const initial = normalizePsc(input.value); 419 applyFilter(initial); 420 421 if (hint) { 422 if (!input.value) hint.textContent = "Zadejte PSÄ â bez PSÄ nejdou zobrazit dopravy ani pokraÄovat."; 423 else if (!initial) hint.textContent = "Neplatné PSÄ â zadejte 5 ÄÃslic (napÅ. 11000 nebo 110 00)."; 424 else hint.textContent = "Zobrazeny dopravy pro PSÄ: " + initial; 425 } 426 } 427 428 // Checkout se může pÅekreslovat â init opakovanÄ 429 let tries = 0; 430 const iv = setInterval(() => { 431 tries++; 432 init(); 433 if (tries > 12) clearInterval(iv); 434 }, 700); 435 436 document.readyState === "loading" 437 ? document.addEventListener("DOMContentLoaded", init) 438 : init(); 439})();
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.