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

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

287 lines 19.8 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 useState = wp.element.useState;
4 var Fragment = wp.element.Fragment;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var __ = wp.i18n.__;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var RangeControl = wp.components.RangeControl;
12 var TextControl = wp.components.TextControl;
13 var TextareaControl = wp.components.TextareaControl;
14 var ToggleControl = wp.components.ToggleControl;
15 var Button = wp.components.Button;
16
17 function getTypographyControl() {
18 return (window.bkbgTypographyControl || function () { return null; });
19 }
20
21 function _tv(typo, prefix) {
22 if (!typo) return {};
23 var s = {};
24 if (typo.family) s[prefix + 'font-family'] = "'" + typo.family + "', sans-serif";
25 if (typo.weight) s[prefix + 'font-weight'] = typo.weight;
26 if (typo.transform) s[prefix + 'text-transform'] = typo.transform;
27 if (typo.style) s[prefix + 'font-style'] = typo.style;
28 if (typo.decoration) s[prefix + 'text-decoration'] = typo.decoration;
29 var su = typo.sizeUnit || 'px';
30 if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) s[prefix + 'font-size-d'] = typo.sizeDesktop + su;
31 if (typo.sizeTablet !== '' && typo.sizeTablet != null) s[prefix + 'font-size-t'] = typo.sizeTablet + su;
32 if (typo.sizeMobile !== '' && typo.sizeMobile != null) s[prefix + 'font-size-m'] = typo.sizeMobile + su;
33 var lhu = typo.lineHeightUnit || '';
34 if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) s[prefix + 'line-height-d'] = typo.lineHeightDesktop + lhu;
35 if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) s[prefix + 'line-height-t'] = typo.lineHeightTablet + lhu;
36 if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) s[prefix + 'line-height-m'] = typo.lineHeightMobile + lhu;
37 var lsu = typo.letterSpacingUnit || 'px';
38 if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) s[prefix + 'letter-spacing-d'] = typo.letterSpacingDesktop + lsu;
39 if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) s[prefix + 'letter-spacing-t'] = typo.letterSpacingTablet + lsu;
40 if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) s[prefix + 'letter-spacing-m'] = typo.letterSpacingMobile + lsu;
41 var wsu = typo.wordSpacingUnit || 'px';
42 if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) s[prefix + 'word-spacing-d'] = typo.wordSpacingDesktop + wsu;
43 if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) s[prefix + 'word-spacing-t'] = typo.wordSpacingTablet + wsu;
44 if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) s[prefix + 'word-spacing-m'] = typo.wordSpacingMobile + wsu;
45 return s;
46 }
47
48 /* ── Helpers ─────────────────────────────────────────────────────── */
49
50 function halfStarEl(starColor, key) {
51 return el('span', { key: key, style:{ position:'relative', display:'inline-block', color:'#d1d5db', lineHeight:1 } },
52 '�
53 ',
54 el('span', { style:{ position:'absolute', left:0, top:0, width:'50%', overflow:'hidden', color: starColor } }, '�
55 ')
56 );
57 }
58
59 function renderStars(rating, max, starColor) {
60 var full = Math.floor(rating);
61 var half = rating - full >= 0.5;
62 var empty = max - full - (half ? 1 : 0);
63 var stars = [];
64 var i;
65 for (i = 0; i < full; i++) stars.push(el('span', { key:'f'+i, style:{ color: starColor } }, '�
66 '));
67 if (half) stars.push(halfStarEl(starColor, 'h'));
68 for (i = 0; i < empty; i++) stars.push(el('span', { key:'e'+i, style:{ color:'#d1d5db' } }, '�
69 '));
70 return el('span', { className:'bkbg-br-stars', style:{ letterSpacing:'2px', fontSize:'22px' } }, stars);
71 }
72
73 /* ── Editor preview component ──────────────────────────────────────── */
74 function BookReviewPreview(props) {
75 var a = props.attributes;
76 var setAttr = props.setAttributes;
77 var cRadius = (a.cardRadius || 16) + 'px';
78 var bRadius = (a.btnRadius || 8) + 'px';
79 var maxW = (a.maxWidth || 860) + 'px';
80 var covW = (a.coverWidth || 160) + 'px';
81
82 /* Cover image or placeholder */
83 var cover = a.coverUrl
84 ? el('img', { src: a.coverUrl, alt: a.coverAlt, className: 'bkbg-br-cover',
85 style: { width: covW, flexShrink: 0, borderRadius: '8px', display: 'block', objectFit: 'cover' } })
86 : el('div', { className: 'bkbg-br-cover-placeholder',
87 style: { width: covW, flexShrink: 0, borderRadius: '8px', background: '#e5e7eb',
88 display: 'flex', alignItems: 'center', justifyContent: 'center',
89 minHeight: '220px', color: '#9ca3af', fontSize: '36px' } },
90 '📖');
91
92 /* Detail row helper */
93 function detailRow(label, value) {
94 if (!value) return null;
95 return el('div', { className: 'bkbg-br-detail-row', style: { display:'flex', gap:'8px', marginBottom:'4px' } },
96 el('span', { style: { color: a.detailLabelClr, fontSize:'13px', minWidth:'82px' } }, label + ':'),
97 el('span', { style: { color: a.bodyColor, fontSize:'13px', fontWeight:600 } }, value)
98 );
99 }
100
101 /* List editor (pros / cons) */
102 function renderEditableList(key, items, color, icon) {
103 return el('div', { className: 'bkbg-br-list' },
104 items.map(function (item, idx) {
105 return el('div', { key: idx, style: { display:'flex', alignItems:'center', gap:'8px', marginBottom:'6px' } },
106 el('span', { style: { color: color, fontSize:'16px', flexShrink:0 } }, icon),
107 el('span', {
108 className: 'bkbg-br-list-text',
109 style: { color: a.bodyColor, flexGrow:1, display:'block' },
110 contentEditable: true,
111 suppressContentEditableWarning: true,
112 onBlur: function (e) {
113 var updated = items.slice();
114 updated[idx] = e.target.innerText.trim();
115 var obj = {};
116 obj[key] = updated;
117 setAttr(obj);
118 },
119 dangerouslySetInnerHTML: { __html: item }
120 }),
121 el(Button, {
122 icon: 'no-alt', isSmall: true,
123 style: { color:'#dc2626', flexShrink:0 },
124 onClick: function () {
125 var upd = items.filter(function (_, i) { return i !== idx; });
126 var obj = {}; obj[key] = upd; setAttr(obj);
127 }
128 })
129 );
130 }),
131 el(Button, {
132 variant: 'link', style: { marginTop:'4px', color: color, fontSize:'13px' },
133 onClick: function () { var upd = items.concat(['']); var obj={}; obj[key]=upd; setAttr(obj); }
134 }, '+ Add item')
135 );
136 }
137
138 return el('div', { style: { maxWidth: maxW, margin: '0 auto', border: '1px solid ' + a.borderColor, borderRadius: cRadius, overflow: 'hidden', background: a.cardBg, fontFamily: 'inherit' } },
139 /* Header */
140 el('div', { style: { background: a.headerBg, padding: '28px', display: 'flex', gap: '24px', alignItems: 'flex-start' } },
141 cover,
142 el('div', { style: { flex: 1, minWidth: 0 } },
143 /* Title */
144 el('div', {
145 className: 'bkbg-br-title',
146 contentEditable: true, suppressContentEditableWarning: true,
147 onBlur: function (e) { setAttr({ bookTitle: e.target.innerText.trim() }); },
148 dangerouslySetInnerHTML: { __html: a.bookTitle },
149 style: { color: a.titleColor, marginBottom: '4px', outline: 'none' }
150 }),
151 /* Author */
152 el('div', {
153 contentEditable: true, suppressContentEditableWarning: true,
154 onBlur: function (e) { setAttr({ bookAuthor: e.target.innerText.trim() }); },
155 dangerouslySetInnerHTML: { __html: a.bookAuthor },
156 style: { color: a.authorColor, fontWeight: 600, fontSize: '15px', marginBottom: '12px', outline: 'none' }
157 }),
158 /* Stars + label */
159 el('div', { style: { display:'flex', alignItems:'center', gap:'10px', marginBottom:'16px' } },
160 renderStars(a.rating, a.maxRating, a.starColor),
161 el('span', { style: { color: a.bodyColor, fontSize:'14px' } }, a.rating + ' / ' + a.maxRating + '' + a.ratingLabel)
162 ),
163 /* Details grid */
164 a.showDetails && el('div', { style: { marginBottom:'8px' } },
165 detailRow('Genre', a.genre),
166 detailRow('Publisher', a.publisher),
167 detailRow('Year', a.publishYear),
168 detailRow('Pages', a.pages),
169 a.isbn && detailRow('ISBN', a.isbn)
170 )
171 )
172 ),
173 /* Body */
174 el('div', { style: { padding: '24px 28px', background: a.cardBg } },
175 /* Summary */
176 el('div', { style: { marginBottom:'20px' } },
177 el('div', { style: { fontWeight:700, color: a.titleColor, fontSize:'14px', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:'8px' } }, 'Review Summary'),
178 el('div', {
179 className: 'bkbg-br-summary',
180 contentEditable: true, suppressContentEditableWarning: true,
181 onBlur: function (e) { setAttr({ summary: e.target.innerHTML }); },
182 dangerouslySetInnerHTML: { __html: a.summary },
183 style: { color: a.bodyColor, outline:'none' }
184 })
185 ),
186 /* Pros / Cons */
187 (a.showPros || a.showCons) && el('div', { style: { display:'grid', gridTemplateColumns: (a.showPros && a.showCons) ? '1fr 1fr' : '1fr', gap:'20px', marginBottom:'20px' } },
188 a.showPros && el('div', null,
189 el('div', { style: { fontWeight:700, color: a.prosColor, marginBottom:'8px', fontSize:'14px' } }, '✓ Pros'),
190 renderEditableList('pros', a.pros, a.prosColor, '')
191 ),
192 a.showCons && el('div', null,
193 el('div', { style: { fontWeight:700, color: a.consColor, marginBottom:'8px', fontSize:'14px' } }, '✗ Cons'),
194 renderEditableList('cons', a.cons, a.consColor, '')
195 )
196 ),
197 /* Button */
198 a.showBtn && el('a', {
199 href: '#', className: 'bkbg-br-btn',
200 style: { display:'inline-block', background: a.btnBg, color: a.btnColor, borderRadius: bRadius,
201 padding:'12px 32px', fontWeight:700, fontSize:'15px', textDecoration:'none', cursor:'pointer' }
202 }, a.btnText)
203 )
204 );
205 }
206
207 /* ── Register block ──────────────────────────────────────────────── */
208 registerBlockType('blockenberg/book-review', {
209 edit: function (props) {
210 var a = props.attributes;
211 var setAttr = props.setAttributes;
212 var blockProps = useBlockProps({ className: 'bkbg-book-review-block', style: Object.assign({}, _tv(a.typoTitle, '--bkbg-br-title-'), _tv(a.typoBody, '--bkbg-br-body-')) });
213
214 return el('div', blockProps,
215 el(InspectorControls, null,
216 el(PanelBody, { title: __('Book Details', 'blockenberg'), initialOpen: true },
217 el(TextControl, { label: 'Book title', value: a.bookTitle, onChange: function (v) { setAttr({ bookTitle: v }); } }),
218 el(TextControl, { label: 'Author', value: a.bookAuthor, onChange: function (v) { setAttr({ bookAuthor: v }); } }),
219 el(TextControl, { label: 'Genre', value: a.genre, onChange: function (v) { setAttr({ genre: v }); } }),
220 el(TextControl, { label: 'Publisher', value: a.publisher, onChange: function (v) { setAttr({ publisher: v }); } }),
221 el(TextControl, { label: 'Publish year', value: a.publishYear, onChange: function (v) { setAttr({ publishYear: v }); } }),
222 el(TextControl, { label: 'Pages', value: a.pages, onChange: function (v) { setAttr({ pages: v }); } }),
223 el(TextControl, { label: 'ISBN (optional)', value: a.isbn, onChange: function (v) { setAttr({ isbn: v }); } }),
224 el(TextControl, { label: 'Cover image URL', value: a.coverUrl, onChange: function (v) { setAttr({ coverUrl: v }); } }),
225 el(TextControl, { label: 'Cover alt text', value: a.coverAlt, onChange: function (v) { setAttr({ coverAlt: v }); } }),
226 ),
227 el(PanelBody, { title: __('Rating', 'blockenberg'), initialOpen: false },
228 el(RangeControl, { label: 'Rating', value: a.rating, min: 0.5, max: 5, step: 0.5, onChange: function (v) { setAttr({ rating: v }); } }),
229 el(RangeControl, { label: 'Max rating', value: a.maxRating, min: 5, max: 10, step: 1, onChange: function (v) { setAttr({ maxRating: v }); } }),
230 el(TextControl, { label: 'Rating label', value: a.ratingLabel, onChange: function (v) { setAttr({ ratingLabel: v }); } }),
231 ),
232 el(PanelBody, { title: __('Buy Button', 'blockenberg'), initialOpen: false },
233 el(ToggleControl, { label: 'Show button', checked: a.showBtn, onChange: function (v) { setAttr({ showBtn: v }); }, __nextHasNoMarginBottom: true }),
234 a.showBtn && el(TextControl, { label: 'Button text', value: a.btnText, onChange: function (v) { setAttr({ btnText: v }); } }),
235 a.showBtn && el(TextControl, { label: 'Button URL', value: a.btnUrl, onChange: function (v) { setAttr({ btnUrl: v }); } }),
236 a.showBtn && el(ToggleControl, { label: 'Open in new tab', checked: a.btnTarget, onChange: function (v) { setAttr({ btnTarget: v }); }, __nextHasNoMarginBottom: true }),
237 ),
238 el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false },
239 el(ToggleControl, { label: 'Show book details', checked: a.showDetails, onChange: function (v) { setAttr({ showDetails: v }); }, __nextHasNoMarginBottom: true }),
240 el(ToggleControl, { label: 'Show pros', checked: a.showPros, onChange: function (v) { setAttr({ showPros: v }); }, __nextHasNoMarginBottom: true }),
241 el(ToggleControl, { label: 'Show cons', checked: a.showCons, onChange: function (v) { setAttr({ showCons: v }); }, __nextHasNoMarginBottom: true }),
242 el(ToggleControl, { label: 'Inject Schema.org JSON-LD', checked: a.showSchema, onChange: function (v) { setAttr({ showSchema: v }); }, __nextHasNoMarginBottom: true }),
243 ),
244
245 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
246 el(getTypographyControl(), { label: __('Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { setAttr({ typoTitle: v }); } }),
247 el(getTypographyControl(), { label: __('Body Text', 'blockenberg'), value: a.typoBody, onChange: function (v) { setAttr({ typoBody: v }); } })
248 ),
249 el(PanelColorSettings, {
250 title: __('Colors', 'blockenberg'),
251 initialOpen: false, colorSettings: [
252 { label: 'Accent', value: a.accentColor, onChange: function (v) { setAttr({ accentColor: v }); } },
253 { label: 'Card background',value: a.cardBg, onChange: function (v) { setAttr({ cardBg: v }); } },
254 { label: 'Header background',value: a.headerBg, onChange: function (v) { setAttr({ headerBg: v }); } },
255 { label: 'Border', value: a.borderColor, onChange: function (v) { setAttr({ borderColor: v }); } },
256 { label: 'Title', value: a.titleColor, onChange: function (v) { setAttr({ titleColor: v }); } },
257 { label: 'Author', value: a.authorColor, onChange: function (v) { setAttr({ authorColor: v }); } },
258 { label: 'Body text', value: a.bodyColor, onChange: function (v) { setAttr({ bodyColor: v }); } },
259 { label: 'Stars', value: a.starColor, onChange: function (v) { setAttr({ starColor: v }); } },
260 { label: 'Pros', value: a.prosColor, onChange: function (v) { setAttr({ prosColor: v }); } },
261 { label: 'Cons', value: a.consColor, onChange: function (v) { setAttr({ consColor: v }); } },
262 { label: 'Button bg', value: a.btnBg, onChange: function (v) { setAttr({ btnBg: v }); } },
263 { label: 'Button text', value: a.btnColor, onChange: function (v) { setAttr({ btnColor: v }); } },
264 ],
265 }),
266 el(PanelBody, { title: __('Sizing', 'blockenberg'), initialOpen: false },
267 el(RangeControl, { label: 'Max width (px)', value: a.maxWidth, min: 400, max: 1200, step: 10, onChange: function (v) { setAttr({ maxWidth: v }); } }),
268 el(RangeControl, { label: 'Cover width (px)',value: a.coverWidth, min: 80, max: 300, step: 10, onChange: function (v) { setAttr({ coverWidth: v }); } }),
269 el(RangeControl, { label: 'Card radius (px)',value: a.cardRadius, min: 0, max: 32, onChange: function (v) { setAttr({ cardRadius: v }); } }),
270 el(RangeControl, { label: 'Button radius (px)',value: a.btnRadius, min: 0, max: 50, onChange: function (v) { setAttr({ btnRadius: v }); } }),
271 )
272 ),
273 el(BookReviewPreview, { attributes: a, setAttributes: setAttr })
274 );
275 },
276
277 save: function (props) {
278 var a = props.attributes;
279 var blockProps = wp.blockEditor.useBlockProps.save({ className: 'bkbg-book-review-block' });
280 return el('div', Object.assign({}, blockProps, {
281 className: (blockProps.className || '') + ' bkbg-br-app',
282 'data-opts': JSON.stringify(a),
283 }));
284 },
285 });
286 }() );
287