1function csrfSafeMethod(method) { 2 // these HTTP methods do not require CSRF protection 3 return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)) 4} 5 6function sleep(ms) { 7 return new Promise(resolve => setTimeout(resolve, ms)); 8} 9 10 11function getCookie(name) { 12 let cookieValue = null 13 if (document.cookie && document.cookie != '') { 14 let cookies = document.cookie.split(';') 15 for (let i = 0; i < cookies.length; i++) { 16 let cookie = cookies[i].trim() 17 if (cookie.substring(0, name.length + 1) == (name + '=')) { 18 cookieValue = decodeURIComponent(cookie.substring(name.length + 1)) 19 break 20 } 21 } 22 } 23 return cookieValue 24} 25 26function setCookie(key, value, options) { // options = {expires: '', path: '', domain: '', secure: ''} 27 if (typeof value != 'undefined') { 28 options = options || {} 29 if (value === null) { 30 value = '' 31 options.expires = -1 32 } 33 let expires = '' 34 if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { 35 let date 36 if (typeof options.expires == 'number') { 37 date = new Date() 38 date.setTime(date.getTime() + (options.expires * 1000)) 39 } else { 40 date = options.expires 41 } 42 expires = '; expires=' + date.toUTCString() 43 } 44 const path = options.path ? '; path=' + (options.path) : '' 45 const domain = options.domain ? '; domain=' + (options.domain) : '' 46 const secure = options.secure ? '; secure' : '' 47 const same_site = options.same_site ? '; samesite=' + (options.same_site) : '' 48 document.cookie = [key, '=', value, expires, path, domain, secure, same_site].join('') 49 } 50} 51 52function escapeHtml(text) { 53 const map = { 54 '&': '&', 55 '<': '<', 56 '>': '>', 57 '"': '"', 58 "'": ''' 59 }; 60 61 return text.replace(/[&<>"']/g, function(m) { return map[m]; }); 62} 63 64function onlyUnique(value, index, self) { 65 return self.indexOf(value) === index 66} 67 68function recentlyViewed(id) { 69 let data = getCookie('gg_rv') 70 data = data ? data.split(' ') : [] 71 data.push(id.toString()) 72 data = data.filter(onlyUnique).slice(-5) 73 setCookie('gg_rv', data.join(' '), {'expires': 365 * 24 * 3600, 'path': '/'}) 74} 75 76function checkStatus(response) { 77 if (response.status >= 200 && response.status < 300) { 78 return response 79 } else { 80 let error = new Error(response.statusText) 81 error.response = response 82 throw error 83 } 84} 85 86function parseJSON(response) { 87 return response.json() 88} 89 90function get_url_with_locale(url, locale_code) { 91 const host = window.location.hostname; 92 const prefix_language = window.location.hostname === 'www.gamersgate.es' ? 'es' : 'en'; 93 const prefix = locale_code == prefix_language ? '' : '/' + locale_code 94 return prefix + url 95} 96 97function timestamp_to_datetime_format(timestamp, locale) { 98 const date = new Date(timestamp*1000) 99 return date.toLocaleDateString(locale) + ' ' + gettext('at') + ' ' + date.toLocaleTimeString(locale, {'hour12': false, 'hour': '2-digit', 'minute': '2-digit'}) 100} 101 102function validatorEmail(email) { 103 let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 104 return email !== '' && re.test(String(email).toLowerCase()) 105} 106 107function validatorRepeat(value, repeat) { 108 return repeat !== '' && !(repeat.length > 0 && repeat !== value) 109} 110 111function validatorEmpty(value) { 112 return value !== '' 113} 114 115function validatorPasswordLength(value) { 116 return value !== '' && value.length >= 8 117} 118 119function validatorPasswordChar(value) { 120 let re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,34}/ 121 return value !== '' && re.test(String(value)) 122} 123 124String.prototype.format = function() { 125 a = this; 126 for (k in arguments) { 127 a = a.replace("{" + k + "}", arguments[k]) 128 } 129 return a 130} 131 132class Cart { 133 constructor(cart_items, language) { 134 this.url = '/api/cart/' 135 this.cart_items = cart_items 136 this.language = language 137 this.max = 3 138 this.min = 1 139 this.wait = false 140 this.AddItemHandlerInstance = this.addItemHandler.bind(this) 141 this.render() 142 this.listen() 143 this.updateCartItemsInfo(null) 144 return this 145 } 146 147 render() { 148 this.updateCartItemsInfo(this.cart_items.size)
149 this.cart_items.forEach(function(id) { 150 const elements = document.querySelectorAll('.cart--add-button[data-id="' + id + '"]') 151 for (let index = 0; index < elements.length; index++) { 152 elements[index].addEventListener('click', function() {top.location.href='/cart/'}) 153 elements[index].innerHTML = gettext('In cart') 154 elements[index].classList.add('item_in_cart') 155 } 156 }) 157 } 158 159 listen() { 160 this.listenCatalog() 161 162 document.querySelectorAll('.cart--remove-button').forEach(function(elem) { 163 elem.addEventListener('click', function(event) { 164 event.preventDefault() 165 this.remove(event.target.dataset.id) 166 }.bind(this)) 167 }.bind(this)) 168 169 document.querySelectorAll('.cart--update-button a').forEach(function(elem) { 170 elem.addEventListener('click', function(event) { 171 this.update(elem.dataset.id, elem.dataset.value) 172 }.bind(this)) 173 }.bind(this)) 174 175 document.addEventListener('catalog-refresh', function() { 176 this.render() 177 this.listenCatalog() 178 }.bind(this)) 179 180 document.addEventListener('moved-to-favorites', function(event) { 181 this.removeDone(event.detail.data) 182 }.bind(this)) 183 } 184 185 listenCatalog() { 186 document.querySelectorAll('.cart--add-button').forEach(function(elem) { 187 if (!elem.classList.contains('item_in_cart')) { 188 elem.addEventListener('click', this.AddItemHandlerInstance) 189 } 190 }.bind(this)) 191 document.addEventListener('addToCart', (e) => { 192 const detailData = e.detail; 193 this.add(detailData.product.id); 194 }); 195 } 196 197 addItemHandler(event) { 198 if (!event.target.classList.contains('item_in_cart')) { 199 this.add(event.target.dataset.id) 200 } 201 } 202 203 add(product_id) { 204 if (!this.wait) { 205 this.request(product_id, 'add') 206 } 207 } 208 209 remove(product_id) { 210 if (!this.wait) { 211 this.request(product_id, 'remove') 212 } 213 } 214 215 update(product_id, value) { 216 if (!this.wait && value >= this.min && value <= this.max) { 217 this.request(product_id, 'update', value) 218 } 219 } 220 221 request(product_id, action, count=1) { 222 this.wait = true 223 const formData = new FormData() 224 formData.append('id', product_id) 225 formData.append('action', action) 226 formData.append('count', count) 227 fetch(get_url_with_locale(this.url, this.language), { 228 method: 'POST', 229 headers: { 230 'X-CSRFToken': getCookie('csrftoken') 231 }, 232 body: formData 233 }) 234 .then(checkStatus) 235 .then(parseJSON) 236 .then(function(data) { 237 this.wait = false 238 return data 239 }.bind(this)) 240 .then(this[action+'Done'].bind(this)) 241 .catch(function() { 242 this.wait = false 243 new PopUp().showErrorPopUp(gettext('An error occurred, try again later')) 244 }.bind(this)) 245 } 246 247 addDone(data) { 248 if (data.need_check_age) { 249 if (data.need_age_request) { 250 new PopUp().showAgeRequest({ 251 id: data.product.id, 252 name: data.product.name, 253 image: data.product.image, 254 }); 255 } else { 256 new PopUp().showAgeError({ 257 id: data.product.id, 258 name: data.product.name, 259 image: data.product.image, 260 }); 261 } 262 } 263 if (data.blocked) { 264 new PopUp().showBlockedCartPopUp(data) 265 } 266 if (data.success) { 267 document.dispatchEvent(new CustomEvent('cart_updated', {detail: {data: data}})) 268 this.cart_items.add(data.product.id) 269 this.render() 270 new PopUp().showAddToCartPopUp(data) 271 } 272 if (data.error) { 273 new PopUp().showErrorPopUp(data.error) 274 } 275 } 276 277 removeDone(data) { 278 if (data.success && data.products.length == 0) { 279 document.dispatchEvent(new CustomEvent('cart_updated', {detail: {data: data}})) 280 location.reload() 281 return 282 } 283 else if (data.success) { 284 document.dispatchEvent(new CustomEvent('cart_updated', {detail: {data: data}})) 285 this.updatePaymentMethods(data) 286 this.updateCartItemsInfo(data.products.length) 287 this.updateAmountInfo(data) 288 let cartItemNode = document.querySelector('.cart-item[data-id="' + data.id + '"]') 289 if (cartItemNode) { 290 cartItemNode.innerHTML = '' 291 cartItemNode.remove() 292 } 293 } 294 295 if (data.error) { 296 new PopUp().showErrorPopUp(data.error) 297 } else { 298 this.updateDone(data) 299 } 300 } 301 302 updateDone(data) { 303 if (data.success && data.products.length == 0) { 304 document.dispatchEvent(new CustomEvent('cart_updated', {detail: {data: data}})) 305 location.reload() 306 } 307 else if (data.success) { 308 document.dispatchEvent(new CustomEvent('cart_updated', {detail: {data: data}})) 309 this.updateAmountInfo(data) 310 this.updatePaymentMethods(data)
311 data.products.forEach(function(product) { 312 const element = document.querySelector('.cart-item[data-id="' + product.id + '"]') 313 element.querySelector('.cart-item--price').innerHTML = (product.full_total == product.total ? '' : ('<span>' + product.full_total.toString() + '</span>')) + data.currency_symbol + product.total.toString() 314 const errorsContainer = element.querySelector('.cart-item--errors') 315 if (errorsContainer) { 316 errorsContainer.innerHTML = '' 317 errorsContainer.remove() 318 } 319 if (product.errors) { 320 element.querySelector('.cart-item--data').insertAdjacentHTML('beforeend', '<div class="cart-item--errors"><div class="form-error">' + product.errors.join('<br>') + '</div></div>') 321 } 322 }) 323 } 324 325 this.updateCartCouponErrors(data.coupon_errors) 326 327 if (data.error) { 328 new PopUp().showErrorPopUp(data.error) 329 } 330 } 331 332 updateCartCouponErrors(errors) { 333 const form = document.querySelector('#cart-coupon-form') 334 if (form) { 335 const errorCouponContainers = form.querySelectorAll('.form-error') 336 errorCouponContainers.forEach(function (elem) { 337 elem.innerHTML = '' 338 elem.remove() 339 }) 340 if (errors) { 341 errors.forEach(function (error) { 342 form.insertAdjacentHTML('beforeend', '<div class="form-error">' + error + '</div>') 343 }) 344 } 345 } 346 } 347 348 updateCartItemsInfo(counter) { 349 const elem = document.querySelector('#cart_link') 350 351 if (elem) { 352 if (counter === null) { 353 counter = elem.dataset.count 354 } 355 356 elem.style.setProperty('--cart-count', counter > 0 ? "'" + counter + "'" : "none") 357 } 358 } 359 360 updatePaymentMethods(data) { 361 const elements = document.querySelectorAll('.cart-payment-item[data-is_account="true"]') 362 elements.forEach(function(elem) { 363 if (data.account_payment_allowed === true) { 364 elem.style.display = '' 365 } else if (data.account_payment_allowed === false) { 366 elem.style.display = 'none' 367 } 368 }) 369 } 370 371 updateAmountInfo(data) { 372 const elemAmount = document.querySelector('.cart-total-value span') 373 if (elemAmount !== null) { 374 elemAmount.innerHTML = data.currency_symbol + data.amount.toString() 375 } 376 const elemDiscount = document.querySelector('.cart-total-description #discount_total') 377 if (elemDiscount !== null) { 378 elemDiscount.innerHTML = data.currency_symbol + data.discount.toString() 379 } 380 const elemTax = document.querySelector('.cart-tax-value') 381 if (elemTax !== null) { 382 let htmlTax = '' 383 if (data.taxes) { 384 for (let index = 0; index < data.taxes.length; index++) { 385 htmlTax += data.taxes[index].type + ': ' + data.currency_symbol + data.taxes[index].tax.toString() + '<br>' 386 } 387 } 388 elemTax.innerHTML = htmlTax 389 } 390 const elemCouponNotification = document.querySelector('.cart-total-description #no_coupon_applied') 391 if (elemCouponNotification) { 392 elemCouponNotification.innerHTML = data.coupon === null ? '(' + elemCouponNotification.dataset.text + ')' : '' 393 } 394 } 395} 396 397class Favorite { 398 constructor(is_auth, language, is_favorit_page) { 399 this.url = '/api/favorite/' 400 this.is_auth = is_auth 401 this.language = language 402 this.is_favorit_page = false 403 404 this.handlerInstance = this.handler.bind(this) 405 406 this.listen() 407 408 return this 409 } 410 411 setIsFavoritPage() { 412 this.is_favorit_page = true 413 } 414 415 listen() { 416 this.listenCatalog() 417 418 document.addEventListener('catalog-refresh', function() { 419 this.listenCatalog() 420 }.bind(this)) 421 } 422 423 listenCatalog() { 424 document.querySelectorAll('.favorite--button').forEach(function(elem) { 425 elem.addEventListener('click', this.handlerInstance) 426 }.bind(this)) 427 } 428 429 handler(event) { 430 event.preventDefault() 431 if (!this.is_auth) { 432 new PopUp().showAddFavoriteAuth() 433 } 434 else if (event.target.classList.contains('is_active')) { 435 this.request(event.target.dataset.id, 'delete') 436 } 437 else if (event.target.dataset.action) { 438 this.request(event.target.dataset.id, event.target.dataset.action) 439 } 440 else { 441 this.request(event.target.dataset.id, 'add') 442 } 443 } 444 445 request(product_id, action) { 446 const formData = new FormData() 447 formData.append('id', product_id) 448 formData.append('action', action) 449 fetch(get_url_with_locale(this.url, this.language), { 450 method: 'POST', 451 headers: { 452 'X-CSRFToken': getCookie('csrftoken') 453 }, 454 body: formData 455 }) 456 .then(checkStatus) 457 .then(parseJSON) 458 .then(function(data) { 459 if (data.hasOwnProperty('success') && data.success) { 460 const element = document.querySelector('.favorite--button[data-id="' + product_id + '"]') 461 if (action == 'move-to-favorites') { 462 let elem = document.querySelector('.cart-item[data-id="' + data.id + '"]') 463 let item = { 464 item_id: elem.dataset.sku, 465 item_name: elem.dataset.name, 466 brand: elem.dataset.vendor_name, 467 item_list_id: 'cart', 468 currency: elem.dataset.currency, 469 price: elem.dataset.price, 470 quantity: elem.dataset.quantity 471 } 472 gtag("event", "add_to_wishlist", { 473 send_to: 'G-4WET38ZXRM', 474 item_list_id: 'cart', 475 items: [item] 476 }) 477 document.dispatchEvent(new CustomEvent('moved-to-favorites', {detail: {data: data}})) 478 } 479 else if (action == 'add') { 480 element.classList.add('is_animation') 481 sleep(500).then(() => { 482 element.classList.add('is_active') 483 element.classList.remove('is_animation') 484 }) 485 try { 486 let catalog = element.closest('.catalog-list') 487 let elem = element.closest('.product--item') 488 let item = { 489 item_id: elem.dataset.sku, 490 item_name: elem.dataset.name, 491 brand: elem.dataset.vendor_name, 492 item_list_id: catalog.dataset.ga_slug, 493 currency: elem.dataset.currency, 494 price: elem.dataset.price, 495 quantity: 1 496 } 497 gtag("event", "add_to_wishlist", { 498 send_to: 'G-4WET38ZXRM', 499 item_list_id: catalog.dataset.ga_slug, 500 items: [item] 501 }) 502 } catch {} 503 } 504 else if (action == 'delete') { 505 element.classList.add('is_animation') 506 sleep(500).then(() => { 507 element.classList.remove('is_active') 508 element.classList.remove('is_animation') 509 }) 510 if (this.is_favorit_page) { 511 const parentElement = element.parentNode.parentNode.parentNode 512 parentElement.innerHTML = '' 513 parentElement.remove() 514 515 if (document.querySelectorAll('.catalog-item-container') === null || document.querySelectorAll('.catalog-item-container').length == 0) { 516 location.reload() 517 } 518 } 519 } 520 } 521 else if (data.hasOwnProperty('blocked') && data.blocked) { 522 new PopUp().showBlockedSystemAlert(data.popup); 523 } 524 else { 525 throw new Error('') 526 } 527 }.bind(this)) 528 .catch(function(e) { 529 new PopUp().showErrorPopUp(gettext('An error occurred, try again later')) 530 }.bind(this)) 531 } 532} 533 534class PopUp { 535 constructor() { 536 this.id = 'popup' 537 this.btnCloseClass = 'popup-close' 538 this.containerClass = '' 539 this.headline = '' 540 this.bodyHtml = '' 541 this.footerHtml = '' 542 this.showPopupCloseButton = true 543 this.runCloseEvent = true 544 this.additionalPopupClass = '' 545 this.isModal = false 546 this.afterClose = function() {} 547 this.removeNode() 548 return this 549 } 550 551 setAdditionalPopupClass(value) { 552 this.additionalPopupClass = value 553 return this 554 } 555 556 setClass(containerClass) { 557 this.containerClass = containerClass 558 return this 559 } 560 561 setHeadline(headline) { 562 this.headline = headline 563 return this 564 } 565 566 setBodyHtml(bodyHtml) { 567 this.bodyHtml = bodyHtml 568 return this 569 } 570 571 setFooterHtml(footerHtml) { 572 this.footerHtml = footerHtml 573 return this 574 } 575 576 show(id=null, attachment=false) { 577 id = id === null ? this.id : id 578 let popupHtml = '' 579 popupHtml += '<div id="' + id + '" class="popup ' + this.additionalPopupClass + '">' 580 popupHtml += '<div class="popup-main ' + this.containerClass + '" id="' + (attachment ? 'top-pop-up-attachment' : 'top-p
580op-up') + '">' 581 if (this.headline != '') { 582 popupHtml += '<div class="popup--header"><h4>' + this.headline + '</h4></div>' 583 } 584 if (this.bodyHtml != '') { 585 popupHtml += '<div class="popup--body">' + this.bodyHtml + '</div>' 586 } 587 if (this.footerHtml != '') { 588 popupHtml += '<div class="popup--footer">' + this.footerHtml + '</div>' 589 } 590 if (this.showPopupCloseButton) { 591 popupHtml += '<div class="popup--close-button popup-close"></div>' 592 } 593 popupHtml += '</div>' 594 popupHtml += '</div>' 595 if (attachment) { 596 document.querySelector('#' + this.id).insertAdjacentHTML('afterend', popupHtml) 597 } 598 else { 599 document.querySelector('main.wrapper').insertAdjacentHTML('beforebegin', popupHtml) 600 } 601 document.querySelector('body').style.overflow = 'hidden' 602 if (this.runCloseEvent) { 603 setTimeout(this.closeEvent.bind(this), 500) 604 } 605 } 606 607 removeNode(id=null) { 608 id = id === null ? this.id : id 609 let node = document.getElementById(id) 610 if (node && node.parentNode) { 611 node.parentNode.removeChild(node) 612 } 613 document.querySelector('body').style.overflow = 'auto' 614 } 615 616 close(id=null) { 617 id = id === null ? this.id : id 618 this.removeNode(id) 619 if (this.afterClose) { 620 this.afterClose() 621 this.afterClose = function() {} 622 } 623 } 624 625 closeEvent() { 626 let element = document.querySelector('#' + this.id + ' > .popup-main') 627 628 let isVisible = function(elem) { 629 return !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) 630 } 631 632 let removeNode = function() { 633 removeClickListener() 634 this.close() 635 }.bind(this) 636 637 let outsideClickListener = function(event) { 638 if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null 639 element.style.display = 'none' 640 removeNode() 641 } 642 } 643 644 let removeClickListener = function() { 645 document.removeEventListener('click', outsideClickListener) 646 } 647 648 if (!this.isModal) { 649 document.addEventListener('click', outsideClickListener) 650 } 651 652 this.closeButtonHandler() 653 } 654 655 closeButtonHandler(id=null) { 656 id = id === null ? this.id : id 657 658 let closeButtonElements = document.querySelectorAll('#' + id + ' .' + this.btnCloseClass) 659 660 let removeNode = function() { 661 removeClickListener() 662 this.close(id) 663 }.bind(this) 664 665 let removeClickListener = function() { 666 if (closeButtonElements !== null) { 667 for (let index = 0; index < closeButtonElements.length; index++) { 668 closeButtonElements[index].removeEventListener('click', removeNode) 669 } 670 } 671 } 672 673 if (closeButtonElements !== null) { 674 for (let index = 0; index < closeButtonElements.length; index++) { 675 closeButtonElements[index].addEventListener('click', removeNode) 676 } 677 } 678 } 679 680 showErrorPopUp(headline, body=null, footer=null) { 681 let popup = this.setClass('popup-container-error').setHeadline(headline) 682 if (body !== null) { 683 popup = popup.setBodyHtml(body) 684 } 685 if (footer !== null) { 686 popup = popup.setFooterHtml(footer) 687 } 688 popup.show() 689 } 690 691 showAddToCartPopUp(data) { 692 let bodyHtml = '' 693 let footerHtml = '' 694 695 bodyHtml += '<div class="popup-container-cart--product-image"><img src="' + data.product.image + '" alt="' + data.product.name + '"></div>' 696 bodyHtml += '<div class="popup-container-cart--product-name">' + data.product.name + '</div>' 697 bodyHtml += '<div class="popup-container-cart--product-price">' 698 if (data.product.base_price > data.product.price) { 699 bodyHtml += '<div class="popup-container-cart--product-price-full">' + data.product.currency_symbol + data.product.base_price + '</div>' 700 } 701 bodyHtml += '<div class="popup-container-cart--product-price-current">' + data.product.currency_symbol + data.product.price + '</div>' 702 bodyHtml += '</div>' 703 704 footerHtml += '<div class="popup-container-cart--footer-title"><font class="color-dodger-blue">' + data.count + '</font> ' + ngettext('product', 'products', data.count) + gettext(' in cart') + '</div>' 705 footerHtml += '<div class="popup-container-cart--footer-checkout"><a href="/cart/" class="button">' + gettext('View cart') + '</a></div>' 706 footerHtml += '<div class="popup-container-cart--footer-close"><button type="button" class="button button-clear popup-close">' + gettext('Continue shopping') + '</button></div>' 707 708 this.setClass('popup-container-cart').setHeadline(gettext('Product added to cart')).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show() 709 } 710 711 showBlockedCartPopUp(data) { 712 let bodyHtml = ''; 713 let footerHtml = ''; 714 715 bodyHtml += '<div class="popup-container-cart--product-name">'; 716 bodyHtml += 'To comply with UK law, you must be 18 or older to view or purchase products with adult content. To verify your age, please contact our support team.'; 717 bodyHtml += '<br><i>This is in accordance with the UK Online Safety Act 2023.</i>'; 718 bodyHtml += '</div>'; 719 720 footerHtml += '<div class="popup-container-cart--footer-checkout"><a href="/contact/" class="button">' + gettext('Contact Support') + '</a></div>' 721 722 this.setClass('popup-container-cart').setHeadline(gettext('Age Verification Required')).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show() 723 } 724 725 showAddFavoriteAuth() { 726 let headline = '' 727 let bodyHtml = '' 728 let footerHtml = '' 729 730 headline += gettext("Heya!") 731 bodyHtml += gettext("Just click to add a game to favo... wait a second, you are not logged in! You'll need to register or log in to do that!") 732 733 footerHtml += '<div class="popup-container--footer-action"><a href="/login?returnurl=' + window.location.pathname.toString() + '" class="button">' + gettext('Sign In') + '</a></div>' 734 footerHtml += '<div class="popup-container--footer-close m-r-1"><button type="button" class="button button-outline popup-close">' + gettext('Cancel') + '</button></div>' 735 736 this.setClass('popup-container').setHeadline(headline).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show() 737 } 738 739 showSystemAlert(message, signin_required) { 740 let headline = '' 741 let bodyHtml = '' 742 let footerHtml = '' 743 744 headline += gettext("Heya!") 745 bodyHtml += message 746 747 if (signin_required) { 748 footerHtml += '<div class="popup-container--footer-action"><a href="/login?returnurl=' + window.location.pathname.toString() + '" class="button">' + gettext('Sign In') + '</a></div>' 749 footerHtml += '<div class="popup-container--footer-close m-r-1"><button type="button" class="button button-outline popup-close">' + gettext('Cancel') + '</button></div>' 750 } 751 this.setClass('popup-container').setHeadline(headline).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show() 752 } 753 754 showBlockedSystemAlert(data) { 755 let headline = ''; 756 let bodyHtml = ''; 757 let footerHtml = ''; 758 759 headline += data.title; 760 bodyHtml += data.content; 761 762 footerHtml += '<div class="popup-container--footer-action"><a href="' + data.btn.link + '" class="button">' + data.btn.text + '</a></div>'; 763 footerHtml += '<div class="popup-container--footer-close m-r-1"><button type="button" class="button button-outline popup-close">' + gettext('Cancel') + '</button></div>'; 764 765 this.setClass('popup-container').setHeadline(headline).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show() 766 } 767 768 showAvatars(data) { 769 let bodyHtml = ''; 770 let footerHtml = ''; 771 772 bodyHtml += '<div class="popup-container-avatars">'; 773 data.forEach((item) => { 774 if (item.is_active) { 775 bodyHtml += '<div class="popup-container-avatar is_active" data-hash="' + item.hash + '" data-src="' + item.url + '" data-action="change-avatar">'; 776 } else { 777 bodyHtml += '<div class="popup-container-avatar" data-hash="' + item.hash + '" data-src="' + item.url + '" data-action="change-avatar">'; 778 } 779 bodyHtml += '<img src="' + item.url + '" alt="">'; 780 bodyHtml += '</div>'; 781 }); 782 bodyHtml += '</div>'; 783 784 this.setClass('popup-container-cart').setHeadline(gettext('Choose profile picture that you like')).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show(); 785 } 786 787 showScreenshot(screenshotUrl) { 788 let bodyHtml = ''; 789 790 bodyHtml += '<div class="popup-screenshot-container with-icon icon-waiting">'; 791 bodyHtml += '<img src="' + screenshotUrl + '" alt="">'; 792 bodyHtml += '</div>'; 793 794 this.setBodyHtml(bodyHtml).show(); 795 } 796 797 showGDPR(message) { 798 const headline = '' 799 const bodyHtml = message 800 let footerHtml = '' 801 footerHtml += '<div class="popup-container--footer-action"><button id="accept_gdpr_button" type="button" class="button">' + gettext('Accept') + '</a></div>' 802 this.showPopupCloseButton = false 803 this.runCloseEvent = false 804 this.setAdditionalPopupClass('popup-gdpr').setClass('popup-container popup-container-gdpr').setHeadline(headline).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show() 805 } 806 807 showGDPRPrivacyPolicy(content) { 808 const id = 'popup_gdpr_privacy_policy' 809 const headline = gettext("Privacy policy") 810 const bodyHtml = content 811 let footerHtml = '' 812 footerHtml += '<div class="popup-container--footer-close"><button type="button" class="button popup-close">' + gettext('Close') + '</button></div>' 813 footerHtml += '<div class="popup-container--footer-action m-r-1"><a href="#top-pop-up-attachment" aria-label="' + gettext('Go back') + '" class="button button-outline">
813' + gettext('Go back') + '</a></div>' 814 815 this.showPopupCloseButton = true 816 this.setClass('popup-container popup-gdpr-privacy-policy').setHeadline(headline).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show(id, true) 817 818 this.closeButtonHandler(id) 819 } 820 821 showRedirect(name, content, redirect_link) { 822 const id = 'popup_redirect' 823 const headline = name; 824 const bodyHtml = content; 825 let footerHtml = '' 826 footerHtml += '<div class="popup-container--footer-action m-r-1"><a href="' + redirect_link + '" class="button">Ir a GamersGate.es</a></div>' 827 footerHtml += '<div class="popup-container--footer-close"><button type="button" class="button button-outline popup-close" id="refuse_redirect_popup_btn">Seguir en .com/Stay on .com</button></div>' 828 829 this.showPopupCloseButton = true 830 this.setClass('popup-container popup-redirect').setHeadline(headline).setBodyHtml(bodyHtml).setFooterHtml(footerHtml).show() 831 832 this.closeButtonHandler(id) 833 } 834 835 showAgeRequest(data) { 836 const id = 'popup_age_request'; 837 const body = document.querySelector('#age_request_popup_body'); 838 body.querySelector('#check-age-container--name').innerHTML = data.name; 839 body.querySelector('#check-age-container--image').src = data.image; 840 body.querySelector('.check-age-form').dataset.productId = data.id; 841 this.showPopupCloseButton = true; 842 this.setClass('popup-container popup_age_request').setBodyHtml(body.innerHTML).show(); 843 844 this.closeButtonHandler(id); 845 new Select('.popup_age_request .select'); 846 ageFormHandler(document.querySelector('.popup_age_request .check-age-form')); 847 } 848 849 showAgeError(data) { 850 const id = 'popup_age_error'; 851 const body = document.querySelector('#age_error_popup_body'); 852 body.querySelector('#check-age-container--name').innerHTML = data.name; 853 body.querySelector('#check-age-container--image').src = data.image; 854 const form = body.querySelector('.check-age-form'); 855 form.dataset.productId = data.id; 856 form.dataset.productName = data.name; 857 form.dataset.productImage = data.image; 858 this.showPopupCloseButton = true; 859 this.setClass('popup-container popup_age_error').setBodyHtml(body.innerHTML).show(); 860 861 this.closeButtonHandler(id); 862 ageFormHandler(document.querySelector('.popup_age_error .check-age-form')); 863 } 864} 865 866class SearchForm { 867 constructor(selector, language) { 868 this.form = document.querySelector(selector) 869 this.language = language 870 this.url = '/api/search/' 871 this.wait = false 872 if (this.form) { 873 this.listen() 874 } 875 return this 876 } 877 878 listen() { 879 this.form.querySelector('input[type="search"]').addEventListener('input', this.query.bind(this)) 880 this.form.querySelector('input[type="button"]').addEventListener('click', function(event) { 881 this.form.submit() 882 }.bind(this)) 883 } 884 885 listenItem() { 886 this.form.parentNode.querySelectorAll('.navigation-search--data--item').forEach(function(elem) { 887 elem.addEventListener('click', function() { 888 document.dispatchEvent(new CustomEvent('select_dynamic_search_item', {detail: {data: this.dataset}})) 889 top.location.href = this.dataset.href 890 }) 891 }) 892 } 893 894 query(event) { 895 const value = event.target.value 896 if (value.length > 0) { 897 this.wait = true 898 this.remove() 899 fetch(encodeURI(get_url_with_locale(this.url, this.language) + value + '/'), { 900 method: 'GET', 901 headers: { 902 'X-CSRFToken': getCookie('csrftoken') 903 } 904 }) 905 .then(checkStatus) 906 .then(parseJSON) 907 .then(function(data) { 908 this.wait = false 909 return data 910 }.bind(this)) 911 .then(this.show.bind(this)) 912 .catch(function() { 913 this.wait = false 914 }.bind(this)) 915 } 916 917 if (value.length == 0) { 918 this.wait = true 919 this.remove() 920 } 921 } 922 923 show(data) { 924 if (data.success) { 925 let html = '' 926 html += '<div class="navigation-search--data">' 927 if (data.genres) { 928 html += '<div class="navigation-search--data--description">' 929 html += '<p>' + gettext('Search by game genres') + '</p>' 930 html += '<div class="navigation-search--data--genres">' 931 for (let index = 0; index < data.genres.length; index++) { 932 html += '<div class="navigation-search--data--genre"><a href="' + data.genres[index].link + '">' + data.genres[index].name + '</a></div>' 933 } 934 html += '</div>' 935 html += '</div>' 936 } 937 html += '<ul class="navigation-search--data--list">' 938 for (let index = 0; index < data.products.length; index++) { 939 html += '<li class="navigation-search--data--item" data-href="' + data.products[index].link + '" data-name="' + data.products[index].name + '" data-sku="' + data.products[index].sku + '" data-vendor_name="' + data.products[index].vendor_name + '" data-price="' + data.products[index].raw_price + '" data-currency="' + data.products[index].currency_code + '">' 940 html += '<div class="navigation-search--data--item--image">' 941 html += '<a href="' + data.products[index].link + '"><img src="' + data.products[index].image + '" alt="' + data.products[index].name + '"></a>' 942 if (data.products[index].labels) { 943 html += '<ul class="product--labels">' 944 for (let key in data.products[index].labels) { 945 html += '<li class="navigation-search--data--item--label product--label-' + key + '"><span>' + data.products[index].labels[key] + '</span></li>' 946 } 947 html += '</ul>' 948 } 949 html += '</div>' 950 html += '<div class="navigation-search--data--item--description">' 951 html += '<div class="navigation-search--data--item--title"><a href="' + data.products[index].link + '">' + data.products[index].name + '</a></div>' 952 if ('discount' in data.products[index].labels) { 953 html += '<div class="navigation-search--data--item--full-price">' + data.products[index].full_price + '</div>' 954 } 955 html += '<div class="navigation-search--data--item--price">' + data.products[index].price + '</div>' 956 html += '</div>' 957 html += '</li>' 958 } 959 html += '</ul>' 960 html += '</div>' 961 962 this.remove() 963 this.form.parentNode.insertAdjacentHTML('beforeend', html) 964 this.listenItem()
965 document.dispatchEvent(new CustomEvent('dynamic_search_shown')) 966 setTimeout(this.closeEvent.bind(this), 500) 967 } 968 } 969 970 remove() { 971 const node = this.form.parentNode.querySelector('.navigation-search--data') 972 if (node && node.parentNode) { 973 node.parentNode.removeChild(node) 974 } 975 this.wait = false 976 } 977 978 closeEvent() { 979 const element = this.form.parentNode 980 981 const isVisible = function(elem) { 982 return !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) 983 } 984 985 const removeNode = function() { 986 removeClickListener() 987 this.remove() 988 }.bind(this) 989 990 const outsideClickListener = function(event) { 991 if (!element.contains(event.target) && isVisible(element)) { 992 removeNode() 993 } 994 } 995 996 const removeClickListener = function() { 997 document.removeEventListener('click', outsideClickListener) 998 } 999 1000 document.addEventListener('click', outsideClickListener) 1001 } 1002} 1003 1004class Slider { 1005 constructor(selector) { 1006 this.speed = 5000; 1007 this.time = null; 1008 this.slider = document.querySelector(selector); 1009 this.bannerCount = 0; 1010 if (this.slider) { 1011 this.init() 1012 this.listen() 1013 this.listenRotate() 1014 this.listenSwipe() 1015 } 1016 } 1017 1018 init() { 1019 this.banners = this.slider.querySelectorAll('.slider-item'); 1020 this.build(); 1021 if (this.bannerCount > 0) { 1022 this.current = parseInt(this.slider.querySelector('.slider-item.is_active').dataset.banner_id); 1023 1024 const firstItem = this.slider.querySelectorAll('.slider-item').item(0); 1025 const firstItemImage = firstItem.querySelector('img'); 1026 1027 firstItemImage.decode().then(() => { 1028 this.slider.querySelectorAll('.slider-item img').forEach((imgEl) => { 1029 imgEl.removeAttribute('loading'); 1030 }); 1031 this.run(); 1032 }); 1033 } 1034 } 1035 1036 listen() { 1037 this.slider.querySelectorAll('.slider-dot').forEach(function(elem, index) { 1038 elem.addEventListener('click', function(event) {this.next(event.target.dataset.banner_id)}.bind(this)) 1039 }.bind(this)) 1040 this.slider.querySelector('.slider-next-button').addEventListener('click', function(event) {this.next()}.bind(this)) 1041 this.slider.querySelector('.slider-prev-button').addEventListener('click', function(event) {this.prev()}.bind(this)) 1042 } 1043 1044 listenSwipe() { 1045 this.slider.addEventListener('swiped', function(event) { 1046 if (event.detail.dir == 'left') { 1047 this.next() 1048 } 1049 else if (event.detail.dir == 'right') { 1050 this.prev() 1051 } 1052 }.bind(this)) 1053 } 1054 1055 listenRotate() { 1056 document.addEventListener('rotate', function(event) { 1057 this.bannerCount = 0 1058 this.time = null 1059 this.removeNode(this.slider.querySelector('.slider-prev-button')) 1060 this.removeNode(this.slider.querySelector('.slider-next-button')) 1061 this.removeNode(this.slider.querySelector('.slider-dots')) 1062 this.init() 1063 this.listen() 1064 }.bind(this)) 1065 } 1066 1067 build() { 1068 let dots = [] 1069 this.banners.forEach(function(elem) { 1070 delete elem.dataset.banner_id 1071 elem.classList.remove('is_active') 1072 const style = getComputedStyle(elem) 1073 if (style.getPropertyValue('--view').trim() === "true") { 1074 elem.dataset.banner_id = this.bannerCount 1075 dots.push('<div class="slider-dot' + (this.bannerCount == 0 ? ' is_active' : '') + '" data-banner_id="' + this.bannerCount + '"></div>') 1076 this.bannerCount = this.bannerCount + 1 1077 } 1078 }.bind(this)) 1079 if (this.bannerCount > 0) { 1080 this.slider.insertAdjacentHTML('beforeend', '<div class="slider-dots">' + dots.join('') + '</div>') 1081 this.slider.querySelector('[data-banner_id="0"]').classList.add('is_active') 1082 this.background() 1083 this.slider.insertAdjacentHTML('beforeend', '<div class="slider-prev-button"></div>') 1084 this.slider.insertAdjacentHTML('beforeend', '<div class="slider-next-button"></div>') 1085 } 1086 this.current = 0 1087 } 1088 1089 run() { 1090 this.time = setTimeout(this.next.bind(this), this.speed) 1091 this.next(0) 1092 } 1093 1094 prev() { 1095 const index = this.current == 0 ? this.bannerCount - 1 : this.current - 1 1096 this.next(index) 1097 } 1098 1099 next(index=null) { 1100 clearTimeout(this.time) 1101 this.slider.querySelectorAll('[data-banner_id="' + this.current + '"]').forEach(function(elem, index) { 1102 elem.classList.remove("is_active") 1103 }) 1104 this.current = (this.current + 1 >= this.bannerCount) ? 0 : this.current + 1 1105 if (index !== undefined && index !== null) { 1106 this.current = parseInt(index) 1107 } 1108 this.slider.querySelectorAll('[data-banner_id="' + this.current + '"]').forEach(function(elem, index) { 1109 elem.classList.add("is_active") 1110 }) 1111 this.background() 1112 1113 let active_frame = this.slider.querySelector('.slider-item[data-banner_id="' + this.current + '"]') 1114 document.dispatchEvent(new CustomEvent('slider_frame_shown', {detail: active_frame.dataset})) 1115 1116 this.time = setTimeout(this.next.bind(this), this.speed) 1117 } 1118 1119 background() { 1120 const dataset = this.slider.querySelector('.slider-item.is_active').dataset 1121 this.slider.parentNode.style.background = 'linear-gradient(to right, ' + dataset.bg_left + ' 50%, ' + dataset.bg_right + ' 50%)' 1122 } 1123 1124 removeNode(node) { 1125 if (node && node.parentNode) { 1126 node.parentNode.removeChild(node) 1127 } 1128 } 1129} 1130 1131class ProductSlider { 1132 constructor(selector) { 1133 this.screen = null 1134 this.screenData = {} 1135 this.screenListenerHandler = (event) => { 1136 if (this.screenData.type === 'youtube') { 1137 const url = 'https://www.youtube-nocookie.com/embed/' + this.screenData.youtubeId + '?enablejsapi=1&version=3&playerapiid=ytplayer-' + this.screenData.youtubeId + '&autoplay=1&showinfo=0&rel=0&loop=1&'; 1138 let markup = ''; 1139 markup += '<iframe id="ytplayer-' + this.screenData.youtubeId + '" type="text/html" referrerpolicy="str
1139ict-origin-when-cross-origin" width="' + this.screen.clientWidth + '" height="' + Math.floor(0.565 * this.screen.clientWidth) + '" data-cookieconsent="preferences" src="' + url + '" frameborder="0" allowfullscreen="" allow="autoplay"></iframe>'; 1140 this.screen.innerHTML = markup; 1141 } 1142 if (this.screenData.type === 'image') { 1143 event.preventDefault(); 1144 new PopUp().showScreenshot(this.screen.querySelector('img').dataset.original); 1145 } 1146 }; 1147 this.choices = document.querySelector('.product-slider-choices') 1148 if (this.choices) { 1149 this.previewContainer = this.choices.querySelector('.preview-container') 1150 this.choicesList = this.previewContainer.querySelector('ul') 1151 this.choicesFirstItem = this.choicesList.querySelectorAll('li')[0] 1152 this.choicesListWidth = this.choicesList.clientWidth/10 1153 this.choicesListFullWidth = this.choicesList.scrollWidth/10 1154 this.sliderItemWidth = 0 1155 this.sliderItemCount = 0 1156 this.offsetMax = 0 1157 this.current = 0 1158 1159 this.build() 1160 this.listen() 1161 } 1162 } 1163 1164 build() { 1165 this.choices.insertAdjacentHTML('beforebegin', '<div class="product-slider-screen"></div>') 1166 this.screen = document.querySelector('.product-slider-screen') 1167 this.sliderItemCount = this.choicesList.querySelectorAll('li').length 1168 this.sliderItemWidth = this.choicesFirstItem.offsetWidth/10 + 2 1169 for (let i = 0; i < this.sliderItemCount; i++) { 1170 const offset = -1*this.sliderItemWidth*i + 1 1171 if (this.choicesListFullWidth + offset + 1 <= this.choicesListWidth) { 1172 break 1173 } 1174 this.offsetMax = offset 1175 } 1176 this.choicesList.style.width = this.choicesListFullWidth + 'rem' 1177 if (this.choicesListWidth < this.choicesListFullWidth) { 1178 this.choices.insertAdjacentHTML('afterbegin', '<button class="prev" id="product-slider-prev-btn" aria-label="Previous"></button>') 1179 this.choices.insertAdjacentHTML('beforeend', '<button class="next" id="product-slider-next-btn" aria-label="Next"></button>') 1180 } 1181 } 1182 1183 listen() { 1184 this.choicesList.querySelectorAll('li').forEach(function(elem, index) { 1185 if (index === 0) { 1186 this.handler(elem) 1187 } 1188 elem.dataset.id = index 1189 elem.addEventListener('click', function(event) { 1190 this.current = parseInt(event.target.dataset.id) 1191 this.handler(event.target) 1192 }.bind(this)) 1193 }.bind(this)) 1194 1195 if (this.choicesListWidth < this.choicesListFullWidth) { 1196 this.choices.querySelector('.next').addEventListener('click', function(event) { 1197 this.current = this.current + 1 1198 if (this.current > this.sliderItemCount - 1) { 1199 this.current = 0 1200 } 1201 this.handler(this.choicesList.querySelector('li[data-id="' + this.current + '"]'), true) 1202 }.bind(this)) 1203 1204 this.choices.querySelector('.prev').addEventListener('click', function(event) { 1205 this.current = this.current - 1 1206 if (this.current < 0) { 1207 this.current = this.sliderItemCount - 1 1208 } 1209 this.handler(this.choicesList.querySelector('li[data-id="' + this.current + '"]'), true) 1210 }.bind(this)) 1211 } 1212 } 1213 1214 handler(elem, to_move=false) { 1215 const type = elem.dataset.type 1216 const src = elem.dataset.src 1217 const original = elem.dataset.original 1218 const youtubeId = elem.dataset.youtubeId 1219 1220 if (this.screen.offsetHeight > 0) { 1221 this.screen.style.height = this.screen.offsetHeight + 'px' 1222 } 1223 this.screen.innerHTML = '' 1224 1225 if (to_move) { 1226 const offset = -1*this.sliderItemWidth*this.current + 1 1227 //this.choicesFirstItem.style.cssText = 'margin-left:' + (offset < this.offsetMax ? this.offsetMax : offset) + 'rem !important' 1228 this.choicesList.style.transform = 'translateX(' + (offset < this.offsetMax ? this.offsetMax : offset) + 'rem)' 1229 } 1230 1231 this.choicesList.querySelectorAll('li').forEach(function(e) { 1232 e.classList.remove('is_active') 1233 }) 1234 elem.classList.add('is_active') 1235 1236 this.screen.removeEventListener('click', this.screenListenerHandler); 1237 1238 switch(type) { 1239 case 'youtube': 1240 this.screenData = { 1241 'type': type, 1242 'src': src, 1243 'youtubeId': youtubeId 1244 } 1245 this.handlerYoutube() 1246 break 1247 case 'image': 1248 this.screenData = { 1249 'type': type, 1250 'src': src, 1251 'original': original 1252 } 1253 this.handlerImage() 1254 break 1255 default: 1256 break 1257 } 1258 } 1259 1260 handlerYoutube() { 1261 this.screen.style.position = 'relative' 1262 let html = '' 1263 html += '<img src="' + this.screenData.src + '" alt="Click to view video">' 1264 html += '<div class="youtube-play-button-for-product-slider"><svg width="72" height="84" viewBox="0 0 72 84" fill="none" xmlns="http://www.w3.org/2000/svg" id="youtube_play_button_for_product_slider">' 1265 html += '<path d="M70.5 39.4019C72.5 40.5566 72.5 43.4434 70.5 44.5981L4.50001 82.7032C2.50001 83.8579 1.13066e-05 82.4145 1.14076e-05 80.1051L1.47388e-05 3.89488C1.48398e-05 1.58548 2.50001 0.142104 4.50001 1.2968L70.5 39.4019Z" fill="white"/>' 1266 html += '</svg></div>' 1267 this.screen.innerHTML = html 1268 this.screen.style.height = Math.floor(this.screen.clientWidth * 0.565) + 'px' 1269 this.screen.addEventListener('click', this.screenListenerHandler) 1270 } 1271 1272 handlerImage() { 1273 this.screen.innerHTML = '<img src="' + this.screenData.src + '" data-original="' + this.screenData.original + '" alt="Click to view larger image">' 1274 this.screen.querySelector('img').style.height = Math.floor(this.screen.clientWidth * 0.565) + 'px' 1275 this.screen.querySelector('img').style.objectFit = 'contain' 1276 this.screen.addEventListener('click', this.screenListenerHandler) 1277 } 1278} 1279 1280class BaseForm { 1281 constructor() { 1282 this.form_errors = [] 1283 this.form = null 1284 this.formErrorElem = null 1285 } 1286 1287 reset() { 1288 this.form_errors = [] 1289 if (this.formErrorElem !== undefined && this.formErrorElem !== null) { 1290 this.formErrorElem.innerHTML = '' 1291 this.formErrorElem.remove() 1292 } 1293 this.formErrorElem = null 1294 } 1295 1296 error(elem, text) { 1297 let i=1; 1298 elem.parentNode.classList.remove('error') 1299 if (text != '') { 1300 elem.parentNode.classList.add('error') 1301 } 1302 while (elem) { 1303 elem = elem.nextSibling 1304 if (elem && elem.classList && elem.classList.contains('field-error')) { 1305 elem.innerHTML = text 1306 elem.style.display = text == '' ? 'none' : 'block' 1307 } 1308 i++ 1309 if (i>7) { 1310 break 1311 } 1312 } 1313 } 1314 1315 form_error(elem, text) { 1316 const error_key = typeof elem === 'string' ? elem : elem.id.toString() 1317 this.form_errors[error_key] = text 1318 if (text == '') { 1319 delete this.form_errors[error_key] 1320 } 1321 1322 if (Object.keys(this.form_errors).length > 0) { 1323 if (this.formErrorElem == null) { 1324 const nodes = this.form.querySelectorAll('.form-group:last-child') 1325 nodes[nodes.length- 1].insertAdjacentHTML('beforebegin', '<div class="form-error"></div>') 1326 this.formErrorElem = this.form.querySelector('.form-error') 1327 } 1328 this.formErrorElem.innerHTML = Object.keys(this.form_errors).map(function(x){return this.form_errors[x];}.bind(this)).join('<br>') 1329 } 1330 else { 1331 this.reset() 1332 } 1333 } 1334} 1335 1336 1337class ProductRecommendedGames { 1338 constructor(product_id, language) { 1339 this.product_id = product_id; 1340 this.language = language; 1341 this.url = '/api/recommended_games/'; 1342 this.get(); 1343 } 1344 1345 get() { 1346 const params = new URLSearchParams(); 1347 params.append('product_id', this.product_id); 1348 fetch(get_url_with_locale(this.url, this.language) + '?' + params.toString(), { 1349 method: 'GET', 1350 headers: { 1351 'X-CSRFToken': getCookie('csrftoken'), 1352 'Accept': 'application/json', 1353 'Content-Type': 'application/json;charset=UTF-8', 1354 'X-Requested-With': 'XMLHttpRequest' 1355 }, 1356 async: true 1357 }) 1358 .then(checkStatus) 1359 .then(parseJSON) 1360 .then(function(data) { 1361 if (data.hasOwnProperty('success') && data.hasOwnProperty('markup')) { 1362 const containerElement = document.getElementById('recommended-games-container'); 1363 containerElement.innerHTML = data.markup; 1364 new Tab(containerElement); 1365 } 1366 }.bind(this)) 1367 .catch(function(message) {}) 1368 } 1369} 1370 1371 1372class SteamDeckDescriptions { 1373 constructor(product_id, language) { 1374 this.product_id = product_id 1375 this.language = language 1376 this.url = '/api/steam_deck_descriptions/' 1377 this.listen() 1378 } 1379 1380 listen() { 1381 const btnEl = document.getElementById('steam-deck-popup') 1382 if (btnEl) { 1383 btnEl.addEventListener('click', (event) => { 1384 event.preventDefault() 1385 this.get_descriptions() 1386 }) 1387 } 1388 } 1389 1390 get_descriptions() {
1391 const params = (new URL(window.location.toString().split(/[?#]/)[0])).searchParams 1392 params.append('product_id', this.product_id) 1393 fetch(get_url_with_locale(this.url, this.language) + '?' + params.toString(), { 1394 method: 'GET', 1395 headers: { 1396 'X-CSRFToken': getCookie('csrftoken'), 1397 'Accept': 'application/json', 1398 'Content-Type': 'application/json;charset=UTF-8', 1399 'X-Requested-With': 'XMLHttpRequest' 1400 }, 1401 async: false 1402 }) 1403 .then(checkStatus) 1404 .then(parseJSON) 1405 .then(function(data) { 1406 this.render(data) 1407 }.bind(this)) 1408 .catch(function(message) {}) 1409 } 1410 1411 render(data) { 1412 let body = '<div class="steam-deck-descriptions">' 1413 data.descriptions.forEach(function(i) { 1414 body = body + '<div class="steam-deck-description steam-deck-' + i.type + '">' + i.content + '</div>' 1415 }) 1416 body = body + '</div>' 1417 new PopUp().showErrorPopUp(data.title, body) 1418 } 1419} 1420 1421class ProductComments extends BaseForm { 1422 constructor(product_id, is_auth, language, nickname, is_disallow, product_url) { 1423 super() 1424 this.product_id = product_id 1425 this.is_auth = is_auth 1426 this.language = language 1427 this.nickname = nickname 1428 this.product_url = product_url 1429 this.is_disallow = is_disallow 1430 this.is_empty = true 1431 this.containerElement = document.querySelector('.product-user-comments') 1432 this.listSectionElement = null 1433 this.url = '/api/comments/' 1434 this.page = null 1435 this.get_comments() 1436 } 1437 1438 get_comments() { 1439 if (!this.containerElement) return; 1440 const params = (new URL(window.location.toString().split(/[?#]/)[0])).searchParams 1441 params.append('product_id', this.product_id) 1442 if (this.page !== null) { 1443 params.append('page', this.page) 1444 } 1445 fetch(get_url_with_locale(this.url, this.language) + '?' + params.toString(), { 1446 method: 'GET', 1447 headers: { 1448 'X-CSRFToken': getCookie('csrftoken'), 1449 'Accept': 'application/json', 1450 'Content-Type': 'application/json;charset=UTF-8', 1451 'X-Requested-With': 'XMLHttpRequest' 1452 }, 1453 async: false 1454 }) 1455 .then(checkStatus) 1456 .then(parseJSON) 1457 .then(function(data) { 1458 this.renderListSection(data) 1459 this.renderEmptySection() 1460 this.renderToolsSection(data.next_page) 1461 this.renderAddNewCommentButton() 1462 }.bind(this)) 1463 .catch(function(message) {}) 1464 } 1465 1466 renderListSection(data) { 1467 if (this.is_empty && data.count > 0) { 1468 this.initListSection() 1469 } 1470 data.tickets.forEach(function(ticket, index) { 1471 this.listSectionElement.insertAdjacentHTML('beforeend', this.getCommentsBranchHtml(ticket.messages)) 1472 }.bind(this)) 1473 this.containerElement.querySelectorAll('.comment--reply-button').forEach(function(elem) { 1474 this.listenReplyCommentButton(elem.dataset.id) 1475 }.bind(this)) 1476 } 1477 1478 initListSection() { 1479 this.is_empty = false 1480 this.containerElement.insertAdjacentHTML('beforeend', '<div class="comment-list"></div>') 1481 this.listSectionElement = this.containerElement.querySelector('.comment-list') 1482 } 1483 1484 getCommentsBranchHtml(messages, get_only_body=false) { 1485 let html = '' 1486 if (messages.length > 0) { 1487 const message = messages.shift() 1488 const id = message.id 1489 1490 if (!get_only_body) { 1491 html += '<div class="comment" data-id="' + message.id + '">' 1492 } 1493 1494 html += this.getCommentHtml(message, !this.is_disallow) 1495 1496 if (messages.length > 0) { 1497 html += '<ul class="comment--child-comments">' 1498 messages.forEach(function(message, index) { 1499 html += this.getCommentHtml(message, false, true) 1500 }.bind(this)) 1501 html += '</ul>' 1502 } 1503 if (!get_only_body) { 1504 html += '</div>' 1505 } 1506 } 1507 return html 1508 } 1509 1510 getCommentHtml(message, show_reply, first_comment=false) { 1511 let html = '' 1512 1513 if (first_comment) { 1514 html += '<div class="comment">' 1515 } 1516 html += ' <div class="comment--header">' 1517 html += ' <div class="comment--avatar"><img src="' + message.user_src + '" loading="auto" alt=""></div>' 1518 html += ' <div class="comment--title">' 1519 html += ' <div class="comment--username">' + (message.is_admin ? gettext('Reply from GamersGate') : message.user_name) + '</div>' 1520 html += ' <div class="comment-created">' + timestamp_to_datetime_format(message.created) +'</div>' 1521 html += ' <div class="comment--content">' + message.content.replace(/\n\r/g, "<br/>").replace(/\n/g, "<br/>") + '</div>' 1522 if (message.is_admin && message.image_url) { 1523 html += ' <div class="comment--image"><a href="' + message.image_url + '" target="_blank"><img src="' + message.image_url + '" loading="auto" alt=""></a></div>' 1524 } 1525 if (show_reply) { 1526 html += ' <a class="comment--reply-button" href="#" data-id="' + message.id + '">' + gettext('Reply') + '</a>' 1527 }
1528 html += ' </div>' 1529 html += ' </div>' 1530 if (first_comment) { 1531 html += '</div>' 1532 } 1533 return html 1534 } 1535 1536 renderEmptySection() { 1537 if (this.containerElement.querySelector('.comment--empty')) { 1538 this.containerElement.querySelector('.comment--empty').innerHTML = '' 1539 this.containerElement.querySelector('.comment--empty').remove() 1540 } 1541 1542 let html = '' 1543 if (this.is_empty && this.is_auth) { 1544 html += '<div class="comment--empty"><p>' + gettext('No user comments yet') + '</p></div>' 1545 } 1546 if (!this.is_auth) { 1547 html += '<div class="comment--empty">' 1548 html += '<p>' 1549 if (this.is_empty) { 1550 html += gettext('Nothing has been said about this yet. You will literally be the first person to say something about it.') 1551 html += '</p><p>' 1552 html += gettext('Just {0} or {1} and make history!').format('<a href="' + get_url_with_locale('/login', this.language) + '?returnurl=' + this.product_url + '">' + gettext('Sign In') + '</a>', '<a href="' + get_url_with_locale('/register', this.language) + '">' + gettext('Create account') + '</a>') 1553 } else { 1554 html += gettext('Please {0} or {1} to comment').format('<a href="' + get_url_with_locale('/login', this.language) + '?returnurl=' + this.product_url + '">' + gettext('Sign In') + '</a>', '<a href="' + get_url_with_locale('/register', this.language) + '">' + gettext('Create account') + '</a>') 1555 } 1556 html += '</p>' 1557 html += '</div>' 1558 } 1559 this.containerElement.querySelector('h2').insertAdjacentHTML('afterend', html) 1560 } 1561 1562 renderToolsSection(page) { 1563 if (this.containerElement.querySelector('.comment--tools')) { 1564 this.containerElement.querySelector('.comment--tools').innerHTML = '' 1565 this.containerElement.querySelector('.comment--tools').remove() 1566 } 1567 if (page !== null) { 1568 this.page = page 1569 this.containerElement.insertAdjacentHTML('beforeend', '<div class="comment--tools"><button class="comment--show-more-button">' + gettext('Show more') + '</button></div>') 1570 this.listenShowMoreButton() 1571 } 1572 } 1573 1574 renderAddNewCommentButton() { 1575 if (!this.is_disallow && this.is_auth && !this.containerElement.querySelector('.comment--add-button')) { 1576 this.containerElement.querySelector('h2').insertAdjacentHTML('beforeend', '<a href="#" class="comment--add-button">' + gettext('+ New Ñomment') + '</a>') 1577 this.listenAddNewCommentButton() 1578 } 1579 } 1580 1581 listenReplyCommentButton(id) { 1582 this.containerElement.querySelector('.comment--reply-button[data-id="' + id + '"]').addEventListener('click', this.replyCommentButtonHandler.bind(this)) 1583 } 1584 1585 listenAddNewCommentButton() { 1586 this.containerElement.querySelector('.comment--add-button').addEventListener('click', this.addNewCommentButtonHandler.bind(this)) 1587 } 1588
1589 listenPostCommentForm() { 1590 this.containerElement.querySelector('input[type="button"]').addEventListener('click', this.postCommentFormHandler.bind(this)) 1591 } 1592 1593 listenRemoveCommentForm() { 1594 this.containerElement.querySelector('input[type="reset"]').addEventListener('click', this.removeCommentFormHandler.bind(this)) 1595 } 1596 1597 listenShowMoreButton() { 1598 this.containerElement.querySelector('.comment--show-more-button').addEventListener('click', this.get_comments.bind(this)) 1599 } 1600 1601 addNewCommentButtonHandler(event) { 1602 event.preventDefault() 1603 event.target.style.display = 'none' 1604 1605 this.removeForms() 1606 this.showAllReplyCommentButton() 1607 this.removeEmptySection() 1608 this.containerElement.querySelector('h2').insertAdjacentHTML('afterend', this.getCommentFormHtml()) 1609 1610 this.listenPostCommentForm() 1611 this.listenRemoveCommentForm() 1612 } 1613 1614 replyCommentButtonHandler(event) { 1615 event.preventDefault() 1616 1617 const id = event.target.dataset.id 1618 1619 this.removeForms() 1620 this.showAllReplyCommentButton() 1621 this.containerElement.querySelector('.comment--add-button').style.display = 'inline-block' 1622 event.target.style.display = 'none' 1623 event.target.insertAdjacentHTML('afterend', this.getCommentFormHtml(id)) 1624
1625 this.listenPostCommentForm() 1626 this.listenRemoveCommentForm() 1627 } 1628 1629 validUsername() { 1630 const element = this.form.querySelector('input[name="nickname"]') 1631 if (element) { 1632 const is_valid = validatorEmpty(element.value.trim()) && element.value.trim().length <= 30 1633 this.form_error(element, is_valid ? '' : gettext('Username must not be empty or longer than 30 characters')) 1634 return is_valid 1635 } 1636 return true 1637 } 1638 1639 validContent() { 1640 const element = this.form.querySelector('[name="content"]') 1641 const is_valid = validatorEmpty(element.value.trim()) && element.value.trim().length <= 3000 1642 this.form_error(element, is_valid ? '' : gettext('Your comment should not be empty or longer than 3000 symbols')) 1643 return is_valid 1644 } 1645 1646 validCharUsername() { 1647 const element = this.form.querySelector('input[name="nickname"]') 1648 if (element) { 1649 const is_valid = element.value.trim().match(/^[\p{L}\p{N}_]+$/u) 1650 this.form_error(element, is_valid ? '' : gettext('Username should contain only letters, numbers and underscore (the "_" symbol)')) 1651 return is_valid 1652 } 1653 return true 1654 } 1655 1656 postCommentFormHandler(event) { 1657 event.preventDefault() 1658 1659 const formElement = event.target.closest('form') 1660 const formData = new FormData(formElement) 1661 formData.append('product_id', this.product_id) 1662 1663 this.form = formElement 1664 this.reset() 1665 1666 if (this.validUsername() && this.validCharUsername() && this.validContent()) { 1667 fetch(get_url_with_locale(this.url, this.language), { 1668 method: 'POST', 1669 headers: { 1670 'X-CSRFToken': getCookie('csrftoken') 1671 }, 1672 body: formData 1673 }) 1674 .then(checkStatus) 1675 .then(parseJSON) 1676 .then(function(data) { 1677 if (data.success) { 1678 if (formData.has('comment_id')) { 1679 this.listSectionElement.querySelector('.comment[data-id="' + formData.get('comment_id') + '"]').innerHTML = '' 1680 this.listSectionElement.querySelector('.comment[data-id="' + formData.get('comment_id') + '"]').insertAdjacentHTML('beforeend', this.getCommentsBranchHtml(data.messages, true)) 1681 this.listenReplyCommentButton(formData.get('comment_id')) 1682 } 1683 else { 1684 if (this.is_empty) { 1685 this.initListSection() 1686 } 1687 this.listSectionElement.insertAdjacentHTML('afterbegin', this.getCommentsBranchHtml(data.messages)) 1688 this.listenReplyCommentButton(data.id) 1689 } 1690 this.removeCommentFormHandler(event) 1691 } 1692 if (data.errors) { 1693 const e = Object.keys(data.errors).map(function(x){ 1694 return data.errors[x].join('<br>') 1695 }).join('<br>') 1696 this.form_error('comment_error', e) 1697 } 1698 }.bind(this)) 1699 .catch(function(error) { 1700 if (error.response.status == 403) { 1701 const title = gettext("Heya!") 1702 const description = gettext('Please {0} or {1} to comment').format('<a href="' + get_url_with_locale('/login', this.language) + '?returnurl=' + this.product_url + '">' + gettext('Sign In') + '</a>', '<a href="' + get_url_with_locale('/register', this.language) + '">' + gettext('Create account') + '</a>') 1703 new PopUp().showErrorPopUp(title, '<p>' + description + '</p>') 1704 return 1705 } 1706 new PopUp().showErrorPopUp(gettext('An error occurred, try again later')) 1707 }.bind(this)) 1708 } 1709 } 1710 1711 removeCommentFormHandler(event) { 1712 const formElement = event.target.parentNode.parentNode 1713 const comment_id = formElement.dataset.id 1714 1715 formElement.innerHTML = '' 1716 formElement.parentNode.remove() 1717 1718 this.containerElement.querySelector('.comment--add-button').style.display = 'inline-block' 1719 1720 if (comment_id > 0) { 1721 this.containerElement.querySelector('.comment--reply-button[data-id="' + comment_id + '"]').style.display = 'inline-block' 1722 } else { 1723 this.renderEmptySection() 1724 } 1725 } 1726 1727 getCommentFormHtml(comment_id=null) { 1728 let html = '' 1729 html += '<div class="comment--form' + (comment_id === null ? '' : ' comment--reply-form') + '">' 1730 html += ' <form method="POST" data-id="' + (comment_id === null ? null : comment_id) + '">' 1731 if (comment_id !== null) { 1732 html += ' <input type="hidden" name="comment_id" value="' + comment_id + '">' 1733 } 1734 if (this.nickname === null || this.nickname.toString().length === 0 || this.nickname.includes('customer')) { 1735 html += ' <div class="form-group">' 1736 html += ' <input type="text" name="nickname" placeholder="' + gettext('Type your name') + ' ..." value="' + (this.nickname.includes('customer') ? this.nickname:'') + '" autocomplete="off">'
1737 html += ' </div>' 1738 } 1739 else if (comment_id == null) { 1740 html += ' <p>' + gettext('You will be commenting as') + ' ' + this.nickname + '</p>' 1741 } 1742 if (comment_id === null) { 1743 html += ' <div class="form-group form-group--input-with-button">' 1744 html += ' <textarea rows="6" name="content" placeholder="' + gettext('Type your comment') + ' ..." autocomplete="off"></textarea>' 1745 html += ' </div>' 1746 html += ' <div class="form-group form-group--input-with-button form-group--with-reply-button">' 1747 html += ' <input type="button" value="' + gettext('POST') + '">' 1748 html += ' <input type="reset" class="button-clear" value="' + gettext('HIDE THIS') + '">' 1749 html += ' </div>' 1750 } else { 1751 html += ' <div class="form-group form-group--input-with-button">' 1752 html += ' <textarea rows="6" name="content" placeholder="' + gettext('Type your comment') + ' ..." autocomplete="off"></textarea>' 1753 html += ' <div class="form-group form-group--input-with-button form-group--with-reply-button">' 1754 html += ' <input type="button" value="' + gettext('POST') + '">' 1755 html += ' <input type="reset" class="button-clear" value="' + gettext('HIDE THIS') + '">' 1756 html += ' </div>' 1757 html += ' </div>' 1758 html += ' <div class="form-group"></div>' 1759 } 1760 html += ' </form>' 1761 html += '</div>' 1762 1763 return html 1764 } 1765 1766 removeForms() { 1767 this.containerElement.querySelectorAll('.comment--form').forEach(function(elem) { 1768 elem.innerHTML = '' 1769 elem.remove() 1770 }) 1771 } 1772 1773 removeEmptySection() { 1774 if (this.containerElement.querySelector('.comment--empty')) { 1775 this.containerElement.querySelector('.comment--empty').remove() 1776 } 1777 } 1778 1779 showAllReplyCommentButton() { 1780 this.containerElement.querySelectorAll('.comment--reply-button').forEach(function(elem) { 1781 elem.style.display = 'inline-block' 1782 }) 1783 } 1784} 1785 1786class AuthorizationForm extends BaseForm { 1787 constructor(selector, show_email, show_password, show_recaptcha) { 1788 super() 1789 this.form = document.querySelector(selector) 1790 this.form_errors = [] 1791 this.auto_valid = false 1792 if (this.form !== null) { 1793 this.formErrorElem = this.form.querySelector('.form-error') 1794 } 1795 } 1796 1797 validEmail() { 1798 this.emailElem.value = this.emailElem.value.trim() 1799 const is_valid = validatorEmail(this.emailElem.value) 1800 this.error(this.emailElem, is_valid ? '' : gettext('Incorrect email')) 1801 return is_valid 1802 } 1803 1804 validEmailRepeat() { 1805 const is_valid = validatorRepeat(this.emailElem.value, this.emailRepeatElem.value) 1806 this.error(this.emailRepeatElem, is_valid ? '' : gettext('No match')) 1807 return is_valid 1808 } 1809 1810 noCopyEmailRepeat() { 1811 this.error(this.emailRepeatElem, gettext('No copy&paste')) 1812 } 1813 1814 validPassword() { 1815 const is_valid = validatorEmpty(this.passwordElem.value) 1816 this.error(this.passwordElem, is_valid ? '' : gettext('Incorrect password')) 1817 return is_valid 1818 } 1819 1820 validCaptcha() { 1821 this.form_error(this.recaptchaElem, this.recaptchaError ? gettext('Incorrect CAPTCHA') : '') 1822 return !this.recaptchaError 1823 } 1824 1825 verifyRecaptchaCallback(response) { 1826 this.recaptchaError = (response === undefined || response === null || response === "") ? true : false 1827 this.validCaptcha() 1828 } 1829} 1830 1831class RemindForm extends AuthorizationForm { 1832 constructor(selector, show_recaptcha) { 1833 super(selector) 1834 this.show_recaptcha = show_recaptcha 1835 if (this.form !== null) { 1836 this.emailElem = this.form.querySelector('#id_email') 1837 if (this.show_recaptcha) { 1838 this.recaptchaError = true 1839 this.recaptchaElem = this.form.querySelector('#id_recaptcha') 1840 } 1841 this.agreeElem = this.form.querySelector('#id_agree') 1842 this.listen() 1843 } 1844 } 1845 1846 listen() { 1847 this.form.querySelector('[type="submit"][name="remind"]').addEventListener('click', function(event) { 1848 event.preventDefault() 1849 if (this.valid()) { 1850 this.form.submit() 1851 } 1852 }.bind(this)) 1853 1854 this.emailElem.addEventListener('input', function(event) { 1855 if (this.auto_valid) { 1856 this.validEmail() 1857 } 1858 }.bind(this)) 1859 1860 this.agreeElem.addEventListener('change', function(event) { 1861 this.validAgree() 1862 }.bind(this)) 1863 } 1864 1865 valid() { 1866 const validItems = [] 1867 validItems.push(this.validEmail()) 1868 validItems.push(this.validAgree()) 1869 if (this.show_recaptcha) { 1870 validItems.push(this.validCaptcha()) 1871 } 1872 this.auto_valid = true 1873 return validItems.reduce(function(res, item) { 1874 return res && item 1875 }) 1876 } 1877 1878 validAgree() { 1879 var is_valid = true 1880 this.form_error(this.agreeElem.parentNode, '') 1881 if (!this.agreeElem.checked) { 1882 this.form_error(this.agreeElem.parentNode, gettext('Please agree to the Privacy Policy')) 1883 is_valid = false 1884 } 1885 return is_valid 1886 } 1887} 1888 1889class LoginForm extends AuthorizationForm { 1890 constructor(selector, show_email, show_password, show_recaptcha) { 1891 super(selector) 1892 this.show_email = show_email 1893 this.show_password = show_password 1894 this.show_recaptcha = show_recaptcha 1895 if (this.form !== null) { 1896 if (this.show_email) { 1897 this.emailElem = this.form.querySelector('#id_email') 1898 } 1899 if (this.show_password) { 1900 this.passwordElem = this.form.querySelector('#id_password') 1901 } 1902 if (this.show_recaptcha) { 1903 this.recaptchaError = true 1904 this.recaptchaElem = this.form.querySelector('#id_recaptcha') 1905 } 1906 this.listen() 1907 } 1908 } 1909 1910 listen() { 1911 this.form.querySelector('[type="submit"][name="login"]').addEventListener('click', function(event) { 1912 event.preventDefault() 1913 if (this.valid()) { 1914 this.form.submit() 1915 } 1916 }.bind(this)) 1917 1918 if (this.show_email) { 1919 this.emailElem.addEventListener('input', function(event) { 1920 if (this.auto_valid) { 1921 this.validEmail() 1922 } 1923 }.bind(this)) 1924 } 1925 if (this.show_repeat) { 1926 this.passwordElem.addEventListener('input', function(event) { 1927 if (this.auto_valid) { 1928 this.validPassword() 1929 } 1930 }.bind(this)) 1931 } 1932 } 1933 1934 valid() {
1935 var validItems = [] 1936 if (this.show_email) { 1937 validItems.push(this.validEmail()) 1938 } 1939 if (this.show_password) { 1940 validItems.push(this.validPassword()) 1941 } 1942 if (this.show_recaptcha) { 1943 validItems.push(this.validCaptcha()) 1944 } 1945 this.auto_valid = true 1946 return validItems.reduce(function(res, item) { 1947 return res && item 1948 }) 1949 } 1950} 1951 1952class RegistrationForm extends AuthorizationForm { 1953 constructor(selector, show_recaptcha) { 1954 super(selector) 1955 this.show_recaptcha = show_recaptcha 1956 if (this.form !== null) { 1957 this.emailElem = this.form.querySelector('#id_email') 1958 this.emailRepeatElem = this.form.querySelector('#id_email_repeat') 1959 if (this.show_recaptcha) { 1960 this.recaptchaError = true 1961 this.recaptchaElem = this.form.querySelector('#id_recaptcha') 1962 } 1963 this.agreeElem = this.form.querySelector('#id_agree') 1964 this.listen() 1965 } 1966 } 1967 1968 listen() { 1969 this.form.querySelector('[type="submit"][name="register"]').addEventListener('click', function(event) { 1970 event.preventDefault() 1971 if (this.valid()) { 1972 this.form.submit() 1973 } 1974 }.bind(this)) 1975 1976 this.emailElem.addEventListener('input', function(event) { 1977 if (this.auto_valid) { 1978 this.validEmail() 1979 this.noCopyEmailRepeat() 1980 } 1981 }.bind(this)) 1982 1983 this.emailRepeatElem.addEventListener('input', function(event) { 1984 this.validEmailRepeat() 1985 }.bind(this)) 1986 1987 this.emailRepeatElem.addEventListener('paste', function(event) { 1988 event.preventDefault() 1989 this.noCopyEmailRepeat() 1990 if (this.auto_valid) { 1991 this.validEmail() 1992 } 1993 }.bind(this)) 1994 1995 this.agreeElem.addEventListener('change', function(event) { 1996 this.validAgree() 1997 }.bind(this)) 1998 } 1999 2000 valid() { 2001 var validItems = [] 2002 validItems.push(this.validEmail()) 2003 validItems.push(this.validEmailRepeat()) 2004 validItems.push(this.validAgree()) 2005 if (this.show_recaptcha) { 2006 validItems.push(this.validCaptcha()) 2007 } 2008 this.auto_valid = true 2009 return validItems.reduce(function(res, item) { 2010 return res && item 2011 }) 2012 } 2013 2014 validAgree() { 2015 var is_valid = true 2016 this.form_error(this.agreeElem.parentNode, '') 2017 if (!this.agreeElem.checked) { 2018 this.form_error(this.agreeElem.parentNode, gettext('Please agree to the terms and conditions')) 2019 is_valid = false 2020 } 2021 return is_valid 2022 } 2023} 2024 2025class Paginator { 2026 constructor(pageNumber, numberOfPages) { 2027 this.init(pageNumber, numberOfPages) 2028 this.calc() 2029 } 2030 2031 init(pageNumber, numberOfPages) { 2032 this.pageNumber = parseInt(pageNumber) 2033 this.pageNumber = !this.pageNumber ? 1 : this.pageNumber 2034 this.numberOfPages = parseInt(numberOfPages) 2035 this.frameLong = 10 2036 this.framePages = [] 2037 this.prevLink 2038 this.prevForwardLink 2039 this.nextLink 2040 this.nextForwardLink 2041 this.showFirst = false 2042 this.showFirstDelimiter = false 2043 this.showLast = false 2044 this.showLastDelimiter = false 2045 } 2046 2047 calc() { 2048 if (this.pageNumber !== null && this.numberOfPages !== null && this.numberOfPages !== 1) { 2049 const frameLong = this.frameLong - 1 2050 2051 this.prevLink = this.pageNumber == 1 ? null : this.pageNumber - 1 2052 this.nextLink = this.numberOfPages == this.pageNumber ? null : this.pageNumber + 1 2053 this.prevForwardLink = this.pageNumber <= this.frameLong ? null : this.pageNumber - this.frameLong 2054 this.nextForwardLink = this.pageNumber >= this.numberOfPages - this.frameLong ? null : this.pageNumber + this.frameLong 2055 2056 let frameStart = this.pageNumber - parseInt(frameLong / 2) 2057 2058 if (frameStart < 1) { 2059 frameStart = 1 2060 } 2061 2062 if (this.numberOfPages - frameStart < frameLong) { 2063 frameStart = this.numberOfPages - frameLong 2064 } 2065 2066 const frameEnd = frameStart + frameLong 2067 2068 let i = 0 2069 while (i < this.numberOfPages) { 2070 i++ 2071 if (i < frameStart) { 2072 continue 2073 } 2074 if (i > frameEnd) { 2075 break 2076 } 2077 this.framePages.push(i) 2078 } 2079 2080 if (this.pageNumber > frameLong/2 + 1) {
2081 this.showFirst = true 2082 } 2083 if (this.pageNumber > frameLong/2 + 2) { 2084 this.showFirstDelimiter = true 2085 } 2086 if (this.pageNumber < this.numberOfPages - frameLong/2 - 1) { 2087 this.showLast = true 2088 } 2089 if (this.pageNumber < this.numberOfPages - frameLong/2 - 2) { 2090 this.showLastDelimiter = true 2091 } 2092 } 2093 } 2094} 2095 2096class CatalogFilter { 2097 constructor() { 2098 this.filters = document.querySelector('.sidebar-catalog-filters') 2099 if (this.filters !== null) { 2100 this.listen() 2101 } 2102 } 2103 2104 listen() { 2105 this.filters.querySelectorAll('.sidebar-catalog-filter .show-all-items').forEach(function(elem) { 2106 elem.addEventListener('click', function(event) { 2107 event.preventDefault() 2108 2109 const newTitle = this.dataset.title 2110 this.dataset.title = this.innerHTML 2111 this.innerHTML = newTitle 2112 2113 const listElement = this.previousElementSibling 2114 listElement.classList && listElement.classList.contains('show_first_any_items') ? listElement.classList.remove('show_first_any_items') : listElement.classList.add('show_first_any_items') 2115 }) 2116 }) 2117 2118 this.filters.querySelectorAll('.sidebar-catalog-filter.sidebar-action-toggle > span').forEach(function(elem) { 2119 elem.addEventListener('click', function(event) { 2120 event.preventDefault() 2121 const filterElement = this.parentNode 2122 filterElement.classList && filterElement.classList.contains('is_show') ? filterElement.classList.remove('is_show') : filterElement.classList.add('is_show') 2123 }) 2124 }) 2125 2126 this.filters.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(function(elem) { 2127 elem.addEventListener('change', function(event) { 2128 this.filters.querySelector('.sidebar-catalog-tools').classList.add('is_visible', 'has_delimiter') 2129 this.handler() 2130 }.bind(this)) 2131 }.bind(this)) 2132 2133 this.filters.querySelectorAll('button[type="submit"]').forEach(function(elem) { 2134 elem.addEventListener('click', function(event) { 2135 event.preventDefault() 2136 this.filters.querySelector('.sidebar-catalog-tools').classList.add('is_visible', 'has_delimiter') 2137 this.handler() 2138 }.bind(this)) 2139 }.bind(this)) 2140 2141 this.filters.querySelectorAll('[type="number"].only-integer').forEach(function(elem){ 2142 const max = parseInt(elem.dataset.max) 2143 const min = parseInt(elem.dataset.min) 2144 2145 function changeInput(e) { 2146 const value = parseInt(this.value) 2147 elem.value = value > max ? max : (value < min ? min : value) 2148 } 2149 2150 function keyInput(e) { 2151 const charCode = (e.which) ? e.which : e.keyCode; 2152 if ((charCode > 47 && charCode < 58) || (charCode >
2152 95 && charCode < 106) || charCode == 8) { 2153 return true 2154 } 2155 e.preventDefault() 2156 return false 2157 } 2158 2159 elem.onfocus = keyInput 2160 elem.onkeydown = keyInput 2161 elem.onchange = changeInput 2162 elem.onpaste = changeInput 2163 2164 elem.oninput = function(e) { 2165 return elem.validity.valid || (elem.value = this.previousValue) 2166 } 2167 }) 2168 2169 this.filters.querySelector('#catalog-filter-reset').addEventListener('click', function(event) { 2170 event.preventDefault() 2171 this.filters.querySelectorAll('input').forEach(function(elem) { 2172 const field_type = elem.type.toLowerCase() 2173 switch (field_type) { 2174 case "email": 2175 case "number": 2176 case "text": 2177 case "password": 2178 elem.value = null 2179 break 2180 case "radio": 2181 case "checkbox": 2182 if (elem.checked) { 2183 elem.checked = false 2184 } 2185 break 2186 default: 2187 break 2188 } 2189 }) 2190 this.filters.querySelector('.sidebar-catalog-tools').classList.remove('is_visible', 'has_delimiter') 2191 this.filters.querySelectorAll('.sidebar-catalog-filter.sidebar-action-toggle.is_show').forEach(function(elem) { 2192 elem.classList.remove('is_show') 2193 }) 2194 this.handler() 2195 }.bind(this)) 2196 2197 document.addEventListener('show_mobile_filter', function(event) { 2198 this.filters.querySelectorAll('.sidebar-catalog-filter.is_show').forEach(function(elem) { 2199 elem.classList.remove('is_show') 2200 }) 2201 this.filters.classList.add('show_mobile') 2202 document.querySelector('body').style.overflow = 'hidden' 2203 if (!this.filters.querySelector('#catalog-filter-hide')) { 2204 const elem = this.filters.querySelector('.sidebar-catalog-tools') 2205 elem.classList.add('is_visible', 'has_delimiter') 2206 elem.insertAdjacentHTML('beforeend', '<button class="button button-outline" id="catalog-filter-hide">' + gettext('Hide filters') + '</button>') 2207 this.filters.querySelector('#catalog-filter-hide').addEventListener('click', function(event) { 2208 event.preventDefault() 2209 this.hideMobile() 2210 }.bind(this)) 2211 } 2212 }.bind(this)) 2213 } 2214 2215 handler() { 2216 document.dispatchEvent(new CustomEvent('catalog', {detail: {}})) 2217 this.hideMobile() 2218 } 2219 2220 hideMobile() { 2221 if ([...this.filters.classList].includes('show_mobile')) { 2222 document.querySelector('body').style.overflow = 'initial' 2223 this.filters.classList.remove('show_mobile') 2224 } 2225 } 2226} 2227 2228class CatalogTools { 2229 constructor() { 2230 this.tools = document.querySelector('.catalog-tools') 2231 if (this.tools) { 2232 this.top = this.tools.getBoundingClientRect().top 2233 this.ticking = false 2234 this.init() 2235 this.listen() 2236 } 2237 } 2238 2239 init() { 2240 if (localStorage.getItem("gg_catalog_view_type")) { 2241 const view = localStorage.getItem("gg_catalog_view_type") 2242 this.tools.querySelectorAll('.catalog-view-type-choice > span').forEach(function(element) { 2243 element.classList.remove('is_active') 2244 if (element.dataset.view == view) { 2245 element.classList.add('is_active') 2246 } 2247 }) 2248 document.dispatchEvent(new CustomEvent('catalog-view', {detail: {'view': view}})) 2249 } 2250 } 2251 2252 show() { 2253 this.tools.style.display = 'flex' 2254 return this 2255 } 2256 2257 hide() { 2258 this.tools.style.display = 'none' 2259 return this 2260 } 2261 2262 listen() { 2263 this.tools.querySelectorAll('.catalog-view-type-choice > span').forEach(function(elem) { 2264 elem.addEventListener('click', function(event) { 2265 event.preventDefault() 2266 this.tools.querySelectorAll('.catalog-view-type-choice > span').forEach(function(element) { 2267 element.classList.remove('is_active') 2268 }) 2269 event.target.classList.add('is_active') 2270 const view = event.target.dataset.view 2271 localStorage.setItem("gg_catalog_view_type", view) 2272 document.dispatchEvent(new CustomEvent('catalog-view', {detail: {'view': view}})) 2273 }.bind(this)) 2274 }.bind(this)) 2275 2276 this.tools.querySelectorAll('.catalog-products-per-page .select a').forEach(function(elem) { 2277 elem.addEventListener('click', function(event) { 2278 event.preventDefault() 2279 this.handlerProductsPerPageChoice(event.target.dataset.value) 2280 }.bind(this)) 2281 }.bind(this)) 2282 2283 this.tools.querySelectorAll('.catalog-sort .select a').forEach(function(elem) { 2284 elem.addEventListener('click', function(event) { 2285 event.preventDefault() 2286 this.handlerSortChoice(event.target.dataset.value) 2287 }.bind(this)) 2288 }.bind(this)) 2289 2290 document.addEventListener('swiped', function(event) { 2291 if (event.detail.dir == 'up' || event.detail.dir == 'down') { 2292 this.animationTools() 2293 } 2294 }.bind(this)) 2295 2296 document.addEventListener('scroll', function(event) { 2297 if (!this.ticking) { 2298 window.requestAnimationFrame(function() { 2299 this.animationTools() 2300 }.bind(this)) 2301 this.ticking = true 2302 } 2303 }.bind(this)) 2304 2305 if (this.tools.querySelector('#catalog-filter-button')) { 2306 this.tools.querySelector('#catalog-filter-button').addEventListener('click', function(event) { 2307 document.dispatchEvent(new CustomEvent('show_mobile_filter')) 2308 }) 2309 } 2310 } 2311 2312 animationTools() { 2313 const toolsTop = this.tools.getBoundingClientRect().top 2314 const bodyTop = document.querySelector('body').getBoundingClientRect().top 2315 const is_fixed = [...this.tools.classList].includes('is_fixed') 2316 if (toolsTop && toolsTop <= 20 && !is_fixed) { 2317 this.tools.classList.add('is_fixed') 2318 } 2319 else if (Math.abs(bodyTop) <= this.top && is_fixed) { 2320 this.tools.classList.remove('is_fixed') 2321 } 2322 this.ticking = false 2323 } 2324 2325 handlerProductsPerPageChoice(value) { 2326 document.dispatchEvent(new CustomEvent('catalog', {detail: {'per_page': value}})) 2327 } 2328 2329 handlerSortChoice(value) { 2330 document.dispatchEvent(new CustomEvent('catalog', {detail: {'sort': value}})) 2331 } 2332} 2333 2334class CatalogPaginator extends Paginator { 2335 constructor(pageNumber=null, numberOfPages=null) { 2336 super(pageNumber, numberOfPages) 2337 this.container = document.querySelector('.catalog-paginator') 2338 } 2339 2340 clear() { 2341 this.container.innerHTML = '' 2342 return this 2343 } 2344 2345 refresh(pageNumber, numberOfPages) { 2346 this.init(pageNumber, numberOfPages) 2347 this.calc() 2348 this.render() 2349 this.listen() 2350 return this 2351 } 2352 2353 render() { 2354 if (this.pageNumber !== null && this.numberOfPages !== null && this.numberOfPages !== 1) { 2355 this.container.insertAdjacentHTML('beforeend', this.template()) 2356 } 2357 return this 2358 } 2359 2360 listen() { 2361 this.container.querySelectorAll('a').forEach(function(elem) { 2362 elem.addEventListener('click', function(event) { 2363 event.preventDefault() 2364 this.handler(event.target.dataset.page) 2365 }.bind(this)) 2366 }.bind(this)) 2367 } 2368 2369 handler(page) { 2370 document.dispatchEvent(new CustomEvent('catalog', {detail: {'page': page}})) 2371 window.scrollTo({top: 0, behavior: 'smooth'}) 2372 } 2373 2374 getUrl(searchParams) { 2375 const loc = document.location 2376 const loc_search = searchParams.toString(); 2377 return loc.protocol + '//' + loc.hostname + loc.pathname + (loc_search ? '?' + loc_search : '') 2378 } 2379 2380 template() {
2381 const params = (new URL(document.location)).searchParams 2382 const originalParams = params 2383 params.delete('page') 2384 let html = '' 2385 if (this.framePages) { 2386 html += '<div class="paginator"><ul>' 2387 2388 if (this.showFirst) { 2389 html += '<li><a href="' + this.getUrl(params) + '" data-page="1">1</a></li>' 2390 } 2391 2392 if (this.showFirstDelimiter) { 2393 html += '<li class="empty"></li>' 2394 } 2395 2396 this.framePages.forEach(function(i) { 2397 if (i === this.pageNumber) { 2398 html += '<li><span class="is_active">' + i + '</span></li>' 2399 } else { 2400 params.delete('page') 2401 if (i !== 1) { 2402 params.append('page', i.toString()) 2403 } 2404 html += '<li><a href="' + this.getUrl(params) + '" data-page="' + i + '">' + i + '</a></li>' 2405 } 2406 }.bind(this)) 2407 2408 if (this.showLastDelimiter) { 2409 html += '<li class="empty"></li>' 2410 } 2411 2412 if (this.showLast) { 2413 params.delete('page') 2414 params.append('page', this.numberOfPages.toString()) 2415 html += '<li><a href="' + this.getUrl(params) + '" data-page="' + this.numberOfPages + '">' + this.numberOfPages + '</a></li>' 2416 } 2417 2418 html += '</ul></div>' 2419 } 2420 return html 2421 } 2422 2423 notVisible() { 2424 return getComputedStyle(this.container).display == 'none' 2425 } 2426} 2427 2428class Catalog { 2429 constructor(language) { 2430 this.language = language 2431 this.catalog = document.querySelector('.catalog-list') 2432 this.catalogs = document.querySelectorAll('.catalog-list') 2433 this.url = null 2434 this.currentUrl = null 2435 this.show_all = true 2436 this.need_change_browser_url = true 2437 this.timestamp = 0 2438 this.filterInstance 2439 this.paginatorInstance 2440 this.toolsInstance 2441 this.buildFilter() 2442 this.buildPaginator() 2443 this.buildTools() 2444 this.listen() 2445 this.blockRequest = false 2446 this.useRefreshSpinner = true 2447 this.useWaitSpinner = false 2448 this.changeContent = true 2449 this.endCatalog = false 2450 2451 return this 2452 } 2453 2454 listen() { 2455 document.addEventListener('catalog', function(e) { 2456 this.request(e.detail) 2457 }.bind(this)) 2458 2459 document.addEventListener('catalog-view', function(e) { 2460 this.catalog.dataset.view = e.detail.view 2461 2462 this.catalog.querySelectorAll('.catalog-item--image > img').forEach(function(elem) { 2463 elem.src = e.detail.view == 'grid' ? elem.src.replace("w=139", "w=310") : elem.src.replace("w=310", "w=139") 2464 }) 2465 }.bind(this)) 2466 2467 this.catalog.addEventListener('swiped', function(event) { 2468 if (this.paginatorInstance.notVisible() && !this.endCatalog && !this.blockRequest && event.detail.dir == 'up') { 2469 const top = this.catalog.getBoundingClientRect().top 2470 const height = this.catalog.getBoundingClientRect().height 2471 const next = this.paginatorInstance.pageNumber + 1 2472 const delta = 1 - 0.5/this.paginatorInstance.pageNumber 2473 if (next <= this.paginatorInstance.numberOfPages && height > 0 && Math.abs(top/height) > delta) { 2474 this.useRefreshSpinner = false 2475 this.useWaitSpinner = true 2476 this.changeContent = false 2477 this.need_change_browser_url = false 2478 this.request({'page': next}) 2479 } 2480 } 2481 }.bind(this)) 2482 2483 window.addEventListener('popstate', function(event) { 2484 location.reload() 2485 }) 2486 } 2487 2488 buildFilter() { 2489 this.filterInstance = new CatalogFilter() 2490 return this 2491 } 2492 2493 buildPaginator() { 2494 this.paginatorInstance = new CatalogPaginator() 2495 this.paginatorInstance.listen() 2496 return this 2497 } 2498 2499 buildTools() { 2500 this.toolsInstance = new CatalogTools() 2501 return this 2502 } 2503 2504 clear() { 2505 if (this.changeContent) { 2506 this.catalog.innerHTML = '' 2507 this.catalog.setAttribute('data-ga_slug', '') 2508 } 2509 return this 2510 } 2511 2512 setSlug(data) { 2513 if(data.catalog.length && data.ga_slug) { 2514 this.catalog.setAttribute('data-ga_slug', data.ga_slug) 2515 } 2516 return this 2517 } 2518 2519 fill(data, message) { 2520 for (let index = 0; index < data.length; index++) { 2521 const item = this.fillItem(data[index]) 2522 this.catalog.insertAdjacentHTML('beforeend', item) 2523 } 2524 this.catalog.classList.remove('catalog-empty-list') 2525 if (data.length == 0) { 2526 this.endCatalog = true 2527 if (this.changeContent) { 2528 const item = this.fillEmpty(message) 2529 this.catalog.insertAdjacentHTML('beforeend', item) 2530 this.catalog.classList.add('catalog-empty-list') 2531 } 2532 } 2533 2534 this.changeContent = true 2535 this.need_change_browser_url = true 2536 2537 return this 2538 } 2539 2540 fillItem(data) { 2541 let html = '' 2542 html += ' <div class="column catalog-item product--item" data-id="' + data.id + '" data-name="' + data.name + '" data-sku="' + data.sku + '" data-vendor_name="' + data.vendor_name + '" data-price="' + data.raw_price + '" data-currency="' + data.currency_code + '">' 2543 html += ' <div class="catalog-item-container">' 2544 html += ' <div class="catalog-item--image' + (data.is_blured_cover ? ' blured':'') + '" data-required-age="' + data.required_age +'">' 2545 html += ' <a href="' + data.link + '" title="' + data.name + '"><img src="' + data.image + '" alt="' + data.name + '" loading="auto"></a>' 2546 html += ' </div>' 2547 html += ' <div class="catalog-item--description">' 2548 html += ' <div class="catalog-item--title"><a href="' + data.link + '" title="' + data.name + '">' + data.name + '</a></div>' 2549 2550 if (this.show_all && data.labels && 'discount' in data.labels) { 2551 html += ' <div class="catalog-item--product-label product--label-discount for-list">' + data.labels['discount'] + '</div>' 2552 } 2553 2554 if (this.show_all) { 2555 html += ' <div class="catalog-item--price">'
2556 html += ' <span>' + data.price + '</span>' 2557 if (data.discount_percent > 0) { 2558 html += ' <div class="catalog-item--full-price">' + data.baseprice + '</div>' 2559 } 2560 html += ' </div>' 2561 } 2562 2563 html += ' </div>' 2564 2565 if (this.show_all) { 2566 html += ' <div class="catalog-item--button">' 2567 html += ' <a class="catalog-item--button-product-link" href="' + data.link + '" title="' + data.name + '"></a>' 2568 html += ' <button id="favorite-button-' + data.id + '" aria-label="Add to favorites" data-id="' + data.id + '" type="button" class="catalog-item--button-add-favorite favorite--button' + (data.is_favorite ? ' is_active':'') + '"></button>' 2569 if (data.is_available && data.st === 2) { 2570 html += '<button data-id="' + data.id + '" type="button" class="catalog-item--button-add-cart cart--add-button">' + gettext('Add to Ñart') + '</button>' 2571 } 2572 else if (data.st === 3) { 2573 html += '<button data-id="' + data.id + '" type="button" class="catalog-item--button-add-cart cart--add-button">' + gettext('Coming soon') + '</button>' 2574 } 2575 else { 2576 html += '<button data-id="' + data.id + '" type="button" class="catalog-item--button-restock-alert not-hover">' + gettext('Out of stock') + '</button>' 2577 } 2578 html += ' </div>' 2579 html += ' <ul class="catalog-item--product-labels-v2 product--labels">' 2580 Object.keys(data.labels).forEach(function(key) { 2581 html += ' <li class="catalog-item--product-label-v2 product--label-' + key + '"><span>' + data.labels[key] + '</span></li>' 2582 }) 2583 html += ' </ul>' 2584 } 2585 html += ' </div>' 2586 html += ' </div>' 2587 2588 return html 2589 } 2590 2591 fillEmpty(message) { 2592 return '<div class="column">' + message + '</div>' 2593 } 2594 2595 refreshPaginator(pageNumber, numberOfPages) { 2596 this.paginatorInstance.clear().refresh(pageNumber, numberOfPages) 2597 return this 2598 } 2599 2600 showTools() { 2601 this.toolsInstance.show() 2602 return this 2603 } 2604 2605 hideTools() { 2606 this.toolsInstance.hide() 2607 return this 2608 } 2609 2610 showRefreshSpinner() { 2611 if (this.useRefreshSpinner) { 2612 this.catalog.insertAdjacentHTML('afterbegin', '<div class="refresh"></div>') 2613 } 2614 if (this.useWaitSpinner) { 2615 this.catalog.insertAdjacentHTML('beforeend', '<div class="wait with-icon icon-waiting"></div>') 2616 } 2617 } 2618 2619 hideRefreshSpinner() { 2620 const refreshSpinnerElement = this.catalog.querySelector('.refresh') 2621 const waitSpinnerElement = this.catalog.querySelector('.wait') 2622 if (this.useRefreshSpinner && refreshSpinnerElement) { 2623 refreshSpinnerElement.remove() 2624 } 2625 if (this.useWaitSpinner && waitSpinnerElement) { 2626 waitSpinnerElement.remove() 2627 } 2628 } 2629 2630 setUrl(url) { 2631 this.url = url 2632 if (this.url.includes('my_games') || this.url.includes('my_discount_codes')) { 2633 this.setShowAll(false) 2634 } 2635 } 2636 2637 setShowAll(value) { 2638 this.show_all = value 2639 } 2640 2641 setNeedChangeBrowserUrl(value) { 2642 this.need_change_browser_url = value 2643 } 2644 2645 request(data={}) { 2646 this.blockRequest = true 2647 2648 this.showRefreshSpinner() 2649 2650 const formData = this.filterInstance.filters === null ? new FormData() : new FormData(this.filterInstance.filters.querySelector('form')) 2651 const currentUrlParams = (new URL(this.currentUrl === null ? window.location.toString().split(/[?#]/)[0] : this.currentUrl)).searchParams 2652 const params = (new URL('https://' + window.location.host)).searchParams 2653 2654 let url = this.url 2655 for (const pair of formData.entries()) { 2656 if (pair[0] === 'api') { 2657 this.url = pair[1] 2658 } 2659 else if (pair[1] !== undefined && pair[1] !== null && pair[1] !== '') { 2660 params.append(pair[0], pair[1].toString()) 2661 } 2662 } 2663 2664 for (const key in data) { 2665 if (data[key] !== undefined) { 2666 params.append(key, data[key]); 2667 } 2668 } 2669 2670 if (data.sort === undefined && currentUrlParams.get('sort')) { 2671 params.set('sort', currentUrlParams.get('sort')) 2672 } 2673 if (data.per_page === undefined && currentUrlParams.get('per_page')) { 2674 params.set('per_page', currentUrlParams.get('per_page')) 2675 } 2676 2677 this.timestamp = Date.now() 2678 params.set('timestamp', this.timestamp) 2679 2680 if (this.need_change_browser_url) { 2681 params.set('need_change_browser_url', this.need_change_browser_url) 2682 } 2683 2684 if (params.get('page') && params.get('page') === '1') { 2685 params.delete('page') 2686 } 2687 2688 fetch(encodeURI(this.url + '?' + params.toString()), { 2689 method: 'GET', 2690 headers: { 2691 'X-CSRFToken': getCookie('csrftoken') 2692 } 2693 }) 2694 .then(checkStatus) 2695 .then(parseJSON) 2696 .then(function(data) { 2697 if(data.timestamp && parseInt(data.timestamp) >= this.timestamp) { 2698 if (data.page_number <= data.number_of_pages) { 2699 this.clear().setSlug(data).fill(data.catalog, data.empty_text).refreshPaginator(data.page_number, data.number_of_pages) 2700 2701 /* 2702 if(data.catalog.length > 0) { 2703 this.showTools() 2704 } else { 2705 this.hideTools() 2706 } 2707 */ 2708 2709 document.dispatchEvent(new CustomEvent('catalog-refresh', {})) 2710 2711 if (this.need_change_browser_url) { 2712 params.delete('timestamp') 2713 params.delete('need_change_browser_url') 2714 this.currentUrl = window.location.protocol + '//' + window.location.hostname + window.location.pathname + (params.toString() ? '?' + params.toString() : '') 2715 window.history.pushState({}, '', this.currentUrl) 2716 } 2717 } 2718 else { 2719 this.endCatalog = true 2720 } 2721 } 2722 this.hideRefreshSpinner() 2723 2724 this.blockRequest = false 2725 this.useRefreshSpinner = true 2726 this.useWaitSpinner = false 2727 }.bind(this)) 2728 .catch(function(message) { 2729 this.hideRefreshSpinner() 2730 this.blockRequest = false 2731 this.useRefreshSpinner = true 2732 this.useWaitSpinner = false 2733 }.bind(this)) 2734 2735 if (this.changeContent) { 2736 window.scrollTo({top: 0, behavior: 'smooth'}) 2737 } 2738 } 2739} 2740 2741class FlatCatalog { 2742 constructor(params) { 2743 this.id = params.id; 2744 2745 this.states = { 2746 'hidden': 'hidden', 2747 'visible': 'visible' 2748 } 2749 2750 this.stateTitles = { 2751 'hidden': gettext("Show more"), 2752 'visible': gettext("Show less") 2753 } 2754 2755 this.catalogElement = null; 2756 this.showCatalogElement = null; 2757 this.run(); 2758 } 2759 2760 run() { 2761 this.catalogElement = document.querySelector('#' + this.id); 2762 if (this.catalogElement) { 2763 this.showCatalogElement = this.catalogElement.parentElement.querySelector('[data-type="show-catalog"]'); 2764 if (this.showCatalogElement) { 2765 if (this.checkWidthViewportForShowButton()) { 2766 this.listen(); 2767 } else { 2768 this.showCatalogElement.remove(); 2769 } 2770 } 2771 } 2772 } 2773 2774 listen() { 2775 this.showCatalogElement.addEventListener('click', (event) => { 2776 this.showCatalogHandler(event.target.dataset.state); 2777 }); 2778 } 2779 2780 showCatalogHandler(state) { 2781 if (state === this.states.hidden) { 2782 this.showCatalogElement.dataset.state = this.states.visible; 2783 this.showCatalogElement.innerHTML = this.stateTitles.visible; 2784 this.catalogElement.classList.add('is_open'); 2785 } 2786 else if (state === this.states.visible) { 2787 this.showCatalogElement.dataset.state = this.states.hidden; 2788 this.showCatalogElement.innerHTML = this.stateTitles.hidden; 2789 this.catalogElement.classList.remove('is_open'); 2790 this.catalogElement.parentElement.scrollIntoView({block: "center", behavior: "smooth"}); 2791 } 2792 this.showCatalogElement.blur(); 2793 this.catalogElement.blur(); 2794 } 2795 2796 checkWidthViewportForShowButton() { 2797 const width = window.innerWidth; 2798 const catalogLength = this.catalogElement.querySelectorAll('.catalog-item').length; 2799 2800 return (width >= 1281 && catalogLength > 3) || (width >= 768 && width < 1281 && catalogLength > 2) || (width < 768 && catalogLength > 1); 2801 } 2802} 2803 2804class Select { 2805 constructor(selectSelector=null) { 2806 this.selectSelector = selectSelector ?? '.select'; 2807 this.toggleList(); 2808 this.listenItem(); 2809 } 2810 2811 toggleList() { 2812 document.querySelectorAll(this.selectSelector + ' > span').forEach((elem) => { 2813 elem.addEventListener('click', (event) => { 2814 event.preventDefault() 2815 const selectElement = elem.parentNode 2816 if (selectElement.classList && selectElement.classList.contains('is_active')) { 2817 selectElement.classList.remove('is_active') 2818 } else { 2819 selectElement.classList.add('is_active') 2820 setTimeout(this.closeEvent.bind(null, selectElement), 500) 2821 } 2822 }) 2823 }) 2824 } 2825 2826 listenItem() { 2827 document.querySelectorAll(this.selectSelector + ' > ul > li > a').forEach((elem) => { 2828 elem.addEventListener('click', (event) => { 2829 event.preventDefault() 2830 const value = elem.dataset.value 2831 const selectElement = elem.parentNode.parentNode.parentNode 2832 const displayElement = selectElement.querySelector('span') 2833 const inputElement = selectElement.querySelector('input[type="hidden"]') 2834 selectElement.querySelectorAll('ul > li').forEach(function(elem) { 2835 const elemValue = elem.querySelector('a').dataset.value 2836 elemValue === value ? elem.classList.add('selected') : elem.classList.remove('selected') 2837 }) 2838 2839 if (displayElement && displayElement.innerHTML.length > 0) { 2840 displayElement.innerHTML = elem.innerHTML 2841 } 2842 if (inputElement) { 2843 inputElement.value = value 2844 } 2845 selectElement.classList.remove('is_active') 2846 }) 2847 }) 2848 } 2849 2850 closeEvent(element) { 2851 const isVisible = function(elem) { 2852 return !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) 2853 } 2854 2855 const removeNode = function() { 2856 removeClickListener() 2857 element.classList.remove('is_active') 2858 } 2859 2860 const outsideClickListener = function(event) { 2861 if (!element.contains(event.target) && isVisible(element)) { 2862 removeNode() 2863 } 2864 } 2865 2866 const removeClickListener = function() { 2867 document.removeEventListener('click', outsideClickListener) 2868 } 2869 2870 document.addEventListener('click', outsideClickListener) 2871 } 2872} 2873 2874class AccountSettings extends BaseForm { 2875 constructor(args) { 2876 super() 2877 2878 this.apiUrl = args.apiUrl 2879 2880 this.accountAvatarForm = document.querySelector('.account-avatar-form') 2881 this.accountUsernameForm = document.querySelector('.account-username-form') 2882 this.accountEmailForm = document.querySelector('.account-email-form') 2883 this.accountPasswordForm = document.querySelector('.account-password-form') 2884 this.accountLanguageForm = document.querySelector('.account-language-form') 2885 2886 this.reset() 2887 this.listen() 2888 } 2889 2890 listen() { 2891 this.accountAvatarForm.querySelector('#show-avatars').addEventListener('click', this.showAvatarsHandler.bind(this)) 2892 this.accountUsernameForm.querySelector('button[type="submit"]').addEventListener('click', this.usernameChangeHandler.bind(this)) 2893 this.accountEmailForm.querySelector('button[type="submit"]').addEventListener('click', this.emailChangeHandler.bind(this)) 2894 this.accountPasswordForm.querySelector('button[type="submit"]').addEventListener('click', this.passwordChangeHandler.bind(this)) 2895 if (this.accountLanguageForm) { 2896 this.accountLanguageForm.querySelector('button[type="submit"]').addEventListener('click', this.languageChangeHandler.bind(this)); 2897 } 2898 } 2899 2900 showAvatarsHandler(event) { 2901 fetch(encodeURI(this.apiUrl), { 2902 method: 'GET', 2903 headers: { 2904 'X-CSRFToken': getCookie('csrftoken') 2905 } 2906 }) 2907 .then(checkStatus) 2908 .then(parseJSON) 2909 .then((data) => { 2910 this.showAvatarsPopUp(data); 2911 }) 2912 .catch((message) => {}); 2913 } 2914 2915 showAvatarsPopUp(data) { 2916 const popup = new PopUp(); 2917 2918 popup.showAvatars(data); 2919 2920 document.querySelector('#' + popup.id).querySelectorAll('[data-action="change-avatar"]').forEach((avatar) => { 2921 const hash = avatar.dataset.hash; 2922 const src = avatar.dataset.src; 2923 avatar.addEventListener('click', (event) => { 2924 const formData = new FormData(); 2925 formData.append('action', 'change-avatar'); 2926 formData.append('hash', hash); 2927 2928 fetch(this.accountAvatarForm.action, { 2929 method: 'POST', 2930 headers: { 2931 'X-CSRFToken': getCookie('csrftoken') 2932 }, 2933 body: formData 2934 }) 2935 .then(checkStatus) 2936 .then(parseJSON) 2937 .then((data) => { 2938 if (data.success) { 2939 return data; 2940 } 2941 }) 2942 .then((data) => { 2943 document.querySelector('.navigation-link--icon-user').style.cssText = '--avatar-url: url("' + src + '")'; 2944 document.querySelectorAll('[data-avatar="true"]').forEach((elem) => { 2945 elem.src = src; 2946 }); 2947 popup.close(); 2948 }) 2949 .catch((message) => {}); 2950 }); 2951 }); 2952 } 2953 2954 usernameChangeHandler(event) { 2955 event.preventDefault() 2956 2957 this.reset() 2958 this.form = this.accountUsernameForm 2959 2960 if (this.validUsername() && this.validCharUsername()) { 2961 this.accountUsernameForm.submit() 2962 } 2963 } 2964 2965 emailChangeHandler(event) { 2966 event.preventDefault() 2967 2968 this.reset() 2969 this.form = this.accountEmailForm 2970 2971 const validItems = [] 2972 validItems.push(this.validEmail()) 2973 validItems.push(this.validEmailRepeat()) 2974 2975 const is_valid = validItems.reduce(function(res, item) { 2976 return res && item 2977 }) 2978 2979 if (is_valid) { 2980 this.accountEmailForm.submit() 2981 } 2982 } 2983 2984 passwordChangeHandler(event) { 2985 event.preventDefault() 2986 2987 this.reset() 2988 this.form = this.accountPasswordForm 2989 2990 const validItems = [] 2991 validItems.push(this.validPassword()) 2992 validItems.push(this.validPasswordRepeat()) 2993 2994 const is_valid = validItems.reduce(function(res, item) { 2995 return res && item 2996 }) 2997 2998 if (is_valid) { 2999 this.accountPasswordForm.submit() 3000 } 3001 } 3002 3003 languageChangeHandler(event) { 3004 event.preventDefault(); 3005 this.reset(); 3006 this.form = this.accountLanguageForm; 3007 this.accountLanguageForm.submit(); 3008 } 3009 3010 validUsername() { 3011 const usernameElement = this.accountUsernameForm.querySelector('input[name="username"]') 3012 const is_valid = validatorEmpty(usernameElement.value.trim()) && usernameElement.value.trim().length <= 30 3013 this.form_error(usernameElement, is_valid ? '' : gettext('Username must not be empty or longer than 30 characters')) 3014 return is_valid 3015 } 3016 3017 validCharUsername() { 3018 const usernameElement = this.accountUsernameForm.querySelector('input[name="username"]') 3019 const is_valid = usernameElement.value.trim().match(/^[\p{L}\p{N}_]+$/u) 3020 this.form_error(usernameElement, is_valid ? '' : gettext('Username should contain only letters, numbers and underscore (the "_" symbol)')) 3021 return is_valid 3022 } 3023 3024 validEmail() { 3025 const emailElement = this.accountEmailForm.querySelector('input[name="new_email"]') 3026 const is_valid = validatorEmail(emailElement.value) 3027 3028 this.form_error(emailElement, is_valid ? '' : gettext('Incorrect email')) 3029 return is_valid 3030 } 3031 3032 validEmailRepeat() { 3033 const emailElement = this.accountEmailForm.querySelector('input[name="new_email"]') 3034 const emailRepeatElement = this.accountEmailForm.querySelector('input[name="new_email_repeat"]') 3035 const is_valid = validatorRepeat(emailElement.value, emailRepeatElement.value) 3036 3037 this.form_error(emailRepeatElement, is_valid ? '' : gettext('The emails you\'ve entered do n
3037ot match')) 3038 return is_valid 3039 } 3040 3041 validPassword() { 3042 const passwordElement = this.accountPasswordForm.querySelector('input[name="new_password"]') 3043 const is_valid_length = validatorPasswordLength(passwordElement.value) 3044 const is_valid_char = validatorPasswordChar(passwordElement.value) 3045 let text = is_valid_length ? '' : gettext('Password must contain at least 8 characters') 3046 3047 this.form_error(passwordElement, text) 3048 this.form_error(passwordElement, is_valid_char ? text : text + '<br>' + gettext('Include a-z, A-Z, 0-9 or symbols for a stronger password')) 3049 return is_valid_length && is_valid_char 3050 } 3051 3052 validPasswordRepeat() { 3053 const passwordElement = this.accountPasswordForm.querySelector('input[name="new_password"]') 3054 const passwordRepeatElement = this.accountPasswordForm.querySelector('input[name="new_password_repeat"]') 3055 const is_valid = validatorRepeat(passwordElement.value, passwordRepeatElement.value) 3056 3057 this.form_error(passwordRepeatElement, is_valid ? '' : gettext('The passwords you\'ve entered do not match')) 3058 return is_valid 3059 } 3060} 3061 3062class AccountDeletion extends BaseForm { 3063 constructor() { 3064 super() 3065 this.form = document.querySelector('.account-delete-form') 3066 this.reset() 3067 this.listen() 3068 } 3069 3070 listen() { 3071 this.form.querySelector('button[type="submit"]').addEventListener('click', this.handler.bind(this)) 3072 } 3073 3074 handler(event) { 3075 event.preventDefault() 3076 3077 this.reset() 3078 3079 const validItems = [] 3080 this.form.querySelectorAll('input[type="text"]').forEach(function(elem) { 3081 validItems.push(this.validTextInput(elem)) 3082 }.bind(this)) 3083 validItems.push(this.validAgree()) 3084 3085 const is_valid = validItems.reduce(function(res, item) { 3086 return res && item 3087 }) 3088 3089 if (is_valid) { 3090 this.form.submit() 3091 } 3092 } 3093 3094 validTextInput(element) { 3095 const is_valid = validatorEmpty(element.value) 3096 this.error(element, is_valid ? '': gettext('Required')) 3097 return is_valid 3098 } 3099 3100 validAgree() { 3101 const element = this.form.querySelector('input[name="agree"]') 3102 const is_valid = element.checked 3103 this.form_error(element, is_valid ? '' : gettext('Please agree to the conditions')) 3104 return is_valid 3105 } 3106} 3107 3108class AccountGDPR extends BaseForm { 3109 constructor() { 3110 super() 3111 this.form = document.querySelector('.account-gdpr-form') 3112 this.listen() 3113 } 3114 3115 listen() { 3116 this.form.querySelector('button[type="submit"]').addEventListener('click', this.handler.bind(this)) 3117 } 3118 3119 handler(event) { 3120 event.preventDefault() 3121 const element = this.form.querySelector('input[name="password"]') 3122 const is_valid = validatorEmpty(element.value) 3123 this.error(element, is_valid ? '': gettext('Required')) 3124 3125 if (is_valid) { 3126 this.form.submit() 3127 } 3128 } 3129} 3130 3131class Product { 3132 constructor(is_auth) { 3133 this.is_auth = is_auth 3134 this.systemRequirementsContainerElement = document.querySelector('.product-system-requirements') 3135 this.listen() 3136 } 3137 3138 listen() { 3139 const regionsButtonElement = document.querySelector('#regions-button') 3140 if (regionsButtonElement) { 3141 regionsButtonElement.addEventListener('click', this.regionsHandler) 3142 } 3143 const systemAlertForm = document.querySelector('#system-alert-form'); 3144 if (systemAlertForm) { 3145 const systemAlertCheckBox = systemAlertForm.querySelector('#id_subscribe'); 3146 if (systemAlertCheckBox) { 3147 systemAlertCheckBox.addEventListener('change', (event) => { 3148 this.systemAlertHandler(new FormData(systemAlertForm)) 3149 }); 3150 } 3151 } 3152 if (this.systemRequirementsContainerElement != null) { 3153 this.systemRequirementsContainerElement.querySelectorAll('.product-system-requirements--item').forEach(function(elem) { 3154 elem.addEventListener('click', function(event) { 3155 const id = event.target.dataset.id 3156 this.systemRequirementsContainerElement.querySelectorAll('.product-system-requirements--item').forEach(function(elem) { 3157 elem.classList.remove('is_active') 3158 }) 3159 this.systemRequirementsContainerElement.querySelectorAll('.product-system-requirements--content').forEach(function(elem) { 3160 elem.classList.remove('is_active') 3161 }) 3162 event.target.classList.add('is_active') 3163 this.systemRequirementsContainerElement.querySelector('.product-system-requirements--content[data-target="' + id + '"]').classList.add('is_active') 3164 }.bind(this)) 3165 }.bind(this)) 3166 } 3167 const product_section = document.querySelector('.page.catalog.product') 3168 if (product_section) { 3169 document.dispatchEvent(new CustomEvent('product_shown', {detail: {data: product_section.dataset}})) 3170 } 3171 } 3172 3173 regionsHandler(event) { 3174 event.preventDefault() 3175 const newTitle = event.target.dataset.title 3176 event.target.dataset.title = event.target.innerHTML 3177 event.target.innerHTML = newTitle 3178 const descriptionElement = document.querySelector('#regions-description') 3179 if (event.target.classList.contains('is_active')) { 3180 event.target.classList.remove('is_active') 3181 descriptionElement.style.display = 'none' 3182 } else { 3183 event.target.classList.add('is_active') 3184 descriptionElement.style.display = 'block' 3185 } 3186 } 3187 3188 systemAlertHandler(data) { 3189 fetch(window.location.pathname.toString(), { 3190 method: 'POST', 3191 headers: { 3192 'X-CSRFToken': getCookie('csrftoken') 3193 }, 3194 body: data 3195 }) 3196 .then(checkStatus) 3197 .then(parseJSON) 3198 .then((data) => { 3199 if (data.hasOwnProperty('success') && data.success && data.message) { 3200 new PopUp().showSystemAlert(data.message, data.signin_required) 3201 } 3202 else if (data.hasOwnProperty('blocked') && data.blocked) { 3203 new PopUp().showBlockedSystemAlert(data.popup) 3204 } 3205 }) 3206 .catch((error) => { 3207 new PopUp().showErrorPopUp(gettext('An error occurred, try again later')) 3208 }) 3209 } 3210} 3211 3212class CouponForm { 3213 constructor() {
3214 this.form = document.querySelector('#cart-coupon-form') 3215 this.listen() 3216 } 3217 3218 listen() { 3219 const el = this.form ? this.form.querySelector('input[type="button"]'):null 3220 if (el) { 3221 el.addEventListener('click', this.handler.bind(this)) 3222 } 3223 } 3224 3225 handler(event) { 3226 event.preventDefault() 3227 const action = event.target.dataset.action 3228 const inputElem = this.form.querySelector('input[type="text"]') 3229 if (inputElem.value.trim() !== '') { 3230 if (action === 'cancel') { 3231 inputElem.value = ' ' 3232 } 3233 this.form.submit() 3234 } 3235 } 3236} 3237 3238class CheckoutForm extends BaseForm { 3239 constructor(is_auth, proceed, apiUrl) { 3240 super() 3241 this.form = document.querySelector('#cart-checkout-form') 3242 this.is_auth = is_auth 3243 this.apiUrl = apiUrl 3244 this.proceed = proceed 3245 if (this.form !== null) { 3246 this.paymentMethodListElem = document.querySelector('.cart-payment-list') 3247 this.paymentMethodElem = this.form.querySelector('#id_payment') 3248 this.phoneElems = this.form.querySelectorAll('#id_phone_container input'); 3249 this.agreeElem = this.form.querySelector('#id_agree'); 3250 this.withdrawalRightAgreeElem = this.form.querySelector('#id_withdrawal_right_agree'); 3251 if (!this.is_auth) { 3252 this.recaptchaError = true 3253 this.emailElem = this.form.querySelector('#id_email') 3254 this.emailRepeatElem = this.form.querySelector('#id_email_repeat') 3255 this.recaptchaElem = this.form.querySelector('#id_recaptcha') 3256 this.changeEmailLinkElem = this.form.querySelector('#change_email_link') 3257 } 3258 this.render() 3259 this.listen() 3260 } 3261 } 3262 3263 render() { 3264 const BreakException = {} 3265 if (this.paymentMethodListElem.querySelectorAll('.cart-payment-item.is_active').length == 0) { 3266 try { 3267 this.paymentMethodListElem.querySelectorAll('.cart-payment-item').forEach(function(elem) { 3268 this.paymentMethodElem.value = elem.dataset.value 3269 elem.classList.add('is_active'); 3270 this.setActivePaymentMethod(); 3271 throw BreakException 3272 }.bind(this)) 3273 } catch (e) { 3274 if (e !== BreakException) throw e 3275 } 3276 } 3277 } 3278 3279 listen() { 3280 this.paymentMethodListElem.querySelectorAll('.cart-payment-item').forEach(function(elem) { 3281 elem.addEventListener('click', function(event) { 3282 this.paymentMethodElem.value = event.target.dataset.value 3283 this.paymentMethodListElem.querySelectorAll('.cart-payment-item.is_active').forEach(function(elem) { 3284 elem.classList.remove('is_active') 3285 }) 3286 event.target.classList.add('is_active') 3287 this.form_error(this.paymentMethodElem, '') 3288 this.setActivePaymentMethod(); 3289 }.bind(this)) 3290 }.bind(this)) 3291 3292 this.form.querySelector('[type="submit"]').addEventListener('click', function(event) { 3293 event.preventDefault() 3294 if (this.valid()) { 3295 try { 3296 this.form.insertAdjacentHTML('beforeend', '<input type="hidden" name="ga_client_id" value="' + window.gClientId + '">') 3297 } catch {} 3298 this.form.submit() 3299 } 3300 }.bind(this)) 3301 3302 if (!this.is_auth) { 3303 if (this.emailElem !== null) { 3304 this.emailElem.addEventListener('input', function(event) { 3305 this.validEmail() 3306 }.bind(this)) 3307 } 3308 3309 if (this.emailRepeatElem !== null) { 3310 this.emailRepeatElem.addEventListener('input', function(event) { 3311 this.validEmailRepeat() 3312 }.bind(this)) 3313 } 3314 3315 if (this.emailRepeatElem !== null) { 3316 this.emailRepeatElem.addEventListener('paste', function(event) { 3317 event.preventDefault() 3318 this.noCopyEmailRepeat() 3319 }.bind(this)) 3320 } 3321 3322 if (this.agreeElem !== null) { 3323 this.agreeElem.addEventListener('change', function(event) { 3324 this.validAgree() 3325 }.bind(this)) 3326 } 3327 3328 if (this.withdrawalRightAgreeElem !== null) { 3329 this.withdrawalRightAgreeElem.addEventListener('change', function(event) { 3330 this.validWithdrawalRightAgree() 3331 }.bind(this)) 3332 } 3333 3334 if (this.changeEmailLinkElem !== null) { 3335 this.changeEmailLinkElem.addEventListener('click', function(event) { 3336 event.preventDefault() 3337 this.form.querySelector('[name="action"]').value = 'change_email' 3338 this.form.submit() 3339 }.bind(this)) 3340 } 3341 } 3342 } 3343 3344 setActivePaymentMethod() { 3345 const formData = new FormData() 3346 formData.append('payment_method', this.paymentMethodElem.value) 3347 formData.append('action', 'SetPaymentMethod') 3348 fetch(encodeURI(this.apiUrl), { 3349 method: 'POST', 3350 headers: { 3351 'X-CSRFToken': getCookie('csrftoken') 3352 }, 3353 body: formData 3354 }) 3355 .then(checkStatus) 3356 .then(parseJSON) 3357 .then((data) => { 3358 if ( 3359 data.hasOwnProperty('id') && data.hasOwnProperty('is_phone_req') && data.hasOwnProperty('success') && 3360 data.success && data.id.toString() === this.paymentMethodElem.value 3361 ) { 3362 const phoneFieldLabel = document.querySelector('#id_phone_label'); 3363 const phoneFieldContainer = document.querySelector('#id_phone_container'); 3364 if (phoneFieldLabel && phoneFieldContainer) { 3365 if (data.is_phone_req) { 3366 phoneFieldLabel.classList.remove('hidden'); 3367 phoneFieldContainer.classList.remove('hidden'); 3368 phoneFieldContainer.querySelectorAll('input').forEach((element) => { 3369 element.setAttribute("required", ""); 3370 }); 3371 } else if (!phoneFieldLabel.classList.contains('hidden')) { 3372 phoneFieldLabel.classList.add('hidden'); 3373 phoneFieldContainer.classList.add('hidden'); 3374 phoneFieldContainer.querySelectorAll('input').forEach((element, index) => { 3375 element.removeAttribute("required"); 3376 if (index === 1) { 3377 element.value = ''; 3378 } 3379 }); 3380 } 3381 } 3382 } 3383 }); 3384 } 3385 3386 valid() { 3387 if (this.paymentMethodListElem.querySelector('.cart-payment-item.is_active') === null) { 3388 this.form_error(this.paymentMethodElem, gettext('Unfortunately there are no payment options available in your location at the moment. Please, check back later. We apologize for the inconvenience.')) 3389 return false 3390 } 3391 3392 let validation_list = [this.validPaymentMethod()] 3393 if (this.agreeElem !== null) { 3394 validation_list.push(this.validAgree()) 3395 } 3396 if (this.withdrawalRightAgreeElem !== null) {
3397 validation_list.push(this.validWithdrawalRightAgree()) 3398 } 3399 if (this.phoneElems && this.phoneElems.length) { 3400 validation_list.push(this.validPhone()); 3401 } 3402 if (!this.is_auth) { 3403 validation_list = validation_list.concat([this.validPaymentMethod()]) 3404 if (this.proceed) { 3405 validation_list = validation_list.concat([this.validEmail(), this.validEmailRepeat(), this.validCaptcha()]) 3406 } 3407 } 3408 return validation_list.reduce(function(res, item) { 3409 return res && item 3410 }) 3411 } 3412 3413 validPaymentMethod() { 3414 const is_valid = this.paymentMethodElem.value.trim().length != '' 3415 this.form_error(this.paymentMethodElem, is_valid ? '' : gettext('Choose a payment method')) 3416 return is_valid 3417 } 3418 3419 validPhone() { 3420 let is_valid = true; 3421 this.phoneElems.forEach((element) => {is_valid = is_valid && element.validity.valid}); 3422 this.form_error(this.phoneElems[0], is_valid ? '' : gettext('Type your valid phone number')) 3423 return is_valid 3424 } 3425 3426 validEmail() { 3427 const is_valid = validatorEmail(this.emailElem.value) 3428 this.form_error(this.emailElem, is_valid ? '' : gettext('Type your email')) 3429 return is_valid 3430 } 3431 3432 validEmailRepeat() { 3433 const is_valid = validatorRepeat(this.emailElem.value, this.emailRepeatElem.value) 3434 this.form_error(this.emailRepeatElem, is_valid ? '' : gettext('The emails you\'ve entered should match')) 3435 return is_valid 3436 } 3437 3438 noCopyEmailRepeat() { 3439 this.form_error(this.emailRepeatElem, gettext('The confirmation email must be entered manually')) 3440 } 3441 3442 validCaptcha() { 3443 this.form_error(this.recaptchaElem, this.recaptchaError ? gettext('Incorrect CAPTCHA') : '') 3444 return !this.recaptchaError 3445 } 3446 3447 verifyRecaptchaCallback(response) { 3448 this.recaptchaError = (response === undefined || response === null || response === "") ? true : false 3449 this.validCaptcha() 3450 } 3451 3452 validAgree() { 3453 this.form_error(this.agreeElem.parentNode, this.agreeElem.checked ? '' : gettext('Please agree to the terms and conditions.')) 3454 return this.agreeElem.checked 3455 } 3456 3457 validWithdrawalRightAgree() { 3458 this.form_error(this.withdrawalRightAgreeElem.parentNode, this.withdrawalRightAgreeElem.checked ? '' : gettext('Please confirm the digital content withdrawal consent before proceeding to checkout.')) 3459 return this.withdrawalRightAgreeElem.checked 3460 } 3461} 3462 3463class Order { 3464 constructor(code, apiUrl) { 3465 this.code = code 3466 this.delay = 20 3467 this.timer = null 3468 this.apiUrl = apiUrl 3469 this.completed_order_viewed_block = false 3470 3471 if (this.code !== null) { 3472 this.runTimer() 3473 } 3474 } 3475 3476 runTimer() { 3477 this.timer = setTimeout(this.timerHandler.bind(this), this.delay*1000) 3478 } 3479 3480 timerHandler() { 3481 fetch(encodeURI(this.apiUrl + '?code=' + this.code), { 3482 method: 'GET', 3483 headers: { 3484 'X-CSRFToken': getCookie('csrftoken') 3485 } 3486 }) 3487 .then(checkStatus) 3488 .then(parseJSON) 3489 .then(function(data) { 3490 if ('code' in data && data.code != this.code.toString()) { 3491 location.reload() 3492 } 3493 this.resetTimer() 3494 }.bind(this)) 3495 .catch(function() { 3496 this.resetTimer() 3497 }.bind(this)) 3498 } 3499 3500 completed_order_conversion() { 3501 if (!this.completed_order_viewed_block) { 3502 const formData = new FormData() 3503 formData.append('completed_order_viewed', 'yes') 3504 this.completed_order_viewed_block = true 3505 fetch(encodeURI(this.apiUrl), { 3506 method: 'POST', 3507 headers: { 3508 'X-CSRFToken': getCookie('csrftoken') 3509 }, 3510 body: formData 3511 }) 3512 .then(checkStatus) 3513 .catch(function() {}) 3514 } 3515 } 3516 3517 resetTimer() { 3518 clearTimeout(this.timer) 3519 this.runTimer() 3520 } 3521} 3522 3523class Support { 3524 constructor(language) { 3525 this.language = language 3526 this.searchElement = document.querySelector('.support--search') 3527 this.searchUrl = '/api/support/search/' 3528 this.searchTimestamp = 0 3529 this.searchData = null 3530 this.listen() 3531 return this 3532 } 3533 3534 listen() { 3535 if (this.searchElement) { 3536 this.searchElement.querySelector('input[type="search"]').addEventListener('input', this.searchHandler.bind(this)) 3537 this.searchElement.querySelector('input[type="search"]').addEventListener("keypress", function(event) { 3538 if (event.keyIdentifier=='U+000A' || event.keyIdentifier=='Enter' || event.keyCode==13) {
3539 this.showFullSearchResult(event) 3540 } 3541 }.bind(this)) 3542 this.searchElement.querySelector('input[type="button"]').addEventListener('click', this.showFullSearchResult.bind(this)) 3543 } 3544 } 3545 3546 listenShowFullSearchResultButton() { 3547 this.searchElement.querySelector('.support--search-show-all-button').addEventListener('click', this.showFullSearchResult.bind(this)) 3548 } 3549 3550 listenLink() { 3551 document.querySelectorAll('.support--search-item').forEach(function(elem) { 3552 elem.addEventListener('click', function() { 3553 top.location.href = this.dataset.href 3554 }) 3555 }) 3556 } 3557 3558 searchHandler(event) { 3559 const value = event.target.value 3560 if (value.length >= 3) { 3561 this.remove() 3562 this.searchData = null 3563 this.searchTimestamp = Date.now() 3564 fetch(encodeURI(get_url_with_locale(this.searchUrl, this.language) + value + '?timestamp=' + this.searchTimestamp), { 3565 method: 'GET', 3566 headers: { 3567 'X-CSRFToken': getCookie('csrftoken') 3568 } 3569 }) 3570 .then(checkStatus) 3571 .then(parseJSON) 3572 .then(function(data) { 3573 if(data.timestamp && parseInt(data.timestamp) < this.searchTimestamp) { 3574 throw 'expired data' 3575 } 3576 this.searchData = data 3577 return data 3578 }.bind(this)) 3579 .then(this.previewSearchResult.bind(this)) 3580 .catch(function(e) { 3581 this.searchData = null 3582 }.bind(this)) 3583 } 3584 3585 if (value.length == 0) { 3586 this.remove() 3587 this.searchData = null 3588 } 3589 } 3590 3591 previewSearchResult(data) { 3592 if (data.success) { 3593 this.searchData = data 3594 3595 let html = '' 3596 3597 html += '<div class="support--search-result">' 3598 html += ' <div class="support--search-header">' 3599 html += ' <div>' + gettext('Search results') + '</div>' 3600 html += ' <div><span class="support--search-total">
3600' + data.total + '</span>' + ngettext('result', 'results', data.total) + '</div>' 3601 html += ' </div>' 3602 if (data.total > 0) { 3603 html += ' <div class="support--search-body">' 3604 for (let index = 0; index < data.items.length && index < 5; index++) { 3605 html += ' <div class="support--search-item"><a href="' + data.items[index].link + '">' + data.items[index].title + '</a></div>' 3606 } 3607 html += ' </div>' 3608 html += ' <div class="support--search-footer">' 3609 html += ' <a href="#" class="support--search-show-all-button">' + gettext('Show all results') + '</a>' 3610 html += ' </div>' 3611 } 3612 html += '</div>' 3613 3614 this.remove() 3615 this.searchElement.insertAdjacentHTML('beforeend', html) 3616 if (data.total > 0) { 3617 this.listenShowFullSearchResultButton() 3618 } 3619 setTimeout(this.closeEventForSearch.bind(this), 500) 3620 } 3621 } 3622 3623 showFullSearchResult(event) { 3624 event.preventDefault() 3625 this.remove() 3626 if (this.searchData !== null) { 3627 const data = this.searchData 3628 let html = '' 3629 3630 html += '<div class="support--search-result full">' 3631 html += ' <div class="support--search-header">' 3632 html += ' <div>' + gettext('Search results for') + ' "' + escapeHtml(data.query) + '"</div>' 3633 html += ' <div><span class="support--search-total">
3633' + data.total + '</span>' + ngettext('result', 'results', data.total) + '</div>' 3634 html += ' </div>' 3635 if (data.total > 0) { 3636 html += ' <div class="support--search-body">' 3637 for (let index = 0; index < data.items.length; index++) { 3638 html += ' <div data-href="' + data.items[index].link + '" class="support--search-item">' 3639 html += ' <a>' + data.items[index].title + '</a>' 3640 html += ' <div class="support--search-item--description">' + data.items[index].desc + '</div>' 3641 html += ' </div>' 3642 } 3643 html += ' </div>' 3644 } 3645 html += '</div>' 3646 3647 document.querySelector('.support--blocks').insertAdjacentHTML('afterend', html) 3648 document.querySelector('.support--blocks').style.display = 'none' 3649 3650 this.listenLink() 3651 } 3652 } 3653 3654 remove() { 3655 this.removeSearchResult() 3656 this.removeSearchFullResult() 3657 } 3658 3659 removeSearchResult() { 3660 const node = this.searchElement.querySelector('.support--search-result') 3661 if (node && node.parentNode) { 3662 node.parentNode.removeChild(node) 3663 } 3664 } 3665 3666 removeSearchFullResult() { 3667 const node = document.querySelector('.support--search-result.full') 3668 if (node && node.parentNode) { 3669 node.parentNode.removeChild(node) 3670 document.querySelector('.support--blocks').style.display = 'flex' 3671 } 3672 } 3673 3674 closeEventForSearch() { 3675 const element = this.searchElement 3676 3677 const isVisible = function(elem) { 3678 return !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) 3679 } 3680 3681 const removeNode = function() { 3682 removeClickListener() 3683 this.removeSearchResult() 3684 }.bind(this) 3685 3686 const outsideClickListener = function(event) { 3687 if (!element.contains(event.target) && isVisible(element)) { 3688 removeNode() 3689 } 3690 } 3691 3692 const removeClickListener = function() { 3693 document.removeEventListener('click', outsideClickListener) 3694 } 3695 3696 document.addEventListener('click', outsideClickListener) 3697 } 3698} 3699 3700class SubscribeForm extends BaseForm { 3701 constructor(language) { 3702 super() 3703 this.language = language 3704 this.form = document.querySelector('.subscribe-form') 3705 if (this.form) { 3706 this.url = '/api/subscription/subscribe/' 3707 this.defaultErrorView = this.form.dataset && this.form.dataset.default_error 3708 this.defaultSuccessView = this.form.dataset && this.form.dataset.default_success 3709 this.searchTimestamp = 0 3710 this.listen() 3711 } 3712 } 3713 3714 listen() { 3715 if (this.form) { 3716 this.form.querySelector('input[name="email"]').addEventListener("input", function(event) { 3717 this.error(event.target, '') 3718 }.bind(this)) 3719 this.form.querySelector('input[name="email"]').addEventListener("keypress", function(event) { 3720 if (event.keyIdentifier=='U+000A' || event.keyIdentifier=='Enter' || event.keyCode==13) { 3721 this.handler(event) 3722 } 3723 }.bind(this)) 3724 this.form.querySelector('.subscribe-button').addEventListener('click', this.handler.bind(this)) 3725 } 3726 } 3727 3728 handler(event) { 3729 event.preventDefault() 3730 this.removeSubscribeMessage() 3731 this.reset() 3732 3733 const error_messages = [] 3734 3735 const element = this.form.querySelector('input[name="email"]') 3736 const is_valid_email = validatorEmail(element.value) && validatorEmpty(element.value) 3737 if (this.defaultErrorView) { 3738 this.error(element, is_valid_email ? '' : gettext('Incorrect email')) 3739 } 3740 else if (!is_valid_email) { 3741 error_messages.push(gettext('Incorrect email')) 3742 } 3743 3744 const elementAgree = this.form.querySelector('input[type="checkbox"]') 3745 const is_valid_agree = elementAgree.checked 3746 if (this.defaultErrorView) { 3747 this.form_error(elementAgree, is_valid_agree ? '' : gettext('Please agree to the privacy policy')) 3748 } 3749 else if (!is_valid_agree) { 3750 error_messages.push(gettext('Please agree to the privacy policy')) 3751 } 3752 3753 if (!this.defaultErrorView) { 3754 this.error('general', error_messages.join('<br>')) 3755 } 3756 3757 if (is_valid_email && is_valid_agree) { 3758 this.searchTimestamp = Date.now() 3759 fetch(encodeURI(get_url_with_locale(this.url, this.language) + '?email=' + element.value + '&agree=1timestamp=' + this.searchTimestamp), { 3760 method: 'GET', 3761 headers: { 3762 'X-CSRFToken': getCookie('csrftoken') 3763 } 3764 }) 3765 .then(checkStatus) 3766 .then(parseJSON) 3767 .then(function(data) { 3768 if(data.timestamp && parseInt(data.timestamp) < this.searchTimestamp) { 3769 throw 'expired data' 3770 } 3771 let html = '' 3772 if (data.success) { 3773 if (this.defaultSuccessView) { 3774 html = '<div class="form-success">' + data.message + '</div>' 3775 } 3776 else { 3777 html = '<p class="subscribe-message m-b-1 color-spring-green">' + data.message + '</p>' 3778 } 3779 } 3780 else { 3781 if (this.defaultErrorView) { 3782 this.form_error('general', data.message) 3783 } 3784 else { 3785 html = '<p class="subscribe-message m-b-1 color-roman">' + data.message + '</p>' 3786 } 3787 } 3788 element.value = '' 3789 this.form.insertAdjacentHTML('afterbegin', html) 3790 setTimeout(this.removeSubscribeMessage.bind(this), 8000) 3791 }.bind(this)) 3792 .catch(function(e) { }) 3793 } 3794 } 3795 3796 error(element, message) { 3797 if (this.defaultErrorView) { 3798 super.error(element, message) 3799 } 3800 else if (message == '') { 3801 this.removeSubscribeMessage() 3802 } 3803 else { 3804 this.form.insertAdjacentHTML('afterbegin', '<p class="subscribe-message m-b-1 color-roman">' + message + '</p>') 3805 } 3806 } 3807 3808 removeSubscribeMessage() { 3809 const nodes = this.form.querySelectorAll('.subscribe-message')
3810 nodes.forEach(function(node) { 3811 if (node && node.parentNode) { 3812 node.parentNode.removeChild(node) 3813 } 3814 }) 3815 } 3816} 3817 3818class ContactForm extends BaseForm { 3819 constructor(show_recaptcha) { 3820 super() 3821 this.show_recaptcha = show_recaptcha 3822 this.form = document.querySelector('.contact-form') 3823 if (this.show_recaptcha) { 3824 this.recaptchaError = true 3825 } 3826 this.listen() 3827 } 3828 3829 listen() { 3830 this.form.querySelector('button[type="submit"]').addEventListener('click', this.handler.bind(this)) 3831 this.form.querySelector('input[name="agree"]').addEventListener('change', function(event) { 3832 this.validAgree() 3833 }.bind(this)) 3834 this.form.querySelector('input[name="email"]').addEventListener('input', function(event) { 3835 this.validEmail() 3836 }.bind(this)) 3837 this.form.querySelector('textarea[name="message"]').addEventListener('input', function(event) { 3838 this.validMessage() 3839 }.bind(this)) 3840 this.form.querySelector('input[name="attach"]').addEventListener('change', function(event) { 3841 this.validAttach() 3842 }.bind(this)) 3843 } 3844 3845 handler(event) { 3846 event.preventDefault() 3847 3848 this.reset() 3849 3850 const validItems = [] 3851 if (this.show_recaptcha) { 3852 validItems.push(this.validCaptcha()) 3853 } 3854 validItems.push(this.validEmail()) 3855 validItems.push(this.validAgree()) 3856 validItems.push(this.validMessage()) 3857 validItems.push(this.validAttach()) 3858 3859 const is_valid = validItems.reduce(function(res, item) { 3860 return res && item 3861 }) 3862 3863 if (is_valid) { 3864 this.form.submit() 3865 } 3866 } 3867 3868 validEmail() { 3869 const emailElement = this.form.querySelector('input[name="email"]') 3870 let is_valid = true 3871 if (this.show_recaptcha || emailElement.value) { 3872 is_valid = validatorEmail(emailElement.value) 3873 } 3874 this.form_error(emailElement, is_valid ? '' : gettext('Incorrect email')) 3875 return is_valid 3876 } 3877 3878 validAgree() { 3879 const element = this.form.querySelector('input[name="agree"]') 3880 const is_valid = element.checked 3881 this.form_error(element, is_valid ? '' : gettext('Please agree to the conditions')) 3882 return is_valid 3883 } 3884 3885 validCaptcha() { 3886 const recaptchaElement = this.form.querySelector('#id_recaptcha') 3887 this.form_error(recaptchaElement, this.recaptchaError ? gettext('Incorrect CAPTCHA') : '') 3888 return !this.recaptchaError 3889 } 3890 3891 verifyRecaptchaCallback(response) { 3892 this.recaptchaError = (response === undefined || response === null || response === "") ? true : false 3893 this.validCaptcha() 3894 } 3895 3896 validMessage() { 3897 const messageElement = this.form.querySelector('textarea[name="message"]') 3898 const is_valid = validatorEmpty(messageElement.value) 3899 this.form_error(messageElement, is_valid ? '': gettext('Your message can not be empty')) 3900 return is_valid 3901 } 3902 3903 validAttach() { 3904 const element = this.form.querySelector('input[name="attach"]'); 3905 const previewElement = this.form.querySelector('input[name="attach"] + div > span'); 3906 if (!element || (element && element.files.length === 0)) { 3907 previewElement.textContent = gettext('No file chosen') 3908 return true; 3909 } 3910 const is_valid_type = this.validFileType(element.files[0], element.dataset.types.split(',')); 3911 const is_valid_size = this.validFileSize(element.files[0], element.dataset.size); 3912 if (!is_valid_type) { 3913 this.form_error(element, gettext('Not a valid file type, images and PDF supported only')); 3914 } 3915 else if (!is_valid_size) { 3916 this.form_error(element, gettext('Please select a file less than 10Mb')); 3917 } 3918 else { 3919 this.form_error(element, ''); 3920 } 3921 3922 const check = is_valid_type && is_valid_size 3923 3924 if (check) { 3925 previewElement.textContent = element.files[0].name.toString(); 3926 } 3927 3928 return check; 3929 } 3930 3931 validFileType(file, allowedTypes) { 3932 return allowedTypes.includes(file.type); 3933 } 3934 3935 validFileSize(file, maxSize) { 3936 return file.size <= parseInt(maxSize) && file.size > 0; 3937 } 3938} 3939 3940class WithdrawForm extends BaseForm { 3941 constructor() { 3942 super() 3943 this.form = document.querySelector('#partner-withdraw-form') 3944 this.listen() 3945 } 3946 3947 listen() { 3948 this.form.addEventListener('submit', this.handler.bind(this)) 3949 this.form.querySelector('input[name="amount"]').addEventListener('beforeinput', (event) => { 3950 const data = event.data; 3951 if(data) { 3952 const isAllow = /^\d*\.?\d*$/.test(data); 3953 if(!isAllow) { 3954 event.preventDefault(); 3955 } 3956 } 3957 }); 3958 this.form.querySelector('input[name="amount"]').addEventListener('change', function(event) { 3959 this.validAmount() 3960 }.bind(this)) 3961 this.form.querySelector('input[name="invoice"]').addEventListener('change', function(event) { 3962 this.validInvoice() 3963 }.bind(this)) 3964 } 3965 3966 handler(event) { 3967 event.preventDefault() 3968 3969 this.reset() 3970 3971 const validItems = [] 3972 validItems.push(this.validAmount()) 3973 validItems.push(this.validInvoice()) 3974 3975 const is_valid = validItems.reduce(function(res, item) { 3976 return res && item 3977 }) 3978 3979 if (is_valid) { 3980 this.form.submit() 3981 } 3982 } 3983 3984 validAmount() { 3985 const amountInput = this.form.querySelector('input[name="amount"]') 3986 const max = parseFloat(amountInput.dataset.vmax) 3987 const min = parseFloat(amountInput.dataset.vmin) 3988 const amount = parseFloat(amountInput.value) 3989 let is_valid = false 3990 if (!validatorEmpty(amountInput.value) || amount == 0) { 3991 this.form_error(amountInput, is_valid ? '': gettext('Amount is required')) 3992 } else if (amount < min) { 3993 this.form_error(amountInput, is_valid ? '': gettext('Min payout amount') + ' - ' + amountInput.dataset.vmin + ' USD') 3994 } else if (amount > max) { 3995 this.form_error(amountInput, is_valid ? '': gettext('Max available payout amount') + ' - ' + amountInput.dataset.vmax + ' USD') 3996 } else { 3997 is_valid = true 3998 } 3999 return is_valid 4000 } 4001 4002 validInvoice() { 4003 const element = this.form.querySelector('input[name="invoice"]'); 4004 if (!element || (element && element.files.length === 0)) { 4005 this.form_error(element, gettext('Please attach your invoice')); 4006 return false;
4007 } 4008 const is_valid_type = this.validFileType(element.files[0], element.dataset.types.split(',')); 4009 const is_valid_size = this.validFileSize(element.files[0], element.dataset.size); 4010 if (!is_valid_type) { 4011 this.form_error(element, gettext('Not a valid file type, images and PDF supported only')); 4012 } 4013 else if (!is_valid_size) { 4014 this.form_error(element, gettext('Please select a file less than 10Mb')); 4015 } 4016 else { 4017 this.form_error(element, ''); 4018 } 4019 return is_valid_type && is_valid_size; 4020 } 4021 4022 validFileType(file, allowedTypes) { 4023 return allowedTypes.includes(file.type); 4024 } 4025 4026 validFileSize(file, maxSize) { 4027 return file.size <= parseInt(maxSize) && file.size > 0; 4028 } 4029} 4030 4031class Tab { 4032 constructor(tabElement=null) { 4033 this.run(tabElement); 4034 } 4035 4036 run(tabElement) { 4037 const tabHandler = (tabElement) => { 4038 let activeButtonElement = undefined; 4039 4040 tabElement.querySelectorAll('.is_active[data-type="navigation"]').forEach((buttonElement) => { 4041 if (activeButtonElement === undefined) { 4042 activeButtonElement = buttonElement; 4043 } 4044 }); 4045 4046 tabElement.querySelectorAll('[data-type="navigation"]').forEach((buttonElement, index) => { 4047 if (activeButtonElement === undefined && index === 0) { 4048 activeButtonElement = buttonElement; 4049 } 4050 4051 const target = buttonElement.dataset.target; 4052 4053 if (activeButtonElement === buttonElement) { 4054 buttonElement.classList.add('is_active'); 4055 tabElement.querySelector('#' + target).classList.add('is_active'); 4056 } else { 4057 buttonElement.classList.remove('is_active'); 4058 tabElement.querySelector('#' + target).classList.remove('is_active'); 4059 } 4060 4061 buttonElement.addEventListener('click', (event) => { 4062 tabElement.querySelectorAll('[data-type="pane"]').forEach((el) => el.classList.remove('is_active')); 4063 tabElement.querySelector('#' + target).classList.add('is_active'); 4064 tabElement.querySelectorAll('[data-type="navigation"]').forEach((el) => el.classList.remove('is_active')); 4065 buttonElement.classList.add('is_active'); 4066 }); 4067 }); 4068 } 4069 4070 if (tabElement !== null) { 4071 tabHandler(tabElement); 4072 } else { 4073 document.querySelectorAll('[data-tab="true"]').forEach((el) => { 4074 tabHandler(el); 4075 }); 4076 } 4077 } 4078} 4079 4080;(() => { 4081 'use strict' 4082 4083 window.ubisoftActivation = { 4084 initiated: false, 4085 lang: window.navigator.language || navigator.userLanguage, 4086 appId: '59adae69-0388-4a38-bd8c-1f5cb3a714ef', 4087 genomeId: '7f6502bf-f172-401c-b565-42679f2168f0', 4088 sdk: null, 4089 url: null, 4090 isFirstTry: true, 4091 callbackUrl: null, 4092 activeForm: null, 4093 productName: null, 4094 popup: null, 4095 4096 init: function() { 4097 if (this.initiated) { 4098 return 4099 } 4100 this.initiated = true 4101 Connect.init({ 4102 env: 'PROD', 4103 appId: this.appId, 4104 genomeId: this.genomeId, 4105 lang: this.lang, 4106 nextUrl: '', 4107 thirdPartyCookiesSupport: false 4108 }); 4109 Connect.sdk.subscribe(function(sdk) { 4110 window.ubisoftActivation.sdk = sdk; 4111 window.ubisoftActivation.proceed(); 4112 }); 4113 this.url = 'https://connect.ubisoft.com/login?appId=' + this.appId + '&genomeId=' + this.genomeId + '&lang=' + this.lang + '&nextUrl=' + this.callbackUrl 4114 }, 4115 4116 proceed: function() { 4117 if (!this.initiated) { 4118 this.init() 4119 } 4120 this.sdk.getTicket().subscribe(function(response) { 4121 if (response.status !== 'ok' && window.ubisoftActivation.isFirstTry) { 4122 let popup = new PopUp() 4123 popup.isModal = true 4124 popup.setHeadline(gettext('Login to Ubisoft')).setBodyHtml('<iframe width="400" height="655" src="' + window.ubisoftActivation.url + '"></iframe>') 4125 popup.afterClose = function() { 4126 if (!window.ubisoftActivation.isFirstTry) { 4127 window.ubisoftActivation.proceed() 4128 } 4129 } 4130 popup.show() 4131 window.ubisoftActivation.popup = popup 4132 return 4133 } 4134 if (response.status !== 'ok' && !window.ubisoftActivation.isFirstTry) { 4135 window.ubisoftActivation.isFirstTry = true 4136 return 4137 } 4138 4139 var ticketData = response.payload 4140 window.ubisoftActivation.sdk.getProfiles(ticketData.ticket, ticketData.sessionId, ticketData.userId).subscribe(function(response){ 4141 var confirm_popup = new PopUp() 4142 confirm_popup.isModal = true 4143 confirm_popup.runCloseEvent = false 4144 confirm_popup.showPopupCloseButton = false 4145 confirm_popup.setClass('popup-container').setHeadline(gettext('Activate in Ubisoft Connect')).setBodyHtml('<p>' + gettext('Are you sure that you want to add game "{0}" to account {1}?').format(window.ubisoftActivation.productName, response.payload.profiles[0].nameOnPlatform) + '</p>').setFooterHtml( 4146 '<div class="popup-container--footer-action"><button type="button" class="button" id="confirm-popup-button-yes">' + gettext('Yes') + '</button></div>' + 4147 '<div class="popup-container--footer-close m-r-1"><button type="button" class="button button-outline" id="confirm-popup-button-no">' + gettext('No') + '</button></div>' 4148 ).show() 4149 document.querySelector('#confirm-popup-button-yes').addEventListener('click', function(event) { 4150 confirm_popup.close() 4151 window.ubisoftActivation.activeForm.querySelector('input[name="user_uid"]').value=ticketData.userId 4152 window.ubisoftActivation.activeForm.querySelector('input[name="ticket"]').value=ticketData.ticket 4153 window.ubisoftActivation.sdk.logout(ticketData.ticket, ticketData.sessionId).subscribe(function(s) { 4154 window.ubisoftActivation.activeForm.submit() 4155 return 4156 }) 4157 }) 4158 4159 document.querySelector('#confirm-popup-button-no').addEventListener('click', function(event) { 4160 confirm_popup.close() 4161 window.ubisoftActivation.sdk.logout(ticketData.ticket, ticketData.sessionId).subscribe(function(s) { 4162 return 4163 }); 4164 }) 4165 }) 4166 }) 4167 } 4168 } 4169})() 4170 4171;(() => { 4172 'use strict' 4173 4174 const throttle = (type, name, obj) => { 4175 obj = obj || window; 4176 let running = false;
4177 let widthScreen = 0; 4178 const func = () => { 4179 const tmpWidthScreen = window.innerWidth; 4180 if (running || widthScreen === tmpWidthScreen) { return; } 4181 running = true; 4182 requestAnimationFrame(function() { 4183 widthScreen = tmpWidthScreen; 4184 document.dispatchEvent(new CustomEvent( 4185 name, 4186 { 4187 bubbles: true, 4188 cancelable: true, 4189 detail: { 4190 orientation: widthScreen > window.innerHeight ? "landscape" : "primary" 4191 } 4192 } 4193 )); 4194 running = false; 4195 }); 4196 }; 4197 obj.addEventListener(type, func); 4198 }; 4199 4200 if (window.DeviceOrientationEvent) { 4201 throttle("resize", "rotate"); 4202 } 4203})() 4204 4205;(() => { 4206 'use strict' 4207 4208 document.addEventListener('touchstart', handleTouchStart, false) 4209 document.addEventListener('touchmove', handleTouchMove, false) 4210 document.addEventListener('touchend', handleTouchEnd, false) 4211 4212 let xDown = null 4213 let yDown = null 4214 let xDiff = null 4215 let yDiff = null 4216 let timeDown = null 4217 let startElement = null 4218 4219 function handleTouchEnd(e) { 4220 if (startElement !== e.target) return 4221 4222 const swipeThreshold = 20 // 20px 4223 const swipeTimeout = 400 // 400ms 4224 const timeDiff = Date.now() - timeDown 4225 let eventDir = null
4226 let changedTouches = e.changedTouches || e.touches || [] 4227 4228 if (Math.abs(xDiff) > Math.abs(yDiff)) { 4229 if (Math.abs(xDiff) > swipeThreshold && timeDiff < swipeTimeout) { 4230 eventDir = xDiff > 0 ? 'left' : 'right' 4231 } 4232 } 4233 else if (Math.abs(yDiff) > swipeThreshold && timeDiff < swipeTimeout) { 4234 eventDir = yDiff > 0 ? 'up' : 'down' 4235 } 4236 4237 if (eventDir !== null) { 4238 startElement.dispatchEvent(new CustomEvent('swiped', { bubbles: true, cancelable: true, detail: {dir: eventDir} })) 4239 } 4240 4241 xDown = null 4242 yDown = null 4243 timeDown = null 4244 } 4245 4246 function handleTouchStart(e) { 4247 startElement = e.target 4248 timeDown = Date.now() 4249 xDown = e.touches[0].clientX 4250 yDown = e.touches[0].clientY 4251 xDiff = 0 4252 yDiff = 0 4253 } 4254 4255 function handleTouchMove(e) { 4256 if (xDown === null || yDown === null) return 4257 xDiff = xDown - e.touches[0].clientX 4258 yDiff = yDown - e.touches[0].clientY 4259 } 4260})() 4261 4262// menu 4263;(() => { 4264 'use strict' 4265 4266 const $popoverLinks = document.querySelectorAll('[data-popover]') 4267 const $popovers = document.querySelectorAll('.popover') 4268 const $popmenuLink = document.querySelector('[data-popmenu]') 4269 const $popmenuCloseLink = document.querySelector('.popmenu-container-close') 4270 const $popmenuToggleLinks = document.querySelectorAll('[data-popmenu_toggle]') 4271 const $popmenutoggles = document.querySelectorAll('.popmenu-toogle') 4272 4273 for (let index = 0; index < $popoverLinks.length; index++) { 4274 $popoverLinks[index].parentNode.addEventListener('mouseover', openPopover) 4275 $popoverLinks[index].addEventListener('click', function() {event.preventDefault()}) 4276 $popoverLinks[index].parentNode.addEventListener('touchstart', openPopover, {passive: false}) 4277 $popoverLinks[index].parentNode.addEventListener('mouseout', closePopover) 4278 } 4279 4280 for (let index = 0; index < $popmenuToggleLinks.length; index++) { 4281 $popmenuToggleLinks[index].addEventListener('touchstart', openPopmenuToggle, {passive: false}) 4282 $popmenuToggleLinks[index].addEventListener('click', openPopmenuToggle) 4283 } 4284 4285 if ($popmenuLink) { 4286 $popmenuLink.parentNode.addEventListener('touchstart', openPopmenu, {passive: false}) 4287 $popmenuLink.parentNode.addEventListener('click', openPopmenu) 4288 } 4289 4290 if ($popmenuCloseLink) { 4291 $popmenuCloseLink.addEventListener('click', closePopmenu) 4292 $popmenuCloseLink.addEventListener('touchstart', closePopmenu, {passive: false}) 4293 } 4294 4295 function closePopover (event) { 4296 for (let index = 0; index < $popovers.length; index++) { 4297 $popovers[index].classList.remove('popover-open') 4298 } 4299 } 4300 4301 function closePopmenu (event) { 4302 document.querySelector('.wrapper').style.filter = 'none' 4303 document.querySelector('body').style.overflow = 'auto' 4304 document.querySelector($popmenuLink.getAttribute('href')).classList.remove('popmenu-open') 4305 } 4306 4307 function closePopmenuToggle (event) { 4308 for (let index = 0; index < $popmenutoggles.length; index++) { 4309 $popmenutoggles[index].classList.remove('popmenu-toogle-open') 4310 } 4311 for (let index = 0; index < $popmenuToggleLinks.length; index++) { 4312 $popmenuToggleLinks[index].classList.remove('is_active') 4313 } 4314 } 4315 4316 function openPopover (event) { 4317 event.preventDefault() 4318 const elem = this.querySelector('[data-popover]') 4319 if (document.querySelector(elem.getAttribute('href')).classList.contains('popover-open')) { 4320 document.querySelector(elem.getAttribute('href')).classList.remove('popover-open') 4321 } else { 4322 closePopover() 4323 document.querySelector(elem.getAttribute('href')).classList.add('popover-open') 4324 } 4325 event.stopImmediatePropagation() 4326 } 4327 4328 function openPopmenu (event) { 4329 event.preventDefault() 4330 document.querySelector('.popmenu-container--for-list').style.height = '0' 4331 const elem = this.querySelector('[data-popmenu]') 4332 document.querySelector(elem.getAttribute('href')).classList.add('popmenu-open') 4333 document.querySelector('.wrapper').style.filter = 'blur(5px)' 4334 document.querySelector('body').style.overflow = 'hidden' 4335 setTimeout(function() { 4336 document.querySelector('.popmenu-container--for-list').style.height = '100%' 4337 }, 300) 4338 event.stopImmediatePropagation() 4339 } 4340 4341 function openPopmenuToggle (event) { 4342 event.preventDefault() 4343 if (document.querySelector(this.getAttribute('href')).classList.contains('popmenu-toogle-open')) { 4344 document.querySelector(this.getAttribute('href')).classList.remove('popmenu-toogle-open') 4345 this.classList.remove('is_active') 4346 } else { 4347 closePopmenuToggle() 4348 document.querySelector(this.getAttribute('href')).classList.add('popmenu-toogle-open') 4349 this.classList.add('is_active') 4350 } 4351 event.stopImmediatePropagation() 4352 } 4353 4354 4355 const formErrorElements = document.querySelectorAll('.form-error') 4356 if (formErrorElements) {
4357 formErrorElements.forEach(function(elem) { 4358 elem.scrollIntoView() 4359 }) 4360 } 4361})() 4362 4363;(() => { 4364 'use strict' 4365 const cartInstance = new Cart(window.cart_items, window.current_language_code) 4366 const searchInstance = new SearchForm('#search-form', window.current_language_code) 4367 const selectInstance = new Select() 4368 const subscribeFormInstance = new SubscribeForm(window.current_language_code) 4369})() 4370 4371const ageFormHandler = (form) => { 4372 const isPopup = form.dataset.popup === '1'; 4373 form.addEventListener('submit', (e) => { 4374 e.stopPropagation(); 4375 e.preventDefault(); 4376 const formData = new FormData(form); 4377 fetch(form.action, { 4378 method: 'POST', 4379 headers: { 4380 'X-CSRFToken': getCookie('csrftoken') 4381 }, 4382 body: formData 4383 }) 4384 .then(checkStatus) 4385 .then(parseJSON) 4386 .then((data) => { 4387 if (!isPopup) { 4388 location.reload(); 4389 return true; 4390 } 4391 4392 if (data.hasOwnProperty('full_years')) { 4393 document.querySelectorAll('[data-required-age]').forEach((el) => { 4394 const requiredAge = parseInt(el.dataset.requiredAge !== '' ? el.dataset.requiredAge : 0); 4395 if (requiredAge <= parseInt(data.full_years)) { 4396 el.classList.remove('blured'); 4397 } 4398 }); 4399 } 4400 4401 const productId = form.dataset.productId; 4402 4403 if (productId) { 4404 document.dispatchEvent(new CustomEvent('addToCart', { 4405 detail: { 4406 product: { 4407 id: productId 4408 } 4409 } 4410 })); 4411 } 4412 }); 4413 }); 4414} 4415 4416document.addEventListener('DOMContentLoaded', function() { 4417 const forms = document.querySelectorAll('.check-age-form'); 4418 forms.forEach((form) => { 4419 ageFormHandler(form); 4420 }); 4421});
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.