1var globObj = { 2 breakpoints: { 3 xxs: 0, 4 xs: 330, 5 sm: 575, 6 md: 767, 7 lg: 1039, 8 xl: 1259 9 }, 10 container: { 11 sm: 540, 12 md: 720, 13 lg: 1000, 14 xl: 1220 15 } 16}; 17 18(function () { 19 //ТаÑÑкÑин или деÑкÑоп 20 var supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints; 21 var root = document.getElementsByTagName('html')[0]; 22 root.classList.add(supportsTouch ? 'touch' : 'no-touch'); 23})(); 24 25// !!! на Ð±Ð¾Ñ Ð¿ÐµÑеменнÑе globDev и globDevAjax вÑегда Ð´Ð¾Ð»Ð¶Ð½Ñ Ð±ÑÑÑ false !!! 26let globDev = false, 27 globDevAjax = false; 28 29let isGlobalReceivedResponseChangeBasked = true; 30let isGlobalNoBasketTimeout = true; 31let isGlobalBasketRefresh = false; 32 33// конÑÑанÑÑ 34// ToDo: полÑÑаÑÑ Ð¸Ð· env-пеÑеменнÑÑ 35const AJAX_URL = '/ajax/' 36const VK_PRICE_LIST_ID = 160575; 37const OK_ACCOUNT_ID = 3244329; 38const OK_LIST_ID = 1; 39const DADATA_TOKEN = 'ca01079585fc9867a25e215ef68a36671bb6a0a5'; 40// todo нÑжно пеÑеделаÑÑ ÑÑо на вÑвод из twig-пеÑеменной globalStatic.labels или пÑидÑмаÑÑ Ð²Ð°ÑÐ¸Ð°Ð½Ñ Ð¿Ð¾Ð»ÑÑÑе 41const LABELS = { 42 bestseller: {class: '_green', text: 'ÐеÑÑÑеллеÑ'}, 43 latest: {class: '_blue', text: 'Ðовинка'}, 44 week: {class: '_violet', text: 'Ðнига недели'}, 45 preorder: {class: '_orange', text: 'ÐÑедзаказ'} 46} 47// if(typeof searchType == 'undefined') { 48// var searchType = 'book'; 49// } 50// глобалÑнÑе ÑÑнкÑии 51let globalFunctions = { 52 53 initBasket: function () { 54 globalApp.basket = {}; 55 }, 56 57 formatPrice: function (num, separator = ' ') { 58 return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1'+separator) 59 }, 60 61 isFunction: function (functionToCheck) { 62 return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]'; 63 }, 64 65 /** 66 * ÐÑовеÑÑеÑ, ÑвлÑеÑÑÑ Ð»Ð¸ пеÑеданнÑй обÑÐµÐºÑ Ñзлом DOM. 67 * 68 * @param {Object} obj - ÐбÑÐµÐºÑ Ð´Ð»Ñ Ð¿ÑовеÑки. 69 * @return {boolean} true, еÑли обÑÐµÐºÑ ÑвлÑеÑÑÑ Ñзлом DOM. 70 */ 71 isDomNode: function(obj) { 72 return obj instanceof Element; 73 }, 74 75 delay: function (fn, ms) { 76 let timer = 0; 77 return function(...args) { 78 clearTimeout(timer); 79 timer = setTimeout(fn.bind(this, ...args), ms || 0); 80 } 81 }, 82 83 /** 84 * Native scrollTo with callback 85 * @param offset - offset to scroll to 86 * @param callback - callback function 87 * 88 * https://stackoverflow.com/questions/52292603/is-there-a-callback-for-window-scrollto 89 */ 90 91 scrollTo: function (offset, callback) { 92 const fixedOffset = offset.toFixed (), 93 onScroll = function () { 94 if (window.pageYOffset.toFixed () === fixedOffset) { 95 window.removeEventListener ('scroll', onScroll); 96 callback () 97 } 98 }; 99 100 window.addEventListener ('scroll', onScroll); 101 onScroll (); 102 window.scrollTo ({ 103 top: offset, 104 behavior: 'smooth' 105 }); 106 }, 107 108 escapeStr: function (str) { 109 return String(str).replace(/[&<>"']/g, function (s) { 110 return {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[s]; 111 }); 112 }, 113 114 declension: function (number, forms, isNumber = false) { 115 const cases = [2, 0, 1, 1, 1, 2]; 116 const formIndex = ((number % 100 > 4) && (number % 100 < 20)) ? 2 : cases[Math.min (number % 10, 5)]; 117 118 let returnVal = forms[formIndex]; 119 if (isNumber) { 120 returnVal = number + " " + returnVal; 121 } 122 123 return returnVal; 124 }, 125 126 /* 127 ÐнимаÑÐ¸Ñ Ð´Ð»Ñ height: 0 -> height: auto 128 ÐдапÑиÑовано ÑеÑение оÑÑÑда 129 https://css-tricks.com/using-css-transitions-auto-dimensions/#comment-1607371 130 */ 131 heightOpenClose: function () { 132 if ($ (this).hasClass ('_open')) { // close 133 $ (this).each (function () { 134 $ (this).height ($ (this).get (0).scrollHeight).height ('').removeClass ('_open'); 135 }); 136 } 137 else { // open 138 $ (this).each (function () { 139 $ (this).height ($ (this).get (0).scrollHeight).addClass ('_open'); // get height and open 140 $ (this).one ('webkitTransitionEnd oTransitionEnd MSTransitionEnd transitionEnd', function () { // after transition complete 141 $ (this).height (''); // revert to CSS-set height 142 }); 143 }); 144 } 145 }, 146 147 makeSrcset: function (objImg) { 148 let srcset = ''; 149 if (objImg.x3) { 150 srcset += objImg.x3 + ' 3x, '; 151 } 152 if (objImg.x2) { 153 srcset += objImg.x2 + ' 2x, '; 154 } 155 srcset += objImg.x1; 156 return srcset; 157 }, 158 159 bytesToSize: function (bytes) {
160 const kilobyte = 1024 161 const megabyte = kilobyte * 1024 162 const gigabyte = megabyte * 1024 163 const terabyte = gigabyte * 1024 164 let formatted = '' 165 166 if (bytes < kilobyte) { 167 formatted = bytes + ' байÑ' 168 } else if (bytes < megabyte) { 169 formatted = (bytes / kilobyte).toFixed(2) + ' Ðб' 170 } else if (bytes < gigabyte) { 171 formatted = (bytes / megabyte).toFixed(2) + ' Ðб' 172 } else if (bytes < terabyte) { 173 formatted = (bytes / gigabyte).toFixed(2) + ' Ðб' 174 } else { 175 formatted = (bytes / terabyte).toFixed(2) + ' Тб' 176 } 177 178 return formatted 179 }, 180 181 // https://css-tricks.com/scroll-triggered-animation-vanilla-javascript/ 182 scrollTrigger: (els, options = {}) => { 183 els = Array.from(els) 184 els.forEach(el => { 185 if(!('IntersectionObserver' in window)){ 186 if(options.cb){ 187 options.cb(el) 188 } else { 189 entry.target.classList.add('active') 190 } 191 return 192 } 193 //this takes a callback function which receives two arguments: the elemts list and the observer instance 194 let observer = new IntersectionObserver((entries, observer) => { 195 entries.forEach(entry => { 196 if(entry.isIntersecting){ 197 if(options.cb){ 198 options.cb(el) 199 } else { 200 entry.target.classList.add('active') 201 } 202 observer.unobserve(entry.target) 203 } 204 }) 205 }, options) 206 observer.observe(el) 207 }) 208 }, 209 210 // ÑоÑмаÑиÑование ÑÑла Ð´Ð»Ñ ÑеÑайзеÑа на каÑÑÐ¸Ð½ÐºÑ Ð¿ÑоизволÑного ÑазмеÑа 211 getResizedImgUrl: (url, width, height) => { 212 const imgSplit = url.split('/') 213 imgSplit[2] = width + 'x' + height 214 return imgSplit.join('/') 215 }, 216 217 // пÑовеÑка user-agent на боÑа 218 isUserBot: () => { 219 const botPattern = "(googlebot\/|bot|Googlebot-Mobile|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)" 220 const re = new RegExp(botPattern, 'i') 221 const userAgent = navigator.userAgent 222 return re.test(userAgent) 223 }, 224 225 // ÑÑоÑÑлинг ÑÑнкÑии 226 throttle: (func, delay) => { 227 let prev = 0; 228 return (...args) => { 229 let now = new Date().getTime(); 230 if(now - prev> delay){ 231 prev = now; 232 return func(...args); 233 } 234 } 235 }, 236 237 // измениÑÑ Ð³ÐµÑ-паÑамеÑÑ ÑÑла 238 updateGetParam: (url, param, value) => { 239 const urlObject = new URL(url) 240 urlObject.searchParams.set(param, value) 241 return urlObject.href 242 }, 243 244 // ÑдалиÑÑ Ð³ÐµÑ-паÑамеÑÑ ÑÑла 245 deleteGetParam: (url, param) => { 246 const urlObject = new URL(url) 247 urlObject.searchParams.delete(param) 248 return urlObject.href 249 }, 250 251 // ÑделаÑÑ Ð¿ÐµÑвÑÑ Ð±ÑÐºÐ²Ñ ÑÑÑоки пÑопиÑной 252 capitalizeFirstLetter: (string) => { 253 return string.charAt(0).toUpperCase() + string.slice(1) 254 }, 255 256 /** 257 ÐагÑÑÐ¶Ð°ÐµÑ ÑкÑÐ¸Ð¿Ñ Ð¿Ð¾ Ð·Ð°Ð´Ð°Ð½Ð½Ð¾Ð¼Ñ URL-адÑеÑÑ Ð¸ возвÑаÑÐ°ÐµÑ Promise Ñ ÑезÑлÑÑаÑом загÑÑзки. 258 @param {string} FILE_URL - URL-адÑÐµÑ Ñайла ÑкÑипÑа. 259 @param {boolean} async - ÐпÑеделÑеÑ, должен ли загÑÑжаÑÑÑÑ ÑкÑÐ¸Ð¿Ñ Ð°ÑÐ¸Ð½Ñ Ñонно. Ðо ÑмолÑÐ°Ð½Ð¸Ñ true. 260 @param {string} type - Тип ÑкÑипÑа. Ðо ÑмолÑÐ°Ð½Ð¸Ñ "text/javascript". 261 @returns {Promise} - Promise Ñ Ð¾Ð±ÑекÑом ÑÑаÑÑÑа загÑÑзки (status) и ÑообÑением об оÑибке (message), еÑли возникла оÑибка. 262 */ 263 // https://www.educative.io/answers/how-to-dynamically-load-a-js-file-in-javascript 264 loadScript: (FILE_URL, async = true, type = "text/javascript") => { 265 return new Promise((resolve, reject) => { 266 try { 267 const scriptEle = document.createElement("script"); 268 scriptEle.type = type; 269 scriptEle.async = async; 270 scriptEle.src =FILE_URL; 271 272 scriptEle.addEventListener("load", (event) => { 273 resolve({ status: true }); 274 }); 275 276 scriptEle.addEventListener("error", (event) => { 277 reject({ 278 status: false, 279 message: `Failed to load the script ${FILE_URL}` 280 }); 281 }); 282 283 document.body.appendChild(scriptEle); 284 } catch (error) { 285 reject(error); 286 } 287 }); 288 }, 289 290 /** 291 * ÐÑовеÑÑеÑ, поддеÑÐ¶Ð¸Ð²Ð°ÐµÑ Ð»Ð¸ ÑÑÑÑойÑÑво ввод touch 292 * @return {boolean} true, еÑли ÑÑÑÑойÑÑво поддеÑÐ¶Ð¸Ð²Ð°ÐµÑ touch, false в пÑоÑивном ÑлÑÑае 293 */ 294 isTouchDevice: () => { 295 return ('ontouchstart' in window) || 296 (navigator.maxTouchPoints > 0) || 297 (navigator.msMaxTouchPoints > 0); 298 }, 299 300 /** 301 * РазделÑÐµÑ Ð¼Ð°ÑÑив на ÑавнÑе ÑаÑÑи 302 * @param {Array} arr - иÑÑ Ð¾Ð´Ð½Ñй маÑÑив 303 * @param {number} parts - колиÑеÑÑво ÑаÑÑей, на коÑоÑÑе нÑжно ÑазделиÑÑ Ð¼Ð°ÑÑив 304 * @returns {Array} - маÑÑив из parts подмаÑÑивов 305 */ 306 splitArray: (arr, parts) => { 307 const result = [];
308 const chunkSize = Math.ceil(arr.length / parts); 309 for (let i = 0; i < parts; i++) { 310 const start = i * chunkSize; 311 const end = start + chunkSize; 312 result.push(arr.slice(start, end)); 313 } 314 return result; 315 }, 316 317 /** 318 * ФоÑмаÑиÑÑÐµÑ ÑиÑло ÑекÑнд в оÑобÑажение ÑаймеÑа 319 * ÐапÑимеÑ, 95 Ñек => 01:35 или 3615 Ñек => 01:00:15 320 * @param {number} seconds - ÑекÑндÑ, наÑÑÑалÑное ÑиÑло 321 * @returns {string} - ÑÑÑока Ñ ÑоÑмаÑиÑованнÑм оÑобÑажением 322 */ 323 formatSecondsToTime: (seconds) => { 324 const date = new Date(0); 325 date.setSeconds(seconds); 326 if (seconds > 3599) { 327 return date.toISOString().substr(11, 8); 328 } else { 329 return date.toISOString().substr(14, 5); 330 } 331 }, 332 333 /** 334 * ÐозвÑаÑÐ°ÐµÑ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ðµ Ð´Ð½Ñ Ð½ÐµÐ´ÐµÐ»Ð¸ на ÑÑÑÑком ÑзÑке на оÑнове его ÑиÑлового пÑедÑÑавлениÑ. 335 * 336 * @param {number} num - ЧиÑловое пÑедÑÑавление Ð´Ð½Ñ Ð½ÐµÐ´ÐµÐ»Ð¸ (Ð¾Ñ 0 до 6, где 0 - воÑкÑеÑенÑе, 1 - понеделÑник и Ñ. д.). 337 * @returns {Object} - ÐбÑекÑ, ÑодеÑжаÑий название Ð´Ð½Ñ Ð½ÐµÐ´ÐµÐ»Ð¸ в имениÑелÑном и ÑодиÑелÑном Ð¿Ð°Ð´ÐµÐ¶Ð°Ñ . 338 */ 339 // ToDo добавиÑÑ Ð¿Ð°Ð´ÐµÐ¶Ð¸, еÑли нÑжно 340 getDayInRus: (num) => { 341 return [ 342 { 343 caseNominative: 'воÑкÑеÑенÑе', 344 caseGenitive: 'воÑкÑеÑенÑÑ' 345 }, 346 { 347 caseNominative: 'понеделÑник', 348 caseGenitive: 'понеделÑника' 349 }, 350 { 351 caseNominative: 'вÑоÑник', 352 caseGenitive: 'вÑоÑника' 353 }, 354 { 355 caseNominative: 'ÑÑеда', 356 caseGenitive: 'ÑÑедÑ' 357 }, 358 { 359 caseNominative: 'ÑеÑвеÑг', 360 caseGenitive: 'ÑеÑвеÑга' 361 }, 362 { 363 caseNominative: 'пÑÑниÑа', 364 caseGenitive: 'пÑÑниÑÑ' 365 }, 366 { 367 caseNominative: 'ÑÑббоÑа', 368 caseGenitive: 'ÑÑббоÑÑ' 369 }, 370 ][num]; 371 }, 372 373 /** 374 * ÐозвÑаÑÐ°ÐµÑ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ðµ меÑÑÑа на ÑÑÑÑком ÑзÑке на оÑнове его ÑиÑлового пÑедÑÑавлениÑ. 375 * 376 * @param {number} num - ЧиÑловое пÑедÑÑавление меÑÑÑа (Ð¾Ñ 0 до 11). 377 * @returns {Object} - ÐбÑекÑ, ÑодеÑжаÑий название меÑÑÑа в имениÑелÑном и ÑодиÑелÑном Ð¿Ð°Ð´ÐµÐ¶Ð°Ñ . 378 */ 379 // ToDo добавиÑÑ Ð¿Ð°Ð´ÐµÐ¶Ð¸, еÑли нÑжно 380 getMonthInRus: (num) => { 381 return [ 382 { 383 caseNominative: 'ÑнваÑÑ', 384 caseGenitive: 'ÑнваÑÑ' 385 }, 386 { 387 caseNominative: 'ÑевÑалÑ', 388 caseGenitive: 'ÑевÑалÑ' 389 }, 390 { 391 caseNominative: 'маÑÑ', 392 caseGenitive: 'маÑÑа' 393 }, 394 { 395 caseNominative: 'апÑелÑ', 396 caseGenitive: 'апÑелÑ' 397 }, 398 { 399 caseNominative: 'май', 400 caseGenitive: 'маÑ' 401 }, 402 { 403 caseNominative: 'иÑнÑ', 404 caseGenitive: 'иÑнÑ' 405 }, 406 { 407 caseNominative: 'иÑлÑ', 408 caseGenitive: 'иÑлÑ' 409 }, 410 { 411 caseNominative: 'авгÑÑÑ', 412 caseGenitive: 'авгÑÑÑа' 413 }, 414 { 415 caseNominative: 'ÑенÑÑбÑÑ', 416 caseGenitive: 'ÑенÑÑбÑÑ' 417 }, 418 { 419 caseNominative: 'окÑÑбÑÑ', 420 caseGenitive: 'окÑÑбÑÑ' 421 }, 422 { 423 caseNominative: 'ноÑбÑÑ', 424 caseGenitive: 'ноÑбÑÑ' 425 }, 426 { 427 caseNominative: 'декабÑÑ', 428 caseGenitive: 'декабÑÑ' 429 }, 430 ][num]; 431 } 432}; 433 434let globalApp = { 435 436 init: function () { 437 438 }, 439 440 // обновление get-паÑамеÑÑа в ÑÑле
441 // https://stackoverflow.com/questions/1090948/change-url-parameters/10997390#10997390 442 updateURLParameter: function (url, param, paramVal) { 443 let TheAnchor = null; 444 let newAdditionalURL = ""; 445 let tempArray = url.split ("?"); 446 let baseURL = tempArray[0]; 447 let additionalURL = tempArray[1]; 448 let temp = ""; 449 450 if (additionalURL) { 451 let tmpAnchor = additionalURL.split ("#"); 452 let TheParams = tmpAnchor[0]; 453 TheAnchor = tmpAnchor[1]; 454 if (TheAnchor) 455 additionalURL = TheParams; 456 457 tempArray = additionalURL.split ("&"); 458 459 // запиÑÑ Ð² ÑÑл паÑамеÑÑов, оÑлиÑнÑÑ Ð¾Ñ Ð¿ÐµÑеданного 460 for (let i = 0; i < tempArray.length; i++) { 461 if (tempArray[i].split ('=')[0] !== param) { 462 newAdditionalURL += temp + tempArray[i]; 463 temp = "&"; 464 } 465 } 466 } 467 else { 468 let tmpAnchor = baseURL.split ("#"); 469 let TheParams = tmpAnchor[0]; 470 TheAnchor = tmpAnchor[1]; 471 472 if (TheParams) 473 baseURL = TheParams; 474 } 475 476 if (TheAnchor) 477 paramVal += "#" + TheAnchor; 478 479 let rows_txt = (paramVal !== '') ? temp + "" + param + "=" + paramVal : ''; 480 return baseURL + ((newAdditionalURL !== "" || rows_txt !== "") ? "?" + newAdditionalURL + rows_txt : ""); 481 }, 482 483 catalogFilter: { 484 init: false, 485 filterGroups: {}, 486 filterCurrentGroups: {}, 487 filterData: {}, 488 sortVal: '', 489 noPages: false, 490 ajaxUrl: '', 491 $itemsContainer: $('.js-catalog-items'), 492 493 setUrl: function (url) { 494 let sortVal = globalApp.catalogFilter.sortVal; 495 496 if (sortVal !== '') { 497 url = globalApp.updateURLParameter(url, 'sort', sortVal); 498 } 499 500 window.history.replaceState('', '', url); 501 }, 502 sendRequest: function () { 503 if (globalApp.catalogFilter.init === true) { 504 let sortVal = globalApp.catalogFilter.sortVal, 505 noPages = globalApp.catalogFilter.noPages, 506 ajaxData = JSON.parse(JSON.stringify(globalApp.catalogFilter.filterData)); 507 508 if (sortVal !== '') { 509 ajaxData.sortVal = globalApp.catalogFilter.sortVal; 510 } 511 512 if (noPages !== false) { 513 ajaxData.noPages = true; 514 } 515 516 $.ajax({ 517 method: 'POST', 518 url: globalApp.catalogFilter.ajaxUrl, 519 data: ajaxData, 520 dataType: 'html', 521 success: function (data) { 522 globalApp.catalogFilter.outputItems(data); 523 }, 524 complete: function () { 525 526 }, 527 error: function (xhr, str) { 528 529 } 530 }); 531 } 532 }, 533 outputItems: function (html, add = false) { 534 let $cont = globalApp.catalogFilter.$itemsContainer; 535 let $contItems = $('.js-catalog-items'); 536 if ($cont.length > 0) { 537 if (!add) { 538 if(($cont.hasClass('js-aside-wrap') && !$('.catalog-empty',html).length) || !$cont.hasClass('js-aside-wrap')) { 539 $cont.html(html); 540 if($cont.hasClass('js-aside-wrap')) { 541 $(document).trigger('init-filter') 542 } 543 } else { 544 $contItems.html(html); 545 } 546 $('.js-catalog-items').trigger('init-books') 547 } else { 548 549 } 550 } 551 } 552 } 553}; 554 555// СÑаÑиÑÑика по кликам Ñ Ð±Ð°Ð½Ð½ÐµÑов 556function statClick(id) { 557 $.ajax({ 558 method: 'POST', 559 url: '/api/stat_banner/', 560 data: {id: id}, 561 dataType: 'json' 562 }); 563} 564 565(function () { 566 $(function () {
567 $('.js-header-droplist-opener').click(function (e) { 568 let $_this = $(this); 569 570 $_this.toggleClass('_opened'); 571 }); 572 573 let headSearch = function ($obj) { 574 if ($obj instanceof jQuery) { 575 let $_this = $obj, 576 $header = $('.js-header-top'), 577 $input = $_this.find('.js-headsearch__input'), 578 $delBtn = $_this.find('.js-headsearch__del'), 579 580 focusClass = '_searching'; 581 582 $delBtn.click(function(e){ 583 e.preventDefault(); 584 $input.val(''); 585 $header.removeClass(focusClass); 586 }); 587 588 $input.focus(function(e){ 589 if (! $header.hasClass(focusClass)) { 590 $header.addClass(focusClass); 591 } 592 }) 593 } 594 }; 595 596 headSearch($('.js-headsearch')); 597 }); 598}()); 599 600(function () { 601 $(function () { 602 603 /* 604 605 РеÑение Ð´Ð»Ñ Ð´ÐµÑкÑопной ÑикÑаÑии Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð±Ð¾ÐºÐ¾Ð²Ð¾Ð¹ панели Ñ ÑилÑÑÑом в каÑалоге, без поÑеÑи возможноÑÑи ÐµÑ 606 пÑоÑкÑоллиÑÑ 607 608 ÐÐ»Ñ ÐºÐ¾ÑÑекÑной ÑабоÑÑ, пÑи ÑеÑайзе и изменениÑÑ Ð²ÑÑоÑÑ Ð±Ð»Ð¾ÐºÐ°, пÑиÑваиваем обÑÑÑке min-height Ñ Ð²ÑÑоÑой боковой 609 панели, инаÑе пÑи коÑоÑком ÑпиÑке ÑоваÑов ÑилÑÑÑ Ð½ÐµÐ²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ доÑкÑоллиÑÑ Ð´Ð¾ конÑа 610 611 ÐÑи загÑÑзке ÑÑÑаниÑÑ Ð¶Ð´Ñм, пока иниÑиализиÑÑÑÑÑÑ Ð²Ñе ползÑнки и каÑÑ Ð² ÑилÑÑÑе, и ÑолÑко заÑем иниÑиализиÑÑем 612 минималÑнÑÑ Ð²ÑÑоÑÑ Ð±Ð»Ð¾ÐºÐ° (инаÑе бÑÐ´ÐµÑ Ð½ÐµÐºÐ¾ÑÑекÑное знаÑение) 613 614 */ 615 616 // TODO пÑÐµÐ»Ð¾Ð°Ð´ÐµÑ Ð±Ð»Ð¾ÐºÐ° 617 618 let lastScrollTop = $ (window).scrollTop (); 619 let wasScrollingDown = true; 620 const $sidebar = $ ('.js-aside-bar'); 621 const $wrap = $ ('.js-aside-wrap'); 622 const $desktopFilter = $sidebar.find ('.js-filt'); 623 let initialSidebarTop; 624 625 if ($sidebar.length === 0 || $wrap.length === 0 || $desktopFilter.length === 0) return false; 626 627 628 // пÑовеÑка, вÑе ли ползÑнки и каÑÑ Ð·Ð°Ð¸Ð½Ð¸ÑилиÑÑ 629 function isReadyForInit () { 630 const $ranges = $desktopFilter.find('.js-filt-range'); 631 const $rangesInited = $ranges.filter('._inited'); 632 const $groups = $desktopFilter.find('.js-filt-group'); 633 const $groupsInited = $groups.filter('._inited'); 634 635 return $ranges.length === $rangesInited.length && $groups.length === $groupsInited.length; 636 } 637 638 function recalcHeight () { 639 $wrap.css('min-height', $sidebar.innerHeight() + 'px'); 640 } 641 642 function recalcPosition () { 643 let windowHeight = $(window).height(), 644 wrapHeight = $wrap.outerHeight(), 645 wrapTop = $wrap.offset().top, 646 wrapBottom = wrapTop + wrapHeight; 647 648 let scrollTop = $(window).scrollTop(), 649 scrollBottom = scrollTop + windowHeight; 650 651 let sidebarHeight = $sidebar.outerHeight(); 652 653 let sidebarTop = $sidebar.offset().top, 654 sidebarBottom = sidebarTop + sidebarHeight; 655 656 let heightDelta = Math.abs(windowHeight - sidebarHeight), 657 scrollDelta = lastScrollTop - scrollTop; 658 659 let isScrollingDown = (scrollTop > lastScrollTop), 660 isWindowLarger = (windowHeight > sidebarHeight); 661 662 let stopDragging = (scrollBottom >= wrapBottom); 663 664 // ÐÑовеÑка на ÑлиÑком маленÑкÑÑ Ð²ÑÑоÑÑ 665 if ($wrap.outerHeight() <= 1000) { 666 // ÐÑли вÑÑоÑа ÑлиÑком маленÑкаÑ, ÑилÑÑÑ Ð¾ÑÑаÑÑÑÑ Ð½Ð° меÑÑе 667 } else if (stopDragging) { 668 $sidebar.removeClass('_fixed').addClass('_stopped').css('top', 'auto'); 669 } else { 670 if ($sidebar.hasClass('_stopped')) { 671 $sidebar.addClass('_fixed').removeClass('_stopped').css('top', -heightDelta); 672 } else { 673 if ((isWindowLarger && scrollTop > initialSidebarTop) || (!isWindowLarger && scrollBottom > initialSidebarTop + sidebarHeight)) { 674 $sidebar.addClass('_fixed'); 675 } else if (!isScrollingDown && scrollTop <= initialSidebarTop) { 676 $sidebar.removeClass('_fixed'); 677 } 678 679 let dragBottomDown = (sidebarBottom <= scrollBottom && isScrollingDown), 680 dragTopUp = (sidebarTop >= scrollTop && !isScrollingDown); 681 682 if (dragBottomDown) { 683 if (isWindowLarger) { 684 $sidebar.css('top', 0); 685 } else { 686 $sidebar.css('top', -heightDelta); 687 } 688 } else if (dragTopUp) { 689 $sidebar.css('top', 0); 690 } else if ($sidebar.hasClass('_fixed')) { 691 let currentTop = parseInt($sidebar.css('top'), 10); 692 693 let minTop = -heightDelta, 694 scrolledTop = currentTop + scrollDelta; 695 696 let isPageAtBottom = (scrollTop + windowHeight >= $(document).height()), 697 newTop = (isPageAtBottom) ? minTop : scrolledTop; 698 699 $sidebar.css('top', newTop); 700 } 701 } 702 } 703 704 lastScrollTop = scrollTop; 705 wasScrollingDown = isScrollingDown; 706 } 707 708 // Ð¸Ð½Ð¸Ñ 709 710 /* 711 ÐдеÑÑ, Ð²Ð²Ð¸Ð´Ñ Ð·Ð½Ð°ÑиÑелÑного влиÑÐ½Ð¸Ñ Ð½Ð° пÑоизводиÑелÑноÑÑÑ, Ñнижаем ÑаÑÑоÑÑ ÑÑиггеÑа ÑеÑайза 712 https://www.onlywebpro.com/2017/03/25/optimize-scrolling-performance-by-debouncing-scroll-event-calls/ 713 */ 714 715 $desktopFilter.find ('.js-filt-group, .js-filt-range').on ('is:inited', () => { 716 if (isReadyForInit ()) { 717 initialSidebarTop = $sidebar.offset ().top; 718 719 recalcHeight (); 720 if (window.innerWidth > 1039) { 721 recalcPosition (); 722 } 723 724 let debounceTimerResize; 725 $ (window).on('resize', (e) => { 726 if (debounceTimerResize) { 727 window.clearTimeout (debounceTimerResize); 728 } 729 debounceTimerResize = window.setTimeout (function () { 730 if (window.innerWidth > 1039) { 731 recalcHeight (); 732 recalcPosition (); 733 } 734 }, 100); 735 }); 736 737 $sidebar.on ('click', '.js-filt-group__expand', (e) => { 738 setTimeout (() => { 739 recalcHeight (); 740 }, 600); 741 }); 742 743 $ (window).on ('scroll', () => { 744 if (window.innerWidth > 1039) { 745 recalcPosition (); 746 } 747 }); 748 } 749 }); 750 }); 751}()); 752 753 754(function () { 755 $(function () { 756 757 $(document).on("basketItemChange", function(e, id, offerId, q, data) { 758 let $btns = $('.js-btn-buy[data-offer-id=' + offerId + ']'), 759 $btnsQuantity = $('.js-btn-buy-quantity[data-offer-id=' + offerId + ']'); 760 761 if ($btns.length > 0) { 762 $btns.each(function(){ 763 if (q > 0) { 764 if (!$(this).is('._added')) { 765 $(this).addClass('_added'); 766 } 767 } else if (q === 0) { 768 if ($(this).is('._added')) { 769 $(this).removeClass('_added'); 770 } 771 } 772 }); 773 } 774 775 if ($btnsQuantity.length > 0) { 776 $btnsQuantity.each(function(){ 777 $(this).find("input").val(q).trigger("change"); 778 }); 779 } 780 }); 781 782 $(document).on("change", ".js-btn-buy-quantity input", function(e){ 783 let $wrap = $(this).parents(".js-btn-buy-quantity"), 784 $qText = $wrap.find(".js-btn-buy-quantity-val"); 785 786 $qText.text($(this).val()); 787 }); 788 }); 789}()); 790 791(function () { 792 $(function () { 793 /* 794 * ÐÑовеÑка кÑк киалабÑа и обновление знаÑений в localStorage, еÑли не ÑÐ¾Ð²Ð¿Ð°Ð´Ð°ÐµÑ 795 * */ 796 let cookieKeaUserId = $.cookie('kea_user_id') 797 let cookieKeaCartId = $.cookie('kea_cart_id') 798 let storageKeaUserId = localStorage.getItem('kea_user_id') 799 let storageKeaCartId = localStorage.getItem('kea_cart_id') 800 if (cookieKeaUserId !== storageKeaUserId) { 801 if (cookieKeaUserId === undefined) { 802 localStorage.removeItem('kea_user_id') 803 } else { 804 localStorage.setItem('kea_user_id', cookieKeaUserId) 805 } 806 } 807 if (cookieKeaCartId !== storageKeaCartId) { 808 if (cookieKeaUserId === undefined) { 809 localStorage.removeItem('kea_cart_id') 810 } else { 811 localStorage.setItem('kea_cart_id', cookieKeaCartId) 812 } 813 } 814 }); 815}()); 816 817function updateWindowScroll (disable) { 818 if (disable) { 819 $('html').addClass('_filter-open'); 820 } else { 821 $('html').removeClass('_filter-open'); 822 } 823 824 disableBodyScroll(disable); 825} 826 827/** 828 * Prevent body scroll and overscroll. 829 * Tested on mac, iOS chrome / Safari, Android Chrome. 830 * 831 * Based on: https://benfrain.com/preventing-body-scroll-for-modals-in-ios/ 832 * https://stackoverflow.com/a/41601290 833 * 834 * Use in combination with: 835 * html, body {overflow: hidden;} 836 * 837 * and: -webkit-overflow-scrolling: touch; for the element that should scroll. 838 * 839 * disableBodyScroll(true, '.i-can-scroll'); 840 */ 841var disableBodyScroll = (function () { 842 843 /** 844 * Private variables 845 */ 846 var _selector = false, 847 _element = false, 848 _clientY; 849 850 /** 851 * Polyfills for Element.matches and Element.closest 852 */ 853 if (!Element.prototype.matches) 854 Element.prototype.matches = Element.prototype.msMatchesSelector || 855 Element.prototype.webkitMatchesSelector; 856 857 if (!Element.prototype.closest) 858 Element.prototype.closest = function (s) { 859 var ancestor = this; 860 if (!document.documentElement.contains(el)) return null; 861 do { 862 if (ancestor.matches(s)) return ancestor; 863 ancestor = ancestor.parentElement; 864 } while (ancestor !== null); 865 return el; 866 }; 867 868 /** 869 * Prevent default unless within _selector 870 * 871 * @param event object event 872 * @return void 873 */ 874 var preventBodyScroll = function (event) { 875 if (false === _element || !event.target.closest(_selector)) { 876 event.preventDefault(); 877 } 878 }; 879 880 /** 881 * Cache the clientY co-ordinates for 882 * comparison 883 * 884 * @param event object event 885 * @return void 886 */ 887 var captureClientY = function (event) { 888 // only respond to a single touch 889 if (event.targetTouches.length === 1) { 890 _clientY = event.targetTouches[0].clientY; 891 } 892 }; 893 894 /** 895 * Detect whether the element is at the top 896 * or the bottom of their scroll and prevent 897 * the user from scrolling beyond 898 * 899 * @param event object event 900 * @return void 901 */ 902 var preventOverscroll = function (event) { 903 // only respond to a single touch 904 if (event.targetTouches.length !== 1) { 905 return; 906 } 907 908 var clientY = event.targetTouches[0].clientY - _clientY; 909 910 // The element at the top of its scroll, 911 // and the user scrolls down 912 if (_element.scrollTop === 0 && clientY > 0) { 913 event.preventDefault(); 914 } 915 916 // The element at the bottom of its scroll, 917 // and the user scrolls up 918 // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions 919 if ((_element.scrollHeight - _element.scrollTop <= _element.clientHeight) && clientY < 0) { 920 event.preventDefault(); 921 } 922 923 }; 924 925 /** 926 * Disable body scroll. Scrolling with the selector is 927 * allowed if a selector is porvided.
928 * 929 * @param boolean allow 930 * @param string selector Selector to element to change scroll permission 931 * @return void 932 */ 933 return function (allow, selector) { 934 if (typeof selector !== "undefined") { 935 _selector = selector; 936 _element = document.querySelector(selector); 937 } 938 939 if (true === allow) { 940 if (false !== _element) { 941 _element.addEventListener('touchstart', captureClientY, false); 942 _element.addEventListener('touchmove', preventOverscroll, {passive: false}); 943 } 944 document.body.addEventListener("touchmove", preventBodyScroll, {passive: false}); 945 } else { 946 if (false !== _element) { 947 _element.removeEventListener('touchstart', captureClientY, false); 948 _element.removeEventListener('touchmove', preventOverscroll, {passive: false}); 949 } 950 document.body.removeEventListener("touchmove", preventBodyScroll, {passive: false}); 951 } 952 }; 953}()); 954 955 956// https://gist.github.com/AlexEmashev/ee8302b5036b01362f63dab35948401f 957// Demo http://codepen.io/AlexEmashev/pen/BKgQdx 958(function ($) { 959 $.fn.swipeDetector = function (options) { 960 // States: 0 - no swipe, 1 - swipe started, 2 - swipe released 961 var swipeState = 0; 962 // Coordinates when swipe started 963 var startX = 0; 964 var startY = 0; 965 // Distance of swipe 966 var pixelOffsetX = 0; 967 var pixelOffsetY = 0; 968 // Target element which should detect swipes. 969 var swipeTarget = this; 970 var defaultSettings = { 971 // Amount of pixels, when swipe don't count. 972 swipeThreshold: 70, 973 // Flag that indicates that plugin should react only on touch events. 974 // Not on mouse events too. 975 useOnlyTouch: false 976 }; 977 978 // Initializer 979 (function init() { 980 options = $.extend(defaultSettings, options); 981 // Support touch and mouse as well. 982 swipeTarget.on('mousedown touchstart', swipeStart); 983 $('html').on('mouseup touchend', swipeEnd); 984 $('html').on('mousemove touchmove', swiping); 985 })(); 986 987 function swipeStart(event) { 988 if (options.useOnlyTouch && !event.originalEvent.touches) 989 return; 990 991 if (event.originalEvent.touches) 992 event = event.originalEvent.touches[0]; 993 994 if (swipeState === 0) { 995 swipeState = 1; 996 startX = event.clientX; 997 startY = event.clientY; 998 } 999 } 1000 1001 function swipeEnd(event) { 1002 if (swipeState === 2) { 1003 swipeState = 0; 1004 1005 if (Math.abs(pixelOffsetX) > Math.abs(pixelOffsetY) && 1006 Math.abs(pixelOffsetX) > options.swipeThreshold) { // Horizontal Swipe 1007 if (pixelOffsetX < 0) { 1008 swipeTarget.trigger($.Event('swipeLeft.sd')); 1009 } else { 1010 swipeTarget.trigger($.Event('swipeRight.sd')); 1011 } 1012 } else if (Math.abs(pixelOffsetY) > options.swipeThreshold) { // Vertical swipe 1013 if (pixelOffsetY < 0) { 1014 swipeTarget.trigger($.Event('swipeUp.sd')); 1015 } else { 1016 swipeTarget.trigger($.Event('swipeDown.sd')); 1017 } 1018 } 1019 } 1020 } 1021 1022 function swiping(event) { 1023 // If swipe don't occuring, do nothing. 1024 if (swipeState !== 1) 1025 return; 1026 1027 1028 if (event.originalEvent.touches) { 1029 event = event.originalEvent.touches[0]; 1030 } 1031 1032 var swipeOffsetX = event.clientX - startX; 1033 var swipeOffsetY = event.clientY - startY; 1034 1035 if ((Math.abs(swipeOffsetX) > options.swipeThreshold) || 1036 (Math.abs(swipeOffsetY) > options.swipeThreshold)) { 1037 swipeState = 2; 1038 pixelOffsetX = swipeOffsetX; 1039 pixelOffsetY = swipeOffsetY; 1040 } 1041 } 1042 1043 return swipeTarget; // Return element available for chaining. 1044 } 1045 1046}(jQuery)); 1047 1048 1049// https://medium.com/talk-like/detecting-if-an-element-is-in-the-viewport-jquery-a6a4405a3ea2 1050(function ($) { 1051 $.fn.isInViewport = function () { 1052 var elementTop = $ (this).offset ().top; 1053 var elementBottom = elementTop + $ (this).outerHeight (); 1054 var viewportTop = $ (window).scrollTop (); 1055 var viewportBottom = viewportTop + $ (window).height (); 1056 return elementBottom > viewportTop && elementTop < viewportBottom; 1057 }; 1058} (jQuery)); 1059 1060 1061(function () { 1062 /** 1063 * ÐобавлÑÐµÑ Ð½Ð° ÑÑÑаниÑÑ Ð¾Ð±ÑÑÑÐºÑ Ð´Ð»Ñ Ð½Ð¸Ð¶Ð½Ð¸Ñ Ð±Ð°Ñов 1064 * и позволÑÐµÑ Ð²ÑÑавлÑÑÑ ÑÑда и ÑдалÑÑÑ Ð¾ÑÑÑда конÑÐµÐ½Ñ 1065 */ 1066 $(function () { 1067 let inited = false
1068 function init() { 1069 if ($('.js-site-bottom-wrapper').length === 0) { 1070 inited = true 1071 $('<div class="site-bottom-wrapper js-site-bottom-wrapper"></div>').appendTo($('body')) 1072 } 1073 } 1074 1075 /** 1076 * ÐобавлÑÐµÑ Ð² наÑало ÑпиÑка 1077 */ 1078 $.fn.prependToBottomWrapper = function () { 1079 if (!inited) 1080 init() 1081 const $_this = $(this).detach() 1082 return $_this.prependTo('.js-site-bottom-wrapper') 1083 } 1084 /** 1085 * ÐобавлÑÐµÑ Ð² ÐºÐ¾Ð½ÐµÑ ÑпиÑка 1086 */ 1087 $.fn.appendToBottomWrapper = function () { 1088 if (!inited) 1089 init() 1090 const $_this = $(this).detach() 1091 return $_this.appendTo('.js-site-bottom-wrapper') 1092 } 1093 /** 1094 * ÐÑÑÐµÐ·Ð°ÐµÑ ÑÐ»ÐµÐ¼ÐµÐ½Ñ Ð¸ возвÑаÑÐ°ÐµÑ ÐµÐ³Ð¾ 1095 */ 1096 $.fn.detachFromBottomWrapper = function () { 1097 const $_this = $(this) 1098 if ($_this.closest('.js-site-bottom-wrapper')) { 1099 return $_this.detach() 1100 } 1101 } 1102 }); 1103 1104 /** 1105 * ТаймаÑÑ Ð²Ð²Ð¾Ð´Ð° в инпÑÑÑ 1106 * https://github.com/andrescevp/jquery.donetyping/blob/master/jquery.donetyping.js 1107 */ 1108 $.fn.extend({ 1109 donetyping: function(callback,timeout){ 1110 timeout = timeout || 1e3; // 1 second default timeout 1111 var timeoutReference, 1112 doneTyping = function(el){ 1113 if (!timeoutReference) return; 1114 timeoutReference = null; 1115 callback.call(el); 1116 }; 1117 return this.each(function(i,el){ 1118 var $el = $(el); 1119 1120 ($el.is(':input') || $el.is('textarea')) && $el.on('keyup keypress',function(e){ 1121 var excludedKeys = [ 1122 16, 17, 18, 20, 35, 36, 37, 1123 38, 39, 40, 45, 144, 225 1124 ]; 1125 // This catches the backspace button in chrome, but also prevents 1126 // the event from triggering too premptively. Without this line, 1127 // using tab/shift+tab will make the focused element fire the callback. 1128 if (e.type=='keyup' && $.inArray( e.keyCode, excludedKeys ) !== -1) return; 1129 1130 // Check if timeout has been set. If it has, "reset" the clock and 1131 // start over again. 1132 if (timeoutReference) clearTimeout(timeoutReference); 1133 timeoutReference = setTimeout(function(){ 1134 // if we made it here, our timeout has elapsed. Fire the 1135 // callback 1136 doneTyping(el); 1137 }, timeout); 1138 }).on('blur',function(){ 1139 // If we can, fire the event since we're leaving the field 1140 doneTyping(el); 1141 }); 1142 }); 1143 } 1144 }); 1145}()); 1146 1147document.addEventListener("DOMContentLoaded", function() { 1148 // ÐÑовеÑÑем, еÑÑÑ Ð»Ð¸ в URL Ñ ÑÑ 1149 if(window.location.hash) { 1150 // ÐолÑÑаем знаÑение Ñ ÑÑа 1151 var hash = window.location.hash.substring(1); // ÐбÑезаем пеÑвÑй Ñимвол '#' 1152 // ÐÐ°Ñ Ð¾Ð´Ð¸Ð¼ ÑÐ»ÐµÐ¼ÐµÐ½Ñ Ð¿Ð¾ id и пÑокÑÑÑиваем к Ð½ÐµÐ¼Ñ 1153 var element = document.getElementById(hash); 1154 if (element) { 1155 setTimeout(function() { 1156 element.scrollIntoView(); 1157 }, 1000); 1158 } 1159 } 1160}); 1161 1162// ÐаÑÑомизаÑÐ¸Ñ Ð½Ð°ÑÑÑоек виджеÑа ÑаÑ-боÑа 1163document.addEventListener("DOMContentLoaded", function() { 1164 let isInited = false; 1165 const initialWidgetSettings = { 1166 offsetX: 20, 1167 offsetY: 20, 1168 }; 1169 1170 const recountWidgetSettings = () => { 1171 const newWidgetSettings = JSON.parse(JSON.stringify(initialWidgetSettings)) 1172 let yValue = initialWidgetSettings.offsetY; 1173 const hasCookieBar = !!document.body.classList.contains('body_global-hasBottomBar'); 1174 const hasMobileNav = !!document.body.classList.contains('body_has-bottom-nav'); 1175 1176 // УÑÑанавливаеÑÑÑ Ð´ÐµÑолÑное знаÑение Ð´Ð»Ñ ÑоÑÑоÑÐ½Ð¸Ñ Ñ Ð¿Ð»Ð°Ñкой пÑо кÑки и/или моб.навигаÑией 1177 if (hasCookieBar) { 1178 yValue = (window.innerWidth > globObj.breakpoints.sm) ? 100 : 160; 1179 } 1180 if (hasMobileNav && window.innerWidth <= globObj.breakpoints.lg) { 1181 yValue = hasCookieBar ? 220 : 80; 1182 } 1183 1184 if (document.body.classList.contains('body_page-basket-digital')) { 1185 // ÐÐ»Ñ ÑÑÑаниÑÑ ÑлекÑÑонной коÑзин
1185Ñ 1186 if (window.innerWidth <= globObj.breakpoints.lg) { 1187 yValue = (hasCookieBar) ? 320 : 180; 1188 } 1189 1190 } else if (['body_page-book', 'ab-component__leap', 'body_page-basket'].some(className => document.body.classList.contains(className))) { 1191 // ÐÐ»Ñ ÑÑÑÐ°Ð½Ð¸Ñ Ñ Ð»Ð¸Ð¿-Ð¼ÐµÐ½Ñ 1192 if (window.innerWidth <= globObj.breakpoints.lg) { 1193 yValue = (hasCookieBar) ? 240 : 100; 1194 } 1195 } 1196 1197 newWidgetSettings.offsetY = yValue; 1198 return newWidgetSettings; 1199 }; 1200 1201 const setSettings = (settings) => { 1202 if (window.fasttrack && window.fasttrack.setSettings) { 1203 window.fasttrack.setSettings(settings); 1204 } 1205 }; 1206 1207 // СобÑÑÐ¸Ñ onReady и onMounted в JS API виджеÑа не ÑÑабаÑÑваÑÑ Ð² Ñакой ÑеализаÑии (возможно, нÑжно вÑÑÑнÑÑ Ð¸Ð½Ð¸ÑиÑÑ 1208 // виджеÑ). 1209 // ÐоÑÑÐ¾Ð¼Ñ Ð¿ÑÐ¸Ñ Ð¾Ð´Ð¸ÑÑÑ Ð²ÑÑÑнÑÑ Ð¿ÑовеÑÑÑÑ Ð³Ð¾ÑовноÑÑÑ Ð²Ð¸Ð´Ð¶ÐµÑа. 1210 // СнаÑала пÑовеÑÑем, полÑÑен ли обÑÐµÐºÑ Ð²Ð¸Ð´Ð¶ÐµÑа. ÐÑли неÑ, Ñо делаем 500 попÑÑок Ñаз в 200мÑ. 1211 // ÐÑли ÑÑÐ°Ð·Ñ Ð¶Ðµ поÑле ÑÑого пÑимениÑÑ Ð½ÑжнÑе наÑÑÑойки, Ñо Ð²Ð¸Ð´Ð¶ÐµÑ Ð¸Ð½Ð¾Ð³Ð´Ð° Ð¼Ð¾Ð¶ÐµÑ Ð¿ÑимениÑÑ Ñвои наÑÑÑойки позднее. 1212 // ÐоÑÑÐ¾Ð¼Ñ Ð¶ÑÑÑко пÑименÑем наÑÑÑойки вÑÑÑнÑÑ Ñаз в 500Ð¼Ñ 5 Ñаз. Ðа ÑÑеÑÑÑ Ð¸ÑеÑаÑÐ¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ñваем кнопкÑ. 1213 // Ð Ñаком ÑлÑÑае минимизиÑÑеÑÑÑ Ð²ÐµÑоÑÑноÑÑÑ Ñого, ÑÑо Ð²Ð¸Ð´Ð¶ÐµÑ Ð±ÑÐ´ÐµÑ Ð¼ÐµÐ½ÑÑÑ Ð¿Ð¾Ð·Ð¸ÑиониÑование пÑи загÑÑзке ÑÑÑаниÑÑ. 1214 let initIntervalCnt = 0; 1215 const initInterval = setInterval(() => { 1216 if (window.fasttrack) { 1217 clearInterval(initInterval); 1218 1219 let mountIntervalCnt = 0; 1220 const mountInterval = setInterval(() => { 1221 setSettings(recountWidgetSettings()); 1222 1223 if (!isInited && mountIntervalCnt > 2) { 1224 const widget = document.querySelector('fstrk-widget'); 1225 if (widget) { 1226 widget.setAttribute('data-status', 'initialized'); 1227 isInited = true; 1228 } 1229 } 1230 1231 if (++mountIntervalCnt > 5) { 1232 clearInterval(mountInterval); 1233 } 1234 }, 500); 1235 } 1236 1237 if (++initIntervalCnt > 500) { 1238 clearInterval(initInterval); 1239 } 1240 }, 200); 1241 1242 // РеÑайз окна 1243 window.addEventListener('resize', globalFunctions.throttle(function(event){ 1244 setSettings(recountWidgetSettings()); 1245 }, 100)); 1246 1247 // ÐÑлавливаеÑÑÑ ÑобÑÑие ÑкÑÑÑÐ¸Ñ Ð¿Ð»Ð°Ñки 1248 document.addEventListener('ab.message.cookies.hide', () => { 1249 setSettings(recountWidgetSettings()); 1250 }); 1251}); 1252 1253 1254/** 1255 * ============================================================================ 1256 * Page Performance Orchestration (PPO) 1257 * ============================================================================ 1258 * 1259 * ÐазнаÑение 1260 * ---------- 1261 * PPO â ÑенÑÑализованнÑй диÑпеÑÑÐµÑ Ð²ÑÐ¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÐºÐ»Ð¸ÐµÐ½ÑÑÐºÐ¸Ñ Ð·Ð°Ð´Ð°Ñ Ð² завиÑимоÑÑи 1262 * Ð¾Ñ ÑоÑÑоÑÐ½Ð¸Ñ Ð·Ð°Ð³ÑÑзки ÑÑÑаниÑÑ Ð¸ полÑзоваÑелÑÑкого взаимодейÑÑвиÑ. 1263 * 1264 * ÐозволÑÐµÑ Ð¾ÑказаÑÑÑÑ Ð¾Ñ ÑазÑозненнÑÑ : 1265 * - setTimeout(...) 1266 * - requestIdleCallback(...) 1267 * - обÑабоÑÑиков DOMContentLoaded 1268 * - обÑабоÑÑиков window.load 1269 * - PerformanceObserver 1270 * внÑÑÑи оÑделÑнÑÑ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñов. 1271 * 1272 * ÐмеÑÑо ÑÑого каждÑй ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ Ð¾Ð¿Ð¸ÑÑÐ²Ð°ÐµÑ ÑолÑко ÑÑÐ»Ð¾Ð²Ð¸Ñ Ñвоего запÑÑка, 1273 * а PPO ÑамоÑÑоÑÑелÑно пÑÐ¸Ð½Ð¸Ð¼Ð°ÐµÑ ÑеÑение, когда вÑполниÑÑ Ð·Ð°Ð´Ð°ÑÑ. 1274 * 1275 * 1276 * --------------------------------------------------------------------------- 1277 * ÐÑÑ Ð¸ÑекÑÑÑа 1278 * --------------------------------------------------------------------------- 1279 * 1280 * PPO ÑоÑÑÐ¾Ð¸Ñ Ð¸Ð· ÑÑÐµÑ Ð¾ÑновнÑÑ ÑаÑÑей: 1281 * 1282 * 1. State 1283 * Ð¥ÑÐ°Ð½Ð¸Ñ ÑекÑÑее ÑоÑÑоÑние ÑÑÑаниÑÑ. 1284 * 1285 * 2. Event Sources 1286 * ÐбновлÑÑÑ ÑоÑÑоÑние ÑÑÑаниÑÑ Ð¿Ð¾ÑÑедÑÑвом PPO.set(...). 1287 * 1288 * 3. Tasks 1289 * ÐезавиÑимÑе задаÑи, заÑегиÑÑÑиÑованнÑе ÑеÑез PPO.register(...). 1290 * ÐоÑле каждого Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑоÑÑоÑÐ½Ð¸Ñ PPO пÑовеÑÑÐµÑ ÑÑÐ»Ð¾Ð²Ð¸Ñ Ð²ÑÐ¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ 1291 * вÑÐµÑ Ð·Ð°ÑегиÑÑÑиÑованнÑÑ Ð·Ð°Ð´Ð°Ñ. 1292 * 1293 * 1294 * --------------------------------------------------------------------------- 1295 * State 1296 * --------------------------------------------------------------------------- 1297 * 1298 * lcp 1299 * Largest Contentful Paint пÑоизоÑел. 1300 * 1301 * idle 1302 * СÑÑаниÑа ÑÑиÑаеÑÑÑ Ñвободной Ð´Ð»Ñ Ð²ÑÐ¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð²ÑоÑоÑÑепеннÑÑ Ð·Ð°Ð´Ð°Ñ. 1303 * 1304 * interaction 1305 * ÐолÑзоваÑÐµÐ»Ñ Ð½Ð°Ñал взаимодейÑÑвоваÑÑ Ñо ÑÑÑаниÑей. 1306 * 1307 * domReady 1308 * DOM полноÑÑÑÑ Ð¿Ð¾ÑÑÑоен (DOMContentLoaded). 1309 * 1310 * load 1311 * ÐолноÑÑÑÑ Ð·Ð°Ð²ÐµÑÑена загÑÑзка ÑÑÑаниÑÑ (window.load). 1312 * 1313 * 1314 * --------------------------------------------------------------------------- 1315 * API 1316 * --------------------------------------------------------------------------- 1317 * 1318 * PPO.set(key, value) 1319 * 1320 * ÐзменÑÐµÑ Ð·Ð½Ð°Ñение ÑоÑÑоÑÐ½Ð¸Ñ Ð¸ иниÑииÑÑÐµÑ Ð¿Ð¾Ð²ÑоÑнÑÑ Ð¿ÑовеÑÐºÑ 1321 * заÑегиÑÑÑиÑованнÑÑ Ð·Ð°Ð´Ð°Ñ. 1322 * 1323 * 1324 * PPO.register({ 1325 * condition(state) {}, 1326 * action(state) {} 1327 * }) 1328 * 1329 * РегиÑÑÑиÑÑÐµÑ Ð½Ð¾Ð²ÑÑ Ð·Ð°Ð´Ð°ÑÑ. 1330 * 1331 * condition(state) 1332 * ФÑнкÑÐ¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ð° веÑнÑÑÑ true, еÑли задаÑа Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð²Ñполнена. 1333 * 1334 * action(state) 1335 * Ðод, коÑоÑÑй Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð¾ вÑполниÑÑ Ð¿Ð¾Ñле вÑÐ¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÑÑловиÑ. 1336 * 1337 * ÐÐ°Ð¶Ð´Ð°Ñ Ð·Ð°Ð´Ð°Ñа вÑполнÑеÑÑÑ ÑолÑко один Ñаз. 1338 * 1339 * 1340 * PPO.debug() 1341 * 1342 * ÐÑÐ²Ð¾Ð´Ð¸Ñ ÑекÑÑее внÑÑÑеннее ÑоÑÑоÑние PPO. 1343 * 1344 * 1345 * --------------------------------------------------------------------------- 1346 * ÐÑÐ¸Ð¼ÐµÑ Ð¸ÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ 1347 * --------------------------------------------------------------------------- 1348 * 1349 * PPO.register({ 1350 * condition(state) { 1351 * return state.lcp && state.load; 1352 * }, 1353 * 1354 * action() { 1355 * initSlider(); 1356 * } 1357 * }); 1358 * 1359 * 1360 * --------------------------------------------------------------------------- 1361 * ÐÑинÑÐ¸Ð¿Ñ Ð¸ÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ 1362 * --------------------------------------------------------------------------- 1363 * 1364 * ⢠PPO не Ð·Ð½Ð°ÐµÑ Ð½Ð¸Ñего о конкÑеÑнÑÑ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½ÑÐ°Ñ ÑайÑа. 1365 * 1366 * ⢠ÐомпоненÑÑ Ð½Ðµ Ð´Ð¾Ð»Ð¶Ð½Ñ ÑамоÑÑоÑÑелÑно оÑÑлеживаÑÑ Ð·Ð°Ð³ÑÑÐ·ÐºÑ ÑÑÑаниÑÑ, 1367 * еÑли ÑооÑвеÑÑÑвÑÑÑее ÑоÑÑоÑние Ñже поддеÑживаеÑÑÑ PPO. 1368 *
1369 * ⢠PPO ÑвлÑеÑÑÑ ÐµÐ´Ð¸Ð½Ñм иÑÑоÑником инÑоÑмаÑии о ÑоÑÑоÑнии жизненного Ñикла 1370 * ÑÑÑаниÑÑ. 1371 * 1372 * ⢠ÐÑбÑе новÑе ÑоÑÑоÑÐ½Ð¸Ñ ÑекомендÑеÑÑÑ Ð´Ð¾Ð±Ð°Ð²Ð»ÑÑÑ Ð¸Ð¼ÐµÐ½Ð½Ð¾ в PPO, а не 1373 * ÑоздаваÑÑ ÑобÑÑвеннÑе незавиÑимÑе Ð¼ÐµÑ Ð°Ð½Ð¸Ð·Ð¼Ñ Ð¾ÑÑлеживаниÑ. 1374 * 1375 * 1376 * --------------------------------------------------------------------------- 1377 * ТекÑÑее ÑоÑÑоÑние ÑеализаÑии 1378 * --------------------------------------------------------------------------- 1379 * 1380 * РеализаÑÐ¸Ñ ÑвлÑеÑÑÑ MVP и покÑÑÐ²Ð°ÐµÑ Ð¾ÑновнÑе ÑÑенаÑии пÑоекÑа. 1381 * 1382 * ÐÑÑ Ð¸ÑекÑÑÑа допÑÑÐºÐ°ÐµÑ Ð´Ð°Ð»ÑнейÑее ÑаÑÑиÑение без Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿ÑблиÑного API, 1383 * напÑимеÑ: 1384 * 1385 * - пÑиоÑиÑеÑÑ Ð²ÑÐ¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ; 1386 * - гÑÑппиÑÐ¾Ð²ÐºÑ Ð·Ð°Ð´Ð°Ñ; 1387 * - блокиÑÑÑÑие задаÑи; 1388 * - Ñдаление заÑегиÑÑÑиÑованнÑÑ Ð·Ð°Ð´Ð°Ñ; 1389 * - полÑзоваÑелÑÑкие ÑобÑÑÐ¸Ñ ÑоÑÑоÑниÑ; 1390 * - дополниÑелÑнÑе ÑоÑÑоÑÐ½Ð¸Ñ Ð¶Ð¸Ð·Ð½ÐµÐ½Ð½Ð¾Ð³Ð¾ Ñикла ÑÑÑаниÑÑ. 1391 * 1392 * ============================================================================ 1393 */ 1394 1395window.PPO = window.PPO || (function () { 1396 1397 const state = { 1398 lcp: false, 1399 idle: false, 1400 interaction: false, 1401 domReady: false, 1402 load: false 1403 }; 1404 1405 const tasks = []; 1406 1407 function set(key, value) { 1408 state[key] = value; 1409 evaluate(); 1410 } 1411 1412 function register(task) { 1413 task.done = false; 1414 1415 task.run = function (state) { 1416 if (this.done) return; 1417 1418 if (task.condition(state)) { 1419 task.done = true; 1420 task.action(state); 1421 } 1422 }; 1423 1424 tasks.push(task); 1425 } 1426 1427 function evaluate() { 1428 for (let i = 0; i < tasks.length; i++) { 1429 const t = tasks[i]; 1430 if (!t.done) { 1431 t.run(state); 1432 } 1433 } 1434 } 1435 1436 function debug() { 1437 console.log('PPO state:', state); 1438 console.log('Tasks:', tasks); 1439 } 1440 1441 return { 1442 state, 1443 set, 1444 register, 1445 debug, 1446 }; 1447})(); 1448 1449 1450/** 1451 * ============================================================================ 1452 * LCP tracking 1453 * ============================================================================ 1454 */ 1455 1456new PerformanceObserver((list) => { 1457 for (const entry of list.getEntries()) { 1458 if (entry.entryType === 'largest-contentful-paint') { 1459 window.PPO.set('lcp', true); 1460 } 1461 } 1462}).observe({ type: 'largest-contentful-paint', buffered: true }); 1463 1464 1465/** 1466 * ============================================================================ 1467 * Interaction + Idle source (единÑй иÑÑоÑник ÑобÑÑий) 1468 * ============================================================================ 1469 */ 1470 1471(function () { 1472 1473 let lastInteraction = Date.now(); 1474 let idleScheduled = false; 1475 let idleTimer = null; 1476 1477 const IDLE_DELAY = 1500; 1478 const INTERACTION_COOLDOWN = 2000; 1479 1480 const passiveOptions = { passive: true }; 1481 1482 function notifyInteraction() { 1483 1484 const now = Date.now(); 1485 lastInteraction = now; 1486 1487 // ÑикÑиÑÑем пеÑвое взаимодейÑÑвие 1488 if (!PPO.state.interaction) { 1489 PPO.set('interaction', true); 1490 } 1491 1492 scheduleIdleCheck(); 1493 } 1494 1495 function scheduleIdleCheck() { 1496 1497 if (idleScheduled) return; 1498 idleScheduled = true; 1499 1500 const check = () => { 1501 1502 const now = Date.now(); 1503 const timeSinceInteraction = now - lastInteraction; 1504 1505 // еÑли недавно бÑло взаимодейÑÑвие â оÑкладÑваем 1506 if (timeSinceInteraction < INTERACTION_COOLDOWN) { 1507 idleTimer = setTimeout(check, INTERACTION_COOLDOWN); 1508 return; 1509 } 1510 1511 // LCP обÑзаÑелен Ð´Ð»Ñ idle 1512 if (!PPO.state.lcp) { 1513 idleScheduled = false;
1514 return; 1515 } 1516 1517 if ('requestIdleCallback' in window) { 1518 requestIdleCallback(() => { 1519 PPO.set('idle', true); 1520 }, { timeout: 2000 }); 1521 } else { 1522 setTimeout(() => { 1523 PPO.set('idle', true); 1524 }, IDLE_DELAY); 1525 } 1526 }; 1527 1528 idleTimer = setTimeout(check, IDLE_DELAY); 1529 } 1530 1531 const events = [ 1532 'pointerdown', 1533 'keydown', 1534 'touchstart', 1535 'wheel' 1536 ]; 1537 1538 events.forEach(evt => { 1539 window.addEventListener(evt, notifyInteraction, passiveOptions); 1540 }); 1541 1542 window.addEventListener('DOMContentLoaded', scheduleIdleCheck); 1543 window.addEventListener('load', scheduleIdleCheck); 1544 1545})(); 1546 1547 1548/** 1549 * ============================================================================ 1550 * DOM lifecycle states 1551 * ============================================================================ 1552 */ 1553 1554document.addEventListener('DOMContentLoaded', function () { 1555 window.PPO.set('domReady', true); 1556}); 1557 1558window.addEventListener('load', function () { 1559 window.PPO.set('load', true); 1560}); 1561 1562/* END PPO */ 1563 1564 1565/** 1566 * ============================================================================ 1567 * Local Storage Helper 1568 * ============================================================================ 1569 */ 1570 1571window.abStorageHelper = { 1572 has(key) { 1573 const value = localStorage.getItem(key); 1574 1575 if (!value) { 1576 return false; 1577 } 1578 1579 try { 1580 const { expiresAt } = JSON.parse(value); 1581 1582 if (expiresAt && Date.now() >= expiresAt) { 1583 localStorage.removeItem(key); 1584 1585 return false; 1586 } 1587 1588 return true; 1589 } catch { 1590 localStorage.removeItem(key); 1591 1592 return false; 1593 } 1594 }, 1595 1596 set(key, ttl) { 1597 localStorage.setItem( 1598 key, 1599 JSON.stringify({ 1600 expiresAt: Date.now() + ttl, 1601 }) 1602 ); 1603 }, 1604}; 1605 1606/* END Local Storage Helper */ 1607 1608//# sourceMappingURL=maps/global.js.map
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.