1/** 2 * Frontend-Skript für den Countdown-Störer 3 * 4 * Dieses Skript liest die durch das Admin-Interface gespeicherten Optionen 5 * (fit711CountDownOptions) und wendet sie im Frontend an. Neben dem 6 * Countdown werden nun auch Hintergrund (einfarbig oder Gradient), ein 7 * benutzerdefinierter Text, Ausrichtungsoptionen und Einstellungen für den 8 * SchlieÃen-Button berücksichtigt. Nach Ablauf kann der Banner wahlweise 9 * ausgeblendet oder durch einen eigenen Text ersetzt werden. 10 */ 11/** 12 * Erweiterte Frontend-Logik für den Countdown-Störer. 13 * 14 * Dieses Skript liest die in den Plugin-Einstellungen gespeicherten Optionen aus 15 * und wendet sie im Frontend an. Unterstützt werden nun FarbigkeitsâIntensitäten, 16 * Gradienten, Einheitenplatzierung, benutzerdefinierte Textpositionen sowie 17 * individuelle Abstände für links/rechts. Alle Farben werden in RGBA umgerechnet 18 * basierend auf der eingestellten Intensität. 19 */ 20(function() { 21 // Wenn keine Optionen definiert sind oder der Banner deaktiviert ist, abbrechen 22 if ( typeof fit711CountDownOptions === 'undefined' || ! fit711CountDownOptions.enabled ) { 23 return; 24 } 25 var options = fit711CountDownOptions; 26 if ( ! options.expiry ) { 27 return; 28 } 29 var expiryDate = new Date( options.expiry ); 30 var bannerEl = document.getElementById( 'fit711-countdown-stoerer' ); 31 if ( ! bannerEl ) return; 32 var contentEl = bannerEl.querySelector( '.fit711-countdown-content' ); 33 var closeBtn = bannerEl.querySelector( '.fit711-countdown-close' ); 34 35 // Wenn eine Ziel-URL angegeben ist, mache den Banner klickbar. Die URL 36 // wird entweder im selben Fenster oder in einem neuen Tab geöffnet, 37 // abhängig von der Option `open_in_new_tab`. Ein Klick auf den Close- 38 // Button darf den Link nicht auslösen. 39 if ( options.target_url && options.target_url.trim() !== '' ) { 40 bannerEl.style.cursor = 'pointer'; 41 bannerEl.addEventListener('click', function(e) { 42 // Nicht ausführen, wenn der Close-Button oder dessen Kind angeklickt wurde 43 if ( e.target && (e.target === closeBtn || (e.target.closest && e.target.closest('.fit711-countdown-close')) ) ) { 44 return; 45 } 46 // Ãffne die Ziel-URL im neuen Tab oder im selben Fenster 47 var url = options.target_url; 48 if ( options.open_in_new_tab ) { 49 window.open(url, '_blank'); 50 } else { 51 window.location.href = url; 52 } 53 }); 54 } 55 56 // Ãberprüfen, ob der Banner auf der aktuellen Seite angezeigt werden soll 57 // 1. URL-Filter: falls target_urls angegeben, muss die aktuelle URL mindestens eine dieser Zeichenfolgen enthalten 58 var shouldShow = true; 59 if ( options.target_urls && options.target_urls.trim() !== '' ) { 60 var patterns = options.target_urls.split(/\n|,/).map(function(p) { return p.trim(); }).filter(Boolean); 61 var currentUrl = window.location.href; 62 shouldShow = patterns.some(function(p) { 63 return currentUrl.indexOf(p) !== -1; 64 }); 65 } 66 // 1b. Ausschluss-Filter: wenn die aktuelle URL eine der exclude_urls enthaelt, NICHT anzeigen (hat Vorrang) 67 if ( shouldShow && options.exclude_urls && options.exclude_urls.trim() !== '' ) { 68 var excludePatterns = options.exclude_urls.split(/\n|,/).map(function(p) { return p.trim(); }).filter(Boolean); 69 var curUrl = window.location.href; 70 if ( excludePatterns.some(function(p) { return curUrl.indexOf(p) !== -1; }) ) { 71 shouldShow = false; 72 } 73 } 74 // 2. Post-Type-Filter: falls ausgewählte Post Types definiert sind 75 if ( shouldShow && options.selected_post_types && options.selected_post_types.length ) { 76 var bodyClasses = document.body.className; 77 shouldShow = options.selected_post_types.some(function(slug) { 78 return bodyClasses.indexOf('post-type-' + slug) !== -1 || 79 bodyClasses.indexOf('single-' + slug) !== -1 || 80 bodyClasses.indexOf('page-' + slug) !== -1; 81 }); 82 } 83 if ( ! shouldShow ) { 84 if ( bannerEl ) { 85 bannerEl.style.display = 'none'; 86 } 87 return; 88 } 89 90 /** 91 * Ermittelt das aktuelle Gerät basierend auf der Fensterbreite und gibt den 92 * entsprechenden CSSâCode aus den gerätespezifischen Einstellungen zurück. 93 * AnschlieÃend wird das CSS in ein <style>-Element eingefügt. Dabei werden 94 * typografische Anführungszeichen in reguläre Anführungszeichen umgewandelt 95 * und durch JSONâEncoding entstandene Escape-Zeichen (\:) entfernt, sodass 96 * gültiges CSS entsteht. Falls kein gerätespezifisches CSS definiert ist, 97 * wird nichts eingefügt. 98 */ 99 function applyDeviceCss() { 100 var deviceStyles = options.device_styles || {}; 101 // Bestimme das Gerät anhand der Fensterbreite. Desktop >= 1025px, 102 // Tablet 601â1024px, Mobil <= 600px. Diese Werte können bei Bedarf 103 // angepasst werden. 104 var width = window.innerWidth; 105 var device; 106 if (width <= 600) { 107 device = 'mobile'; 108 } else if (width <= 1024) { 109 device = 'tablet'; 110 } else { 111 device = 'desktop'; 112 } 113 var css = ''; 114 if (deviceStyles[device] && deviceStyles[device].customCss) { 115 css = deviceStyles[device].customCss; 116 // Ersetze Escape-Sequenzen für Doppelpunkte, die beim Einfügen über 117 // wp_localize_script entstehen können (\: -> :). 118 css = css.replace(/\\:/g, ':'); 119 // Konvertiere typografische Anführungszeichen in gerade Anführungszeichen 120 // damit JavaScript den String korrekt interpretieren kann. 121 css = css.replace(/[\u201C\u201D\u201E\u201F\u2039\u203A\u00AB\u00BB]/g, '"'); 122 } 123 if (css) { 124 // Style-Element nur einmal einfügen. Wenn es bereits existiert, wird der 125 // Inhalt aktualisiert. Dadurch verhindern wir, dass sich mehrere 126 // <style>-Tags anhäufen, wenn applyDeviceCss mehrfach aufgerufen wird. 127 var existing = document.getElementById('fit711-countdown-device-css'); 128 if (!existing) { 129 existing = document.createElement('style'); 130 existing.type = 'text/css'; 131 existing.id = 'fit711-countdown-device-css'; 132 document.head.appendChild(existing); 133 } 134 existing.innerHTML = css; 135 } 136 } 137 138 // Füge gerätespezifisches CSS hinzu, falls definiert 139 applyDeviceCss(); 140 141 // Prüfen, ob der Banner aktuell ausgeblendet werden soll (z.â¯B. nach SchlieÃen) 142 var hideUntilTs = localStorage.getItem( 'fit711_countdown_hide_until' ); 143 if ( hideUntilTs && parseInt( hideUntilTs, 10 ) > Date.now() ) { 144 bannerEl.style.display = 'none'; 145 return; 146 } 147 148 // Hilfsfunktion: Hex nach RGBA unter Berücksichtigung der Intensität (0â100) 149 function hexToRgba(hex, intensity) { 150 if (!hex) return hex; 151 // Wenn bereits ein rgb/rgba-Wert übergeben wurde, diesen direkt zurückgeben 152 if (typeof hex === 'string' && hex.trim().match(/^rgba?\(/)) { 153 return hex; 154 } 155 // Entferne #:e 156 var h = hex.replace('#', ''); 157 if (h.length === 3) { 158 h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; 159 } 160 var r = parseInt(h.substring(0, 2), 16); 161 var g = parseInt(h.substring(2, 4), 16); 162 var b = parseInt(h.substring(4, 6), 16); 163 var alpha; 164 if ( typeof intensity === 'number' && ! isNaN(intensity) ) { 165 alpha = Math.max(0, Math.min(1, intensity / 100)); 166 } else { 167 alpha = 1; 168 } 169 return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; 170 } 171 172 // Wende Hintergrund an 173 var styles = options.global_styles || {}; 174 if ( styles.backgroundType === 'gradient' ) { 175 var c1 = hexToRgba(styles.gradientStart || '#333333', parseInt(styles.gradientStartIntensity, 10)); 176 var c2 = hexToRgba(styles.gradientMiddle || '#666666', parseInt(styles.gradientMiddleIntensity, 10)); 177 var c3 = hexToRgba(styles.gradientEnd || '#999999', parseInt(styles.gradientEndIntensity, 10)); 178 bannerEl.style.background = 'linear-gradient(to right, ' + c1 + ', ' + c2 + ', ' + c3 + ')'; 179 } else { 180 var bg = hexToRgba(styles.backgroundColor || '#333333', parseInt(styles.backgroundColorIntensity, 10)); 181 bannerEl.style.background = bg; 182 } 183 // Einheitenfarbe (Units) inkl. Intensität 184 var unitColor = hexToRgba(styles.textColor || '#ffffff', parseInt(styles.textColorIntensity, 10)); 185 contentEl.style.color = unitColor; 186 // SchriftgröÃe der Ziffern wird am numbersEl separat gesetzt 187 // Richte den Inhaltscontainer ein 188 contentEl.style.display = 'flex'; 189 contentEl.style.alignItems = 'center'; 190 contentEl.style.justifyContent = 'center'; 191 192 // Lege separate Container für Zahlen und benutzerdefinierten Text an 193 var numbersEl = document.createElement('div'); 194 numbersEl.className = 'fit711-countdown-numbers'; 195 // SchriftgröÃe für Ziffern 196 if ( styles.fontSize ) { 197 numbersEl.style.fontSize = styles.fontSize + 'px'; 198 } 199 // Der messageEl wird nur erstellt, wenn ein benutzerdefinierter Text gesetzt ist 200 var messageEl = null; 201 if ( styles.customText && styles.customText.trim() !== '' ) { 202 messageEl = document.createElement('div'); 203 messageEl.className = 'fit711-countdown-message';
204 // HTML erlauben: setze innerHTML statt textContent 205 messageEl.innerHTML = styles.customText; 206 // Farbe und GröÃe des Texts 207 var msgColor = hexToRgba(styles.customTextColor || '#ffffff', parseInt(styles.customTextColorIntensity, 10)); 208 messageEl.style.color = msgColor; 209 if ( styles.customTextSize ) { 210 messageEl.style.fontSize = styles.customTextSize + 'px'; 211 } 212 } 213 // Bereite Inhaltscontainer basierend auf Textposition vor 214 contentEl.innerHTML = ''; 215 // Setze Flex-Richtung abhängig von Position 216 var pos = styles.customTextPosition || 'below'; 217 var margin = parseInt(styles.customTextMargin || 0, 10); 218 if ( pos === 'left' ) { 219 contentEl.style.flexDirection = 'row'; 220 contentEl.appendChild(messageEl ? messageEl : document.createDocumentFragment()); 221 contentEl.appendChild(numbersEl); 222 if ( messageEl ) { 223 messageEl.style.marginRight = margin + 'px'; 224 } 225 } else if ( pos === 'right' ) { 226 contentEl.style.flexDirection = 'row'; 227 contentEl.appendChild(numbersEl); 228 contentEl.appendChild(messageEl ? messageEl : document.createDocumentFragment()); 229 if ( messageEl ) { 230 messageEl.style.marginLeft = margin + 'px'; 231 } 232 } else if ( pos === 'above' ) { 233 contentEl.style.flexDirection = 'column'; 234 if ( messageEl ) { 235 contentEl.appendChild(messageEl); 236 messageEl.style.marginBottom = margin + 'px'; 237 } 238 contentEl.appendChild(numbersEl); 239 } else { // below 240 contentEl.style.flexDirection = 'column'; 241 contentEl.appendChild(numbersEl); 242 if ( messageEl ) { 243 messageEl.style.marginTop = margin + 'px'; 244 contentEl.appendChild(messageEl); 245 } 246 } 247 248 // Stil des SchlieÃen-Buttons 249 if ( closeBtn ) { 250 // Farbe inkl. Intensität 251 if ( styles.closeButtonColor ) { 252 var xColor = hexToRgba(styles.closeButtonColor, parseInt(styles.closeButtonColorIntensity, 10)); 253 closeBtn.style.color = xColor; 254 } 255 if ( styles.closeButtonSize ) { 256 closeBtn.style.fontSize = styles.closeButtonSize + 'px'; 257 } 258 if ( styles.closeButtonWeight ) { 259 closeBtn.style.fontWeight = styles.closeButtonWeight; 260 } 261 // Position: Reset Standard und dann setzen 262 closeBtn.style.position = 'absolute'; 263 // horizontale Position 264 if ( styles.closeButtonPositionHorizontal === 'left' ) { 265 closeBtn.style.left = '0.5rem'; 266 closeBtn.style.right = ''; 267 } else { 268 closeBtn.style.right = '0.5rem'; 269 closeBtn.style.left = ''; 270 } 271 // vertikale Position 272 if ( styles.closeButtonPositionVertical === 'middle' ) { 273 closeBtn.style.top = '50%'; 274 closeBtn.style.bottom = ''; 275 closeBtn.style.transform = 'translateY(-50%)'; 276 } else if ( styles.closeButtonPositionVertical === 'bottom' ) { 277 closeBtn.style.bottom = '0.5rem'; 278 closeBtn.style.top = ''; 279 closeBtn.style.transform = ''; 280 } else { 281 closeBtn.style.top = '0.5rem'; 282 closeBtn.style.bottom = ''; 283 closeBtn.style.transform = ''; 284 } 285 // Close-Button Event 286 closeBtn.addEventListener('click', function() { 287 bannerEl.style.display = 'none'; 288 var days = parseInt(options.hide_for_days || 0, 10); 289 var hideUntil = new Date(); 290 hideUntil.setDate(hideUntil.getDate() + days); 291 localStorage.setItem('fit711_countdown_hide_until', hideUntil.getTime()); 292 }); 293 } 294 295 // Funktion zur Aktualisierung des Countdowns und Darstellung der Einheiten 296 function updateCountdown() { 297 var now = new Date(); 298 var diff = expiryDate.getTime() - now.getTime(); 299 if ( diff <= 0 ) { 300 clearInterval(interval); 301 if ( options.expire_behavior === 'custom_text' && options.expire_text ) { 302 numbersEl.innerHTML = options.expire_text; 303 // benutzerdefinierte Nachricht ausblenden, falls vorhanden 304 if ( messageEl ) { 305 messageEl.style.display = 'none'; 306 } 307 } else { 308 bannerEl.style.display = 'none'; 309 } 310 return; 311 } 312 var days = Math.floor(diff / (1000 * 60 * 60 * 24)); 313 var hours = Math.floor((diff / (1000 * 60 * 60)) % 24); 314 var mins = Math.floor((diff / (1000 * 60)) % 60); 315 var secs = Math.floor((diff / 1000) % 60); 316 317 // Berechne RGBA für Ziffern und Einheiten 318 var numColor = hexToRgba(styles.numberColor || '#ff0000', parseInt(styles.numberColorIntensity, 10)); 319 var unitColorNow = unitColor; // use previously calculated unit color 320 321 // Erzeuge HTML abhängig von der Einheitenposition 322 if ( styles.unitsPosition === 'below' ) { 323 // Jedes Segment wird vertikal angeordnet (Zahl über Einheit) 324 var seg = function(value, label) { 325 return '<div style="display:inline-flex; flex-direction:column; align-items:center; margin-right:8px;">' + 326 '<span style="color:' + numColor + ';" class="fit711-countdown-number">' + value + '</span>' + 327 '<span style="color:' + unitColorNow + '; font-size:0.8em;">' + label + '</span>' + 328 '</div>'; 329 }; 330 numbersEl.innerHTML = 331 seg(days, 'Tage') + 332 seg(hours, 'Std') + 333 seg(mins, 'Min') + 334 seg(secs, 'Sek'); 335 } else {
336 // Inline-Anordnung: Zahl gefolgt von Einheit 337 numbersEl.innerHTML = 338 '<span style="color:' + numColor + ';" class="fit711-countdown-number">' + days + '</span> <span style="color:' + unitColorNow + ';">Tage</span> ' + 339 '<span style="color:' + numColor + ';" class="fit711-countdown-number">' + hours + '</span> <span style="color:' + unitColorNow + ';">Std</span> ' + 340 '<span style="color:' + numColor + ';" class="fit711-countdown-number">' + mins + '</span> <span style="color:' + unitColorNow + ';">Min</span> ' + 341 '<span style="color:' + numColor + ';" class="fit711-countdown-number">' + secs + '</span> <span style="color:' + unitColorNow + ';">Sek</span>'; 342 } 343 } 344 345 // Starte den Countdown 346 updateCountdown(); 347 var interval = setInterval(updateCountdown, 1000); 348})();
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.