1/** 2 * SenWWW Ads SDK v1.1 - Fixed image_path support 3 * Automatically loads and displays advertisements from www.seneweb.com/ads/ 4 */ 5class SenWWWAdsSDK { 6 constructor(apiBase = null) { 7 this.apiBase = apiBase || window.location.origin + '/ads'; 8 this.debugMode = false; 9 this.observedAds = new Set(); 10 11 // Auto-initialize when DOM is ready 12 if (document.readyState === 'loading') { 13 document.addEventListener('DOMContentLoaded', () => this.autoInit()); 14 } else { 15 this.autoInit(); 16 } 17 } 18 19 log(...args) { 20 if (this.debugMode) { 21 console.log('[SenWWW Ads]', ...args); 22 } 23 } 24 25 async autoInit() { 26 this.log('Auto-initializing SDK...'); 27 const containers = document.querySelectorAll('[data-senwww-ads-zone]'); 28 29 for (const container of containers) { 30 const zoneName = container.getAttribute('data-senwww-ads-zone'); 31 const cssClass = container.getAttribute('data-senwww-ads-class') || ''; 32 const showEmpty = container.getAttribute('data-senwww-ads-show-empty') !== 'false'; 33 const clear = container.getAttribute('data-senwww-ads-clear') !== 'false'; 34 const limit = container.getAttribute('data-senwww-ads-limit'); 35 36 await this.renderAds(container, zoneName, { cssClass, showEmpty, clear, limit }); 37 } 38 } 39 40 async renderAds(container, zoneName, options = {}) { 41 const { cssClass = '', showEmpty = true, clear = true, limit = null } = options; 42 43 try { 44 this.log(`Loading ads for zone: ${zoneName}${limit ? ' (limit: ' + limit + ')' : ''}`); 45 46 const url = `${this.apiBase}/zone/name/${encodeURIComponent(zoneName)}${limit ? '?limit=' + limit : ''}`; 47 const response = await fetch(url); 48 const data = await response.json(); 49 50 if (!data.success) { 51 throw new Error(data.message || 'Failed to load ads'); 52 } 53 54 if (clear) { 55 container.innerHTML = ''; 56 } 57 58 if (data.ads.length === 0) { 59 if (showEmpty) { 60 container.innerHTML = '<div class="senwww-ads-empty">No ads available for ' + zoneName + '</div>'; 61 } 62 return; 63 } 64 65 // Create ads container 66 const adsContainer = document.createElement('div'); 67 adsContainer.className = `senwww-ads-zone ${cssClass}`.trim(); 68 69 data.ads.forEach(ad => { 70 const adElement = this.createAdElement(ad); 71 adsContainer.appendChild(adElement); 72 }); 73 74 container.appendChild(adsContainer); 75 76 // Setup impression tracking 77 this.setupImpressionTracking(container); 78 79 this.log(`Rendered ${data.ads.length} ads for zone: ${zoneName}`); 80 81 } catch (error) { 82 this.log('Error loading ads:', error.message); 83 if (showEmpty) { 84 container.innerHTML = '<div class="senwww-ads-error">Failed to load ads: ' + error.message + '</div>'; 85 } 86 } 87 } 88 89 createAdElement(ad) { 90 const wrapper = document.createElement('div'); 91 wrapper.className = 'senwww-ad'; 92 wrapper.setAttribute('data-ad-id', ad.id); 93 wrapper.setAttribute('data-zone-id', ad.zone_id); 94 95 let content; 96 switch (ad.type) { 97 case 'html': 98 content = document.createElement('div'); 99 content.className = 'senwww-ad-html'; 100 content.innerHTML = ad.content; 101 break; 102 103 case 'image': 104 content = document.createElement('img'); 105 content.className = 'senwww-ad-image'; 106 content.src = ad.image_path || ad.content; 107 content.alt = ad.title; 108 content.style.maxWidth = '100%'; 109 content.style.height = 'auto'; 110 break; 111 112 case 'text': 113 content = document.createElement('div'); 114 content.className = 'senwww-ad-text'; 115 content.textContent = ad.content; 116 content.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; 117 content.style.color = 'white'; 118 content.style.padding = '15px'; 119 content.style.borderRadius = '6px'; 120 content.style.fontWeight = 'bold'; 121 content.style.textAlign = 'center'; 122 break; 123 124 default: 125 content = document.createElement('div'); 126 content.textContent = ad.content; 127 } 128 129 if (ad.url) { 130 const link = document.createElement('a'); 131 link.className = 'senwww-ad-link'; 132 link.href = ad.url; 133 link.target = '_blank'; 134 link.rel = 'noopener noreferrer';
135 link.style.textDecoration = 'none'; 136 link.style.display = 'block'; 137 link.appendChild(content); 138 139 // Add hover effect 140 link.addEventListener('mouseenter', () => { 141 link.style.opacity = '0.8'; 142 link.style.transform = 'translateY(-1px)'; 143 link.style.transition = 'all 0.2s ease'; 144 }); 145 146 link.addEventListener('mouseleave', () => { 147 link.style.opacity = '1'; 148 link.style.transform = 'translateY(0)'; 149 }); 150 151 link.addEventListener('click', (e) => { 152 this.trackClick(ad.id, ad.zone_id); 153 }); 154 155 wrapper.appendChild(link); 156 } else { 157 wrapper.appendChild(content); 158 } 159 160 return wrapper; 161 } 162 163 setupImpressionTracking(container) { 164 if (!('IntersectionObserver' in window)) { 165 // Fallback: track immediately if no IntersectionObserver 166 this.trackVisibleAds(container); 167 return; 168 } 169 170 const observer = new IntersectionObserver((entries) => { 171 entries.forEach(entry => { 172 if (entry.isIntersecting && entry.intersectionRatio >= 0.5) { 173 const adId = entry.target.getAttribute('data-ad-id'); 174 const zoneId = entry.target.getAttribute('data-zone-id'); 175 176 if (!this.observedAds.has(adId)) { 177 this.observedAds.add(adId); 178 this.trackImpression(parseInt(adId), zoneId ? parseInt(zoneId) : null); 179 observer.unobserve(entry.target); 180 } 181 } 182 }); 183 }, { threshold: 0.5 }); 184 185 container.querySelectorAll('.senwww-ad').forEach(ad => { 186 observer.observe(ad); 187 }); 188 } 189 190 trackVisibleAds(container) { 191 container.querySelectorAll('.senwww-ad').forEach(ad => { 192 const adId = ad.getAttribute('data-ad-id'); 193 const zoneId = ad.getAttribute('data-zone-id'); 194 195 if (!this.observedAds.has(adId)) { 196 this.observedAds.add(adId); 197 this.trackImpression(parseInt(adId), zoneId ? parseInt(zoneId) : null); 198 } 199 }); 200 } 201 202 async trackImpression(adId, zoneId = null) { 203 try { 204 this.log(`Tracking impression for ad ${adId}`); 205 206 const response = await fetch(`${this.apiBase}/track/impression`, { 207 method: 'POST', 208 headers: { 209 'Content-Type': 'application/json', 210 'X-Requested-With': 'XMLHttpRequest', 211 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' 212 }, 213 body: JSON.stringify({ 214 ad_id: adId, 215 zone_id: zoneId 216 }) 217 }); 218 219 if (response.ok) { 220 const data = await response.json(); 221 if (data.success) { 222 this.log(`â Impression tracked for ad ${adId}`); 223 } 224 } 225 } catch (error) { 226 this.log('Error tracking impression:', error.message); 227 } 228 } 229 230 async trackClick(adId, zoneId = null) { 231 try { 232 this.log(`Tracking click for ad ${adId}`); 233 234 const response = await fetch(`${this.apiBase}/track/click`, { 235 method: 'POST', 236 headers: { 237 'Content-Type': 'application/json', 238 'X-Requested-With': 'XMLHttpRequest', 239 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' 240 }, 241 body: JSON.stringify({ 242 ad_id: adId, 243 zone_id: zoneId, 244 referrer: document.referrer, 245 user_agent: navigator.userAgent 246 }) 247 }); 248 249 if (response.ok) { 250 const data = await response.json(); 251 if (data.success) { 252 this.log(`â Click tracked for ad ${adId}`); 253 } 254 } 255 } catch (error) { 256 this.log('Error tracking click:', error.message); 257 } 258 } 259 260 // Public methods for manual control 261 enableDebug() { 262 this.debugMode = true; 263 this.log('Debug mode enabled'); 264 } 265 266 disableDebug() { 267 this.debugMode = false;
268 } 269 270 async refreshZone(zoneName, options = {}) { 271 const containers = document.querySelectorAll(`[data-senwww-ads-zone="${zoneName}"]`); 272 for (const container of containers) { 273 await this.renderAds(container, zoneName, options); 274 } 275 } 276} 277 278// Global instance 279window.senwwwAds = new SenWWWAdsSDK(); 280 281// Export for module usage 282if (typeof module !== 'undefined' && module.exports) { 283 module.exports = SenWWWAdsSDK; 284} 285 286// Add some basic CSS styles 287if (!document.querySelector('#senwww-ads-styles')) { 288 const style = document.createElement('style'); 289 style.id = 'senwww-ads-styles'; 290 style.textContent = ` 291 .senwww-ad { 292 margin: 10px 0; 293 transition: all 0.3s ease; 294 } 295 296 .senwww-ad-link { 297 text-decoration: none; 298 display: block; 299 } 300 301 .senwww-ad-link:hover { 302 opacity: 0.8; 303 transform: translateY(-1px); 304 transition: all 0.2s ease; 305 } 306 307 .senwww-ad-image { 308 max-width: 100%; 309 height: auto; 310 border-radius: 4px; 311 } 312 313 .senwww-ad-html { 314 border-radius: 4px; 315 } 316 317 .senwww-ads-empty { 318 color: #7f8c8d; 319 font-style: italic; 320 text-align: center; 321 padding: 20px; 322 font-size: 14px; 323 } 324 325 .senwww-ads-error { 326 color: #e74c3c; 327 text-align: center; 328 padding: 20px; 329 font-size: 14px; 330 background: #ffeaa7; 331 border-radius: 4px; 332 } 333 `; 334 document.head.appendChild(style); 335} 336 337console.log('SenWWW Ads SDK v1.0 loaded successfully');
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.