PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.11
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.11
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 / google-map / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.11, at blocks/google-map/index.js

357 lines 20.6 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 useBlockProps = wp.blockEditor.useBlockProps;
9 var RichText = wp.blockEditor.RichText;
10 var PanelBody = wp.components.PanelBody;
11 var ColorPicker = wp.components.ColorPicker;
12 var Popover = wp.components.Popover;
13 var Button = wp.components.Button;
14 var ToggleControl = wp.components.ToggleControl;
15 var RangeControl = wp.components.RangeControl;
16 var SelectControl = wp.components.SelectControl;
17 var TextControl = wp.components.TextControl;
18 var TextareaControl = wp.components.TextareaControl;
19 var Notice = wp.components.Notice;
20
21 var _gmTC, _gmTV;
22 function _tc() { return _gmTC || (_gmTC = window.bkbgTypographyControl); }
23 function _tv() { return _gmTV || (_gmTV = window.bkbgTypoCssVars); }
24
25 // ── Color swatch control (same pattern as flip-card / blockquote) ───────────
26 function makeColorControl(openKey, setOpenKey) {
27 return function renderColorControl(key, label, value, onChange) {
28 var isOpen = openKey === key;
29 return el('div', {
30 key: key,
31 style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' }
32 },
33 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
34 el('div', { style: { position: 'relative', flexShrink: 0 } },
35 el('button', {
36 type: 'button',
37 title: value || 'none',
38 onClick: function () { setOpenKey(isOpen ? null : key); },
39 style: {
40 width: '28px', height: '28px', borderRadius: '4px',
41 border: isOpen ? '2px solid #007cba' : '2px solid #ddd',
42 cursor: 'pointer', padding: 0, display: 'block',
43 background: value || '#ffffff', flexShrink: 0
44 }
45 }),
46 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
47 el('div', { style: { padding: '8px' }, onMouseDown: function (e) { e.stopPropagation(); } },
48 el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' } },
49 el('strong', { style: { fontSize: '12px' } }, label),
50 el(Button, { icon: 'no-alt', isSmall: true, onClick: function () { setOpenKey(null); } })
51 ),
52 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
53 )
54 )
55 )
56 );
57 };
58 }
59
60 // ── Section sub-header ───────────────────────────────────────────────────────
61 function sectionLabel(text) {
62 return el('p', {
63 style: { margin: '8px 0 4px', fontWeight: 600, fontSize: '11px', textTransform: 'uppercase', color: '#888', letterSpacing: '.5px' }
64 }, text);
65 }
66
67 // ── Build Google Maps embed URL ─────────────────────────────────────────────
68 // With API key: uses Embed API v1 (full zoom + maptype support)
69 // Without API key: uses legacy iframe embed (limited but no key needed)
70 function buildEmbedUrl(a) {
71 var q = encodeURIComponent(a.address || 'New York');
72 if (a.apiKey) {
73 return 'https://www.google.com/maps/embed/v1/place'
74 + '?key=' + encodeURIComponent(a.apiKey)
75 + '&q=' + q
76 + '&zoom=' + a.zoom
77 + '&maptype=' + a.mapType
78 + '&language=en';
79 }
80 // Fallback: legacy Maps embed (zoom and type work with some limitations)
81 var typeMap = { roadmap: 'm', satellite: 'k', terrain: 'p', hybrid: 'h' };
82 return 'https://maps.google.com/maps'
83 + '?q=' + q
84 + '&z=' + a.zoom
85 + '&t=' + (typeMap[a.mapType] || 'm')
86 + '&output=embed'
87 + '&hl=en';
88 }
89
90 // ── Build box-shadow css value ───────────────────────────────────────────────
91 function buildShadow(a) {
92 if (!a.showShadow) return 'none';
93 return a.shadowH + 'px ' + a.shadowV + 'px ' + a.shadowBlur + 'px ' + a.shadowSpread + 'px ' + a.shadowColor;
94 }
95
96 // ── Build wrapper inline style ───────────────────────────────────────────────
97 function buildWrapStyle(a) {
98 return {
99 height: a.mapHeight + 'px',
100 borderRadius: a.borderRadius + 'px',
101 overflow: 'hidden',
102 border: a.showBorder ? (a.borderWidth + 'px ' + a.borderStyle + ' ' + a.borderColor) : 'none',
103 boxShadow: buildShadow(a),
104 maxWidth: a.maxWidth > 0 ? a.maxWidth + 'px' : '100%',
105 margin: '0 auto'
106 };
107 }
108
109 // ────────────────────────────────────────────────────────────────────────────
110 registerBlockType('blockenberg/google-map', {
111 title: __('Google Map', 'blockenberg'),
112 icon: 'location-alt',
113 category: 'bkbg-business',
114
115 edit: function (props) {
116 var a = props.attributes;
117 var setAttributes = props.setAttributes;
118
119 // Color picker popover state
120 var openColorKeyState = useState(null);
121 var openColorKey = openColorKeyState[0];
122 var setOpenColorKey = openColorKeyState[1];
123 var renderColorControl = makeColorControl(openColorKey, setOpenColorKey);
124
125 // ── Options ───────────────────────────────────────────────────────
126 var mapTypeOptions = [
127 { label: __('Roadmap (default)', 'blockenberg'), value: 'roadmap' },
128 { label: __('Satellite', 'blockenberg'), value: 'satellite' },
129 { label: __('Hybrid (satellite + labels)', 'blockenberg'), value: 'hybrid' },
130 { label: __('Terrain', 'blockenberg'), value: 'terrain' }
131 ];
132 var borderStyleOptions = [
133 { label: __('Solid', 'blockenberg'), value: 'solid' },
134 { label: __('Dashed', 'blockenberg'), value: 'dashed' },
135 { label: __('Dotted', 'blockenberg'), value: 'dotted' }
136 ];
137 var textAlignOptions = [
138 { label: __('Left', 'blockenberg'), value: 'left' },
139 { label: __('Center', 'blockenberg'), value: 'center' },
140 { label: __('Right', 'blockenberg'), value: 'right' }
141 ];
142 var fontWeightOptions = [
143 { label: '300 — Light', value: 300 },
144 { label: '400 — Regular', value: 400 },
145 { label: '500 — Medium', value: 500 },
146 { label: '600 — Semi Bold', value: 600 },
147 { label: '700 — Bold', value: 700 }
148 ];
149
150 var embedUrl = buildEmbedUrl(a);
151 var wrapStyle = buildWrapStyle(a);
152
153 // ── Inspector ─────────────────────────────────────────────────────
154 var inspector = el(InspectorControls, {},
155
156 // ── Map Settings ──────────────────────────────────────────────
157 el(PanelBody, { title: __('Map Settings', 'blockenberg'), initialOpen: true },
158 el(TextControl, {
159 label: __('Address or Place Name', 'blockenberg'),
160 help: __('Any address, city, landmark, or coordinates.', 'blockenberg'),
161 value: a.address,
162 onChange: function (v) { setAttributes({ address: v }); }
163 }),
164 el(RangeControl, {
165 label: __('Zoom Level', 'blockenberg'),
166 help: __('1 = world, 14 = street, 20 = building', 'blockenberg'),
167 value: a.zoom, min: 1, max: 21,
168 onChange: function (v) { setAttributes({ zoom: v }); }
169 }),
170 el(SelectControl, {
171 label: __('Map Type', 'blockenberg'),
172 value: a.mapType,
173 options: mapTypeOptions,
174 onChange: function (v) { setAttributes({ mapType: v }); }
175 }),
176 el('hr', {}),
177 el(ToggleControl, {
178 label: __('Allow fullscreen', 'blockenberg'),
179 checked: a.allowFullscreen,
180 __nextHasNoMarginBottom: true,
181 onChange: function (v) { setAttributes({ allowFullscreen: v }); }
182 }),
183 el(ToggleControl, {
184 label: __('Lazy-load iframe', 'blockenberg'),
185 help: __('Defers loading until the map is near the viewport.', 'blockenberg'),
186 checked: a.lazyLoad,
187 __nextHasNoMarginBottom: true,
188 onChange: function (v) { setAttributes({ lazyLoad: v }); }
189 }),
190 el('hr', {}),
191 el(TextControl, {
192 label: __('Google Maps API Key (optional)', 'blockenberg'),
193 help: __('Required for satellite/terrain/hybrid types and precise zoom. Leave empty for basic embed.', 'blockenberg'),
194 value: a.apiKey,
195 type: 'password',
196 onChange: function (v) { setAttributes({ apiKey: v }); }
197 })
198 ),
199
200 // ── Dimensions ────────────────────────────────────────────────
201 el(PanelBody, { title: __('Dimensions', 'blockenberg'), initialOpen: false },
202 el(RangeControl, {
203 label: __('Map Height (px)', 'blockenberg'),
204 value: a.mapHeight, min: 150, max: 900,
205 onChange: function (v) { setAttributes({ mapHeight: v }); }
206 }),
207 el(RangeControl, {
208 label: __('Max Width (px, 0 = full)', 'blockenberg'),
209 help: __('Set a max-width and the map will be centered.', 'blockenberg'),
210 value: a.maxWidth, min: 0, max: 1600, step: 10,
211 onChange: function (v) { setAttributes({ maxWidth: v }); }
212 }),
213 el(RangeControl, {
214 label: __('Corner Radius (px)', 'blockenberg'),
215 value: a.borderRadius, min: 0, max: 48,
216 onChange: function (v) { setAttributes({ borderRadius: v }); }
217 })
218 ),
219
220 // ── Border & Shadow ───────────────────────────────────────────
221 el(PanelBody, { title: __('Border & Shadow', 'blockenberg'), initialOpen: false },
222 el(ToggleControl, {
223 label: __('Show Border', 'blockenberg'),
224 checked: a.showBorder,
225 __nextHasNoMarginBottom: true,
226 onChange: function (v) { setAttributes({ showBorder: v }); }
227 }),
228 a.showBorder && el(Fragment, {},
229 renderColorControl('borderColor', __('Border Color', 'blockenberg'), a.borderColor, function (c) { setAttributes({ borderColor: c }); }),
230 el(RangeControl, {
231 label: __('Border Width (px)', 'blockenberg'),
232 value: a.borderWidth, min: 1, max: 12,
233 onChange: function (v) { setAttributes({ borderWidth: v }); }
234 }),
235 el(SelectControl, {
236 label: __('Border Style', 'blockenberg'),
237 value: a.borderStyle,
238 options: borderStyleOptions,
239 onChange: function (v) { setAttributes({ borderStyle: v }); }
240 })
241 ),
242 el('hr', {}),
243 el(ToggleControl, {
244 label: __('Show Shadow', 'blockenberg'),
245 checked: a.showShadow,
246 __nextHasNoMarginBottom: true,
247 onChange: function (v) { setAttributes({ showShadow: v }); }
248 }),
249 a.showShadow && el(Fragment, {},
250 renderColorControl('shadowColor', __('Shadow Color', 'blockenberg'), a.shadowColor, function (c) { setAttributes({ shadowColor: c }); }),
251 el(RangeControl, { label: __('Horizontal (px)', 'blockenberg'), value: a.shadowH, min: -60, max: 60, onChange: function (v) { setAttributes({ shadowH: v }); } }),
252 el(RangeControl, { label: __('Vertical (px)', 'blockenberg'), value: a.shadowV, min: -60, max: 60, onChange: function (v) { setAttributes({ shadowV: v }); } }),
253 el(RangeControl, { label: __('Blur (px)', 'blockenberg'), value: a.shadowBlur, min: 0, max: 80, onChange: function (v) { setAttributes({ shadowBlur: v }); } }),
254 el(RangeControl, { label: __('Spread (px)', 'blockenberg'), value: a.shadowSpread, min: -20, max: 40, onChange: function (v) { setAttributes({ shadowSpread: v }); } })
255 )
256 ),
257
258 // ── Caption ───────────────────────────────────────────────────
259 el(PanelBody, { title: __('Caption', 'blockenberg'), initialOpen: false },
260 el(ToggleControl, {
261 label: __('Show Caption', 'blockenberg'),
262 checked: a.showCaption,
263 __nextHasNoMarginBottom: true,
264 onChange: function (v) { setAttributes({ showCaption: v }); }
265 }),
266 ),
267 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
268 a.showCaption && el(Fragment, {},
269 _tc()({ label: __('Caption', 'blockenberg'), value: a.typoCaption, onChange: function (v) { setAttributes({ typoCaption: v }); } }),
270 renderColorControl('captionColor', __('Text Color', 'blockenberg'), a.captionColor, function (c) { setAttributes({ captionColor: c }); }),
271 el(SelectControl, {
272 label: __('Alignment', 'blockenberg'),
273 value: a.captionAlign,
274 options: textAlignOptions,
275 onChange: function (v) { setAttributes({ captionAlign: v }); }
276 }),
277 el(RangeControl, {
278 label: __('Spacing Above (px)', 'blockenberg'),
279 value: a.captionSpacing, min: 0, max: 40,
280 onChange: function (v) { setAttributes({ captionSpacing: v }); }
281 })
282 )
283 )
284 );
285
286 // ── Editor preview ────────────────────────────────────────────────
287 return el(Fragment, {},
288 inspector,
289 el('div', useBlockProps({ className: 'bkbg-gm-outer', style: Object.assign({}, _tv()(a.typoCaption, '--bkbg-gm-cap-')) }),
290 el('div', { className: 'bkbg-gm-wrap', style: wrapStyle },
291 el('iframe', {
292 src: embedUrl,
293 width: '100%',
294 height: '100%',
295 style: { border: 'none', display: 'block' },
296 allowFullScreen: a.allowFullscreen,
297 loading: a.lazyLoad ? 'lazy' : 'eager',
298 referrerPolicy: 'no-referrer-when-downgrade',
299 title: a.address || 'Google Map'
300 })
301 ),
302 a.showCaption && el(RichText, {
303 tagName: 'p',
304 className: 'bkbg-gm-caption',
305 value: a.caption,
306 placeholder: __('Caption text…', 'blockenberg'),
307 onChange: function (v) { setAttributes({ caption: v }); },
308 allowedFormats: ['core/bold', 'core/italic', 'core/link'],
309 style: {
310 color: a.captionColor,
311 textAlign: a.captionAlign,
312 marginTop: a.captionSpacing + 'px',
313 marginBottom: 0,
314 padding: 0
315 }
316 })
317 )
318 );
319 },
320
321 // ── Save ────────────────────────────────────────────────────────────────
322 save: function (props) {
323 var a = props.attributes;
324 var RichTextContent = wp.blockEditor.RichText.Content;
325 var embedUrl = buildEmbedUrl(a);
326 var wrapStyle = buildWrapStyle(a);
327
328 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-gm-outer', style: Object.assign({}, _tv()(a.typoCaption, '--bkbg-gm-cap-')) }),
329 el('div', { className: 'bkbg-gm-wrap', style: wrapStyle },
330 el('iframe', {
331 src: embedUrl,
332 width: '100%',
333 height: '100%',
334 style: { border: 'none', display: 'block' },
335 allowFullScreen: a.allowFullscreen ? true : undefined,
336 loading: a.lazyLoad ? 'lazy' : 'eager',
337 referrerPolicy: 'no-referrer-when-downgrade',
338 title: a.address || 'Google Map'
339 })
340 ),
341 a.showCaption && el(RichTextContent, {
342 tagName: 'p',
343 className: 'bkbg-gm-caption',
344 value: a.caption,
345 style: {
346 color: a.captionColor,
347 textAlign: a.captionAlign,
348 marginTop: a.captionSpacing + 'px',
349 marginBottom: 0,
350 padding: 0
351 }
352 })
353 );
354 }
355 });
356 }() );
357