1// ============================================================ 2// LIVE.JS - Script principale tema LiveNetwork 3// Gestisce menu, overlay, ricerca, password, validazioni e fade menu scroll 4// ============================================================ 5 6document.addEventListener("DOMContentLoaded", function () { 7 try { 8 // ------------------------------------------------------------ 9 // MENU SINISTRO / DESTRO + OVERLAY 10 // ------------------------------------------------------------ 11 var toggle = document.getElementById("btnSidebarLeft"); 12 var sideMenu = document.getElementById("side-menu"); 13 var overlay = document.getElementById("menu-overlay"); 14 var closeBtns = document.querySelectorAll(".menu-close"); 15 var btnRight = document.getElementById("btnSidebarRight"); 16 var sideRight = document.getElementById("side-menu-right"); 17 18 if (toggle && sideMenu && overlay) { 19 toggle.addEventListener("click", function () { 20 sideMenu.classList.add("active"); 21 overlay.classList.add("active"); 22 }); 23 24 overlay.addEventListener("click", function () { 25 sideMenu.classList.remove("active"); 26 if (sideRight) sideRight.classList.remove("active"); 27 overlay.classList.remove("active"); 28 }); 29 } 30 31 if (btnRight && sideRight && overlay) { 32 btnRight.addEventListener("click", function () { 33 sideRight.classList.add("active"); 34 overlay.classList.add("active"); 35 }); 36 } 37 38 if (closeBtns && closeBtns.length) { 39 closeBtns.forEach(function (btn) { 40 btn.addEventListener("click", function () { 41 if (sideMenu) sideMenu.classList.remove("active"); 42 if (sideRight) sideRight.classList.remove("active"); 43 if (overlay) overlay.classList.remove("active"); 44 }); 45 }); 46 } 47 48 // ------------------------------------------------------------ 49 // RICERCA PRINCIPALE 50 // ------------------------------------------------------------ 51 var btnSearch = document.getElementById("btnSearch"); 52 var btnSearchInner = document.getElementById("btnSearchInner"); 53 var searchBox = document.querySelector(".live-main-search-box"); 54 var inputSearch = 55 searchBox && searchBox.querySelector(".livenetwork-live-search__input") 56 ? searchBox.querySelector(".livenetwork-live-search__input") 57 : null; 58 59 function toggleSearchBox() { 60 if (!searchBox) return; 61 62 searchBox.classList.toggle("active"); 63 64 if (searchBox.classList.contains("overflow-visible")) { 65 searchBox.classList.remove("overflow-visible"); 66 if (inputSearch) inputSearch.value = ""; 67 } else { 68 if (inputSearch) inputSearch.focus(); 69 } 70 } 71 72 if (btnSearch && searchBox) { 73 btnSearch.addEventListener("click", toggleSearchBox); 74 } 75 76 if (btnSearchInner && searchBox) { 77 btnSearchInner.addEventListener("click", toggleSearchBox); 78 79 // Classe quando l'animazione finisce (serve per overflow visibile) 80 searchBox.addEventListener("transitionend", function () { 81 if (searchBox.classList.contains("active")) { 82 searchBox.classList.add("overflow-visible"); 83 } 84 }); 85 } 86 87 // ------------------------------------------------------------ 88 // TOGGLE PASSWORD ðï¸ / ð 89 // ------------------------------------------------------------ 90 function bindPasswordToggles(root) { 91 root = root || document; 92 var buttons = root.querySelectorAll( 93 ".live-password-field .toggle-password" 94 ); 95 if (!buttons || !buttons.length) return; 96 97 buttons.forEach(function (btn) { 98 if (btn.dataset.bound === "1") return; 99 btn.dataset.bound = "1"; 100 101 btn.addEventListener("click", function (e) { 102 e.preventDefault(); 103 104 var wrapper = btn.closest(".live-password-field"); 105 if (!wrapper) return; 106 107 var input = wrapper.querySelector( 108 'input[type="password"], input[type="text"]' 109 ); 110 if (!input) return; 111 112 var isHidden = input.type === "password"; 113 input.type = isHidden ? "text" : "password"; 114 btn.classList.toggle("active", isHidden); 115 btn.setAttribute("aria-pressed", isHidden ? "true" : "false"); 116 }); 117 }); 118 } 119 120bindPasswordToggles(document); 121 122 // ------------------------------------------------------------ 123 // VALIDAZIONE LOGIN (HTML5) 124 // ------------------------------------------------------------ 125 function bindLoginValidation(root) { 126 root = root || document; 127 var form = root.querySelector(".live-login-form"); 128 if (!form) return; 129 130 form.addEventListener("submit", function (e) { 131 if (!form.checkValidity()) { 132 e.preventDefault(); 133 form.reportValidity(); 134 return; 135 } 136 }); 137 } 138 139 bindLoginValidation(document); 140 141 // ------------------------------------------------------------ 142 // VALIDAZIONE PASSWORD (feedback UI) 143 // ------------------------------------------------------------ 144 function bindPasswordStrength(root) { 145 root = root || document; 146 147 var passInput = root.querySelector( 148 'form.live-register-form input[name="user_pass"]' 149 ); 150 var hint = root.querySelector("form.live-register-form #password-strength"); 151 if (!passInput || !hint) return; 152 153 function meetsPolicy(v) { 154 return ( 155 v.length >= 8 && 156 /[A-Z]/.test(v) && 157 /\d/.test(v) && 158 /[^A-Za-z0-9]/.test(v) 159 ); 160 } 161 162 function isStrong(v) { 163 return ( 164 v.length >= 12 && 165 /[a-z]/.test(v) && 166 /[A-Z]/.test(v) && 167 /\d/.test(v) && 168 /[^A-Za-z0-9]/.test(v) 169 ); 170 } 171 172 function render() { 173 var val = (passInput.value || "").trim(); 174 175 if (!val.length) { 176 hint.textContent = 177 "La tua password deve contenere almeno 8 caratteri, una lettera maiuscola, un numero e un simbolo."; 178 hint.className = "password-hint"; 179 return; 180 } 181 182 if (!meetsPolicy(val)) { 183 hint.textContent = "Password troppo debole"; 184 hint.className = "password-hint weak"; 185 return; 186 } 187 188 if (isStrong(val)) { 189 hint.textContent = "Password forte"; 190 hint.className = "password-hint strong"; 191 } else { 192 hint.textContent = "Password accettabile"; 193 hint.className = "password-hint medium"; 194 } 195 } 196 197 passInput.addEventListener("input", render); 198 render(); 199 } 200 201 // ------------------------------------------------------------ 202 // VALIDAZIONE REGISTRAZIONE (HTML5 + policy password) 203 // ------------------------------------------------------------ 204 function bindRegisterValidation(root) { 205 root = root || document; 206 207 var form = root.querySelector("form.live-register-form"); 208 if (!form) return; 209 210 var passInput = form.querySelector('input[name="user_pass"]'); 211 var hint = form.querySelector("#password-strength"); 212 213 function meetsPolicy(v) { 214 return ( 215 v.length >= 8 && 216 /[A-Z]/.test(v) && 217 /\d/.test(v) && 218 /[^A-Za-z0-9]/.test(v) 219 ); 220 } 221 222 form.addEventListener("submit", function (e) { 223 if (!form.checkValidity()) { 224 e.preventDefault(); 225 form.reportValidity(); 226 return; 227 } 228 229 var val = ((passInput && passInput.value) || "").trim(); 230 if (!meetsPolicy(val)) { 231 e.preventDefault(); 232 if (hint) { 233 hint.textContent = 234 "La password non rispetta i requisiti di sicurezza."; 235 hint.className = "password-hint weak"; 236 } 237 if (passInput) passInput.focus(); 238 } 239 }); 240 } 241 242 bindPasswordStrength(document); 243 bindRegisterValidation(document); 244 245 // ------------------------------------------------------------ 246 // UPLOAD FOTO PROFILO DURANTE LA REGISTRAZIONE 247 // (FIX: niente return âglobaleâ se mancano progress/bar) 248 // ------------------------------------------------------------ 249 (function initRegisterAvatarUpload() { 250 var avatarWrapper = document.querySelector(".avatar-wrapper"); 251 var inputFile = document.getElementById("user_profile_upload"); 252 if (!avatarWrapper || !inputFile) return; 253 254 var avatarPreview = document.getElementById("avatar-preview"); 255 var progressBar = document.querySelector(".upload-progress"); 256 var bar = progressBar ? progressBar.querySelector(".bar") : null; 257 var removeBtn = document.getElementById("remove-avatar"); 258 var hiddenField = document.getElementById("user_profile_image"); 259 var registerBtn = document.querySelector( 260 'button[name="live_register_submit"]' 261 ); 262 263 if (!progressBar || !bar) { 264 console.warn( 265 "[live.js] upload progress missing: skip avatar upload init" 266 ); 267 return; // <-- esce SOLO da questa IIFE, non blocca il resto del file 268 } 269 270 avatarWrapper.addEventListener("click", function () { 271 inputFile.click(); 272 }); 273 274 inputFile.addEventListener("change", function () { 275 var file = inputFile.files && inputFile.files[0]; 276 if (!file) return; 277 278 var allowed = ["image/jpeg", "image/png", "image/webp"]; 279 if (allowed.indexOf(file.type) === -1) { 280 alert("Formato non supportato. Usa JPG, PNG o WEBP."); 281 return; 282 } 283 284 if (file.size > 3 * 1024 * 1024) { 285 alert("Lâimmagine non deve superare i 3 MB."); 286 return; 287 } 288 289 if (registerBtn) registerBtn.disabled = true; 290 progressBar.style.display = "block"; 291 progressBar.classList.remove("indeterminate"); 292 bar.style.width = "0%"; 293 294 var formData = new FormData(); 295 formData.append("action", "live_upload_avatar"); 296 formData.append("avatar", file); 297 298 var xhr = new XMLHttpRequest(); 299 xhr.open("POST", LiveNetworkData.ajaxurl, true); 300 301 // PROGRESS: massima al 90% durante l'upload 302 xhr.upload.addEventListener("progress", function (e) { 303 if (e.lengthComputable) { 304 var percent = (e.loaded / e.total) * 100; 305 var shown = Math.min(90, Math.max(5, percent)); 306 bar.style.width = shown + "%"; 307 } else { 308 progressBar.classList.add("indeterminate"); 309 } 310 }); 311 312 // Upload finito ma il server sta ancora elaborando 313 xhr.upload.addEventListener("load", function () { 314 progressBar.classList.remove("indeterminate"); 315 bar.style.width = "95%"; 316 }); 317 318 // Risposta ricevuta dal server â chiudi a 100% e nascondi 319 xhr.addEventListener("load", function () { 320 var ok = xhr.status === 200; 321 bar.style.width = "100%"; 322 323 setTimeout(function () { 324 if (ok) { 325 var res; 326 try { 327 res = JSON.parse(xhr.responseText); 328 } catch (err) { 329 console.error("Errore di parsing JSON:", err); 330 alert("Risposta non valida dal server."); 331 progressBar.style.display = "none"; 332 bar.style.width = "0%"; 333 if (registerBtn) registerBtn.disabled = false; 334 return; 335 } 336 337 if (res && res.success) { 338 if (avatarPreview) avatarPreview.src = res.data.url; 339 if (hiddenField) hiddenField.value = res.data.id; 340 if (removeBtn) removeBtn.classList.add("show"); 341 } else { 342 alert((res && res.data) || "Errore durante lâupload"); 343 } 344 } else { 345 alert("Errore di rete durante il caricamento"); 346 } 347 348 progressBar.style.display = "none"; 349 bar.style.width = "0%"; 350 if (registerBtn) registerBtn.disabled = false; 351 }, 250); 352 }); 353 354 xhr.addEventListener("error", function () { 355 progressBar.classList.remove("indeterminate"); 356 alert("Errore di rete durante il caricamento"); 357 progressBar.style.display = "none"; 358 bar.style.width = "0%"; 359 if (registerBtn) registerBtn.disabled = false; 360 }); 361 362 xhr.send(formData); 363 }); 364 365 if (removeBtn) { 366 removeBtn.addEventListener("click", function (e) { 367 e.preventDefault(); 368 e.stopPropagation(); 369 370 if (avatarPreview && avatarPreview.dataset.default) { 371 avatarPreview.src = avatarPreview.dataset.default; 372 } 373 if (hiddenField) hiddenField.value = ""; 374 removeBtn.classList.remove("show"); 375 }); 376 } 377 })(); 378 379 // ------------------------------------------------------------ 380 // A11Y (PageSpeed/Lighthouse): Complianz labels 381 // ------------------------------------------------------------ 382 var complianzLinks = document.querySelectorAll(
383 ".cmplz-documents a.cmplz-link" 384 ); 385 if (complianzLinks && complianzLinks.length) { 386 complianzLinks.forEach(function (a) { 387 var hasLabel = !!a.getAttribute("aria-label"); 388 var txt = (a.textContent || "").trim(); 389 390 var label = ""; 391 if (a.classList.contains("cookie-statement")) label = "Apri la Cookie Policy"; 392 else if (a.classList.contains("privacy-statement")) label = "Apri la Privacy Policy"; 393 else label = txt ? "Apri documento: " + txt : "Apri documento informativo"; 394 395 if (!hasLabel) a.setAttribute("aria-label", label); 396 397 if (!txt) { 398 a.insertAdjacentHTML( 399 "beforeend", 400 '<span class="screen-reader-text">' + label + "</span>" 401 ); 402 } 403 }); 404 } 405 406 // ------------------------------------------------------------ 407 // DRAG TO SCROLL (desktop) 408 // - trascina con mouse/pointer per scroll orizzontale 409 // - soglia per non ârompereâ i click sui link 410 // ------------------------------------------------------------ 411 412function enableHeaderMenuDrag(nav, scheduleUpdate) { 413 if (!nav) return; 414 415 // â drag solo su desktop (pointer fine + hover) -> niente interferenze su mobile 416 var canDragDesktop = window.matchMedia 417 ? window.matchMedia("(pointer: fine) and (hover: hover)").matches 418 : true; 419 420 if (!canDragDesktop) return; 421 422 nav.addEventListener("dragstart", function (e) { e.preventDefault(); }); 423 nav.querySelectorAll("a, img").forEach(function (el) { el.draggable = false; }); 424 425 var isDown = false, startX = 0, startScrollLeft = 0; 426 var moved = false, dragging = false, capturedId = null; 427 var TH = 6; 428 429 nav.addEventListener("pointerdown", function (e) { 430 // â solo mouse 431 if (e.pointerType !== "mouse") return; 432 if (e.button !== 0) return; 433 434 isDown = true; 435 moved = false; 436 dragging = false; 437 capturedId = null; 438 startX = e.clientX; 439 startScrollLeft = nav.scrollLeft; 440 }); 441 442 nav.addEventListener("pointermove", function (e) { 443 if (!isDown) return; 444 if (e.pointerType !== "mouse") return; 445 446 var dx = e.clientX - startX; 447 448 if (!dragging && Math.abs(dx) > TH) { 449 dragging = true; 450 moved = true; 451 nav.classList.add("is-dragging"); 452 if (nav.setPointerCapture && typeof e.pointerId === "number") { 453 try { nav.setPointerCapture(e.pointerId); capturedId = e.pointerId; } catch (_) {} 454 } 455 } 456 457 if (!dragging) return; 458 459 nav.scrollLeft = startScrollLeft - dx; 460 if (typeof scheduleUpdate === "function") scheduleUpdate(); 461 462 // â ok qui perché è mouse (non touch) 463 e.preventDefault(); 464 }, { passive: false }); 465 466 function end() { 467 if (!isDown) return; 468 isDown = false; 469 nav.classList.remove("is-dragging"); 470 dragging = false; 471 472 if (capturedId !== null && nav.releasePointerCapture) { 473 try { nav.releasePointerCapture(capturedId); } catch (_) {} 474 } 475 capturedId = null; 476 477 if (typeof scheduleUpdate === "function") scheduleUpdate(); 478 } 479 480 nav.addEventListener("pointerup", end); 481 nav.addEventListener("pointercancel", end); 482 483 // niente pointerleave: su alcuni browser può âchiudereâ mentre ti muovi fuori di 1px 484 // nav.addEventListener("pointerleave", end); 485 486 nav.addEventListener("click", function (e) { 487 if (!moved) return; 488 e.preventDefault(); 489 e.stopPropagation(); 490 moved = false; 491 }, true); 492} 493 494 495 496 497 // ------------------------------------------------------------ 498 // MENU HEADER SCROLL + FADE 499 // - se scrolla: menu a sinistra (via .is-scrollable) 500 // - fade: classi .has-left / .has-right sul wrapper .nav-fade 501 // - supporto a resize tardivi (skin adv) con ResizeObserver + recheck breve 502 // ------------------------------------------------------------ 503 504 505 506 function initHeaderMenuFade() { 507 var section = document.querySelector(".main-menu"); 508 if (!section) return; 509 510 var nav = section.querySelector(".live-main-menu-container"); 511 if (!nav) return; 512 513 // wrapper 514 var wrap = section.querySelector(".nav-fade"); 515 if (!wrap) { 516 wrap = document.createElement("div"); 517 wrap.className = "nav-fade nav-fade--menu"; 518 nav.parentNode.insertBefore(wrap, nav); 519 wrap.appendChild(nav); 520 } 521 522 var shell = document.querySelector(".lnv-shell"); 523 524 var rafId = null; 525 function schedule() { 526 if (rafId) return; 527 rafId = requestAnimationFrame(function () { 528 rafId = null; 529 update(); 530 }); 531 } 532
533 function update() { 534 var max = nav.scrollWidth - nav.clientWidth; 535 var x = nav.scrollLeft; 536 var scrollable = max > 1; 537 538 nav.classList.toggle("is-scrollable", scrollable); 539 wrap.classList.toggle("has-left", scrollable && x > 1); 540 wrap.classList.toggle("has-right", scrollable && x < max - 1); 541 } 542 543 // â ATTACCA DRAG QUI (con update fade mentre trascini) 544 enableHeaderMenuDrag(nav, schedule); 545 546 // prima valutazione 547 schedule(); 548 549 nav.addEventListener("scroll", schedule, { passive: true }); 550 window.addEventListener("resize", schedule); 551 window.addEventListener("load", schedule); 552 553 if ("ResizeObserver" in window) { 554 var ro = new ResizeObserver(function () { schedule(); }); 555 ro.observe(nav); 556 ro.observe(wrap); 557 if (shell) ro.observe(shell); 558 } 559 560 if (document.fonts && document.fonts.ready && typeof document.fonts.ready.then === "function") { 561 document.fonts.ready.then(schedule).catch(function () {}); 562 } 563 564 var tries = 0; 565 var t = setInterval(function () { 566 tries++; 567 schedule(); 568 if (tries >= 20) clearInterval(t); 569 }, 100); 570 } 571 572 573 initHeaderMenuFade(); 574 } catch (e) { 575 console.error("[live.js] fatal error:", e); 576 } 577});
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.