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

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

356 lines 20.8 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 ColorPicker = wp.components.ColorPicker;
18 var Popover = wp.components.Popover;
19
20 var _TypographyControl, _typoCssVars;
21 function getTypographyControl() { return _TypographyControl || (_TypographyControl = window.bkbgTypographyControl); }
22 function getTypoCssVars() { return _typoCssVars || (_typoCssVars = window.bkbgTypoCssVars); }
23
24 /* ── CSS vars ─────────────────────────────────────────────────────────── */
25 function buildWrapStyle(a) {
26 var style = {
27 '--bkbg-ig-cols' : a.columns,
28 '--bkbg-ig-cols-tab' : a.columnsTablet,
29 '--bkbg-ig-cols-mob' : a.columnsMobile,
30 '--bkbg-ig-gap' : a.gap + 'px',
31 '--bkbg-ig-radius' : a.borderRadius + 'px',
32 '--bkbg-ig-hover-bg' : a.hoverOverlayBg,
33 '--bkbg-ig-cap-bg' : a.captionBg,
34 '--bkbg-ig-cap-color' : a.captionColor,
35 '--bkbg-ig-icon-color': a.overlayIconColor,
36 '--bkbg-ig-filter-active': a.filterActiveColor
37 };
38 if (a.bgColor) style['--bkbg-ig-bg'] = a.bgColor;
39 if (a.filterBgColor) style['--bkbg-ig-filter-bg'] = a.filterBgColor;
40 if (a.filterTextColor) style['--bkbg-ig-filter-text'] = a.filterTextColor;
41 if (a.filterBorderRadius) style['--bkbg-ig-filter-radius'] = a.filterBorderRadius + 'px';
42 var fn = getTypoCssVars();
43 if (fn) { Object.assign(style, fn(a.captionTypo, '--bkbg-ig-cp-'), fn(a.filterTypo, '--bkbg-ig-ft-')); }
44 return style;
45 }
46
47 /* ── Aspect ratio map ─────────────────────────────────────────────────── */
48 var RATIOS = {
49 square: '1 / 1',
50 landscape: '4 / 3',
51 wide: '16 / 9',
52 portrait: '3 / 4',
53 tall: '2 / 3',
54 auto: 'auto'
55 };
56
57 /* ── Color picker helper ──────────────────────────────────────────────── */
58 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
59 var isOpen = openKey === key;
60 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
61 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
62 el('div', { style: { position: 'relative', flexShrink: 0 } },
63 el('button', {
64 type: 'button', title: value || 'default',
65 onClick: function () { setOpenKey(isOpen ? null : key); },
66 style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#cccccc' }
67 }),
68 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
69 el('div', { style: { padding: '8px' } },
70 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
71 )
72 )
73 )
74 );
75 }
76
77 /* ── Render one gallery item in editor ────────────────────────────────── */
78 function renderItem(img, a, onRemove, onCaptionChange) {
79 var ar = RATIOS[a.aspectRatio] || '1 / 1';
80 var autoAr = a.aspectRatio === 'auto';
81 return el('div', {
82 key: img.id || img.url,
83 className: 'bkbg-ig-item',
84 style: { position: 'relative', borderRadius: 'var(--bkbg-ig-radius, 8px)', overflow: 'hidden', aspectRatio: autoAr ? undefined : ar }
85 },
86 el('img', {
87 src: img.url,
88 alt: img.alt || '',
89 style: { width: '100%', height: autoAr ? 'auto' : '100%', objectFit: 'cover', display: 'block' }
90 }),
91 a.showCaptions && el('div', {
92 className: 'bkbg-ig-caption',
93 style: { position: 'absolute', bottom: 0, left: 0, right: 0, padding: '6px 8px', background: 'rgba(0,0,0,0.5)', color: '#fff' }
94 }, img.caption || ''),
95 el('button', {
96 type: 'button',
97 onClick: function () { onRemove(img); },
98 style: { position: 'absolute', top: '4px', right: '4px', background: 'rgba(0,0,0,0.65)', color: '#fff', border: 'none', borderRadius: '50%', width: '22px', height: '22px', cursor: 'pointer', fontSize: '14px', lineHeight: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }
99 }, '×')
100 );
101 }
102
103 /* ── Register ─────────────────────────────────────────────────────────── */
104 registerBlockType('blockenberg/image-gallery', {
105 title: __('Image Gallery', 'blockenberg'),
106 icon: 'format-gallery',
107 category: 'bkbg-media',
108 description: __('Responsive gallery with grid/masonry layouts and lightbox.', 'blockenberg'),
109
110 edit: function (props) {
111 var a = props.attributes;
112 var setAttributes = props.setAttributes;
113
114 var openColorKeyState = useState(null);
115 var openColorKey = openColorKeyState[0];
116 var setOpenColorKey = openColorKeyState[1];
117
118 function cc(key, label, attrKey) {
119 return renderColorControl(key, label, a[attrKey], function (val) {
120 var upd = {}; upd[attrKey] = val; setAttributes(upd);
121 }, openColorKey, setOpenColorKey);
122 }
123
124 function removeImage(img) {
125 setAttributes({ images: a.images.filter(function (i) { return i.url !== img.url; }) });
126 }
127
128 var blockProps = useBlockProps({
129 className: 'bkbg-ig-wrapper bkbg-ig-layout-' + a.layout,
130 style: buildWrapStyle(a)
131 });
132
133 /* ── Inspector ──────────────────────────────────────────────── */
134 var inspector = el(InspectorControls, {},
135 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
136 el(SelectControl, {
137 label: __('Layout', 'blockenberg'), value: a.layout,
138 options: [
139 { label: __('Grid (equal)', 'blockenberg'), value: 'grid' },
140 { label: __('Masonry', 'blockenberg'), value: 'masonry' },
141 { label: __('Justified (rows)', 'blockenberg'), value: 'justified' }
142 ],
143 onChange: function (v) { setAttributes({ layout: v }); }
144 }),
145 el(RangeControl, {
146 label: __('Columns (Desktop)', 'blockenberg'), value: a.columns, min: 1, max: 8,
147 onChange: function (v) { setAttributes({ columns: v }); }
148 }),
149 el(RangeControl, {
150 label: __('Columns (Tablet)', 'blockenberg'), value: a.columnsTablet, min: 1, max: 5,
151 onChange: function (v) { setAttributes({ columnsTablet: v }); }
152 }),
153 el(RangeControl, {
154 label: __('Columns (Mobile)', 'blockenberg'), value: a.columnsMobile, min: 1, max: 3,
155 onChange: function (v) { setAttributes({ columnsMobile: v }); }
156 }),
157 el(RangeControl, {
158 label: __('Gap (px)', 'blockenberg'), value: a.gap, min: 0, max: 60,
159 onChange: function (v) { setAttributes({ gap: v }); }
160 }),
161 el(SelectControl, {
162 label: __('Aspect Ratio', 'blockenberg'), value: a.aspectRatio,
163 options: [
164 { label: __('Square (1:1)', 'blockenberg'), value: 'square' },
165 { label: __('Landscape (4:3)', 'blockenberg'), value: 'landscape' },
166 { label: __('Wide (16:9)', 'blockenberg'), value: 'wide' },
167 { label: __('Portrait (3:4)', 'blockenberg'), value: 'portrait' },
168 { label: __('Tall (2:3)', 'blockenberg'), value: 'tall' },
169 { label: __('Auto (original)', 'blockenberg'), value: 'auto' }
170 ],
171 onChange: function (v) { setAttributes({ aspectRatio: v }); }
172 }),
173 el(RangeControl, {
174 label: __('Border Radius (px)', 'blockenberg'), value: a.borderRadius, min: 0, max: 30,
175 onChange: function (v) { setAttributes({ borderRadius: v }); }
176 })
177 ),
178
179 el(PanelBody, { title: __('Hover & Captions', 'blockenberg'), initialOpen: false },
180 el(SelectControl, {
181 label: __('Hover Effect', 'blockenberg'), value: a.hoverEffect,
182 options: [
183 { label: __('Zoom', 'blockenberg'), value: 'zoom' },
184 { label: __('Fade Overlay', 'blockenberg'), value: 'fade' },
185 { label: __('Slide Up Caption', 'blockenberg'), value: 'caption-slide' },
186 { label: __('Grayscale', 'blockenberg'), value: 'grayscale' },
187 { label: __('None', 'blockenberg'), value: 'none' }
188 ],
189 onChange: function (v) { setAttributes({ hoverEffect: v }); }
190 }),
191 cc('hoverOverlayBg', __('Hover Overlay Color', 'blockenberg'), 'hoverOverlayBg'),
192 cc('overlayIconColor', __('Overlay Icon Color', 'blockenberg'), 'overlayIconColor'),
193 el(ToggleControl, {
194 label: __('Show Captions', 'blockenberg'), checked: a.showCaptions,
195 onChange: function (v) { setAttributes({ showCaptions: v }); }
196 }),
197 a.showCaptions && el(SelectControl, {
198 label: __('Caption Style', 'blockenberg'), value: a.captionStyle,
199 options: [
200 { label: __('Overlay (always visible)', 'blockenberg'), value: 'overlay' },
201 { label: __('Hover reveal', 'blockenberg'), value: 'hover' }
202 ],
203 onChange: function (v) { setAttributes({ captionStyle: v }); }
204 }),
205 a.showCaptions && cc('captionBg', __('Caption Background', 'blockenberg'), 'captionBg'),
206 a.showCaptions && cc('captionColor', __('Caption Text Color', 'blockenberg'), 'captionColor')
207 ),
208
209 el(PanelBody, { title: __('Lightbox', 'blockenberg'), initialOpen: false },
210 el(ToggleControl, {
211 label: __('Enable Lightbox', 'blockenberg'), checked: a.lightbox,
212 onChange: function (v) { setAttributes({ lightbox: v }); }
213 })
214 ),
215
216 el(PanelBody, { title: __('Filter Bar', 'blockenberg'), initialOpen: false },
217 el(ToggleControl, {
218 label: __('Enable Category Filter', 'blockenberg'), checked: a.filterEnabled,
219 help: __('Add a "category" field to image metadata for filtering.', 'blockenberg'),
220 onChange: function (v) { setAttributes({ filterEnabled: v }); }
221 }),
222 a.filterEnabled && el(Fragment, {},
223 cc('filterActiveColor', __('Active Filter Color', 'blockenberg'), 'filterActiveColor'),
224 cc('filterBgColor', __('Filter Bar Background', 'blockenberg'), 'filterBgColor'),
225 cc('filterTextColor', __('Filter Text Color', 'blockenberg'), 'filterTextColor'),
226 el(RangeControl, {
227 label: __('Filter Button Radius', 'blockenberg'), value: a.filterBorderRadius, min: 0, max: 30,
228 onChange: function (v) { setAttributes({ filterBorderRadius: v }); }
229 })
230 )
231 ),
232 el(PanelBody, { title: 'Typography', initialOpen: false },
233 getTypographyControl() && el(getTypographyControl(), { label: __('Caption', 'blockenberg'), value: a.captionTypo, onChange: function (v) { setAttributes({ captionTypo: v }); } }),
234 getTypographyControl() && el(getTypographyControl(), { label: __('Filter Button', 'blockenberg'), value: a.filterTypo, onChange: function (v) { setAttributes({ filterTypo: v }); } })
235 ),
236
237 );
238
239 /* ── Editor render ────────────────────────────────────────────── */
240 return el(Fragment, {},
241 inspector,
242 el('div', blockProps,
243 // Add images toolbar
244 el('div', { style: { marginBottom: '12px', display: 'flex', gap: '8px', flexWrap: 'wrap', alignItems: 'center' } },
245 el(MediaUploadCheck, {},
246 el(MediaUpload, {
247 onSelect: function (medias) {
248 // MediaUpload multiple
249 var arr = Array.isArray(medias) ? medias : [medias];
250 var existing = a.images || [];
251 var existingUrls = existing.map(function (i) { return i.url; });
252 var toAdd = arr.filter(function (m) { return existingUrls.indexOf(m.url) === -1; }).map(function (m) {
253 return { id: m.id, url: m.url, alt: m.alt, caption: m.caption, sizes: m.sizes };
254 });
255 setAttributes({ images: existing.concat(toAdd) });
256 },
257 type: 'image',
258 multiple: true,
259 gallery: true,
260 value: (a.images || []).map(function (i) { return i.id; }),
261 render: function (ref) {
262 return el(Button, { variant: 'primary', onClick: ref.open },
263 a.images && a.images.length > 0
264 ? __('Edit Gallery Images', 'blockenberg')
265 : __('Add Gallery Images', 'blockenberg')
266 );
267 }
268 })
269 ),
270 a.images && a.images.length > 0 && el('span', { style: { fontSize: '12px', color: '#888' } }, a.images.length + ' ' + __('images', 'blockenberg'))
271 ),
272
273 // Grid preview
274 a.images && a.images.length > 0
275 ? el('div', { className: 'bkbg-ig-grid' },
276 a.images.map(function (img) {
277 return renderItem(img, a, removeImage);
278 })
279 )
280 : el('div', { style: { background: '#f0f7ff', border: '2px dashed #90c4f7', borderRadius: '8px', padding: '40px 20px', textAlign: 'center', color: '#2563eb' } },
281 el('span', { className: 'dashicons dashicons-format-gallery', style: { fontSize: '36px', display: 'block', marginBottom: '8px' } }),
282 el('span', {}, __('Add images to start your gallery →', 'blockenberg'))
283 )
284 )
285 );
286 },
287
288 /* ── Save ───────────────────────────────────────────────────────── */
289 save: function (props) {
290 var a = props.attributes;
291 var images = a.images || [];
292 var ar = RATIOS[a.aspectRatio] || '1 / 1';
293 var autoAr = a.aspectRatio === 'auto';
294
295 // Gather unique categories for filter
296 var categories = [];
297 if (a.filterEnabled) {
298 images.forEach(function (img) {
299 if (img.category && categories.indexOf(img.category) === -1) categories.push(img.category);
300 });
301 }
302
303 return el('div', wp.blockEditor.useBlockProps.save({
304 className: 'bkbg-ig-wrapper bkbg-ig-layout-' + a.layout + ' bkbg-ig-hover-' + a.hoverEffect,
305 style: buildWrapStyle(a)
306 }),
307 // Filter bar
308 a.filterEnabled && categories.length > 0 && el('div', { className: 'bkbg-ig-filter-bar' },
309 el('button', { type: 'button', className: 'bkbg-ig-filter-btn is-active', 'data-filter': 'all' }, __('All', 'blockenberg')),
310 categories.map(function (cat) {
311 return el('button', { key: cat, type: 'button', className: 'bkbg-ig-filter-btn', 'data-filter': cat }, cat);
312 })
313 ),
314
315 // Gallery grid
316 el('div', {
317 className: 'bkbg-ig-grid',
318 'data-lightbox': a.lightbox ? '1' : '0',
319 'data-layout': a.layout
320 },
321 images.map(function (img, idx) {
322 return el('div', {
323 key: img.id || img.url,
324 className: 'bkbg-ig-item',
325 'data-category': img.category || '',
326 'data-index': idx
327 },
328 el('a', {
329 href: a.lightbox ? img.url : undefined,
330 className: 'bkbg-ig-link' + (a.lightbox ? ' bkbg-ig-lb-trigger' : ''),
331 'aria-label': img.alt || img.caption || __('View image', 'blockenberg'),
332 onClick: a.lightbox ? 'return false;' : undefined
333 },
334 el('img', {
335 src: (img.sizes && img.sizes.large && img.sizes.large.url) || img.url,
336 alt: img.alt || '',
337 className: 'bkbg-ig-img',
338 loading: 'lazy',
339 decoding: 'async',
340 style: !autoAr ? { aspectRatio: ar, objectFit: 'cover', width: '100%', display: 'block' } : { width: '100%', display: 'block' }
341 }),
342 el('div', { className: 'bkbg-ig-overlay', 'aria-hidden': 'true' },
343 a.lightbox && el('svg', { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', fill: 'currentColor', width: '28', height: '28', className: 'bkbg-ig-eye-icon' },
344 el('path', { d: 'M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z' })
345 )
346 ),
347 (a.showCaptions && img.caption) && el('div', { className: 'bkbg-ig-caption bkbg-ig-caption-' + a.captionStyle }, img.caption)
348 )
349 );
350 })
351 )
352 );
353 }
354 });
355 }() );
356