PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 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 / video-testimonial / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at blocks/video-testimonial/index.js

346 lines 24.3 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 __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var MediaUpload = wp.blockEditor.MediaUpload;
9 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11 var PanelBody = wp.components.PanelBody;
12 var Button = wp.components.Button;
13 var ToggleControl = wp.components.ToggleControl;
14 var RangeControl = wp.components.RangeControl;
15 var SelectControl = wp.components.SelectControl;
16 var TextControl = wp.components.TextControl;
17 var TextareaControl = wp.components.TextareaControl;
18 var ColorPicker = wp.components.ColorPicker;
19 var Popover = wp.components.Popover;
20
21 // Resolve the shared components lazily without defining globals for each block.
22 function _tc(label, key, attrs, setA) {
23 var Control = window.bkbgTypographyControl;
24 return Control ? el(Control, {
25 label: label,
26 value: attrs[key] || {},
27 onChange: function (value) { var update = {}; update[key] = value; setA(update); }
28 }) : null;
29 }
30 function _tvf(vars, key, attrs, prefix) {
31 var cssVars = window.bkbgTypoCssVars;
32 if (cssVars) Object.assign(vars, cssVars(attrs[key] || {}, prefix));
33 return vars;
34 }
35 function getTypoControl(label, key, attrs, setA) { return _tc(label, key, attrs, setA); }
36 function getTypoCssVars(attrs) {
37 var v = {};
38 _tvf(v, 'quoteTypo', attrs, '--bkvt-qt-');
39 _tvf(v, 'authorTypo', attrs, '--bkvt-au-');
40 _tvf(v, 'roleTypo', attrs, '--bkvt-rl-');
41 return v;
42 }
43
44 var LAYOUT_OPTIONS = [
45 { label: __('Grid', 'blockenberg'), value: 'grid' },
46 { label: __('Masonry', 'blockenberg'), value: 'masonry' },
47 { label: __('Featured', 'blockenberg'), value: 'featured' },
48 ];
49 var SHADOW_OPTIONS = [
50 { label: __('None', 'blockenberg'), value: 'none' },
51 { label: __('Small', 'blockenberg'), value: 'sm' },
52 { label: __('Medium', 'blockenberg'), value: 'md' },
53 { label: __('Large', 'blockenberg'), value: 'lg' },
54 ];
55 var ASPECT_OPTIONS = [
56 { label: '16:9', value: '16/9' }, { label: '4:3', value: '4/3' }, { label: '1:1', value: '1/1' },
57 ];
58
59 function makeId() { return 'vt' + Math.random().toString(36).substr(2, 6); }
60
61 function buildWrapStyle(a) {
62 var shadow = a.cardShadow === 'sm' ? '0 1px 4px rgba(0,0,0,0.07)'
63 : a.cardShadow === 'md' ? '0 4px 20px rgba(0,0,0,0.1)'
64 : a.cardShadow === 'lg' ? '0 8px 40px rgba(0,0,0,0.14)' : 'none';
65 var s = getTypoCssVars(a);
66 s['--bkbg-vt-shadow'] = shadow;
67 s['--bkbg-vt-radius'] = a.cardRadius + 'px';
68 s['--bkbg-vt-pad'] = a.cardPadding + 'px';
69 s['--bkbg-vt-card-bg'] = a.cardBg;
70 s['--bkbg-vt-quote-color'] = a.quoteColor;
71 s['--bkbg-vt-author-color'] = a.authorColor;
72 s['--bkbg-vt-role-color'] = a.roleColor;
73 s['--bkbg-vt-star-color'] = a.starColor;
74 s['--bkbg-vt-accent'] = a.accentColor;
75 s['--bkbg-vt-overlay-bg'] = a.overlayBg;
76 s['--bkbg-vt-play-color'] = a.playIconColor;
77 s.paddingTop = a.paddingTop + 'px';
78 s.paddingBottom = a.paddingBottom + 'px';
79 s.backgroundColor = a.bgColor || undefined;
80 return s;
81 }
82
83 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
84 var isOpen = openKey === key;
85 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
86 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
87 el('div', { style: { position: 'relative', flexShrink: 0 } },
88 el('button', { type: 'button', title: value || 'default', onClick: function () { setOpenKey(isOpen ? null : key); }, style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#cccccc' } }),
89 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
90 el('div', { style: { padding: '8px' } },
91 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
92 )
93 )
94 )
95 );
96 }
97
98 /* ── Get YouTube/Vimeo embed URL ─────────────────────────────────────── */
99 function getEmbedUrl(url) {
100 if (!url) return '';
101 var ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&?/]+)/);
102 if (ytMatch) return 'https://www.youtube.com/embed/' + ytMatch[1] + '?autoplay=1&rel=0';
103 var vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
104 if (vimeoMatch) return 'https://player.vimeo.com/video/' + vimeoMatch[1] + '?autoplay=1';
105 return url;
106 }
107
108 function getVideoId(url) {
109 if (!url) return '';
110 var ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&?/]+)/);
111 if (ytMatch) return 'yt:' + ytMatch[1];
112 var vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
113 if (vimeoMatch) return 'vimeo:' + vimeoMatch[1];
114 return url;
115 }
116
117 /* ── Star row ─────────────────────────────────────────────────────────── */
118 function StarRow(props) {
119 var rating = props.rating || 5;
120 var size = props.size || 16;
121 var color = props.color || '#f59e0b';
122 var stars = [];
123 for (var i = 1; i <= 5; i++) {
124 stars.push(el('svg', { key: i, width: size, height: size, viewBox: '0 0 24 24', style: { display: 'inline-block', marginRight: '2px' } },
125 el('polygon', { points: '12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2', fill: i <= rating ? color : '#e5e7eb', stroke: i <= rating ? color : '#e5e7eb', strokeWidth: '1' })
126 ));
127 }
128 return el('div', { className: 'bkbg-vt-stars', style: { display: 'flex', alignItems: 'center', gap: '1px' } }, stars);
129 }
130
131 /* ── Play button SVG ──────────────────────────────────────────────────── */
132 function PlayBtn(props) {
133 return el('div', { className: 'bkbg-vt-play', style: { width: '56px', height: '56px', borderRadius: '50%', background: 'rgba(255,255,255,0.2)', backdropFilter: 'blur(4px)', border: '2px solid rgba(255,255,255,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', transition: 'transform 0.2s' } },
134 el('svg', { width: 22, height: 22, viewBox: '0 0 24 24', fill: props.color || '#fff' },
135 el('polygon', { points: '5 3 19 12 5 21 5 3' })
136 )
137 );
138 }
139
140 /* ── Single card ──────────────────────────────────────────────────────── */
141 function VTCard(props) {
142 var item = props.item;
143 var a = props.a;
144 var aspectParts = a.videoAspect.split('/');
145 var aspectPadding = ((parseInt(aspectParts[1], 10) / parseInt(aspectParts[0], 10)) * 100) + '%';
146
147 return el('div', { className: 'bkbg-vt-card' + (a.animate ? ' bkbg-vt-anim' : ''), style: { background: 'var(--bkbg-vt-card-bg)', borderRadius: 'var(--bkbg-vt-radius)', overflow: 'hidden', boxShadow: 'var(--bkbg-vt-shadow)', display: 'flex', flexDirection: 'column' } },
148 /* video thumbnail area */
149 el('div', { className: 'bkbg-vt-video-wrap', 'data-embed': getEmbedUrl(item.videoUrl), style: { position: 'relative', paddingBottom: aspectPadding, background: '#111', flexShrink: 0, cursor: 'pointer' } },
150 item.thumbnailUrl && el('img', { src: item.thumbnailUrl, alt: '', style: { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' } }),
151 !item.thumbnailUrl && el('div', { style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: '8px', color: '#9ca3af', background: '#1e1e2e' } },
152 el('span', { style: { fontSize: '28px' } }, '▶'),
153 el('span', { style: { fontSize: '11px' } }, item.videoUrl || __('Paste video URL in settings', 'blockenberg'))
154 ),
155 /* overlay */
156 el('div', { style: { position: 'absolute', inset: 0, background: 'var(--bkbg-vt-overlay-bg)', display: 'flex', alignItems: 'center', justifyContent: 'center' } },
157 el(PlayBtn, { color: a.playIconColor })
158 )
159 ),
160 /* text area */
161 el('div', { className: 'bkbg-vt-body', style: { padding: 'var(--bkbg-vt-pad)', display: 'flex', flexDirection: 'column', gap: '12px', flex: 1 } },
162 a.showRating && el(StarRow, { rating: item.rating, size: a.starSize, color: a.starColor }),
163 a.showQuote && item.quote && el('p', { className: 'bkbg-vt-quote', style: { color: 'var(--bkbg-vt-quote-color)', margin: 0 } },
164 el('span', { style: { color: a.accentColor, fontSize: (a.quoteSize + 6) + 'px', lineHeight: 0.5, verticalAlign: '-0.3em', fontStyle: 'normal', marginRight: '4px', fontFamily: 'Georgia, serif' } }, '\u201C'),
165 item.quote
166 ),
167 el('div', { className: 'bkbg-vt-author-row', style: { display: 'flex', alignItems: 'center', gap: '10px' } },
168 el('div', { style: { width: '36px', height: '36px', borderRadius: '50%', background: a.accentColor + '22', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '15px', fontWeight: 700, color: a.accentColor, flexShrink: 0 } }, (item.author || '?').charAt(0).toUpperCase()),
169 el('div', null,
170 el('p', { className: 'bkbg-vt-author', style: { margin: 0, color: 'var(--bkbg-vt-author-color)' } }, item.author),
171 (item.role || item.company) && el('p', { className: 'bkbg-vt-role', style: { margin: 0, color: 'var(--bkbg-vt-role-color)' } },
172 [item.role, a.showCompany && item.company].filter(Boolean).join(' · ')
173 )
174 )
175 )
176 )
177 );
178 }
179
180 registerBlockType('blockenberg/video-testimonial', {
181 title: __('Video Testimonial', 'blockenberg'),
182 icon: 'video-alt3',
183 category: 'bkbg-marketing',
184 description: __('Testimonial cards with embedded video, star rating, and author.', 'blockenberg'),
185
186 edit: function (props) {
187 var a = props.attributes;
188 var setAttributes = props.setAttributes;
189
190 var openColorKeyState = useState(null);
191 var openColorKey = openColorKeyState[0];
192 var setOpenColorKey = openColorKeyState[1];
193
194 var blockProps = useBlockProps({ style: buildWrapStyle(a) });
195
196 function cc(key, label, attrKey) {
197 return renderColorControl(key, label, a[attrKey], function (val) {
198 var upd = {}; upd[attrKey] = val; setAttributes(upd);
199 }, openColorKey, setOpenColorKey);
200 }
201
202 function updateItem(idx, key, val) {
203 var items = a.items.map(function (it, i) { if (i !== idx) return it; var u = Object.assign({}, it); u[key] = val; return u; });
204 setAttributes({ items: items });
205 }
206 function addItem() {
207 setAttributes({ items: a.items.concat([{ id: makeId(), videoUrl: '', thumbnailUrl: '', thumbnailId: 0, quote: 'This product exceeded every expectation we had. Highly recommend it to any team.', author: 'New Customer', role: 'Role', company: 'Company', rating: 5 }]) });
208 }
209 function removeItem(idx) {
210 if (a.items.length <= 1) return;
211 setAttributes({ items: a.items.filter(function (_, i) { return i !== idx; }) });
212 }
213
214 var gridStyle = a.layout === 'masonry'
215 ? { columnCount: a.columns, columnGap: a.gap + 'px' }
216 : { display: 'grid', gridTemplateColumns: 'repeat(' + a.columns + ', 1fr)', gap: a.gap + 'px' };
217
218 return el(Fragment, null,
219 el(InspectorControls, null,
220 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
221 el(SelectControl, { label: __('Layout', 'blockenberg'), value: a.layout, options: LAYOUT_OPTIONS, onChange: function (v) { setAttributes({ layout: v }); } }),
222 el(RangeControl, { label: __('Columns', 'blockenberg'), value: a.columns, min: 1, max: 4, onChange: function (v) { setAttributes({ columns: v }); } }),
223 el(RangeControl, { label: __('Gap (px)', 'blockenberg'), value: a.gap, min: 8, max: 60, onChange: function (v) { setAttributes({ gap: v }); } }),
224 el(SelectControl, { label: __('Video aspect ratio', 'blockenberg'), value: a.videoAspect, options: ASPECT_OPTIONS, onChange: function (v) { setAttributes({ videoAspect: v }); } }),
225 el(RangeControl, { label: __('Card radius (px)', 'blockenberg'), value: a.cardRadius, min: 0, max: 40, onChange: function (v) { setAttributes({ cardRadius: v }); } }),
226 el(RangeControl, { label: __('Card padding (px)', 'blockenberg'), value: a.cardPadding, min: 8, max: 52, onChange: function (v) { setAttributes({ cardPadding: v }); } }),
227 el(SelectControl, { label: __('Card shadow', 'blockenberg'), value: a.cardShadow, options: SHADOW_OPTIONS, onChange: function (v) { setAttributes({ cardShadow: v }); } }),
228 el(ToggleControl, { label: __('Animate on scroll', 'blockenberg'), checked: a.animate, onChange: function (v) { setAttributes({ animate: v }); }, __nextHasNoMarginBottom: true })
229 ),
230 el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false },
231 el(ToggleControl, { label: __('Show star rating', 'blockenberg'), checked: a.showRating, onChange: function (v) { setAttributes({ showRating: v }); }, __nextHasNoMarginBottom: true }),
232 el(ToggleControl, { label: __('Show quote text', 'blockenberg'), checked: a.showQuote, onChange: function (v) { setAttributes({ showQuote: v }); }, __nextHasNoMarginBottom: true }),
233 el(ToggleControl, { label: __('Show role', 'blockenberg'), checked: a.showRole, onChange: function (v) { setAttributes({ showRole: v }); }, __nextHasNoMarginBottom: true }),
234 el(ToggleControl, { label: __('Show company', 'blockenberg'), checked: a.showCompany, onChange: function (v) { setAttributes({ showCompany: v }); }, __nextHasNoMarginBottom: true })
235 ),
236 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
237 getTypoControl(__('Quote', 'blockenberg'), 'quoteTypo', a, setAttributes),
238 getTypoControl(__('Author', 'blockenberg'), 'authorTypo', a, setAttributes),
239 getTypoControl(__('Role', 'blockenberg'), 'roleTypo', a, setAttributes),
240 el(RangeControl, { label: __('Star size (px)', 'blockenberg'), value: a.starSize, min: 10, max: 28, onChange: function (v) { setAttributes({ starSize: v }); } })
241 ),
242 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
243 cc('accent', __('Accent', 'blockenberg'), 'accentColor'),
244 cc('cardBg', __('Card background', 'blockenberg'), 'cardBg'),
245 cc('quoteColor', __('Quote text', 'blockenberg'), 'quoteColor'),
246 cc('authorColor', __('Author name', 'blockenberg'), 'authorColor'),
247 cc('roleColor', __('Role / company', 'blockenberg'), 'roleColor'),
248 cc('starColor', __('Stars', 'blockenberg'), 'starColor'),
249 cc('overlayBg', __('Video overlay bg', 'blockenberg'), 'overlayBg'),
250 cc('playColor', __('Play icon', 'blockenberg'), 'playIconColor'),
251 cc('bgColor', __('Section bg', 'blockenberg'), 'bgColor')
252 ),
253 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
254 el(RangeControl, { label: __('Padding top (px)', 'blockenberg'), value: a.paddingTop, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingTop: v }); } }),
255 el(RangeControl, { label: __('Padding bottom (px)', 'blockenberg'), value: a.paddingBottom, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingBottom: v }); } })
256 ),
257 el(PanelBody, { title: __('Testimonials', 'blockenberg'), initialOpen: false },
258 a.items.map(function (item, idx) {
259 return el(PanelBody, { key: item.id, title: item.author || (__('Testimonial', 'blockenberg') + ' ' + (idx + 1)), initialOpen: false },
260 el(TextControl, { label: __('Video URL (YouTube or Vimeo)', 'blockenberg'), value: item.videoUrl, onChange: function (v) { updateItem(idx, 'videoUrl', v); } }),
261 el(MediaUploadCheck, null,
262 el(MediaUpload, {
263 onSelect: function (media) { setAttributes({ items: a.items.map(function (it, i) { return i !== idx ? it : Object.assign({}, it, { thumbnailUrl: media.url, thumbnailId: media.id }); }) }); },
264 allowedTypes: ['image'], value: item.thumbnailId,
265 render: function (p) {
266 return el('div', { style: { marginBottom: '8px' } },
267 item.thumbnailUrl && el('img', { src: item.thumbnailUrl, style: { width: '100%', height: '60px', objectFit: 'cover', borderRadius: '4px', marginBottom: '4px' } }),
268 el(Button, { onClick: p.open, variant: item.thumbnailUrl ? 'secondary' : 'primary', size: 'compact' }, item.thumbnailUrl ? __('Replace thumbnail', 'blockenberg') : __('Upload thumbnail', 'blockenberg'))
269 );
270 }
271 })
272 ),
273 el(TextareaControl, { label: __('Quote', 'blockenberg'), value: item.quote, onChange: function (v) { updateItem(idx, 'quote', v); } }),
274 el(TextControl, { label: __('Author name', 'blockenberg'), value: item.author, onChange: function (v) { updateItem(idx, 'author', v); } }),
275 el(TextControl, { label: __('Role', 'blockenberg'), value: item.role, onChange: function (v) { updateItem(idx, 'role', v); } }),
276 el(TextControl, { label: __('Company', 'blockenberg'), value: item.company, onChange: function (v) { updateItem(idx, 'company', v); } }),
277 el(RangeControl, { label: __('Star rating', 'blockenberg'), value: item.rating, min: 1, max: 5, onChange: function (v) { updateItem(idx, 'rating', v); } }),
278 a.items.length > 1 && el(Button, { onClick: function () { removeItem(idx); }, variant: 'tertiary', isDestructive: true, size: 'compact', style: { marginTop: '8px' } }, __('Remove', 'blockenberg'))
279 );
280 }),
281 el(Button, { onClick: addItem, variant: 'primary', style: { marginTop: '8px' } }, __('+ Add Testimonial', 'blockenberg'))
282 )
283 ),
284
285 el('div', blockProps,
286 el('div', { className: 'bkbg-vt-grid bkbg-vt-layout-' + a.layout, style: gridStyle },
287 a.items.map(function (item, idx) {
288 return el(VTCard, { key: item.id || idx, item: item, a: a });
289 })
290 )
291 )
292 );
293 },
294
295 save: function (props) {
296 var a = props.attributes;
297 var gridStyle = a.layout === 'masonry'
298 ? { columnCount: a.columns, columnGap: a.gap + 'px' }
299 : { display: 'grid', gridTemplateColumns: 'repeat(' + a.columns + ', 1fr)', gap: a.gap + 'px' };
300
301 return el('div', wp.blockEditor.useBlockProps.save({
302 className: 'bkbg-vt-wrapper bkbg-vt-layout-' + a.layout,
303 style: buildWrapStyle(a)
304 }),
305 el('div', { className: 'bkbg-vt-grid', style: gridStyle },
306 a.items.map(function (item, idx) {
307 var aspectParts = a.videoAspect.split('/');
308 var aspectPadding = ((parseInt(aspectParts[1], 10) / parseInt(aspectParts[0], 10)) * 100) + '%';
309 return el('div', { key: item.id || idx, className: 'bkbg-vt-card' + (a.animate ? ' bkbg-vt-anim' : ''), 'data-embed': getEmbedUrl(item.videoUrl) },
310 el('div', { className: 'bkbg-vt-video-wrap', style: { position: 'relative', paddingBottom: aspectPadding } },
311 item.thumbnailUrl && el('img', { className: 'bkbg-vt-thumb', src: item.thumbnailUrl, alt: '', loading: 'lazy' }),
312 el('div', { className: 'bkbg-vt-overlay' },
313 el('div', { className: 'bkbg-vt-play' },
314 el('svg', { width: 22, height: 22, viewBox: '0 0 24 24', fill: a.playIconColor },
315 el('polygon', { points: '5 3 19 12 5 21 5 3' })
316 )
317 )
318 )
319 ),
320 el('div', { className: 'bkbg-vt-body' },
321 a.showRating && el('div', { className: 'bkbg-vt-stars' },
322 Array.from({ length: 5 }).map(function (_, i) {
323 return el('svg', { key: i, width: a.starSize, height: a.starSize, viewBox: '0 0 24 24', style: { display: 'inline-block', marginRight: '2px' } },
324 el('polygon', { points: '12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2', fill: i < item.rating ? a.starColor : '#e5e7eb', stroke: i < item.rating ? a.starColor : '#e5e7eb', strokeWidth: '1' })
325 );
326 })
327 ),
328 a.showQuote && item.quote && el('p', { className: 'bkbg-vt-quote' }, item.quote),
329 el('div', { className: 'bkbg-vt-author-row' },
330 el('div', { className: 'bkbg-vt-avatar', style: { background: a.accentColor + '22', color: a.accentColor } }, (item.author || '?').charAt(0).toUpperCase()),
331 el('div', null,
332 el('p', { className: 'bkbg-vt-author', style: { color: a.authorColor } }, item.author),
333 (item.role || item.company) && el('p', { className: 'bkbg-vt-role', style: { color: a.roleColor } },
334 [item.role, a.showCompany && item.company].filter(Boolean).join(' · ')
335 )
336 )
337 )
338 )
339 );
340 })
341 )
342 );
343 }
344 });
345 }() );
346