PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / assets / js / modules / inactive-tab-message / admin / preview.js

preview.js in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.3.2, at assets/js/modules/inactive-tab-message/admin/preview.js

325 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2
3 ;
4 (function ($) {
5 'use strict';
6
7 var emojiMap = {
8 wave: '👋',
9 bell: '🔔',
10 cart: '🛒',
11 clock: '',
12 alert: '',
13 money: '💰',
14 fire: '🔥',
15 star: ''
16 };
17 var rotationTimer = null;
18 var rotationIndex = 0;
19 var scrollTimer = null;
20 var scrollDelayTimer = null;
21
22 // Sample values for admin preview.
23 var sampleData = {
24 cart_count: '3',
25 cart_total: '$45.00',
26 site_name: typeof merchantItmPreview !== 'undefined' && merchantItmPreview.siteName || 'Your Store'
27 };
28
29 /**
30 * Replace dynamic variable placeholders with sample values.
31 */
32 function interpolate(text) {
33 return text.replace(/\{cart_count\}/g, sampleData.cart_count).replace(/\{cart_total\}/g, sampleData.cart_total).replace(/\{site_name\}/g, sampleData.site_name);
34 }
35
36 /**
37 * Detect if a string is predominantly RTL (Arabic, Hebrew, etc.).
38 */
39 function isRTL(text) {
40 return /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(text);
41 }
42
43 /**
44 * Stop any running scroll ticker and short-message delay timer.
45 */
46 function stopScroll() {
47 if (scrollTimer) {
48 clearInterval(scrollTimer);
49 scrollTimer = null;
50 }
51 if (scrollDelayTimer) {
52 clearTimeout(scrollDelayTimer);
53 scrollDelayTimer = null;
54 }
55 }
56
57 /**
58 * Set the preview tab title, starting a scroll ticker when enabled.
59 *
60 * @param {string} text Already-interpolated message text.
61 * @param {Object} titleEl jQuery element for the tab title node.
62 * @param {Function} onComplete Optional callback fired after one full scroll cycle.
63 */
64 function setPreviewTitle(text, titleEl, onComplete) {
65 var enabled = getCheckedVal('enable_scroll') === '1';
66 stopScroll();
67 if (!enabled || !text) {
68 titleEl.text(text);
69 if (onComplete && !enabled) {
70 var rotInterval = (parseInt(getCheckedVal('rotation_interval'), 10) || 3) * 1000;
71 scrollDelayTimer = setTimeout(onComplete, rotInterval);
72 }
73 return;
74 }
75
76 // Don't scroll short messages — they fit in the tab without truncating.
77 // Browser tabs typically show ~12-15 characters before truncation.
78 var SCROLL_THRESHOLD = 15;
79 if (text.length <= SCROLL_THRESHOLD) {
80 titleEl.text(text);
81 // Short message with callback: display for rotationInterval, then advance.
82 if (onComplete) {
83 var rotInterval = (parseInt(getCheckedVal('rotation_interval'), 10) || 3) * 1000;
84 scrollDelayTimer = setTimeout(onComplete, rotInterval);
85 }
86 return;
87 }
88 var rtl = isRTL(text);
89 var pos = 0;
90 var isMultiWord = text.indexOf(' ') !== -1;
91
92 // Fixed scroll speed — matches frontend (1 character per second).
93 var SCROLL_SPEED = 1000;
94 titleEl.text(text);
95 scrollTimer = setInterval(function () {
96 pos += 1;
97
98 // Full cycle complete — all characters consumed.
99 if (pos >= text.length) {
100 clearInterval(scrollTimer);
101 scrollTimer = null;
102 if (onComplete) {
103 onComplete();
104 }
105 return;
106 }
107 var remaining;
108 if (rtl) {
109 remaining = text.slice(0, text.length - pos);
110 } else {
111 remaining = text.slice(pos);
112 }
113
114 // Multi-word text: once only the last word remains, complete immediately.
115 if (isMultiWord && remaining.indexOf(' ') === -1) {
116 clearInterval(scrollTimer);
117 scrollTimer = null;
118 if (onComplete) {
119 onComplete();
120 }
121 return;
122 }
123 titleEl.text(remaining);
124 }, SCROLL_SPEED);
125 }
126 function getField(id) {
127 return $('[name="merchant[' + id + ']"]');
128 }
129 function getCheckedVal(id) {
130 var el = getField(id);
131 if (el.attr('type') === 'radio') {
132 return el.filter(':checked').val();
133 }
134 if (el.attr('type') === 'checkbox') {
135 return el.is(':checked') ? '1' : '0';
136 }
137 return el.val();
138 }
139
140 /**
141 * Read rotation messages from the sortable repeater hidden input.
142 */
143 function getRotationMessages() {
144 var raw = getField('rotation_messages').val();
145 if (!raw) {
146 return [];
147 }
148 try {
149 var parsed = JSON.parse(raw);
150 return Array.isArray(parsed) ? parsed.filter(function (m) {
151 return m && m.trim();
152 }) : [];
153 } catch (e) {
154 return [];
155 }
156 }
157
158 // ── Favicon preview ──────────────────────────────────────────────
159 function updateFavicon() {
160 var enabled = getCheckedVal('enable_favicon') === '1';
161 var type = getCheckedVal('favicon_type') || 'emoji';
162 var emojiKey = getCheckedVal('favicon_emoji') || 'wave';
163 var faviconEl = $('.mrc-inactive-tab-preview-tab__favicon--swap');
164 if (!faviconEl.length) {
165 return;
166 }
167 if (enabled && type === 'emoji') {
168 var emoji = emojiMap[emojiKey] || emojiMap.wave;
169 faviconEl.html('<span class="mrc-inactive-tab-favicon-emoji">' + emoji + '</span>');
170 } else if (enabled && type === 'image') {
171 // Read the uploaded image thumbnail from the upload field's preview.
172 var uploadImg = $('[data-id="favicon_url"] .merchant-upload-image img');
173 if (uploadImg.length) {
174 faviconEl.empty().append($('<img />', {
175 'class': 'mrc-inactive-tab-favicon-custom',
176 src: uploadImg.attr('src'),
177 alt: ''
178 }));
179 } else {
180 // No image uploaded yet — show default site favicon.
181 var activeImg = $('.mrc-inactive-tab-preview-tab--active .mrc-inactive-tab-preview-tab__favicon img');
182 if (activeImg.length) {
183 faviconEl.empty().append($('<img />', {
184 src: activeImg.attr('src'),
185 alt: ''
186 }));
187 }
188 }
189 } else {
190 // Favicon disabled — show default site favicon.
191 var activeImg = $('.mrc-inactive-tab-preview-tab--active .mrc-inactive-tab-preview-tab__favicon img');
192 if (activeImg.length) {
193 faviconEl.empty().append($('<img />', {
194 src: activeImg.attr('src'),
195 alt: ''
196 }));
197 }
198 }
199 }
200
201 // ── Rotation preview ─────────────────────────────────────────────
202 function stopRotation() {
203 if (rotationTimer) {
204 clearInterval(rotationTimer);
205 rotationTimer = null;
206 }
207 stopScroll();
208 rotationIndex = 0;
209 }
210 function startRotation() {
211 stopRotation();
212 var messages = getRotationMessages();
213 var titleEl = $('.mrc-inactive-tab-message-text');
214 if (!messages.length || !titleEl.length) {
215 return;
216 }
217 var enableScroll = getCheckedVal('enable_scroll') === '1';
218 if (enableScroll) {
219 // Chain: scroll current message → on complete → advance → next.
220 rotationIndex = 0;
221 var _showCurrent = function showCurrent() {
222 setPreviewTitle(interpolate(messages[rotationIndex]), titleEl, function () {
223 rotationIndex = (rotationIndex + 1) % messages.length;
224 _showCurrent();
225 });
226 };
227 _showCurrent();
228 } else {
229 // Show first message immediately.
230 setPreviewTitle(interpolate(messages[0]), titleEl);
231 var interval = (parseInt(getCheckedVal('rotation_interval'), 10) || 3) * 1000;
232 rotationTimer = setInterval(function () {
233 rotationIndex = (rotationIndex + 1) % messages.length;
234 setPreviewTitle(interpolate(messages[rotationIndex]), titleEl);
235 }, interval);
236 }
237 }
238 function updateRotation() {
239 var enabled = getCheckedVal('enable_rotation') === '1';
240 var titleEl = $('.mrc-inactive-tab-message-text');
241 if (enabled) {
242 startRotation();
243 } else {
244 stopRotation();
245 // Restore single message with looping scroll.
246 if (titleEl.length) {
247 var text = interpolate(getField('message').val() || '');
248 var enableScroll = getCheckedVal('enable_scroll') === '1';
249 if (enableScroll && text.length > 15) {
250 // Loop: scroll → restart → scroll → …
251 var _loop = function loop() {
252 setPreviewTitle(text, titleEl, _loop);
253 };
254 _loop();
255 } else {
256 setPreviewTitle(text, titleEl);
257 }
258 }
259 }
260 }
261
262 // ── Init ─────────────────────────────────────────────────────────
263 $(document).ready(function () {
264 // Favicon bindings.
265 getField('enable_favicon').on('change', updateFavicon);
266 getField('favicon_type').on('change', updateFavicon);
267 getField('favicon_emoji').on('change', updateFavicon);
268
269 // Favicon custom image upload binding.
270 $(document).on('change', '[data-id="favicon_url"] .merchant-upload-input', updateFavicon);
271 $(document).on('click', '[data-id="favicon_url"] .merchant-upload-remove', function () {
272 setTimeout(updateFavicon, 100);
273 });
274
275 // Single message binding.
276 getField('message').on('keyup input', function () {
277 if (getCheckedVal('enable_rotation') !== '1') {
278 setPreviewTitle(interpolate($(this).val() || ''), $('.mrc-inactive-tab-message-text'));
279 }
280 });
281
282 // Scroll binding — restart when toggled.
283 getField('enable_scroll').on('change', function () {
284 if (getCheckedVal('enable_rotation') === '1') {
285 startRotation();
286 } else {
287 var text = interpolate(getField('message').val() || '');
288 var titleEl = $('.mrc-inactive-tab-message-text');
289 var enableScroll = getCheckedVal('enable_scroll') === '1';
290 if (enableScroll && text.length > 15) {
291 var _loop2 = function loop() {
292 setPreviewTitle(text, titleEl, _loop2);
293 };
294 _loop2();
295 } else {
296 setPreviewTitle(text, titleEl);
297 }
298 }
299 });
300
301 // Rotation bindings.
302 getField('enable_rotation').on('change', updateRotation);
303 getField('rotation_messages').on('change', updateRotation);
304 getField('rotation_interval').on('input change', updateRotation);
305
306 // The range slider has name="" so getField won't find it.
307 // Listen to it via the parent container.
308 $(document).on('input', '[data-id="rotation_interval"] .merchant-range-input', updateRotation);
309
310 // Also listen to the visible repeater text inputs (they update the hidden input).
311 $(document).on('input', '.merchant-sortable-repeater .repeater-input', function () {
312 // Small delay so the hidden input is synced first.
313 setTimeout(updateRotation, 50);
314 });
315
316 // Listen for add/remove repeater items.
317 $(document).on('click', '.customize-control-sortable-repeater-add, .customize-control-sortable-repeater-delete', function () {
318 setTimeout(updateRotation, 100);
319 });
320
321 // Initial state.
322 updateFavicon();
323 updateRotation();
324 });
325 })(jQuery);