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

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

261 lines 19.4 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 __ = wp.i18n.__;
4 var registerBlockType = wp.blocks.registerBlockType;
5 var InspectorControls = wp.blockEditor.InspectorControls;
6 var MediaUpload = wp.blockEditor.MediaUpload;
7 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var RichText = wp.blockEditor.RichText;
10 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
11 var PanelBody = wp.components.PanelBody;
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 Button = wp.components.Button;
17
18 var _tc, _tv;
19 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
20 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
21
22 var LAYOUT_OPTIONS = [
23 { value: 'list', label: 'List (one per row)' },
24 { value: 'grid', label: 'Grid (2 columns)' },
25 { value: 'magazine', label: 'Magazine (alternating)' }
26 ];
27 var NUMBER_STYLES = [
28 { value: 'badge', label: 'Badge (filled circle)' },
29 { value: 'circle', label: 'Circle (outline)' },
30 { value: 'large', label: 'Large (background)' },
31 { value: 'minimal', label: 'Minimal (just number)' }
32 ];
33 var RATIO_OPTIONS = [
34 { value: '16:9', label: '16:9' },
35 { value: '4:3', label: '4:3' },
36 { value: '1:1', label: '1:1 Square' },
37 { value: '3:4', label: '3:4 Portrait' }
38 ];
39
40 function ratioPad(r) {
41 var map = { '16:9': '56.25%', '4:3': '75%', '1:1': '100%', '3:4': '133.333%' };
42 return map[r] || '56.25%';
43 }
44
45 function emptyItem() {
46 return { title: '', subtitle: '', description: '', imageUrl: '', imageId: 0, imageAlt: '', tag: '', verdict: '', rating: 5 };
47 }
48
49 function Stars(rating, color) {
50 var stars = [];
51 for (var i = 1; i <= 5; i++) {
52 stars.push(el('span', { key: i, style: { color: i <= rating ? color : '#d1d5db', fontSize: '14px' } }, '�
53 '));
54 }
55 return el('div', { style: { display: 'flex', gap: '1px', marginTop: '4px' } }, stars);
56 }
57
58 registerBlockType('blockenberg/listicle', {
59 edit: function (props) {
60 var attr = props.attributes;
61 var set = props.setAttributes;
62 var items = attr.items || [emptyItem()];
63
64 var blockProps = useBlockProps((function () {
65 var _tvFn = getTypoCssVars();
66 var s = {
67 background: attr.bgColor || undefined,
68 paddingTop: attr.paddingTop + 'px',
69 paddingBottom: attr.paddingBottom + 'px'
70 };
71 if (_tvFn) {
72 Object.assign(s, _tvFn(attr.headingTypo, '--bkbg-lis-h-'));
73 Object.assign(s, _tvFn(attr.titleTypo, '--bkbg-lis-tt-'));
74 Object.assign(s, _tvFn(attr.descTypo, '--bkbg-lis-d-'));
75 }
76 return { className: 'bkbg-lis-section', style: s };
77 })());
78
79 function updateItem(idx, field, val) {
80 var updated = items.map(function (it, i) {
81 if (i !== idx) return it;
82 var patch = {}; patch[field] = val;
83 return Object.assign({}, it, patch);
84 });
85 set({ items: updated });
86 }
87
88 function numBadge(n, attr) {
89 var numTxt = attr.countDown ? (items.length - n + 1) : n;
90 if (attr.numberStyle === 'badge') {
91 return el('div', { style: { minWidth: '40px', height: '40px', background: attr.numberBg, color: attr.numberColor, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: '18px', flexShrink: 0 } }, numTxt);
92 } else if (attr.numberStyle === 'circle') {
93 return el('div', { style: { minWidth: '40px', height: '40px', border: '2px solid ' + attr.numberBg, color: attr.numberBg, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: '18px', flexShrink: 0 } }, numTxt);
94 } else if (attr.numberStyle === 'large') {
95 return el('div', { style: { fontWeight: 900, fontSize: '72px', lineHeight: 1, color: attr.numberBg + '22', minWidth: '70px', textAlign: 'center', flexShrink: 0, fontFamily: 'Georgia, serif', userSelect: 'none' } }, numTxt);
96 } else {
97 return el('div', { style: { fontWeight: 900, fontSize: '20px', color: attr.numberBg, minWidth: '32px', flexShrink: 0 } }, '#' + numTxt);
98 }
99 }
100
101 var controls = el(InspectorControls, {},
102 el(PanelBody, { title: __('List Settings', 'blockenberg'), initialOpen: true },
103 el(SelectControl, { label: __('Layout', 'blockenberg'), value: attr.layout, options: LAYOUT_OPTIONS, __nextHasNoMarginBottom: true, onChange: function (v) { set({ layout: v }); } }),
104 el('div', { style: { marginTop: '10px' } },
105 el(SelectControl, { label: __('Number Style', 'blockenberg'), value: attr.numberStyle, options: NUMBER_STYLES, __nextHasNoMarginBottom: true, onChange: function (v) { set({ numberStyle: v }); } })
106 ),
107 el('div', { style: { marginTop: '8px' } },
108 el(ToggleControl, { label: __('Show Numbers', 'blockenberg'), checked: attr.showNumbers, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showNumbers: v }); } })
109 ),
110 el('div', { style: { marginTop: '8px' } },
111 el(ToggleControl, { label: __('Count Down (10, 9, 8…)', 'blockenberg'), checked: attr.countDown, __nextHasNoMarginBottom: true, onChange: function (v) { set({ countDown: v }); } })
112 ),
113 el('div', { style: { marginTop: '8px' } },
114 el(ToggleControl, { label: __('Show Images', 'blockenberg'), checked: attr.showImages, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showImages: v }); } })
115 ),
116 attr.showImages && el('div', { style: { marginTop: '8px' } },
117 el(SelectControl, { label: __('Image Ratio', 'blockenberg'), value: attr.imageRatio, options: RATIO_OPTIONS, __nextHasNoMarginBottom: true, onChange: function (v) { set({ imageRatio: v }); } })
118 ),
119 attr.showImages && el('div', { style: { marginTop: '8px' } },
120 el(RangeControl, { label: __('Image Radius', 'blockenberg'), value: attr.imageRadius, min: 0, max: 30, __nextHasNoMarginBottom: true, onChange: function (v) { set({ imageRadius: v }); } })
121 ),
122 el('div', { style: { marginTop: '8px' } },
123 el(ToggleControl, { label: __('Show Tags', 'blockenberg'), checked: attr.showTags, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showTags: v }); } })
124 ),
125 el('div', { style: { marginTop: '8px' } },
126 el(ToggleControl, { label: __('Show Verdict Badge', 'blockenberg'), checked: attr.showVerdicts, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showVerdicts: v }); } })
127 ),
128 el('div', { style: { marginTop: '8px' } },
129 el(ToggleControl, { label: __('Show Star Rating', 'blockenberg'), checked: attr.showRatings, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showRatings: v }); } })
130 )
131 ),
132 el(PanelBody, { title: __('Header', 'blockenberg'), initialOpen: false },
133 el(ToggleControl, { label: __('Show Section Heading', 'blockenberg'), checked: attr.showHeading, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showHeading: v }); } }),
134 attr.showHeading && el('div', { style: { marginTop: '8px' } },
135 el(TextControl, { label: __('Heading Text', 'blockenberg'), value: attr.heading, __nextHasNoMarginBottom: true, onChange: function (v) { set({ heading: v }); } })
136 )
137 ),
138 el(PanelBody, { title: __('Style & Spacing', 'blockenberg'), initialOpen: false },
139 el(RangeControl, { label: __('Card Radius', 'blockenberg'), value: attr.cardRadius, min: 0, max: 30, __nextHasNoMarginBottom: true, onChange: function (v) { set({ cardRadius: v }); } }),
140 el('div', { style: { marginTop: '10px' } },
141 el(RangeControl, { label: __('Gap Between Items', 'blockenberg'), value: attr.gap, min: 4, max: 80, __nextHasNoMarginBottom: true, onChange: function (v) { set({ gap: v }); } })
142 ),
143 el('div', { style: { marginTop: '10px' } },
144 el(RangeControl, { label: __('Padding Top', 'blockenberg'), value: attr.paddingTop, min: 0, max: 120, __nextHasNoMarginBottom: true, onChange: function (v) { set({ paddingTop: v }); } })
145 ),
146 el('div', { style: { marginTop: '10px' } },
147 el(RangeControl, { label: __('Padding Bottom', 'blockenberg'), value: attr.paddingBottom, min: 0, max: 120, __nextHasNoMarginBottom: true, onChange: function (v) { set({ paddingBottom: v }); } })
148 )
149 ),
150 el(PanelColorSettings, {
151 title: __('Colors', 'blockenberg'), initialOpen: false,
152 colorSettings: [
153 { label: __('Section Background', 'blockenberg'), value: attr.bgColor, onChange: function (v) { set({ bgColor: v || '' }); } },
154 { label: __('Card Background', 'blockenberg'), value: attr.cardBg, onChange: function (v) { set({ cardBg: v || '#ffffff' }); } },
155 { label: __('Card Border', 'blockenberg'), value: attr.cardBorder, onChange: function (v) { set({ cardBorder: v || '#e5e7eb' }); } },
156 { label: __('Number Background', 'blockenberg'), value: attr.numberBg, onChange: function (v) { set({ numberBg: v || '#6366f1' }); } },
157 { label: __('Number Text', 'blockenberg'), value: attr.numberColor, onChange: function (v) { set({ numberColor: v || '#ffffff' }); } },
158 { label: __('Title', 'blockenberg'), value: attr.titleColor, onChange: function (v) { set({ titleColor: v || '#111827' }); } },
159 { label: __('Subtitle', 'blockenberg'), value: attr.subtitleColor, onChange: function (v) { set({ subtitleColor: v || '#6b7280' }); } },
160 { label: __('Description', 'blockenberg'), value: attr.descColor, onChange: function (v) { set({ descColor: v || '#374151' }); } },
161 { label: __('Tag Background', 'blockenberg'), value: attr.tagBg, onChange: function (v) { set({ tagBg: v || '#ede9fe' }); } },
162 { label: __('Tag Text', 'blockenberg'), value: attr.tagColor, onChange: function (v) { set({ tagColor: v || '#6366f1' }); } },
163 { label: __('Verdict Background', 'blockenberg'), value: attr.verdictBg, onChange: function (v) { set({ verdictBg: v || '#dcfce7' }); } },
164 { label: __('Verdict Text', 'blockenberg'), value: attr.verdictColor, onChange: function (v) { set({ verdictColor: v || '#15803d' }); } },
165 { label: __('Stars', 'blockenberg'), value: attr.starColor, onChange: function (v) { set({ starColor: v || '#f59e0b' }); } }
166 ]
167 }),
168 el(PanelBody, { title: 'Typography', initialOpen: false },
169 getTypoControl() && el(getTypoControl(), { label: __('Heading', 'blockenberg'), value: attr.headingTypo || {}, onChange: function (v) { set({ headingTypo: v }); } }),
170 getTypoControl() && el(getTypoControl(), { label: __('Item Title', 'blockenberg'), value: attr.titleTypo || {}, onChange: function (v) { set({ titleTypo: v }); } }),
171 getTypoControl() && el(getTypoControl(), { label: __('Description', 'blockenberg'), value: attr.descTypo || {}, onChange: function (v) { set({ descTypo: v }); } })
172 ),
173
174 );
175
176 var wrapStyle = {};
177 if (attr.layout === 'grid') { wrapStyle = { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: attr.gap + 'px' }; }
178 else { wrapStyle = { display: 'flex', flexDirection: 'column', gap: attr.gap + 'px' }; }
179
180 return el('div', blockProps,
181 controls,
182 attr.showHeading && attr.heading && el('h2', { className: 'bkbg-lis-heading', style: { color: attr.titleColor } }, attr.heading),
183 el('div', { style: wrapStyle },
184 items.map(function (item, idx) {
185 var cardinal = attr.countDown ? (items.length - idx) : (idx + 1);
186 var isEven = attr.layout === 'magazine' && idx % 2 === 1;
187
188 return el('div', {
189 key: idx,
190 style: {
191 background: attr.cardBg,
192 border: '1px solid ' + attr.cardBorder,
193 borderRadius: attr.cardRadius + 'px',
194 overflow: 'hidden',
195 display: 'flex',
196 flexDirection: attr.layout === 'list' ? 'row' : 'column',
197 alignItems: attr.layout === 'list' ? 'flex-start' : undefined
198 }
199 },
200 /* image area */
201 attr.showImages && el('div', { style: { position: 'relative', flexShrink: 0, width: attr.layout === 'list' ? '220px' : '100%' } },
202 el('div', { style: { paddingBottom: attr.layout === 'list' ? '0' : ratioPad(attr.imageRatio), height: attr.layout === 'list' ? '160px' : undefined, position: 'relative', background: '#f3f4f6', overflow: 'hidden', borderRadius: attr.layout === 'list' ? attr.imageRadius + 'px 0 0 ' + attr.imageRadius + 'px' : '0' } },
203 item.imageUrl
204 ? el('img', { src: item.imageUrl, alt: item.imageAlt, style: { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' } })
205 : el(MediaUploadCheck, {},
206 el(MediaUpload, {
207 onSelect: function (m) { updateItem(idx, 'imageUrl', m.url); updateItem(idx, 'imageId', m.id); updateItem(idx, 'imageAlt', m.alt || ''); },
208 allowedTypes: ['image'], value: item.imageId,
209 render: function (r) { return el(Button, { onClick: r.open, style: { position: 'absolute', inset: 0, width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'transparent', border: 'none', cursor: 'pointer', color: '#9ca3af', fontSize: '13px' } }, '+ Add Image'); }
210 })
211 ),
212 /* number badge overlay on image */
213 attr.showNumbers && attr.numberStyle !== 'large' && el('div', { style: { position: 'absolute', top: '10px', left: '10px' } }, numBadge(cardinal, attr))
214 )
215 ),
216
217 /* content */
218 el('div', { style: { padding: '16px 20px', flex: 1 } },
219 /* number (large style) inside content */
220 attr.showNumbers && (!attr.showImages || attr.numberStyle === 'large') && el('div', { style: { marginBottom: '8px' } }, numBadge(cardinal, attr)),
221
222 /* number inline when no image and badge */
223 attr.showNumbers && !attr.showImages && attr.numberStyle !== 'large' && el('div', { style: { marginBottom: '10px' } }, numBadge(cardinal, attr)),
224
225 /* top row: tag + verdict */
226 el('div', { style: { display: 'flex', gap: '6px', flexWrap: 'wrap', marginBottom: '8px' } },
227 attr.showTags && el(TextControl, { value: item.tag, label: '', placeholder: __('Tag…', 'blockenberg'), __nextHasNoMarginBottom: true, style: { fontSize: '11px', width: '100px' }, onChange: function (v) { updateItem(idx, 'tag', v); } }),
228 attr.showVerdicts && el(TextControl, { value: item.verdict, label: '', placeholder: __('Verdict…', 'blockenberg'), __nextHasNoMarginBottom: true, style: { fontSize: '11px', width: '120px' }, onChange: function (v) { updateItem(idx, 'verdict', v); } })
229 ),
230
231 el(RichText, { tagName: 'h3', className: 'bkbg-lis-title', value: item.title, allowedFormats: ['core/bold', 'core/italic'], placeholder: __('Item title…', 'blockenberg'), style: { color: attr.titleColor }, onChange: function (v) { updateItem(idx, 'title', v); } }),
232 el(TextControl, { value: item.subtitle, label: '', placeholder: __('Subtitle / category…', 'blockenberg'), __nextHasNoMarginBottom: true, style: { fontSize: '13px', color: attr.subtitleColor }, onChange: function (v) { updateItem(idx, 'subtitle', v); } }),
233 attr.showRatings && Stars(item.rating, attr.starColor),
234 el(RichText, { tagName: 'p', className: 'bkbg-lis-desc', value: item.description, allowedFormats: ['core/bold', 'core/italic', 'core/link'], placeholder: __('Description…', 'blockenberg'), style: { color: attr.descColor }, onChange: function (v) { updateItem(idx, 'description', v); } }),
235
236 el('div', { style: { marginTop: '12px', display: 'flex', gap: '6px' } },
237 el(Button, { isSmall: true, variant: 'secondary', onClick: function () {
238 var copy = Object.assign({}, item);
239 set({ items: items.concat([copy]) });
240 } }, ''),
241 el(Button, { isSmall: true, isDestructive: true, variant: 'tertiary', onClick: function () { set({ items: items.filter(function (_, i) { return i !== idx; }) }); } }, '×')
242 )
243 )
244 );
245 })
246 ),
247 el('div', { style: { marginTop: '16px', textAlign: 'center' } },
248 el(Button, { variant: 'primary', isSmall: true, onClick: function () { set({ items: items.concat([emptyItem()]) }); } }, '+ Add Item')
249 )
250 );
251 },
252 save: function (props) {
253 var attr = props.attributes;
254 var useBlockProps = wp.blockEditor.useBlockProps;
255 return el('div', useBlockProps.save(),
256 el('div', { className: 'bkbg-lis-app', 'data-opts': JSON.stringify(attr) })
257 );
258 }
259 });
260 }() );
261