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 / article-series / index.js

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

333 lines 17.9 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 registerBlockType = wp.blocks.registerBlockType;
6 var useBlockProps = wp.blockEditor.useBlockProps;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var PanelBody = wp.components.PanelBody;
10 var TextControl = wp.components.TextControl;
11 var TextareaControl = wp.components.TextareaControl;
12 var ToggleControl = wp.components.ToggleControl;
13 var RangeControl = wp.components.RangeControl;
14 var SelectControl = wp.components.SelectControl;
15 var Button = wp.components.Button;
16 var __ = wp.i18n.__;
17
18 /* ── Typography lazy getters ───────────────────────────────────────── */
19 var _Typo, _tv;
20 Object.defineProperty(window, '__bkbgAserTypoReady', { get: function () {
21 if (!_Typo) _Typo = window.bkbgTypographyControl;
22 if (!_tv) _tv = window.bkbgTypoCssVars;
23 return !!(_Typo && _tv);
24 }});
25 function getTypoCssVars() { window.__bkbgAserTypoReady; return _tv; }
26 function getTypoComponent() { window.__bkbgAserTypoReady; return _Typo; }
27
28 function updatePart(parts, idx, field, val) {
29 return parts.map(function (p, i) {
30 if (i !== idx) return p;
31 var u = {}; u[field] = val;
32 return Object.assign({}, p, u);
33 });
34 }
35
36 // ── SeriesPreview ───────────────────────────────────────────────────────────
37 function SeriesPreview(props) {
38 var a = props.attributes;
39 var setAttributes = props.setAttributes;
40 var total = a.parts.length;
41 var pct = total > 0 ? Math.round((a.currentPart / total) * 100) : 0;
42
43 return el('div', {
44 className: 'bkbg-aser-block',
45 style: { background: a.bgColor, borderColor: a.borderColor, borderRadius: a.borderRadius + 'px' }
46 },
47 // Header
48 el('div', { className: 'bkbg-aser-header' },
49 el('div', { className: 'bkbg-aser-series-label', style: { color: a.accentColor } }, 'Article Series'),
50 el('h3', {
51 className: 'bkbg-aser-title',
52 style: { color: a.titleColor },
53 contentEditable: true,
54 suppressContentEditableWarning: true,
55 onBlur: function (e) { setAttributes({ seriesTitle: e.target.textContent }); }
56 }, a.seriesTitle),
57 a.showSeriesDescription && el('p', {
58 className: 'bkbg-aser-desc',
59 style: { color: a.descColor },
60 contentEditable: true,
61 suppressContentEditableWarning: true,
62 onBlur: function (e) { setAttributes({ seriesDescription: e.target.textContent }); }
63 }, a.seriesDescription)
64 ),
65 // Progress bar
66 a.showProgress && el('div', { className: 'bkbg-aser-progress-wrap' },
67 el('div', { className: 'bkbg-aser-progress-bar', style: { background: a.progressBg } },
68 el('div', {
69 className: 'bkbg-aser-progress-fill',
70 style: { width: pct + '%', background: a.progressColor }
71 })
72 ),
73 el('span', { className: 'bkbg-aser-progress-label', style: { color: a.descColor } },
74 'Part ' + a.currentPart + ' of ' + total
75 )
76 ),
77 // Parts list
78 el('ol', { className: 'bkbg-aser-list bkbg-aser-layout-' + a.layout },
79 a.parts.map(function (part, i) {
80 var isCurrent = i + 1 === a.currentPart;
81 return el('li', {
82 key: i,
83 className: 'bkbg-aser-item' + (isCurrent ? ' is-current' : ''),
84 style: {
85 background: isCurrent ? a.currentBg : a.itemBg,
86 color: isCurrent ? a.currentColor : a.itemColor,
87 borderColor: isCurrent ? a.currentBorderColor : a.itemBorderColor,
88 borderRadius: Math.max(0, a.borderRadius - 4) + 'px'
89 }
90 },
91 a.showNumbers && el('span', {
92 className: 'bkbg-aser-num',
93 style: {
94 background: isCurrent ? a.currentNumberBg : a.numberBg,
95 color: isCurrent ? a.currentNumberColor : a.numberColor
96 }
97 }, String(i + 1)),
98 el('div', { className: 'bkbg-aser-item-body' },
99 el('span', {
100 className: 'bkbg-aser-part-title',
101 style: { color: isCurrent ? a.currentColor : a.itemColor }
102 }, part.title),
103 a.showExcerpt && part.excerpt && el('span', {
104 className: 'bkbg-aser-excerpt',
105 style: { color: isCurrent ? a.currentColor : a.excerptColor }
106 }, part.excerpt)
107 ),
108 isCurrent && el('span', { className: 'bkbg-aser-current-badge', style: { background: a.accentColor } }, 'Current')
109 );
110 })
111 )
112 );
113 }
114
115 // ── PartEditor ──────────────────────────────────────────────────────────────
116 function PartEditor(props) {
117 var parts = props.parts;
118 var currentPart = props.currentPart;
119 var setAttributes = props.setAttributes;
120 var accentColor = props.accentColor;
121
122 var openState = useState(parts.map(function () { return false; }));
123 var open = openState[0];
124 var setOpen = openState[1];
125
126 function toggle(i) {
127 setOpen(open.map(function (v, j) { return j === i ? !v : v; }));
128 }
129
130 function remove(i) {
131 var newParts = parts.filter(function (_, j) { return j !== i; });
132 var newCurrent = currentPart > i + 1 ? currentPart - 1 : currentPart;
133 setAttributes({ parts: newParts, currentPart: Math.min(newCurrent, newParts.length) });
134 setOpen(open.filter(function (_, j) { return j !== i; }));
135 }
136
137 function add() {
138 setAttributes({ parts: parts.concat([{ title: 'Part ' + (parts.length + 1) + ': New Article', url: '#', excerpt: 'Short description of this article.' }]) });
139 setOpen(open.concat([true]));
140 }
141
142 return el('div', null,
143 parts.map(function (part, i) {
144 var isCurrent = i + 1 === currentPart;
145 return el('div', { key: i, className: 'bkbg-aser-part-ed' },
146 el('div', {
147 className: 'bkbg-aser-part-ed-header',
148 onClick: function () { toggle(i); },
149 style: { borderLeftColor: isCurrent ? '#3b82f6' : accentColor }
150 },
151 el('span', null, (i + 1) + '. ' + part.title.slice(0, 35) + (part.title.length > 35 ? '' : '')),
152 el('span', null, open[i] ? '' : '')
153 ),
154 open[i] && el('div', { className: 'bkbg-aser-part-ed-fields' },
155 el(TextControl, {
156 label: __('Title'),
157 value: part.title,
158 __nextHasNoMarginBottom: true,
159 onChange: function (v) { setAttributes({ parts: updatePart(parts, i, 'title', v) }); }
160 }),
161 el(TextControl, {
162 label: __('URL'),
163 value: part.url,
164 type: 'url',
165 __nextHasNoMarginBottom: true,
166 onChange: function (v) { setAttributes({ parts: updatePart(parts, i, 'url', v) }); }
167 }),
168 el(TextareaControl, {
169 label: __('Excerpt (optional)'),
170 value: part.excerpt,
171 rows: 2,
172 __nextHasNoMarginBottom: true,
173 onChange: function (v) { setAttributes({ parts: updatePart(parts, i, 'excerpt', v) }); }
174 }),
175 el('div', { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
176 el(Button, {
177 variant: isCurrent ? 'primary' : 'secondary',
178 isSmall: true,
179 onClick: function () { setAttributes({ currentPart: i + 1 }); }
180 }, isCurrent ? '�
181 Current article' : 'Set as current'),
182 parts.length > 1 && el(Button, {
183 variant: 'link',
184 isDestructive: true,
185 isSmall: true,
186 onClick: function () { remove(i); }
187 }, __('Remove'))
188 )
189 )
190 );
191 }),
192 el(Button, {
193 variant: 'secondary',
194 onClick: add,
195 style: { marginTop: '8px' }
196 }, __('+ Add Part'))
197 );
198 }
199
200 // ── register ────────────────────────────────────────────────────────────────
201 registerBlockType('blockenberg/article-series', {
202 edit: function (props) {
203 var a = props.attributes;
204 var setAttributes = props.setAttributes;
205
206 return el(Fragment, null,
207 el(InspectorControls, null,
208 el(PanelBody, { title: __('Series Parts'), initialOpen: true },
209 el(PartEditor, {
210 parts: a.parts,
211 currentPart: a.currentPart,
212 setAttributes: setAttributes,
213 accentColor: a.accentColor
214 })
215 ),
216 el(PanelBody, { title: __('Display'), initialOpen: false },
217 el(SelectControl, {
218 label: __('Layout'),
219 value: a.layout,
220 __nextHasNoMarginBottom: true,
221 options: [
222 { value: 'list', label: 'List' },
223 { value: 'compact', label: 'Compact' },
224 { value: 'grid', label: 'Grid (2-col)' }
225 ],
226 onChange: function (v) { setAttributes({ layout: v }); }
227 }),
228 el(ToggleControl, {
229 label: __('Show progress bar'),
230 checked: a.showProgress,
231 __nextHasNoMarginBottom: true,
232 onChange: function (v) { setAttributes({ showProgress: v }); }
233 }),
234 el(ToggleControl, {
235 label: __('Show series description'),
236 checked: a.showSeriesDescription,
237 __nextHasNoMarginBottom: true,
238 onChange: function (v) { setAttributes({ showSeriesDescription: v }); }
239 }),
240 el(ToggleControl, {
241 label: __('Show part numbers'),
242 checked: a.showNumbers,
243 __nextHasNoMarginBottom: true,
244 onChange: function (v) { setAttributes({ showNumbers: v }); }
245 }),
246 el(ToggleControl, {
247 label: __('Show excerpts'),
248 checked: a.showExcerpt,
249 __nextHasNoMarginBottom: true,
250 onChange: function (v) { setAttributes({ showExcerpt: v }); }
251 }),
252 el(RangeControl, {
253 label: __('Border radius (px)'),
254 value: a.borderRadius,
255 min: 0, max: 24,
256 __nextHasNoMarginBottom: true,
257 onChange: function (v) { setAttributes({ borderRadius: v }); }
258 })
259 ),
260 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
261 getTypoComponent() && el(getTypoComponent(), {
262 label: __('Title Typography', 'blockenberg'),
263 value: a.titleTypo || {},
264 onChange: function (v) { setAttributes({ titleTypo: v }); }
265 }),
266 getTypoComponent() && el(getTypoComponent(), {
267 label: __('Description Typography', 'blockenberg'),
268 value: a.descTypo || {},
269 onChange: function (v) { setAttributes({ descTypo: v }); }
270 }),
271 getTypoComponent() && el(getTypoComponent(), {
272 label: __('Part Title Typography', 'blockenberg'),
273 value: a.partTitleTypo || {},
274 onChange: function (v) { setAttributes({ partTitleTypo: v }); }
275 }),
276 getTypoComponent() && el(getTypoComponent(), {
277 label: __('Excerpt Typography', 'blockenberg'),
278 value: a.excerptTypo || {},
279 onChange: function (v) { setAttributes({ excerptTypo: v }); }
280 })
281 ),
282 el(PanelColorSettings, {
283 title: __('Colors'),
284 initialOpen: false,
285 colorSettings: [
286 { label: __('Background'), value: a.bgColor, onChange: function (v) { setAttributes({ bgColor: v || '#f8fafc' }); } },
287 { label: __('Border'), value: a.borderColor, onChange: function (v) { setAttributes({ borderColor: v || '#e2e8f0' }); } },
288 { label: __('Accent'), value: a.accentColor, onChange: function (v) { setAttributes({ accentColor: v || '#3b82f6' }); } },
289 { label: __('Title'), value: a.titleColor, onChange: function (v) { setAttributes({ titleColor: v || '#0f172a' }); } },
290 { label: __('Description'), value: a.descColor, onChange: function (v) { setAttributes({ descColor: v || '#64748b' }); } },
291 { label: __('Item background'), value: a.itemBg, onChange: function (v) { setAttributes({ itemBg: v || '#ffffff' }); } },
292 { label: __('Item text'), value: a.itemColor, onChange: function (v) { setAttributes({ itemColor: v || '#334155' }); } },
293 { label: __('Current item bg'), value: a.currentBg, onChange: function (v) { setAttributes({ currentBg: v || '#eff6ff' }); } },
294 { label: __('Current item text'), value: a.currentColor, onChange: function (v) { setAttributes({ currentColor: v || '#1d4ed8' }); } },
295 { label: __('Current item border'), value: a.currentBorderColor, onChange: function (v) { setAttributes({ currentBorderColor: v || '#3b82f6' }); } },
296 { label: __('Progress bar fill'), value: a.progressColor, onChange: function (v) { setAttributes({ progressColor: v || '#3b82f6' }); } },
297 { label: __('Number bg'), value: a.numberBg, onChange: function (v) { setAttributes({ numberBg: v || '#e2e8f0' }); } },
298 { label: __('Current number bg'), value: a.currentNumberBg, onChange: function (v) { setAttributes({ currentNumberBg: v || '#3b82f6' }); } }
299 ]
300 })
301 ),
302 el('div', useBlockProps((function () {
303 var _tv = getTypoCssVars();
304 var s = {
305 '--bkbg-aser-title-sz': (a.titleFontSize || 20) + 'px',
306 '--bkbg-aser-desc-sz': (a.descFontSize || 14) + 'px',
307 '--bkbg-aser-pt-sz': (a.partTitleFontSize || 14) + 'px',
308 '--bkbg-aser-exc-sz': (a.excerptFontSize || 12) + 'px'
309 };
310 if (_tv) {
311 Object.assign(s, _tv(a.titleTypo || {}, '--bkbg-aser-title-'));
312 Object.assign(s, _tv(a.descTypo || {}, '--bkbg-aser-desc-'));
313 Object.assign(s, _tv(a.partTitleTypo || {}, '--bkbg-aser-pt-'));
314 Object.assign(s, _tv(a.excerptTypo || {}, '--bkbg-aser-exc-'));
315 }
316 return { style: s };
317 })()),
318 el(SeriesPreview, { attributes: a, setAttributes: setAttributes })
319 )
320 );
321 },
322
323 save: function (props) {
324 return el('div', useBlockProps.save(),
325 el('div', {
326 className: 'bkbg-aser-app',
327 'data-opts': JSON.stringify(props.attributes)
328 })
329 );
330 }
331 });
332 }() );
333