1class BannerManager { 2 3 /** 4 * @param {boolean} isMobile 5 */ 6 constructor(isMobile = false) { 7 this.isMobile = isMobile; 8 this.cookiePrefix = 'CL_POPUP_HIDDEN_'; 9 this.SESSION_LIFETIME = 1800; 10 11 this.config = { 12 closeSelectors: [ 13 '.toast-banner__close', 14 '.banner-close', 15 '.promo-banner-short__close' 16 ], 17 fullBannerSelector: '.promo-banner-wrapper', 18 shortBannerSelector: '.promo-banner-short', 19 menuContainerSelector: '.desktop-menu-container', 20 toastSelector: '.toast-banner', 21 cookieBannerSelector: 'cookie-consent-banner' 22 }; 23 24 this.fullBanner = document.querySelector(this.config.fullBannerSelector); 25 this.shortBanner = document.querySelector(this.config.shortBannerSelector); 26 this.companyFilter = document.querySelector('.new-company__container .new-company__filter'); 27 this.filterContainer = document.querySelector('.new-company__filter-container'); 28 this.companyContainer = document.querySelector('.new-company__container'); 29 this.pageWrapper = document.querySelector('.page-wrapper'); 30 31 if (this.isMobile && this.shortBanner && !this.filterContainer && this.pageWrapper) { 32 this.pageWrapper.style.marginTop = '-14px'; 33 } 34 35 if (this.isMobile && this.companyContainer && this.companyFilter && this.shortBanner) { 36 this.companyContainer.style.zIndex = 'auto'; 37 } 38 39 if (this.companyFilter) { 40 this.SCROLL_THRESHOLD_DOWN = 250; 41 this.SCROLL_THRESHOLD_UP = 220; 42 } else { 43 this.SCROLL_THRESHOLD_DOWN = 120; 44 this.SCROLL_THRESHOLD_UP = 80; 45 } 46 47 this.isPromoDismissed = false; 48 this.cookieObserver = null; 49 this.menuResizeObserver = null; 50 this.activeBannerState = 'full'; 51 this.ticking = false; 52 53 this.menuContainer = document.querySelector(this.config.menuContainerSelector); 54 this.toastBanner = document.querySelector(this.config.toastSelector); 55 this.cookieBanner = null; 56 57 this.refreshSessionCookies(); 58 this.checkInitialState(); 59 60 this.init(); 61 } 62 63 /** ÐÑÐµÑ Ð²Ñе кÑки ÑкÑÑÑÑÑ Ð¿Ð¾Ð¿Ð°Ð¿Ð¾Ð² и обновлÑÐµÑ Ð¸Ð¼ ÑÑок жизни. */ 64 refreshSessionCookies() { 65 const cookies = document.cookie.split(';'); 66 67 cookies.forEach(cookie => { 68 const [name, value] = cookie.trim().split('='); 69 if (name.indexOf(this.cookiePrefix) === 0) { 70 this.setCookie(name, value, { 71 path: '/', 72 samesite: 'strict', 73 'max-age': this.SESSION_LIFETIME 74 }); 75 } 76 }); 77 } 78 79 /** ÐеÑод пÑовеÑки ÑÐ¾Ñ Ñаненного ÑоÑÑоÑÐ½Ð¸Ñ (ÑкÑÑÑие ÑÑÐ°Ð·Ñ Ð¿Ñи загÑÑзке) */ 80 checkInitialState() { 81 const promoId = this.fullBanner?.dataset.popupId || this.shortBanner?.dataset.popupIdShort; 82 const toastId = this.toastBanner?.dataset.toastId; 83 if (promoId && localStorage.getItem(this.storagePrefix + 'closed_' + promoId)) { 84 this.isPromoDismissed = true; 85 if (this.fullBanner) this.fullBanner.classList.add('d-none'); 86 if (this.shortBanner) this.shortBanner.classList.add('d-none'); 87 } 88 if (toastId && localStorage.getItem(this.storagePrefix + 'closed_' + toastId)) { 89 if (this.toastBanner) this.toastBanner.dataset.dismissedByCookie = "true"; 90 } 91 } 92 93 /** 94 * ÐниÑиализиÑÑÐµÑ Ð¾ÑновнÑÑ Ð»Ð¾Ð³Ð¸ÐºÑ Ð±Ð°Ð½Ð½ÐµÑа, вÑзÑÐ²Ð°Ñ Ð¼ÐµÑÐ¾Ð´Ñ 95 * Ð´Ð»Ñ Ð¿Ð¾Ð·Ð¸ÑиониÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ð¸ ÑÑÑановки обÑабоÑÑиков ÑобÑÑий. 96 */ 97 init() { 98 localStorage.setItem(this.sessionKeepaliveKey, Date.now().toString()); 99 setInterval(() => localStorage.setItem(this.sessionKeepaliveKey, Date.now().toString()), 5000); 100 101 if (this.isMobile) { 102 this.updateMobileBannerPosition(); 103 } else { 104 this.initMenuObserver(); 105 this.updateShortBannerTopPosition(); 106 } 107 108 this.initEventListeners(); 109 this.initializeCookieBannerLogic(); 110 } 111 112 /** 113 * ÐбновлÑÐµÑ Ð²ÐµÑÑикалÑное положение (top) коÑоÑкого баннеÑа на деÑкÑопе. 114 * ÐозиÑÐ¸Ñ ÑаÑÑÑиÑÑваеÑÑÑ Ð² завиÑимоÑÑи Ð¾Ñ Ð½Ð°Ð»Ð¸ÑÐ¸Ñ Ð¸ ÑаÑположениÑ
115 * конÑейнеÑа Ñ ÑилÑÑÑом компаний или вÑÑоÑÑ Ð¼ÐµÐ½Ñ. 116 */ 117 updateShortBannerTopPosition() { 118 if (this.isMobile || !this.shortBanner || this.isPromoDismissed) { 119 return; 120 } 121 122 if (this.companyFilter && this.filterContainer) { 123 const menuStickyOffset = 72; 124 const filterHeight = this.filterContainer.offsetHeight; 125 const gap = 8; 126 const newTopPosition = menuStickyOffset + filterHeight + gap; 127 128 this.shortBanner.style.top = `${newTopPosition}px`; 129 this.shortBanner.style.zIndex = '999'; 130 if (this.companyContainer) { 131 this.companyContainer.style.zIndex = 'auto'; 132 } 133 134 } else if (this.menuContainer) { 135 const menuHeight = this.menuContainer.getBoundingClientRect().height; 136 this.shortBanner.style.top = `${menuHeight + 8}px`; 137 } else { 138 this.shortBanner.style.top = '72px'; 139 } 140 } 141 142 /** 143 * ÐниÑиализиÑÑÐµÑ ResizeObserver Ð´Ð»Ñ Ð¾ÑÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ ÑазмеÑа 144 * конÑейнеÑа Ð¼ÐµÐ½Ñ Ð¸ поÑледÑÑÑего Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð·Ð¸Ñии баннеÑа на деÑкÑопе. 145 */ 146 initMenuObserver() { 147 if (this.isMobile || !this.menuContainer) return; 148 this.menuResizeObserver = new ResizeObserver(() => this.updateShortBannerTopPosition()); 149 this.menuResizeObserver.observe(this.menuContainer); 150 } 151 152 /** 153 * ÐниÑиализиÑÑÐµÑ Ð²Ñе Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ñе обÑабоÑÑики ÑобÑÑий: 154 * ÑкÑолл, изменение ÑазмеÑа окна и клики по кнопкам закÑÑÑиÑ. 155 */ 156 initEventListeners() { 157 this.handleResize = this.handleResize.bind(this); 158 this.handleScroll = this.handleScroll.bind(this); 159 this.handleScrollRequest = this.handleScrollRequest.bind(this); 160 this.handleClose = this.handleClose.bind(this); 161 162 if (this.shortBanner) { 163 window.addEventListener('scroll', this.handleScrollRequest); 164 window.addEventListener('resize', this.handleResize); 165 } 166 167 this.config.closeSelectors.forEach(selector => { 168 document.querySelectorAll(selector).forEach(button => { 169 button.addEventListener('click', this.handleClose); 170 }); 171 }); 172 } 173 174 /** 175 * ÐбÑабаÑÑÐ²Ð°ÐµÑ ÑобÑÑие Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÑазмеÑа окна, вÑзÑÐ²Ð°Ñ 176 * ÑооÑвеÑÑÑвÑÑÑий меÑод Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð·Ð¸Ñии баннеÑа. 177 */ 178 handleResize() { 179 if (this.isMobile) { 180 this.updateMobileBannerPosition(); 181 } else { 182 this.updateShortBannerTopPosition(); 183 } 184 } 185 186 /** 187 * ÐпÑимизиÑÑÐµÑ Ð¾Ð±ÑабоÑÐºÑ ÑкÑолла Ñ Ð¿Ð¾Ð¼Ð¾ÑÑÑ requestAnimationFrame, 188 * ÑÑÐ¾Ð±Ñ Ð¸Ð·Ð±ÐµÐ¶Ð°ÑÑ Ð»Ð¸ÑÐ½Ð¸Ñ Ð²ÑÑиÑлений и ÑлÑÑÑиÑÑ Ð¿ÑоизводиÑелÑноÑÑÑ. 189 */ 190 handleScrollRequest() { 191 if (!this.ticking) { 192 window.requestAnimationFrame(() => { 193 this.handleScroll(); 194 this.ticking = false; 195 }); 196 this.ticking = true; 197 } 198 } 199 200 /** 201 * ÐбÑабаÑÑÐ²Ð°ÐµÑ ÑобÑÑие ÑкÑолла ÑÑÑаниÑÑ. ÐÑвеÑÐ°ÐµÑ Ð·Ð° пеÑеклÑÑение 202 * Ð¼ÐµÐ¶Ð´Ñ Ð¿Ð¾Ð»Ð½Ñм и коÑоÑким баннеÑом на деÑкÑопе и обновление 203 * позиÑии баннеÑа на мобилÑнÑÑ ÑÑÑÑойÑÑÐ²Ð°Ñ . 204 */ 205 handleScroll() { 206 if (this.isPromoDismissed) return; 207 208 const scrollY = window.scrollY; 209 210 if (!this.isMobile && this.fullBanner && this.shortBanner) { 211 if (scrollY >= this.SCROLL_THRESHOLD_DOWN && this.activeBannerState === 'full') { 212 this.fullBanner.classList.add('d-none'); 213 this.shortBanner.classList.remove('d-none'); 214 this.activeBannerState = 'short'; 215 this.updateShortBannerTopPosition(); 216 } else if (scrollY <= this.SCROLL_THRESHOLD_UP && this.activeBannerState === 'short') { 217 this.shortBanner.classList.add('d-none'); 218 this.fullBanner.classList.remove('d-none'); 219 this.activeBannerState = 'full'; 220 } 221 } 222 223 if (this.isMobile) { 224 this.updateMobileBannerPosition(); 225 } 226 } 227 228 /** 229 * ÐбновлÑÐµÑ Ð¿Ð¾Ð·Ð¸ÑÐ¸Ñ Ð±Ð°Ð½Ð½ÐµÑа на мобилÑнÑÑ ÑÑÑÑойÑÑÐ²Ð°Ñ . 230 * РаÑÑÐµÑ Ð·Ð°Ð²Ð¸ÑÐ¸Ñ Ð¾Ñ Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÑилÑÑÑа компаний пÑи ÑкÑолле. 231 */ 232 updateMobileBannerPosition() { 233 if (!this.isMobile || !this.shortBanner || this.isPromoDismissed) return; 234 235 const scrollY = window.scrollY; 236 if (this.filterContainer && scrollY > 0) { 237 const filterRect = this.filterContainer.getBoundingClientRect(); 238 const newTopPosition = filterRect.bottom > 0 ? filterRect.bottom : 0; 239 this.shortBanner.style.top = `${newTopPosition}px`; 240 } else { 241 this.shortBanner.style.top = '0px'; 242 } 243 this.shortBanner.style.zIndex = '999'; 244 } 245 246 /** 247 * ÐниÑиализиÑÑÐµÑ Ð»Ð¾Ð³Ð¸ÐºÑ, ÑвÑзаннÑÑ Ñ Ð±Ð°Ð½Ð½ÐµÑом о cookie. 248 * ÐÐ¶Ð¸Ð´Ð°ÐµÑ Ð¿Ð¾ÑÐ²Ð»ÐµÐ½Ð¸Ñ cookie-баннеÑа внÑÑÑи ÑлеменÑа #analytics_area 249 * и запÑÑÐºÐ°ÐµÑ Ð·Ð°Ð²Ð¸ÑимÑÑ Ð»Ð¾Ð³Ð¸ÐºÑ, когда он поÑвл
249ÑеÑÑÑ 250 */ 251 initializeCookieBannerLogic() { 252 if (!this.toastBanner || this.toastBanner.dataset.dismissedByCookie === "true") return; 253 254 const handleCookieBannerFinished = (event) => { 255 if (event.detail.bannerShown) { 256 this.cookieBanner = document.querySelector(this.config.cookieBannerSelector); 257 if (this.cookieBanner) this.runCookieDependentLogic(); 258 } else { 259 this.showToast(); 260 } 261 }; 262 document.addEventListener('cookieBanner:finished', handleCookieBannerFinished, { once: true }); 263 } 264 /** 265 * ÐапÑÑÐºÐ°ÐµÑ Ð»Ð¾Ð³Ð¸ÐºÑ, коÑоÑÐ°Ñ Ð·Ð°Ð²Ð¸ÑÐ¸Ñ Ð¾Ñ ÑоÑÑоÑÐ½Ð¸Ñ cookie-баннеÑа. 266 * ÐÑли cookie-Ð±Ð°Ð½Ð½ÐµÑ Ð°ÐºÑивен, Ð¾Ð¶Ð¸Ð´Ð°ÐµÑ ÐµÐ³Ð¾ закÑÑÑиÑ. ÐÑли неакÑивен, 267 * ÑÑÐ°Ð·Ñ Ð¿Ð¾ÐºÐ°Ð·ÑÐ²Ð°ÐµÑ toast-Ñведомление. 268 */ 269 runCookieDependentLogic() { 270 const isCookieBannerActive = this.cookieBanner && this.cookieBanner.classList.contains('active'); 271 if (isCookieBannerActive) { 272 document.addEventListener('cookieBanner:closed', () => this.showToast(), { once: true }); 273 } else { 274 this.showToast(); 275 } 276 } 277 278 /** 279 * ÐбÑабоÑÑик закÑÑÑÐ¸Ñ 280 */ 281 handleClose(event) { 282 event.preventDefault(); 283 event.stopPropagation(); 284 285 const closeButton = event.currentTarget; 286 const popupWrapper = closeButton.closest('[data-toast-id], [data-popup-id], [data-popup-id-short]'); 287 if (!popupWrapper) return; 288 289 const popupId = popupWrapper.dataset.toastId || popupWrapper.dataset.popupId || popupWrapper.dataset.popupIdShort; 290 291 if (popupId) { 292 this.setCookie(this.cookiePrefix + popupId, '1', { 293 path: '/', 294 samesite: 'strict', 295 'max-age': this.SESSION_LIFETIME 296 }); 297 } 298 299 if (popupWrapper.matches(this.config.toastSelector)) { 300 this.dismissToast(); 301 } else { 302 this.dismissPromo(); 303 } 304 } 305 /** 306 * Ð¥ÐµÐ»Ð¿ÐµÑ ÑÑÑановки кÑки 307 */ 308 setCookie(name, value, options = {}) { 309 options = { path: '/', ...options }; 310 if (options.expires instanceof Date) options.expires = options.expires.toUTCString(); 311 312 let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value); 313 for (let optionKey in options) { 314 updatedCookie += "; " + optionKey; 315 let optionValue = options[optionKey]; 316 if (optionValue !== true) updatedCookie += "=" + optionValue; 317 } 318 document.cookie = updatedCookie; 319 } 320 321 /** 322 * ÐоказÑÐ²Ð°ÐµÑ toast-Ñведомление, Ð´ÐµÐ»Ð°Ñ ÐµÐ³Ð¾ видимÑм 323 * Ñ Ð¿Ð»Ð°Ð²Ð½Ð¾Ð¹ анимаÑией. 324 */ 325 showToast() { 326 if (!this.toastBanner || this.toastBanner.dataset.dismissedByCookie === "true") return; 327 this.toastBanner.classList.remove('d-none'); 328 setTimeout(() => this.toastBanner.classList.add('active'), 50); 329 } 330 331 /** 332 * СкÑÑÐ²Ð°ÐµÑ toast-Ñведомление Ñ Ð°Ð½Ð¸Ð¼Ð°Ñией и ÑдалÑÐµÑ 333 * ÑвÑзаннÑе Ñ Ð½Ð¸Ð¼ обÑабоÑÑики ÑобÑÑий. 334 */ 335 dismissToast() { 336 if (!this.toastBanner) return; 337 this.cleanupEventListeners(); 338 this.toastBanner.addEventListener('transitionend', () => this.toastBanner.classList.add('d-none'), {once: true}); 339 this.toastBanner.classList.remove('active'); 340 } 341 342 /** 343 * СкÑÑÐ²Ð°ÐµÑ Ð¿Ñомо-баннеÑÑ (полнÑй и коÑоÑкий) и ÑÑÑÐ°Ð½Ð°Ð²Ð»Ð¸Ð²Ð°ÐµÑ Ñлаг, 344 * ÑÑÐ¾Ð±Ñ Ð¾Ð½Ð¸ болÑÑе не поÑвлÑлиÑÑ Ð¿Ñи ÑкÑолле. 345 */ 346 dismissPromo() { 347 this.isPromoDismissed = true; 348 if (this.fullBanner) this.fullBanner.classList.add('d-none'); 349 if (this.shortBanner) this.shortBanner.classList.add('d-none'); 350 if (this.pageWrapper) this.pageWrapper.style.marginTop = ''; 351 352 this.cleanupEventListeners(); 353 } 354 355 356 /** 357 * УдалÑÐµÑ Ð²Ñе глобалÑнÑе обÑабоÑÑики ÑобÑÑий и оÑклÑÑÐ°ÐµÑ Ð½Ð°Ð±Ð»ÑдаÑелей, 358 * ÑÑÐ¾Ð±Ñ Ð¿ÑедоÑвÑаÑиÑÑ ÑÑеÑки памÑÑи. 359 */ 360 cleanupEventListeners() { 361 window.removeEventListener('scroll', this.handleScrollRequest); 362 window.removeEventListener('resize', this.handleResize); 363 this.menuResizeObserver?.disconnect(); 364 } 365}
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.