| 1 |
( function () { |
| 2 |
var el = wp.element.createElement; |
| 3 |
var Fragment = wp.element.Fragment; |
| 4 |
var useState = wp.element.useState; |
| 5 |
var useEffect = wp.element.useEffect; |
| 6 |
var __ = wp.i18n.__; |
| 7 |
var registerBlockType = wp.blocks.registerBlockType; |
| 8 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 9 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 10 |
var RichText = wp.blockEditor.RichText; |
| 11 |
var PanelBody = wp.components.PanelBody; |
| 12 |
var ColorPicker = wp.components.ColorPicker; |
| 13 |
var Popover = wp.components.Popover; |
| 14 |
var Button = wp.components.Button; |
| 15 |
var ToggleControl = wp.components.ToggleControl; |
| 16 |
var RangeControl = wp.components.RangeControl; |
| 17 |
var SelectControl = wp.components.SelectControl; |
| 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 |
// ── Generate unique block ID ───────────────────────────────────────────────── |
| 51 |
function generateId() { |
| 52 |
return 'bq' + Math.random().toString(36).substr(2, 8); |
| 53 |
} |
| 54 |
|
| 55 |
// ── Decorative quote mark SVG ──────────────────────────────────────────────── |
| 56 |
// Uses SVG <text> elements for authentic typographic marks. |
| 57 |
function buildMarkSVG(markStyle, markSize, markColor) { |
| 58 |
var base = { |
| 59 |
xmlns: 'http://www.w3.org/2000/svg', |
| 60 |
'aria-hidden': 'true', |
| 61 |
focusable: 'false', |
| 62 |
style: { display: 'block' } |
| 63 |
}; |
| 64 |
|
| 65 |
if (markStyle === 'fancy') { |
| 66 |
// Typographic open-double-quote " (U+201C) — curly serif |
| 67 |
return el('svg', Object.assign({}, base, { viewBox: '0 0 120 100', width: markSize, height: Math.round(markSize * 0.84) }), |
| 68 |
el('text', { x: '4', y: '96', fontSize: '140', fontFamily: 'Georgia,"Times New Roman",serif', fill: markColor }, '\u201C') |
| 69 |
); |
| 70 |
} |
| 71 |
if (markStyle === 'simple') { |
| 72 |
// Straight double quote " |
| 73 |
return el('svg', Object.assign({}, base, { viewBox: '0 0 80 70', width: markSize, height: Math.round(markSize * 0.875) }), |
| 74 |
el('text', { x: '2', y: '66', fontSize: '100', fontFamily: 'Arial,Helvetica,sans-serif', fill: markColor }, '"') |
| 75 |
); |
| 76 |
} |
| 77 |
if (markStyle === 'chevron') { |
| 78 |
// Double angle quotation mark » |
| 79 |
return el('svg', Object.assign({}, base, { viewBox: '0 0 80 64', width: markSize, height: Math.round(markSize * 0.8) }), |
| 80 |
el('text', { x: '0', y: '60', fontSize: '88', fontFamily: 'Georgia,serif', fill: markColor }, '\u00BB') |
| 81 |
); |
| 82 |
} |
| 83 |
if (markStyle === 'ornate') { |
| 84 |
// Heavy double turned comma quotation mark ❝ (U+275D) |
| 85 |
return el('svg', Object.assign({}, base, { viewBox: '0 0 100 88', width: markSize, height: Math.round(markSize * 0.88) }), |
| 86 |
el('text', { x: '0', y: '84', fontSize: '120', fontFamily: 'Georgia,serif', fill: markColor }, '\u275D') |
| 87 |
); |
| 88 |
} |
| 89 |
// lines: two horizontal rectangular strokes (typographic em-dash pair) |
| 90 |
var lh = Math.max(3, Math.round(markSize * 0.07)); |
| 91 |
var lw = Math.round(markSize * 0.8); |
| 92 |
var gap = lh * 2.5; |
| 93 |
return el('svg', Object.assign({}, base, { viewBox: '0 0 ' + (lw + 2) + ' ' + (lh * 2 + gap), width: lw + 2, height: lh * 2 + gap }), |
| 94 |
el('rect', { x: 0, y: 0, width: lw, height: lh, rx: lh / 2, fill: markColor }), |
| 95 |
el('rect', { x: 0, y: lh + gap, width: Math.round(lw * 0.6), height: lh, rx: lh / 2, fill: markColor }) |
| 96 |
); |
| 97 |
} |
| 98 |
|
| 99 |
// ── CSS custom properties on wrapper ──────────────────────────────────────── |
| 100 |
function buildWrapStyle(a) { |
| 101 |
return Object.assign({ |
| 102 |
'--bkbg-bq-accent-color': a.accentColor, |
| 103 |
'--bkbg-bq-accent-w': a.accentLineWidth + 'px', |
| 104 |
'--bkbg-bq-accent-r': a.accentLineRadius + 'px', |
| 105 |
'--bkbg-bq-bg': a.bgColor, |
| 106 |
'--bkbg-bq-radius': a.borderRadius + 'px', |
| 107 |
'--bkbg-bq-pad-t': a.paddingTop + 'px', |
| 108 |
'--bkbg-bq-pad-r': a.paddingRight + 'px', |
| 109 |
'--bkbg-bq-pad-b': a.paddingBottom + 'px', |
| 110 |
'--bkbg-bq-pad-l': a.paddingLeft + 'px', |
| 111 |
'--bkbg-bq-mark-opacity': (a.markOpacity / 100), |
| 112 |
'--bkbg-bq-mark-side-gap': a.markOpacity + 'px', |
| 113 |
'--bkbg-bq-shadow': a.enableShadow ? '0 4px 28px rgba(0,0,0,0.09)' : 'none', |
| 114 |
'--bkbg-bq-quote-color': a.quoteColor, |
| 115 |
'--bkbg-bq-quote-spacing': a.quoteSpacing + 'px', |
| 116 |
'--bkbg-bq-name-color': a.nameColor, |
| 117 |
'--bkbg-bq-role-color': a.roleColor, |
| 118 |
'--bkbg-bq-photo-size': a.photoSize + 'px', |
| 119 |
'--bkbg-bq-photo-border': a.photoBorder ? ('2px solid ' + a.photoBorderColor) : 'none', |
| 120 |
'--bkbg-bq-gap-photo': a.gapPhotoText + 'px' |
| 121 |
}, _tv(a.typoQuote, '--bkbg-bq-quote-'), _tv(a.typoName, '--bkbg-bq-name-'), _tv(a.typoRole, '--bkbg-bq-role-')); |
| 122 |
} |
| 123 |
|
| 124 |
registerBlockType('blockenberg/blockquote', { |
| 125 |
title: __('Blockquote / Pull Quote', 'blockenberg'), |
| 126 |
icon: 'format-quote', |
| 127 |
category: 'bkbg-content', |
| 128 |
description: __('Stylish blockquote with accent line, decorative marks, author photo and role.', 'blockenberg'), |
| 129 |
|
| 130 |
edit: function (props) { |
| 131 |
var a = props.attributes; |
| 132 |
var setAttributes = props.setAttributes; |
| 133 |
|
| 134 |
// ── Generate blockId on first mount ────────────────────────────── |
| 135 |
useEffect(function () { |
| 136 |
if (!a.blockId) { setAttributes({ blockId: generateId() }); } |
| 137 |
}, []); |
| 138 |
|
| 139 |
// ── Color picker state (one open at a time) ──────────────────────── |
| 140 |
var openColorKeyState = useState(null); |
| 141 |
var openColorKey = openColorKeyState[0]; |
| 142 |
var setOpenColorKey = openColorKeyState[1]; |
| 143 |
|
| 144 |
function renderColorControl(key, label, value, onChange) { |
| 145 |
var isOpen = openColorKey === key; |
| 146 |
return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } }, |
| 147 |
el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label), |
| 148 |
el('div', { style: { position: 'relative', flexShrink: 0 } }, |
| 149 |
el('button', { |
| 150 |
type: 'button', |
| 151 |
title: value || 'none', |
| 152 |
onClick: function () { setOpenColorKey(isOpen ? null : key); }, |
| 153 |
style: { |
| 154 |
width: '28px', height: '28px', borderRadius: '4px', |
| 155 |
border: isOpen ? '2px solid #007cba' : '2px solid #ddd', |
| 156 |
cursor: 'pointer', padding: 0, display: 'block', |
| 157 |
background: value || '#ffffff', flexShrink: 0 |
| 158 |
} |
| 159 |
}), |
| 160 |
isOpen && el(Popover, { |
| 161 |
position: 'bottom left', |
| 162 |
onClose: function () { setOpenColorKey(null); } |
| 163 |
}, |
| 164 |
el('div', { |
| 165 |
style: { padding: '8px' }, |
| 166 |
onMouseDown: function (e) { e.stopPropagation(); } |
| 167 |
}, |
| 168 |
el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' } }, |
| 169 |
el('strong', { style: { fontSize: '12px' } }, label), |
| 170 |
el(Button, { icon: 'no-alt', isSmall: true, onClick: function () { setOpenColorKey(null); } }) |
| 171 |
), |
| 172 |
el(ColorPicker, { |
| 173 |
color: value, |
| 174 |
enableAlpha: true, |
| 175 |
onChange: function (c) { onChange(c); } |
| 176 |
}) |
| 177 |
) |
| 178 |
) |
| 179 |
) |
| 180 |
); |
| 181 |
} |
| 182 |
|
| 183 |
// ── Open wp.media for author photo ─────────────────────────────── |
| 184 |
function openPhotoLibrary() { |
| 185 |
var frame = wp.media({ |
| 186 |
title: __('Select Author Photo', 'blockenberg'), |
| 187 |
button: { text: __('Use as Author Photo', 'blockenberg') }, |
| 188 |
multiple: false, |
| 189 |
library: { type: 'image' } |
| 190 |
}); |
| 191 |
frame.on('select', function () { |
| 192 |
var att = frame.state().get('selection').first().toJSON(); |
| 193 |
setAttributes({ photoUrl: att.url, photoAlt: att.alt || att.title || '' }); |
| 194 |
}); |
| 195 |
frame.open(); |
| 196 |
} |
| 197 |
|
| 198 |
// ── Option lists ────────────────────────────────────────────────── |
| 199 |
var quoteStyleOptions = [ |
| 200 |
{ label: __('Line — left accent bar', 'blockenberg'), value: 'line' }, |
| 201 |
{ label: __('Quotes — mark centred', 'blockenberg'), value: 'quotes' }, |
| 202 |
{ label: __('Card — boxed background', 'blockenberg'), value: 'card' }, |
| 203 |
{ label: __('Minimal — clean type', 'blockenberg'), value: 'minimal' }, |
| 204 |
{ label: __('Bordered — full border', 'blockenberg'), value: 'bordered' }, |
| 205 |
{ label: __('Top Bar — header accent', 'blockenberg'), value: 'top-bar' } |
| 206 |
]; |
| 207 |
|
| 208 |
var quoteAlignOptions = [ |
| 209 |
{ label: __('Left', 'blockenberg'), value: 'left' }, |
| 210 |
{ label: __('Center', 'blockenberg'), value: 'center' }, |
| 211 |
{ label: __('Right', 'blockenberg'), value: 'right' } |
| 212 |
]; |
| 213 |
|
| 214 |
var markStyleOptions = [ |
| 215 |
{ label: __('Fancy — curly serif "', 'blockenberg'), value: 'fancy' }, |
| 216 |
{ label: __('Simple — straight "', 'blockenberg'), value: 'simple' }, |
| 217 |
{ label: __('Chevron — »', 'blockenberg'), value: 'chevron' }, |
| 218 |
{ label: __('Ornate — ❝ heavy', 'blockenberg'), value: 'ornate' }, |
| 219 |
{ label: __('Lines — two strokes', 'blockenberg'), value: 'lines' } |
| 220 |
]; |
| 221 |
|
| 222 |
var markPositionOptions = [ |
| 223 |
{ label: __('Side (left of text)', 'blockenberg'), value: 'float' }, |
| 224 |
{ label: __('Side (right of text)', 'blockenberg'), value: 'right' }, |
| 225 |
{ label: __('Above quote text', 'blockenberg'), value: 'above' } |
| 226 |
]; |
| 227 |
|
| 228 |
var authorLayoutOptions = [ |
| 229 |
{ label: __('Horizontal — photo left', 'blockenberg'), value: 'horizontal' }, |
| 230 |
{ label: __('Horizontal — photo right', 'blockenberg'), value: 'horizontal-right' }, |
| 231 |
{ label: __('Vertical — photo above', 'blockenberg'), value: 'vertical' } |
| 232 |
]; |
| 233 |
|
| 234 |
var photoShapeOptions = [ |
| 235 |
{ label: __('Circle', 'blockenberg'), value: 'circle' }, |
| 236 |
{ label: __('Rounded', 'blockenberg'), value: 'rounded' }, |
| 237 |
{ label: __('Square', 'blockenberg'), value: 'square' } |
| 238 |
]; |
| 239 |
|
| 240 |
var fontWeightOptions = [ |
| 241 |
{ label: '300 — Light', value: 300 }, |
| 242 |
{ label: '400 — Regular', value: 400 }, |
| 243 |
{ label: '500 — Medium', value: 500 }, |
| 244 |
{ label: '600 — Semi Bold', value: 600 }, |
| 245 |
{ label: '700 — Bold', value: 700 }, |
| 246 |
{ label: '800 — Extra Bold', value: 800 } |
| 247 |
]; |
| 248 |
|
| 249 |
var quoteFontStyleOptions = [ |
| 250 |
{ label: __('Italic', 'blockenberg'), value: 'italic' }, |
| 251 |
{ label: __('Normal', 'blockenberg'), value: 'normal' } |
| 252 |
]; |
| 253 |
|
| 254 |
var hasAccentLineControl = ( |
| 255 |
a.quoteStyle === 'line' || |
| 256 |
a.quoteStyle === 'bordered'|| |
| 257 |
a.quoteStyle === 'top-bar' |
| 258 |
); |
| 259 |
|
| 260 |
// ── Inspector panels ────────────────────────────────────────────── |
| 261 |
var inspector = el(InspectorControls, {}, |
| 262 |
|
| 263 |
// ── Style ───────────────────────────────────────────────────── |
| 264 |
el(PanelBody, { title: __('Style', 'blockenberg'), initialOpen: true }, |
| 265 |
el(SelectControl, { |
| 266 |
label: __('Quote Style', 'blockenberg'), |
| 267 |
value: a.quoteStyle, |
| 268 |
options: quoteStyleOptions, |
| 269 |
onChange: function (v) { setAttributes({ quoteStyle: v }); } |
| 270 |
}), |
| 271 |
el(SelectControl, { |
| 272 |
label: __('Alignment', 'blockenberg'), |
| 273 |
value: a.quoteAlign, |
| 274 |
options: quoteAlignOptions, |
| 275 |
onChange: function (v) { setAttributes({ quoteAlign: v }); } |
| 276 |
}), |
| 277 |
el('hr', {}), |
| 278 |
el(ToggleControl, { |
| 279 |
label: __('Show Decorative Mark', 'blockenberg'), |
| 280 |
checked: a.showMark, |
| 281 |
__nextHasNoMarginBottom: true, |
| 282 |
onChange: function (v) { setAttributes({ showMark: v }); } |
| 283 |
}), |
| 284 |
a.showMark && el(Fragment, {}, |
| 285 |
el(SelectControl, { |
| 286 |
label: __('Mark Style', 'blockenberg'), |
| 287 |
value: a.markStyle, |
| 288 |
options: markStyleOptions, |
| 289 |
onChange: function (v) { setAttributes({ markStyle: v }); } |
| 290 |
}), |
| 291 |
el(SelectControl, { |
| 292 |
label: __('Mark Position', 'blockenberg'), |
| 293 |
value: a.markPosition, |
| 294 |
options: markPositionOptions, |
| 295 |
onChange: function (v) { setAttributes({ markPosition: v }); } |
| 296 |
}), |
| 297 |
el(RangeControl, { |
| 298 |
label: __('Mark Size (px)', 'blockenberg'), |
| 299 |
value: a.markSize, |
| 300 |
min: 32, |
| 301 |
max: 200, |
| 302 |
onChange: function (v) { setAttributes({ markSize: v }); } |
| 303 |
}), |
| 304 |
a.markPosition === 'float' && el(RangeControl, { |
| 305 |
label: __('Gap between mark and text (px)', 'blockenberg'), |
| 306 |
value: a.markOpacity, |
| 307 |
min: 0, |
| 308 |
max: 60, |
| 309 |
onChange: function (v) { setAttributes({ markOpacity: v }); } |
| 310 |
}), |
| 311 |
a.markPosition === 'right' && el(RangeControl, { |
| 312 |
label: __('Gap between text and mark (px)', 'blockenberg'), |
| 313 |
value: a.markOpacity, |
| 314 |
min: 0, |
| 315 |
max: 60, |
| 316 |
onChange: function (v) { setAttributes({ markOpacity: v }); } |
| 317 |
}) |
| 318 |
) |
| 319 |
), |
| 320 |
|
| 321 |
// ── Typography ──────────────────────────────────────────────── |
| 322 |
el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false }, |
| 323 |
el(getTypographyControl(), { label: __('Quote Text', 'blockenberg'), value: a.typoQuote, onChange: function (v) { setAttributes({ typoQuote: v }); } }), |
| 324 |
el(getTypographyControl(), { label: __('Author Name', 'blockenberg'), value: a.typoName, onChange: function (v) { setAttributes({ typoName: v }); } }), |
| 325 |
el(getTypographyControl(), { label: __('Role / Title', 'blockenberg'), value: a.typoRole, onChange: function (v) { setAttributes({ typoRole: v }); } }) |
| 326 |
), |
| 327 |
|
| 328 |
// ── Author ──────────────────────────────────────────────────── |
| 329 |
el(PanelBody, { title: __('Author', 'blockenberg'), initialOpen: false }, |
| 330 |
el(ToggleControl, { |
| 331 |
label: __('Show Author', 'blockenberg'), |
| 332 |
checked: a.showAuthor, |
| 333 |
__nextHasNoMarginBottom: true, |
| 334 |
onChange: function (v) { setAttributes({ showAuthor: v }); } |
| 335 |
}), |
| 336 |
a.showAuthor && el(Fragment, {}, |
| 337 |
el(SelectControl, { |
| 338 |
label: __('Author Layout', 'blockenberg'), |
| 339 |
value: a.authorLayout, |
| 340 |
options: authorLayoutOptions, |
| 341 |
onChange: function (v) { setAttributes({ authorLayout: v }); } |
| 342 |
}), |
| 343 |
el(ToggleControl, { |
| 344 |
label: __('Show Role / Title', 'blockenberg'), |
| 345 |
checked: a.showRole, |
| 346 |
__nextHasNoMarginBottom: true, |
| 347 |
onChange: function (v) { setAttributes({ showRole: v }); } |
| 348 |
}), |
| 349 |
el('hr', {}), |
| 350 |
el(ToggleControl, { |
| 351 |
label: __('Show Author Photo', 'blockenberg'), |
| 352 |
checked: a.showPhoto, |
| 353 |
__nextHasNoMarginBottom: true, |
| 354 |
onChange: function (v) { setAttributes({ showPhoto: v }); } |
| 355 |
}), |
| 356 |
a.showPhoto && el(Fragment, {}, |
| 357 |
el(SelectControl, { |
| 358 |
label: __('Photo Shape', 'blockenberg'), |
| 359 |
value: a.photoShape, |
| 360 |
options: photoShapeOptions, |
| 361 |
onChange: function (v) { setAttributes({ photoShape: v }); } |
| 362 |
}), |
| 363 |
el(RangeControl, { |
| 364 |
label: __('Photo Size (px)', 'blockenberg'), |
| 365 |
value: a.photoSize, min: 24, max: 120, |
| 366 |
onChange: function (v) { setAttributes({ photoSize: v }); } |
| 367 |
}), |
| 368 |
el(RangeControl, { |
| 369 |
label: __('Gap photo → text (px)', 'blockenberg'), |
| 370 |
value: a.gapPhotoText, min: 0, max: 48, |
| 371 |
onChange: function (v) { setAttributes({ gapPhotoText: v }); } |
| 372 |
}), |
| 373 |
el(ToggleControl, { |
| 374 |
label: __('Photo Border', 'blockenberg'), |
| 375 |
checked: a.photoBorder, |
| 376 |
__nextHasNoMarginBottom: true, |
| 377 |
onChange: function (v) { setAttributes({ photoBorder: v }); } |
| 378 |
}), |
| 379 |
el('div', { style: { marginTop: '8px' } }, |
| 380 |
a.photoUrl |
| 381 |
? el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } }, |
| 382 |
el('img', { |
| 383 |
src: a.photoUrl, alt: '', |
| 384 |
style: { width: '48px', height: '48px', objectFit: 'cover', borderRadius: '50%', border: '2px solid #ddd', flexShrink: 0 } |
| 385 |
}), |
| 386 |
el(Button, { |
| 387 |
isSecondary: true, isSmall: true, |
| 388 |
onClick: openPhotoLibrary |
| 389 |
}, __('Change', 'blockenberg')), |
| 390 |
el(Button, { |
| 391 |
isDestructive: true, isSmall: true, |
| 392 |
onClick: function () { setAttributes({ photoUrl: '', photoAlt: '' }); } |
| 393 |
}, __('Remove', 'blockenberg')) |
| 394 |
) |
| 395 |
: el(Button, { |
| 396 |
isPrimary: true, isSmall: true, |
| 397 |
onClick: openPhotoLibrary |
| 398 |
}, __('Choose Author Photo', 'blockenberg')) |
| 399 |
) |
| 400 |
) |
| 401 |
) |
| 402 |
), |
| 403 |
|
| 404 |
// ── Colors ──────────────────────────────────────────────────── |
| 405 |
el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false }, |
| 406 |
el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, |
| 407 |
__('Quote', 'blockenberg') |
| 408 |
), |
| 409 |
renderColorControl('quoteColor', __('Quote text', 'blockenberg'), a.quoteColor, |
| 410 |
function (c) { setAttributes({ quoteColor: c }); }), |
| 411 |
|
| 412 |
el('hr', {}), |
| 413 |
el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, |
| 414 |
__('Accent', 'blockenberg') |
| 415 |
), |
| 416 |
renderColorControl('accentColor', __('Accent / border color', 'blockenberg'), a.accentColor, |
| 417 |
function (c) { setAttributes({ accentColor: c }); }), |
| 418 |
|
| 419 |
a.showMark && el(Fragment, {}, |
| 420 |
el('hr', {}), |
| 421 |
el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, |
| 422 |
__('Quote Mark', 'blockenberg') |
| 423 |
), |
| 424 |
renderColorControl('markColor', __('Mark color', 'blockenberg'), a.markColor, |
| 425 |
function (c) { setAttributes({ markColor: c }); }) |
| 426 |
), |
| 427 |
|
| 428 |
el('hr', {}), |
| 429 |
el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, |
| 430 |
__('Author', 'blockenberg') |
| 431 |
), |
| 432 |
renderColorControl('nameColor', __('Author name', 'blockenberg'), a.nameColor, |
| 433 |
function (c) { setAttributes({ nameColor: c }); }), |
| 434 |
a.showRole && renderColorControl('roleColor', __('Role / title', 'blockenberg'), a.roleColor, |
| 435 |
function (c) { setAttributes({ roleColor: c }); }), |
| 436 |
|
| 437 |
el('hr', {}), |
| 438 |
el('p', { style: { margin: '4px 0 6px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888' } }, |
| 439 |
__('Block', 'blockenberg') |
| 440 |
), |
| 441 |
renderColorControl('bgColor', __('Background', 'blockenberg'), a.bgColor, |
| 442 |
function (c) { setAttributes({ bgColor: c }); }), |
| 443 |
a.showPhoto && a.photoBorder && renderColorControl('photoBorderColor', __('Photo border', 'blockenberg'), a.photoBorderColor, |
| 444 |
function (c) { setAttributes({ photoBorderColor: c }); }) |
| 445 |
), |
| 446 |
|
| 447 |
// ── Layout & Spacing ────────────────────────────────────────── |
| 448 |
el(PanelBody, { title: __('Layout & Spacing', 'blockenberg'), initialOpen: false }, |
| 449 |
el(RangeControl, { label: __('Padding Top (px)', 'blockenberg'), value: a.paddingTop, min: 0, max: 120, onChange: function (v) { setAttributes({ paddingTop: v }); } }), |
| 450 |
el(RangeControl, { label: __('Padding Right (px)', 'blockenberg'), value: a.paddingRight, min: 0, max: 120, onChange: function (v) { setAttributes({ paddingRight: v }); } }), |
| 451 |
el(RangeControl, { label: __('Padding Bottom (px)', 'blockenberg'), value: a.paddingBottom, min: 0, max: 120, onChange: function (v) { setAttributes({ paddingBottom: v }); } }), |
| 452 |
el(RangeControl, { label: __('Padding Left (px)', 'blockenberg'), value: a.paddingLeft, min: 0, max: 120, onChange: function (v) { setAttributes({ paddingLeft: v }); } }), |
| 453 |
el('hr', {}), |
| 454 |
el(RangeControl, { |
| 455 |
label: __('Border Radius (px)', 'blockenberg'), |
| 456 |
value: a.borderRadius, min: 0, max: 48, |
| 457 |
onChange: function (v) { setAttributes({ borderRadius: v }); } |
| 458 |
}), |
| 459 |
el(ToggleControl, { |
| 460 |
label: __('Drop Shadow', 'blockenberg'), |
| 461 |
checked: a.enableShadow, |
| 462 |
__nextHasNoMarginBottom: true, |
| 463 |
onChange: function (v) { setAttributes({ enableShadow: v }); } |
| 464 |
}), |
| 465 |
el('hr', {}), |
| 466 |
el(RangeControl, { |
| 467 |
label: __('Gap after quote text (px)', 'blockenberg'), |
| 468 |
value: a.quoteSpacing, min: 0, max: 80, |
| 469 |
onChange: function (v) { setAttributes({ quoteSpacing: v }); } |
| 470 |
}), |
| 471 |
hasAccentLineControl && el(Fragment, {}, |
| 472 |
el(RangeControl, { |
| 473 |
label: __('Accent Line Width (px)', 'blockenberg'), |
| 474 |
value: a.accentLineWidth, min: 1, max: 16, |
| 475 |
onChange: function (v) { setAttributes({ accentLineWidth: v }); } |
| 476 |
}), |
| 477 |
el(RangeControl, { |
| 478 |
label: __('Accent Line Radius (px)', 'blockenberg'), |
| 479 |
value: a.accentLineRadius, min: 0, max: 8, |
| 480 |
onChange: function (v) { setAttributes({ accentLineRadius: v }); } |
| 481 |
}) |
| 482 |
) |
| 483 |
) |
| 484 |
); |
| 485 |
|
| 486 |
// ── Build sub-elements ──────────────────────────────────────────── |
| 487 |
var markEl = a.showMark |
| 488 |
? el('span', { className: 'bkbg-bq-mark', 'aria-hidden': 'true' }, |
| 489 |
buildMarkSVG(a.markStyle, a.markSize, a.markColor)) |
| 490 |
: null; |
| 491 |
|
| 492 |
// Author photo: real img or click-to-upload placeholder in editor |
| 493 |
var photoEl = a.showPhoto |
| 494 |
? el('div', { className: 'bkbg-bq-photo-wrap', 'data-shape': a.photoShape }, |
| 495 |
a.photoUrl |
| 496 |
? el('img', { className: 'bkbg-bq-photo', src: a.photoUrl, alt: a.photoAlt }) |
| 497 |
: el('div', { |
| 498 |
className: 'bkbg-bq-photo-placeholder', |
| 499 |
onClick: openPhotoLibrary, |
| 500 |
title: __('Click to add author photo', 'blockenberg'), |
| 501 |
style: { cursor: 'pointer' } |
| 502 |
}, '+') |
| 503 |
) |
| 504 |
: null; |
| 505 |
|
| 506 |
var authorEl = a.showAuthor |
| 507 |
? el('div', { className: 'bkbg-bq-author', 'data-layout': a.authorLayout }, |
| 508 |
a.showPhoto && photoEl, |
| 509 |
el('div', { className: 'bkbg-bq-info' }, |
| 510 |
el(RichText, { |
| 511 |
tagName: 'p', |
| 512 |
className: 'bkbg-bq-name', |
| 513 |
value: a.authorName, |
| 514 |
onChange: function (v) { setAttributes({ authorName: v }); }, |
| 515 |
placeholder: __('Author name…', 'blockenberg'), |
| 516 |
allowedFormats: ['core/bold', 'core/italic', 'core/text-color'] |
| 517 |
}), |
| 518 |
a.showRole && el(RichText, { |
| 519 |
tagName: 'p', |
| 520 |
className: 'bkbg-bq-role', |
| 521 |
value: a.authorRole, |
| 522 |
onChange: function (v) { setAttributes({ authorRole: v }); }, |
| 523 |
placeholder: __('Role or title…', 'blockenberg'), |
| 524 |
allowedFormats: ['core/bold', 'core/italic', 'core/text-color'] |
| 525 |
}) |
| 526 |
) |
| 527 |
) |
| 528 |
: null; |
| 529 |
|
| 530 |
// ── Edit render ─────────────────────────────────────────────────── |
| 531 |
var blockProps = useBlockProps({ |
| 532 |
className: 'bkbg-editor-wrap', |
| 533 |
'data-block-label': 'Blockquote' |
| 534 |
}); |
| 535 |
|
| 536 |
return el('div', blockProps, |
| 537 |
inspector, |
| 538 |
el('figure', Object.assign( |
| 539 |
{ className: 'bkbg-bq-wrap', style: buildWrapStyle(a) }, |
| 540 |
{ |
| 541 |
'data-style': a.quoteStyle, |
| 542 |
'data-quote-align': a.quoteAlign, |
| 543 |
'data-mark-pos': a.showMark ? a.markPosition : 'none', |
| 544 |
'data-photo-shape': a.photoShape, |
| 545 |
'data-author-layout': a.authorLayout |
| 546 |
} |
| 547 |
), |
| 548 |
// Float mark = flex row, mark on left |
| 549 |
a.showMark && a.markPosition === 'float' && markEl, |
| 550 |
// Right mark = flex row, mark on right (inserted after body via CSS order) |
| 551 |
a.showMark && a.markPosition === 'right' && markEl, |
| 552 |
el('div', { className: 'bkbg-bq-body' }, |
| 553 |
// Above mark = in-flow before quote text |
| 554 |
a.showMark && a.markPosition === 'above' && markEl, |
| 555 |
el(RichText, { |
| 556 |
tagName: 'blockquote', |
| 557 |
className: 'bkbg-bq-text', |
| 558 |
value: a.quoteText, |
| 559 |
onChange: function (v) { setAttributes({ quoteText: v }); }, |
| 560 |
placeholder: __('Type your quote here…', 'blockenberg'), |
| 561 |
allowedFormats: ['core/bold', 'core/italic', 'core/text-color', 'core/link'] |
| 562 |
}), |
| 563 |
authorEl |
| 564 |
) |
| 565 |
) |
| 566 |
); |
| 567 |
}, |
| 568 |
|
| 569 |
// ── Save ───────────────────────────────────────────────────────────────── |
| 570 |
save: function (props) { |
| 571 |
var a = props.attributes; |
| 572 |
var RichTextContent = wp.blockEditor.RichText.Content; |
| 573 |
|
| 574 |
var markEl = a.showMark |
| 575 |
? el('span', { className: 'bkbg-bq-mark', 'aria-hidden': 'true' }, |
| 576 |
buildMarkSVG(a.markStyle, a.markSize, a.markColor)) |
| 577 |
: null; |
| 578 |
|
| 579 |
var photoEl = a.showPhoto && a.photoUrl |
| 580 |
? el('div', { className: 'bkbg-bq-photo-wrap', 'data-shape': a.photoShape }, |
| 581 |
el('img', { className: 'bkbg-bq-photo', src: a.photoUrl, alt: a.photoAlt })) |
| 582 |
: null; |
| 583 |
|
| 584 |
var authorEl = a.showAuthor |
| 585 |
? el('div', { className: 'bkbg-bq-author', 'data-layout': a.authorLayout }, |
| 586 |
a.showPhoto && photoEl, |
| 587 |
el('div', { className: 'bkbg-bq-info' }, |
| 588 |
el(RichTextContent, { tagName: 'p', className: 'bkbg-bq-name', value: a.authorName }), |
| 589 |
a.showRole && el(RichTextContent, { tagName: 'p', className: 'bkbg-bq-role', value: a.authorRole }) |
| 590 |
) |
| 591 |
) |
| 592 |
: null; |
| 593 |
|
| 594 |
return el('figure', Object.assign( |
| 595 |
{ className: 'bkbg-bq-wrap', style: buildWrapStyle(a) }, |
| 596 |
{ |
| 597 |
'data-style': a.quoteStyle, |
| 598 |
'data-quote-align': a.quoteAlign, |
| 599 |
'data-mark-pos': a.showMark ? a.markPosition : 'none', |
| 600 |
'data-photo-shape': a.photoShape, |
| 601 |
'data-author-layout': a.authorLayout |
| 602 |
} |
| 603 |
), |
| 604 |
a.showMark && a.markPosition === 'float' && markEl, |
| 605 |
a.showMark && a.markPosition === 'right' && markEl, |
| 606 |
el('div', { className: 'bkbg-bq-body' }, |
| 607 |
a.showMark && a.markPosition === 'above' && markEl, |
| 608 |
el(RichTextContent, { |
| 609 |
tagName: 'blockquote', |
| 610 |
className: 'bkbg-bq-text', |
| 611 |
value: a.quoteText |
| 612 |
}), |
| 613 |
authorEl |
| 614 |
) |
| 615 |
); |
| 616 |
} |
| 617 |
}); |
| 618 |
}() ); |
| 619 |
|