PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 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 / word-cloud / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at blocks/word-cloud/index.js

337 lines 21.0 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 __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
10 var PanelBody = wp.components.PanelBody;
11 var ToggleControl = wp.components.ToggleControl;
12 var RangeControl = wp.components.RangeControl;
13 var SelectControl = wp.components.SelectControl;
14 var TextControl = wp.components.TextControl;
15 var Button = wp.components.Button;
16 var ColorPicker = wp.components.ColorPicker;
17 var Popover = wp.components.Popover;
18
19 // Resolve the shared components lazily without defining globals for each block.
20 function _tc(label, key, attrs, setA) {
21 var Control = window.bkbgTypographyControl;
22 return Control ? el(Control, {
23 label: label,
24 value: attrs[key] || {},
25 onChange: function (value) { var update = {}; update[key] = value; setA(update); }
26 }) : null;
27 }
28 function _tvf(vars, key, attrs, prefix) {
29 var cssVars = window.bkbgTypoCssVars;
30 if (cssVars) Object.assign(vars, cssVars(attrs[key] || {}, prefix));
31 return vars;
32 }
33 function getTypoControl(label, key, attrs, setA) { return _tc(label, key, attrs, setA); }
34 function getTypoCssVars(attrs) {
35 var v = {};
36 _tvf(v, 'cloudTypo', attrs, '--bkwcl-cl-');
37 return v;
38 }
39
40 /* ── helpers ──────────────────────────────────────────────────────────────── */
41 function weightToSize(w, min, max) {
42 var clamped = Math.max(1, Math.min(10, w));
43 return Math.round(min + (max - min) * ((clamped - 1) / 9));
44 }
45
46 function getPalette(paletteStr) {
47 return (paletteStr || '#6c3fb5').split(',').map(function (c) { return c.trim(); }).filter(Boolean);
48 }
49
50 function getWordColor(word, idx, colorMode, palette, single) {
51 if (word.colorOverride) { return word.colorOverride; }
52 if (colorMode === 'single') { return single || '#6c3fb5'; }
53 var cols = getPalette(palette);
54 return cols[idx % cols.length] || '#6c3fb5';
55 }
56
57 /* seeded shuffle — deterministic so save() renders same order each time */
58 function seededShuffle(arr, seed) {
59 var out = arr.slice();
60 var s = seed || 42;
61 for (var i = out.length - 1; i > 0; i--) {
62 s = (s * 1664525 + 1013904223) & 0xffffffff;
63 var j = Math.abs(s) % (i + 1);
64 var tmp = out[i]; out[i] = out[j]; out[j] = tmp;
65 }
66 return out;
67 }
68
69 function sortWords(words, order, seed) {
70 if (order === 'asc') { return words.slice().sort(function (a, b) { return a.weight - b.weight; }); }
71 if (order === 'desc') { return words.slice().sort(function (a, b) { return b.weight - a.weight; }); }
72 if (order === 'alpha') { return words.slice().sort(function (a, b) { return a.text.localeCompare(b.text); }); }
73 if (order === 'random') { return seededShuffle(words, seed); }
74 return words;
75 }
76
77 /* ── hex → rgba ───────────────────────────────────────────────────────────── */
78 function hexToRgba(hex, alpha) {
79 var r = 0, g = 0, b = 0;
80 var h = hex.replace('#', '');
81 if (h.length === 3) { h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2]; }
82 r = parseInt(h.substring(0, 2), 16);
83 g = parseInt(h.substring(2, 4), 16);
84 b = parseInt(h.substring(4, 6), 16);
85 return 'rgba(' + r + ',' + g + ',' + b + ',' + (alpha/100) + ')';
86 }
87
88 /* ── word render ──────────────────────────────────────────────────────────── */
89 function renderWord(word, idx, displayIdx, a, isEdit) {
90 var color = getWordColor(word, displayIdx, a.colorMode, a.palette, a.singleColor);
91 var size = weightToSize(word.weight, a.minFontSize, a.maxFontSize);
92
93 var style = {
94 fontSize: size + 'px',
95 fontWeight: a.fontWeight,
96 color: color,
97 lineHeight: a.lineHeight,
98 letterSpacing: a.letterSpacing + 'em',
99 cursor: word.url ? 'pointer' : 'default',
100 display: 'inline-block',
101 textDecoration: 'none',
102 transition: 'transform 0.2s ease, opacity 0.2s ease',
103 };
104 if (a.showWordBg) {
105 style.background = hexToRgba(color, a.wordBgOpacity);
106 style.borderRadius = a.wordBorderRadius + 'px';
107 style.padding = a.wordPaddingV + 'px ' + a.wordPaddingH + 'px';
108 }
109
110 if (!isEdit && word.url) {
111 return el('a', {
112 key: idx, href: word.url, className: 'bkwc-word',
113 'data-hover': a.hoverEffect,
114 style: style
115 }, word.text);
116 }
117 return el('span', {
118 key: idx, className: 'bkwc-word',
119 'data-hover': a.hoverEffect,
120 style: style
121 }, word.text);
122 }
123
124 /* ── WordRow editor component ──────────────────────────────────────────── */
125 function WordRow(props) {
126 var word = props.word, idx = props.idx;
127 var onUpdate = props.onUpdate, onRemove = props.onRemove;
128 var expandState = useState(false);
129 var expanded = expandState[0], setExpanded = expandState[1];
130
131 return el('div', { style: { border: '1px solid #e5e7eb', borderRadius: '6px', marginBottom: '6px', overflow: 'hidden' } },
132 el('div', {
133 style: { display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 8px', background: '#f9fafb', cursor: 'pointer' },
134 onClick: function () { setExpanded(!expanded); }
135 },
136 el('span', { style: { flex: 1, fontWeight: 600, fontSize: '13px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, word.text || '(empty)'),
137 el('span', { style: { fontSize: '11px', color: '#9ca3af', minWidth: '40px' } }, 'w:' + word.weight),
138 el('span', { style: { fontSize: '12px', color: '#9ca3af' } }, expanded ? '▲' : '▼'),
139 el(Button, { isSmall: true, variant: 'tertiary', isDestructive: true, onClick: function (e) { e.stopPropagation(); onRemove(idx); } }, '×')
140 ),
141 expanded && el('div', { style: { padding: '10px', display: 'flex', flexDirection: 'column', gap: '6px' } },
142 el(TextControl, { label: __('Word / Phrase', 'blockenberg'), value: word.text, onChange: function (v) { onUpdate(idx, 'text', v); } }),
143 el(RangeControl, { label: __('Weight (1=tiny → 10=huge)', 'blockenberg'), value: word.weight, min: 1, max: 10, onChange: function (v) { onUpdate(idx, 'weight', v); } }),
144 el(TextControl, { label: __('Link URL (optional)', 'blockenberg'), value: word.url, onChange: function (v) { onUpdate(idx, 'url', v); } }),
145 el(BkbgColorSwatch, { label: __('Color Override (optional hex)', 'blockenberg'), value: word.colorOverride, onChange: function (v) { onUpdate(idx, 'colorOverride', v); } })
146 )
147 );
148 }
149
150 /* ── BkbgColorSwatch ─────────────────────────────────────────── */
151 function BkbgColorSwatch(props) {
152 var st = useState(false); var isOpen = st[0]; var setIsOpen = st[1];
153 return el(Fragment, null,
154 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px' } },
155 el('span', { style: { fontSize: '11px', fontWeight: 500, textTransform: 'uppercase' } }, props.label),
156 el('button', { type: 'button', style: { width: 28, height: 28, borderRadius: 4, border: '1px solid #ccc', background: props.value || '#fff', cursor: 'pointer', padding: 0 }, onClick: function () { setIsOpen(!isOpen); } })
157 ),
158 isOpen && el(Popover, { onClose: function () { setIsOpen(false); } }, el('div', { style: { padding: '8px' } }, el(ColorPicker, { color: props.value, enableAlpha: true, onChange: props.onChange })))
159 );
160 }
161
162 /* ── BkbgMultiColorControl ──────────────────────────────────────── */
163 function BkbgMultiColorControl(props) {
164 var label = props.label;
165 var value = props.value || '';
166 var onChange = props.onChange;
167 var colors = value.split(',').map(function (c) { return c.trim(); }).filter(Boolean);
168 var st2 = useState(-1); var openIdx = st2[0]; var setOpenIdx = st2[1];
169 return el(Fragment, null,
170 el('div', { style: { marginBottom: '8px' } },
171 el('span', { style: { fontSize: '11px', fontWeight: 500, textTransform: 'uppercase', display: 'block', marginBottom: '4px' } }, label),
172 el('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center' } },
173 colors.map(function (c, i) {
174 return el('div', { key: i, style: { position: 'relative', display: 'inline-flex' } },
175 el('button', { type: 'button', style: { width: 28, height: 28, borderRadius: 4, border: '1px solid #ccc', background: c, cursor: 'pointer', padding: 0 }, onClick: function () { setOpenIdx(openIdx === i ? -1 : i); } }),
176 el('button', { type: 'button', 'aria-label': 'Remove', style: { position: 'absolute', top: -6, right: -6, width: 14, height: 14, borderRadius: '50%', border: 'none', background: '#d00', color: '#fff', fontSize: '10px', lineHeight: '14px', cursor: 'pointer', padding: 0, textAlign: 'center' }, onClick: function () { var n = colors.slice(); n.splice(i, 1); onChange(n.join(',')); } }, '×'),
177 openIdx === i && el(Popover, { onClose: function () { setOpenIdx(-1); } }, el('div', { style: { padding: '8px' } }, el(ColorPicker, { color: c, enableAlpha: true, onChange: function (v) { var n = colors.slice(); n[i] = v; onChange(n.join(',')); } })))
178 );
179 }),
180 el(Button, { isSmall: true, variant: 'secondary', onClick: function () { onChange(value ? value + ',#6c3fb5' : '#6c3fb5'); }, style: { height: 28, minWidth: 28 } }, '+')
181 )
182 )
183 );
184 }
185
186 registerBlockType('blockenberg/word-cloud', {
187 title: __('Word Cloud', 'blockenberg'),
188 icon: 'tag',
189 category: 'bkbg-charts',
190 description: __('Visual word cloud with weighted font sizes.', 'blockenberg'),
191
192 edit: function (props) {
193 var attributes = props.attributes;
194 var setAttributes = props.setAttributes;
195 var a = attributes;
196
197 function updateWord(idx, key, val) {
198 var arr = a.words.slice();
199 arr[idx] = Object.assign({}, arr[idx]);
200 arr[idx][key] = val;
201 setAttributes({ words: arr });
202 }
203 function addWord() {
204 setAttributes({ words: a.words.concat([{ text: 'New Word', weight: 5, url: '', colorOverride: '' }]) });
205 }
206 function removeWord(idx) {
207 setAttributes({ words: a.words.filter(function (_, i) { return i !== idx; }) });
208 }
209
210 var colorModeOptions = [
211 { label: __('Cycle Palette', 'blockenberg'), value: 'palette' },
212 { label: __('Single Color', 'blockenberg'), value: 'single' },
213 ];
214 var sortOptions = [
215 { label: __('Random (shuffled)', 'blockenberg'), value: 'random' },
216 { label: __('Largest first', 'blockenberg'), value: 'desc' },
217 { label: __('Smallest first', 'blockenberg'), value: 'asc' },
218 { label: __('Alphabetical', 'blockenberg'), value: 'alpha' },
219 ];
220 var hoverOptions = [
221 { label: __('Scale up', 'blockenberg'), value: 'scale' },
222 { label: __('Bold', 'blockenberg'), value: 'bold' },
223 { label: __('None', 'blockenberg'), value: 'none' },
224 ];
225 var textAlignOptions = [
226 { label: __('Center', 'blockenberg'), value: 'center' },
227 { label: __('Left', 'blockenberg'), value: 'left' },
228 { label: __('Justify', 'blockenberg'), value: 'justify' },
229 ];
230 var weightOptions = [
231 { label: '400', value: 400 }, { label: '500', value: 500 },
232 { label: '600', value: 600 }, { label: '700', value: 700 }, { label: '800', value: 800 },
233 ];
234
235 var displayed = sortWords(a.words, a.sortOrder, a.shuffleSeed);
236 var blockProps = useBlockProps({ className: 'bkwc-wrap' });
237
238 return el(Fragment, null,
239 el(InspectorControls, null,
240
241 /* — Words — */
242 el(PanelBody, { title: __('Words', 'blockenberg') + ' (' + a.words.length + ')', initialOpen: true },
243 a.words.map(function (w, idx) {
244 return el(WordRow, { key: idx, word: w, idx: idx, onUpdate: updateWord, onRemove: removeWord });
245 }),
246 el(Button, { variant: 'primary', onClick: addWord, style: { width: '100%', marginTop: '8px' } },
247 __('+ Add Word', 'blockenberg'))
248 ),
249
250 /* — Layout — */
251 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
252 el(SelectControl, { label: __('Word Order', 'blockenberg'), value: a.sortOrder, options: sortOptions, onChange: function (v) { setAttributes({ sortOrder: v }); } }),
253 a.sortOrder === 'random' && el(RangeControl, { label: __('Shuffle Seed (change for different layout)', 'blockenberg'), value: a.shuffleSeed, min: 1, max: 999, onChange: function (v) { setAttributes({ shuffleSeed: v }); } }),
254 el(RangeControl, { label: __('Gap (px)', 'blockenberg'), value: a.gap, min: 4, max: 48, onChange: function (v) { setAttributes({ gap: v }); } }),
255 el(RangeControl, { label: __('Padding Vertical (px)', 'blockenberg'), value: a.paddingV, min: 0, max: 120, onChange: function (v) { setAttributes({ paddingV: v }); } }),
256 el(RangeControl, { label: __('Padding Horizontal (px)', 'blockenberg'), value: a.paddingH, min: 0, max: 120, onChange: function (v) { setAttributes({ paddingH: v }); } }),
257 el(SelectControl, { label: __('Text Align', 'blockenberg'), value: a.textAlign, options: textAlignOptions, onChange: function (v) { setAttributes({ textAlign: v }); } })
258 ),
259
260 /* — Word Tags — */
261 el(PanelBody, { title: __('Word Tags Style', 'blockenberg'), initialOpen: false },
262 el(ToggleControl, { label: __('Show Background per Word', 'blockenberg'), checked: a.showWordBg, onChange: function (v) { setAttributes({ showWordBg: v }); }, __nextHasNoMarginBottom: true }),
263 a.showWordBg && el(RangeControl, { label: __('Background Opacity (%)', 'blockenberg'), value: a.wordBgOpacity, min: 4, max: 40, onChange: function (v) { setAttributes({ wordBgOpacity: v }); } }),
264 a.showWordBg && el(RangeControl, { label: __('Border Radius (px)', 'blockenberg'), value: a.wordBorderRadius, min: 0, max: 40, onChange: function (v) { setAttributes({ wordBorderRadius: v }); } }),
265 a.showWordBg && el(RangeControl, { label: __('Tag Padding V (px)', 'blockenberg'), value: a.wordPaddingV, min: 0, max: 20, onChange: function (v) { setAttributes({ wordPaddingV: v }); } }),
266 a.showWordBg && el(RangeControl, { label: __('Tag Padding H (px)', 'blockenberg'), value: a.wordPaddingH, min: 0, max: 40, onChange: function (v) { setAttributes({ wordPaddingH: v }); } }),
267 el(SelectControl, { label: __('Hover Effect', 'blockenberg'), value: a.hoverEffect, options: hoverOptions, onChange: function (v) { setAttributes({ hoverEffect: v }); } })
268 ),
269
270 /* — Colors — */
271 el(PanelColorSettings, {
272 title: __('Colors', 'blockenberg'),
273 initialOpen: false,
274 colorSettings: [
275 { label: __('Container Background', 'blockenberg'), value: a.bgColor, onChange: function (v) { setAttributes({ bgColor: v || 'transparent' }); } },
276 { label: __('Single Color', 'blockenberg'), value: a.singleColor, onChange: function (v) { setAttributes({ singleColor: v || '#6c3fb5' }); } },
277 ]
278 }),
279
280
281 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
282 getTypoControl(__('Cloud', 'blockenberg'), 'cloudTypo', a, setAttributes),
283 el(RangeControl, { label: __('Min Font Size (px)', 'blockenberg'), value: a.minFontSize, min: 10, max: 32, onChange: function (v) { setAttributes({ minFontSize: v }); } }),
284 el(RangeControl, { label: __('Max Font Size (px)', 'blockenberg'), value: a.maxFontSize, min: 24, max: 100, onChange: function (v) { setAttributes({ maxFontSize: v }); } }),
285 el(SelectControl, { label: __('Font Weight', 'blockenberg'), value: a.fontWeight, options: weightOptions, onChange: function (v) { setAttributes({ fontWeight: parseInt(v, 10) }); } }),
286 el(RangeControl, { label: __('Letter Spacing (em ×10)', 'blockenberg'), value: Math.round(a.letterSpacing * 10), min: -2, max: 10, onChange: function (v) { setAttributes({ letterSpacing: v / 10 }); } }),
287 el(RangeControl, { label: __('Line Height (×10)', 'blockenberg'), value: Math.round(a.lineHeight * 10), min: 10, max: 24, onChange: function (v) { setAttributes({ lineHeight: v / 10 }); } })
288 ),
289 el(PanelBody, { title: __('Color Mode', 'blockenberg'), initialOpen: false },
290 el(SelectControl, { label: __('Color Mode', 'blockenberg'), value: a.colorMode, options: colorModeOptions, onChange: function (v) { setAttributes({ colorMode: v }); } }),
291 a.colorMode === 'palette' && el(BkbgMultiColorControl, {
292 label: __('Palette (comma-separated hex)', 'blockenberg'),
293 value: a.palette,
294 onChange: function (v) { setAttributes({ palette: v }); }
295 })
296 )
297 ),
298
299 /* ── Canvas ─────────────────────────────────────────────────── */
300 el('div', {
301 ...blockProps,
302 style: Object.assign({}, getTypoCssVars(a), { background: a.bgColor, padding: a.paddingV + 'px ' + a.paddingH + 'px', textAlign: a.textAlign })
303 },
304 el('div', { className: 'bkwc-cloud', style: { display: 'flex', flexWrap: 'wrap', justifyContent: a.textAlign === 'left' ? 'flex-start' : 'center', gap: a.gap + 'px', lineHeight: a.lineHeight } },
305 displayed.map(function (w, displayIdx) {
306 var origIdx = a.words.indexOf(w);
307 return renderWord(w, origIdx, displayIdx, a, true);
308 })
309 )
310 )
311 );
312 },
313
314 save: function (props) {
315 var a = props.attributes;
316 var blockProps = wp.blockEditor.useBlockProps.save({ className: 'bkwc-wrap' });
317 var displayed = sortWords(a.words, a.sortOrder, a.shuffleSeed);
318
319 return el('div', {
320 ...blockProps,
321 'data-hover': a.hoverEffect,
322 style: Object.assign({}, getTypoCssVars(a), { background: a.bgColor, padding: a.paddingV + 'px ' + a.paddingH + 'px', textAlign: a.textAlign })
323 },
324 el('div', {
325 className: 'bkwc-cloud',
326 style: { display: 'flex', flexWrap: 'wrap', justifyContent: a.textAlign === 'left' ? 'flex-start' : 'center', gap: a.gap + 'px', lineHeight: a.lineHeight }
327 },
328 displayed.map(function (w, displayIdx) {
329 var origIdx = a.words.indexOf(w);
330 return renderWord(w, origIdx, displayIdx, a, false);
331 })
332 )
333 );
334 }
335 });
336 }() );
337