1document.addEventListener("DOMContentLoaded", function () { 2 //// FIRST LOAD CAROUSEL IMAGE 3 const firstItem = document.querySelector(".owl-carousel .item"); 4 if (firstItem) { 5 const smallImg = firstItem.querySelector(".sm\\:hidden"); 6 const largeImg = firstItem.querySelector(".max-sm\\:hidden"); 7 if (window.innerWidth < 640 && smallImg) { 8 smallImg.setAttribute("fetchpriority", "high"); 9 } else if (largeImg) { 10 largeImg.setAttribute("fetchpriority", "high"); 11 } 12 } 13 //// LEFT MENU 14 const html = document.documentElement; 15 const menuOpen = document.getElementById("menu_open"); 16 const menuClose = document.getElementById("menu_close"); 17 const menu = document.getElementById("left_menu"); 18 19 // Menu toggle functionality 20 if (menuOpen && menuClose && menu) { 21 menuOpen.addEventListener("click", () => { 22 html.classList.add("overflow-hidden"); 23 menu.classList.remove("hidden"); 24 }); 25 26 menuClose.addEventListener("click", () => { 27 html.classList.remove("overflow-hidden"); 28 menu.classList.add("hidden"); 29 }); 30 } 31 32 // Set theme directory for JavaScript (will be localized by WordPress) 33 if (typeof themeData !== "undefined") { 34 window.themeDirectory = themeData.themeDirectory; 35 } 36 37 // Initialize favorites if the manager exists 38 if ( 39 typeof window.favoritesManager === "undefined" && 40 typeof FavoritesManager !== "undefined" 41 ) { 42 window.favoritesManager = new FavoritesManager(); 43 } 44 45 //// TOP MENU 46 const searchOpen = document.getElementById("search_open"); 47 const searchClose = document.getElementById("search_close"); 48 const buttonWrapper = document.getElementById("button_wrapper"); 49 const searchWrapper = document.getElementById("search_wrapper"); 50 51 // Add null checks before adding event listeners 52 if (searchOpen && searchClose && buttonWrapper && searchWrapper) { 53 searchOpen.addEventListener("click", function () { 54 searchWrapper.classList.remove("hidden"); 55 buttonWrapper.classList.add("hidden"); 56 }); 57 58 searchClose.addEventListener("click", function () { 59 searchWrapper.classList.add("hidden"); 60 buttonWrapper.classList.remove("hidden"); 61 }); 62 } 63 64 ///// TEMPLATE_1 65 function initAccordion(containerSelector) { 66 const container = document.querySelector(containerSelector); 67 if (!container) return; 68 69 const headers = container.querySelectorAll(".accordion-header"); 70 71 headers.forEach((header) => { 72 const targetId = header.getAttribute("data-accordion-target"); 73 const panel = targetId 74 ? document.getElementById(targetId.replace("#", "")) 75 : null; 76 77 const togglePanel = (forceState) => { 78 const expanded = header.getAttribute("aria-expanded") === "true"; 79 const willOpen = 80 typeof forceState === "boolean" ? forceState : !expanded; 81 82 header.setAttribute("aria-expanded", willOpen); 83 if (panel) { 84 panel.classList.toggle("h-0", !willOpen); 85 panel.classList.toggle("h-[auto]", willOpen); 86 panel.classList.toggle("opacity-0", !willOpen); 87 panel.classList.toggle("opacity-100", willOpen); 88 } 89 90 const icon = header.querySelector(".accordion-icon"); 91 console.log(container); 92 if (icon) 93 if (willOpen) { 94 //- icon.classList.add('rotate-180'); 95 icon.classList.remove("-rotate-90"); 96 } else { 97 //- icon.classList.remove('rotate-180') 98 icon.classList.add("-rotate-90"); 99 } 100 }; 101 102 // Click event 103 header.addEventListener("click", () => togglePanel()); 104 105 // Keyboard accessibility 106 header.addEventListener("keydown", (e) => { 107 switch (e.key) { 108 case "Enter": 109 case " ": 110 e.preventDefault(); 111 togglePanel(); 112 break; 113 case "ArrowDown": { 114 e.preventDefault(); 115 const next = 116 header.parentElement.nextElementSibling?.querySelector( 117 ".accordion-header" 118 ); 119 if (next) next.focus(); 120 break; 121 } 122 case "ArrowUp": { 123 e.preventDefault(); 124 const prev = 125 header.parentElement.previousElementSibling?.querySelector( 126 ".accordion-header" 127 ); 128 if (prev) prev.focus(); 129 break; 130 } 131 } 132 }); 133 }); 134 } 135 136 function initSidebar(sidebarSelector) { 137 const sidebar = document.querySelector(sidebarSelector); 138 if (!sidebar) return; 139 140 sidebar.addEventListener("click", (e) => {
141 const navItem = e.target.closest(".nav-item"); 142 if (!navItem) return; 143 144 // Clear previous active state 145 const current = sidebar.querySelector("#current-link"); 146 if (current) { 147 current.removeAttribute("id"); 148 current.setAttribute("aria-pressed", "false"); 149 } 150 151 // Set new active item 152 navItem.id = "current-link"; 153 navItem.setAttribute("aria-pressed", "true"); 154 155 const link = navItem.querySelector("a"); 156 if ( 157 link && 158 link.getAttribute("href") && 159 link.getAttribute("href") !== "#" 160 ) { 161 // Uncomment for real navigation 162 // window.location.href = link.getAttribute('href'); 163 } 164 }); 165 166 // Keyboard accessibility 167 sidebar.querySelectorAll(".nav-item").forEach((item) => { 168 item.addEventListener("keydown", (e) => { 169 if (e.key === "Enter" || e.key === " ") { 170 e.preventDefault(); 171 item.click(); 172 } 173 }); 174 }); 175 } 176 177 initAccordion("#policy-accordion"); 178 initSidebar("#page-side-bar"); 179 180 //// sticky top bar on large screen 181 182 function throttle(func, delay) { 183 let lastExecution = 0; 184 let timeoutId; 185 return (...args) => { 186 const now = Date.now(); 187 const remaining = delay - (now - lastExecution); 188 189 clearTimeout(timeoutId); 190 if (remaining <= 0) { 191 lastExecution = now; 192 func(...args); 193 } else { 194 timeoutId = setTimeout(() => { 195 lastExecution = Date.now(); 196 func(...args); 197 }, remaining); 198 } 199 }; 200 } 201 202 const stickyBlock = document.querySelector("#search_wrapper"); 203 204 const handleScroll = () => { 205 if (window.scrollY > 32) { 206 stickyBlock.style.paddingBlock = ".5rem"; 207 } else { 208 stickyBlock.style.removeProperty("padding-block"); 209 } 210 }; 211 212 if (window.innerWidth >= 1024) 213 window.addEventListener("scroll", handleScroll); 214 window.addEventListener("resize", () => { 215 if (window.innerWidth >= 1024) { 216 window.addEventListener("scroll", handleScroll); 217 } else { 218 window.removeEventListener("scroll", handleScroll); 219 } 220 }); 221 222 const stikyLeftBannerContainer = document.querySelector( 223 "#left_menu > div > div:last-child" 224 ); 225 window.addEventListener("scroll", () => { 226 const rect = stikyLeftBannerContainer.getBoundingClientRect(); 227 stikyLeftBannerContainer.classList.toggle( 228 "rounded-tr-[25px]", 229 rect.top > 0 230 ); 231 }); 232 233 function initRedirectCountdown() { 234 const countdownEl = document.getElementById("countdown"); 235 const secondsEl = document.getElementById("seconds"); 236 237 // Only run if countdown elements exist (redirect page) 238 if (!countdownEl || !secondsEl) return; 239 240 // Get countdown value and target URL from data attributes 241 const countdownContainer = countdownEl.closest(".max-w-2xl"); 242 if (!countdownContainer) return; 243 244 const initialSeconds = parseInt(countdownEl.textContent) || 5; 245 const targetUrl = countdownContainer.dataset.redirectUrl || "/"; 246 const queryParams = countdownContainer.dataset.queryParams || ""; 247 248 let seconds = initialSeconds; 249 250 // Update countdown display 251 function updateCountdown() { 252 countdownEl.textContent = seconds; 253 secondsEl.textContent = seconds; 254 } 255 256 // Build final URL with query params 257 function buildFinalUrl() { 258 if (!targetUrl || targetUrl === "#") { 259 return "/"; 260 } 261 262 // If no query params, return original URL 263 if (!queryParams) { 264 return targetUrl; 265 } 266 267 // Remove leading ? from queryParams if exists 268 const params = queryParams.replace(/^\?/, ''); 269 270 // Determine separator: ? if URL doesn't have query string, & if it does 271 const separator = targetUrl.indexOf('?') !== -1 ? '&' : '?'; 272 273 // Build final URL 274 return targetUrl + separator + params; 275 } 276 277 // Redirect to target URL with query params 278 window.redirectNow = function () { 279 const finalUrl = buildFinalUrl(); 280 window.location.href = finalUrl; 281 }; 282 283 // Start countdown timer 284 const timer = setInterval(function () { 285 seconds--; 286 updateCountdown(); 287 288 if (seconds <= 0) { 289 clearInterval(timer); 290 window.redirectNow(); 291 } 292 }, 1000); 293 294 // Cleanup on page unload 295 window.addEventListener("beforeunload", function () { 296 clearInterval(timer); 297 }); 298 } 299 300 // Initialize redirect countdown if on redirect page 301 initRedirectCountdown(); 302 303 //// AUTHENTICATION PAGE VIEW SWITCHING 304 /** 305 * Initialize login page view switching 306 * Handles toggle between social login and email login forms 307 */ 308 function initLoginViewSwitching() { 309 const initialView = document.getElementById("initial-view"); 310 const emailView = document.getElementById("email-view"); 311 const showEmailBtn = document.getElementById("show-email-login-btn"); 312 const backBtn = document.getElementById("back-to-initial-btn"); 313 314 // Only run if login elements exist 315 if (!initialView || !emailView) return; 316 317 if (showEmailBtn) { 318 showEmailBtn.addEventListener("click", () => { 319 initialView.classList.add("hidden"); 320 emailView.classList.remove("hidden"); 321 }); 322 } 323 324 if (backBtn) { 325 backBtn.addEventListener("click", () => { 326 emailView.classList.add("hidden"); 327 initialView.classList.remove("hidden"); 328 }); 329 } 330 } 331 332 // Initialize login view switching if on login page 333 initLoginViewSwitching(); 334 335 const backToTop = document.getElementById("backToTop"); 336 if (backToTop) { 337 window.addEventListener("scroll", () => { 338 const scrollPosition = window.scrollY;
339 const viewportHeight = window.innerHeight; 340 const nearBottom = 341 scrollPosition + viewportHeight >= 342 document.body.offsetHeight - viewportHeight / 2; 343 344 if (scrollPosition > viewportHeight / 2) { 345 backToTop.classList.remove("hidden"); 346 } else { 347 backToTop.classList.add("hidden"); 348 } 349 350 if (nearBottom) { 351 backToTop.classList.add("jump"); 352 } else { 353 backToTop.classList.remove("jump"); 354 } 355 }); 356 357 backToTop.addEventListener("click", () => { 358 window.scrollTo({ top: 0, behavior: "smooth" }); 359 }); 360 } 361 362 // 6. Change user avatar (profile page) 363 (function changeAvatarInit() { 364 const changeBtn = document.getElementById('profile-avatar-change-btn'); 365 const fileInput = document.getElementById('profile-avatar-input'); 366 const avatarImg = document.getElementById('profile-avatar-img'); 367 const userIdInput = document.getElementById('profile-user-id'); 368 if (!changeBtn || !fileInput || !avatarImg || !userIdInput) return; 369 changeBtn.addEventListener('click', function (e) { 370 e.preventDefault(); 371 fileInput.click(); 372 }); 373 fileInput.addEventListener('change', function () { 374 const file = fileInput.files[0]; 375 if (!file) return; 376 if (!file.type.match(/^image\/(jpeg|png|jpg)$/)) { 377 alert('Chá» há» trợ ảnh JPG hoặc PNG!'); 378 return; 379 } 380 const reader = new FileReader(); 381 reader.onload = function (evt) { 382 const base64 = evt.target.result; 383 fetch('/wp-json/api/v1/user/avatar', { 384 method: 'POST', 385 headers: { 386 'Content-Type': 'application/json', 387 'X-WP-Nonce': window.wpApiSettings.nonce 388 }, 389 credentials: 'include', 390 body: JSON.stringify({ avatar: base64, user_id: userIdInput.value }) 391 }) 392 .then(res => res.json()) 393 .then(data => { 394 if (data.success && data.avatar_url) { 395 avatarImg.src = data.avatar_url + '?t=' + Date.now(); 396 } else { 397 alert(data.message || 'Lá»i khi cáºp nháºt ảnh Äại diá»n!'); 398 } 399 }) 400 .catch(() => alert('Lá»i khi cáºp nháºt ảnh Äại diá»n!')); 401 }; 402 reader.readAsDataURL(file); 403 }); 404 })(); 405 406 function initBlogPagination() { 407 const postGrids = document.querySelectorAll('[id^="post-grid-"]'); 408 409 if (postGrids.length === 0) return; 410 411 postGrids.forEach(container => { 412 const gridId = container.id; 413 const categoryId = container.dataset.categoryId; 414 const baseUrl = container.dataset.baseUrl || ''; 415 const pageContext = container.dataset.pageContext || 'blog'; 416 const enableUrlPagination = container.dataset.urlPagination === 'true'; 417 const postsPerPage = parseInt(container.dataset.postsPerPage) || 6; 418 const postsContainer = container.querySelector('.post-grid-container'); 419 const loadingOverlay = container.querySelector('.post-grid-loading'); 420 const prevBtn = container.querySelector('.pagination-prev'); 421 const nextBtn = container.querySelector('.pagination-next'); 422 const pageButtons = container.querySelectorAll('.pagination-page'); 423 424 if (!postsContainer) return; 425 426 let currentPage = parseInt(container.dataset.currentPage) || 1; 427 let totalPages = parseInt(container.dataset.totalPages) || 1; 428 let isLoading = false; 429 430 async function loadPosts(page, updateUrl = true) { 431 if (isLoading || page < 1 || page > totalPages) return; 432 433 isLoading = true; 434 if (loadingOverlay) loadingOverlay.classList.remove('hidden'); 435 436 try { 437 const formData = new FormData(); 438 formData.append('action', 'load_blog_posts'); 439 formData.append('category_id', categoryId || '0'); 440 formData.append('page', page); 441 formData.append('per_page', postsPerPage); 442 443 const nonce = container.dataset.nonce || 444 document.querySelector('meta[name="blog-pagination-nonce"]')?.content; 445 446 if (nonce) { 447 formData.append('nonce', nonce); 448 } 449 450 const ajaxUrl = window.location.origin + '/wp-admin/admin-ajax.php'; 451 452 const response = await fetch(ajaxUrl, { 453 method: 'POST', 454 body: formData, 455 credentials: 'same-origin' 456 }); 457 458 if (!response.ok) { 459 throw new Error(`HTTP error! status: ${response.status}`); 460 } 461 462 const data = await response.json(); 463 464 if (data.success && data.data.html) { 465 // Update posts 466 postsContainer.innerHTML = data.data.html; 467 468 // Update pagination state 469 currentPage = page; 470 container.dataset.currentPage = page; 471 472 // Update total pages if returned (dynamic categories may change) 473 if (data.data.total_pages) { 474 totalPages = data.data.total_pages; 475 container.dataset.totalPages = totalPages; 476 } 477 478 updatePagination(); 479 480 if (updateUrl && enableUrlPagination && baseUrl) { 481 const newUrl = (page === 1) ? baseUrl + '/' : baseUrl + '/page/' + page + '/'; 482 const pageTitle = document.title.replace(/- Trang \d+/, ''); 483 const newTitle = (page === 1) ? pageTitle : pageTitle + ' - Trang ' + page; 484 485 window.history.pushState( 486 { page: page, gridId: gridId }, 487 newTitle, 488 newUrl 489 ); 490 document.title = newTitle; 491 } 492 493 const headerOffset = 100; 494 const elementPosition = container.getBoundingClientRect().top; 495 const offsetPosition = elementPosition + window.pageYOffset - headerOffset; 496 497 window.scrollTo({ 498 top: offsetPosition, 499 behavior: 'smooth' 500 }); 501 } else { 502 console.error('Failed to load posts:', data.data?.message || 'Unknown error'); 503 showError('Không thá» tải bà i viết. Vui lòng thá» lại.'); 504 } 505 } catch (error) { 506 console.error('Error loading posts:', error); 507 showError('Äã xảy ra lá»i khi tải bà i viết.'); 508 } finally { 509 isLoading = false;
510 if (loadingOverlay) loadingOverlay.classList.add('hidden'); 511 } 512 } 513 514 /** 515 * Update pagination buttons state 516 */ 517 function updatePagination() { 518 // Update prev/next buttons 519 if (prevBtn) { 520 prevBtn.disabled = currentPage <= 1; 521 prevBtn.classList.toggle('opacity-50', currentPage <= 1); 522 prevBtn.classList.toggle('cursor-not-allowed', currentPage <= 1); 523 } 524 525 if (nextBtn) { 526 nextBtn.disabled = currentPage >= totalPages; 527 nextBtn.classList.toggle('opacity-50', currentPage >= totalPages); 528 nextBtn.classList.toggle('cursor-not-allowed', currentPage >= totalPages); 529 } 530 531 pageButtons.forEach(btn => { 532 const page = parseInt(btn.dataset.page); 533 if (page === currentPage) { 534 btn.classList.add('bg-primary', 'text-white'); 535 btn.classList.remove('bg-gray-100'); 536 btn.disabled = true; 537 } else { 538 btn.classList.remove('bg-primary', 'text-white'); 539 btn.classList.add('bg-gray-100'); 540 btn.disabled = false; 541 } 542 }); 543 } 544 545 function showError(message) { 546 const notification = document.createElement('div'); 547 notification.className = 'fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 animate-fade-in'; 548 notification.textContent = message; 549 document.body.appendChild(notification); 550 551 setTimeout(() => { 552 notification.classList.add('animate-fade-out'); 553 setTimeout(() => notification.remove(), 300); 554 }, 3000); 555 } 556 557 if (prevBtn) { 558 prevBtn.addEventListener('click', (e) => { 559 e.preventDefault(); 560 if (currentPage > 1) { 561 loadPosts(currentPage - 1); 562 } 563 }); 564 } 565 566 if (nextBtn) { 567 nextBtn.addEventListener('click', (e) => { 568 e.preventDefault(); 569 if (currentPage < totalPages) { 570 loadPosts(currentPage + 1); 571 } 572 }); 573 } 574 575 pageButtons.forEach(btn => { 576 btn.addEventListener('click', (e) => { 577 e.preventDefault(); 578 const page = parseInt(btn.dataset.page); 579 if (page !== currentPage && page >= 1 && page <= totalPages) { 580 loadPosts(page); 581 } 582 }); 583 }); 584 585 container.addEventListener('keydown', (e) => { 586 if (e.target.classList.contains('pagination-page') || 587 e.target.classList.contains('pagination-btn')) { 588 if (e.key === 'ArrowLeft' && currentPage > 1) { 589 e.preventDefault(); 590 loadPosts(currentPage - 1); 591 } else if (e.key === 'ArrowRight' && currentPage < totalPages) { 592 e.preventDefault(); 593 loadPosts(currentPage + 1); 594 } 595 } 596 }); 597 598 container._loadPosts = loadPosts; 599 }); 600 } 601 602 initBlogPagination(); 603 604 window.addEventListener('popstate', (event) => { 605 if (event.state && event.state.page && event.state.gridId) { 606 const container = document.getElementById(event.state.gridId); 607 if (container && container._loadPosts) { 608 const enableUrlPagination = container.dataset.urlPagination === 'true'; 609 if (enableUrlPagination) { 610 container._loadPosts(event.state.page, false); 611 } 612 } 613 } 614 }); 615 616 617 618}); 619 620(function ($) { 621 'use strict'; 622 if (typeof jQuery === 'undefined') { 623 return; 624 } 625 626 $(document).ready(function () { 627 if (typeof $.fn.owlCarousel === 'undefined') { 628 return; 629 } 630 631 const $owl = $('.owl-carousel'); 632 633 if ($owl.length > 0) { 634 let isDragging = false; 635 let startX = 0; 636 const tolerance = 5; 637 638 $owl.owlCarousel({ 639 items: 1, 640 loop: true, 641 margin: 10, 642 nav: false, 643 dots: true, 644 autoplay: true, 645 autoplayTimeout: 6000, 646 autoplayHoverPause: true, 647 smartSpeed: 3000, 648 mouseDrag: true, 649 touchDrag: true, 650 pullDrag: true, 651 onDrag: function(event) { 652 isDragging = true; 653 } 654 }); 655 656 $owl.on('mousedown touchstart', 'a', function(e) { 657 isDragging = false;
658 startX = e.pageX || e.originalEvent.touches[0].pageX; 659 }); 660 661 $owl.on('mousemove touchmove', 'a', function(e) { 662 const currentX = e.pageX || e.originalEvent.touches[0].pageX; 663 if (Math.abs(currentX - startX) > tolerance) { 664 isDragging = true; 665 } 666 }); 667 668 $owl.on('click', 'a', function(e) { 669 if (isDragging) { 670 e.preventDefault(); 671 } 672 }); 673 } 674}); 675})(jQuery);
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.