PageSourceSearch

https://campersmarts.com/wp-content/plugins/RVL-Newsletter-Modal-P…/src/js/newsletter-modal.js?ver=10.3.0

js campersmarts.com collected 2026-10-04 00:03:08 UTC 10,892 bytes, 371 lines download raw bytes

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.