| 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 |
// Column icon |
| 92 |
var columnIcon = el('svg', { |
| 93 |
width: 24, |
| 94 |
height: 24, |
| 95 |
viewBox: '0 0 24 24', |
| 96 |
xmlns: 'http://www.w3.org/2000/svg' |
| 97 |
}, |
| 98 |
el('path', { |
| 99 |
d: 'M5 4h14v16H5V4zm2 2v12h10V6H7z', |
| 100 |
fill: 'currentColor' |
| 101 |
}) |
| 102 |
); |
| 103 |
|
| 104 |
// Generate unique ID |
| 105 |
function generateUid() { |
| 106 |
return 'col-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); |
| 107 |
} |
| 108 |
|
| 109 |
registerBlockType('blockenberg/column', { |
| 110 |
title: __('Column', 'blockenberg'), |
| 111 |
icon: columnIcon, |
| 112 |
category: 'blockenberg', |
| 113 |
parent: ['blockenberg/row'], |
| 114 |
description: __('A column container within a row for content.', 'blockenberg'), |
| 115 |
|
| 116 |
edit: function (props) { |
| 117 |
var attributes = props.attributes; |
| 118 |
var setAttributes = props.setAttributes; |
| 119 |
var clientId = props.clientId; |
| 120 |
var a = attributes; |
| 121 |
|
| 122 |
var moveBlockToPosition = useDispatch('core/block-editor').moveBlockToPosition; |
| 123 |
var replaceInnerBlocks = useDispatch('core/block-editor').replaceInnerBlocks; |
| 124 |
var updateBlockAttributes = useDispatch('core/block-editor').updateBlockAttributes; |
| 125 |
var createBlock = wp.blocks.createBlock; |
| 126 |
|
| 127 |
// Generate UID if not set |
| 128 |
useEffect(function () { |
| 129 |
if (!a.uid) { |
| 130 |
setAttributes({ uid: generateUid() }); |
| 131 |
} |
| 132 |
}, []); |
| 133 |
|
| 134 |
// Get parent row info (responsive mode + column index) |
| 135 |
var parentData = useSelect(function (select) { |
| 136 |
var parents = select('core/block-editor').getBlockParents(clientId); |
| 137 |
for (var i = parents.length - 1; i >= 0; i--) { |
| 138 |
var parent = select('core/block-editor').getBlock(parents[i]); |
| 139 |
if (parent && parent.name === 'blockenberg/row') { |
| 140 |
var siblings = parent.innerBlocks || []; |
| 141 |
var colIndex = siblings.findIndex(function (b) { return b.clientId === clientId; }); |
| 142 |
return { |
| 143 |
rowClientId: parents[i], |
| 144 |
colIndex: colIndex, |
| 145 |
colCount: siblings.length, |
| 146 |
responsiveMode: parent.attributes.responsiveMode || 'desktop', |
| 147 |
stackOnMobile: parent.attributes.stackOnMobile !== false |
| 148 |
}; |
| 149 |
} |
| 150 |
} |
| 151 |
return { rowClientId: null, colIndex: -1, colCount: 0, responsiveMode: 'desktop', stackOnMobile: true }; |
| 152 |
}, [clientId]); |
| 153 |
|
| 154 |
var responsiveMode = parentData.responsiveMode; |
| 155 |
var stackOnMobile = parentData.stackOnMobile; |
| 156 |
|
| 157 |
function moveSelf(direction) { |
| 158 |
if (!parentData.rowClientId) return; |
| 159 |
if (parentData.colIndex < 0) return; |
| 160 |
var newIndex = direction === 'left' ? parentData.colIndex - 1 : parentData.colIndex + 1; |
| 161 |
if (newIndex < 0 || newIndex >= parentData.colCount) return; |
| 162 |
moveBlockToPosition(clientId, parentData.rowClientId, parentData.rowClientId, newIndex); |
| 163 |
} |
| 164 |
|
| 165 |
function removeSelfWithConfirm() { |
| 166 |
if (!parentData.rowClientId) return; |
| 167 |
if (parentData.colCount <= 1) return; |
| 168 |
|
| 169 |
var msg = __('Delete this column? All content inside will be removed.', 'blockenberg'); |
| 170 |
if (typeof window !== 'undefined' && window.confirm && !window.confirm(msg)) return; |
| 171 |
|
| 172 |
var blocks = wp.data.select('core/block-editor').getBlocks(parentData.rowClientId); |
| 173 |
if (!blocks || !blocks.length) return; |
| 174 |
|
| 175 |
var newCount = blocks.length - 1; |
| 176 |
if (newCount < 1) return; |
| 177 |
|
| 178 |
var equalWidth = Math.round((100 / newCount) * 10) / 10; |
| 179 |
var newColumns = []; |
| 180 |
var newIndex = 0; |
| 181 |
|
| 182 |
for (var i = 0; i < blocks.length; i++) { |
| 183 |
var existingCol = blocks[i]; |
| 184 |
if (!existingCol || !existingCol.clientId) continue; |
| 185 |
if (existingCol.clientId === clientId) continue; |
| 186 |
|
| 187 |
var width = equalWidth; |
| 188 |
if (newIndex === newCount - 1) { |
| 189 |
width = 100 - (equalWidth * (newCount - 1)); |
| 190 |
width = Math.round(width * 10) / 10; |
| 191 |
} |
| 192 |
|
| 193 |
var recreatedCol = createBlock('blockenberg/column', { |
| 194 |
widths: { desktop: width, tablet: width, mobile: 100 }, |
| 195 |
uid: (existingCol.attributes && existingCol.attributes.uid) || ('col-' + Date.now() + '-' + newIndex), |
| 196 |
paddingInner: (existingCol.attributes && existingCol.attributes.paddingInner) || 'none' |
| 197 |
}, existingCol.innerBlocks); |
| 198 |
|
| 199 |
newColumns.push(recreatedCol); |
| 200 |
newIndex++; |
| 201 |
} |
| 202 |
|
| 203 |
replaceInnerBlocks(parentData.rowClientId, newColumns, false); |
| 204 |
updateBlockAttributes(parentData.rowClientId, { columnsCount: newCount }); |
| 205 |
} |
| 206 |
|
| 207 |
// Get width for current mode with fallback |
| 208 |
function getWidth(mode) { |
| 209 |
var widths = a.widths || { desktop: 50, tablet: 50, mobile: 100 }; |
| 210 |
if (mode === 'mobile') { |
| 211 |
return widths.mobile !== undefined ? widths.mobile : |
| 212 |
(widths.tablet !== undefined ? widths.tablet : |
| 213 |
(widths.desktop !== undefined ? widths.desktop : 50)); |
| 214 |
} |
| 215 |
if (mode === 'tablet') { |
| 216 |
return widths.tablet !== undefined ? widths.tablet : |
| 217 |
(widths.desktop !== undefined ? widths.desktop : 50); |
| 218 |
} |
| 219 |
return widths.desktop !== undefined ? widths.desktop : 50; |
| 220 |
} |
| 221 |
|
| 222 |
var currentWidth = getWidth(responsiveMode); |
| 223 |
var displayWidth = (responsiveMode === 'mobile' && stackOnMobile) ? 100 : currentWidth; |
| 224 |
|
| 225 |
// Update width for specific mode |
| 226 |
function updateWidth(mode, value) { |
| 227 |
var newWidths = Object.assign({}, a.widths || { desktop: 50, tablet: 50, mobile: 100 }); |
| 228 |
newWidths[mode] = value; |
| 229 |
setAttributes({ widths: newWidths }); |
| 230 |
} |
| 231 |
|
| 232 |
var blockProps = useBlockProps({ |
| 233 |
className: [ |
| 234 |
'bkbg-column', |
| 235 |
'bkbg-column--padding-' + a.paddingInner |
| 236 |
].join(' '), |
| 237 |
style: { |
| 238 |
'--bkbg-col-desktop': (a.widths && a.widths.desktop || 50) + '%', |
| 239 |
'--bkbg-col-tablet': (a.widths && a.widths.tablet || a.widths && a.widths.desktop || 50) + '%', |
| 240 |
'--bkbg-col-mobile': (a.widths && a.widths.mobile || 100) + '%', |
| 241 |
flexBasis: displayWidth + '%', |
| 242 |
maxWidth: displayWidth + '%', |
| 243 |
width: displayWidth + '%' |
| 244 |
}, |
| 245 |
'data-uid': a.uid |
| 246 |
}); |
| 247 |
|
| 248 |
// Inspector controls |
| 249 |
var inspector = el(InspectorControls, {}, |
| 250 |
el(PanelBody, { title: __('Column Settings', 'blockenberg'), initialOpen: true }, |
| 251 |
el(BaseControl, { |
| 252 |
label: __('Column Widths', 'blockenberg'), |
| 253 |
help: __('Widths are typically controlled via drag handles on the row. You can fine-tune them here.', 'blockenberg') |
| 254 |
}), |
| 255 |
el('div', { className: 'bkbg-column-widths-controls' }, |
| 256 |
el(RangeControl, { |
| 257 |
label: __('Desktop', 'blockenberg'), |
| 258 |
value: a.widths && a.widths.desktop || 50, |
| 259 |
onChange: function (v) { updateWidth('desktop', v); }, |
| 260 |
min: 5, |
| 261 |
max: 100, |
| 262 |
step: 0.5, |
| 263 |
marks: [ |
| 264 |
{ value: 25, label: '25%' }, |
| 265 |
{ value: 50, label: '50%' }, |
| 266 |
{ value: 75, label: '75%' }, |
| 267 |
{ value: 100, label: '100%' } |
| 268 |
] |
| 269 |
}), |
| 270 |
el(RangeControl, { |
| 271 |
label: __('Tablet', 'blockenberg'), |
| 272 |
value: a.widths && a.widths.tablet || a.widths && a.widths.desktop || 50, |
| 273 |
onChange: function (v) { updateWidth('tablet', v); }, |
| 274 |
min: 5, |
| 275 |
max: 100, |
| 276 |
step: 0.5 |
| 277 |
}), |
| 278 |
el(RangeControl, { |
| 279 |
label: __('Mobile', 'blockenberg'), |
| 280 |
value: a.widths && a.widths.mobile || 100, |
| 281 |
onChange: function (v) { updateWidth('mobile', v); }, |
| 282 |
min: 5, |
| 283 |
max: 100, |
| 284 |
step: 0.5, |
| 285 |
help: stackOnMobile ? __('Mobile width is overridden when "Stack on Mobile" is enabled in the row settings.', 'blockenberg') : '' |
| 286 |
}) |
| 287 |
), |
| 288 |
el(SelectControl, { |
| 289 |
label: __('Inner Padding', 'blockenberg'), |
| 290 |
value: a.paddingInner, |
| 291 |
options: paddingOptions, |
| 292 |
onChange: function (v) { setAttributes({ paddingInner: v }); } |
| 293 |
}) |
| 294 |
), |
| 295 |
el(PanelBody, { title: __('Column Info', 'blockenberg'), initialOpen: false }, |
| 296 |
el('div', { className: 'bkbg-column-info' }, |
| 297 |
el('p', {}, |
| 298 |
el('strong', {}, __('Current Mode:', 'blockenberg')), |
| 299 |
' ' + responsiveMode.charAt(0).toUpperCase() + responsiveMode.slice(1) |
| 300 |
), |
| 301 |
el('p', {}, |
| 302 |
el('strong', {}, __('Current Width:', 'blockenberg')), |
| 303 |
' ' + Math.round(currentWidth * 10) / 10 + '%' |
| 304 |
), |
| 305 |
el('p', {}, |
| 306 |
el('strong', {}, __('UID:', 'blockenberg')), |
| 307 |
' ' + (a.uid || 'N/A') |
| 308 |
) |
| 309 |
) |
| 310 |
) |
| 311 |
); |
| 312 |
|
| 313 |
return el('div', blockProps, |
| 314 |
inspector, |
| 315 |
// Move controls (hover) |
| 316 |
parentData.colCount > 1 && el('div', { className: 'bkbg-column__move-actions' }, |
| 317 |
el(Button, { |
| 318 |
className: 'bkbg-column__move-btn', |
| 319 |
icon: 'arrow-left-alt2', |
| 320 |
label: __('Move column left', 'blockenberg'), |
| 321 |
onClick: function () { moveSelf('left'); }, |
| 322 |
disabled: parentData.colIndex <= 0 |
| 323 |
}), |
| 324 |
el(Button, { |
| 325 |
className: 'bkbg-column__move-btn', |
| 326 |
icon: 'no-alt', |
| 327 |
label: __('Delete column', 'blockenberg'), |
| 328 |
onClick: removeSelfWithConfirm |
| 329 |
}), |
| 330 |
el(Button, { |
| 331 |
className: 'bkbg-column__move-btn', |
| 332 |
icon: 'arrow-right-alt2', |
| 333 |
label: __('Move column right', 'blockenberg'), |
| 334 |
onClick: function () { moveSelf('right'); }, |
| 335 |
disabled: parentData.colIndex >= parentData.colCount - 1 |
| 336 |
}) |
| 337 |
), |
| 338 |
el('div', { className: 'bkbg-column__inner' }, |
| 339 |
el(InnerBlocks, { |
| 340 |
templateLock: false, |
| 341 |
renderAppender: function () { |
| 342 |
return el(SidebarInserterAppender, { rootClientId: clientId }); |
| 343 |
} |
| 344 |
}) |
| 345 |
), |
| 346 |
el('div', { className: 'bkbg-column__width-label' }, |
| 347 |
Math.round(currentWidth) + '%' |
| 348 |
) |
| 349 |
); |
| 350 |
}, |
| 351 |
|
| 352 |
save: function (props) { |
| 353 |
var a = props.attributes; |
| 354 |
var widths = a.widths || { desktop: 50, tablet: 50, mobile: 100 }; |
| 355 |
|
| 356 |
var blockProps = useBlockProps.save({ |
| 357 |
className: [ |
| 358 |
'bkbg-column', |
| 359 |
'bkbg-column--padding-' + a.paddingInner |
| 360 |
].join(' '), |
| 361 |
style: { |
| 362 |
'--bkbg-col-desktop': widths.desktop + '%', |
| 363 |
'--bkbg-col-tablet': (widths.tablet || widths.desktop) + '%', |
| 364 |
'--bkbg-col-mobile': (widths.mobile || 100) + '%' |
| 365 |
}, |
| 366 |
'data-uid': a.uid |
| 367 |
}); |
| 368 |
|
| 369 |
return el('div', blockProps, |
| 370 |
el('div', { className: 'bkbg-column__inner' }, |
| 371 |
el(InnerBlocks.Content) |
| 372 |
) |
| 373 |
); |
| 374 |
} |
| 375 |
}); |
| 376 |
}); |
| 377 |
|