1function Divecounter() { 2 // Number of pages to visit until interstitial is displayed 3 const interstitialPageCount = 3; 4 // Number of days after the interstitial was last displayed before it can be displayed again 5 const interstitialMinDays = 3; 6 // Cookie names for the three-cookie split (Osano compliance) 7 const coreCookieName = '_divecounter_core'; 8 const personalizationCookieName = '_divecounter_personalization'; 9 const marketingCookieName = '_divecounter_marketing'; 10 const cookieExpire = 365; 11 // Core data (cookie classified in Osano as "Essential") 12 let coreData = { 13 page_views: 0, 14 last_shown: 0, 15 first_page_view: 0, 16 is_staff: false, 17 }; 18 // Personalization data (cookie classified in Osano as "Personalization") 19 let personalizationData = { 20 is_subscriber: false, 21 }; 22 // Marketing data (cookie classified in Osano as "Marketing") 23 let marketingData = { 24 is_subscriber: false, 25 }; 26 27 this.testing = function() { 28 // Disable interstitial testing params if configured (e.g., for WardsAuto to prevent bot signups) 29 if (this.getOption('disableInterstitialTestingParams')) { 30 return false; 31 } 32 return location.search.indexOf('show_hit_ad') !== -1 || location.search.indexOf('show_interstitial') !== -1; 33 }; 34 35 this.resetCookie = function() { 36 // Expiration in past deletes cookies. 37 setCookie(coreCookieName, '', -100);
38 setCookie(personalizationCookieName, '', -100); 39 setCookie(marketingCookieName, '', -100); 40 // Also delete old _divecounter cookie if it exists. TODO: remove refs to old _divecounter cookie after 5/1/2027 41 setCookie('_divecounter', '', -100); 42 }; 43 44 this.countPageViews = function() { 45 if (coreData.page_views === 0) { 46 coreData.first_page_view = (new Date()).getTime(); 47 } 48 coreData.page_views++; 49 return coreData.page_views; 50 }; 51 52 this.canShow = function(pageCount = interstitialPageCount, minDays = interstitialMinDays) { 53 // Check personalization data to see if user is a subscriber 54 if (personalizationData.is_subscriber) { 55 return false; 56 } 57 // Interstitial is displayed after 'pageCount' pages, no less than 'minDays' days from the last time it was displayed 58 if (coreData.page_views > 0 && coreData.page_views % pageCount === 0) { 59 if (!coreData.last_shown || (new Date()).getTime() - coreData.last_shown >= minDays * 24 * 60 * 60 * 1000) { 60 return true; 61 } 62 } 63 return false; 64 }; 65 66 this.markShown = function() { 67 coreData.last_shown = (new Date()).getTime(); 68 }; 69 70 // Helper function to check Osano consent state 71 const hasOsanoConsent = function(category) { 72 // If Osano isn't loaded, conservatively assume no consent 73 // Optional chaining returns undefined if any part of the chain is undefined 74 const consent = window.Osano?.cm?.getConsent?.(); 75 // Osano uses 'ACCEPT' / 'DENY' strings, not booleans 76 return consent?.[category] === 'ACCEPT'; 77 }; 78 79 this.markSubscriber = function(valueIn) { 80 // Typecast to bool with default value of true. 81 const value = (typeof valueIn === 'undefined') ? true : !!valueIn; 82 83 // Only set personalization if user has consented to Personalization 84 if (hasOsanoConsent('PERSONALIZATION')) { 85 personalizationData.is_subscriber = value; 86 } else { 87 personalizationData.is_subscriber = false; 88 } 89 90 // Only set marketing if user has consented to Marketing 91 if (hasOsanoConsent('MARKETING')) { 92 marketingData.is_subscriber = value; 93 } else { 94 marketingData.is_subscriber = false; 95 } 96 }; 97 98 this.markStaff = function(valueIn) { 99 // Typecast to bool with default value of true. 100 const value = (typeof valueIn === 'undefined') ? true : !!valueIn; 101 coreData.is_staff = value; 102 }; 103 104 this.save = function() { 105 writeCoreData(); 106 writePersonalizationData(); 107 writeMarketingData(); 108 }; 109 110 // Accessor methods for specific cookie data 111 this.getCoreData = function() { 112 return coreData; 113 }; 114 115 this.getPersonalizationData = function() { 116 return personalizationData; 117 }; 118 119 this.getMarketingData = function() { 120 return marketingData; 121 }; 122 123 this.getOption = function(key) { 124 const defaults = { 125 showSignupAd: false, 126 showWhichAd: false, /** "original" or "choice" **/ 127 markStaff: false, 128 }; 129 if ( (typeof divecounter_settings !== 'undefined') && (key in divecounter_settings) ) { 130 return divecounter_settings[key]; 131 } 132 return defaults[key]; 133 }; 134 135 // The purpose of this method is to make debugging easier by printing a string that consists of 136 // cookie values setup in `divecounter.js` and screen dimensions 137 this.prettyPrint = function() { 138 let s = ''; 139 140 // Core data 141 for (const core_key in coreData) { 142 if (!coreData.hasOwnProperty(core_key)) continue; 143 let core_value = coreData[core_key]; 144 // Check for keys to convert to date strings. 145 if (['first_page_view', 'last_shown'].indexOf(core_key) >= 0) { 146 if (parseInt(core_value, 10) > 0) { 147 core_value = (new Date(parseInt(core_value, 10))).toString(); // JS timestamp to string. 148 } else { 149 core_value = ''; 150 } 151 } 152 s += core_key + ': ' + core_value + '\n'; 153 } 154 155 // Personalization data 156 s += 'is_subscriber (personalization): ' + personalizationData.is_subscriber + '\n'; 157 158 // Marketing data 159 s += 'is_subscriber (marketing): ' + marketingData.is_subscriber + '\n'; 160 161 // Add in bonus stats not technically part of divecounter data. 162 s += 'initial referrer: ' + getCookie('_referrer') + '\n';
163 s += 'screen dimensions: ' + window.screen.availWidth.toString() + 'x' + window.screen.availHeight.toString() + '\n'; 164 165 return s.trim(); 166 }; 167 168 const setCookie = function(c_name, value, expiredays) { 169 const exdate = new Date(); 170 exdate.setDate(exdate.getDate() + expiredays); 171 document.cookie = c_name + '=' + escape(value) + 172 ((expiredays === null) ? '' : ';expires=' + exdate.toUTCString()) + '; path=/'; 173 }; 174 175 const getCookie = function(c_name) { 176 let c_start; 177 let c_end; 178 if (document.cookie.length > 0) { 179 c_start = document.cookie.indexOf(c_name + '='); 180 if (c_start !== -1) { 181 c_start = c_start + c_name.length + 1; 182 c_end = document.cookie.indexOf(';', c_start); 183 if (c_end === -1) c_end = document.cookie.length; 184 return unescape(document.cookie.substring(c_start, c_end)); 185 } 186 } 187 return ''; 188 }; 189 190 // Core cookie read/write functions 191 const readCoreData = function() { 192 const c = getCookie(coreCookieName); 193 if (c !== '') { 194 const arr = c.split('\t'); 195 coreData.page_views = arr[0]; 196 coreData.last_shown = arr[1]; 197 if (arr.length > 2) { 198 coreData.first_page_view = arr[2]; 199 } 200 if (arr.length > 3) { 201 coreData.is_staff = !!parseInt(arr[3], 10); 202 } 203 } 204 }; 205 206 const buildCoreCookie = function() { 207 const arr = [ 208 coreData.page_views, 209 coreData.last_shown, 210 coreData.first_page_view, 211 coreData.is_staff ? 1 : 0, 212 ]; 213 return arr.join('\t'); 214 }; 215 216 const writeCoreData = function() { 217 setCookie(coreCookieName, buildCoreCookie(), cookieExpire); 218 }; 219 220 // Personalization cookie read/write functions 221 const readPersonalizationData = function() { 222 const c = getCookie(personalizationCookieName); 223 if (c !== '') { 224 personalizationData.is_subscriber = !!parseInt(c, 10); 225 } 226 }; 227 228 const writePersonalizationData = function() { 229 setCookie(personalizationCookieName, personalizationData.is_subscriber ? '1' : '0', cookieExpire); 230 }; 231 232 // Marketing cookie read/write functions 233 const readMarketingData = function() { 234 const c = getCookie(marketingCookieName); 235 if (c !== '') { 236 marketingData.is_subscriber = !!parseInt(c, 10); 237 } 238 }; 239 240 const writeMarketingData = function() { 241 setCookie(marketingCookieName, marketingData.is_subscriber ? '1' : '0', cookieExpire); 242 }; 243 244 // Auto-load all cookies on initialization 245 readCoreData(); 246 readPersonalizationData(); 247 readMarketingData(); 248 249 // Deleting old _divecounter cookie on first load. TODO: Remove any _divecounter cookie code after 5/1/2027 250 if (getCookie('_divecounter') !== '') { 251 setCookie('_divecounter', '', -100); 252 } 253} 254 255 256function divecounter_close_ad() { 257 $.modal.close(); 258 // adds back in scrolling to body 259 $('body').removeClass('no-scroll'); 260} 261 262function isAtLeastOneNewsletterSelected() { 263 return $('#newsletter-list-section li input:checked').length > 0; 264} 265 266function isSignupWithMultiPub() { 267 return $('#newsletter-list-section li input').length > 0; 268} 269 270function isNewsletterSelectionValid() { 271 return isSignupWithMultiPub() ? isAtLeastOneNewsletterSelected() : true; 272} 273 274function isUserConsentChecked() { 275 return $('#interstitial-consent-container input:checked').length > 0; 276} 277 278function windowLargeEnough() { 279 return $(window).width() > 640 && $(window).height() > 640; 280} 281 282function divecounter_open_ad() { 283 // Figure out if window is wide/high enough to show a prestitial 284 if (windowLargeEnough()) { 285 $(function() { 286 var $form = $('#signup-inter').find('form'); 287 $form.find('.newsletter-partner').hide(); // hide all partnership info, see #1029 288 var current_dialog; 289 $('#signup-inter').modal({ 290 minWidth: 400, 291 maxWidth: 600, 292 minHeight: 180, 293 autoResize: true, 294 autoPosition: true, 295 overlayClose: false, 296 focus: false, 297 opacity: 80, 298 closeHTML: `<a href="#" title="${window.pgettext('verb', 'Close')}" class="modal-close"></a>`, 299 onClose: divecounter_close_ad, 300 // force resize of box. See https://stackoverflow.com/questions/1407059/jquery-simplemodal-dynamic-height 301 onShow: function(dialog) { 302 $('#interstitial-error').hide(); 303 current_dialog = dialog; 304 // prevents scrolling on body when modal is open 305 $('body').addClass('no-scroll'); 306 dialog.container.css('height', 'auto'); 307 $.modal.setContainerDimensions(); 308 $.modal.setPosition(); 309 }, 310 }); 311 312 313 // add email checking -- see email_check_js.html 314 $form.find('input.email').on('blur', function() { 315 checkEmailAndRespond(this); // eslint-disable-line no-undef 316 }); 317 318 $form.find('input.email').focus(); 319 if (current_dialog !== 'undefined') { 320 $(current_dialog.container).css('height', 'auto'); 321 } 322 323 // Validation for submit. 324 $('#signup-inter form').submit(function(event) { 325 var email_regex = new RegExp('[^@]+@[^@]+[\.][^@]+'); 326 var email_value = $(this.email).val(); 327 var match_test = email_regex.test(email_value);
328 var $interstitialError = $('#interstitial-error'); 329 var error_msg = ''; 330 331 $interstitialError.hide(); 332 $(this).find('input[type=email]').removeClass('form-error__highlight'); 333 334 if (match_test && isNewsletterSelectionValid() && isUserConsentChecked()) { 335 return true; 336 } else if (!match_test) { 337 error_msg = window.gettext('That is not a valid email address. Please enter a valid email.'); 338 $(this).find('input[type=email]').addClass('form-error__highlight'); 339 } else if (!isNewsletterSelectionValid() && !isUserConsentChecked()) { 340 error_msg = window.gettext('You must select at least one newsletter and agree to our Terms of Use and Privacy Policy.'); 341 } else if (!isUserConsentChecked()) { 342 error_msg = window.gettext('You must agree to our Terms of Use and Privacy Policy.'); 343 } else if (!isNewsletterSelectionValid()) { 344 error_msg = window.gettext('Please select at least one newsletter.'); 345 } 346 347 $interstitialError.text(error_msg); 348 $interstitialError.show(); 349 // force resize of box. See https://stackoverflow.com/questions/1407059/jquery-simplemodal-dynamic-height 350 $('#simplemodal-container').css({ 351 'height': 'auto', 352 'max-height': '90%', 353 }); 354 // Make sure the content is scrollable if it exceeds the container height 355 $('.simplemodal-wrap').css('overflow', 'scroll'); 356 $.modal.setContainerDimensions(); 357 $.modal.setPosition(); 358 event.preventDefault(); 359 return false; 360 }); 361 362 // wait a moment and then allow user to close by clicking on the background 363 setTimeout(function() { $('.simplemodal-overlay').click($.modal.close); }, 750); 364 }); 365 } 366} 367 368var divecounter = new Divecounter(); 369 370divecounter.countPageViews(); 371 372if (window.location.search.indexOf('utm_source=Sailthru') !== -1) { 373 // if they came from a newsletter, assume they are a subscriber 374 divecounter.markSubscriber(); 375} 376 377if (divecounter.getOption('markStaff')) { 378 divecounter.markStaff(); 379} 380 381if (divecounter.getOption('showSignupAd') || divecounter.testing()) { 382 if (divecounter.canShow() || divecounter.testing()) { 383 // Use a 50 ms interval to detect whether prestitial.js has determined whether the 384 // page is prestitial eligible or if the prestitial ad was empty. Interstitial should display only if 385 // prestitial is not displaying - prestitial should always have preference. 386 var prestitial_sniffer = setInterval(function() { 387 if (typeof prestitial_ad_is_empty === 'undefined' || prestitial_ad_is_empty === true) { // eslint-disable-line no-undef 388 clearInterval(prestitial_sniffer); 389 divecounter_open_ad(); 390 // Don't set cookies if we're testing 391 if (!divecounter.testing()) { 392 divecounter.markShown(); 393 divecounter.save(); 394 } 395 } else if (typeof prestitial_ad_is_loaded !== 'undefined' && prestitial_ad_is_loaded === true) { // eslint-disable-line no-undef 396 // If the prestitial loads, we can stop sniffing 397 clearInterval(prestitial_sniffer); 398 clearInterval(window.prestitial_sniffer_clear); 399 } 400 // Make sure prestitial_ads exists and we only set the timeout once 401 if (typeof window.prestitialAds !== 'undefined' && typeof window.prestitial_sniffer_clear === 'undefined') { 402 // Stop looking for the prestitial after the prescribed timeout + 1 second of flex 403 window.prestitial_sniffer_clear = setTimeout( 404 function() { 405 clearInterval(prestitial_sniffer); 406 }, 407 3900 // this number is 2900 from prestitial.html + 1000 408 ); // eslint-disable-line no-undef 409 } 410 }, 50); 411 } 412} 413 414divecounter.save(); 415 416// 417// Following is not actually stored in divecounter cookier (yet) 418// 419 420// Save initial referrer 421(function() { 422 function createCookie(name, value, days) { 423 if (days) { 424 var date = new Date(); 425 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 426 var expires = '; expires=' + date.toGMTString(); 427 } else { 428 var expires = ''; // eslint-disable-line no-redeclare 429 } 430 document.cookie = name + '=' + value + expires + '; path=/'; 431 } 432 433 function readCookie(name) { 434 var nameEQ = name + '='; 435 var ca = document.cookie.split(';'); 436 for (var i = 0; i < ca.length; i++) { 437 var c = ca[i]; 438 while (c.charAt(0) === ' ') c = c.substring(1, c.length); 439 if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); 440 } 441 return null; 442 } 443 444 // save referrer if doesn't already exist 445 if (!readCookie('_referrer')) { 446 // get referring page unless it's unavailable, then grab current (i.e. first) page 447 var referrer = document.referrer ? document.referrer : window.location; 448 // limit length 449 if (referrer.length > 150) { 450 referrer = referrer.slice(0, 150); 451 } 452 createCookie('_referrer', referrer); 453 } 454})();
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.