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-popup / frontend.js

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

258 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 // ── Parse video URL → {type, src, ...} ─────────────────────────────────────
5 function parseVideoUrl(url, autoplay, muted) {
6 if (!url || !url.trim()) return null;
7 url = url.trim();
8
9 // YouTube: watch, embed, shorts, youtu.be
10 var ytMatch = url.match(
11 /(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
12 );
13 if (ytMatch) {
14 var ytId = ytMatch[1];
15 var ytQ = 'autoplay=' + (autoplay ? '1' : '0')
16 + '&mute=' + (muted ? '1' : '0')
17 + '&rel=0&modestbranding=1&playsinline=1&enablejsapi=1';
18 return { type: 'iframe', src: 'https://www.youtube.com/embed/' + ytId + '?' + ytQ };
19 }
20
21 // Vimeo: vimeo.com/ID or player.vimeo.com/video/ID
22 var vmMatch = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
23 if (vmMatch) {
24 var vmId = vmMatch[1];
25 var vmQ = 'autoplay=' + (autoplay ? '1' : '0')
26 + '&muted=' + (muted ? '1' : '0')
27 + '&byline=0&portrait=0&title=0';
28 return { type: 'iframe', src: 'https://player.vimeo.com/video/' + vmId + '?' + vmQ };
29 }
30
31 // Direct video file
32 if (/\.(mp4|webm|ogv|ogg)(\?|#|$)/i.test(url)) {
33 return { type: 'video', src: url, autoplay: autoplay, muted: muted };
34 }
35
36 // Fallback — use as-is inside an iframe
37 return { type: 'iframe', src: url };
38 }
39
40 // ── Close icon SVG ─────────────────────────────────────────────────────────
41 function closeSvg(color) {
42 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"'
43 + ' fill="none" stroke="' + color + '" stroke-width="2.2" stroke-linecap="round">'
44 + '<line x1="18" y1="6" x2="6" y2="18"/>'
45 + '<line x1="6" y1="6" x2="18" y2="18"/></svg>';
46 }
47
48 // ── Open popup modal ───────────────────────────────────────────────────────
49 function openModal(opts, triggerBtn) {
50 var video = parseVideoUrl(opts.url, opts.autoplay, opts.muted);
51 if (!video) return;
52
53 var ratioMap = { '16-9': '56.25%', '4-3': '75%', '1-1': '100%', '21-9': '42.86%' };
54 var padPct = ratioMap[opts.aspectRatio] || '56.25%';
55 var blur = parseInt(opts.blur, 10) || 0;
56
57 // ── Overlay ────────────────────────────────────────────────────────────
58 var overlay = document.createElement('div');
59 overlay.className = 'bkbg-vp-modal-overlay';
60 overlay.setAttribute('role', 'dialog');
61 overlay.setAttribute('aria-modal', 'true');
62 overlay.setAttribute('aria-label', 'Video popup');
63 overlay.style.backgroundColor = opts.overlayColor;
64 if (blur > 0) {
65 overlay.style.backdropFilter = 'blur(' + blur + 'px)';
66 overlay.style.webkitBackdropFilter = 'blur(' + blur + 'px)';
67 }
68
69 // ── Content card ───────────────────────────────────────────────────────
70 var content = document.createElement('div');
71 content.className = 'bkbg-vp-modal-content';
72 content.style.maxWidth = opts.maxWidth + 'px';
73 content.style.borderRadius = opts.radius + 'px';
74
75 // ── Close button ───────────────────────────────────────────────────────
76 var closeBtn = document.createElement('button');
77 closeBtn.className = 'bkbg-vp-modal-close';
78 closeBtn.type = 'button';
79 closeBtn.setAttribute('aria-label', 'Close video');
80 closeBtn.style.color = opts.closeColor;
81 closeBtn.innerHTML = closeSvg(opts.closeColor);
82
83 // ── Iframe / video wrapper ─────────────────────────────────────────────
84 var iWrap = document.createElement('div');
85 iWrap.className = 'bkbg-vp-modal-iframe-wrap';
86 iWrap.style.paddingBottom = padPct;
87
88 // ── Loading spinner ────────────────────────────────────────────────────
89 var loader = document.createElement('div');
90 loader.className = 'bkbg-vp-modal-loader';
91
92 // ── Media element ──────────────────────────────────────────────────────
93 var mediaEl;
94 if (video.type === 'video') {
95 mediaEl = document.createElement('video');
96 mediaEl.src = video.src;
97 mediaEl.controls = true;
98 mediaEl.autoplay = video.autoplay;
99 mediaEl.muted = video.muted;
100 mediaEl.playsInline = true;
101 mediaEl.tabIndex = 0;
102 mediaEl.addEventListener('canplay', function () {
103 loader.classList.add('is-hidden');
104 });
105 } else {
106 mediaEl = document.createElement('iframe');
107 mediaEl.src = video.src;
108 mediaEl.setAttribute('allowfullscreen', 'true');
109 mediaEl.setAttribute('allow', 'autoplay; fullscreen; picture-in-picture; clipboard-write');
110 mediaEl.setAttribute('title', 'Video');
111 mediaEl.tabIndex = 0;
112 mediaEl.style.border = 'none';
113 mediaEl.addEventListener('load', function () {
114 loader.classList.add('is-hidden');
115 });
116 }
117
118 // ── Assemble DOM ───────────────────────────────────────────────────────
119 iWrap.appendChild(loader);
120 iWrap.appendChild(mediaEl);
121 content.appendChild(closeBtn);
122 content.appendChild(iWrap);
123 overlay.appendChild(content);
124 document.body.appendChild(overlay);
125 document.body.style.overflow = 'hidden';
126
127 // ── Animate in (double RAF ensures transition fires) ───────────────────
128 requestAnimationFrame(function () {
129 requestAnimationFrame(function () {
130 overlay.classList.add('is-open');
131 closeBtn.focus();
132 });
133 });
134
135 // ── Teardown ───────────────────────────────────────────────────────────
136 function close() {
137 overlay.classList.remove('is-open');
138 document.removeEventListener('keydown', onKeyDown);
139
140 // Stop playback immediately by clearing src
141 if (mediaEl.tagName.toLowerCase() === 'iframe') {
142 mediaEl.src = '';
143 } else {
144 try { mediaEl.pause(); } catch (e) { /* noop */ }
145 mediaEl.removeAttribute('src');
146 mediaEl.load();
147 }
148
149 setTimeout(function () {
150 if (overlay.parentNode) {
151 overlay.parentNode.removeChild(overlay);
152 }
153 document.body.style.overflow = '';
154 if (triggerBtn) {
155 triggerBtn.focus();
156 }
157 }, 320); // matches CSS transition duration
158 }
159
160 // Close on close button
161 closeBtn.addEventListener('click', function (e) {
162 e.stopPropagation();
163 close();
164 });
165
166 // Close on overlay click (if enabled)
167 if (opts.closeOverlay === '1') {
168 overlay.addEventListener('click', function (e) {
169 if (e.target === overlay) {
170 close();
171 }
172 });
173 }
174
175 // ESC key + basic focus trap
176 function onKeyDown(e) {
177 if (e.key === 'Escape') {
178 e.preventDefault();
179 close();
180 return;
181 }
182 // Tab cycles focus between close button and iframe/video
183 if (e.key === 'Tab') {
184 e.preventDefault();
185 if (document.activeElement === closeBtn) {
186 mediaEl.focus();
187 } else {
188 closeBtn.focus();
189 }
190 }
191 }
192
193 document.addEventListener('keydown', onKeyDown);
194 }
195
196 // ── Initialise all blocks on page ──────────────────────────────────────────
197 function init() {
198 var blocks = document.querySelectorAll('.bkbg-vp-outer[data-vp-url]');
199
200 blocks.forEach(function (block) {
201 var btn = block.querySelector('.bkbg-vp-btn');
202 if (!btn) return;
203
204 var opts = {
205 url: block.dataset.vpUrl || '',
206 autoplay: block.dataset.vpAutoplay !== '0',
207 muted: block.dataset.vpMuted === '1',
208 maxWidth: parseInt(block.dataset.vpMaxWidth, 10) || 900,
209 radius: parseInt(block.dataset.vpRadius, 10) || 12,
210 aspectRatio: block.dataset.vpRatio || '16-9',
211 overlayColor: block.dataset.vpOverlay || 'rgba(0,0,0,0.88)',
212 blur: block.dataset.vpBlur || '0',
213 closeOverlay: block.dataset.vpCloseOverlay || '1',
214 closeColor: block.dataset.vpCloseColor || '#ffffff'
215 };
216
217 // Skip blocks with no URL set
218 if (!opts.url) {
219 btn.setAttribute('aria-disabled', 'true');
220 btn.style.opacity = '0.5';
221 btn.style.cursor = 'not-allowed';
222 return;
223 }
224
225 // ── JS hover color swap ─────────────────────────────────────────────
226 var hoverBg = btn.dataset.vpBgHover;
227 var hoverTxt = btn.dataset.vpTextHover;
228
229 if (hoverBg || hoverTxt) {
230 var cachedBg = btn.style.background;
231 var cachedColor = btn.style.color;
232
233 btn.addEventListener('mouseenter', function () {
234 if (hoverBg) btn.style.background = hoverBg;
235 if (hoverTxt) btn.style.color = hoverTxt;
236 });
237 btn.addEventListener('mouseleave', function () {
238 btn.style.background = cachedBg;
239 btn.style.color = cachedColor;
240 });
241 }
242
243 // ── Click → open modal ──────────────────────────────────────────────
244 btn.addEventListener('click', function () {
245 openModal(opts, btn);
246 });
247 });
248 }
249
250 // ── Boot ───────────────────────────────────────────────────────────────────
251 if (document.readyState === 'loading') {
252 document.addEventListener('DOMContentLoaded', init);
253 } else {
254 init();
255 }
256
257 }());
258