1/** 2 * Dynamic Ads Loader 3 * Load ads via AJAX to bypass nginx cache 4 * 5 * Usage: 6 * 1. Add class "qc_ajax" to any ads container 7 * 2. Add data-position="position_key" attribute 8 * 3. Script auto-initializes on page load 9 */ 10 11(function (window) { 12 'use strict'; 13 14 var WnAdsLoader = { 15 config: { 16 apiEndpoint: '/ajax/ads/by-position', 17 containerClass: 'qc_ajax', 18 loadedClass: 'qc_loaded', 19 loadingClass: 'qc_loading', 20 debug: false // Set to true to enable debug logging 21 }, 22 23 /** 24 * Debug log helper 25 */ 26 log: function () { 27 if (this.config.debug && console && console.log) { 28 console.log.apply(console, ['[WnAdsLoader]'].concat(Array.prototype.slice.call(arguments))); 29 } 30 }, 31 32 /** 33 * Initialize the ads loader 34 */ 35 init: function () { 36 this.log('Initializing...'); 37 this.loadAllAds(); 38 }, 39 40 /** 41 * Check if device is mobile 42 */ 43 isMobile: function () { 44 return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 45 }, 46 47 /** 48 * Get current page URL path 49 */ 50 getCurrentUrl: function () { 51 return window.location.pathname + window.location.search; 52 }, 53 54 /** 55 * Get CSRF token from meta tag 56 */ 57 getCsrfToken: function () { 58 var meta = document.querySelector('meta[name="csrf-token"]'); 59 return meta ? meta.getAttribute('content') : ''; 60 }, 61 62 /** 63 * Convert NodeList to Array for better compatibility 64 */ 65 toArray: function (nodeList) { 66 var arr = []; 67 for (var i = 0; i < nodeList.length; i++) { 68 arr.push(nodeList[i]); 69 } 70 return arr; 71 }, 72 73 /** 74 * Find all ads containers and load ads 75 */ 76 loadAllAds: function () { 77 var self = this; 78 var nodeList = document.querySelectorAll('.' + this.config.containerClass + ':not(.' + this.config.loadedClass + ')'); 79 var containers = this.toArray(nodeList); 80 81 this.log('Found', containers.length, 'containers'); 82 83 if (containers.length === 0) { 84 return; 85 } 86 87 // Collect all positions 88 var positions = []; 89 for (var i = 0; i < containers.length; i++) { 90 var container = containers[i]; 91 var position = container.getAttribute('data-position'); 92 this.log('Container position:', position); 93 if (position && positions.indexOf(position) === -1) { 94 positions.push(position); 95 container.classList.add(self.config.loadingClass); 96 } 97 } 98 99 this.log('Positions to fetch:', positions); 100 101 if (positions.length === 0) { 102 return; 103 } 104 105 // Store containers for later use 106 this._containers = containers; 107 108 // Make AJAX request 109 this.fetchAds(positions, function (data) { 110 self.log('Received data:', data); 111 self.renderAds(data, self._containers); 112 }); 113 }, 114 115 /** 116 * Fetch ads from API using vanilla JavaScript 117 */ 118 fetchAds: function (positions, callback) { 119 var self = this; 120 var xhr = new XMLHttpRequest(); 121 122 xhr.open('POST', this.config.apiEndpoint, true); 123 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 124 xhr.setRequestHeader('X-CSRF-TOKEN', this.getCsrfToken()); 125 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 126 127 xhr.onreadystatechange = function () { 128 if (xhr.readyState === 4) { 129 self.log('XHR status:', xhr.status); 130 if (xhr.status === 200) { 131 try { 132 var response = JSON.parse(xhr.responseText); 133 self.log('Parsed response:', response); 134 if (response && response.data) { 135 callback(response.data); 136 } else { 137 self.log('No data in response'); 138 self.clearLoadingState(); 139 } 140 } catch (e) { 141 console.error('WnAdsLoader: Failed to parse response', e); 142 self.clearLoadingState(); 143 } 144 } else { 145 console.error('WnAdsLoader: Failed to load ads', xhr.status); 146 self.clearLoadingState(); 147 } 148 } 149 }; 150 151 // Build form data 152 var formData = []; 153 for (var i = 0; i < positions.length; i++) { 154 formData.push('positions[' + i + ']=' + encodeURIComponent(positions[i])); 155 } 156 formData.push('url=' + encodeURIComponent(this.getCurrentUrl())); 157 formData.push('is_mobile=' + (this.isMobile() ? '1' : '0')); 158 159 this.log('Sending request:', formData.join('&')); 160 xhr.send(formData.join('&')); 161 }, 162 163 /** 164 * Clear loading state on error 165 */ 166 clearLoadingState: function () { 167 var loadingElements = document.querySelectorAll('.' + this.config.loadingClass); 168 for (var i = 0; i < loadingElements.length; i++) { 169 loadingElements[i].classList.remove(this.config.loadingClass); 170 } 171 }, 172 173 /**
174 * Render ads into containers 175 */ 176 renderAds: function (data, containers) { 177 var self = this; 178 179 this.log('Rendering', containers.length, 'containers with data keys:', Object.keys(data)); 180 181 for (var i = 0; i < containers.length; i++) { 182 var container = containers[i]; 183 var position = container.getAttribute('data-position'); 184 var positionData = data[position]; 185 186 this.log('Rendering position:', position, 'Data:', positionData); 187 188 container.classList.remove(self.config.loadingClass); 189 container.classList.add(self.config.loadedClass); 190 191 if (!positionData || !positionData.ads || positionData.ads.length === 0) { 192 // No ads for this position, hide container 193 this.log('No ads for position:', position); 194 container.style.display = 'none'; 195 continue; 196 } 197 198 var html = self.generateAdsHtml(positionData); 199 this.log('Generated HTML:', html.substring(0, 100) + '...'); 200 container.innerHTML = html; 201 container.style.display = ''; 202 203 // Initialize rotation if needed 204 if (positionData.display_type === 'rotation' && positionData.ads.length > 1) { 205 self.initRotation(container); 206 } 207 } 208 209 // Refresh lazy load if available 210 if (window.lazyLoadInstance) { 211 window.lazyLoadInstance.update(); 212 } 213 }, 214 215 /** 216 * Generate HTML for ads 217 */ 218 generateAdsHtml: function (positionData) { 219 var html = ''; 220 var ads = positionData.ads; 221 var displayType = positionData.display_type || 'rotation'; 222 223 if (ads.length > 1 && displayType === 'rotation') { 224 html += '<div class="qc_rotation">'; 225 } 226 227 for (var i = 0; i < ads.length; i++) { 228 var ad = ads[i]; 229 230 if (ad.type === 'gg_ads' || ad.type === 'custom_code') { 231 html += '<div class="qc_gg">' + (ad.gg_ads || '') + '</div>'; 232 } else if (ad.type === 'google_adsense') { 233 html += '<div class="qc_gg">'; 234 html += '<ins class="adsbygoogle" style="display:block" data-ad-slot="' + (ad.google_adsense_slot_id || '') + '"></ins>'; 235 html += '</div>'; 236 } else if (ad.image) { 237 var visibleClass = (i === 0 || ads.length === 1) ? 'visible' : 'hidden'; 238 var target = ad.open_in_new_tab ? '_blank' : '_self'; 239 240 html += '<a href="' + (ad.link || '#') + '" class="promotions ' + visibleClass + '" target="' + target + '" rel="nofollow" title="' + (ad.name || '') + '">'; 241 html += '<img src="' + ad.image + '" alt="' + (ad.name || '') + '" loading="lazy">'; 242 html += '</a>'; 243 } 244 } 245 246 if (ads.length > 1 && displayType === 'rotation') { 247 html += '</div>'; 248 } 249 250 return html; 251 }, 252 253 /** 254 * Initialize rotation for multiple ads 255 */ 256 initRotation: function (container) { 257 var items = container.querySelectorAll('.promotions'); 258 if (items.length <= 1) return; 259 260 var currentIndex = 0; 261 var rotationInterval = 5000; // 5 seconds 262 263 setInterval(function () { 264 items[currentIndex].classList.remove('visible'); 265 items[currentIndex].classList.add('hidden'); 266 267 currentIndex = (currentIndex + 1) % items.length; 268 269 items[currentIndex].classList.remove('hidden'); 270 items[currentIndex].classList.add('visible'); 271 }, rotationInterval); 272 }, 273 274 /** 275 * Load ads for a specific position only 276 */ 277 loadPosition: function (position) { 278 var nodeList = document.querySelectorAll('[data-position="' + position + '"]:not(.' + this.config.loadedClass + ')'); 279 var containers = this.toArray(nodeList); 280 281 if (containers.length === 0) return; 282 283 var self = this; 284 for (var i = 0; i < containers.length; i++) { 285 containers[i].classList.add(self.config.loadingClass); 286 } 287 288 this.fetchAds([position], function (data) {
289 self.renderAds(data, containers); 290 }); 291 }, 292 293 /** 294 * Refresh all ads (force reload) 295 */ 296 refresh: function () { 297 var self = this; 298 var loadedElements = document.querySelectorAll('.' + this.config.loadedClass); 299 for (var i = 0; i < loadedElements.length; i++) { 300 loadedElements[i].classList.remove(self.config.loadedClass); 301 } 302 303 this.loadAllAds(); 304 }, 305 306 /** 307 * Enable debug mode 308 */ 309 enableDebug: function () { 310 this.config.debug = true; 311 console.log('[WnAdsLoader] Debug mode enabled'); 312 } 313 }; 314 315 // Export to window 316 window.WnAdsLoader = WnAdsLoader; 317 318 // Auto-init when DOM is ready with retry mechanism 319 var initWithRetry = function (retryCount) { 320 retryCount = retryCount || 0; 321 var maxRetries = 5; 322 var retryDelay = 200; // ms 323 324 var containers = document.querySelectorAll('.' + WnAdsLoader.config.containerClass); 325 326 if (containers.length > 0) { 327 // Found containers, initialize 328 WnAdsLoader.init(); 329 } else if (retryCount < maxRetries) { 330 // No containers found, retry after delay 331 setTimeout(function () { 332 initWithRetry(retryCount + 1); 333 }, retryDelay); 334 } 335 }; 336 337 if (document.readyState === 'loading') { 338 document.addEventListener('DOMContentLoaded', function () { 339 // Wait a bit for widgets/components to render 340 setTimeout(function () { 341 initWithRetry(); 342 }, 100); 343 }); 344 } else { 345 // DOM already loaded, wait a bit then init 346 setTimeout(function () { 347 initWithRetry(); 348 }, 100); 349 } 350 351})(window);
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.