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

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

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