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

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

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