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

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

619 lines 35.7 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 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