1class ReviewStickyNav { 2 constructor() { 3 this.lastScrollTop = 0; 4 this.isMobile = window.innerWidth <= 767; 5 this.scrollHandler = this.handleScroll.bind(this); 6 this.scrollToReviewsHandler = this.scrollToReviews.bind(this); 7 8 this.init(); 9 } 10 11 init() { 12 this.waitForElement(".tab-review-tags-nav", () => { 13 this.setupStickyNavigation(); 14 }); 15 } 16 17 waitForElement(selector, callback) { 18 const observer = new MutationObserver((mutations, observer) => { 19 const element = document.querySelector(selector); 20 if (element) { 21 observer.disconnect(); 22 callback(element); 23 } 24 }); 25 26 observer.observe(document.body, { 27 childList: true, 28 subtree: true, 29 }); 30 } 31 32 setupStickyNavigation() { 33 this.filtersNav = document.querySelector(".tab-review-tags-nav"); 34 this.filters = document.querySelectorAll(".tab-review-tags-nav-item"); 35 this.filterParagraph = document.querySelector(".tab-review-tags > p"); 36 this.reviewsHeader = document.querySelector("#reviews_header"); 37 this.reviewsSections = document.querySelectorAll("#revive-reviews"); 38 39 if (!this.filtersNav || !this.filterParagraph || !this.reviewsHeader) return; 40 41 this.prepareNavigation(); 42 this.setupIntersectionObserver(); 43 } 44 45 prepareNavigation() { 46 const filterCopy = this.filterParagraph.cloneNode(true); 47 const firstNavItem = this.filtersNav.querySelector(".tab-review-tags-nav-item:first-child"); 48 this.filtersNav.insertBefore(filterCopy, firstNavItem); 49 } 50 51 setupIntersectionObserver() { 52 this.observer = new IntersectionObserver( 53 ([entry]) => { 54 const isBelowTop = entry.boundingClientRect.top < 0; 55 56 if (isBelowTop && !entry.isIntersecting) { 57 this.activateStickyNav(); 58 } else { 59 this.deactivateStickyNav(); 60 } 61 }, 62 { threshold: 0 } 63 ); 64 65 this.observer.observe(this.reviewsHeader); 66 } 67 68 activateStickyNav() { 69 if (this.isMobile) { 70 this.setupMobileScrollHandler(); 71 } 72 this.filtersNav.classList.add("fixed"); 73 this.reviewsSections.forEach((section) => section.classList.add("extra-margin-xs")); 74 this.filters.forEach((filter) => filter.addEventListener("click", this.scrollToReviewsHandler)); 75 } 76 77 deactivateStickyNav() { 78 this.filtersNav.classList.remove("fixed"); 79 this.reviewsSections.forEach((section) => section.classList.remove("extra-margin-xs")); 80 this.filters.forEach((filter) => filter.removeEventListener("click", this.scrollToReviewsHandler)); 81 } 82 83 setupMobileScrollHandler() { 84 window.addEventListener("scroll", this.scrollHandler, { passive: true }); 85 } 86 87 handleScroll() { 88 const fixedNav = document.querySelector(".tab-review-tags-nav.fixed"); 89 if (!fixedNav) return; 90 91 const currentScrollTop = window.pageYOffset; 92 const scrollingUp = currentScrollTop < this.lastScrollTop; 93 this.lastScrollTop = currentScrollTop; 94 95 if (scrollingUp) { 96 fixedNav.classList.add("overwrite-fixed-top"); 97 } else { 98 fixedNav.classList.remove("overwrite-fixed-top"); 99 } 100 } 101 102 scrollToReviews(e) { 103 const href = e.target.getAttribute("data-href") || e.target.querySelector("[data-href]")?.getAttribute("data-href"); 104 if (!href) return; 105 106 const isMobile = window.innerWidth <= 767; 107 const nav = document.querySelector(".tab-review-tags-nav"); 108 const navHeight = nav ? nav.offsetHeight : 0; 109 const extraMobileOffset = isMobile ? 100 : 0; 110 111 let scrollPos; 112 113 if (href === "#tab-review-tags-all") { 114 const reviewsHeader = document.querySelector("#reviews_header"); 115 if (!reviewsHeader) return; 116 117 const baseOffset = isMobile ? extraMobileOffset * 0.75 : navHeight / 2; 118 scrollPos = reviewsHeader.getBoundingClientRect().top + window.pageYOffset - baseOffset; 119 } else { 120 const reviewSection = document.querySelector(`${href} #revive-reviews`); 121 if (!reviewSection) return; 122 123 scrollPos = reviewSection.getBoundingClientRect().top + window.pageYOffset - navHeight - 40 - extraMobileOffset; 124 } 125 126 window.scrollTo({ top: scrollPos, behavior: "smooth" }); 127 } 128 129 destroy() { 130 if (this.observer) { 131 this.observer.disconnect(); 132 } 133 window.removeEventListener("scroll", this.scrollHandler);
134 this.filters.forEach((filter) => filter.removeEventListener("click", this.scrollToReviewsHandler)); 135 } 136} 137 138// Initialize the class when DOM is ready 139document.addEventListener('DOMContentLoaded', function() { 140 new ReviewStickyNav(); 141});
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.