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 / calendar / index.js

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

387 lines 24.6 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 useState = wp.element.useState;
5 var __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var RichText = wp.blockEditor.RichText;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var Button = wp.components.Button;
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 var ColorPicker = wp.components.ColorPicker;
17 var Popover = wp.components.Popover;
18
19 function getTypographyControl() {
20 return (window.bkbgTypographyControl || function () { return null; });
21 }
22
23 function _tv(typo, prefix) {
24 if (!typo) return {};
25 var s = {};
26 if (typo.family) s[prefix + 'font-family'] = "'" + typo.family + "', sans-serif";
27 if (typo.weight) s[prefix + 'font-weight'] = typo.weight;
28 if (typo.transform) s[prefix + 'text-transform'] = typo.transform;
29 if (typo.style) s[prefix + 'font-style'] = typo.style;
30 if (typo.decoration) s[prefix + 'text-decoration'] = typo.decoration;
31 var su = typo.sizeUnit || 'px';
32 if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) s[prefix + 'font-size-d'] = typo.sizeDesktop + su;
33 if (typo.sizeTablet !== '' && typo.sizeTablet != null) s[prefix + 'font-size-t'] = typo.sizeTablet + su;
34 if (typo.sizeMobile !== '' && typo.sizeMobile != null) s[prefix + 'font-size-m'] = typo.sizeMobile + su;
35 var lhu = typo.lineHeightUnit || '';
36 if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) s[prefix + 'line-height-d'] = typo.lineHeightDesktop + lhu;
37 if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) s[prefix + 'line-height-t'] = typo.lineHeightTablet + lhu;
38 if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) s[prefix + 'line-height-m'] = typo.lineHeightMobile + lhu;
39 var lsu = typo.letterSpacingUnit || 'px';
40 if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) s[prefix + 'letter-spacing-d'] = typo.letterSpacingDesktop + lsu;
41 if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) s[prefix + 'letter-spacing-t'] = typo.letterSpacingTablet + lsu;
42 if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) s[prefix + 'letter-spacing-m'] = typo.letterSpacingMobile + lsu;
43 var wsu = typo.wordSpacingUnit || 'px';
44 if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) s[prefix + 'word-spacing-d'] = typo.wordSpacingDesktop + wsu;
45 if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) s[prefix + 'word-spacing-t'] = typo.wordSpacingTablet + wsu;
46 if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) s[prefix + 'word-spacing-m'] = typo.wordSpacingMobile + wsu;
47 return s;
48 }
49
50 var MONTHS_EN = ['January','February','March','April','May','June','July','August','September','October','November','December'];
51
52 function makeId() { return 'cal' + Math.random().toString(36).substr(2, 5); }
53
54 function buildWrapStyle(a) {
55 return Object.assign({
56 '--bkbg-cal-accent': a.accentColor,
57 '--bkbg-cal-header-bg': a.headerBg,
58 '--bkbg-cal-header-color': a.headerColor,
59 '--bkbg-cal-grid-bg': a.gridBg,
60 '--bkbg-cal-day-border': a.dayBorderColor,
61 '--bkbg-cal-day-hover': a.dayHoverBg,
62 '--bkbg-cal-today-bg': a.todayBg,
63 '--bkbg-cal-today-color': a.todayColor,
64 '--bkbg-cal-other-color': a.otherMonthColor,
65 '--bkbg-cal-title-color': a.titleColor,
66 '--bkbg-cal-day-name-color': a.dayNameColor,
67 '--bkbg-cal-list-title': a.listTitleColor,
68 '--bkbg-cal-list-date': a.listDateColor,
69 '--bkbg-cal-card-bg': a.cardBg,
70 '--bkbg-cal-card-border': a.cardBorder,
71 '--bkbg-cal-card-r': a.cardRadius + 'px',
72 paddingTop: a.paddingTop + 'px',
73 paddingBottom: a.paddingBottom + 'px',
74 backgroundColor: a.bgColor || undefined,
75 }, _tv(a.typoTitle, '--bkbg-cal-title-'));
76 }
77
78 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
79 var isOpen = openKey === key;
80 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
81 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
82 el('div', { style: { position: 'relative', flexShrink: 0 } },
83 el('button', { type: 'button', onClick: function () { setOpenKey(isOpen ? null : key); }, style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#ccc' } }),
84 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
85 el('div', { style: { padding: '8px' } },
86 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
87 )
88 )
89 )
90 );
91 }
92
93 /* ── Calendar grid ─────────────────────────────────────────────── */
94 function CalendarGrid(props) {
95 var year = props.year;
96 var month = props.month;
97 var events = props.events;
98 var a = props.a;
99 var today = new Date();
100
101 /* Day names */
102 var dayNames = props.a.firstDayMonday
103 ? ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
104 : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
105
106 /* Events indexed by date */
107 var eventsByDate = {};
108 events.forEach(function (ev) {
109 var d = ev.date;
110 if (!eventsByDate[d]) eventsByDate[d] = [];
111 eventsByDate[d].push(ev);
112 });
113
114 /* Build grid cells */
115 var firstDay = new Date(year, month, 1).getDay(); /* 0=Sun */
116 if (a.firstDayMonday) firstDay = (firstDay + 6) % 7; /* shift: Mon=0 */
117 var daysInMonth = new Date(year, month + 1, 0).getDate();
118 var prevDays = new Date(year, month, 0).getDate();
119
120 var cells = [];
121 /* Prev month tail */
122 for (var p = firstDay - 1; p >= 0; p--) {
123 cells.push({ day: prevDays - p, current: false, dateStr: '' });
124 }
125 /* Current month */
126 for (var d = 1; d <= daysInMonth; d++) {
127 var ds = year + '-' + String(month + 1).padStart(2, '0') + '-' + String(d).padStart(2, '0');
128 cells.push({ day: d, current: true, dateStr: ds });
129 }
130 /* Next month fill */
131 var remaining = 42 - cells.length;
132 for (var n = 1; n <= remaining; n++) {
133 cells.push({ day: n, current: false, dateStr: '' });
134 }
135
136 return el('div', { className: 'bkbg-cal-grid-wrap', style: { background: a.gridBg, borderRadius: a.cardRadius + 'px', overflow: 'hidden', border: '1px solid ' + a.cardBorder } },
137 /* Day name headers */
138 el('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', background: a.headerBg } },
139 dayNames.map(function (name) {
140 return el('div', { key: name, style: { textAlign: 'center', padding: '10px 4px', fontSize: '12px', fontWeight: 700, color: a.headerColor, textTransform: 'uppercase', letterSpacing: '0.05em' } }, name);
141 })
142 ),
143 /* Day cells */
144 el('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7,1fr)' } },
145 cells.map(function (cell, idx) {
146 var isToday = cell.current && a.highlightToday
147 && today.getFullYear() === year
148 && today.getMonth() === month
149 && today.getDate() === cell.day;
150 var cellEvents = cell.dateStr ? (eventsByDate[cell.dateStr] || []) : [];
151
152 return el('div', { key: idx,
153 style: {
154 minHeight: '80px',
155 padding: '6px',
156 borderRight: '1px solid ' + a.dayBorderColor,
157 borderBottom: '1px solid ' + a.dayBorderColor,
158 background: a.gridBg,
159 opacity: cell.current ? 1 : 0.4,
160 }
161 },
162 /* Day number */
163 el('div', { style: {
164 width: '26px', height: '26px', borderRadius: '50%',
165 display: 'flex', alignItems: 'center', justifyContent: 'center',
166 fontSize: '13px', fontWeight: isToday ? 800 : 500,
167 background: isToday ? a.todayBg : 'transparent',
168 color: isToday ? a.todayColor : (cell.current ? a.titleColor : a.otherMonthColor),
169 marginBottom: '4px',
170 }}, cell.day),
171 /* Events */
172 cellEvents.slice(0, 3).map(function (ev) {
173 return el('div', { key: ev.id, style: {
174 background: ev.color || a.accentColor,
175 color: '#fff',
176 fontSize: '10px',
177 fontWeight: 600,
178 padding: '1px 5px',
179 borderRadius: '3px',
180 marginBottom: '2px',
181 overflow: 'hidden',
182 textOverflow: 'ellipsis',
183 whiteSpace: 'nowrap',
184 }}, ev.title);
185 }),
186 cellEvents.length > 3 && el('div', { style: { fontSize: '10px', color: a.accentColor, fontWeight: 700 } }, '+' + (cellEvents.length - 3) + ' more')
187 );
188 })
189 )
190 );
191 }
192
193 registerBlockType('blockenberg/calendar', {
194 title: __('Events Calendar', 'blockenberg'),
195 icon: 'calendar-alt',
196 category: 'bkbg-interactive',
197
198 edit: function (props) {
199 var a = props.attributes;
200 var setAttributes = props.setAttributes;
201
202 var openColorKeyState = useState(null);
203 var openColorKey = openColorKeyState[0];
204 var setOpenColorKey = openColorKeyState[1];
205
206 /* Navigate months in editor */
207 var today = new Date();
208 var viewDateState = useState({ year: today.getFullYear(), month: today.getMonth() });
209 var viewDate = viewDateState[0];
210 var setViewDate = viewDateState[1];
211
212 /* Editing event */
213 var editEventState = useState(null);
214 var editEventId = editEventState[0];
215 var setEditEventId = editEventState[1];
216
217 var blockProps = useBlockProps({ style: buildWrapStyle(a) });
218
219 function cc(key, label, attrKey) {
220 return renderColorControl(key, label, a[attrKey], function (val) {
221 var upd = {}; upd[attrKey] = val; setAttributes(upd);
222 }, openColorKey, setOpenColorKey);
223 }
224
225 function updateEvent(id, key, val) {
226 setAttributes({ events: a.events.map(function (ev) {
227 if (ev.id !== id) return ev;
228 var u = Object.assign({}, ev); u[key] = val; return u;
229 }) });
230 }
231
232 function removeEvent(id) {
233 setAttributes({ events: a.events.filter(function (ev) { return ev.id !== id; }) });
234 }
235
236 function addEvent() {
237 var today2 = new Date();
238 var ds = today2.getFullYear() + '-' + String(today2.getMonth()+1).padStart(2,'0') + '-' + String(today2.getDate()).padStart(2,'0');
239 var newId = makeId();
240 setAttributes({ events: a.events.concat([{ id: newId, title: 'New Event', date: ds, endDate: '', color: a.accentColor, url: '', allDay: true, category: '' }]) });
241 setEditEventId(newId);
242 }
243
244 var prevMonth = function () {
245 setViewDate(function (vd) {
246 var m = vd.month - 1;
247 var y = vd.year;
248 if (m < 0) { m = 11; y--; }
249 return { year: y, month: m };
250 });
251 };
252 var nextMonth = function () {
253 setViewDate(function (vd) {
254 var m = vd.month + 1;
255 var y = vd.year;
256 if (m > 11) { m = 0; y++; }
257 return { year: y, month: m };
258 });
259 };
260
261 /* Sort upcoming events for list */
262 var upcoming = a.events.slice().sort(function (a2, b2) { return a2.date > b2.date ? 1 : -1; }).slice(0, 8);
263
264 return el(Fragment, null,
265 el(InspectorControls, null,
266 el(PanelBody, { title: __('Events', 'blockenberg'), initialOpen: true },
267 a.events.map(function (ev) {
268 var isEdit = editEventId === ev.id;
269 return el('div', { key: ev.id, style: { border: '1px solid #e0e0e0', borderRadius: '8px', marginBottom: '8px', overflow: 'hidden' } },
270 el('div', { onClick: function () { setEditEventId(isEdit ? null : ev.id); },
271 style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '8px 10px', cursor: 'pointer', background: isEdit ? '#f0e9ff' : '#fafafa' } },
272 el('span', { style: { width: '10px', height: '10px', borderRadius: '50%', background: ev.color || '#6c3fb5', flexShrink: 0 } }),
273 el('span', { style: { flex: 1, fontSize: '12px', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, ev.title),
274 el('span', { style: { fontSize: '11px', color: '#888', flexShrink: 0 } }, ev.date)
275 ),
276 isEdit && el('div', { style: { padding: '10px', borderTop: '1px solid #e0e0e0' } },
277 el(TextControl, { label: __('Title', 'blockenberg'), value: ev.title, onChange: function (v) { updateEvent(ev.id, 'title', v); } }),
278 el(TextControl, { label: __('Date (YYYY-MM-DD)', 'blockenberg'), value: ev.date, onChange: function (v) { updateEvent(ev.id, 'date', v); } }),
279 el(TextControl, { label: __('Category', 'blockenberg'), value: ev.category || '', onChange: function (v) { updateEvent(ev.id, 'category', v); } }),
280 el(TextControl, { label: __('URL (optional)', 'blockenberg'), value: ev.url || '', onChange: function (v) { updateEvent(ev.id, 'url', v); } }),
281 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } },
282 el('span', { style: { fontSize: '12px', fontWeight: 600 } }, __('Color:', 'blockenberg')),
283 el('input', { type: 'color', value: ev.color || '#6c3fb5', onChange: function (e) { updateEvent(ev.id, 'color', e.target.value); }, style: { width: '32px', height: '28px', padding: 0, border: 'none', cursor: 'pointer', background: 'none' } })
284 ),
285 el(Button, { isDestructive: true, variant: 'tertiary', size: 'compact', onClick: function () { removeEvent(ev.id); } }, __('Remove Event', 'blockenberg'))
286 )
287 );
288 }),
289 el(Button, { variant: 'secondary', onClick: addEvent, style: { width: '100%', justifyContent: 'center' } }, '+ ' + __('Add Event', 'blockenberg'))
290 ),
291 el(PanelBody, { title: __('Options', 'blockenberg'), initialOpen: false },
292 el(ToggleControl, { label: __('Show title', 'blockenberg'), checked: a.showTitle, onChange: function (v) { setAttributes({ showTitle: v }); }, __nextHasNoMarginBottom: true }),
293 el(ToggleControl, { label: __('Highlight today', 'blockenberg'), checked: a.highlightToday, onChange: function (v) { setAttributes({ highlightToday: v }); }, __nextHasNoMarginBottom: true }),
294 el(ToggleControl, { label: __('Week starts on Monday', 'blockenberg'), checked: a.firstDayMonday, onChange: function (v) { setAttributes({ firstDayMonday: v }); }, __nextHasNoMarginBottom: true }),
295 el(ToggleControl, { label: __('Show event list below', 'blockenberg'), checked: a.showEventList, onChange: function (v) { setAttributes({ showEventList: v }); }, __nextHasNoMarginBottom: true }),
296 el(ToggleControl, { label: __('Show year in header', 'blockenberg'), checked: a.showYear, onChange: function (v) { setAttributes({ showYear: v }); }, __nextHasNoMarginBottom: true }),
297 el(RangeControl, { label: __('Card radius (px)', 'blockenberg'), value: a.cardRadius, min: 0, max: 24, onChange: function (v) { setAttributes({ cardRadius: v }); } }),
298 ),
299
300 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
301 el(getTypographyControl(), { label: __('Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { setAttributes({ typoTitle: v }); } })
302 ),
303 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
304 cc('accentColor', __('Accent', 'blockenberg'), 'accentColor'),
305 cc('headerBg', __('Calendar header bg', 'blockenberg'), 'headerBg'),
306 cc('headerColor', __('Header text', 'blockenberg'), 'headerColor'),
307 cc('gridBg', __('Grid background', 'blockenberg'), 'gridBg'),
308 cc('dayBorderColor', __('Day borders', 'blockenberg'), 'dayBorderColor'),
309 cc('dayHoverBg', __('Day hover bg', 'blockenberg'), 'dayHoverBg'),
310 cc('todayBg', __('Today circle bg', 'blockenberg'), 'todayBg'),
311 cc('todayColor', __('Today number', 'blockenberg'), 'todayColor'),
312 cc('otherMonthColor', __('Other month day', 'blockenberg'), 'otherMonthColor'),
313 cc('titleColor', __('Day number', 'blockenberg'), 'titleColor'),
314 cc('dayNameColor', __('Day names', 'blockenberg'), 'dayNameColor'),
315 cc('listTitleColor', __('List event title','blockenberg'), 'listTitleColor'),
316 cc('listDateColor', __('List event date', 'blockenberg'), 'listDateColor'),
317 cc('cardBg', __('Card background', 'blockenberg'), 'cardBg'),
318 cc('cardBorder', __('Card border', 'blockenberg'), 'cardBorder'),
319 cc('bgColor', __('Section bg', 'blockenberg'), 'bgColor')
320 ),
321 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
322 el(RangeControl, { label: __('Padding top', 'blockenberg'), value: a.paddingTop, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingTop: v }); } }),
323 el(RangeControl, { label: __('Padding bottom', 'blockenberg'), value: a.paddingBottom, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingBottom: v }); } })
324 )
325 ),
326
327 el('div', blockProps,
328 /* Title */
329 a.showTitle && el(RichText, { tagName: 'h2', className: 'bkbg-cal-title', value: a.title, onChange: function (v) { setAttributes({ title: v }); }, placeholder: __('Events Calendar', 'blockenberg'), style: { color: a.titleColor, margin: '0 0 20px', textAlign: 'center' } }),
330
331 /* Month navigation */
332 el('div', { className: 'bkbg-cal-nav', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px' } },
333 el('button', { onClick: prevMonth, style: { border: 'none', background: 'none', cursor: 'pointer', fontSize: '18px', color: a.accentColor, padding: '4px 10px', borderRadius: '6px' } }, ''),
334 el('span', { style: { fontSize: '16px', fontWeight: 700, color: a.titleColor } },
335 MONTHS_EN[viewDate.month] + (a.showYear ? ' ' + viewDate.year : '')
336 ),
337 el('button', { onClick: nextMonth, style: { border: 'none', background: 'none', cursor: 'pointer', fontSize: '18px', color: a.accentColor, padding: '4px 10px', borderRadius: '6px' } }, '')
338 ),
339
340 /* Calendar grid */
341 el(CalendarGrid, { year: viewDate.year, month: viewDate.month, events: a.events, a: a }),
342
343 /* Event list */
344 a.showEventList && upcoming.length > 0 && el('div', { className: 'bkbg-cal-event-list', style: { marginTop: '24px' } },
345 el('h3', { style: { fontSize: '14px', fontWeight: 700, color: a.listTitleColor, marginBottom: '12px', textTransform: 'uppercase', letterSpacing: '0.06em' } }, __('Upcoming Events', 'blockenberg')),
346 upcoming.map(function (ev) {
347 return el('div', { key: ev.id, className: 'bkbg-cal-event-item', style: { display: 'flex', alignItems: 'center', gap: '12px', padding: '10px 14px', borderRadius: '8px', marginBottom: '6px', background: a.cardBg, border: '1px solid ' + a.cardBorder } },
348 el('div', { style: { width: '4px', alignSelf: 'stretch', borderRadius: '4px', background: ev.color || a.accentColor, flexShrink: 0 } }),
349 el('div', { style: { flex: 1, minWidth: 0 } },
350 el('p', { style: { margin: 0, fontSize: '14px', fontWeight: 700, color: a.listTitleColor, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, ev.title),
351 ev.category && el('p', { style: { margin: '2px 0 0', fontSize: '11px', color: a.accentColor, fontWeight: 600 } }, ev.category)
352 ),
353 el('span', { style: { fontSize: '12px', color: a.listDateColor, fontWeight: 600, flexShrink: 0 } }, ev.date)
354 );
355 })
356 )
357 )
358 );
359 },
360
361 save: function (props) {
362 var a = props.attributes;
363 var sortedEvents = a.events.slice().sort(function (x, y) { return x.date > y.date ? 1 : -1; });
364
365 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-cal-wrapper', style: buildWrapStyle(a) }),
366 a.showTitle && el(RichText.Content, { tagName: 'h2', className: 'bkbg-cal-title', value: a.title }),
367
368 el('div', { className: 'bkbg-cal-nav' },
369 el('button', { type: 'button', className: 'bkbg-cal-nav-btn bkbg-cal-prev', 'aria-label': __('Previous month', 'blockenberg') }, ''),
370 el('span', { className: 'bkbg-cal-month-label' }),
371 el('button', { type: 'button', className: 'bkbg-cal-nav-btn bkbg-cal-next', 'aria-label': __('Next month', 'blockenberg') }, '')
372 ),
373
374 el('div', {
375 className: 'bkbg-cal-grid-wrap',
376 'data-events': JSON.stringify(a.events),
377 'data-first-monday': a.firstDayMonday ? '1' : '0',
378 'data-highlight-today': a.highlightToday ? '1' : '0',
379 'data-show-year': a.showYear ? '1' : '0',
380 }),
381
382 a.showEventList && el('div', { className: 'bkbg-cal-event-list', 'data-events': JSON.stringify(sortedEvents.slice(0, 8)) })
383 );
384 }
385 });
386 }() );
387