PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / trunk
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar vtrunk
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
notificationx / blocks / countdown / frontend.js

frontend.js in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar trunk, at blocks/countdown/frontend.js

209 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * NotificationX Countdown Timer — Gutenberg block (frontend).
3 *
4 * Vanilla port of assets/public/js/nx-countdown.js (which is Elementor-only).
5 * Initializes every block-rendered `.nx-countdown-wrapper` on the page and runs
6 * the identical due-date / evergreen / expiry logic, without jQuery or Elementor.
7 */
8 (function () {
9 "use strict";
10
11 function pad(n) {
12 return String(n).padStart(2, "0");
13 }
14
15 function initCountdown(wrapper) {
16 var widgetId = wrapper.getAttribute("data-countdown-id") || "";
17 var countdownType = wrapper.getAttribute("data-countdown-type") || "due_date";
18 var expireType = wrapper.getAttribute("data-expire-type") || "none";
19 var expireTitle = wrapper.getAttribute("data-expiry-title") || "";
20 var expireText = wrapper.getAttribute("data-expiry-text") || "";
21 var redirectUrl = wrapper.getAttribute("data-redirect-url") || "";
22 var evergreenTime = parseInt(wrapper.getAttribute("data-evergreen-time") || 0, 10);
23 var recurring = wrapper.getAttribute("data-evergreen-recurring");
24 var recurringStop = wrapper.getAttribute("data-evergreen-recurring-stop") || "";
25
26 var list = wrapper.querySelector(".nx-countdown-container");
27 if (!list) return;
28
29 var daysEl = list.querySelector("[data-days]");
30 var hoursEl = list.querySelector("[data-hours]");
31 var minutesEl = list.querySelector("[data-minutes]");
32 var secondsEl = list.querySelector("[data-seconds]");
33
34 var targetTime;
35
36 if (countdownType === "evergreen") {
37 var storageKeyTime = "nx_countdown_evergreen_time_" + widgetId;
38 var storageKeyInterval = "nx_countdown_evergreen_interval_" + widgetId;
39 var storedTime = localStorage.getItem(storageKeyTime);
40 var storedInterval = localStorage.getItem(storageKeyInterval);
41 var HOUR_MS = 60 * 60 * 1000;
42
43 if (
44 storedTime === null ||
45 storedInterval === null ||
46 parseInt(storedInterval, 10) !== evergreenTime
47 ) {
48 storedTime = Date.now() + evergreenTime * 1000;
49 localStorage.setItem(storageKeyInterval, String(evergreenTime));
50 localStorage.setItem(storageKeyTime, String(storedTime));
51 }
52
53 storedTime = parseInt(storedTime, 10);
54
55 if (recurring !== null && recurring !== "" && recurring !== "false") {
56 var recurringAfterMs = parseFloat(recurring) * HOUR_MS;
57 if (storedTime + recurringAfterMs < Date.now()) {
58 storedTime = Date.now() + evergreenTime * 1000;
59 localStorage.setItem(storageKeyTime, String(storedTime));
60 }
61 if (recurringStop) {
62 var stopTs = new Date(recurringStop).getTime();
63 if (!isNaN(stopTs) && stopTs < storedTime) {
64 storedTime = stopTs;
65 }
66 }
67 }
68
69 targetTime = storedTime;
70 } else {
71 var dateStr = list.getAttribute("data-date") || "";
72 targetTime = dateStr ? new Date(dateStr).getTime() : 0;
73 }
74
75 if (!targetTime || isNaN(targetTime)) return;
76
77 var intervalId;
78
79 function handleExpiry() {
80 clearInterval(intervalId);
81 if (expireType === "text") {
82 list.innerHTML =
83 '<div class="nx-countdown-finish-message">' +
84 '<h4 class="nx-expiry-title">' +
85 expireTitle +
86 "</h4>" +
87 '<div class="nx-expiry-text">' +
88 expireText +
89 "</div>" +
90 "</div>";
91 } else if (expireType === "url" && redirectUrl) {
92 window.location.href = redirectUrl;
93 }
94 }
95
96 function tick() {
97 var diff = targetTime - Date.now();
98
99 if (diff <= 0) {
100 if (daysEl) daysEl.textContent = "00";
101 if (hoursEl) hoursEl.textContent = "00";
102 if (minutesEl) minutesEl.textContent = "00";
103 if (secondsEl) secondsEl.textContent = "00";
104 handleExpiry();
105 return;
106 }
107
108 var days = Math.floor(diff / (1000 * 60 * 60 * 24));
109 var hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
110 var minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
111 var seconds = Math.floor((diff % (1000 * 60)) / 1000);
112
113 if (daysEl) daysEl.textContent = pad(days);
114 if (hoursEl) hoursEl.textContent = pad(hours);
115 if (minutesEl) minutesEl.textContent = pad(minutes);
116 if (secondsEl) secondsEl.textContent = pad(seconds);
117 }
118
119 tick();
120 intervalId = setInterval(tick, 1000);
121 }
122
123 function initAll() {
124 var wrappers = document.querySelectorAll(".nx-countdown-wrapper");
125 for (var i = 0; i < wrappers.length; i++) {
126 var wrapper = wrappers[i];
127 // Leave Elementor-rendered timers to the Elementor script.
128 if (wrapper.closest && wrapper.closest(".elementor-widget")) continue;
129 if (wrapper.getAttribute("data-nx-countdown-init")) continue;
130 wrapper.setAttribute("data-nx-countdown-init", "1");
131 initCountdown(wrapper);
132 }
133 }
134
135 // Run once on initial page load.
136 if (document.readyState === "loading") {
137 document.addEventListener("DOMContentLoaded", initAll);
138 } else {
139 initAll();
140 }
141
142 // NotificationX injects press bars into the DOM after page load and calls
143 // window.ebRunCountDown() once a bar is mounted. Expose initAll under that
144 // hook so block countdowns inside a dynamically-injected bar — e.g. on the
145 // preview screen, where the bar's assets aren't re-loaded — get initialized.
146 // Chain any pre-existing hook (e.g. Essential Blocks) instead of clobbering
147 // it; initAll() is idempotent via the data-nx-countdown-init guard.
148 var prevRunCountDown =
149 typeof window.ebRunCountDown === "function" ? window.ebRunCountDown : null;
150 window.ebRunCountDown = function () {
151 if (prevRunCountDown) {
152 try {
153 prevRunCountDown();
154 } catch (e) {}
155 }
156 initAll();
157 };
158
159 // Fallback: watch for late-injected countdowns directly, in case a bar is
160 // mounted without the hook firing. Guarded to a single observer per page and
161 // scoped so it only does work when a countdown wrapper actually appears.
162 if (
163 !window.__nxCountdownObserverAttached &&
164 typeof MutationObserver !== "undefined"
165 ) {
166 window.__nxCountdownObserverAttached = true;
167
168 var hasCountdown = function (node) {
169 if (!node || node.nodeType !== 1) return false;
170 if (node.classList && node.classList.contains("nx-countdown-wrapper")) {
171 return true;
172 }
173 return (
174 typeof node.querySelector === "function" &&
175 !!node.querySelector(".nx-countdown-wrapper")
176 );
177 };
178
179 var pending = false;
180 var observer = new MutationObserver(function (mutations) {
181 if (pending) return;
182 for (var i = 0; i < mutations.length; i++) {
183 var added = mutations[i].addedNodes;
184 for (var j = 0; j < added.length; j++) {
185 if (hasCountdown(added[j])) {
186 pending = true;
187 setTimeout(function () {
188 pending = false;
189 initAll();
190 }, 50);
191 return;
192 }
193 }
194 }
195 });
196
197 var startObserving = function () {
198 if (document.body) {
199 observer.observe(document.body, { childList: true, subtree: true });
200 }
201 };
202 if (document.body) {
203 startObserving();
204 } else {
205 document.addEventListener("DOMContentLoaded", startObserving);
206 }
207 }
208 })();
209