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 / content-outline / index.js

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

370 lines 22.2 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 registerBlockType = wp.blocks.registerBlockType;
6 var InspectorControls = wp.blockEditor.InspectorControls;
7 var useBlockProps = wp.blockEditor.useBlockProps;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var PanelBody = wp.components.PanelBody;
10 var Button = wp.components.Button;
11 var TextControl = wp.components.TextControl;
12 var TextareaControl = wp.components.TextareaControl;
13 var ToggleControl = wp.components.ToggleControl;
14 var RangeControl = wp.components.RangeControl;
15 var SelectControl = wp.components.SelectControl;
16
17 var _TC, _tv;
18 Object.defineProperty(window, '_bkbgCOgetTC', { get: function () { return _TC || (_TC = window.bkbgTypographyControl); } });
19 Object.defineProperty(window, '_bkbgCOgetTV', { get: function () { return _tv || (_tv = window.bkbgTypoCssVars); } });
20
21 var STATUS_OPTIONS = [
22 { label: __('— None', 'blockenberg'), value: 'none' },
23 { label: __('✓ Done', 'blockenberg'), value: 'done' },
24 { label: __('⟳ In Progress', 'blockenberg'), value: 'in-progress' },
25 { label: __('○ Planned', 'blockenberg'), value: 'planned' }
26 ];
27
28 function updateSection(sections, sIdx, field, val) {
29 return sections.map(function (s, i) {
30 if (i !== sIdx) return s;
31 var u = {}; u[field] = val;
32 return Object.assign({}, s, u);
33 });
34 }
35
36 function updatePoint(sections, sIdx, pIdx, field, val) {
37 return sections.map(function (s, i) {
38 if (i !== sIdx) return s;
39 var newPts = s.points.map(function (p, j) {
40 if (j !== pIdx) return p;
41 var u = {}; u[field] = val;
42 return Object.assign({}, p, u);
43 });
44 return Object.assign({}, s, { points: newPts });
45 });
46 }
47
48 function addPoint(sections, sIdx) {
49 return sections.map(function (s, i) {
50 if (i !== sIdx) return s;
51 return Object.assign({}, s, { points: s.points.concat([{ text: 'New sub-point', status: 'planned' }]) });
52 });
53 }
54
55 function removePoint(sections, sIdx, pIdx) {
56 return sections.map(function (s, i) {
57 if (i !== sIdx) return s;
58 return Object.assign({}, s, { points: s.points.filter(function (_, j) { return j !== pIdx; }) });
59 });
60 }
61
62 function getStatusStyle(status, a) {
63 if (status === 'done') return { background: a.doneBg, color: a.doneColor };
64 if (status === 'in-progress') return { background: a.inProgressBg, color: a.inProgressColor };
65 if (status === 'planned') return { background: a.plannedBg, color: a.plannedColor };
66 return null;
67 }
68
69 function getStatusLabel(status) {
70 if (status === 'done') return '✓ Done';
71 if (status === 'in-progress') return '⟳ In Progress';
72 if (status === 'planned') return '○ Planned';
73 return '';
74 }
75
76 registerBlockType('blockenberg/content-outline', {
77 title: __('Content Outline', 'blockenberg'),
78 icon: 'list-view',
79 category: 'bkbg-blog',
80 description: __('Structured article outline with numbered sections, sub-points, and status badges.', 'blockenberg'),
81
82 edit: function (props) {
83 var a = props.attributes;
84 var set = props.setAttributes;
85 var TC = window._bkbgCOgetTC;
86 var tv = window._bkbgCOgetTV || function () { return {}; };
87
88 var styleOptions = [
89 { label: __('Card', 'blockenberg'), value: 'card' },
90 { label: __('List', 'blockenberg'), value: 'list' },
91 { label: __('Compact', 'blockenberg'), value: 'compact' }
92 ];
93
94 var inspector = el(InspectorControls, {},
95 // Header
96 el(PanelBody, { title: __('Title & Intro', 'blockenberg'), initialOpen: true },
97 el(ToggleControl, {
98 label: __('Show title', 'blockenberg'), checked: a.showTitle, __nextHasNoMarginBottom: true,
99 onChange: function (v) { set({ showTitle: v }); }
100 }),
101 a.showTitle && el(TextControl, {
102 label: __('Title', 'blockenberg'), value: a.outlineTitle, __nextHasNoMarginBottom: true,
103 onChange: function (v) { set({ outlineTitle: v }); }
104 }),
105 el(ToggleControl, {
106 label: __('Show intro', 'blockenberg'), checked: a.showIntro, __nextHasNoMarginBottom: true,
107 onChange: function (v) { set({ showIntro: v }); }
108 }),
109 a.showIntro && el(TextareaControl, {
110 label: __('Intro text', 'blockenberg'), value: a.intro, rows: 2, __nextHasNoMarginBottom: true,
111 onChange: function (v) { set({ intro: v }); }
112 })
113 ),
114
115 // Sections
116 el(PanelBody, { title: __('Sections', 'blockenberg'), initialOpen: false },
117 a.sections.map(function (section, sIdx) {
118 return el('div', { key: 's-' + sIdx, style: { marginBottom: '16px', border: '1px solid #e2e8f0', borderRadius: '8px', overflow: 'hidden' } },
119 el('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', background: '#f8fafc', gap: '6px' } },
120 el('strong', { style: { color: '#6366f1', flexShrink: 0, fontSize: '13px' } }, (sIdx + 1) + '.'),
121 el(TextControl, {
122 label: '', value: section.title, __nextHasNoMarginBottom: true,
123 placeholder: __('Section title', 'blockenberg'),
124 onChange: function (v) { set({ sections: updateSection(a.sections, sIdx, 'title', v) }); }
125 }),
126 el('div', { style: { display: 'flex', gap: '4px' } },
127 el(Button, {
128 isSmall: true, icon: 'plus-alt2',
129 onClick: function () { set({ sections: addPoint(a.sections, sIdx) }); }
130 }),
131 el(Button, {
132 isSmall: true, isDestructive: true, icon: 'trash',
133 disabled: a.sections.length <= 1,
134 onClick: function () { set({ sections: a.sections.filter(function (_, i) { return i !== sIdx; }) }); }
135 })
136 )
137 ),
138 el('div', { style: { padding: '8px 12px', borderBottom: '1px solid #f1f5f9' } },
139 el(TextControl, {
140 label: __('Description', 'blockenberg'), value: section.description || '', __nextHasNoMarginBottom: true,
141 onChange: function (v) { set({ sections: updateSection(a.sections, sIdx, 'description', v) }); }
142 }),
143 el(RangeControl, {
144 label: __('Estimated read time (min)', 'blockenberg'),
145 value: section.readTime || 0, min: 0, max: 60, __nextHasNoMarginBottom: true,
146 onChange: function (v) { set({ sections: updateSection(a.sections, sIdx, 'readTime', v) }); }
147 })
148 ),
149 el('div', { style: { padding: '8px 12px', display: 'flex', flexDirection: 'column', gap: '6px' } },
150 section.points.map(function (point, pIdx) {
151 return el('div', { key: 'p-' + pIdx, style: { display: 'flex', gap: '6px', alignItems: 'center' } },
152 el(SelectControl, {
153 label: '', value: point.status || 'none', options: STATUS_OPTIONS, __nextHasNoMarginBottom: true,
154 onChange: function (v) { set({ sections: updatePoint(a.sections, sIdx, pIdx, 'status', v) }); }
155 }),
156 el(TextControl, {
157 label: '', value: point.text, __nextHasNoMarginBottom: true,
158 placeholder: __('Sub-point', 'blockenberg'),
159 onChange: function (v) { set({ sections: updatePoint(a.sections, sIdx, pIdx, 'text', v) }); }
160 }),
161 el(Button, {
162 isSmall: true, isDestructive: true, icon: 'trash',
163 disabled: section.points.length <= 1,
164 onClick: function () { set({ sections: removePoint(a.sections, sIdx, pIdx) }); }
165 })
166 );
167 })
168 )
169 );
170 }),
171 el(Button, {
172 variant: 'secondary', isSmall: true, icon: 'plus-alt2',
173 onClick: function () {
174 set({ sections: a.sections.concat([{ title: 'New Section', description: '', readTime: 3, points: [{ text: 'Sub-point', status: 'planned' }] }]) });
175 }
176 }, __('Add Section', 'blockenberg'))
177 ),
178
179 // Display
180 el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false },
181 el(SelectControl, {
182 label: __('Style', 'blockenberg'), value: a.style, options: styleOptions, __nextHasNoMarginBottom: true,
183 onChange: function (v) { set({ style: v }); }
184 }),
185 el(ToggleControl, {
186 label: __('Show section numbers', 'blockenberg'), checked: a.showNumbers, __nextHasNoMarginBottom: true,
187 onChange: function (v) { set({ showNumbers: v }); }
188 }),
189 el(ToggleControl, {
190 label: __('Show status badges', 'blockenberg'), checked: a.showStatus, __nextHasNoMarginBottom: true,
191 onChange: function (v) { set({ showStatus: v }); }
192 }),
193 el(ToggleControl, {
194 label: __('Show read time', 'blockenberg'), checked: a.showReadTime, __nextHasNoMarginBottom: true,
195 onChange: function (v) { set({ showReadTime: v }); }
196 }),
197 el(ToggleControl, {
198 label: __('Show section descriptions', 'blockenberg'), checked: a.showDescription, __nextHasNoMarginBottom: true,
199 onChange: function (v) { set({ showDescription: v }); }
200 }),
201 el(ToggleControl, {
202 label: __('Show sub-points', 'blockenberg'), checked: a.showPoints, __nextHasNoMarginBottom: true,
203 onChange: function (v) { set({ showPoints: v }); }
204 })
205 ),
206
207 // Spacing
208 el(PanelBody, { title: __('Spacing & Shape', 'blockenberg'), initialOpen: false },
209 el(RangeControl, {
210 label: __('Border radius', 'blockenberg'), value: a.borderRadius, min: 0, max: 32, __nextHasNoMarginBottom: true,
211 onChange: function (v) { set({ borderRadius: v }); }
212 }),
213 el(RangeControl, {
214 label: __('Gap between sections', 'blockenberg'), value: a.gap, min: 4, max: 48, __nextHasNoMarginBottom: true,
215 onChange: function (v) { set({ gap: v }); }
216 }),
217 ),
218
219 // Colors
220
221 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
222 TC && el(TC, { label: __('Outline Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { set({ typoTitle: v }); } }),
223 TC && el(TC, { label: __('Section Title', 'blockenberg'), value: a.typoSection, onChange: function (v) { set({ typoSection: v }); } }),
224 el(RangeControl, {
225 label: __('Point font size', 'blockenberg'), value: a.fontSize, min: 11, max: 20, __nextHasNoMarginBottom: true,
226 onChange: function (v) { set({ fontSize: v }); }
227 })
228 ),
229 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
230 el(PanelColorSettings, {
231 title: __('Layout Colors', 'blockenberg'), initialOpen: false,
232 colorSettings: [
233 { value: a.bgColor, label: __('Background', 'blockenberg'), onChange: function (c) { set({ bgColor: c }); } },
234 { value: a.cardBg, label: __('Card bg', 'blockenberg'), onChange: function (c) { set({ cardBg: c }); } },
235 { value: a.borderColor, label: __('Border', 'blockenberg'), onChange: function (c) { set({ borderColor: c }); } },
236 { value: a.accentColor, label: __('Accent', 'blockenberg'), onChange: function (c) { set({ accentColor: c }); } },
237 { value: a.numberBg, label: __('Number bg', 'blockenberg'), onChange: function (c) { set({ numberBg: c }); } },
238 { value: a.numberColor, label: __('Number text', 'blockenberg'), onChange: function (c) { set({ numberColor: c }); } }
239 ]
240 }),
241 el(PanelColorSettings, {
242 title: __('Text Colors', 'blockenberg'), initialOpen: false,
243 colorSettings: [
244 { value: a.titleColor, label: __('Outline title', 'blockenberg'), onChange: function (c) { set({ titleColor: c }); } },
245 { value: a.sectionTitleColor,label: __('Section title', 'blockenberg'), onChange: function (c) { set({ sectionTitleColor: c }); } },
246 { value: a.descriptionColor, label: __('Description', 'blockenberg'), onChange: function (c) { set({ descriptionColor: c }); } },
247 { value: a.pointColor, label: __('Point text', 'blockenberg'), onChange: function (c) { set({ pointColor: c }); } },
248 { value: a.introColor, label: __('Intro', 'blockenberg'), onChange: function (c) { set({ introColor: c }); } }
249 ]
250 }),
251 el(PanelColorSettings, {
252 title: __('Status Badge Colors', 'blockenberg'), initialOpen: false,
253 colorSettings: [
254 { value: a.doneBg, label: __('Done background', 'blockenberg'), onChange: function (c) { set({ doneBg: c }); } },
255 { value: a.doneColor, label: __('Done text', 'blockenberg'), onChange: function (c) { set({ doneColor: c }); } },
256 { value: a.inProgressBg, label: __('In Progress background', 'blockenberg'), onChange: function (c) { set({ inProgressBg: c }); } },
257 { value: a.inProgressColor,label: __('In Progress text', 'blockenberg'), onChange: function (c) { set({ inProgressColor: c }); } },
258 { value: a.plannedBg, label: __('Planned background', 'blockenberg'), onChange: function (c) { set({ plannedBg: c }); } },
259 { value: a.plannedColor, label: __('Planned text', 'blockenberg'), onChange: function (c) { set({ plannedColor: c }); } }
260 ]
261 })
262 )
263 );
264
265 // ── Preview ──────────────────────────────────────────────
266 var isCard = a.style === 'card';
267 var isList = a.style === 'list';
268
269 var sectionsPreview = a.sections.map(function (section, sIdx) {
270 var cardStyle = {
271 background: a.cardBg,
272 border: '1px solid ' + a.borderColor,
273 borderRadius: a.borderRadius + 'px',
274 overflow: 'hidden'
275 };
276 if (isList || a.style === 'compact') {
277 cardStyle.border = 'none';
278 cardStyle.borderBottom = '1px solid ' + a.borderColor;
279 cardStyle.borderRadius = '0';
280 cardStyle.background = 'transparent';
281 }
282
283 return el('div', { key: 'sec-' + sIdx, className: 'bkbg-co-section', style: cardStyle },
284 el('div', {
285 style: {
286 display: 'flex', alignItems: 'center', gap: '12px',
287 padding: isCard ? '16px 20px' : '12px 0',
288 borderBottom: a.showPoints && section.points.length ? '1px solid ' + a.borderColor : 'none'
289 }
290 },
291 a.showNumbers && el('span', {
292 style: {
293 background: a.numberBg, color: a.numberColor, width: '28px', height: '28px',
294 borderRadius: '50%', display: 'flex', alignItems: 'center',
295 justifyContent: 'center', fontWeight: 800, fontSize: '12px', flexShrink: 0
296 }
297 }, sIdx + 1),
298 el('div', { style: { flex: 1, minWidth: 0 } },
299 el('div', { className: 'bkbg-co-section-title', style: { color: a.sectionTitleColor } }, section.title),
300 a.showDescription && section.description && el('div', {
301 style: { fontSize: '12px', color: a.descriptionColor, marginTop: '2px' }
302 }, section.description)
303 ),
304 a.showReadTime && section.readTime > 0 && el('span', {
305 style: { fontSize: '11px', color: a.descriptionColor, flexShrink: 0, fontWeight: 500 }
306 }, section.readTime + ' min')
307 ),
308 a.showPoints && section.points.length > 0 && el('ul', {
309 style: { margin: 0, padding: isCard ? '12px 20px 16px' : '8px 0 12px 28px', listStyle: 'none' }
310 },
311 section.points.map(function (point, pIdx) {
312 var statusStyle = a.showStatus && getStatusStyle(point.status, a);
313 var statusLabel = getStatusLabel(point.status);
314 return el('li', {
315 key: 'pt-' + pIdx,
316 style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px', fontSize: a.fontSize + 'px' }
317 },
318 el('span', { style: { color: a.accentColor, fontSize: '10px', flexShrink: 0 } }, ''),
319 el('span', { style: { color: a.pointColor, flex: 1 } }, point.text),
320 a.showStatus && statusStyle && el('span', {
321 style: Object.assign({
322 fontSize: '10px', fontWeight: 700, padding: '2px 7px',
323 borderRadius: '100px', flexShrink: 0, whiteSpace: 'nowrap'
324 }, statusStyle)
325 }, statusLabel)
326 );
327 })
328 )
329 );
330 });
331
332 var blockProps = useBlockProps({ className: 'bkbg-editor-wrap', 'data-block-label': 'Content Outline' });
333
334 return el('div', blockProps,
335 inspector,
336 el('div', {
337 className: 'bkbg-co-block',
338 style: Object.assign({ background: a.bgColor, borderRadius: a.borderRadius + 'px', padding: '4px 0' }, tv(a.typoTitle, '--bkco-title-'), tv(a.typoSection, '--bkco-sec-'))
339 },
340 (a.showTitle || a.showIntro) && el('div', {
341 style: { padding: '20px 0 16px', borderBottom: '1px solid ' + a.borderColor, marginBottom: a.gap + 'px' }
342 },
343 a.showTitle && el('h3', {
344 className: 'bkbg-co-title', style: { margin: '0 0 6px', color: a.titleColor }
345 }, a.outlineTitle),
346 a.showIntro && el('p', {
347 style: { margin: 0, fontSize: '14px', color: a.introColor, lineHeight: '1.5' }
348 }, a.intro)
349 ),
350 el('div', {
351 style: {
352 display: 'flex', flexDirection: 'column', gap: a.gap + 'px'
353 }
354 }, sectionsPreview)
355 )
356 );
357 },
358
359 save: function (props) {
360 var useBlockProps = wp.blockEditor.useBlockProps;
361 return el('div', useBlockProps.save(),
362 el('div', {
363 className: 'bkbg-content-outline-app',
364 'data-opts': JSON.stringify(props.attributes)
365 })
366 );
367 }
368 });
369 }() );
370