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

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

486 lines 22.1 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 useEffect = wp.element.useEffect;
4 var __ = wp.i18n.__;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var useBlockProps = wp.blockEditor.useBlockProps;
8 var InnerBlocks = wp.blockEditor.InnerBlocks;
9 var useSelect = wp.data.useSelect;
10 var useDispatch = wp.data.useDispatch;
11 var PanelBody = wp.components.PanelBody;
12 var SelectControl = wp.components.SelectControl;
13 var RangeControl = wp.components.RangeControl;
14 var BaseControl = wp.components.BaseControl;
15 var Button = wp.components.Button;
16
17 // Custom Appender that opens sidebar inserter instead of popover
18 function SidebarInserterAppender(props) {
19 var rootClientId = props.rootClientId;
20
21 // Get the inserter toggle function
22 var insertionPoint = useSelect(function (select) {
23 var blockEditor = select('core/block-editor');
24 var innerBlocks = blockEditor.getBlocks(rootClientId);
25 return {
26 index: innerBlocks.length
27 };
28 }, [rootClientId]);
29
30 function handleClick() {
31 // Toggle the global inserter sidebar (not the popover).
32 try {
33 var editPostDispatch = wp.data.dispatch('core/edit-post');
34 if (editPostDispatch && typeof editPostDispatch.setIsInserterOpened === 'function') {
35 editPostDispatch.setIsInserterOpened(true);
36 }
37 } catch (e) {}
38
39 try {
40 var editSiteDispatch = wp.data.dispatch('core/edit-site');
41 if (editSiteDispatch && typeof editSiteDispatch.setIsInserterOpened === 'function') {
42 editSiteDispatch.setIsInserterOpened(true);
43 }
44 } catch (e) {}
45
46 // Attempt to set an insertion point inside this column.
47 try {
48 var beDispatch = wp.data.dispatch('core/block-editor');
49 if (beDispatch) {
50 if (typeof beDispatch.setInsertionPoint === 'function') {
51 beDispatch.setInsertionPoint(rootClientId, insertionPoint.index);
52 } else if (typeof beDispatch.__unstableSetInsertionPoint === 'function') {
53 try {
54 beDispatch.__unstableSetInsertionPoint({ rootClientId: rootClientId, index: insertionPoint.index });
55 } catch (e2) {
56 beDispatch.__unstableSetInsertionPoint(rootClientId, insertionPoint.index);
57 }
58 }
59 }
60 } catch (e) {}
61 }
62
63 return el(Button, {
64 className: 'bkbg-sidebar-appender block-editor-button-block-appender',
65 onClick: handleClick,
66 label: __('Add block', 'blockenberg')
67 },
68 el('svg', {
69 xmlns: 'http://www.w3.org/2000/svg',
70 viewBox: '0 0 24 24',
71 width: '24',
72 height: '24',
73 'aria-hidden': 'true',
74 focusable: 'false'
75 },
76 el('path', { d: 'M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z' })
77 )
78 );
79 }
80
81 // Spacing token scale for inner padding
82 var paddingOptions = [
83 { label: __('None', 'blockenberg'), value: 'none' },
84 { label: __('XS (8px)', 'blockenberg'), value: 'xs' },
85 { label: __('S (16px)', 'blockenberg'), value: 's' },
86 { label: __('M (24px)', 'blockenberg'), value: 'm' },
87 { label: __('L (32px)', 'blockenberg'), value: 'l' },
88 { label: __('XL (48px)', 'blockenberg'), value: 'xl' }
89 ];
90
91 var vAlignOptions = [
92 { label: __('Top', 'blockenberg'), value: 'top' },
93 { label: __('Middle', 'blockenberg'), value: 'middle' },
94 { label: __('Bottom', 'blockenberg'), value: 'bottom' }
95 ];
96
97 var spacingUnitOptions = [
98 { label: 'px', value: 'px' },
99 { label: '%', value: '%' },
100 { label: 'em', value: 'em' },
101 { label: 'rem', value: 'rem' },
102 { label: 'vw', value: 'vw' },
103 { label: 'vh', value: 'vh' },
104 { label: __('Custom', 'blockenberg'), value: 'custom' }
105 ];
106
107 function getSpacingSideValue(attrs, type, side) {
108 var cap = side.charAt(0).toUpperCase() + side.slice(1);
109 var value = attrs[type + cap];
110 var unit = attrs[type + cap + 'Unit'] || 'px';
111 var customUnit = attrs[type + cap + 'CustomUnit'] || '';
112
113 if (value === undefined || value === null) return undefined;
114 var raw = String(value).trim();
115 if (!raw) return undefined;
116
117 if (unit === 'custom') {
118 return customUnit ? raw + customUnit : raw;
119 }
120
121 return raw + unit;
122 }
123
124 function getSpacingStyles(attrs) {
125 return {
126 paddingTop: getSpacingSideValue(attrs, 'padding', 'top'),
127 paddingRight: getSpacingSideValue(attrs, 'padding', 'right'),
128 paddingBottom: getSpacingSideValue(attrs, 'padding', 'bottom'),
129 paddingLeft: getSpacingSideValue(attrs, 'padding', 'left'),
130 marginTop: getSpacingSideValue(attrs, 'margin', 'top'),
131 marginRight: getSpacingSideValue(attrs, 'margin', 'right'),
132 marginBottom: getSpacingSideValue(attrs, 'margin', 'bottom'),
133 marginLeft: getSpacingSideValue(attrs, 'margin', 'left')
134 };
135 }
136
137 // Column icon
138 var columnIcon = el('svg', {
139 width: 24,
140 height: 24,
141 viewBox: '0 0 24 24',
142 xmlns: 'http://www.w3.org/2000/svg'
143 },
144 el('path', {
145 d: 'M5 4h14v16H5V4zm2 2v12h10V6H7z',
146 fill: 'currentColor'
147 })
148 );
149
150 // Generate unique ID
151 function generateUid() {
152 return 'col-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
153 }
154
155 registerBlockType('blockenberg/column', {
156 title: __('Column', 'blockenberg'),
157 icon: columnIcon,
158 category: 'blockenberg',
159 parent: ['blockenberg/row'],
160 description: __('A column container within a row for content.', 'blockenberg'),
161
162 edit: function (props) {
163 var attributes = props.attributes;
164 var setAttributes = props.setAttributes;
165 var clientId = props.clientId;
166 var a = attributes;
167
168 var moveBlockToPosition = useDispatch('core/block-editor').moveBlockToPosition;
169 var replaceInnerBlocks = useDispatch('core/block-editor').replaceInnerBlocks;
170 var updateBlockAttributes = useDispatch('core/block-editor').updateBlockAttributes;
171 var createBlock = wp.blocks.createBlock;
172
173 // Generate UID if not set
174 useEffect(function () {
175 if (!a.uid) {
176 setAttributes({ uid: generateUid() });
177 }
178 }, []);
179
180 // Get parent row info (responsive mode + column index)
181 var parentData = useSelect(function (select) {
182 var parents = select('core/block-editor').getBlockParents(clientId);
183 for (var i = parents.length - 1; i >= 0; i--) {
184 var parent = select('core/block-editor').getBlock(parents[i]);
185 if (parent && parent.name === 'blockenberg/row') {
186 var siblings = parent.innerBlocks || [];
187 var colIndex = siblings.findIndex(function (b) { return b.clientId === clientId; });
188 return {
189 rowClientId: parents[i],
190 colIndex: colIndex,
191 colCount: siblings.length,
192 responsiveMode: parent.attributes.responsiveMode || 'desktop',
193 stackOnMobile: parent.attributes.stackOnMobile !== false
194 };
195 }
196 }
197 return { rowClientId: null, colIndex: -1, colCount: 0, responsiveMode: 'desktop', stackOnMobile: true };
198 }, [clientId]);
199
200 var responsiveMode = parentData.responsiveMode;
201 var stackOnMobile = parentData.stackOnMobile;
202
203 function moveSelf(direction) {
204 if (!parentData.rowClientId) return;
205 if (parentData.colIndex < 0) return;
206 var newIndex = direction === 'left' ? parentData.colIndex - 1 : parentData.colIndex + 1;
207 if (newIndex < 0 || newIndex >= parentData.colCount) return;
208 moveBlockToPosition(clientId, parentData.rowClientId, parentData.rowClientId, newIndex);
209 }
210
211 function removeSelfWithConfirm() {
212 if (!parentData.rowClientId) return;
213 if (parentData.colCount <= 1) return;
214
215 var msg = __('Delete this column? All content inside will be removed.', 'blockenberg');
216 if (typeof window !== 'undefined' && window.confirm && !window.confirm(msg)) return;
217
218 var blocks = wp.data.select('core/block-editor').getBlocks(parentData.rowClientId);
219 if (!blocks || !blocks.length) return;
220
221 var newCount = blocks.length - 1;
222 if (newCount < 1) return;
223
224 var equalWidth = Math.round((100 / newCount) * 10) / 10;
225 var newColumns = [];
226 var newIndex = 0;
227
228 for (var i = 0; i < blocks.length; i++) {
229 var existingCol = blocks[i];
230 if (!existingCol || !existingCol.clientId) continue;
231 if (existingCol.clientId === clientId) continue;
232
233 var width = equalWidth;
234 if (newIndex === newCount - 1) {
235 width = 100 - (equalWidth * (newCount - 1));
236 width = Math.round(width * 10) / 10;
237 }
238
239 var recreatedCol = createBlock('blockenberg/column', {
240 widths: { desktop: width, tablet: width, mobile: 100 },
241 uid: (existingCol.attributes && existingCol.attributes.uid) || ('col-' + Date.now() + '-' + newIndex),
242 paddingInner: (existingCol.attributes && existingCol.attributes.paddingInner) || 'none'
243 }, existingCol.innerBlocks);
244
245 recreatedCol.attributes = Object.assign({}, existingCol.attributes || {}, recreatedCol.attributes || {});
246
247 newColumns.push(recreatedCol);
248 newIndex++;
249 }
250
251 replaceInnerBlocks(parentData.rowClientId, newColumns, false);
252 updateBlockAttributes(parentData.rowClientId, { columnsCount: newCount });
253 }
254
255 // Get width for current mode with fallback
256 function getWidth(mode) {
257 var widths = a.widths || { desktop: 50, tablet: 50, mobile: 100 };
258 if (mode === 'mobile') {
259 return widths.mobile !== undefined ? widths.mobile :
260 (widths.tablet !== undefined ? widths.tablet :
261 (widths.desktop !== undefined ? widths.desktop : 50));
262 }
263 if (mode === 'tablet') {
264 return widths.tablet !== undefined ? widths.tablet :
265 (widths.desktop !== undefined ? widths.desktop : 50);
266 }
267 return widths.desktop !== undefined ? widths.desktop : 50;
268 }
269
270 var currentWidth = getWidth(responsiveMode);
271 var displayWidth = (responsiveMode === 'mobile' && stackOnMobile) ? 100 : currentWidth;
272
273 // Update width for specific mode
274 function updateWidth(mode, value) {
275 var newWidths = Object.assign({}, a.widths || { desktop: 50, tablet: 50, mobile: 100 });
276 newWidths[mode] = value;
277 setAttributes({ widths: newWidths });
278 }
279
280 var blockProps = useBlockProps({
281 className: [
282 'bkbg-column',
283 'bkbg-column--padding-' + a.paddingInner,
284 'bkbg-column--valign-' + (a.vAlign || 'top')
285 ].join(' '),
286 style: Object.assign({}, {
287 '--bkbg-col-desktop': (a.widths && a.widths.desktop || 50) + '%',
288 '--bkbg-col-tablet': (a.widths && a.widths.tablet || a.widths && a.widths.desktop || 50) + '%',
289 '--bkbg-col-mobile': (a.widths && a.widths.mobile || 100) + '%',
290 flexBasis: displayWidth + '%',
291 maxWidth: displayWidth + '%',
292 width: displayWidth + '%'
293 }, getSpacingStyles(a)),
294 'data-uid': a.uid
295 });
296
297 function renderSpacingControl(type, label) {
298 var sides = [
299 { key: 'Top', label: __('Top', 'blockenberg') },
300 { key: 'Left', label: __('Left', 'blockenberg') },
301 { key: 'Bottom', label: __('Bottom', 'blockenberg') },
302 { key: 'Right', label: __('Right', 'blockenberg') }
303 ];
304
305 return el('div', { className: 'bkbg-spacing-control' },
306 el('div', { className: 'bkbg-spacing-control__title' }, label),
307 el('div', { className: 'bkbg-spacing-control__grid' },
308 sides.map(function (side) {
309 var valueKey = type + side.key;
310 var unitKey = type + side.key + 'Unit';
311 var customUnitKey = type + side.key + 'CustomUnit';
312
313 return el('div', { className: 'bkbg-spacing-control__item', key: valueKey },
314 el('label', { className: 'bkbg-spacing-control__label' }, side.label),
315 el('div', { className: 'bkbg-spacing-control__row' },
316 el('input', {
317 type: 'text',
318 className: 'components-text-control__input',
319 value: a[valueKey] || '',
320 placeholder: '0',
321 onChange: function (e) {
322 var next = {};
323 next[valueKey] = e.target.value;
324 setAttributes(next);
325 }
326 }),
327 el('select', {
328 className: 'components-select-control__input',
329 value: a[unitKey] || 'px',
330 onChange: function (e) {
331 var next = {};
332 next[unitKey] = e.target.value;
333 setAttributes(next);
334 }
335 }, spacingUnitOptions.map(function (opt) {
336 return el('option', { key: opt.value, value: opt.value }, opt.label);
337 }))
338 ),
339 (a[unitKey] || 'px') === 'custom' && el('input', {
340 type: 'text',
341 className: 'components-text-control__input bkbg-spacing-control__custom-unit',
342 value: a[customUnitKey] || '',
343 placeholder: __('unit, e.g. ch', 'blockenberg'),
344 onChange: function (e) {
345 var next = {};
346 next[customUnitKey] = e.target.value;
347 setAttributes(next);
348 }
349 })
350 );
351 })
352 )
353 );
354 }
355
356 // Inspector controls
357 var inspector = el(InspectorControls, {},
358 el(PanelBody, { title: __('Column Settings', 'blockenberg'), initialOpen: true },
359 el(BaseControl, {
360 label: __('Column Widths', 'blockenberg'),
361 help: __('Widths are typically controlled via drag handles on the row. You can fine-tune them here.', 'blockenberg')
362 }),
363 el('div', { className: 'bkbg-column-widths-controls' },
364 el(RangeControl, {
365 label: __('Desktop', 'blockenberg'),
366 value: a.widths && a.widths.desktop || 50,
367 onChange: function (v) { updateWidth('desktop', v); },
368 min: 5,
369 max: 100,
370 step: 0.5,
371 marks: [
372 { value: 25, label: '25%' },
373 { value: 50, label: '50%' },
374 { value: 75, label: '75%' },
375 { value: 100, label: '100%' }
376 ]
377 }),
378 el(RangeControl, {
379 label: __('Tablet', 'blockenberg'),
380 value: a.widths && a.widths.tablet || a.widths && a.widths.desktop || 50,
381 onChange: function (v) { updateWidth('tablet', v); },
382 min: 5,
383 max: 100,
384 step: 0.5
385 }),
386 el(RangeControl, {
387 label: __('Mobile', 'blockenberg'),
388 value: a.widths && a.widths.mobile || 100,
389 onChange: function (v) { updateWidth('mobile', v); },
390 min: 5,
391 max: 100,
392 step: 0.5,
393 help: stackOnMobile ? __('Mobile width is overridden when "Stack on Mobile" is enabled in the row settings.', 'blockenberg') : ''
394 })
395 ),
396 el(SelectControl, {
397 label: __('Vertical Alignment', 'blockenberg'),
398 value: a.vAlign || 'top',
399 options: vAlignOptions,
400 onChange: function (v) { setAttributes({ vAlign: v }); }
401 }),
402 ),
403 el(PanelBody, { title: __('Column Info', 'blockenberg'), initialOpen: false },
404 el('div', { className: 'bkbg-column-info' },
405 el('p', {},
406 el('strong', {}, __('Current Mode:', 'blockenberg')),
407 ' ' + responsiveMode.charAt(0).toUpperCase() + responsiveMode.slice(1)
408 ),
409 el('p', {},
410 el('strong', {}, __('Current Width:', 'blockenberg')),
411 ' ' + Math.round(currentWidth * 10) / 10 + '%'
412 ),
413 el('p', {},
414 el('strong', {}, __('UID:', 'blockenberg')),
415 ' ' + (a.uid || 'N/A')
416 )
417 )
418 )
419 );
420
421 return el('div', blockProps,
422 inspector,
423 // Move controls (hover)
424 parentData.colCount > 1 && el('div', { className: 'bkbg-column__move-actions' },
425 el(Button, {
426 className: 'bkbg-column__move-btn',
427 icon: 'arrow-left-alt2',
428 label: __('Move column left', 'blockenberg'),
429 onClick: function () { moveSelf('left'); },
430 disabled: parentData.colIndex <= 0
431 }),
432 el(Button, {
433 className: 'bkbg-column__move-btn',
434 icon: 'no-alt',
435 label: __('Delete column', 'blockenberg'),
436 onClick: removeSelfWithConfirm
437 }),
438 el(Button, {
439 className: 'bkbg-column__move-btn',
440 icon: 'arrow-right-alt2',
441 label: __('Move column right', 'blockenberg'),
442 onClick: function () { moveSelf('right'); },
443 disabled: parentData.colIndex >= parentData.colCount - 1
444 })
445 ),
446 el('div', { className: 'bkbg-column__inner' },
447 el(InnerBlocks, {
448 templateLock: false,
449 renderAppender: function () {
450 return el(SidebarInserterAppender, { rootClientId: clientId });
451 }
452 })
453 ),
454 el('div', { className: 'bkbg-column__width-label' },
455 Math.round(currentWidth) + '%'
456 )
457 );
458 },
459
460 save: function (props) {
461 var a = props.attributes;
462 var widths = a.widths || { desktop: 50, tablet: 50, mobile: 100 };
463
464 var blockProps = useBlockProps.save({
465 className: [
466 'bkbg-column',
467 'bkbg-column--padding-' + a.paddingInner,
468 'bkbg-column--valign-' + (a.vAlign || 'top')
469 ].join(' '),
470 style: Object.assign({}, {
471 '--bkbg-col-desktop': widths.desktop + '%',
472 '--bkbg-col-tablet': (widths.tablet || widths.desktop) + '%',
473 '--bkbg-col-mobile': (widths.mobile || 100) + '%'
474 }, getSpacingStyles(a)),
475 'data-uid': a.uid
476 });
477
478 return el('div', blockProps,
479 el('div', { className: 'bkbg-column__inner' },
480 el(InnerBlocks.Content)
481 )
482 );
483 }
484 });
485 });
486