1/** 2 * UI Utilities module 3 * Helper functions for UI interactions and utilities 4 */ 5 6export function addAttributesToDisabledButtons() { 7 // No size chosen yet: that click must reach the inline validation, not open the cart panel. 8 jQuery(".single_add_to_cart_button.disabled:not(.wc-variation-selection-needed)").each(function () { 9 const $button = jQuery(this); 10 if (!$button.attr("data-toggle")) { 11 $button 12 .attr("data-toggle", "off-canvas") 13 .attr("data-target", "cart-panel"); 14 } 15 }); 16} 17 18export function loadRecaptcha(callback) { 19 if (!jQuery("#recaptcha-script").length) { 20 var script = document.createElement("script"); 21 script.src = 22 "https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit"; 23 script.id = "recaptcha-script"; 24 document.head.appendChild(script); 25 } else if (callback) { 26 callback(); 27 } 28} 29 30export function initializeRecaptcha() { 31 const $recaptchaElement = jQuery(".recaptcha-container"); 32 33 if ($recaptchaElement.length) { 34 $recaptchaElement.each(function () { 35 const $item = jQuery(this); 36 37 if (!$item.data("widget-id")) { 38 const widgetId = grecaptcha.render(this, { 39 sitekey: window.kapsula.captcha.sitekey, 40 theme: "light", 41 }); 42 43 $item.data("widget-id", widgetId); 44 45 $item 46 .closest(".woocommerce-form-register.register") 47 .on("submit", function (event) { 48 const response = grecaptcha.getResponse(widgetId); 49 if (response.length === 0) { 50 event.preventDefault(); 51 alert("ÐÑÐ´Ñ Ð»Ð°Ñка, пÑдÑвеÑдÑÑÑ, Ñо ви не ÑобоÑ."); 52 } 53 }); 54 } 55 }); 56 } 57} 58 59export function checkCartPanelAndInitSlider() { 60 const cartPanel = document.getElementById("cart-panel"); 61 62 if (cartPanel && cartPanel.classList.contains("open")) { 63 document.body.classList.add("offcanvas-opened"); 64 if (window.initRecommendedProductsSlider) { 65 window.initRecommendedProductsSlider(); 66 } 67 } 68} 69 70export function setupAccountNavScroll() { 71 const navAccountEdit = document.querySelector( 72 ".woocommerce-MyAccount-navigation" 73 ); 74 const SCROLL_SPEED = 30; 75 76 if (navAccountEdit) { 77 navAccountEdit.addEventListener("wheel", (event) => { 78 event.preventDefault(); 79 navAccountEdit.scrollLeft += event.deltaY * SCROLL_SPEED; 80 }); 81 82 let isTouching = false; 83 let startX = 0; 84 let scrollLeft = 0; 85 86 navAccountEdit.addEventListener("touchstart", (event) => { 87 isTouching = true; 88 startX = event.touches[0].pageX; 89 scrollLeft = navAccountEdit.scrollLeft; 90 }); 91 92 navAccountEdit.addEventListener("touchmove", (event) => { 93 if (!isTouching) return; 94 const moveX = event.touches[0].pageX - startX; 95 navAccountEdit.scrollLeft = scrollLeft - moveX; 96 }); 97 98 navAccountEdit.addEventListener("touchend", () => { 99 isTouching = false; 100 }); 101 } 102} 103 104export function setupSearchFieldBlur() { 105 const searchField = document.querySelector(".search-field"); 106 107 if (searchField !== null) { 108 searchField.addEventListener("focusout", () => { 109 searchField.blur(); 110 }); 111 112 document.addEventListener("touchend", () => { 113 if (document.activeElement === searchField) { 114 searchField.blur(); 115 } 116 }); 117 } 118} 119 120export function setupEmailVerification() { 121 let isRegisterButtonActive = false; 122 jQuery("body").on( 123 'submit', 124 '.woocommerce-form.woocommerce-form-register', 125 function (e) { 126 e.preventDefault(); 127 128 if (isRegisterButtonActive) { 129 return; 130 } 131 132 let $form = jQuery(this); 133 134 const printError = (message) => { 135 const errorMessage = $form.find('.woocommerce-error'); 136 if (errorMessage) { 137 errorMessage.remove(); 138 } 139 $form.prepend(message); 140 } 141 142 isRegisterButtonActive = true; 143 144 jQuery.post(
145 window.kapsula.ajaxURL, 146 { 147 'action': 'kapsula_create_customer', 148 'email': $form.find('input[name="email"]').val(), 149 'nonce': window.kapsula.nonce, 150 '_wp_http_referer': $form.find('input[name="_wp_http_referer"]').val(), 151 } 152 ).done(function (response) { 153 if (response.success && response.data.redirect) { 154 window.location.href = response.data.redirect; 155 } 156 157 printError(response.data.message); 158 159 isRegisterButtonActive = false; 160 }).fail(function () { 161 printError( 162 '<div class="woocommerce-error" role="alert"><ul class="error-message">' + 163 '<li>ÐÐ°Ð¿Ð¸Ñ Ð½Ðµ вдавÑÑ. СпÑобÑйÑе пÑзнÑÑe.</li>' + 164 '</ul></div>' 165 ) 166 167 isRegisterButtonActive = false; 168 }); 169 } 170 ); 171} 172 173export function setupContactFormLoading() { 174 document 175 .querySelectorAll(".page-template-contacts .wpcf7-form") 176 .forEach((el) => { 177 const submitButton = el.querySelector('input[type="submit"]'); 178 179 if (submitButton === null) { 180 return; 181 } 182 183 el.addEventListener("submit", () => { 184 submitButton.classList.add("loading"); 185 }); 186 187 el.addEventListener("wpcf7submit", () => { 188 submitButton.classList.remove("loading"); 189 }); 190 }); 191} 192 193export function setupSearchPopular() { 194 jQuery(document).ready(function ($) { 195 const $searchPopular = $('[data-el="search-popular"]'); 196 const $searchResult = $('[data-el="search-results"]'); 197 const $searchResultEmpty = $('[data-el="search-results-empty"]'); 198 if ($searchResult.length) { 199 let requestAction = null; 200 const inputWait = 500; 201 $('body') 202 .on('keyup', '.kapsula-search', function () { 203 if (requestAction !== null) { 204 clearTimeout(requestAction); 205 } 206 207 requestAction = setTimeout(() => { 208 const $input = $(this); 209 const $form = $input.closest('form'); 210 if ($input.val()) { 211 $searchForm.addClass('search-loading'); 212 $.ajax({ 213 url: kapsula.ajaxURL, 214 type: 'POST', 215 data: { 216 'action': 'kapsula_search_categories', 217 'nonce': $form.find('[name="nonce"]').val(), 218 's': $input.val(), 219 }, 220 success: (data) => { 221 if (data.data.html) { 222 $searchResultEmpty.addClass('hidden'); 223 $searchPopular.addClass('hidden'); 224 $searchResult.removeClass('hidden'); 225 } else { 226 $searchPopular.removeClass('hidden'); 227 } 228 $searchResult.html(data.data.html); 229 $searchBestsellersBlock.addClass('hidden'); 230 $searchForm.removeClass('search-loading'); 231 }, 232 error: (data) => { 233 console.log('error', data.data.error); 234 }, 235 }); 236 } else { 237 $searchResultEmpty.addClass('hidden'); 238 $searchResult.addClass('hidden'); 239 $searchPopular.removeClass('hidden'); 240 $searchBestsellersBlock.removeClass('hidden'); 241 } 242 }, inputWait); 243 }) 244 .on('focusout', '.kapsula-search', function () { 245 if (requestAction === null) { 246 return; 247 } 248 249 clearTimeout(requestAction); 250 }); 251 } 252 253 function closeSearch() { 254 $('[data-el="search-block"]').removeClass('opened'); 255 $('body').removeClass('search-opened'); 256 $('[data-el="search-results"]').html(''); 257 $searchPopular.removeClass('hidden'); 258 $searchBestsellersBlock.removeClass('hidden'); 259 $searchForm.find('#kapsula-search').val(''); 260 } 261 262 $('[data-event="close-search"]').on('click', function (e) { 263 e.stopPropagation(); 264 closeSearch(); 265 }); 266 267 $(document).on('click', 'body.search-opened', function (e) { 268 if ($(e.target).closest('[data-el="search-block"]').length === 0 269 && !$(e.target).is('[data-event="open-search"]') 270 && !$(e.target).closest('[data-event="open-search"]').length) { 271 closeSearch(); 272 } 273 }); 274 275 const $searchForm = $('#kapsula-search-form'); 276 if ($searchForm.length) { 277 $searchForm.on('submit', function (e) { 278 e.preventDefault(); 279 280 const searched = $searchForm.find('#kapsula-search').val(); 281 if (searched.length > 0) { 282 window.location.href = 'https://' + window.location.hostname + '?post_type=product&s=' + searched; 283 } 284 }); 285 } 286 const $searchBestsellers = $('[data-el="search-bestsellers"]'); 287 const $searchBestsellersBlock = $('[data-el="search-bestsellers-wrapper"]'); 288 289 $('[data-event="open-search"]').on('click', function () { 290 $('[data-el="search-block"]').addClass('opened'); 291 $('body').addClass('search-opened'); 292 293 if ($searchBestsellers.length && $searchBestsellers.children().length === 0) { 294 $searchBestsellers.addClass('search-loading'); 295 $.ajax({ 296 url: kapsula.ajaxURL, 297 type: 'POST', 298 data: { 299 'action': 'load_bestseller_products', 300 'nonce': bestsellers.nonce ?? '', 301 }, 302 success: (data) => { 303 $searchBestsellers.html(data.data.html); 304 $searchBestsellers.removeClass('search-loading'); 305 }, 306 error: (data) => { 307 console.log('error', data.data.error); 308 }, 309 }); 310 } 311 }); 312 }); 313} 314 315export function setupMenu() { 316 jQuery(document).ready(function ($) { 317 $('[data-event="toggle-gender"]').on('click', function () { 318 const gender = $(this).data('type'); 319 $('[data-el="menu-block"], [data-event="toggle-gender"]').removeClass('active'); 320 $(`[data-el="menu-block"][data-type="${gender}"]`).addClass('active'); 321 $(this).addClass('active'); 322 }); 323 324 let menuTimer; 325 $('.menu-block .level-0.menu-item-has-children').hover(function () { 326 const $this = $(this); 327 328 clearTimeout(menuTimer); 329 menuTimer = setTimeout(() => { 330 $('.level-0.menu-item-has-children').not($this).removeClass('hover'); 331 $this.addClass('hover'); 332 333 $('body').addClass('menu-opened'); 334 }, 200); 335 }, function () { 336 clearTimeout(menuTimer); 337 const $this = $(this); 338 menuTimer = setTimeout(() => { 339 $this.removeClass('hover'); 340 if ($('.level-0.menu-item-has-children.hover').length === 0) { 341 $('body').removeClass('menu-opened'); 342 } 343 }, 300); 344 }); 345 346 $('[data-event="open-mobile-menu"]').on('click', function () { 347 $('[data-el="mobile-menu"]').addClass('opened'); 348 $('body').addClass('mobile-menu-opened'); 349 }); 350 $('[data-event="close-mobile-menu"]').on('click', function () { 351 $('[data-el="mobile-menu"]').removeClass('opened'); 352 $('body').removeClass('mobile-menu-opened menu-opened'); 353 }); 354 $('.menu-item.level-0').on('click', function () { 355 $(this).addClass('opened'); 356 $('[data-el="mobile-menu"]').addClass('submenu-opened'); 357 }); 358 $('[data-event="close-submenu"]').on('click', function (e) { 359 e.stopPropagation(); 360 $(this).closest('.level-0').removeClass('opened'); 361 $('[data-el="mobile-menu"]').removeClass('submenu-opened'); 362 }) 363 364 let touchstartX = 0; 365 let touchendX = 0; 366 const SWIPE_THRESHOLD = 50; 367 368 document.addEventListener('touchstart', (e) => { 369 const target = e.target.closest('.menu-item-has-children.opened'); 370 if (!target) return; 371 372 touchstartX = e.changedTouches[0].screenX; 373 }, { passive: false }); 374 375 document.addEventListener('touchend', (e) => { 376 const target = e.target.closest('.menu-item-has-children.opened'); 377 if (!target) return; 378 379 touchendX = e.changedTouches[0].screenX; 380 handleSwipe(target); 381 }, { passive: false }); 382 383 function handleSwipe(element) { 384 if (touchstartX - touchendX > SWIPE_THRESHOLD) { 385 element.classList.remove('opened'); 386 $('[data-el="mobile-menu"]').removeClass('submenu-opened'); 387 } 388 } 389 }); 390} 391 392export function addDragScrollElement() { 393 const dragScrollContainers = document.querySelectorAll('[data-el="drag-scroll-container"]'); 394 395 if (!dragScrollContainers.length || window.matchMedia('(pointer: coarse)').matches) return; 396
397 dragScrollContainers.forEach((dragScrollContainer) => { 398 let isDown = false; 399 let startX, startY; 400 let scrollLeft, scrollTop; 401 let moved = false; 402 let touchAxis = null; 403 const dragThreshold = 5; 404 405 let velocityX = 0; 406 let velocityY = 0; 407 let lastX = 0; 408 let lastY = 0; 409 let lastTime = 0; 410 let rafId = null; 411 412 const FRICTION = 0.92; 413 const MIN_VELOCITY = 0.5; 414 415 const velocityHistory = []; 416 const HISTORY_MS = 100; 417 418 function recordVelocity(x, y) { 419 const now = performance.now(); 420 velocityHistory.push({ x, y, t: now }); 421 422 while (velocityHistory.length > 1 && now - velocityHistory[0].t > HISTORY_MS) { 423 velocityHistory.shift(); 424 } 425 } 426 427 function computeVelocity() { 428 if (velocityHistory.length < 2) return { vx: 0, vy: 0 }; 429 430 const first = velocityHistory[0]; 431 const last = velocityHistory[velocityHistory.length - 1]; 432 const dt = last.t - first.t; 433 434 if (dt === 0) return { vx: 0, vy: 0 }; 435 436 return { 437 vx: (last.x - first.x) / dt * 16, // scale to ~60fps frame 438 vy: (last.y - first.y) / dt * 16, 439 }; 440 } 441 442 function startInertia() { 443 if (rafId) cancelAnimationFrame(rafId); 444 445 function loop() { 446 velocityX *= FRICTION; 447 velocityY *= FRICTION; 448 449 const absVX = Math.abs(velocityX); 450 const absVY = Math.abs(velocityY); 451 452 if (absVX < MIN_VELOCITY && absVY < MIN_VELOCITY) { 453 dragScrollContainer.style.scrollBehavior = 'smooth'; 454 return; 455 } 456 457 dragScrollContainer.scrollLeft -= velocityX; 458 dragScrollContainer.scrollTop -= velocityY; 459 460 rafId = requestAnimationFrame(loop); 461 } 462 463 rafId = requestAnimationFrame(loop); 464 } 465 466 function stopInertia() { 467 if (rafId) { 468 cancelAnimationFrame(rafId); 469 rafId = null; 470 } 471 } 472 473 function onStart(x, y) { 474 stopInertia(); 475 isDown = true; 476 moved = false; 477 touchAxis = null; 478 velocityHistory.length = 0; 479 velocityX = 0; 480 velocityY = 0; 481 startX = x - dragScrollContainer.offsetLeft; 482 startY = y - dragScrollContainer.offsetTop; 483 scrollLeft = dragScrollContainer.scrollLeft; 484 scrollTop = dragScrollContainer.scrollTop; 485 lastX = x; 486 lastY = y; 487 lastTime = performance.now(); 488 dragScrollContainer.style.scrollBehavior = 'auto'; 489 } 490 491 function onMove(x, y, e, inputType = 'mouse') { 492 if (!isDown) return; 493 494 const walkX = (x - dragScrollContainer.offsetLeft) - startX; 495 const walkY = (y - dragScrollContainer.offsetTop) - startY; 496 497 if (Math.abs(walkX) > dragThreshold || Math.abs(walkY) > dragThreshold) { 498 moved = true; 499 500 if (inputType === 'touch' && !touchAxis) { 501 touchAxis = Math.abs(walkX) >= Math.abs(walkY) ? 'x' : 'y'; 502 } 503 } 504 505 if (moved) { 506 if (inputType === 'touch' && touchAxis === 'y') { 507 isDown = false; 508 return; 509 } 510 511 if (inputType === 'touch') { 512 e.preventDefault(); 513 } 514 515 recordVelocity(x, y); 516 517 dragScrollContainer.scrollLeft = scrollLeft - walkX; 518 519 if (inputType !== 'touch') { 520 dragScrollContainer.scrollTop = scrollTop - walkY; 521 } 522 } 523 } 524 525 function onEnd(inputType = 'mouse') { 526 if (!isDown) return; 527 isDown = false; 528 529 if (moved) { 530 const { vx, vy } = computeVelocity(); 531 velocityX = vx; 532 533 velocityY = inputType === 'touch' ? 0 : vy; 534 535 startInertia(); 536 } else { 537 dragScrollContainer.style.scrollBehavior = 'smooth'; 538 } 539 } 540 541 dragScrollContainer.addEventListener('mousedown', (e) => { 542 onStart(e.pageX, e.pageY); 543 const isLink = e.target.closest('a'); 544 if (isLink) e.preventDefault(); 545 }); 546 547 dragScrollContainer.addEventListener('mousemove', (e) => { 548 onMove(e.pageX, e.pageY, e, 'mouse'); 549 }); 550 551 dragScrollContainer.addEventListener('mouseup', () => onEnd('mouse')); 552 dragScrollContainer.addEventListener('mouseleave', () => onEnd('mouse')); 553 554 dragScrollContainer.addEventListener('click', (e) => { 555 if (moved) { 556 e.preventDefault(); 557 e.stopPropagation(); 558 } 559 }, true); 560 561 dragScrollContainer.addEventListener('touchstart', (e) => { 562 const touch = e.touches[0]; 563 onStart(touch.pageX, touch.pageY); 564 }, { passive: false }); 565 566 dragScrollContainer.addEventListener('touchmove', (e) => { 567 const touch = e.touches[0]; 568 onMove(touch.pageX, touch.pageY, e, 'touch'); 569 }, { passive: false }); 570 571 dragScrollContainer.addEventListener('touchend', (e) => { 572 if (moved && touchAxis === 'x') { 573 e.preventDefault(); 574 } 575 onEnd('touch'); 576 }, { passive: false }); 577 578 dragScrollContainer.addEventListener('touchcancel', () => onEnd('touch')); 579 }); 580} 581 582export function stickyHeader() { 583 const body = document.querySelector('body'); 584 585 let lastScrollY = window.scrollY; 586 function debounce(fn, delay) { 587 let timer; return (...args) => { 588 clearTimeout(timer); 589 timer = setTimeout(() => fn(...args), delay); 590 }; 591 } 592 const handleScroll = debounce(() => { 593 const currentScrollY = window.scrollY; 594 if (currentScrollY < lastScrollY) { 595 body.classList.add('header-sticky'); 596 } else { 597 body.classList.remove('header-sticky'); 598 } 599 lastScrollY = currentScrollY; 600 }, 50); 601 602 window.addEventListener('scroll', handleScroll); 603}
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.