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 / org-chart / index.js

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

358 lines 23.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 useState = wp.element.useState;
5 var __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var RichText = wp.blockEditor.RichText;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var MediaUpload = wp.blockEditor.MediaUpload;
11 var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
12 var PanelBody = wp.components.PanelBody;
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 ColorPicker = wp.components.ColorPicker;
19 var Popover = wp.components.Popover;
20
21 var _tc, _tv;
22 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
23 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
24
25 function makeId() { return 'oc' + Math.random().toString(36).substr(2, 5); }
26
27 function buildWrapStyle(a) {
28 return {
29 '--bkbg-oc-accent': a.accentColor,
30 '--bkbg-oc-card-bg': a.cardBg,
31 '--bkbg-oc-card-border': a.cardBorder,
32 '--bkbg-oc-name-color': a.nameColor,
33 '--bkbg-oc-role-color': a.roleColor,
34 '--bkbg-oc-dept-color': a.deptColor,
35 '--bkbg-oc-title-color': a.titleColor,
36 '--bkbg-oc-connector': a.connectorColor,
37 '--bkbg-oc-conn-w': a.connectorWidth + 'px',
38 '--bkbg-oc-avatar-size': a.avatarSize + 'px',
39 '--bkbg-oc-node-r': a.nodeRadius + 'px',
40 '--bkbg-oc-name-sz': a.nameSize + 'px',
41 '--bkbg-oc-role-sz': a.roleSize + 'px',
42 '--bkbg-oc-title-sz': a.titleSize + 'px',
43 paddingTop: a.paddingTop + 'px',
44 paddingBottom: a.paddingBottom + 'px',
45 backgroundColor: a.bgColor || undefined,
46 };
47 }
48
49 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
50 var isOpen = openKey === key;
51 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
52 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
53 el('div', { style: { position: 'relative', flexShrink: 0 } },
54 el('button', { type: 'button', onClick: function () { setOpenKey(isOpen ? null : key); }, style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#ccc' } }),
55 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
56 el('div', { style: { padding: '8px' } },
57 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
58 )
59 )
60 )
61 );
62 }
63
64 /* ── Build parent→children map ─────────────────────────────────── */
65 function buildTree(nodes) {
66 var map = {};
67 nodes.forEach(function (n) { map[n.id] = Object.assign({}, n, { children: [] }); });
68 var roots = [];
69 nodes.forEach(function (n) {
70 if (!n.parentId || !map[n.parentId]) {
71 roots.push(map[n.id]);
72 } else {
73 map[n.parentId].children.push(map[n.id]);
74 }
75 });
76 return roots;
77 }
78
79 /* ── Node card ─────────────────────────────────────────────────── */
80 function NodeCard(props) {
81 var node = props.node;
82 var a = props.a;
83 var initial = (node.name || '?').charAt(0).toUpperCase();
84
85 return el('div', { className: 'bkbg-oc-node-wrap' },
86 /* vertical line up (except root) */
87 props.hasParent && el('div', { className: 'bkbg-oc-conn-v-up', style: { width: a.connectorWidth + 'px', minHeight: '24px', background: a.connectorColor, margin: '0 auto' } }),
88
89 el('div', { className: 'bkbg-oc-card', style: {
90 background: a.cardBg,
91 border: '1.5px solid ' + (node.color || a.cardBorder),
92 borderRadius: a.nodeRadius + 'px',
93 padding: '14px',
94 display: 'flex',
95 flexDirection: 'column',
96 alignItems: 'center',
97 gap: '6px',
98 minWidth: '120px',
99 maxWidth: '160px',
100 boxShadow: '0 2px 8px rgba(0,0,0,0.07)',
101 }},
102 /* Avatar */
103 a.showAvatars && el('div', { style: {
104 width: a.avatarSize + 'px', height: a.avatarSize + 'px', borderRadius: '50%',
105 background: node.color || a.accentColor,
106 display: 'flex', alignItems: 'center', justifyContent: 'center',
107 overflow: 'hidden', flexShrink: 0,
108 border: '2px solid ' + (node.color || a.accentColor),
109 }},
110 node.avatarUrl ? el('img', { src: node.avatarUrl, alt: node.name, style: { width: '100%', height: '100%', objectFit: 'cover' } })
111 : el('span', { style: { fontSize: Math.round(a.avatarSize * 0.4) + 'px', fontWeight: 800, color: '#fff' } }, initial)
112 ),
113 el('div', { style: { textAlign: 'center' } },
114 el('p', { className: 'bkbg-oc-name', style: { color: a.nameColor } }, node.name),
115 el('p', { className: 'bkbg-oc-role', style: { margin: '2px 0 0', color: a.roleColor } }, node.role),
116 a.showDepartment && node.department && el('p', { className: 'bkbg-oc-dept', style: { margin: '2px 0 0', color: a.deptColor } }, node.department)
117 )
118 ),
119
120 /* children */
121 node.children && node.children.length > 0 && el(Fragment, null,
122 /* vertical line down */
123 el('div', { style: { width: a.connectorWidth + 'px', height: '24px', background: a.connectorColor, margin: '0 auto' } }),
124 /* horizontal bar if multiple children */
125 node.children.length > 1 && el('div', { style: {
126 height: a.connectorWidth + 'px',
127 background: a.connectorColor,
128 width: 'calc(100% - ' + (a.nodeRadius * 4) + 'px)',
129 margin: '0 auto',
130 }}),
131 /* children row */
132 el('div', { style: { display: 'flex', justifyContent: 'center', gap: '16px', alignItems: 'flex-start' } },
133 node.children.map(function (child) {
134 return el(NodeCard, { key: child.id, node: child, a: a, hasParent: true });
135 })
136 )
137 )
138 );
139 }
140
141 registerBlockType('blockenberg/org-chart', {
142 title: __('Org Chart', 'blockenberg'),
143 icon: 'networking',
144 category: 'bkbg-business',
145
146 edit: function (props) {
147 var a = props.attributes;
148 var setAttributes = props.setAttributes;
149
150 var openColorKeyState = useState(null);
151 var openColorKey = openColorKeyState[0];
152 var setOpenColorKey = openColorKeyState[1];
153
154 var editNodeState = useState(null);
155 var editNodeId = editNodeState[0];
156 var setEditNodeId = editNodeState[1];
157
158 var blockProps = useBlockProps((function () {
159 var tv = getTypoCssVars();
160 var s = {
161 '--bkbg-oc-accent': a.accentColor,
162 '--bkbg-oc-card-bg': a.cardBg,
163 '--bkbg-oc-card-border': a.cardBorder,
164 '--bkbg-oc-name-color': a.nameColor,
165 '--bkbg-oc-role-color': a.roleColor,
166 '--bkbg-oc-dept-color': a.deptColor,
167 '--bkbg-oc-title-color': a.titleColor,
168 '--bkbg-oc-connector': a.connectorColor,
169 '--bkbg-oc-conn-w': a.connectorWidth + 'px',
170 '--bkbg-oc-avatar-size': a.avatarSize + 'px',
171 '--bkbg-oc-node-r': a.nodeRadius + 'px',
172 paddingTop: a.paddingTop + 'px',
173 paddingBottom: a.paddingBottom + 'px',
174 backgroundColor: a.bgColor || undefined,
175 };
176 Object.assign(s, tv(a.titleTypo, '--bkbg-oc-tt-'));
177 Object.assign(s, tv(a.nameTypo, '--bkbg-oc-nm-'));
178 Object.assign(s, tv(a.roleTypo, '--bkbg-oc-rl-'));
179 return { className: 'bkbg-oc-wrapper', style: s };
180 })());
181
182 function cc(key, label, attrKey) {
183 return renderColorControl(key, label, a[attrKey], function (val) {
184 var upd = {}; upd[attrKey] = val; setAttributes(upd);
185 }, openColorKey, setOpenColorKey);
186 }
187
188 function updateNode(id, key, val) {
189 setAttributes({ nodes: a.nodes.map(function (n) {
190 if (n.id !== id) return n;
191 var u = Object.assign({}, n); u[key] = val; return u;
192 }) });
193 }
194
195 function removeNode(id) {
196 setAttributes({ nodes: a.nodes.filter(function (n) { return n.id !== id && n.parentId !== id; }) });
197 }
198
199 function addNode() {
200 var newId = makeId();
201 var rootId = a.nodes.length > 0 ? a.nodes[0].id : '';
202 setAttributes({ nodes: a.nodes.concat([{ id: newId, parentId: rootId, name: 'New Person', role: 'Role', department: '', avatarUrl: '', avatarId: 0, color: '' }]) });
203 setEditNodeId(newId);
204 }
205
206 var tree = buildTree(a.nodes);
207 var parentOptions = [{ label: '(root — no parent)', value: '' }].concat(a.nodes.map(function (n) {
208 return { label: n.name + ' [' + n.id + ']', value: n.id };
209 }));
210
211 return el(Fragment, null,
212 el(InspectorControls, null,
213 el(PanelBody, { title: __('Nodes', 'blockenberg'), initialOpen: true },
214 a.nodes.map(function (node) {
215 var isEdit = editNodeId === node.id;
216 var initial = (node.name || '?').charAt(0).toUpperCase();
217 return el('div', { key: node.id, style: { border: '1px solid #e0e0e0', borderRadius: '8px', marginBottom: '8px', overflow: 'hidden' } },
218 el('div', { onClick: function () { setEditNodeId(isEdit ? null : node.id); },
219 style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '8px 10px', cursor: 'pointer', background: isEdit ? '#f0e9ff' : '#fafafa' } },
220 el('div', { style: { width: '22px', height: '22px', borderRadius: '50%', background: node.color || a.accentColor, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 } },
221 el('span', { style: { fontSize: '11px', fontWeight: 800, color: '#fff' } }, initial)
222 ),
223 el('div', { style: { flex: 1, overflow: 'hidden' } },
224 el('p', { style: { margin: 0, fontSize: '12px', fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, node.name),
225 el('p', { style: { margin: 0, fontSize: '11px', color: '#888' } }, node.role)
226 )
227 ),
228 isEdit && el('div', { style: { padding: '10px', borderTop: '1px solid #e0e0e0' } },
229 el(TextControl, { label: __('Name', 'blockenberg'), value: node.name, onChange: function (v) { updateNode(node.id, 'name', v); } }),
230 el(TextControl, { label: __('Role / Job title', 'blockenberg'), value: node.role, onChange: function (v) { updateNode(node.id, 'role', v); } }),
231 el(TextControl, { label: __('Department', 'blockenberg'), value: node.department, onChange: function (v) { updateNode(node.id, 'department', v); } }),
232 el(SelectControl, { label: __('Reports to', 'blockenberg'), value: node.parentId, options: parentOptions.filter(function (o) { return o.value !== node.id; }), onChange: function (v) { updateNode(node.id, 'parentId', v); } }),
233 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } },
234 el('span', { style: { fontSize: '12px', fontWeight: 600 } }, __('Accent color:', 'blockenberg')),
235 el('input', { type: 'color', value: node.color || a.accentColor, onChange: function (e) { updateNode(node.id, 'color', e.target.value); }, style: { width: '32px', height: '28px', padding: 0, border: 'none', cursor: 'pointer', background: 'none' } })
236 ),
237 el(MediaUploadCheck, null,
238 el(MediaUpload, { onSelect: function (m) { updateNode(node.id, 'avatarUrl', m.url); updateNode(node.id, 'avatarId', m.id); }, allowedTypes: ['image'], value: node.avatarId,
239 render: function (mp) {
240 return el(Fragment, null,
241 node.avatarUrl && el('img', { src: node.avatarUrl, style: { width: '48px', height: '48px', borderRadius: '50%', objectFit: 'cover', display: 'block', marginBottom: '6px', border: '2px solid ' + (node.color || a.accentColor) } }),
242 el(Button, { variant: 'secondary', size: 'compact', onClick: mp.open }, node.avatarUrl ? __('Change photo', 'blockenberg') : __('Upload photo', 'blockenberg')),
243 node.avatarUrl && el(Button, { variant: 'tertiary', size: 'compact', isDestructive: true, onClick: function () { updateNode(node.id, 'avatarUrl', ''); updateNode(node.id, 'avatarId', 0); }, style: { marginLeft: '6px' } }, __('Remove', 'blockenberg'))
244 );
245 }
246 })
247 ),
248 el('div', { style: { marginTop: '10px' } },
249 el(Button, { isDestructive: true, variant: 'tertiary', size: 'compact', onClick: function () { removeNode(node.id); } }, __('Remove node', 'blockenberg'))
250 )
251 )
252 );
253 }),
254 el(Button, { variant: 'secondary', onClick: addNode, style: { width: '100%', justifyContent: 'center' } }, '+ ' + __('Add Node', 'blockenberg'))
255 ),
256 el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false },
257 el(ToggleControl, { label: __('Show title', 'blockenberg'), checked: a.showTitle, onChange: function (v) { setAttributes({ showTitle: v }); }, __nextHasNoMarginBottom: true }),
258 el(ToggleControl, { label: __('Show avatars', 'blockenberg'), checked: a.showAvatars, onChange: function (v) { setAttributes({ showAvatars: v }); }, __nextHasNoMarginBottom: true }),
259 el(ToggleControl, { label: __('Show department', 'blockenberg'), checked: a.showDepartment, onChange: function (v) { setAttributes({ showDepartment: v }); }, __nextHasNoMarginBottom: true }),
260 el(ToggleControl, { label: __('Collapse on click (frontend)', 'blockenberg'), checked: a.collapseOnClick, onChange: function (v) { setAttributes({ collapseOnClick: v }); }, __nextHasNoMarginBottom: true })
261 ),
262 el(PanelBody, { title: __('Connector Lines', 'blockenberg'), initialOpen: false },
263 el(SelectControl, { label: __('Style', 'blockenberg'), value: a.connectorStyle, options: [{ label: 'Elbow', value: 'elbow' }, { label: 'Straight', value: 'straight' }, { label: 'Curved', value: 'curved' }], onChange: function (v) { setAttributes({ connectorStyle: v }); } }),
264 el(RangeControl, { label: __('Line width (px)', 'blockenberg'), value: a.connectorWidth, min: 1, max: 6, onChange: function (v) { setAttributes({ connectorWidth: v }); } }),
265 renderColorControl('connectorColor', __('Connector color', 'blockenberg'), a.connectorColor, function (v) { setAttributes({ connectorColor: v }); }, openColorKey, setOpenColorKey)
266 ),
267 el(PanelBody, { title: __('Appearance', 'blockenberg'), initialOpen: false },
268 el(RangeControl, { label: __('Avatar size (px)', 'blockenberg'), value: a.avatarSize, min: 32, max: 96, onChange: function (v) { setAttributes({ avatarSize: v }); } }),
269 el(RangeControl, { label: __('Card radius (px)', 'blockenberg'), value: a.nodeRadius, min: 0, max: 32, onChange: function (v) { setAttributes({ nodeRadius: v }); } })
270 ),
271
272 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
273 el(getTypoControl(), { label: __('Title', 'blockenberg'), value: a.titleTypo, onChange: function (v) { setAttributes({ titleTypo: v }); } }),
274 el(getTypoControl(), { label: __('Name', 'blockenberg'), value: a.nameTypo, onChange: function (v) { setAttributes({ nameTypo: v }); } }),
275 el(getTypoControl(), { label: __('Role', 'blockenberg'), value: a.roleTypo, onChange: function (v) { setAttributes({ roleTypo: v }); } })
276 ),
277 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
278 cc('accentColor', __('Accent', 'blockenberg'), 'accentColor'),
279 cc('cardBg', __('Card background','blockenberg'), 'cardBg'),
280 cc('cardBorder', __('Card border', 'blockenberg'), 'cardBorder'),
281 cc('nameColor', __('Name color', 'blockenberg'), 'nameColor'),
282 cc('roleColor', __('Role color', 'blockenberg'), 'roleColor'),
283 cc('deptColor', __('Department color','blockenberg'), 'deptColor'),
284 cc('titleColor', __('Title color', 'blockenberg'), 'titleColor'),
285 cc('bgColor', __('Section bg', 'blockenberg'), 'bgColor')
286 ),
287 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
288 el(RangeControl, { label: __('Padding top', 'blockenberg'), value: a.paddingTop, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingTop: v }); } }),
289 el(RangeControl, { label: __('Padding bottom', 'blockenberg'), value: a.paddingBottom, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingBottom: v }); } })
290 )
291 ),
292
293 el('div', blockProps,
294 a.showTitle && el(RichText, { tagName: 'h2', className: 'bkbg-oc-title', value: a.title, onChange: function (v) { setAttributes({ title: v }); }, placeholder: __('Our Team', 'blockenberg'), style: { color: a.titleColor, textAlign: 'center', margin: '0 0 32px' } }),
295
296 el('div', { className: 'bkbg-oc-tree-root', style: { display: 'flex', flexDirection: 'column', alignItems: 'center', overflowX: 'auto' } },
297 tree.map(function (root) {
298 return el(NodeCard, { key: root.id, node: root, a: a, hasParent: false });
299 })
300 )
301 )
302 );
303 },
304
305 save: function (props) {
306 var a = props.attributes;
307 var tv = getTypoCssVars();
308 var s = {
309 '--bkbg-oc-accent': a.accentColor,
310 '--bkbg-oc-card-bg': a.cardBg,
311 '--bkbg-oc-card-border': a.cardBorder,
312 '--bkbg-oc-name-color': a.nameColor,
313 '--bkbg-oc-role-color': a.roleColor,
314 '--bkbg-oc-dept-color': a.deptColor,
315 '--bkbg-oc-title-color': a.titleColor,
316 '--bkbg-oc-connector': a.connectorColor,
317 '--bkbg-oc-conn-w': a.connectorWidth + 'px',
318 '--bkbg-oc-avatar-size': a.avatarSize + 'px',
319 '--bkbg-oc-node-r': a.nodeRadius + 'px',
320 paddingTop: a.paddingTop + 'px',
321 paddingBottom: a.paddingBottom + 'px',
322 backgroundColor: a.bgColor || undefined,
323 };
324 Object.assign(s, tv(a.titleTypo, '--bkbg-oc-tt-'));
325 Object.assign(s, tv(a.nameTypo, '--bkbg-oc-nm-'));
326 Object.assign(s, tv(a.roleTypo, '--bkbg-oc-rl-'));
327 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-oc-wrapper', style: s }),
328 a.showTitle && el(RichText.Content, { tagName: 'h2', className: 'bkbg-oc-title', value: a.title }),
329 el('div', {
330 className: 'bkbg-oc-tree',
331 'data-nodes': JSON.stringify(a.nodes),
332 'data-show-avatars': a.showAvatars ? '1' : '0',
333 'data-show-dept': a.showDepartment ? '1' : '0',
334 'data-collapse': a.collapseOnClick ? '1' : '0',
335 'data-connector-style': a.connectorStyle,
336 })
337 );
338 },
339
340 deprecated: [{
341 save: function (props) {
342 var a = props.attributes;
343 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-oc-wrapper', style: buildWrapStyle(a) }),
344 a.showTitle && el(RichText.Content, { tagName: 'h2', className: 'bkbg-oc-title', value: a.title }),
345 el('div', {
346 className: 'bkbg-oc-tree',
347 'data-nodes': JSON.stringify(a.nodes),
348 'data-show-avatars': a.showAvatars ? '1' : '0',
349 'data-show-dept': a.showDepartment ? '1' : '0',
350 'data-collapse': a.collapseOnClick ? '1' : '0',
351 'data-connector-style': a.connectorStyle,
352 })
353 );
354 }
355 }]
356 });
357 }() );
358