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

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

541 lines 24.5 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 __ = wp.i18n.__;
5 var useState = wp.element.useState;
6
7 var registerBlockType = wp.blocks.registerBlockType;
8 var InspectorControls = wp.blockEditor.InspectorControls;
9 var BlockControls = wp.blockEditor.BlockControls;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11
12 var PanelBody = wp.components.PanelBody;
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 Button = wp.components.Button;
18 var ToolbarGroup = wp.components.ToolbarGroup;
19 var ToolbarButton = wp.components.ToolbarButton;
20 var Popover = wp.components.Popover;
21 var ColorPicker = wp.components.ColorPicker;
22
23 /* ── Typography helpers (lazy) ───────────────────────────────── */
24 var _tc, _tvf;
25 Object.defineProperty(window, '__bkytp_tc', { get: function () { return _tc || (_tc = window.bkbgTypographyControl); } });
26 Object.defineProperty(window, '__bkytp_tvf', { get: function () { return _tvf || (_tvf = window.bkbgTypoCssVars); } });
27 function getTypoControl(label, typoObj, setAttributes, attrName) {
28 var fn = window.__bkytp_tc;
29 return fn ? fn({ label: label, value: typoObj || {}, onChange: function (v) { var o = {}; o[attrName] = v; setAttributes(o); } }) : null;
30 }
31 function getTypoCssVarsObj(a) {
32 var fn = window.__bkytp_tvf;
33 var s = {};
34 if (fn) {
35 Object.assign(s, fn(a.titleTypo || {}, '--bkytp-tt-'));
36 }
37 return s;
38 }
39
40 function clamp(n, min, max) {
41 n = parseFloat(n);
42 if (isNaN(n)) n = min;
43 return Math.max(min, Math.min(max, n));
44 }
45
46 function colorToRgbaString(color) {
47 if (!color || !color.rgb) return '';
48 var a = (typeof color.rgb.a === 'number') ? color.rgb.a : 1;
49 return 'rgba(' + color.rgb.r + ',' + color.rgb.g + ',' + color.rgb.b + ',' + a + ')';
50 }
51
52 function normalizeImage(att) {
53 return {
54 id: att && att.id ? att.id : 0,
55 url: att && att.url ? att.url : '',
56 alt: att && att.alt ? att.alt : ''
57 };
58 }
59
60 function openMedia(onSelect) {
61 if (!wp || !wp.media) return;
62 var frame = wp.media({
63 title: __('Select Poster Image', 'blockenberg'),
64 button: { text: __('Use image', 'blockenberg') },
65 multiple: false,
66 library: { type: 'image' }
67 });
68 frame.on('select', function () {
69 var attachment = frame.state().get('selection').first().toJSON();
70 onSelect(attachment);
71 });
72 frame.open();
73 }
74
75 function extractYouTubeId(input) {
76 if (!input) return '';
77 var s = String(input).trim();
78 // If user pasted only the ID
79 if (/^[a-zA-Z0-9_-]{11}$/.test(s)) return s;
80
81 // youtu.be/<id>
82 var m = s.match(/youtu\.be\/([a-zA-Z0-9_-]{11})/);
83 if (m && m[1]) return m[1];
84
85 // youtube.com/watch?v=<id>
86 m = s.match(/[?&]v=([a-zA-Z0-9_-]{11})/);
87 if (m && m[1]) return m[1];
88
89 // youtube.com/embed/<id>
90 m = s.match(/\/embed\/([a-zA-Z0-9_-]{11})/);
91 if (m && m[1]) return m[1];
92
93 // youtube.com/shorts/<id>
94 m = s.match(/\/shorts\/([a-zA-Z0-9_-]{11})/);
95 if (m && m[1]) return m[1];
96
97 return '';
98 }
99
100 function youtubeThumb(id, variant) {
101 if (!id) return '';
102 var v = variant || 'youtube-max';
103 if (v === 'youtube-hq') return 'https://i.ytimg.com/vi/' + id + '/hqdefault.jpg';
104 return 'https://i.ytimg.com/vi/' + id + '/maxresdefault.jpg';
105 }
106
107 function buildEmbedSrc(a, autoplay) {
108 if (!a.videoId) return '';
109 var host = a.privacyMode ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com';
110 var params = [];
111 params.push('autoplay=' + (autoplay ? '1' : '0'));
112 params.push('controls=' + (a.controls ? '1' : '0'));
113 params.push('rel=' + (a.rel ? '1' : '0'));
114 if (a.mute) params.push('mute=1');
115 if (a.startTime && a.startTime > 0) params.push('start=' + Math.floor(a.startTime));
116 if (a.loop) {
117 params.push('loop=1');
118 params.push('playlist=' + encodeURIComponent(a.videoId));
119 }
120 return host + '/embed/' + encodeURIComponent(a.videoId) + '?' + params.join('&');
121 }
122
123 registerBlockType('blockenberg/youtube-performance', {
124 title: __('YouTube Performance Embed', 'blockenberg'),
125 icon: 'video-alt3',
126 category: 'bkbg-dev',
127 description: __('YouTube embed with poster + lazy iframe created only on user click.', 'blockenberg'),
128
129 edit: function (props) {
130 var a = props.attributes;
131 var setAttributes = props.setAttributes;
132 var isSelected = props.isSelected;
133
134 var previewState = useState(false);
135 var previewOn = previewState[0];
136 var setPreviewOn = previewState[1];
137
138 var playingState = useState(false);
139 var isPlaying = playingState[0];
140 var setIsPlaying = playingState[1];
141
142 var colorPopoverState = useState(null);
143 var openColor = colorPopoverState[0];
144 var setOpenColor = colorPopoverState[1];
145
146 function setVideoUrl(v) {
147 var id = extractYouTubeId(v);
148 setAttributes({ videoUrl: v, videoId: id });
149 setIsPlaying(false);
150 }
151
152 function setPoster(att) {
153 setAttributes({ posterImage: normalizeImage(att) });
154 }
155
156 var posterUrl = '';
157 if (a.posterSource === 'custom') {
158 posterUrl = (a.posterImage && a.posterImage.url) ? a.posterImage.url : '';
159 } else {
160 posterUrl = youtubeThumb(a.videoId, a.posterSource);
161 }
162
163 var wrapperClass = 'bkbg-ytp-wrap bkbg-editor-wrap';
164 if (isSelected) wrapperClass += ' bkbg-block-selected';
165 if (a.shadow) wrapperClass += ' has-shadow';
166 wrapperClass += ' is-play-' + (a.playStyle || 'circle');
167
168 var styleVars = {
169 '--bkbg-ytp-radius': (a.borderRadius || 0) + 'px',
170 '--bkbg-ytp-min-height': (a.minHeight || 260) + 'px',
171 '--bkbg-ytp-play-size': (a.playSize || 64) + 'px',
172 '--bkbg-ytp-play-bg': a.playBg,
173 '--bkbg-ytp-play-color': a.playColor,
174 '--bkbg-ytp-play-hover': (a.playHoverScale || 1.05)
175 };
176
177 var blockProps = useBlockProps({
178 className: wrapperClass,
179 style: styleVars,
180 'data-block-label': 'YouTube'
181 });
182
183 var toolbar = el(BlockControls, {},
184 el(ToolbarGroup, {},
185 el(ToolbarButton, {
186 icon: 'visibility',
187 label: __('Toggle editor preview', 'blockenberg'),
188 isPressed: previewOn,
189 onClick: function () {
190 setPreviewOn(!previewOn);
191 setIsPlaying(false);
192 }
193 }),
194 el(ToolbarButton, {
195 icon: 'controls-repeat',
196 label: __('Reset', 'blockenberg'),
197 onClick: function () { setIsPlaying(false); }
198 })
199 )
200 );
201
202 var inspector = el(InspectorControls, {},
203 el(PanelBody, { title: __('Video', 'blockenberg'), initialOpen: true },
204 el(TextControl, {
205 label: __('YouTube URL or Video ID', 'blockenberg'),
206 value: a.videoUrl,
207 onChange: setVideoUrl,
208 help: __('Paste a YouTube link (watch, short, embed, youtu.be) or an 11‑char ID.', 'blockenberg')
209 }),
210 el(ToggleControl, {
211 label: __('Show title overlay', 'blockenberg'),
212 checked: !!a.showTitle,
213 __nextHasNoMarginBottom: true,
214 onChange: function (v) { setAttributes({ showTitle: v }); }
215 }),
216 !!a.showTitle && el(TextControl, {
217 label: __('Title', 'blockenberg'),
218 value: a.title,
219 onChange: function (v) { setAttributes({ title: v }); }
220 }),
221 el(ToggleControl, {
222 label: __('Privacy mode (youtube-nocookie.com)', 'blockenberg'),
223 checked: !!a.privacyMode,
224 __nextHasNoMarginBottom: true,
225 onChange: function (v) { setAttributes({ privacyMode: v }); }
226 }),
227 el(RangeControl, {
228 label: __('Start time (seconds)', 'blockenberg'),
229 value: a.startTime || 0,
230 onChange: function (v) { setAttributes({ startTime: v || 0 }); },
231 min: 0,
232 max: 600
233 }),
234 el(ToggleControl, {
235 label: __('Show controls', 'blockenberg'),
236 checked: !!a.controls,
237 __nextHasNoMarginBottom: true,
238 onChange: function (v) { setAttributes({ controls: v }); }
239 }),
240 el(ToggleControl, {
241 label: __('Related videos at end', 'blockenberg'),
242 checked: !!a.rel,
243 __nextHasNoMarginBottom: true,
244 onChange: function (v) { setAttributes({ rel: v }); }
245 }),
246 el(ToggleControl, {
247 label: __('Mute on play', 'blockenberg'),
248 checked: !!a.mute,
249 __nextHasNoMarginBottom: true,
250 onChange: function (v) { setAttributes({ mute: v }); }
251 }),
252 el(ToggleControl, {
253 label: __('Loop', 'blockenberg'),
254 checked: !!a.loop,
255 __nextHasNoMarginBottom: true,
256 onChange: function (v) { setAttributes({ loop: v }); }
257 })
258 ),
259
260 el(PanelBody, { title: __('Poster', 'blockenberg'), initialOpen: false },
261 el(SelectControl, {
262 label: __('Poster source', 'blockenberg'),
263 value: a.posterSource,
264 options: [
265 { label: __('YouTube (maxresdefault)', 'blockenberg'), value: 'youtube-max' },
266 { label: __('YouTube (hqdefault)', 'blockenberg'), value: 'youtube-hq' },
267 { label: __('Custom image', 'blockenberg'), value: 'custom' }
268 ],
269 onChange: function (v) { setAttributes({ posterSource: v }); }
270 }),
271 a.posterSource === 'custom' && el('div', { className: 'bkbg-ytp-media' },
272 (a.posterImage && a.posterImage.url)
273 ? el('img', { className: 'bkbg-ytp-media-thumb', src: a.posterImage.url, alt: a.posterImage.alt || '' })
274 : el('div', { className: 'bkbg-ytp-media-placeholder' }, __('No poster selected', 'blockenberg')),
275 el(Button, {
276 variant: 'secondary',
277 onClick: function () { openMedia(setPoster); }
278 }, (a.posterImage && a.posterImage.url) ? __('Replace poster', 'blockenberg') : __('Select poster', 'blockenberg')),
279 (a.posterImage && a.posterImage.url) && el(Button, {
280 variant: 'tertiary',
281 isDestructive: true,
282 onClick: function () { setAttributes({ posterImage: { id: 0, url: '', alt: '' } }); }
283 }, __('Remove', 'blockenberg'))
284 )
285 ),
286
287 el(PanelBody, { title: __('Design', 'blockenberg'), initialOpen: false },
288 el(SelectControl, {
289 label: __('Aspect ratio', 'blockenberg'),
290 value: a.aspect,
291 options: [
292 { label: '16:9', value: '16/9' },
293 { label: '4:3', value: '4/3' },
294 { label: '1:1', value: '1/1' },
295 { label: __('Auto', 'blockenberg'), value: 'auto' }
296 ],
297 onChange: function (v) { setAttributes({ aspect: v }); }
298 }),
299 el(RangeControl, {
300 label: __('Minimum height (px)', 'blockenberg'),
301 value: a.minHeight,
302 onChange: function (v) { setAttributes({ minHeight: v }); },
303 min: 120,
304 max: 900
305 }),
306 el(RangeControl, {
307 label: __('Border radius', 'blockenberg'),
308 value: a.borderRadius,
309 onChange: function (v) { setAttributes({ borderRadius: v }); },
310 min: 0,
311 max: 40
312 }),
313 el(ToggleControl, {
314 label: __('Shadow', 'blockenberg'),
315 checked: !!a.shadow,
316 __nextHasNoMarginBottom: true,
317 onChange: function (v) { setAttributes({ shadow: v }); }
318 })
319 ),
320
321 el(PanelBody, { title: __('Play Button', 'blockenberg'), initialOpen: false },
322 el(SelectControl, {
323 label: __('Style', 'blockenberg'),
324 value: a.playStyle,
325 options: [
326 { label: __('YouTube', 'blockenberg'), value: 'youtube' },
327 { label: __('Circle', 'blockenberg'), value: 'circle' },
328 { label: __('Rounded', 'blockenberg'), value: 'rounded' },
329 { label: __('Square', 'blockenberg'), value: 'square' }
330 ],
331 onChange: function (v) { setAttributes({ playStyle: v }); }
332 }),
333 el(RangeControl, {
334 label: __('Size', 'blockenberg'),
335 value: a.playSize,
336 onChange: function (v) { setAttributes({ playSize: v }); },
337 min: 36,
338 max: 120
339 }),
340 el(RangeControl, {
341 label: __('Hover scale', 'blockenberg'),
342 value: a.playHoverScale || 1.05,
343 onChange: function (v) { setAttributes({ playHoverScale: v }); },
344 min: 1,
345 max: 1.2,
346 step: 0.01
347 }),
348 el('div', { className: 'bkbg-ytp-color-row' },
349 el('button', {
350 className: 'bkbg-ytp-color-btn',
351 style: { backgroundColor: a.playBg },
352 onClick: function () { setOpenColor(openColor === 'playBg' ? null : 'playBg'); }
353 }),
354 openColor === 'playBg' && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
355 el(ColorPicker, {
356 color: a.playBg,
357 onChangeComplete: function (c) {
358 var rgba = colorToRgbaString(c);
359 setAttributes({ playBg: rgba || (c.hex ? c.hex : a.playBg) });
360 }
361 })
362 )
363 ),
364 el('div', { className: 'bkbg-ytp-color-row' },
365 el('span', {}, __('Icon', 'blockenberg')),
366 el('button', {
367 className: 'bkbg-ytp-color-btn',
368 style: { backgroundColor: a.playColor },
369 onClick: function () { setOpenColor(openColor === 'playColor' ? null : 'playColor'); }
370 }),
371 openColor === 'playColor' && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
372 el(ColorPicker, {
373 color: a.playColor,
374 onChangeComplete: function (c) { setAttributes({ playColor: c.hex }); }
375 })
376 )
377 )
378 ),
379 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
380 getTypoControl( 'Title', a.titleTypo, setAttributes, 'titleTypo' )
381 )
382 );
383
384 function renderPoster() {
385 var hasVideo = !!a.videoId;
386
387 return el('div', { className: 'bkbg-ytp-inner' },
388 el('div', {
389 className: 'bkbg-ytp-media-layer',
390 style: { minHeight: (a.minHeight || 260) + 'px' }
391 },
392 posterUrl
393 ? el('img', {
394 className: 'bkbg-ytp-poster-img',
395 src: posterUrl,
396 alt: (a.posterSource === 'custom' && a.posterImage && a.posterImage.alt) ? a.posterImage.alt : ''
397 })
398 : el('div', { className: 'bkbg-ytp-empty' },
399 el('p', {}, hasVideo
400 ? __('Poster will appear here (YouTube thumbnail or custom).', 'blockenberg')
401 : __('Paste a YouTube URL in the sidebar to get started.', 'blockenberg'))
402 )
403 ),
404 el('div', { className: 'bkbg-ytp-overlay' },
405 (a.showTitle && a.title) && el('div', { className: 'bkbg-ytp-title' }, a.title),
406 el('button', {
407 type: 'button',
408 className: 'bkbg-ytp-play',
409 disabled: !hasVideo,
410 onMouseDown: function (e) {
411 // allow selecting the block on click when not selected
412 if (!isSelected) return;
413 e.stopPropagation();
414 },
415 onClick: function (e) {
416 if (!isSelected) return;
417 e.preventDefault();
418 e.stopPropagation();
419 if (!previewOn) {
420 // No iframe preview unless enabled; keep block selectable.
421 return;
422 }
423 setIsPlaying(true);
424 },
425 'aria-label': __('Play YouTube video', 'blockenberg')
426 },
427 el('span', { className: 'bkbg-ytp-play-triangle' })
428 ),
429 (!previewOn && hasVideo) && el('div', { className: 'bkbg-ytp-hint' },
430 __('Preview is off (no iframe). Enable preview in the toolbar.', 'blockenberg')
431 )
432 )
433 );
434 }
435
436 function renderPreviewIframe() {
437 if (!a.videoId) return null;
438 if (!previewOn) return null;
439 if (!isPlaying) return null;
440
441 var src = buildEmbedSrc(a, true);
442
443 return el('div', { className: 'bkbg-ytp-inner' },
444 el('iframe', {
445 className: 'bkbg-ytp-iframe',
446 src: src,
447 title: __('YouTube video', 'blockenberg'),
448 allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share',
449 allowFullScreen: true
450 })
451 );
452 }
453
454 var aspectStyle = (a.aspect && a.aspect !== 'auto') ? { aspectRatio: a.aspect } : {};
455
456 var content = el('div', blockProps,
457 el('div', Object.assign({ className: 'bkbg-ytp-aspect' + (a.aspect !== 'auto' ? ' has-aspect' : '') }, aspectStyle),
458 renderPreviewIframe() || renderPoster()
459 )
460 );
461
462 return el(Fragment, {}, toolbar, inspector, content);
463 },
464
465 save: function (props) {
466 var a = props.attributes;
467
468 function youtubeThumbSaved(id, variant) {
469 if (!id) return '';
470 var v = variant || 'youtube-max';
471 if (v === 'youtube-hq') return 'https://i.ytimg.com/vi/' + id + '/hqdefault.jpg';
472 return 'https://i.ytimg.com/vi/' + id + '/maxresdefault.jpg';
473 }
474
475 var posterUrl = '';
476 if (a.posterSource === 'custom') {
477 posterUrl = (a.posterImage && a.posterImage.url) ? a.posterImage.url : '';
478 } else {
479 posterUrl = youtubeThumbSaved(a.videoId, a.posterSource);
480 }
481
482 var cls = 'bkbg-ytp-wrap';
483 if (a.shadow) cls += ' has-shadow';
484 cls += ' is-play-' + (a.playStyle || 'circle');
485
486 var styleVars = (function () {
487 var s = {
488 '--bkbg-ytp-radius': (a.borderRadius || 0) + 'px',
489 '--bkbg-ytp-min-height': (a.minHeight || 260) + 'px',
490 '--bkbg-ytp-play-size': (a.playSize || 64) + 'px',
491 '--bkbg-ytp-play-bg': a.playBg,
492 '--bkbg-ytp-play-color': a.playColor,
493 '--bkbg-ytp-play-hover': (a.playHoverScale || 1.05)
494 };
495 Object.assign(s, getTypoCssVarsObj(a));
496 return s;
497 })();
498
499 var blockProps = useBlockProps.save({
500 className: cls,
501 style: styleVars,
502 'data-video-id': a.videoId || '',
503 'data-privacy': a.privacyMode ? '1' : '0',
504 'data-start': String(a.startTime || 0),
505 'data-controls': a.controls ? '1' : '0',
506 'data-rel': a.rel ? '1' : '0',
507 'data-mute': a.mute ? '1' : '0',
508 'data-loop': a.loop ? '1' : '0',
509 'data-poster-source': a.posterSource || 'youtube-max'
510 });
511
512 var aspectStyle = (a.aspect && a.aspect !== 'auto') ? { aspectRatio: a.aspect } : {};
513
514 return el('div', blockProps,
515 el('div', Object.assign({ className: 'bkbg-ytp-aspect' + (a.aspect !== 'auto' ? ' has-aspect' : '') }, aspectStyle),
516 el('div', { className: 'bkbg-ytp-inner' },
517 el('div', { className: 'bkbg-ytp-media-layer' },
518 posterUrl
519 ? el('img', {
520 className: 'bkbg-ytp-poster-img',
521 src: posterUrl,
522 alt: (a.posterSource === 'custom' && a.posterImage && a.posterImage.alt) ? a.posterImage.alt : ''
523 })
524 : null
525 ),
526 el('button', {
527 type: 'button',
528 className: 'bkbg-ytp-play',
529 'aria-label': __('Play YouTube video', 'blockenberg')
530 },
531 el('span', { className: 'bkbg-ytp-play-triangle' })
532 ),
533 (a.showTitle && a.title) && el('div', { className: 'bkbg-ytp-title' }, a.title),
534 el('div', { className: 'bkbg-ytp-embed' })
535 )
536 )
537 );
538 }
539 });
540 }() );
541