PageSourceSearch

https://glidehq.io/wp-content/themes/v1/html/dist/js/main.js?ver=1.6.2

js glidehq.io collected 2026-10-09 21:52:39 UTC 21,120 bytes, 675 lines download raw bytes

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.