PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.6
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.6
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.6, at blocks/countdown/index.js

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