1function createDataLayerEvent(event) { 2 let eventData; 3 switch (event) { 4 case 'pageView': 5 eventData = { 6 'event': 'Page View', 7 'userID': window.userID, 8 'domainID': window.domainID, 9 'propertyID': window.propertyID, 10 }; 11 window.dataLayer.push(eventData); 12 break; 13 14 case 'login': 15 if ($.cookie('event_login')) return; 16 eventData = { 17 'event': 'User Login', 18 'userID': window.userID, 19 'domainID': window.domainID, 20 'propertyID': window.propertyID, 21 }; 22 window.dataLayer.push(eventData); 23 $.cookie('event_login', 'true', { expires: 1, path: '/' }); 24 break; 25 26 case 'transactionCompleted': 27 let pricePlanID = window.pricePlanID; 28 if ($.cookie('event_transactionCompleted_' + pricePlanID)) return; 29 30 const getParam = (param) => new URLSearchParams(window.location.search).get(param); 31 32 /* Retrieve the transaction */ 33 retrieveTransaction(window.userID, pricePlanID) 34 .then((transaction) => { 35 eventData = { 36 'event': 'Transaction Completed', 37 'userID': window.userID, 38 'domainID': window.domainID, 39 'propertyID': window.propertyID, 40 'currentAsset': getParam('asset'), 41 'pricePlanID': pricePlanID, 42 'currency': window.currency, 43 'transactionID': transaction.payload.transactionID, 44 'transactionType': window.transactionType, 45 'transactionValue': (transaction.payload.price / 100).toFixed(2), 46 'transactionAmountCharged': (transaction.payload.amountCharged / 100).toFixed(2), 47 'discountPercentage': transaction.payload.discountPercentage, 48 'discountCode': transaction.payload.discountName 49 }; 50 51 console.log('Data layer event sent!'); 52 console.log(eventData); 53 54 window.dataLayer.push(eventData); 55 $.cookie('event_transactionCompleted_' + pricePlanID, 'true', { expires: 1, path: '/' }); 56 }) 57 .catch((error) => { 58 console.error('Error retrieving transaction:', error); 59 }); 60 break; 61 62 case 'watchedAsset': 63 let assetID = window.assetID; 64 if ($.cookie('event_watchedAsset_' + assetID)) return; 65 eventData = { 66 'event': 'Watched asset', 67 'userID': window.userID, 68 'domainID': window.domainID, 69 'assetID': assetID, 70 'channelID': window.channelID, 71 'mediaID': window.mediaID, 72 'tags': window.tags 73 }; 74 window.dataLayer.push(eventData); 75 $.cookie('event_watchedAsset_' + assetID, 'true', { expires: 1, path: '/' }); 76 break; 77 } 78} 79 80function retrieveTransaction(userID, pricePlanID, attempt = 0) { 81 return new Promise((resolve, reject) => { 82 $.ajax({ 83 url: '/api/get-transaction', 84 type: 'GET', 85 data: { 86 userID: userID, 87 pricePlanID: pricePlanID 88 }, 89 success: function(response) { 90 if (response.success) { 91 resolve(response); 92 } else { 93 if (attempt < 15) { 94 console.log('No transaction found, retrying in 1.5 seconds'); 95 setTimeout(() => { 96 retrieveTransaction(userID, pricePlanID, attempt + 1).then(resolve).cat
96ch(reject); 97 }, 1500); 98 } else { 99 reject('Max retry attempts reached'); 100 } 101 } 102 }, 103 error: function(xhr, status, error) { 104 reject(error); 105 } 106 }); 107 }); 108} 109 110function checkSessionState() 111{ 112 113 fetch("/api/session-state").then((response) => { 114 115 response.json().then((parsedBody) => { 116 117 /* If we aren't signed in anymore - redirect. */ 118 if (parsedBody["isLoggedIn"] === false) 119 { 120 localStorage.setItem("notifyExpiration", "true"); 121 window.location.href = `/`; 122 } 123 124 }); 125 126 }); 127 128} 129 130function showSessionExpiredModal() 131{ 132 133 const headerMeta = document.querySelector("meta[name='notify_expiration_header']"); 134 const textMeta = document.querySelector("meta[name='notify_expiration_text']"); 135 136 console.log(headerMeta); 137 console.log(textMeta); 138 139 if (!headerMeta || !textMeta) 140 return; 141 142 $("#global-modal-title").text(headerMeta.content); 143 $('#global-modal-body').text(textMeta.content); 144 $('#global-modal-element').modal('show'); 145 $('#global-modal-footer button')[0].innerText = 'OK'; 146 147 /* Remove tags from DOM. */ 148 headerMeta.remove(); 149 textMeta.remove(); 150 151} 152 153function showLoginModalContent() { 154 155 /* Do we have loginPlatformChoice? - show it. */ 156 if ($("#loginPlatformChoice").length) 157 { 158 $("#loginPlatformChoice").show(); 159 $("#loginFormContainer").addClass("d-none"); 160 } 161 162 // Hide step 1 and show the login form 163 $("[class^=register-step]").fadeOut(0, function() { 164 $(".login-step-1").hide().removeClass("d-none").fadeIn(0); 165 }); 166 167 /* Show login bottom and hide registration bottom. */ 168 $(".bot-login").show(); 169 $(".bot-register").hide(); 170} 171 172function showRegistrationModalContent() { 173 174 // Hide step 1 and show the registration form 175 $("[class^=login-step]").fadeOut(0, function() { 176 $(".register-step-1").hide().removeClass("d-none").fadeIn(0); 177 }); 178 179 /* Show register bottom and hide login bottom. */ 180 $(".bot-login").hide(); 181 $(".bot-register").show(); 182 183} 184function showAuthenticationModal() { 185 const authModal = new bootstrap.Modal('#authModal', { 186 keyboard: false 187 }); 188 authModal.show(); 189} 190 191function backgroundPusher() { 192 setInterval(() => { 193 $('.background-pusher').css({ 194 "maxHeight": ($('.video-background').height() - $('header').height()) + "px" 195 }); 196 }, 100); 197} 198 199document.addEventListener('DOMContentLoaded', (() => { 200 201 // Data layer events 202 createDataLayerEvent('pageView') 203 204 // Login 205 var urlParams = new URLSearchParams(window.location.search); 206 if(urlParams.has('loggedIn')) { 207 createDataLayerEvent('login'); 208 } 209 210 // Transaction completed 211 if(window.location.href.indexOf('payment-complete') > -1 && urlParams.has('plan')) { 212 createDataLayerEvent('transactionCompleted'); 213 } 214 215 // Watched asset 216 if(window.location.href.indexOf('watch/~') > -1) { 217 createDataLayerEvent('watchedAsset'); 218 } 219 220 // Auth modal 221 const authModal = $("#authModal"); 222 if(authModal.length) { 223 224 // Validation strings 225 const validationStrings = { 226 'en': { 227 'required': 'This field is required', 228 'password_required': 'Please provide a password', 229 'email': 'Please enter a valid email address', 230 'email_taken': 'This email address has already been taken. <a id="loginButton" class="text-reset" href="#">Click here to log in</a>.', 231 'jumpTOS': 'Agree with the TOS to create your account.', 232 'min_length': 'The password must be at least 6 characters long.' 233 }, 234 'nl': { 235 'required': 'Dit veld is verplicht', 236 'password_required': 'Geef een wachtwoord op', 237 'email': 'Voer een geldig e-mailadres in', 238 'email_taken': 'Dit e-mailadres is al in gebruik. <a id="loginButton" class="text-reset" href="#">Klik hier om in te loggen</a>.', 239 'jumpTOS': 'Ga akkoord met de algemene voorwaarden om verder te gaan.',
240 'min_length': 'Het wachtwoord moet minimaal 6 tekens lang zijn.' 241 } 242 } 243 244 function escapeHtml(value) { 245 return String(value) 246 .replace(/&/g, '&') 247 .replace(/</g, '<') 248 .replace(/>/g, '>') 249 .replace(/"/g, '"') 250 .replace(/'/g, '''); 251 } 252 253 function setLoginReturnUrl() { 254 const currentUrl = window.location.pathname + window.location.search; 255 const urlWithoutReturnUrl = currentUrl.split('&returnUrl=')[0].split('?returnUrl=')[0]; 256 const loginForm = $(".login-step-1 form"); 257 if (!loginForm.length) { 258 return; 259 } 260 loginForm.find('input[name="returnUrl"]').remove(); 261 loginForm.append( 262 '<input type="hidden" name="returnUrl" value="' + encodeURIComponent(urlWithoutReturnUrl) + '">' 263 ); 264 } 265 266 authModal[0].addEventListener('show.bs.modal', function () { 267 setLoginReturnUrl(); 268 }); 269 270 function showCustomTermsStep(response) { 271 const terms = response.terms || []; 272 const redirectUrl = response.redirect_to || "/"; 273 const container = $("#customTermsLoginContainer"); 274 container.html(''); 275 276 terms.forEach(term => { 277 const termId = escapeHtml(term.id); 278 const termTitle = escapeHtml(term.title); 279 const termFile = escapeHtml(term.file); 280 container.append(` 281 <div class="form-check mb-2"> 282 <input class="form-check-input custom-term-login-check" type="checkbox" value="on" name="custom_terms[${termId}]" id="customTermLogin${termId}" required="required"> 283 <label class="form-check-label" for="customTermLogin${termId}"> 284 Ik ga akkoord met de <a target="_blank" href="${termFile}">${termTitle}</a><span class="text-danger m-1">*</span> 285 </label> 286 </div> 287 `); 288 }); 289 290 $("[class^=register-step], [class^=login-step]").fadeOut(0, function() { 291 $(".login-terms-step").hide().removeClass("d-none").fadeIn(0); 292 }); 293 294 $(".login-terms-step form") 295 .off('submit.customTerms') 296 .on('submit.customTerms', function(event) { 297 event.preventDefault(); 298 299 if ($(this).find('.custom-term-login-check:not(:checked)').length > 0) { 300 $(this).find('.alert-danger').remove(); 301 $(this).prepend('<div class="alert alert-danger">Ga akkoord met de voorwaarden om verder te gaan.</div>'); 302 return; 303 } 304 305 const submitButton = $("#customTermsAcceptSubmit"); 306 submitButton.attr("disabled", "disabled"); 307 308 $.ajax({ 309 type: "POST", 310 cache: false, 311 url: "/api/custom-terms/accept", 312 data: $(this).serialize(), 313 success: () => { 314 window.location = redirectUrl.indexOf('loggedIn=') === -1 315 ? redirectUrl + (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1' 316 : redirectUrl; 317 }, 318 error: (jqXHR) => { 319 const message = jqXHR.responseJSON?.message || 'Opslaan mislukt. Probeer het opnieuw.'; 320 $(".login-terms-step form").find('.alert-danger').remove(); 321 $(".login-terms-step form").prepend(`<div class="alert alert-danger">${message}</div>`); 322 }, 323 complete: () => submitButton.removeAttr("disabled") 324 }); 325 }); 326 } 327 328 // Step one logic 329 $("#registerStepOneSubmit").on('click', function () { 330 331 // Registration step 1 form validation 332 $(".register-step-1 form").validate({ 333 rules: { 334 email_address: { 335 required: true, 336 email: true 337 }, 338 password: { 339 required: true, 340 minlength: 6, 341 maxlength: 72, 342 }, 343 jumpTOS: { 344 required: true, 345 } 346 }, 347 messages: { 348 password: { 349 required: validationStrings[$('html').attr('lang')]['password_required'], 350 minlength: validationStrings[$('html').attr('lang')]['min_length'], 351 }, 352 jumpTOS: { 353 required: validationStrings[$('html').attr('lang')]['jumpTOS'] 354 }, 355 email_address: validationStrings[$('html').attr('lang')]['email'], 356 }, 357 submitHandler: function (form, event) { 358 event.preventDefault(); 359 360 console.log('Submitting step one'); 361 362 // Hide step 1 and show step 2 363 $(".register-step-1").fadeOut(500, function() { 364 $(".register-step-2").hide().removeClass("d-none").fadeIn(500); 365 }); 366 } 367 }); 368 369 }); 370 371 // Step two logic 372 $("#registerStepTwoSubmit").on('click', function () { 373 374 // Registration step 1 form validation 375 $(".register-step-2 form").validate({ 376 rules: { 377 firstname: { 378 required: true, 379 }, 380 infix: { 381 required: true, 382 }, 383 lastname: { 384 required: true, 385 } 386 }, 387 messages: { 388 required: validationStrings[$('html').attr('lang')]['required'], 389 }, 390 submitHandler: function (form, event) { 391 event.preventDefault(); 392 393 // Hide step 2 and show step 3 394 $(".register-step-2").fadeOut(500, function() { 395 $(".register-step-3").hide().removeClass("d-none").fadeIn(500); 396 }); 397 } 398 }); 399 400 }); 401 402 // Login button 403 $("body").on('click', '#loginButton', function () { 404 showLoginModalContent(); 405 }); 406 407 // Register button 408 $("body").on('click', '#registerButton', function () { 409 showRegistrationModalContent(); 410 }); 411 412 // Navbar buttons and toggles 413 $("#authModalLogin, #authModalRegistration, .toggleLogin").on('click', function (e) { 414 415 // Check if clicked button is log in button or sign up 416 if($(e.target).attr('id') === 'authModalLogin' || $(e.target).hasClass("toggleLogin")) { 417 showLoginModalContent(); 418 setLoginReturnUrl(); 419 } else { 420 showRegistrationModalContent(); 421 } 422 423 // Show the authentication modal 424 showAuthenticationModal(); 425 426 }); 427 428 // Login form validation 429 $(".login-step-1 form").validate({ 430 rules: { 431 email_address: { 432 required: true, 433 email: true 434 }, 435 password: { 436 required: true, 437 } 438 }, 439 messages: { 440 password: { 441 required: validationStrings['en']['required'] 442 }, 443 email_address: validationStrings[$('html').attr('lang')]['email'] 444 } 445 }); 446 447 /* Handle loginWithPlatformButton. */ 448 $("#loginWithPlatformButton").click(() => { 449 $("#loginFormContainer").removeClass("d-none"); 450 $("#loginPlatformChoice").hide(); 451 }); 452 453 /* Handle login form submissions. */ 454 $(".login-step-1 form").submit(e => { 455 456 /* Prevent default action. */ 457 e.preventDefault(); 458 459 /* Perform data serialization. */ 460 const formData = $(e.currentTarget).serialize(); 461 462 // Extract returnUrl from form data or use current URL 463 const formDataObj = new URLSearchParams(formData); 464 let returnUrl = formDataObj.get('returnUrl'); 465 466 // If no returnUrl in form, use current URL 467 if (!returnUrl) { 468 const currentUrl = window.location.pathname + window.location.search; 469 const urlWithoutReturnUrl = currentUrl.split('&returnUrl=')[0].split('?returnUrl=')[0]; 470 returnUrl = urlWithoutReturnUrl; 471 } 472 473 /* Retrieve the elements we want to disable. */ 474 const elements = $(".login-step-1 input, #logInStepOneSubmit"); 475 476 /* Disable all forms to indicate a network request is being done. */ 477 elements.attr("disabled", "disabled"); 478 479 /* Send a POST request to the login API with returnUrl as query parameter */ 480 $.ajax({ 481 type: "POST", 482 cache: false, 483 url: "/api/login" + (returnUrl ? "?returnUrl=" + encodeURIComponent(returnUrl) : ""), 484 data: formData, 485 dataType: 'json', // Expect JSON response 486 timeout: 30000, // 30 second timeout 487 488 /* Handle success - redirect based on returnUrl or home page. */ 489 success: (response) => { 490 console.log("Login success response:", response); 491 if (response && response.terms_required) { 492 showCustomTermsStep(response); 493 return; 494 } 495 496 /* Check if API returned a redirect URL (returnUrl) */ 497 let redirectUrl = "/"; 498 if (response && response.redirect_to) { 499 redirectUrl = response.redirect_to; 500 /* Add loggedIn parameter if not already present */ 501 if (redirectUrl.indexOf('loggedIn=') === -1) { 502 redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1'; 503 } 504 } else { 505 /* Check for returnUrl in current URL query parameters */
506 const urlParams = new URLSearchParams(window.location.search); 507 const returnUrl = urlParams.get('returnUrl'); 508 if (returnUrl) { 509 redirectUrl = decodeURIComponent(returnUrl); 510 if (redirectUrl.indexOf('loggedIn=') === -1) { 511 redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1'; 512 } 513 } 514 } 515 window.location = redirectUrl; 516 }, 517 518 /* Show errors to the user when they occur. */ 519 error: (jqXHR) => { 520 console.error("Login error:", jqXHR); 521 console.error("Status:", jqXHR.status); 522 console.error("Status Text:", jqXHR.statusText); 523 console.error("Response Text:", jqXHR.responseText); 524 console.error("Response JSON:", jqXHR.responseJSON); 525 526 /* Check if response might be valid JSON but status is not 200 (e.g., PHP warnings corrupted response) */ 527 if (jqXHR.responseText) { 528 try { 529 const parsed = JSON.parse(jqXHR.responseText); 530 console.log("Parsed response:", parsed); 531 532 /* If response has redirect_to or status 200, it's actually a success */ 533 if (parsed && (parsed.redirect_to || parsed.status === 200)) { 534 console.log("Login successful (detected in error handler), redirecting..."); 535 let redirectUrl = parsed.redirect_to || "/"; 536 if (redirectUrl.indexOf('loggedIn=') === -1) { 537 redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1'; 538 } 539 window.location = redirectUrl; 540 return; // Don't show error 541 } 542 } catch (e) { 543 console.error("Failed to parse response as JSON:", e); 544 } 545 } 546 547 /* Check if response is JSON and has errors */ 548 let errors = {}; 549 550 if (jqXHR.responseJSON && jqXHR.responseJSON.errors) { 551 errors = jqXHR.responseJSON.errors; 552 } else if (jqXHR.responseJSON && jqXHR.responseJSON.error_details) { 553 /* Handle error_details format */ 554 errors = { 555 "general": jqXHR.responseJSON.error_details || "An error occurred during login. Please try again." 556 }; 557 } else if (jqXHR.responseText) { 558 /* Try to parse response text as JSON */ 559 try { 560 const parsed = JSON.parse(jqXHR.responseText); 561 if (parsed.errors) { 562 errors = parsed.errors; 563 } else if (parsed.error_details) { 564 errors = { 565 "general": parsed.error_details || "An error occurred during login. Please try again." 566 }; 567 } 568 } catch (e) { 569 /* Not JSON, use generic error */ 570 errors = { 571 "general": "An error occurred during login. Please try again." 572 }; 573 } 574 } else { 575 /* No response, use generic error */ 576 errors = { 577 "general": "An error occurred during login. Please try again." 578 }; 579 } 580 581 /* Show server response to the user. */ 582 const $loginForm = $(".login-step-1 form"); 583 584 /* Remove any existing error messages */ 585 $loginForm.find('.alert-danger').remove(); 586 $loginForm.find('.is-invalid').removeClass('is-invalid'); 587 588 /* Try to use jQuery Validate if available and properly initialized */ 589 try { 590 if ($loginForm.length && $loginForm.data('validator')) { 591 /* Form has validator initialized, use it */ 592 const validator = $loginForm.validate(); 593 if (validator && typeof validator.showErrors === 'function') { 594 /* Ensure errors object has correct format for jQuery Validate */ 595 const formattedErrors = {}; 596 for (const [field, message] of Object.entries(errors)) { 597 /* Map field names to actual form field names */ 598 const fieldName = field === 'general' ? 'email_address' : field; 599 formattedErrors[fieldName] = message; 600 } 601 validator.showErrors(formattedErrors); 602 return; /* Successfully showed errors */ 603 } 604 } 605 } catch (e) { 606 console.error("Error showing validation errors:", e); 607 /* Fall through to manual error display */ 608 } 609 610 /* Fallback: show errors manually */ 611 console.error("Login errors:", errors); 612 let errorHtml = '<div class="alert alert-danger alert-dismissible fade show" role="alert"><ul class="mb-0">'; 613 for (const [field, message] of Object.entries(errors)) { 614 errorHtml += `<li>${message}</li>`; 615 } 616 errorHtml += '</ul><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>'; 617 618 if ($loginForm.length) { 619 $loginForm.prepend(errorHtml); 620 } else { 621 $(".login-step-1").prepend(errorHtml); 622 } 623 624 }, 625 626 /* Enable the fields again as the network request has finished pending. */ 627 complete: () => elements.removeAttr("disabled") 628 629 }); 630 631 }); 632 633 /* Handle registration form submissions. */ 634 $("#registerStepThreeSubmit, #registerStepThreeFinalSubmit").click(e => { 635 636 /* Disable the button we have just pressed - to indicate we are performing a network request. */ 637 $("#registerStepThreeSubmit, #registerStepThreeFinalSubmit, #loginButton").attr("disabled", "disabled"); 638 639 /* Disable the user's ability to click on login - we are processing. */ 640 $("#loginButton").hide(); 641 642 /* Retrieve data from all the forms. */ 643 const step1FormData = $(".register-step-1 form") 644 .serializeArray() 645 .reduce((obj, { name, value }) => (obj[name] = value, obj), {}); 646 647 const step2FormData = $(".register-step-2 form") 648 .serializeArray() 649 .reduce((obj, { name, value }) => { 650 if (obj[name]) { 651 if (!Array.isArray(obj[name])) { 652 obj[name] = [obj[name]]; 653 } 654 obj[name].push(value); 655 } else { 656 obj[name] = value; 657 } 658 return obj; 659 }, {}); 660 661 const step3FormData = $(".register-step-3 form") 662 .serializeArray() 663 .reduce((obj, { name, value }) => (obj[name] = value, obj), {}); 664 665 /* Combine all the data from the forms. */ 666 const combinedFormData = { 667 ...step1FormData, 668 ...step2FormData, 669 ...step3FormData 670 }; 671 672 /* Perform the registration request. */ 673 $.ajax({ 674 type: "POST", 675 cache: false, 676 url: "/api/register", 677 data: combinedFormData, 678 679 /* Handle success states. */ 680 success: (response) => { 681 682 /* Check if API returned a redirect URL (e.g., return_to) */ 683 let redirectUrl = "/first-logon-survey"; 684 if (response && response.redirect_to) { 685 redirectUrl = response.redirect_to; 686 /* Add loggedIn parameter if not already present */ 687 if (redirectUrl.indexOf('loggedIn=') === -1) { 688 redirectUrl += (redirectUrl.indexOf('?') === -1 ? '?' : '&') + 'loggedIn=1'; 689 } 690 } 691 692 /* Redirect to the appropriate page */ 693 window.location = redirectUrl; 694 695 }, 696 697 /* Handle form errors. */ 698 error: (jqXHR) => { 699 console.error("Registration error:", jqXHR); 700 console.error("Status:", jqXHR.status); 701 console.error("Status Text:", jqXHR.statusText); 702 console.error("Response Text:", jqXHR.responseText); 703 console.error("Response JSON:", jqXHR.responseJSON); 704 705 /* Parse errors from response */ 706 let errors = {}; 707 708 if (jqXHR.responseJSON && jqXHR.responseJSON.errors) { 709 errors = jqXHR.responseJSON.errors; 710 } else if (jqXHR.responseJSON && jqXHR.responseJSON.error) { 711 // Handle single error message 712 errors = { 713 "general": jqXHR.responseJSON.error 714 }; 715 } else if (jqXHR.responseText) { 716 try { 717 const parsed = JSON.parse(jqXHR.responseText); 718 if (parsed.errors) { 719 errors = parsed.errors; 720 } else if (parsed.error) { 721 errors = { 722 "general": parsed.error 723 }; 724 } else if (parsed.message) { 725 errors = { 726 "general": parsed.message 727 }; 728 } 729 } catch (e) { 730 console.error("Failed to parse error response:", e); 731 /* Not JSON, use generic error */ 732 errors = { 733 "general": "An error occurred during registration. Please try again." 734 }; 735 } 736 } else { 737 // No response text, check status code 738 if (jqXHR.status === 0) { 739 errors = { 740 "general": "Network error. Please check your internet connection." 741 }; 742 } else if (jqXHR.status === 500) { 743 errors = { 744 "general": "Server error. Please try again later." 745 }; 746 } else { 747 errors = { 748 "general": "An error occurred during registration. Please try again." 749 }; 750 } 751 } 752 753 /* Remove existing error messages */ 754 $(".register-step-2 form").find('.alert-danger').remove(); 755 $(".register-step-2 form").find('.is-invalid').removeClass('is-invalid'); 756 757 /* Try to use jQuery Validate if available */ 758 try { 759 const $registerForm = $(".register-step-2 form"); 760 if ($registerForm.length && $registerForm.data('validator')) { 761 const validator = $registerForm.validate(); 762 if (validator && typeof validator.showErrors === 'function') { 763 validator.showErrors(errors); 764 return; /* Successfully showed errors */ 765 } 766 } 767 } catch (e) { 768 console.error("Error showing validation errors:", e); 769 /* Fall through to manual error display */ 770 } 771 772 /* Fallback: show errors manually */ 773 let errorHtml = '<div class="alert alert-danger alert-dismissible fade show" role="alert"><ul class="mb-0">'; 774 for (const [field, message] of Object.entries(errors)) { 775 errorHtml += `<li>${message}</li>`; 776 } 777 errorHtml += '</ul><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>'; 778 $(".register-step-2 form").prepend(errorHtml); 779 780 }, 781 782 /* Handle the finishing of the network request by enabling inputs again. */ 783 complete: () => { 784 785 /* Enable buttons again. */ 786 $("#registerStepThreeSubmit, #registerStepThreeFinalSubmit, #loginButton").removeAttr("disabled"); 787 788 /* Show the login button again. */ 789 $("#loginButton").show(); 790 791 } 792 }); 793 794 }); 795 796 /** 797 * Checks email availability. 798 */ 799 const checkEmailAvailability = (emailAddress, error_text) => { 800 801 /* Remove the emailTakenError element and enable the button again. */ 802 $(".emailTakenError").remove(); 803 $("#registerStepOneSubmit").removeAttr("disabled"); 804 805 /** 806 * Checks whether the emailAddress value is a val]id email address to ensure we only check valid email addresses. 807 * @param emailAddress 808 * @returns {boolean} 809 */ 810 const isValidEmail = (emailAddress) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailAddress); 811 812 /* If the emailAddress variable is not a valid email address yet, we do not bother to continue. */ 813 if (!isValidEmail(emailAddress)) 814 { 815 return; 816 } 817 818 /* Perform a GET request to the email availability endpoint. */ 819 $.get(`/api/email-availability?email_address=${emailAddress}`, data => { 820 821 /* If the email is not available - show an error message on the email address input field. */ 822 if (!data.available) 823 { 824 $("#emailInput").after(`<span class="text-danger emailTakenError small">${error_text}</span>`); 825 826 /* Disable the submission button - until the user solves this error. */ 827 $("#registerStepOneSubmit").attr("disabled", "disabled"); 828 } 829 830 }); 831 832 } 833 834 /* Variable to store the last entered email address. */ 835 let lastCheckedEmailAddress = ""; 836 837 /* Listen to input events on the email address field. */ 838 $(".register-step-1 input[name='email_address']").on("keydown blur", () => { 839 840 /* Retrieve the current value. */ 841 const emailAddress = $(".register-step-1 input[name='email_address']").val(); 842 843 /* If the la
843stCheckedEmailAddress is equal to the current email address - do not continue. */ 844 if (emailAddress === lastCheckedEmailAddress) 845 { 846 return; 847 } 848 849 /* Check lastCheckedEmailAddress here. */ 850 lastCheckedEmailAddress = emailAddress; 851 852 /* Determine the error validation string */ 853 var validationString = validationStrings[$('html').attr('lang')]['email_taken']; 854 855 /* Check email address availability. */ 856 checkEmailAvailability(emailAddress, validationString); 857 858 }); 859 860 } 861 862 $('.glide').not('[data-collection-carousel-glide]').each(function() { 863 864 const slides = $(this).find('li.glide__slide').length + $('.glide__slides div').length; 865 var autoplaySetting = $(this).parents('.video-content').length && slides > 2 ? 2000 : false; 866 var amountOfSlidesVisible = $(this).data('amount-of-slides-visible'); 867 868 // Fallback for empty amounts 869 if(!amountOfSlidesVisible) { 870 amountOfSlidesVisible = 4; 871 } 872 873 const config = { 874 875 startAt: 0, 876 perView: amountOfSlidesVisible, 877 autoplay: autoplaySetting, 878 type: 'carousel', 879 controls: true, 880 881 breakpoints: { 882 883 600: { 884 perView: 1.25, 885 autoplay: slides > 1 ? 5000 : false 886 }, 887 888 1200: { 889 perView: 2.25, 890 autoplay: autoplaySetting, 891 } 892 893 } 894 895 }; 896 897 new Glide(this, config).mount() 898 899 }); 900 901 backgroundPusher(); 902 903 // Preview video only on desktop/laptop (not loaded on mobile/tablet for speed; first tap goes to card) 904 var isDesktop = window.matchMedia('(min-width: 992px)').matches && !('ontouchstart' in window); 905 906 $('.on-demand-video').each(function() { 907 var videoElement = $(this).find('video'); 908 var videoUrl = videoElement.attr('data-src'); 909 if (!videoUrl) return; 910 911 if (!isDesktop) return; 912 913 $.ajax({ 914 url: videoUrl, 915 success: function() { 916 videoElement.attr('src', videoUrl); 917 videoElement.closest('.on-demand-video').on('mouseover', function() { 918 $(this).find('video').removeClass('d-none').trigger('play'); 919 $(this).find('img').addClass('d-none'); 920 }).on('mouseleave', function() { 921 $(this).find('video').addClass('d-none').trigger('pause'); 922 $(this).find('img').removeClass('d-none'); 923 }); 924 }, 925 fail: function() { 926 console.error('Test!'); 927 } 928 }); 929 }); 930 931 $('[data-bs-target="#searchBarModal"]').on('click', function(e) { 932 $("input[name=query]").focus(); 933 }); 934 935 // Copy link 936 $(".copy-link").click(function(){ 937 // Create a temporary input element to hold the URL 938 var tempInput = $("<input>"); 939 $("body").append(tempInput); 940 tempInput.val(window.location.href).select(); 941 942 // Copy the URL to the clipboard 943 document.execCommand("copy"); 944 945 // Remove the temporary input element 946 tempInput.remove(); 947 948 // Temporarily change the icon to let the user know the link has been copied 949 var button = $(this); 950 button.html('<i class="fas fa-check"></i>'); 951 setTimeout(function () { 952 button.html('<i class="fas fa-link"></i>'); 953 }, 1000) 954 }); 955 956 // Enable tooltips 957 const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') 958 const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) 959 960 // Tag overview: hide "load more" button when there is nothing more (data-has-more="0") 961 $('.js-tags-overview-section[data-has-more="0"] .js-tags-overview-load-more').closest('.text-center').hide(); 962 963 // Tag overview: "Next video's" load more 964 $(document).on('click', '.js-tags-overview-load-more', function() { 965 var btn = $(this); 966 var section = btn.closest('.js-tags-overview-section'); 967 if (!section.length) return; 968 var tag = section.data('tag'); 969 var offset = parseInt(section.data('offset'), 10) || 24; 970 var total = parseInt(section.data('total'), 10) || 0; 971 var limit = 24; 972 var sort = section.data('sort') || 'created_date:asc'; 973 var assetType = section.data('asset-type') || 'all'; 974 var aspectRatio = section.data('aspect-ratio') || '16/9'; 975 var carousel = section.data('carousel') === 1 || section.data('carousel') === '1'; 976 var slidesContainer = section.find('.js-tags-overview-slides'); 977 if (!slidesContainer.length || !tag) return; 978 btn.prop('disabled', true); 979 var url = '/api/tag-overview-more?tag=' + encodeURIComponent(tag) + '&offset=' + offset + '&limit=' + limit + '&sort=' + encodeURIComponent(sort) + '&asset_type_filter=' + encodeURIComponent(assetType) + '&aspect_ratio=' + encodeURIComponent(aspectRatio) + '&carousel=' + (carousel ? '1' : '0'); 980 $.get(url) 981 .done(function(html, textStatus, jqXHR) { 982 slidesContainer.append(html); 983 var count = parseInt(jqXHR && jqXHR.getResponseHeader('X-Tag-Overview-Count'), 10); 984 if (isNaN(count)) count = 0; 985 var newOffset = offset + count; 986 section.data('offset', newOffset); 987 /* Hide when no more items, or when we have a total and reached it */ 988 if (count < limit || (total > 0 && newOffset >= total)) { 989 section.data('has-more', '0'); 990 btn.closest('.text-center').hide(); 991 } 992 if (carousel && typeof Glide !== 'undefined') { 993 var glideEl = section.find('.glide')[0]; 994 if (glideEl && glideEl.__glide && typeof glideEl.__glide.update === 'function') {
995 glideEl.__glide.update(); 996 } 997 } 998 }) 999 .fail(function() { 1000 btn.prop('disabled', false); 1001 }) 1002 .always(function() { 1003 btn.prop('disabled', false); 1004 }); 1005 }); 1006 1007})); 1008 1009$(document).ready(function() { 1010 1011 /* If we have to show the session expiration modal - do so now. */ 1012 if (window.notifySessionExpiration || localStorage.getItem("notifyExpiration")) 1013 { 1014 1015 /* Show expiration modal. */ 1016 showSessionExpiredModal(); 1017 1018 /* If we have the notifyExpiration query string - remove it now. */ 1019 if (localStorage.getItem("notifyExpiration")) 1020 { 1021 localStorage.removeItem("notifyExpiration"); 1022 } 1023 1024 return; 1025 } 1026 1027 if (window.loggedIn) 1028 { 1029 const SESSION_STATE_INTERVAL_MS = 30000; 1030 let sessionStateTimer = null; 1031 1032 function startSessionStatePolling() { 1033 if (sessionStateTimer) { 1034 return; 1035 } 1036 checkSessionState(); 1037 sessionStateTimer = setInterval(checkSessionState, SESSION_STATE_INTERVAL_MS); 1038 } 1039 1040 function stopSessionStatePolling() { 1041 if (!sessionStateTimer) { 1042 return; 1043 } 1044 clearInterval(sessionStateTimer); 1045 sessionStateTimer = null; 1046 } 1047 1048 startSessionStatePolling(); 1049 1050 document.addEventListener('visibilitychange', function () { 1051 if (document.hidden) { 1052 stopSessionStatePolling(); 1053 } else { 1054 startSessionStatePolling(); 1055 } 1056 }); 1057 } 1058 1059});
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.