1/** 2 * Newsletter Modal Handler 3 * 4 * This script manages the newsletter signup modal functionality, including: 5 * - Modal display timing and frequency 6 * - Cookie and session management for display preferences 7 * - Form submission and validation 8 * - Thank you modal display 9 * - Error handling 10 * 11 * Dependencies: 12 * - jQuery 13 * - my_plugin global object containing: 14 * - ajax_url: WordPress AJAX endpoint 15 * - signup_nonce: Security nonce for form submission 16 * - mnp_modal_frequency: Maximum number of times to show modal 17 * - mnp_time_frame: Time period for modal frequency ("day", "week", "month") 18 * 19 * @requires jQuery 20 * @since 10.0.0 21 * @since 10.2.7 Ensure AJAX submit works on pages and show inline thank-you (no overlay) for shortcode. 22 */ 23 24 25// Cookie Management Utility 26const CookieManager = { 27 get: (name) => { 28 const value = `; ${document.cookie}`; 29 const parts = value.split(`; ${name}=`); 30 if (parts.length === 2) return parts.pop().split(';').shift(); 31 return null; 32 }, 33 set: (name, value, days) => { 34 const date = new Date(); 35 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 36 document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`; 37 } 38}; 39 40// Session Management Utility 41const SessionManager = { 42 get: (key) => { 43 try { 44 return sessionStorage.getItem(key); 45 } catch (e) { 46 if (my_plugin && my_plugin.mnp_troubleshooting) { 47 console.error('SessionStorage error:', e); 48 } 49 return null; 50 } 51 }, 52 set: (key, value) => { 53 try { 54 sessionStorage.setItem(key, value); 55 } catch (e) { 56 if (my_plugin && my_plugin.mnp_troubleshooting) { 57 console.error('SessionStorage error:', e); 58 } 59 } 60 } 61}; 62 63jQuery(document).ready(($) => { 64 'use strict'; 65 66 // Guard against undefined dependencies 67 if (typeof my_plugin === 'undefined') { 68 if (window.console && my_plugin && my_plugin.mnp_troubleshooting) { 69 console.error('Required my_plugin configuration object is missing'); 70 } 71 return; 72 } 73 74 // Validate required my_plugin properties 75 const requiredProps = ['ajax_url', 'signup_nonce', 'mnp_modal_frequency', 'mnp_time_frame']; 76 for (const prop of requiredProps) { 77 if (!my_plugin.hasOwnProperty(prop)) { 78 if (my_plugin.mnp_troubleshooting) { 79 console.error(`Required my_plugin property missing: ${prop}`); 80 } 81 return; 82 } 83 } 84 85 /** 86 * Check if current page is a single post 87 */ 88 const checkIsSinglePost = () => { 89 const $body = $('body'); 90 return ( 91 $body.hasClass('single') || 92 $body.hasClass('single-post') || 93 $body.hasClass('postid-' + my_plugin?.post_id) || 94 $body.hasClass('single-format-standard') || 95 $body.attr('class')?.includes('template-single') || 96 my_plugin?.post_type === 'post' 97 ); 98 }; 99 100 const isSinglePost = checkIsSinglePost(); 101 102 103 /** 104 * Cache DOM elements 105 */ 106 const elements = { 107 newsletterBox: $('.mnp-newsletter-box'), 108 modal: $('.mnp-modal'), 109 form: $('.newsletter-form'), 110 thankYouModal: $('.mnp-thank-you-modal'), 111 thankYouContent: $('.mnp-thank-you-content'), 112 thankYouDiv: $('.mnp-thank-you-div'), 113 errorMessage: $('.error-occurred') 114 }; 115 116 // Non-fatal if the modal container is missing on Pages; we still bind submit handler 117 if (!elements.newsletterBox.length && my_plugin.mnp_troubleshooting) { 118 console.warn('Newsletter box not found. Binding submit handler only.'); 119 } 120 121 /** 122 * Modal Display Manager 123 */ 124 class ModalManager { 125 constructor() { 126 this.displayed = false; 127 this.displayCount = parseInt(CookieManager.get('modalDisplayCount')) || 0; 128 this.maxDisplays = parseInt(my_plugin.mnp_modal_frequency) || 1; 129 this.displayDuration = 5000; // 5 seconds 130 this.modalTimer = null; 131 this.setupIntersectionObserver(); 132 this.setupScrollHandler(); 133 } 134 135 calculateExpiration() { 136 switch (my_plugin.mnp_time_frame) { 137 case 'week': 138 return 7; 139 case 'month': 140 return 30; 141 default: // day 142 return 1; 143 } 144 } 145 146 canShowModal() { 147 // Reset session flag on every page load 148 SessionManager.set('modalShown', 'false'); 149 150 // Check display count against max displays 151 if (this.displayCount >= this.maxDisplays) { 152 return false;
153 } 154 155 // Allow modal to show if not already displayed in this session 156 return !this.displayed; 157 } 158 159 setupIntersectionObserver() { 160 const observer = new IntersectionObserver( 161 (entries) => { 162 entries.forEach((entry) => { 163 if (entry.isIntersecting && this.canShowModal()) { 164 this.showModal(); 165 } 166 }); 167 }, 168 { threshold: 0.5 } 169 ); 170 171 if (elements.newsletterBox.length) { 172 observer.observe(elements.newsletterBox[0]); 173 } 174 } 175 176 setupScrollHandler() { 177 $(window).on('scroll', () => { 178 if (!this.displayed) return; 179 180 if (elements.newsletterBox.length) { 181 const rect = elements.newsletterBox[0].getBoundingClientRect(); 182 if (rect.bottom < 0 || rect.top > window.innerHeight) { 183 this.hideModal(); 184 } 185 } 186 }); 187 } 188 189 showModal() { 190 if (this.modalTimer) { 191 clearTimeout(this.modalTimer); 192 } 193 194 elements.modal 195 .removeClass('hide') 196 .addClass('show') 197 .removeAttr('inert'); 198 199 this.displayed = true; 200 this.displayCount++; 201 202 // Update both cookie and session storage 203 CookieManager.set('modalDisplayCount', this.displayCount, this.calculateExpiration()); 204 SessionManager.set('modalShown', 'true'); 205 206 this.modalTimer = setTimeout(() => { 207 this.hideModal(); 208 this.modalTimer = null; 209 }, this.displayDuration); 210 } 211 212 hideModal() { 213 elements.modal 214 .removeClass('show') 215 .addClass('hide') 216 .attr('inert', ''); 217 218 if (this.modalTimer) { 219 clearTimeout(this.modalTimer); 220 this.modalTimer = null; 221 } 222 this.displayed = false; 223 } 224 } 225 226 /** 227 * Form Submission Handler 228 */ 229 class FormHandler { 230 constructor() { 231 this.setupFormListener(); 232 this.setupModalCloseHandlers(); 233 } 234 235 /** 236 * Delegate submit binding so forms work on Pages and dynamic inserts. 237 * @since 10.2.7 238 */ 239 setupFormListener() { 240 // Use delegated listener so dynamically injected forms on Pages work too 241 $(document).on('submit', '.newsletter-form', (e) => { 242 e.preventDefault(); 243 this.handleFormSubmission(e); 244 }); 245 } 246 247 handleFormSubmission(event) { 248 event.preventDefault(); 249 const $form = $(event.target); 250 const $newsletterBox = $form.closest('.mnp-newsletter-box'); 251 252 // Validate email before starting loading state 253 const $emailInput = $form.find('input[type="email"]'); 254 const email = ($emailInput.val() || '').trim(); 255 if (!this.validateEmail(email)) { 256 $form.find('.invalid-email').show(); 257 return; 258 } 259 260 // Dots animation on submit button (@since 10.2.7) 261 const $submit = $form.find('input[type="submit"]'); 262 const originalText = $submit.val() || 'Subscribe'; 263 let dots = 0; let tick = null; 264 const startLoading = () => { 265 $submit.prop('disabled', true); 266 tick = setInterval(() => { 267 dots = (dots + 1) % 4; // 0..3 268 $submit.val('Subscribing' + '.'.repeat(dots)); 269 }, 300); 270 }; 271 const stopLoading = () => { 272 if (tick) clearInterval(tick); 273 $submit.prop('disabled', false).val(originalText); 274 }; 275 startLoading(); 276 277 // Prefer hidden nonce from DOM; fall back to localized values 278 const nonce = $form.find('input[name="newsletter_subscribe_nonce"]').val() 279 || (my_plugin?.nonces && my_plugin.nonces.subscribe) 280 || my_plugin.signup_nonce; 281 282 $.ajax({ 283 url: my_plugin.ajax_url, 284 type: 'POST', 285 data: { 286 action: 'mnp_subscribe_user', 287 email: email, 288 newsletter_subscribe_nonce: nonce 289 }, 290 success: (response) => { 291 if (response.success) { 292 const isPermanentThankYou = $newsletterBox.data('permanent-thank-you'); 293 294 if (isPermanentThankYou) { 295 // Make sure we have all required variables 296 if (!my_plugin.site_logo || !my_plugin.site_name || !my_plugin.thank_you_message) { 297 return; 298 } 299 300 // Replace the entire newsletter box with a thank you message 301 // Inline thank-you only for shortcode usage (@since 10.2.7) 302 const thankYouMessage = ` 303 <div class="mnp-thank-you-content has-text-align-center no-border"> 304 <img src="${my_plugin.site_logo}" 305 class="align-center logo-thank-you" 306 alt="${my_plugin.site_name} logo" 307 loading="lazy"> 308 <h3>${my_plugin.thank_you_message}</h3> 309 </div>`; 310 311 $newsletterBox.html(thankYouMessage).addClass('success'); 312 } else { 313 // Original modal behavior for in-post newsletter 314 if (elements.modal.length) { 315 elements.modal.removeClass('show').addClass('hide'); 316 } 317 if (elements.thankYouModal.length) { 318 elements.thankYouModal.removeClass('hide').addClass('show'); 319 } 320 } 321 } else { 322 // Handle error case 323 const msg = response?.data?.message || 'Subscription failed.'; 324 $form.find('.error-occurred').text(msg).show(); 325 } 326 }, 327 error: (xhr, status, error) => { 328 $form.find('.error-occurred').show(); 329 }, 330 complete: () => { 331 stopLoading(); 332 } 333 }); 334 } 335 336 validateEmail(email) { 337 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 338 return emailRegex.test(email); 339 } 340 341 hideThankYou() { 342 elements.thankYouModal 343 .addClass('hide') 344 .removeClass('show') 345 .attr('inert', ''); 346 347 // Ensure the newsletter box stays hidden 348 elements.newsletterBox.addClass('hide'); 349 } 350 351 352 setupModalCloseHandlers() { 353 $('#mnp-modal-close').on('click', () => this.hideThankYou()); 354 355 $(window).on('click', (event) => { 356 if ($(event.target).is(elements.thankYouModal)) { 357 this.hideThankYou(); 358 } 359 }); 360 } 361 } 362 363 // Initialize managers 364 // Always bind the form handler so Pages and Posts submit via AJAX (@since 10.2.7) 365 new FormHandler(); 366 367 // Only initialize modal behavior on single posts when the modal exists 368 if (elements.modal.length && isSinglePost) { 369 new ModalManager(); 370 } 371});
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.