PageSourceSearch

https://hoisvcvn.org.vn/themes/tinhte/public/js/ads-loader.js?ver=1786432649

js hoisvcvn.org.vn collected 2026-10-09 03:27:45 UTC 12,240 bytes, 351 lines download raw bytes

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.