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

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

265 lines 21.9 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 BADGES = ["Editor's Pick", "Best Value", "Premium", "Budget Pick", "Most Popular", "Best Overall", ""];
19
20 var _tc; function getTypoControl(){ return _tc || (_tc = window.bkbgTypographyControl); }
21 var _tv; function getTypoCssVars(){ return _tv || (_tv = window.bkbgTypoCssVars); }
22
23 function stars(rating, color) {
24 var full = Math.floor(rating);
25 var half = rating - full >= 0.5;
26 var html = '';
27 for (var i = 0; i < 5; i++) {
28 if (i < full) html += '�
29 ';
30 else if (i === full && half) html += '½';
31 else html += '';
32 }
33 return el('span', { style: { color: color, fontSize: '15px', letterSpacing: '1px' } }, html,
34 el('span', { style: { fontSize: '12px', color: '#94a3b8', marginLeft: '4px' } }, '(' + rating.toFixed(1) + ')')
35 );
36 }
37
38 function updateProduct(products, idx, key, val) {
39 return products.map(function (p, i) {
40 if (i !== idx) return p;
41 var c = Object.assign({}, p); c[key] = val; return c;
42 });
43 }
44
45 function updateProsConsItem(products, idx, field, itemIdx, val) {
46 return products.map(function (p, i) {
47 if (i !== idx) return p;
48 var arr = (p[field] || []).map(function (s, j) { return j === itemIdx ? val : s; });
49 return Object.assign({}, p, { [field]: arr });
50 });
51 }
52
53 registerBlockType('blockenberg/product-roundup', {
54 edit: function (props) {
55 var attr = props.attributes;
56 var set = props.setAttributes;
57 var products = attr.products || [];
58 var TC = getTypoControl();
59
60 var blockProps = useBlockProps((function(){
61 var fn = getTypoCssVars();
62 var s = { padding: attr.paddingTop + 'px 0 ' + attr.paddingBottom + 'px', background: attr.bgColor, borderRadius: attr.borderRadius + 'px' };
63 if(fn){
64 Object.assign(s, fn(attr.titleTypo||{}, '--bkbg-pru-tt-'));
65 Object.assign(s, fn(attr.subtitleTypo||{}, '--bkbg-pru-st-'));
66 Object.assign(s, fn(attr.nameTypo||{}, '--bkbg-pru-nm-'));
67 Object.assign(s, fn(attr.summaryTypo||{}, '--bkbg-pru-sm-'));
68 }
69 return { style: s };
70 })());
71
72 function addProduct() {
73 set({ products: products.concat([{ rank: products.length + 1, name: 'Product #' + (products.length + 1), image: '', imageId: 0, badge: "Editor's Pick", showBadge: false, rating: 4.0, summary: '', pros: [''], cons: [''], price: '', ctaLabel: 'Check Price', ctaUrl: '' }]) });
74 }
75 function removeProduct(idx) {
76 set({ products: products.filter(function (_, i) { return i !== idx; }).map(function (p, i) { return Object.assign({}, p, { rank: i + 1 }); }) });
77 }
78
79 var controls = el(InspectorControls, {},
80 el(PanelBody, { title: __('Layout & Display', 'blockenberg'), initialOpen: true },
81 el(SelectControl, {
82 label: __('Layout', 'blockenberg'), value: attr.layout, __nextHasNoMarginBottom: true,
83 options: [{ label: 'List (vertical)', value: 'list' }, { label: 'Grid (2 columns)', value: 'grid' }],
84 onChange: function (v) { set({ layout: v }); }
85 }),
86 el('div', { style: { marginTop: '12px' } },
87 el(ToggleControl, { label: __('Show Subtitle', 'blockenberg'), checked: attr.showSubtitle, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showSubtitle: v }); } })
88 ),
89 el('div', { style: { marginTop: '8px' } },
90 el(ToggleControl, { label: __('Show Rank Badge', 'blockenberg'), checked: attr.showRank, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showRank: v }); } })
91 ),
92 el('div', { style: { marginTop: '8px' } },
93 el(ToggleControl, { label: __('Show Product Image', 'blockenberg'), checked: attr.showImage, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showImage: v }); } })
94 ),
95 el('div', { style: { marginTop: '8px' } },
96 el(ToggleControl, { label: __('Show Star Rating', 'blockenberg'), checked: attr.showRating, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showRating: v }); } })
97 ),
98 el('div', { style: { marginTop: '8px' } },
99 el(ToggleControl, { label: __('Show Pros', 'blockenberg'), checked: attr.showPros, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showPros: v }); } })
100 ),
101 el('div', { style: { marginTop: '8px' } },
102 el(ToggleControl, { label: __('Show Cons', 'blockenberg'), checked: attr.showCons, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showCons: v }); } })
103 ),
104 el('div', { style: { marginTop: '8px' } },
105 el(ToggleControl, { label: __('Show Price', 'blockenberg'), checked: attr.showPrice, __nextHasNoMarginBottom: true, onChange: function (v) { set({ showPrice: v }); } })
106 ),
107 el('div', { style: { marginTop: '8px' } },
108 el(ToggleControl, { label: __('Open Links in New Tab', 'blockenberg'), checked: attr.openInNewTab, __nextHasNoMarginBottom: true, onChange: function (v) { set({ openInNewTab: v }); } })
109 )
110 ),
111 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
112 el(RangeControl, { label: __('Border Radius', 'blockenberg'), value: attr.borderRadius, min: 0, max: 30, __nextHasNoMarginBottom: true, onChange: function (v) { set({ borderRadius: v }); } }),
113 el('div', { style: { marginTop: '12px' } },
114 el(RangeControl, { label: __('Card Radius', 'blockenberg'), value: attr.cardRadius, min: 0, max: 20, __nextHasNoMarginBottom: true, onChange: function (v) { set({ cardRadius: v }); } })
115 ),
116 el('div', { style: { marginTop: '12px' } },
117 el(RangeControl, { label: __('Padding Top', 'blockenberg'), value: attr.paddingTop, min: 0, max: 100, __nextHasNoMarginBottom: true, onChange: function (v) { set({ paddingTop: v }); } })
118 ),
119 el('div', { style: { marginTop: '12px' } },
120 el(RangeControl, { label: __('Padding Bottom', 'blockenberg'), value: attr.paddingBottom, min: 0, max: 100, __nextHasNoMarginBottom: true, onChange: function (v) { set({ paddingBottom: v }); } })
121 )
122 ),
123 el(PanelBody, {title:__('Typography','blockenberg'),initialOpen:false},
124 TC && el(TC, {label:__('Section Title','blockenberg'),typo:attr.titleTypo||{},onChange:function(v){set({titleTypo:v});}}),
125 TC && el(TC, {label:__('Subtitle','blockenberg'),typo:attr.subtitleTypo||{},onChange:function(v){set({subtitleTypo:v});}}),
126 TC && el(TC, {label:__('Product Name','blockenberg'),typo:attr.nameTypo||{},onChange:function(v){set({nameTypo:v});}}),
127 TC && el(TC, {label:__('Summary','blockenberg'),typo:attr.summaryTypo||{},onChange:function(v){set({summaryTypo:v});}})
128 ),
129 el(PanelColorSettings, {
130 title: __('Colors', 'blockenberg'), initialOpen: false,
131 colorSettings: [
132 { label: __('Section Background', 'blockenberg'), value: attr.bgColor, onChange: function (v) { set({ bgColor: v || '#ffffff' }); } },
133 { label: __('Section Border', 'blockenberg'), value: attr.borderColor, onChange: function (v) { set({ borderColor: v || '#e2e8f0' }); } },
134 { label: __('Heading', 'blockenberg'), value: attr.headingColor, onChange: function (v) { set({ headingColor: v || '#0f172a' }); } },
135 { label: __('Subtitle', 'blockenberg'), value: attr.subtitleColor, onChange: function (v) { set({ subtitleColor: v || '#64748b' }); } },
136 { label: __('Card Background', 'blockenberg'), value: attr.cardBg, onChange: function (v) { set({ cardBg: v || '#ffffff' }); } },
137 { label: __('Card Border', 'blockenberg'), value: attr.cardBorder, onChange: function (v) { set({ cardBorder: v || '#e2e8f0' }); } },
138 { label: __('Rank Badge BG', 'blockenberg'), value: attr.rankBg, onChange: function (v) { set({ rankBg: v || '#0f172a' }); } },
139 { label: __('Rank Badge Text', 'blockenberg'), value: attr.rankColor, onChange: function (v) { set({ rankColor: v || '#ffffff' }); } },
140 { label: __('Pick Badge BG', 'blockenberg'), value: attr.badgeBg, onChange: function (v) { set({ badgeBg: v || '#f59e0b' }); } },
141 { label: __('Pick Badge Text', 'blockenberg'), value: attr.badgeColor, onChange: function (v) { set({ badgeColor: v || '#ffffff' }); } },
142 { label: __('Product Name', 'blockenberg'), value: attr.nameColor, onChange: function (v) { set({ nameColor: v || '#0f172a' }); } },
143 { label: __('Rating Stars', 'blockenberg'), value: attr.ratingColor, onChange: function (v) { set({ ratingColor: v || '#f59e0b' }); } },
144 { label: __('Summary Text', 'blockenberg'), value: attr.summaryColor, onChange: function (v) { set({ summaryColor: v || '#475569' }); } },
145 { label: __('Pros Color', 'blockenberg'), value: attr.prosColor, onChange: function (v) { set({ prosColor: v || '#16a34a' }); } },
146 { label: __('Cons Color', 'blockenberg'), value: attr.consColor, onChange: function (v) { set({ consColor: v || '#dc2626' }); } },
147 { label: __('Price', 'blockenberg'), value: attr.priceColor, onChange: function (v) { set({ priceColor: v || '#0f172a' }); } },
148 { label: __('CTA Button BG', 'blockenberg'), value: attr.ctaBg, onChange: function (v) { set({ ctaBg: v || '#0f172a' }); } },
149 { label: __('CTA Button Text', 'blockenberg'), value: attr.ctaColor, onChange: function (v) { set({ ctaColor: v || '#ffffff' }); } }
150 ]
151 })
152 );
153
154 var headerEl = el('div', { style: { marginBottom: '24px' } },
155 el(RichText, { tagName: 'h2', className: 'bkbg-pru-title', value: attr.sectionTitle, allowedFormats: ['core/bold', 'core/italic'], placeholder: __('Section Title', 'blockenberg'), style: { margin: '0 0 6px', color: attr.headingColor }, onChange: function (v) { set({ sectionTitle: v }); } }),
156 attr.showSubtitle && el(RichText, { tagName: 'p', className: 'bkbg-pru-subtitle', value: attr.sectionSubtitle, allowedFormats: ['core/bold', 'core/italic'], placeholder: __('Optional subtitle…', 'blockenberg'), style: { margin: 0, color: attr.subtitleColor }, onChange: function (v) { set({ sectionSubtitle: v }); } })
157 );
158
159 var gridStyle = attr.layout === 'grid'
160 ? { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px' }
161 : { display: 'flex', flexDirection: 'column', gap: '16px' };
162
163 var productCards = el('div', { style: gridStyle },
164 products.map(function (prod, idx) {
165 var cardStyle = { background: attr.cardBg, border: '1px solid ' + attr.cardBorder, borderRadius: attr.cardRadius + 'px', overflow: 'hidden' };
166 if (idx === 0 && attr.showRank) { cardStyle.borderTop = '3px solid #f59e0b'; }
167
168 var headerRow = el('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '12px', padding: '14px 16px 0' } },
169 attr.showRank && el('div', { style: { background: attr.rankBg, color: attr.rankColor, width: '32px', height: '32px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: '14px', flexShrink: 0 } }, '#' + prod.rank),
170 attr.showImage && el('div', { style: { flexShrink: 0 } },
171 el(MediaUploadCheck, {},
172 el(MediaUpload, {
173 onSelect: function (media) { set({ products: products.map(function (p, i) { return i !== idx ? p : Object.assign({}, p, { image: media.url, imageId: media.id }); }) }); },
174 allowedTypes: ['image'],
175 value: prod.imageId,
176 render: function (ref) {
177 return el(Button, { onClick: ref.open, style: { padding: 0, border: '1px dashed #94a3b8', borderRadius: '4px', overflow: 'hidden', display: 'block', width: '64px', height: '64px', background: prod.image ? 'transparent' : '#f8fafc' } },
178 prod.image
179 ? el('img', { src: prod.image, style: { width: '64px', height: '64px', objectFit: 'cover', display: 'block' } })
180 : el('span', { style: { fontSize: '10px', color: '#94a3b8', padding: '4px', textAlign: 'center', display: 'block', lineHeight: '1.2' } }, '+ Photo')
181 );
182 }
183 })
184 )
185 ),
186 el('div', { style: { flex: 1, minWidth: 0 } },
187 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap', marginBottom: '4px' } },
188 el(RichText, { tagName: 'span', className: 'bkbg-pru-name', value: prod.name, allowedFormats: [], placeholder: __('Product Name', 'blockenberg'), style: { color: attr.nameColor }, onChange: function (v) { set({ products: updateProduct(products, idx, 'name', v) }); } }),
189 prod.showBadge && prod.badge && el('span', { style: { background: attr.badgeBg, color: attr.badgeColor, padding: '2px 8px', borderRadius: '999px', fontSize: '11px', fontWeight: 600, whiteSpace: 'nowrap' } }, prod.badge)
190 ),
191 attr.showRating && el('div', {}, stars(prod.rating || 0, attr.ratingColor))
192 )
193 );
194
195 var bodyEl = el('div', { style: { padding: '10px 16px 4px' } },
196 el(RichText, { tagName: 'p', className: 'bkbg-pru-summary', value: prod.summary, allowedFormats: ['core/bold', 'core/italic', 'core/link'], placeholder: __('Short product description…', 'blockenberg'), style: { margin: '0 0 10px', color: attr.summaryColor }, onChange: function (v) { set({ products: updateProduct(products, idx, 'summary', v) }); } }),
197 (attr.showPros || attr.showCons) && el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginBottom: '10px' } },
198 attr.showPros && el('div', {},
199 el('div', { style: { fontSize: '11px', fontWeight: 700, color: attr.prosColor, textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' } }, '✓ Pros'),
200 (prod.pros || []).map(function (pro, pIdx) {
201 return el('div', { key: pIdx, style: { display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '4px' } },
202 el(TextControl, { value: pro, placeholder: __('Pro', 'blockenberg'), __nextHasNoMarginBottom: true, style: { fontSize: '12px' }, onChange: function (v) { set({ products: updateProsConsItem(products, idx, 'pros', pIdx, v) }); } }),
203 el(Button, { isSmall: true, isDestructive: true, variant: 'tertiary', onClick: function () { var arr = (prod.pros || []).filter(function (_, i) { return i !== pIdx; }); set({ products: updateProduct(products, idx, 'pros', arr) }); } }, '×')
204 );
205 }),
206 el(Button, { variant: 'link', style: { fontSize: '11px' }, onClick: function () { set({ products: updateProduct(products, idx, 'pros', (prod.pros || []).concat([''])) }); } }, '+')
207 ),
208 attr.showCons && el('div', {},
209 el('div', { style: { fontSize: '11px', fontWeight: 700, color: attr.consColor, textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' } }, '✗ Cons'),
210 (prod.cons || []).map(function (con, cIdx) {
211 return el('div', { key: cIdx, style: { display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '4px' } },
212 el(TextControl, { value: con, placeholder: __('Con', 'blockenberg'), __nextHasNoMarginBottom: true, style: { fontSize: '12px' }, onChange: function (v) { set({ products: updateProsConsItem(products, idx, 'cons', cIdx, v) }); } }),
213 el(Button, { isSmall: true, isDestructive: true, variant: 'tertiary', onClick: function () { var arr = (prod.cons || []).filter(function (_, i) { return i !== cIdx; }); set({ products: updateProduct(products, idx, 'cons', arr) }); } }, '×')
214 );
215 }),
216 el(Button, { variant: 'link', style: { fontSize: '11px' }, onClick: function () { set({ products: updateProduct(products, idx, 'cons', (prod.cons || []).concat([''])) }); } }, '+')
217 )
218 )
219 );
220
221 var footerEl = el('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px 14px', borderTop: '1px solid ' + attr.cardBorder, marginTop: '8px', flexWrap: 'wrap', gap: '8px' } },
222 el('div', { style: { display: 'flex', alignItems: 'center', gap: '10px', flexWrap: 'wrap' } },
223 attr.showPrice && el(TextControl, { label: '', value: prod.price, placeholder: '$0.00', __nextHasNoMarginBottom: true, style: { fontWeight: 700, color: attr.priceColor, fontSize: '16px', width: '90px' }, onChange: function (v) { set({ products: updateProduct(products, idx, 'price', v) }); } }),
224 el(ToggleControl, { label: __('Badge', 'blockenberg'), checked: prod.showBadge, __nextHasNoMarginBottom: true, onChange: function (v) { set({ products: updateProduct(products, idx, 'showBadge', v) }); } }),
225 prod.showBadge && el(TextControl, { value: prod.badge, placeholder: "Editor's Pick", __nextHasNoMarginBottom: true, style: { width: '130px' }, onChange: function (v) { set({ products: updateProduct(products, idx, 'badge', v) }); } })
226 ),
227 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
228 el(TextControl, { value: prod.ctaLabel, placeholder: __('CTA Label', 'blockenberg'), __nextHasNoMarginBottom: true, style: { width: '120px' }, onChange: function (v) { set({ products: updateProduct(products, idx, 'ctaLabel', v) }); } }),
229 el(TextControl, { value: prod.ctaUrl, placeholder: __('URL', 'blockenberg'), __nextHasNoMarginBottom: true, style: { width: '160px' }, onChange: function (v) { set({ products: updateProduct(products, idx, 'ctaUrl', v) }); } }),
230 attr.showRating && el(RangeControl, { label: __('Rating', 'blockenberg'), value: prod.rating, min: 0, max: 5, step: 0.1, __nextHasNoMarginBottom: true, style: { width: '160px' }, onChange: function (v) { set({ products: updateProduct(products, idx, 'rating', v) }); } }),
231 el(Button, { variant: 'link', isDestructive: true, style: { fontSize: '11px' }, onClick: function () { removeProduct(idx); } }, __('Remove Product', 'blockenberg'))
232 )
233 );
234
235 return el('div', { key: idx, style: cardStyle },
236 headerRow,
237 bodyEl,
238 footerEl
239 );
240 })
241 );
242
243 var addBtn = el('div', { style: { textAlign: 'center', marginTop: '16px' } },
244 el(Button, { variant: 'secondary', onClick: addProduct }, __('+ Add Product', 'blockenberg'))
245 );
246
247 return el('div', blockProps,
248 controls,
249 el('div', { className: 'bkbg-pru-wrap', style: { padding: '24px' } },
250 headerEl,
251 productCards,
252 addBtn
253 )
254 );
255 },
256 save: function (props) {
257 var attr = props.attributes;
258 var useBlockProps = wp.blockEditor.useBlockProps;
259 return el('div', useBlockProps.save(),
260 el('div', { className: 'bkbg-pru-app', 'data-opts': JSON.stringify(attr) })
261 );
262 }
263 });
264 }() );
265