PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / countdown / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/countdown/index.js

535 lines 25.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var Fragment = wp.element.Fragment;
4 var __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var useEffect = wp.element.useEffect;
7 var registerBlockType = wp.blocks.registerBlockType;
8 var InspectorControls = wp.blockEditor.InspectorControls;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
12 var ToggleControl = wp.components.ToggleControl;
13 var RangeControl = wp.components.RangeControl;
14 var SelectControl = wp.components.SelectControl;
15 var TextControl = wp.components.TextControl;
16
17 var _cdTC, _cdTV;
18 function _tc() { return _cdTC || (_cdTC = window.bkbgTypographyControl); }
19 function _tv(obj, prefix) { var fn = _cdTV || (_cdTV = window.bkbgTypoCssVars); return fn ? fn(obj, prefix) : {}; }
20
21 // Calculate time remaining
22 function getTimeRemaining(targetDate, targetTime) {
23 if (!targetDate) return { days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 };
24
25 var target = new Date(targetDate + 'T' + (targetTime || '00:00') + ':00');
26 var now = new Date();
27 var total = target - now;
28
29 if (total <= 0) {
30 return { days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 };
31 }
32
33 return {
34 days: Math.floor(total / (1000 * 60 * 60 * 24)),
35 hours: Math.floor((total / (1000 * 60 * 60)) % 24),
36 minutes: Math.floor((total / (1000 * 60)) % 60),
37 seconds: Math.floor((total / 1000) % 60),
38 total: total
39 };
40 }
41
42 // Pad number with leading zero
43 function pad(num) {
44 return num < 10 ? '0' + num : String(num);
45 }
46
47 // Get default date (7 days from now)
48 function getDefaultDate() {
49 var d = new Date();
50 d.setDate(d.getDate() + 7);
51 return d.toISOString().split('T')[0];
52 }
53
54 registerBlockType('blockenberg/countdown', {
55 title: __('Countdown', 'blockenberg'),
56 icon: 'clock',
57 category: 'bkbg-effects',
58 description: __('Display a countdown timer to a specific date and time.', 'blockenberg'),
59
60 edit: function (props) {
61 var attributes = props.attributes;
62 var setAttributes = props.setAttributes;
63 var a = attributes;
64
65 // State for live countdown in editor
66 var timeState = useState(function () {
67 return getTimeRemaining(a.targetDate || getDefaultDate(), a.targetTime);
68 });
69 var time = timeState[0];
70 var setTime = timeState[1];
71
72 // State for editing
73 var editingState = useState(null);
74 var editing = editingState[0];
75 var setEditing = editingState[1];
76
77 // Ref for evergreen simulation start time
78 var evergreenStartRef = wp.element.useRef(null);
79
80 // Update countdown every second in editor
81 useEffect(function () {
82 var targetDate = a.targetDate || getDefaultDate();
83
84 // Set default date if not set and not evergreen
85 if (!a.targetDate && !a.evergreenMode) {
86 setAttributes({ targetDate: targetDate });
87 }
88
89 // For evergreen, set up simulation start time
90 if (a.evergreenMode && !evergreenStartRef.current) {
91 evergreenStartRef.current = Date.now();
92 }
93 if (!a.evergreenMode) {
94 evergreenStartRef.current = null;
95 }
96
97 // Calculate time remaining
98 function updateTime() {
99 if (a.evergreenMode) {
100 // Calculate total duration in ms
101 var totalDuration = (a.evergreenDays * 24 * 60 * 60 * 1000) +
102 (a.evergreenHours * 60 * 60 * 1000) +
103 (a.evergreenMinutes * 60 * 1000);
104
105 // Calculate elapsed time since simulation started
106 var elapsed = Date.now() - evergreenStartRef.current;
107 var remaining = Math.max(0, totalDuration - elapsed);
108
109 // If expired, restart simulation
110 if (remaining <= 0) {
111 evergreenStartRef.current = Date.now();
112 remaining = totalDuration;
113 }
114
115 var days = Math.floor(remaining / (1000 * 60 * 60 * 24));
116 var hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
117 var minutes = Math.floor((remaining / (1000 * 60)) % 60);
118 var seconds = Math.floor((remaining / 1000) % 60);
119 setTime({ days: days, hours: hours, minutes: minutes, seconds: seconds, total: remaining });
120 } else {
121 setTime(getTimeRemaining(targetDate, a.targetTime));
122 }
123 }
124
125 var interval = setInterval(updateTime, 1000);
126 updateTime();
127
128 return function () { clearInterval(interval); };
129 }, [a.targetDate, a.targetTime, a.evergreenMode, a.evergreenDays, a.evergreenHours, a.evergreenMinutes]);
130
131 var layoutOptions = [
132 { label: __('Cards', 'blockenberg'), value: 'cards' },
133 { label: __('Inline', 'blockenberg'), value: 'inline' },
134 { label: __('Minimal', 'blockenberg'), value: 'minimal' },
135 { label: __('Circle', 'blockenberg'), value: 'circle' }
136 ];
137
138 var expiredOptions = [
139 { label: __('Show Message', 'blockenberg'), value: 'message' },
140 { label: __('Hide Block', 'blockenberg'), value: 'hide' },
141 { label: __('Redirect', 'blockenberg'), value: 'redirect' },
142 { label: __('Keep Last State', 'blockenberg'), value: 'keep' }
143 ];
144
145 var fontWeightOptions = [
146 { label: '400', value: 400 },
147 { label: '500', value: 500 },
148 { label: '600', value: 600 },
149 { label: '700', value: 700 },
150 { label: '800', value: 800 }
151 ];
152
153 // Inspector controls
154 var inspector = el(InspectorControls, {},
155 el(PanelBody, { title: __('Date & Time', 'blockenberg'), initialOpen: true },
156 el(ToggleControl, {
157 label: __('Evergreen Mode', 'blockenberg'),
158 help: a.evergreenMode ? __('Countdown resets for each visitor', 'blockenberg') : __('Fixed date countdown', 'blockenberg'),
159 checked: a.evergreenMode,
160 __nextHasNoMarginBottom: true,
161 onChange: function (v) {
162 setAttributes({ evergreenMode: v });
163 // Generate unique ID for evergreen
164 if (v && !a.evergreenId) {
165 setAttributes({ evergreenId: 'eg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9) });
166 }
167 }
168 }),
169 !a.evergreenMode && el('div', { className: 'bkbg-cd-date-control' },
170 el('label', {}, __('Target Date', 'blockenberg')),
171 el('input', {
172 type: 'date',
173 value: a.targetDate,
174 onChange: function (e) { setAttributes({ targetDate: e.target.value }); },
175 className: 'components-text-control__input'
176 })
177 ),
178 !a.evergreenMode && el('div', { className: 'bkbg-cd-date-control', style: { marginTop: '12px' } },
179 el('label', {}, __('Target Time', 'blockenberg')),
180 el('input', {
181 type: 'time',
182 value: a.targetTime,
183 onChange: function (e) { setAttributes({ targetTime: e.target.value }); },
184 className: 'components-text-control__input'
185 })
186 ),
187 a.evergreenMode && el(RangeControl, {
188 label: __('Days', 'blockenberg'),
189 value: a.evergreenDays,
190 min: 0,
191 max: 30,
192 onChange: function (v) { setAttributes({ evergreenDays: v }); }
193 }),
194 a.evergreenMode && el(RangeControl, {
195 label: __('Hours', 'blockenberg'),
196 value: a.evergreenHours,
197 min: 0,
198 max: 23,
199 onChange: function (v) { setAttributes({ evergreenHours: v }); }
200 }),
201 a.evergreenMode && el(RangeControl, {
202 label: __('Minutes', 'blockenberg'),
203 value: a.evergreenMinutes,
204 min: 0,
205 max: 59,
206 onChange: function (v) { setAttributes({ evergreenMinutes: v }); }
207 })
208 ),
209
210 el(PanelBody, { title: __('Display Options', 'blockenberg'), initialOpen: true },
211 el(SelectControl, {
212 label: __('Layout Style', 'blockenberg'),
213 value: a.layoutStyle,
214 options: layoutOptions,
215 onChange: function (v) { setAttributes({ layoutStyle: v }); }
216 }),
217 el(ToggleControl, {
218 label: __('Show Title', 'blockenberg'),
219 checked: a.showTitle,
220 __nextHasNoMarginBottom: true,
221 onChange: function (v) { setAttributes({ showTitle: v }); }
222 }),
223 el(ToggleControl, {
224 label: __('Show Days', 'blockenberg'),
225 checked: a.showDays,
226 __nextHasNoMarginBottom: true,
227 onChange: function (v) { setAttributes({ showDays: v }); }
228 }),
229 el(ToggleControl, {
230 label: __('Show Hours', 'blockenberg'),
231 checked: a.showHours,
232 __nextHasNoMarginBottom: true,
233 onChange: function (v) { setAttributes({ showHours: v }); }
234 }),
235 el(ToggleControl, {
236 label: __('Show Minutes', 'blockenberg'),
237 checked: a.showMinutes,
238 __nextHasNoMarginBottom: true,
239 onChange: function (v) { setAttributes({ showMinutes: v }); }
240 }),
241 el(ToggleControl, {
242 label: __('Show Seconds', 'blockenberg'),
243 checked: a.showSeconds,
244 __nextHasNoMarginBottom: true,
245 onChange: function (v) { setAttributes({ showSeconds: v }); }
246 }),
247 el(ToggleControl, {
248 label: __('Show Labels', 'blockenberg'),
249 checked: a.showLabels,
250 __nextHasNoMarginBottom: true,
251 onChange: function (v) { setAttributes({ showLabels: v }); }
252 }),
253 el(ToggleControl, {
254 label: __('Show Separators', 'blockenberg'),
255 checked: a.showSeparators,
256 __nextHasNoMarginBottom: true,
257 onChange: function (v) { setAttributes({ showSeparators: v }); }
258 })
259 ),
260
261 el(PanelBody, { title: __('Labels', 'blockenberg'), initialOpen: false },
262 el(TextControl, {
263 label: __('Days Label', 'blockenberg'),
264 value: a.labelDays,
265 onChange: function (v) { setAttributes({ labelDays: v }); }
266 }),
267 el(TextControl, {
268 label: __('Hours Label', 'blockenberg'),
269 value: a.labelHours,
270 onChange: function (v) { setAttributes({ labelHours: v }); }
271 }),
272 el(TextControl, {
273 label: __('Minutes Label', 'blockenberg'),
274 value: a.labelMinutes,
275 onChange: function (v) { setAttributes({ labelMinutes: v }); }
276 }),
277 el(TextControl, {
278 label: __('Seconds Label', 'blockenberg'),
279 value: a.labelSeconds,
280 onChange: function (v) { setAttributes({ labelSeconds: v }); }
281 })
282 ),
283
284 el(PanelBody, { title: __('Expiration', 'blockenberg'), initialOpen: false },
285 el(SelectControl, {
286 label: __('When Expired', 'blockenberg'),
287 value: a.expiredAction,
288 options: expiredOptions,
289 onChange: function (v) { setAttributes({ expiredAction: v }); }
290 }),
291 a.expiredAction === 'message' && el(TextControl, {
292 label: __('Expired Message', 'blockenberg'),
293 value: a.expiredMessage,
294 onChange: function (v) { setAttributes({ expiredMessage: v }); }
295 }),
296 a.expiredAction === 'redirect' && el(TextControl, {
297 label: __('Redirect URL', 'blockenberg'),
298 value: a.expiredRedirectUrl,
299 onChange: function (v) { setAttributes({ expiredRedirectUrl: v }); }
300 })
301 ),
302
303 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
304 _tc() && el(_tc(), { label: __('Digit', 'blockenberg'), value: a.typoDigit, onChange: function (v) { setAttributes({ typoDigit: v }); } }),
305 _tc() && el(_tc(), { label: __('Label', 'blockenberg'), value: a.typoLabel, onChange: function (v) { setAttributes({ typoLabel: v }); } }),
306 _tc() && el(_tc(), { label: __('Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { setAttributes({ typoTitle: v }); } })
307 ),
308
309 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
310 el(RangeControl, {
311 label: __('Container Padding', 'blockenberg'),
312 value: a.padding,
313 min: 0,
314 max: 60,
315 onChange: function (v) { setAttributes({ padding: v }); }
316 }),
317 el(RangeControl, {
318 label: __('Gap Between Units', 'blockenberg'),
319 value: a.gap,
320 min: 8,
321 max: 48,
322 onChange: function (v) { setAttributes({ gap: v }); }
323 }),
324 el(RangeControl, {
325 label: __('Digit Box Padding', 'blockenberg'),
326 value: a.digitPadding,
327 min: 8,
328 max: 40,
329 onChange: function (v) { setAttributes({ digitPadding: v }); }
330 }),
331 el(RangeControl, {
332 label: __('Container Radius', 'blockenberg'),
333 value: a.borderRadius,
334 min: 0,
335 max: 30,
336 onChange: function (v) { setAttributes({ borderRadius: v }); }
337 }),
338 el(RangeControl, {
339 label: __('Digit Box Radius', 'blockenberg'),
340 value: a.digitRadius,
341 min: 0,
342 max: 24,
343 onChange: function (v) { setAttributes({ digitRadius: v }); }
344 })
345 ),
346
347 el(PanelColorSettings, {
348 title: __('Colors', 'blockenberg'),
349 initialOpen: false,
350 colorSettings: [
351 { value: a.wrapBg, onChange: function (c) { setAttributes({ wrapBg: c }); }, label: __('Background', 'blockenberg') },
352 { value: a.digitBg, onChange: function (c) { setAttributes({ digitBg: c }); }, label: __('Digit Background', 'blockenberg') },
353 { value: a.digitColor, onChange: function (c) { setAttributes({ digitColor: c }); }, label: __('Digit Color', 'blockenberg') },
354 { value: a.labelColor, onChange: function (c) { setAttributes({ labelColor: c }); }, label: __('Label Color', 'blockenberg') },
355 { value: a.separatorColor, onChange: function (c) { setAttributes({ separatorColor: c }); }, label: __('Separator Color', 'blockenberg') },
356 { value: a.titleColor, onChange: function (c) { setAttributes({ titleColor: c }); }, label: __('Title Color', 'blockenberg') }
357 ]
358 })
359 );
360
361 // CSS variables
362 var wrapStyle = {
363 '--bkbg-cd-wrap-bg': a.wrapBg,
364 '--bkbg-cd-digit-bg': a.digitBg,
365 '--bkbg-cd-digit-color': a.digitColor,
366 '--bkbg-cd-label-color': a.labelColor,
367 '--bkbg-cd-separator-color': a.separatorColor,
368 '--bkbg-cd-title-color': a.titleColor,
369 '--bkbg-cd-digit-size': a.digitFontSize + 'px',
370 '--bkbg-cd-digit-weight': a.digitFontWeight,
371 '--bkbg-cd-label-size': a.labelFontSize + 'px',
372 '--bkbg-cd-label-weight': a.labelFontWeight,
373 '--bkbg-cd-title-size': a.titleFontSize + 'px',
374 '--bkbg-cd-padding': a.padding + 'px',
375 '--bkbg-cd-gap': a.gap + 'px',
376 '--bkbg-cd-radius': a.borderRadius + 'px',
377 '--bkbg-cd-digit-padding': a.digitPadding + 'px',
378 '--bkbg-cd-digit-radius': a.digitRadius + 'px'
379 };
380
381 // Build time units
382 var units = [];
383
384 if (a.showDays) {
385 units.push({ key: 'days', value: time.days, label: a.labelDays });
386 }
387 if (a.showHours) {
388 units.push({ key: 'hours', value: time.hours, label: a.labelHours });
389 }
390 if (a.showMinutes) {
391 units.push({ key: 'minutes', value: time.minutes, label: a.labelMinutes });
392 }
393 if (a.showSeconds) {
394 units.push({ key: 'seconds', value: time.seconds, label: a.labelSeconds });
395 }
396
397 // Title element
398 var titleEl = null;
399 if (a.showTitle) {
400 if (editing === 'title') {
401 titleEl = el('input', {
402 type: 'text',
403 className: 'bkbg-cd-title bkbg-cd-input-active',
404 value: a.title,
405 autoFocus: true,
406 placeholder: __('Countdown Title', 'blockenberg'),
407 onChange: function (e) { setAttributes({ title: e.target.value }); },
408 onBlur: function () { setEditing(null); },
409 onKeyDown: function (e) { if (e.key === 'Enter') setEditing(null); }
410 });
411 } else {
412 titleEl = el('div', {
413 className: 'bkbg-cd-title bkbg-cd-clickable',
414 onClick: function () { setEditing('title'); }
415 }, a.title || __('Countdown Title', 'blockenberg'));
416 }
417 }
418
419 // Build countdown units
420 var countdownUnits = units.map(function (unit, index) {
421 var isLast = index === units.length - 1;
422 var labelKey = 'label-' + unit.key;
423 var valueStr = pad(unit.value);
424
425 // Label element - click to edit
426 var labelEl = null;
427 if (a.showLabels) {
428 if (editing === labelKey) {
429 labelEl = el('input', {
430 type: 'text',
431 className: 'bkbg-cd-label bkbg-cd-input-active',
432 value: unit.label,
433 autoFocus: true,
434 onChange: function (e) {
435 var attr = 'label' + unit.key.charAt(0).toUpperCase() + unit.key.slice(1);
436 var obj = {};
437 obj[attr] = e.target.value;
438 setAttributes(obj);
439 },
440 onBlur: function () { setEditing(null); },
441 onKeyDown: function (e) { if (e.key === 'Enter') setEditing(null); }
442 });
443 } else {
444 labelEl = el('span', {
445 className: 'bkbg-cd-label bkbg-cd-clickable',
446 onClick: function () { setEditing(labelKey); }
447 }, unit.label);
448 }
449 }
450
451 return el(Fragment, { key: unit.key },
452 el('div', { className: 'bkbg-cd-unit' },
453 el('div', { className: 'bkbg-cd-digit' },
454 el('span', { className: 'bkbg-cd-number' }, valueStr)
455 ),
456 labelEl
457 ),
458 a.showSeparators && !isLast && el('span', { className: 'bkbg-cd-separator' }, ':')
459 );
460 });
461
462 var blockProps = useBlockProps({
463 className: 'bkbg-editor-wrap',
464 'data-block-label': 'Countdown'
465 });
466
467 return el('div', blockProps,
468 inspector,
469 el('div', {
470 className: 'bkbg-cd-wrap',
471 style: wrapStyle,
472 'data-layout': a.layoutStyle
473 },
474 titleEl,
475 el('div', { className: 'bkbg-cd-countdown' }, countdownUnits)
476 )
477 );
478 },
479
480 save: function (props) {
481 var a = props.attributes;
482
483 var wrapStyle = Object.assign({
484 '--bkbg-cd-wrap-bg': a.wrapBg,
485 '--bkbg-cd-digit-bg': a.digitBg,
486 '--bkbg-cd-digit-color': a.digitColor,
487 '--bkbg-cd-label-color': a.labelColor,
488 '--bkbg-cd-separator-color': a.separatorColor,
489 '--bkbg-cd-title-color': a.titleColor,
490 '--bkbg-cd-digit-size': a.digitFontSize + 'px',
491 '--bkbg-cd-digit-weight': a.digitFontWeight,
492 '--bkbg-cd-label-size': a.labelFontSize + 'px',
493 '--bkbg-cd-label-weight': a.labelFontWeight,
494 '--bkbg-cd-title-size': a.titleFontSize + 'px',
495 '--bkbg-cd-padding': a.padding + 'px',
496 '--bkbg-cd-gap': a.gap + 'px',
497 '--bkbg-cd-radius': a.borderRadius + 'px',
498 '--bkbg-cd-digit-padding': a.digitPadding + 'px',
499 '--bkbg-cd-digit-radius': a.digitRadius + 'px'
500 }, _tv(a.typoDigit, '--bkbg-cd-dig-'), _tv(a.typoLabel, '--bkbg-cd-lbl-'), _tv(a.typoTitle, '--bkbg-cd-ttl-'));
501
502 // Build units config as data attributes
503 var unitsConfig = [];
504 if (a.showDays) unitsConfig.push('days:' + a.labelDays);
505 if (a.showHours) unitsConfig.push('hours:' + a.labelHours);
506 if (a.showMinutes) unitsConfig.push('minutes:' + a.labelMinutes);
507 if (a.showSeconds) unitsConfig.push('seconds:' + a.labelSeconds);
508
509 // Calculate evergreen duration in milliseconds
510 var evergreenDuration = (a.evergreenDays * 24 * 60 * 60 * 1000) +
511 (a.evergreenHours * 60 * 60 * 1000) +
512 (a.evergreenMinutes * 60 * 1000);
513
514 return el('div', {
515 className: 'bkbg-cd-wrap',
516 style: wrapStyle,
517 'data-layout': a.layoutStyle,
518 'data-target': a.evergreenMode ? '' : (a.targetDate + 'T' + a.targetTime),
519 'data-units': unitsConfig.join('|'),
520 'data-show-labels': a.showLabels ? '1' : '0',
521 'data-show-separators': a.showSeparators ? '1' : '0',
522 'data-expired-action': a.expiredAction,
523 'data-expired-message': a.expiredMessage,
524 'data-expired-redirect': a.expiredRedirectUrl,
525 'data-evergreen': a.evergreenMode ? '1' : '0',
526 'data-evergreen-duration': String(evergreenDuration),
527 'data-evergreen-id': a.evergreenId
528 },
529 a.showTitle && a.title && el('div', { className: 'bkbg-cd-title' }, a.title),
530 el('div', { className: 'bkbg-cd-countdown' })
531 );
532 }
533 });
534 }() );
535