1/** 2 * Quản lý viá»c tải và hiá»n thá» quảng cáo 3 */ 4const AdsManager = { 5 adsData: null, 6 isLoading: false, 7 isLoaded: false, 8 initializedCodes: new Set(), 9 10 isElementVisible: function (element) { 11 if (!element) return false; 12 13 const style = window.getComputedStyle(element); 14 if (style.display === 'none' || style.visibility === 'hidden') { 15 return false; 16 } 17 18 if (typeof element.checkVisibility === 'function') { 19 return element.checkVisibility(); 20 } 21 22 return true; 23 }, 24 25 getGptSlotsByIds: function (slotIds) { 26 if (!window.googletag?.pubads || !slotIds.length) { 27 return []; 28 } 29 30 return slotIds 31 .map(id => window.googletag.pubads().getSlots().find(slot => slot.getSlotElementId() === id)) 32 .filter(Boolean); 33 }, 34 35 enableGptServicesOnce: function () { 36 if (window.__ADS_GPT_SERVICES_ENABLED__) { 37 return; 38 } 39 40 window.googletag.pubads().enableSingleRequest(); 41 window.googletag.pubads().collapseEmptyDivs(true); 42 window.googletag.enableServices(); 43 window.__ADS_GPT_SERVICES_ENABLED__ = true; 44 }, 45 46 setAdContainerState: function (container, hasAds) { 47 if (!container) return; 48 49 const stateTargets = [container]; 50 const adsWrap = container.closest('.adsWrap'); 51 52 if (adsWrap) { 53 stateTargets.push(adsWrap); 54 } 55 56 stateTargets.forEach(element => { 57 element.classList.toggle('no-ads', !hasAds); 58 element.classList.toggle('has-ads', hasAds); 59 }); 60 }, 61 62 getCategoryId: function () { 63 const categoryElement = document.getElementById('ads-category-id'); 64 const rawCategoryId = categoryElement?.textContent?.trim(); 65 66 if (!rawCategoryId) { 67 return ''; 68 } 69 70 try { 71 return JSON.parse(rawCategoryId); 72 } catch (error) { 73 console.warn('[AdsManager] Category ID không hợp lá»:', error); 74 return ''; 75 } 76 }, 77 78 getRequestContext: function () { 79 const isMobile = window.innerWidth <= 768; 80 const isDesktop = window.innerWidth > 768; 81 const isHome = window.location.pathname === '/'; 82 const isDetail = window.location.pathname.includes('.html'); 83 const isCategory = window.location.pathname !== '/' && !window.location.pathname.includes('.html'); 84 const categoryId = this.getCategoryId(); 85 const params = new URLSearchParams({ 86 isMobile: String(isMobile), 87 isDesktop: String(isDesktop), 88 isHome: String(isHome), 89 isDetail: String(isDetail), 90 isCategory: String(isCategory), 91 category_id: categoryId == null ? '' : String(categoryId), 92 current_path: window.location.pathname, 93 }); 94 95 return { 96 isMobile, 97 isDesktop, 98 isHome, 99 isDetail, 100 isCategory, 101 categoryId, 102 url: `/api/ads/get-list?${params.toString()}`, 103 }; 104 }, 105 106 getAdsDataPromise: function () { 107 if (!window.__ADS_DATA_PROMISE__) { 108 const { url } = this.getRequestContext(); 109 window.__ADS_DATA_PROMISE__ = fetch(url) 110 .then(response => response.json()) 111 .then(data => { 112 if (data && data.status && data.data && data.data.length > 0) { 113 return data.data; 114 } else { 115 return []; 116 } 117 }) 118 .catch(error => { 119 console.error('[AdsManager] Lá»i khi tải quảng cáo:', error); 120 return []; 121 }); 122 } 123 return window.__ADS_DATA_PROMISE__; 124 }, 125 126 initialize: function () { 127 if (this.isLoaded || this.isLoading) { 128 return Promise.resolve(this.adsData); 129 } 130 131 console.log('[AdsManager] Bắt Äầu tải dữ liá»u quảng cáo...'); 132 this.isLoading = true; 133 134 const { 135 isMobile, 136 isDesktop, 137 isHome, 138 isDetail, 139 isCategory, 140 categoryId, 141 url, 142 } = this.getRequestContext(); 143 144 console.log(`[AdsManager] Tham sá»: Mobile=${isMobile}, Desktop=${isDesktop}, Home=${isHome}, Detail=${isDetail}, Category=${isCategory}, CategoryID=${categoryId}`); 145 146 console.log(`[AdsManager] Gá»i API: ${url}`); 147 148 return this.getAdsDataPromise().then(data => { 149 this.isLoading = false; 150 this.adsData = data; 151 this.isLoaded = true; 152 console.log(`[AdsManager] Äã tải ${this.adsData.length} vùng quảng cáo thà nh công`); 153 return this.adsData; 154 }); 155 }, 156 157 displayAdsV2: function (code, container) { 158 this.initialize().then(adsData => { 159 const adDiv = container || document.getElementById(`ads-${code}`); 160 if (!adDiv) { 161 console.error(`[AdsManager] Không tìm thấy div: ads-${
161code}`); 162 return; 163 } 164 165 const matchingArea = adsData.find(area => area.code === code); 166 if (!matchingArea || !matchingArea.adsCode?.length) { 167 this.setAdContainerState(adDiv, false); 168 return; 169 } 170 171 try { 172 this.setAdContainerState(adDiv, true); 173 adDiv.innerHTML = ''; 174 matchingArea.adsCode.forEach(adCodeObj => { 175 const tempDiv = document.createElement('div'); 176 tempDiv.innerHTML = adCodeObj.code; 177 178 // Append phần tá» không phải script 179 Array.from(tempDiv.childNodes).forEach(node => { 180 if (node.tagName !== 'SCRIPT') { 181 adDiv.appendChild(node.cloneNode(true)); 182 } else { 183 const script = document.createElement('script'); 184 if (node?.src) { 185 script.src = node.src; 186 script.async = true; 187 } else { 188 script.textContent = node?.textContent; 189 } 190 adDiv.appendChild(script); 191 } 192 }); 193 }); 194 195 const scriptCode = matchingArea.adsCode[0].head; 196 if (scriptCode) { 197 const adSlotDivs = adDiv.querySelectorAll('div[id^="div-gpt-ad-"]'); 198 window.googletag.cmd.push(function() { 199 const adSlotIds = Array.from(adSlotDivs) 200 .map(div => div.id) 201 .filter(Boolean); 202 let slotsToRefresh = AdsManager.getGptSlotsByIds(adSlotIds); 203 const shouldDefineSlots = !adSlotIds.length || slotsToRefresh.length !== adSlotIds.length; 204 205 if (shouldDefineSlots) { 206 try { 207 new Function(scriptCode)(); 208 } catch (error) { 209 console.error(`[AdsManager] Lá»i khi Äá»nh nghÄ©a GPT slot: ${code}`, error); 210 } 211 212 slotsToRefresh = AdsManager.getGptSlotsByIds(adSlotIds); 213 } else { 214 console.debug(`[AdsManager] Bá» qua GPT slot Äã ÄÆ°á»£c Äá»nh nghÄ©a: ${code}`); 215 } 216 217 AdsManager.enableGptServicesOnce(); 218 219 if (slotsToRefresh.length) { 220 window.googletag.pubads().refresh(slotsToRefresh); 221 } 222 }); 223 } 224 } catch (error) { 225 this.setAdContainerState(adDiv, false); 226 console.error(`[AdsManager] Lá»i khi hiá»n thá» quảng cáo: ${code}`, error); 227 } 228 }); 229 }, 230 231 displayAds: function (code, container) { 232 this.initialize().then(adsData => { 233 const adDiv = container || document.getElementById(`ads-${code}`); 234 if (!adDiv) { 235 console.error(`[AdsManager] Không tìm thấy div: ads-${code}`); 236 return; 237 } 238 239 const matchingArea = adsData.find(area => area.code === code); 240 if (matchingArea && matchingArea.adsCode && matchingArea.adsCode.length > 0) { 241 this.setAdContainerState(adDiv, true); 242 // console.log({matchingArea, code}); 243 // console.log(`[AdsManager] Hiá»n thá» quảng cáo cho vùng: ${code} (${matchingArea.adsCode.length} mã)`); 244 adDiv.innerHTML = ''; // clear trưá»c 245 matchingArea.adsCode.forEach(adCodeObj => { 246 const tempDiv = document.createElement('div'); 247 tempDiv.innerHTML = adCodeObj.code; 248 249 // Append phần tá» không phải script 250 Array.from(tempDiv.childNodes).forEach(node => { 251 if (node.tagName !== 'SCRIPT') { 252 adDiv.appendChild(node.cloneNode(true)); 253 } 254 }); 255 // Äảm bảo slot Äã ÄÆ°á»£c define 256 if (adCodeObj.head) { 257 googletag.cmd.push(function () { 258 eval(adCodeObj.head); // defineSlot 259 }); 260 } 261 262 // Re-execute script 263 const scripts = tempDiv.querySelectorAll('script'); 264 scripts.forEach(oldScript => { 265 const newScript = document.createElement('script'); 266 267 if (oldScript.src) { 268 newScript.src = oldScript.src; 269 } else { 270 newScript.text = oldScript.textContent; 271 } 272
273 Array.from(oldScript.attributes).forEach(attr => { 274 if (attr.name !== 'src') { 275 newScript.setAttribute(attr.name, attr.value); 276 } 277 }); 278 279 adDiv.appendChild(newScript); 280 }); 281 }); 282 } else { 283 this.setAdContainerState(adDiv, false); 284 console.log(`[AdsManager] Không tìm thấy quảng cáo cho vùng: ${code}`); 285 } 286 }); 287 }, 288 289 initAllAdsBlocks: function () { 290 const adsWrappers = document.querySelectorAll('.ads-wrapper'); 291 console.log(`[AdsManager] Tìm thấy ${adsWrappers.length} vùng quảng cáo trên trang`); 292 293 const wrappersByCode = new Map(); 294 295 adsWrappers.forEach(wrapper => { 296 const codeMatch = wrapper.id.match(/ads-(.+)/); 297 if (codeMatch && codeMatch[1]) { 298 const code = codeMatch[1]; 299 const currentWrapper = wrappersByCode.get(code); 300 301 if (!currentWrapper || (!this.isElementVisible(currentWrapper) && this.isElementVisible(wrapper))) { 302 wrappersByCode.set(code, wrapper); 303 } 304 } 305 }); 306 307 wrappersByCode.forEach((wrapper, code) => { 308 if (this.initializedCodes.has(code)) { 309 console.debug(`[AdsManager] Bá» qua vùng quảng cáo Äã khá»i tạo: ${code}`); 310 return; 311 } 312 313 this.initializedCodes.add(code); 314 console.log(`[AdsManager] Khá»i tạo vùng quảng cáo: ${code}`); 315 this.displayAdsV2(code, wrapper); 316 }); 317 } 318}; 319 320document.addEventListener('DOMContentLoaded', function () { 321 console.log('[AdsManager] DOM Äã sẵn sà ng, bắt Äầu khá»i tạo AdsManager'); 322 323 AdsManager.initialize().then(() => { 324 AdsManager.initAllAdsBlocks(); 325 }); 326});
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.