1/** 2 * OTT Popup â front-end behavior: view/conversion tracking, modal/bar close & 3 * dismiss, and bottom-bar height sync. 4 * 5 * A view is counted once per session (sessionStorage) and only when the popup 6 * is actually visible (so device-hidden or dismissed popups are not counted). 7 * A conversion is counted when the CTA is clicked. The tallest bottom-bar 8 * height is published to --ottpopup-bar-h so themes can offset their footer. 9 */ 10( function () { 11 'use strict'; 12 13 if ( typeof window.ottPopupConfig === 'undefined' || ! window.ottPopupConfig.restUrl ) { 14 return; 15 } 16 17 var config = window.ottPopupConfig; 18 19 /** 20 * Sends an event to the REST API. 21 * 22 * @param {number} id Popup ID. 23 * @param {string} type 'view' or 'conversion'. 24 * @param {string} button Optional CTA button: 'primary' or 'secondary'. 25 */ 26 function track( id, type, button ) { 27 id = parseInt( id, 10 ); 28 if ( ! id || ( type !== 'view' && type !== 'conversion' ) ) { 29 return; 30 } 31 32 var data = { id: id, type: type, nonce: config.nonce || '', token: config.token || '' }; 33 if ( button === 'primary' || button === 'secondary' ) { 34 data.button = button; 35 } 36 37 var payload = JSON.stringify( data ); 38 39 // sendBeacon survives navigation (clicking the CTA link). 40 if ( navigator.sendBeacon ) { 41 try { 42 var blob = new Blob( [ payload ], { type: 'application/json' } ); 43 if ( navigator.sendBeacon( config.restUrl, blob ) ) { 44 return; 45 } 46 } catch ( e ) { 47 // Fall through to fetch below. 48 } 49 } 50 51 fetch( config.restUrl, { 52 method: 'POST', 53 headers: { 54 'Content-Type': 'application/json', 55 'X-WP-Nonce': config.nonce || '', 56 'X-OTT-Token': config.token || '' 57 }, 58 body: payload, 59 keepalive: true, 60 credentials: 'same-origin' 61 } ).catch( function () {} ); 62 } 63 64 /** 65 * Counts a view once per session for the given ID. 66 * 67 * @param {string} id Popup ID. 68 */ 69 function trackViewOnce( id ) { 70 var key = 'ottpopup_viewed_' + id; 71 try { 72 if ( window.sessionStorage && sessionStorage.getItem( key ) ) { 73 return; 74 } 75 if ( window.sessionStorage ) { 76 sessionStorage.setItem( key, '1' ); 77 } 78 } catch ( e ) { 79 // No sessionStorage â count anyway. 80 } 81 track( id, 'view' ); 82 } 83 84 function dismissKey( id ) { 85 return 'ottpopup_closed_' + id; 86 } 87 88 function isDismissed( id ) { 89 try { 90 return !! ( window.sessionStorage && sessionStorage.getItem( dismissKey( id ) ) ); 91 } catch ( e ) { 92 return false; 93 } 94 } 95 96 function setDismissed( id ) { 97 try { 98 if ( window.sessionStorage ) { 99 sessionStorage.setItem( dismissKey( id ), '1' ); 100 } 101 } catch ( e ) {} 102 } 103 104 /** 105 * Whether the element is currently rendered (not display:none / hidden). 106 * Works for position:fixed elements (offsetParent is null for those). 107 * 108 * @param {Element} el Element. 109 * @return {boolean} 110 */ 111 function isVisible( el ) { 112 var s = window.getComputedStyle( el ); 113 return s.display !== 'none' && s.visibility !== 'hidden'; 114 } 115 116 function closePopup( el ) { 117 var id = el.getAttribute( 'data-ottpopup-id' ); 118 // Only remember the dismissal when the popup opted in; otherwise it 119 // reappears on every page view (attribute is "0"). 120 if ( id && el.getAttribute( 'data-ottpopup-remember' ) !== '0' ) { 121 setDismissed( id ); 122 } 123 el.classList.add( 'ott-popup-is-closed' ); 124 syncBarHeight(); 125 } 126 127 /** 128 * Publishes the tallest visible bottom-bar height to --ottpopup-bar-h so 129 * themes can offset their footer below the bar. 130 */ 131 function syncBarHeight() { 132 var bars = document.querySelectorAll( '.ott-popup--bottom' ); 133 var height = 0; 134 135 Array.prototype.forEach.call( bars, function ( el ) { 136 if ( isVisible( el ) && ! el.classList.contains( 'ott-popup-is-closed' ) ) { 137 height = Math.max( height, el.offsetHeight ); 138 } 139 } ); 140 141 document.documentElement.style.setProperty( '--ottpopup-bar-h', height + 'px' ); 142 } 143 144 function init() { 145 var popups = document.querySelectorAll( '[data-ottpopup-id]' ); 146 147 // Hide popups dismissed earlier this session. 148 Array.prototype.forEach.call( popups, function ( el ) { 149 var id = el.getAttribute( 'data-ottpopup-id' ); 150 if ( isDismissed( id ) ) { 151 el.classList.add( 'ott-popup-is-closed' ); 152 } 153 } ); 154 155 syncBarHeight(); 156 157 if ( window.addEventListener ) { 158 window.addEventListener( 'resize', syncBarHeight ); 159 window.addEventListener( 'load', syncBarHeight ); 160 } 161 162 // Count views â only for popups that are actually visible right now. 163 Array.prototype.forEach.call( popups, function ( el ) { 164 var id = el.getAttribute( 'data-ottpopup-id' ); 165 if ( ! el.classList.contains( 'ott-popup-is-closed' ) && isVisible( el ) ) { 166 trackViewOnce( id ); 167 } 168 } ); 169 170 // Clicks: close button, overlay backdrop, CTA conversion. 171 document.addEventListener( 'click', function ( event ) { 172 if ( ! event.target.closest ) { 173 return; 174 } 175 176 var closeBtn = event.target.closest( '[data-ottpopup-close]' ); 177 if ( closeBtn ) { 178 var pop = closeBtn.closest( '[data-ottpopup-id]' ); 179 if ( pop ) { 180 closePopup( pop ); 181 } 182 return; 183 } 184 185 // Click on the overlay backdrop itself (not its content) closes it. 186 if ( event.target.classList && event.target.classList.contains( 'ott-popup-overlay' ) ) { 187 closePopup( event.target ); 188 return; 189 } 190 191 // CTA: data attribute carries the popup ID directly. 192 var cta = event.target.closest( '[data-ottpopup-cta]' ); 193 if ( cta ) { 194 track( cta.getAttribute( 'data-ottpopup-cta' ), 'conversion', cta.getAttribute( 'data-ottpopup-button' ) ); 195 } 196 } ); 197 198 // Escape closes any open overlay. 199 document.addEventListener( 'keydown', function ( event ) { 200 if ( event.key !== 'Escape' ) { 201 return; 202 } 203 var overlays = document.querySelectorAll( '.ott-popup-overlay' ); 204 Array.prototype.forEach.call( overlays, function ( el ) { 205 if ( ! el.classList.contains( 'ott-popup-is-closed' ) ) { 206 closePopup( el ); 207 } 208 } ); 209 } ); 210 } 211 212 if ( document.readyState === 'loading' ) { 213 document.addEventListener( 'DOMContentLoaded', init ); 214 } else { 215 init(); 216 } 217} )();
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.