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

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

292 lines 17.7 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 __ = wp.i18n.__;
4 var useState = wp.element.useState;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var MediaUpload = wp.blockEditor.MediaUpload;
8 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var RichText = wp.blockEditor.RichText;
11 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
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 TextareaControl = wp.components.TextareaControl;
19 var Fragment = wp.element.Fragment;
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, 'playlistTitleTypo', attrs, '--bkvpl-pt-');
28 _tvf(v, 'epTitleTypo', attrs, '--bkvpl-et-');
29 _tvf(v, 'epMetaTypo', attrs, '--bkvpl-em-');
30 return v;
31 }
32
33 function EmbedUrl(videoType, videoId, videoUrl) {
34 if (videoType === 'youtube' && videoId) return 'https://www.youtube.com/embed/' + videoId + '?rel=0';
35 if (videoType === 'vimeo' && videoId) return 'https://player.vimeo.com/video/' + videoId;
36 return videoUrl || '';
37 }
38
39 registerBlockType('blockenberg/video-playlist', {
40 edit: function (props) {
41 var attr = props.attributes;
42 var set = props.setAttributes;
43 var episodes = attr.episodes || [];
44
45 var activeIdx = useState(0);
46 var active = activeIdx[0];
47 var setActive = activeIdx[1];
48 var activeEp = episodes[active] || episodes[0] || {};
49
50 function updateEp(idx, field, val) {
51 var updated = episodes.map(function (ep, i) {
52 if (i !== idx) return ep;
53 var p = {}; p[field] = val;
54 return Object.assign({}, ep, p);
55 });
56 set({ episodes: updated });
57 }
58
59 function addEpisode() {
60 set({ episodes: episodes.concat([{ title: 'New Episode', description: '', videoType: 'youtube', videoId: '', videoUrl: '', duration: '', thumbnailUrl: '', thumbnailId: 0, thumbnailAlt: '' }]) });
61 }
62
63 function removeEp(idx) { set({ episodes: episodes.filter(function (_, i) { return i !== idx; }) }); }
64
65 /* Inspector */
66 var inspector = el(InspectorControls, {},
67 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
68 el(SelectControl, {
69 label: __('Orientation', 'blockenberg'),
70 value: attr.layout,
71 options: [
72 { label: 'Side by Side', value: 'side' },
73 { label: 'Stacked (episodes below)', value: 'stack' }
74 ],
75 onChange: function (v) { set({ layout: v }); },
76 __nextHasNoMarginBottom: true
77 }),
78 attr.layout === 'side' && el(SelectControl, {
79 label: __('Episode List Position', 'blockenberg'),
80 value: attr.listPosition,
81 options: [
82 { label: 'Right', value: 'right' },
83 { label: 'Left', value: 'left' }
84 ],
85 onChange: function (v) { set({ listPosition: v }); },
86 __nextHasNoMarginBottom: true
87 }),
88 attr.layout === 'side' && el(RangeControl, {
89 label: __('Episode List Width (px)', 'blockenberg'),
90 value: attr.listWidth, min: 240, max: 500, step: 10,
91 onChange: function (v) { set({ listWidth: v }); },
92 __nextHasNoMarginBottom: true
93 }),
94 el(RangeControl, {
95 label: __('Border Radius (px)', 'blockenberg'),
96 value: attr.borderRadius, min: 0, max: 24,
97 onChange: function (v) { set({ borderRadius: v }); },
98 __nextHasNoMarginBottom: true
99 }),
100 el(RangeControl, {
101 label: __('Padding Top (px)', 'blockenberg'),
102 value: attr.paddingTop, min: 0, max: 200,
103 onChange: function (v) { set({ paddingTop: v }); },
104 __nextHasNoMarginBottom: true
105 }),
106 el(RangeControl, {
107 label: __('Padding Bottom (px)', 'blockenberg'),
108 value: attr.paddingBottom, min: 0, max: 200,
109 onChange: function (v) { set({ paddingBottom: v }); },
110 __nextHasNoMarginBottom: true
111 })
112 ),
113 el(PanelBody, { title: __('Visibility', 'blockenberg'), initialOpen: false },
114 el(ToggleControl, { label: __('Show Playlist Title', 'blockenberg'), checked: attr.showPlaylistTitle, onChange: function (v) { set({ showPlaylistTitle: v }); }, __nextHasNoMarginBottom: true }),
115 el(ToggleControl, { label: __('Show Episode Numbers', 'blockenberg'), checked: attr.showNumbers, onChange: function (v) { set({ showNumbers: v }); }, __nextHasNoMarginBottom: true }),
116 el(ToggleControl, { label: __('Show Duration', 'blockenberg'), checked: attr.showDuration, onChange: function (v) { set({ showDuration: v }); }, __nextHasNoMarginBottom: true }),
117 el(ToggleControl, { label: __('Show Description under player', 'blockenberg'), checked: attr.showDescription, onChange: function (v) { set({ showDescription: v }); }, __nextHasNoMarginBottom: true }),
118 el(ToggleControl, { label: __('Show Thumbnails', 'blockenberg'), checked: attr.showThumbnails, onChange: function (v) { set({ showThumbnails: v }); }, __nextHasNoMarginBottom: true })
119 ),
120 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
121 getTypoControl(__('Playlist Title', 'blockenberg'), 'playlistTitleTypo', attr, set),
122 getTypoControl(__('Episode Title', 'blockenberg'), 'epTitleTypo', attr, set),
123 getTypoControl(__('Episode Meta', 'blockenberg'), 'epMetaTypo', attr, set)
124 ),
125 el(PanelColorSettings, {
126 title: __('Colors', 'blockenberg'), initialOpen: false,
127 colorSettings: [
128 { value: attr.bgColor, onChange: function (v) { set({ bgColor: v || '#0f0f1a' }); }, label: __('Block Background', 'blockenberg') },
129 { value: attr.playerBg, onChange: function (v) { set({ playerBg: v || '#000000' }); }, label: __('Player Background', 'blockenberg') },
130 { value: attr.listBg, onChange: function (v) { set({ listBg: v || '#1a1a2e' }); }, label: __('List Background', 'blockenberg') },
131 { value: attr.activeBg, onChange: function (v) { set({ activeBg: v || '#2d2b55' }); }, label: __('Active Episode BG', 'blockenberg') },
132 { value: attr.titleColor, onChange: function (v) { set({ titleColor: v || '#ffffff' }); }, label: __('Playlist Title', 'blockenberg') },
133 { value: attr.epTitleColor, onChange: function (v) { set({ epTitleColor: v || '#f0f0f0' }); }, label: __('Episode Title', 'blockenberg') },
134 { value: attr.epMetaColor, onChange: function (v) { set({ epMetaColor: v || '#718096' }); }, label: __('Episode Meta', 'blockenberg') },
135 { value: attr.activeColor, onChange: function (v) { set({ activeColor: v || '#6366f1' }); }, label: __('Active Accent', 'blockenberg') },
136 { value: attr.accentColor, onChange: function (v) { set({ accentColor: v || '#6366f1' }); }, label: __('Accent', 'blockenberg') }
137 ]
138 })
139 );
140
141 /* Episode config panels */
142 var epPanels = el(PanelBody.displayName ? PanelBody : PanelBody, { title: __('Episodes', 'blockenberg'), initialOpen: false, style: {}, className: '' },
143 /* render inside InspectorControls */
144 );
145
146 var epInspector = el(InspectorControls, {},
147 el(PanelBody, { title: __('Episodes', 'blockenberg'), initialOpen: true },
148 episodes.map(function (ep, idx) {
149 return el('div', { key: idx, style: { borderBottom: '1px solid #e5e7eb', marginBottom: '12px', paddingBottom: '12px' } },
150 el('strong', { style: { display: 'block', marginBottom: '6px', color: idx === active ? attr.activeColor : 'inherit' } }, (idx + 1) + '. ' + (ep.title || 'Episode')),
151 el(SelectControl, {
152 label: __('Type', 'blockenberg'),
153 value: ep.videoType,
154 options: [
155 { label: 'YouTube', value: 'youtube' },
156 { label: 'Vimeo', value: 'vimeo' },
157 { label: 'Direct URL (mp4)', value: 'file' }
158 ],
159 onChange: function (v) { updateEp(idx, 'videoType', v); },
160 __nextHasNoMarginBottom: true
161 }),
162 ep.videoType !== 'file' ? el(TextControl, {
163 label: __('Video ID', 'blockenberg'),
164 value: ep.videoId,
165 onChange: function (v) { updateEp(idx, 'videoId', v); },
166 placeholder: ep.videoType === 'youtube' ? 'dQw4w9WgXcQ' : '123456789',
167 __nextHasNoMarginBottom: true
168 }) : el(TextControl, {
169 label: __('Video URL', 'blockenberg'),
170 value: ep.videoUrl,
171 onChange: function (v) { updateEp(idx, 'videoUrl', v); },
172 placeholder: 'https://example.com/video.mp4',
173 __nextHasNoMarginBottom: true
174 }),
175 el(TextControl, {
176 label: __('Duration', 'blockenberg'),
177 value: ep.duration,
178 onChange: function (v) { updateEp(idx, 'duration', v); },
179 placeholder: '10:30',
180 __nextHasNoMarginBottom: true
181 }),
182 el(Button, {
183 onClick: function () { removeEp(idx); },
184 variant: 'link', isDestructive: true, isSmall: true,
185 style: { marginTop: '4px' }
186 }, __('Remove Episode', 'blockenberg'))
187 );
188 }),
189 el(Button, { onClick: addEpisode, variant: 'primary', isSmall: true }, __('+ Add Episode', 'blockenberg'))
190 )
191 );
192
193 /* live preview */
194 var isSide = attr.layout === 'side';
195 var isListLeft = attr.listPosition === 'left';
196 var embedSrc = EmbedUrl(activeEp.videoType, activeEp.videoId, activeEp.videoUrl);
197
198 var playerPane = el('div', { className: 'bkbg-vp-player-pane', style: { flex: 1, background: attr.playerBg, borderRadius: attr.borderRadius + 'px', overflow: 'hidden', minWidth: 0 } },
199 el('div', { className: 'bkbg-vp-player-ratio', style: { position: 'relative', paddingTop: '56.25%', background: '#000' } },
200 embedSrc
201 ? el('iframe', { src: embedSrc, style: { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', border: 'none' }, allowFullScreen: true })
202 : el('div', { style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#666' } },
203 el('span', {}, '' + __('No video ID set', 'blockenberg'))
204 )
205 ),
206 el('div', { style: { padding: '16px 20px' } },
207 el('div', { className: 'bkbg-vp-now-title', style: { color: attr.titleColor, marginBottom: '4px' } }, activeEp.title || ''),
208 attr.showDescription && activeEp.description && el('div', { className: 'bkbg-vp-now-desc', style: { color: attr.metaColor } }, activeEp.description)
209 )
210 );
211
212 var listPane = el('div', { className: 'bkbg-vp-list-pane', style: { width: isSide ? attr.listWidth + 'px' : '100%', background: attr.listBg, borderRadius: attr.borderRadius + 'px', overflow: 'hidden', flexShrink: 0 } },
213 attr.showPlaylistTitle && el('div', { className: 'bkbg-vp-list-header', style: { padding: '14px 16px', borderBottom: '1px solid rgba(255,255,255,0.08)', color: attr.titleColor } }, attr.playlistTitle),
214 el('div', { style: { overflowY: 'auto', maxHeight: isSide ? '480px' : '220px' } },
215 episodes.map(function (ep, idx) {
216 var isAct = idx === active;
217 return el('div', {
218 key: idx,
219 onClick: function () { setActive(idx); },
220 style: {
221 display: 'flex', gap: '10px', padding: '10px 14px', cursor: 'pointer',
222 background: isAct ? attr.activeBg : 'transparent',
223 borderLeft: isAct ? ('3px solid ' + attr.activeColor) : '3px solid transparent',
224 alignItems: 'flex-start'
225 }
226 },
227 attr.showNumbers && el('div', { style: { width: '22px', height: '22px', borderRadius: '50%', background: isAct ? attr.activeColor : 'rgba(255,255,255,0.1)', color: isAct ? '#fff' : attr.epMetaColor, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '11px', fontWeight: '700', flexShrink: 0, marginTop: '2px' } }, idx + 1),
228 el('div', { style: { flex: 1, minWidth: 0 } },
229 el('div', { className: 'bkbg-vp-ep-title', style: { color: attr.epTitleColor, marginBottom: '2px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, ep.title || ''),
230 el('div', { className: 'bkbg-vp-ep-meta', style: { color: attr.epMetaColor } }, ep.duration || '')
231 )
232 );
233 })
234 )
235 );
236
237 var blockStyle = {
238 background: attr.bgColor,
239 paddingTop: attr.paddingTop + 'px',
240 paddingBottom: attr.paddingBottom + 'px',
241 borderRadius: attr.borderRadius + 'px'
242 };
243
244 var layoutChildren = isSide
245 ? (isListLeft ? [listPane, playerPane] : [playerPane, listPane])
246 : [playerPane, listPane];
247
248 return el(Fragment, {},
249 inspector,
250 epInspector,
251 el('div', useBlockProps((function () {
252 var s = getTypoCssVars(attr);
253 return { style: s };
254 })()),
255 el('div', { className: 'bkbg-vp-editor-wrap', style: blockStyle },
256 el('div', {
257 style: {
258 display: 'flex',
259 flexDirection: isSide ? 'row' : 'column',
260 gap: '2px',
261 overflow: 'hidden',
262 borderRadius: attr.borderRadius + 'px'
263 }
264 }, layoutChildren),
265 el('div', { style: { padding: '12px 16px', display: 'flex', gap: '8px', flexWrap: 'wrap' } },
266 episodes.map(function (ep, idx) {
267 return el(Button, {
268 key: idx,
269 onClick: function () { setActive(idx); },
270 variant: idx === active ? 'primary' : 'secondary',
271 isSmall: true
272 }, (idx + 1) + '. ' + (ep.title || __('Episode', 'blockenberg')));
273 }),
274 el(Button, { onClick: addEpisode, variant: 'link', isSmall: true }, '+ ' + __('Add', 'blockenberg'))
275 )
276 )
277 )
278 );
279 },
280
281 save: function (props) {
282 var a = props.attributes;
283 return el('div', useBlockProps.save((function () {
284 var s = getTypoCssVars(a);
285 return { style: s };
286 })()),
287 el('div', { className: 'bkbg-vp-app', 'data-opts': JSON.stringify(a) })
288 );
289 }
290 });
291 }() );
292