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