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

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

369 lines 21.3 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 registerBlockType = wp.blocks.registerBlockType;
5 var __ = wp.i18n.__;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
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 TextControl = wp.components.TextControl;
13 var TextareaControl = wp.components.TextareaControl;
14 var ToggleControl = wp.components.ToggleControl;
15 var RangeControl = wp.components.RangeControl;
16 var SelectControl = wp.components.SelectControl;
17 var Button = wp.components.Button;
18
19 var _tc, _tv;
20 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
21 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
22
23 var STYLE_OPTIONS = [
24 { label: __('Horizontal (image left)', 'blockenberg'), value: 'horizontal' },
25 { label: __('Vertical (image top)', 'blockenberg'), value: 'vertical' },
26 { label: __('Minimal (no image)', 'blockenberg'), value: 'minimal' },
27 { label: __('Large banner (top image)', 'blockenberg'), value: 'large' },
28 ];
29
30 function CardPreview(props) {
31 var a = props.attributes;
32 var isHorizontal = a.cardStyle === 'horizontal';
33 var isVertical = a.cardStyle === 'vertical';
34 var isLarge = a.cardStyle === 'large';
35 var isMinimal = a.cardStyle === 'minimal';
36
37 var wrapStyle = {
38 display: 'flex',
39 flexDirection: isHorizontal ? 'row' : 'column',
40 background: a.bgColor,
41 border: a.borderWidth + 'px solid ' + a.borderColor,
42 borderRadius: a.borderRadius + 'px',
43 overflow: 'hidden',
44 boxShadow: a.shadow ? '0 2px 16px rgba(0,0,0,0.09)' : 'none',
45 maxWidth: a.maxWidth + 'px',
46 cursor: 'pointer',
47 textDecoration: 'none',
48 transition: 'transform 0.2s, box-shadow 0.2s',
49 };
50
51 var imageEl = null;
52 if (a.showImage && !isMinimal) {
53 var imgStyle = {};
54 if (isHorizontal) {
55 imgStyle = { minWidth: a.imageWidth + 'px', maxWidth: a.imageWidth + 'px', height: '100%', minHeight: a.imageHeight + 'px', objectFit: 'cover', display: 'block' };
56 } else if (isLarge) {
57 imgStyle = { width: '100%', height: (a.imageHeight * 1.5) + 'px', objectFit: 'cover', display: 'block' };
58 } else {
59 imgStyle = { width: '100%', height: a.imageHeight + 'px', objectFit: 'cover', display: 'block' };
60 }
61
62 imageEl = a.imageUrl
63 ? el('img', { src: a.imageUrl, alt: a.imageAlt, style: imgStyle })
64 : el('div', { style: Object.assign({}, imgStyle, { background: '#e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#9ca3af', fontSize: '13px' }) }, __('Image', 'blockenberg'));
65 }
66
67 var textPad = isLarge ? '20px 24px 24px' : isHorizontal ? '20px' : '16px 20px 20px';
68
69 var textContent = el('div', { style: { padding: textPad, flex: 1, minWidth: 0 } },
70 // Domain row
71 (a.showFavicon || a.showDomain) && el('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' } },
72 a.showFavicon && (
73 a.faviconUrl
74 ? el('img', { src: a.faviconUrl, style: { width: '14px', height: '14px', borderRadius: '2px' } })
75 : el('span', { style: { width: '14px', height: '14px', background: a.domainColor, borderRadius: '2px', display: 'inline-block', opacity: 0.5 } })
76 ),
77 a.showDomain && el('span', {
78 style: { fontSize: '12px', color: a.domainColor, fontWeight: 600, letterSpacing: '0.2px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
79 }, a.domain || a.siteName || 'example.com'),
80 a.siteName && a.domain && el('span', { style: { color: '#d1d5db', fontSize: '11px' } }, '·'),
81 a.siteName && a.domain && el('span', { style: { fontSize: '12px', color: '#9ca3af' } }, a.siteName)
82 ),
83 // Title
84 el('p', {
85 className: 'bklp-title',
86 style: { color: a.titleColor }
87 }, a.title || __('Link title', 'blockenberg')),
88 // Description
89 a.showDesc && el('p', {
90 className: 'bklp-desc',
91 style: { margin: '6px 0 0', color: a.descColor }
92 }, a.description || __('Description text…', 'blockenberg'))
93 );
94
95 return el('div', { className: 'bklp-card bklp-style-' + a.cardStyle, style: wrapStyle },
96 isHorizontal && imageEl,
97 textContent,
98 !isHorizontal && imageEl && el(Fragment, null) // images at top are rendered above text
99 );
100 }
101
102 registerBlockType('blockenberg/link-preview', {
103 title: __('Link Preview', 'blockenberg'),
104 description: __('OG-style link preview card with image, domain, title, and description.', 'blockenberg'),
105 category: 'bkbg-content',
106 icon: 'admin-links',
107
108 edit: function (props) {
109 var attributes = props.attributes;
110 var setAttributes = props.setAttributes;
111 var blockProps = useBlockProps((function () {
112 var _tvFn = getTypoCssVars();
113 var s = {};
114 if (_tvFn) {
115 Object.assign(s, _tvFn(attributes.titleTypo, '--bklp-tt-'));
116 Object.assign(s, _tvFn(attributes.descTypo, '--bklp-d-'));
117 }
118 return { className: 'bklp-editor-wrap', style: s };
119 })());
120
121 return el(Fragment, null,
122 el(InspectorControls, null,
123 /* ── Link / URL ───────────────────────────────────────── */
124 el(PanelBody, { title: __('Link', 'blockenberg'), initialOpen: true },
125 el(TextControl, {
126 label: __('URL', 'blockenberg'),
127 value: attributes.url,
128 type: 'url',
129 placeholder: 'https://example.com/article',
130 onChange: function (v) { setAttributes({ url: v }); },
131 }),
132 el(ToggleControl, {
133 label: __('Open in new tab', 'blockenberg'),
134 checked: attributes.openNewTab,
135 onChange: function (v) { setAttributes({ openNewTab: v }); },
136 __nextHasNoMarginBottom: true,
137 })
138 ),
139 /* ── Content ──────────────────────────────────────────── */
140 el(PanelBody, { title: __('Content', 'blockenberg'), initialOpen: true },
141 el(TextControl, {
142 label: __('Title', 'blockenberg'),
143 value: attributes.title,
144 onChange: function (v) { setAttributes({ title: v }); },
145 }),
146 el(TextareaControl, {
147 label: __('Description', 'blockenberg'),
148 value: attributes.description,
149 rows: 3,
150 onChange: function (v) { setAttributes({ description: v }); },
151 }),
152 el(TextControl, {
153 label: __('Domain (e.g. github.com)', 'blockenberg'),
154 value: attributes.domain,
155 onChange: function (v) { setAttributes({ domain: v }); },
156 }),
157 el(TextControl, {
158 label: __('Site name (e.g. GitHub)', 'blockenberg'),
159 value: attributes.siteName,
160 onChange: function (v) { setAttributes({ siteName: v }); },
161 }),
162 el(ToggleControl, {
163 label: __('Show description', 'blockenberg'),
164 checked: attributes.showDesc,
165 onChange: function (v) { setAttributes({ showDesc: v }); },
166 __nextHasNoMarginBottom: true,
167 })
168 ),
169 /* ── Image ────────────────────────────────────────────── */
170 el(PanelBody, { title: __('Image', 'blockenberg'), initialOpen: false },
171 el(ToggleControl, {
172 label: __('Show image', 'blockenberg'),
173 checked: attributes.showImage,
174 onChange: function (v) { setAttributes({ showImage: v }); },
175 __nextHasNoMarginBottom: true,
176 }),
177 attributes.showImage && el(MediaUploadCheck, null,
178 el(MediaUpload, {
179 onSelect: function (m) { setAttributes({ imageUrl: m.url, imageId: m.id, imageAlt: m.alt || '' }); },
180 allowedTypes: ['image'],
181 value: attributes.imageId,
182 render: function (r) {
183 return el(Fragment, null,
184 attributes.imageUrl && el('img', { src: attributes.imageUrl, style: { width: '100%', borderRadius: '6px', marginBottom: '8px', display: 'block' } }),
185 el(Button, { onClick: r.open, variant: 'secondary', style: { width: '100%' } },
186 attributes.imageUrl ? __('Change image', 'blockenberg') : __('Select image', 'blockenberg')
187 ),
188 attributes.imageUrl && el(Button, {
189 onClick: function () { setAttributes({ imageUrl: '', imageId: 0 }); },
190 variant: 'link', isDestructive: true,
191 style: { marginTop: '4px' }
192 }, __('Remove image', 'blockenberg'))
193 );
194 },
195 })
196 ),
197 attributes.showImage && el(RangeControl, {
198 label: __('Image height (px)', 'blockenberg'),
199 value: attributes.imageHeight,
200 min: 80, max: 480,
201 onChange: function (v) { setAttributes({ imageHeight: v }); },
202 }),
203 attributes.showImage && attributes.cardStyle === 'horizontal' && el(RangeControl, {
204 label: __('Image width — horizontal (px)', 'blockenberg'),
205 value: attributes.imageWidth,
206 min: 100, max: 400,
207 onChange: function (v) { setAttributes({ imageWidth: v }); },
208 })
209 ),
210 /* ── Favicon ──────────────────────────────────────────── */
211 el(PanelBody, { title: __('Favicon', 'blockenberg'), initialOpen: false },
212 el(ToggleControl, {
213 label: __('Show favicon', 'blockenberg'),
214 checked: attributes.showFavicon,
215 onChange: function (v) { setAttributes({ showFavicon: v }); },
216 __nextHasNoMarginBottom: true,
217 }),
218 attributes.showFavicon && el(ToggleControl, {
219 label: __('Show domain', 'blockenberg'),
220 checked: attributes.showDomain,
221 onChange: function (v) { setAttributes({ showDomain: v }); },
222 __nextHasNoMarginBottom: true,
223 }),
224 attributes.showFavicon && el(MediaUploadCheck, null,
225 el(MediaUpload, {
226 onSelect: function (m) { setAttributes({ faviconUrl: m.url, faviconId: m.id }); },
227 allowedTypes: ['image'],
228 value: attributes.faviconId,
229 render: function (r) {
230 return el(Fragment, null,
231 attributes.faviconUrl && el('img', { src: attributes.faviconUrl, style: { width: '20px', height: '20px', objectFit: 'cover', marginBottom: '6px', display: 'block' } }),
232 el(Button, { onClick: r.open, variant: 'secondary', style: { width: '100%' } },
233 attributes.faviconUrl ? __('Change favicon', 'blockenberg') : __('Select favicon', 'blockenberg')
234 )
235 );
236 },
237 })
238 )
239 ),
240 /* ── Style & Layout ───────────────────────────────────── */
241 el(PanelBody, { title: __('Style & Layout', 'blockenberg'), initialOpen: false },
242 el(SelectControl, {
243 label: __('Card style', 'blockenberg'),
244 value: attributes.cardStyle,
245 options: STYLE_OPTIONS,
246 onChange: function (v) { setAttributes({ cardStyle: v }); },
247 }),
248 el(RangeControl, {
249 label: __('Max width (px)', 'blockenberg'),
250 value: attributes.maxWidth,
251 min: 240, max: 1000,
252 onChange: function (v) { setAttributes({ maxWidth: v }); },
253 }),
254 el(RangeControl, {
255 label: __('Border radius (px)', 'blockenberg'),
256 value: attributes.borderRadius,
257 min: 0, max: 32,
258 onChange: function (v) { setAttributes({ borderRadius: v }); },
259 }),
260 el(RangeControl, {
261 label: __('Border width (px)', 'blockenberg'),
262 value: attributes.borderWidth,
263 min: 0, max: 4,
264 onChange: function (v) { setAttributes({ borderWidth: v }); },
265 }),
266 el(ToggleControl, {
267 label: __('Drop shadow', 'blockenberg'),
268 checked: attributes.shadow,
269 onChange: function (v) { setAttributes({ shadow: v }); },
270 __nextHasNoMarginBottom: true,
271 }),
272 el(ToggleControl, {
273 label: __('Hover lift effect', 'blockenberg'),
274 checked: attributes.hoverLift,
275 onChange: function (v) { setAttributes({ hoverLift: v }); },
276 __nextHasNoMarginBottom: true,
277 })
278 ),
279 /* ── Colors ───────────────────────────────────────────── */
280
281 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
282 getTypoControl() && el(getTypoControl(), { label: __('Title'), value: attributes.titleTypo || {}, onChange: function (v) { setAttributes({ titleTypo: v }); } }),
283 getTypoControl() && el(getTypoControl(), { label: __('Description'), value: attributes.descTypo || {}, onChange: function (v) { setAttributes({ descTypo: v }); } })
284 ),
285 el(PanelColorSettings, {
286 title: __('Colors', 'blockenberg'),
287 initialOpen: false,
288 colorSettings: [
289 { value: attributes.bgColor, onChange: function (v) { setAttributes({ bgColor: v || '#ffffff' }); }, label: __('Background', 'blockenberg') },
290 { value: attributes.borderColor, onChange: function (v) { setAttributes({ borderColor: v || '#e5e7eb' }); }, label: __('Border', 'blockenberg') },
291 { value: attributes.titleColor, onChange: function (v) { setAttributes({ titleColor: v || '#111827' }); }, label: __('Title', 'blockenberg') },
292 { value: attributes.descColor, onChange: function (v) { setAttributes({ descColor: v || '#6b7280' }); }, label: __('Description', 'blockenberg') },
293 { value: attributes.domainColor, onChange: function (v) { setAttributes({ domainColor: v || '#6c3fb5' }); }, label: __('Domain / accent', 'blockenberg') },
294 ],
295 })
296 ),
297 el('div', blockProps,
298 el(CardPreview, { attributes: attributes })
299 )
300 );
301 },
302
303 save: function (props) {
304 var a = props.attributes;
305
306 var imageEl = null;
307 if (a.showImage && a.imageUrl && a.cardStyle !== 'minimal') {
308 var imgH = a.cardStyle === 'large' ? (a.imageHeight * 1.5) : a.imageHeight;
309 imageEl = el('img', {
310 className: 'bklp-image',
311 src: a.imageUrl,
312 alt: a.imageAlt,
313 style: {
314 width: '100%',
315 height: imgH + 'px',
316 objectFit: 'cover',
317 display: 'block',
318 maxWidth: a.cardStyle === 'horizontal' ? a.imageWidth + 'px' : undefined,
319 },
320 loading: 'lazy',
321 });
322 }
323
324 return el('a', {
325 className: 'bklp-card bklp-style-' + a.cardStyle + (a.hoverLift ? ' bklp-hover-lift' : ''),
326 href: a.url || '#',
327 target: a.openNewTab ? '_blank' : undefined,
328 rel: a.openNewTab ? 'noopener noreferrer' : undefined,
329 style: (function () {
330 var _tvFn = getTypoCssVars();
331 var s = {
332 '--bklp-bg': a.bgColor,
333 '--bklp-border': a.borderColor,
334 '--bklp-title': a.titleColor,
335 '--bklp-desc': a.descColor,
336 '--bklp-domain': a.domainColor,
337 '--bklp-radius': a.borderRadius + 'px',
338 '--bklp-bw': a.borderWidth + 'px',
339 '--bklp-shadow': a.shadow ? '0 2px 16px rgba(0,0,0,0.09)' : 'none',
340 '--bklp-img-w': a.imageWidth + 'px',
341 maxWidth: a.maxWidth + 'px',
342 };
343 if (_tvFn) {
344 Object.assign(s, _tvFn(a.titleTypo, '--bklp-tt-'));
345 Object.assign(s, _tvFn(a.descTypo, '--bklp-d-'));
346 }
347 return s;
348 })(),
349 },
350 a.cardStyle === 'horizontal' && imageEl,
351 !a.cardStyle || a.cardStyle !== 'horizontal' && imageEl,
352 el('div', { className: 'bklp-text' },
353 (a.showFavicon || a.showDomain) && el('div', { className: 'bklp-meta' },
354 a.showFavicon && a.faviconUrl && el('img', {
355 className: 'bklp-favicon',
356 src: a.faviconUrl, alt: '', width: 14, height: 14
357 }),
358 a.showDomain && el('span', { className: 'bklp-domain' }, a.domain || a.siteName),
359 a.siteName && a.domain && el('span', { className: 'bklp-dot', 'aria-hidden': 'true' }, '·'),
360 a.siteName && a.domain && el('span', { className: 'bklp-sitename' }, a.siteName)
361 ),
362 el('p', { className: 'bklp-title' }, a.title),
363 a.showDesc && el('p', { className: 'bklp-desc' }, a.description)
364 )
365 );
366 },
367 });
368 }() );
369