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 / course-curriculum / index.js

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

401 lines 27.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 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 PanelBody = wp.components.PanelBody;
11 var Button = wp.components.Button;
12 var ToggleControl = wp.components.ToggleControl;
13 var RangeControl = wp.components.RangeControl;
14 var SelectControl = wp.components.SelectControl;
15 var TextControl = wp.components.TextControl;
16 var ColorPicker = wp.components.ColorPicker;
17 var Popover = wp.components.Popover;
18
19 var _ccrTC, _ccrTV;
20 function _tc() { return _ccrTC || (_ccrTC = window.bkbgTypographyControl); }
21 function _tv(obj, prefix) { var fn = _ccrTV || (_ccrTV = window.bkbgTypoCssVars); return fn ? fn(obj, prefix) : {}; }
22
23 var LESSON_TYPE_OPTIONS = [
24 { label: __('Video', 'blockenberg'), value: 'video' },
25 { label: __('Quiz', 'blockenberg'), value: 'quiz' },
26 { label: __('Exercise', 'blockenberg'), value: 'exercise' },
27 { label: __('Article', 'blockenberg'), value: 'article' },
28 ];
29
30 var LESSON_ICONS = { video: '', quiz: '📝', exercise: '💪', article: '📖' };
31
32 function makeId() { return 'cc' + Math.random().toString(36).substr(2, 5); }
33
34 function buildWrapStyle(a) {
35 return Object.assign({
36 '--bkbg-cc-accent': a.accentColor,
37 '--bkbg-cc-card-bg': a.cardBg,
38 '--bkbg-cc-card-border': a.cardBorder,
39 '--bkbg-cc-section-bg': a.sectionBg,
40 '--bkbg-cc-section-border': a.sectionBorder,
41 '--bkbg-cc-section-hover': a.sectionHoverBg,
42 '--bkbg-cc-lesson-bg': a.lessonBg,
43 '--bkbg-cc-lesson-hover': a.lessonHoverBg,
44 '--bkbg-cc-free-bg': a.freeBg,
45 '--bkbg-cc-free-color': a.freeColor,
46 '--bkbg-cc-locked-color': a.lockedColor,
47 '--bkbg-cc-title-color': a.titleColor,
48 '--bkbg-cc-sec-title-color': a.sectionTitleColor,
49 '--bkbg-cc-lesson-color': a.lessonTitleColor,
50 '--bkbg-cc-meta-color': a.metaColor,
51 '--bkbg-cc-btn-bg': a.btnBg,
52 '--bkbg-cc-btn-color': a.btnColor,
53 '--bkbg-cc-card-r': a.cardRadius + 'px',
54 '--bkbg-cc-sec-r': a.sectionRadius + 'px',
55 '--bkbg-cc-btn-r': a.btnRadius + 'px',
56 '--bkbg-cc-title-sz': a.titleSize + 'px',
57 '--bkbg-cc-sec-title-sz': a.sectionTitleSize + 'px',
58 '--bkbg-cc-lesson-sz': a.lessonTitleSize + 'px',
59 paddingTop: a.paddingTop + 'px',
60 paddingBottom: a.paddingBottom + 'px',
61 backgroundColor: a.bgColor || undefined,
62 }, _tv(a.typoTitle, '--bkbg-ccr-ttl-'), _tv(a.typoSection, '--bkbg-ccr-sec-'), _tv(a.typoLesson, '--bkbg-ccr-les-'));
63 }
64
65 /** Pre-typography save style (no typo CSS vars) — used by deprecated. */
66 function buildWrapStyleLegacy(a) {
67 return {
68 '--bkbg-cc-accent': a.accentColor,
69 '--bkbg-cc-card-bg': a.cardBg,
70 '--bkbg-cc-card-border': a.cardBorder,
71 '--bkbg-cc-section-bg': a.sectionBg,
72 '--bkbg-cc-section-border': a.sectionBorder,
73 '--bkbg-cc-section-hover': a.sectionHoverBg,
74 '--bkbg-cc-lesson-bg': a.lessonBg,
75 '--bkbg-cc-lesson-hover': a.lessonHoverBg,
76 '--bkbg-cc-free-bg': a.freeBg,
77 '--bkbg-cc-free-color': a.freeColor,
78 '--bkbg-cc-locked-color': a.lockedColor,
79 '--bkbg-cc-title-color': a.titleColor,
80 '--bkbg-cc-sec-title-color': a.sectionTitleColor,
81 '--bkbg-cc-lesson-color': a.lessonTitleColor,
82 '--bkbg-cc-meta-color': a.metaColor,
83 '--bkbg-cc-btn-bg': a.btnBg,
84 '--bkbg-cc-btn-color': a.btnColor,
85 '--bkbg-cc-card-r': a.cardRadius + 'px',
86 '--bkbg-cc-sec-r': a.sectionRadius + 'px',
87 '--bkbg-cc-btn-r': a.btnRadius + 'px',
88 '--bkbg-cc-title-sz': a.titleSize + 'px',
89 '--bkbg-cc-sec-title-sz': a.sectionTitleSize + 'px',
90 '--bkbg-cc-lesson-sz': a.lessonTitleSize + 'px',
91 paddingTop: a.paddingTop + 'px',
92 paddingBottom: a.paddingBottom + 'px',
93 backgroundColor: a.bgColor || undefined
94 };
95 }
96
97 function saveCurriculum(a, styleFn) {
98 var totalLessons = a.sections.reduce(function (sum, s) { return sum + s.lessons.length; }, 0);
99 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-cc-wrapper', style: styleFn(a) }),
100 el('div', { className: 'bkbg-cc-card', 'data-expand-all': a.expandAll ? '1' : '0', 'data-default-open': a.defaultOpen },
101 el('div', { className: 'bkbg-cc-card-hdr' },
102 el(RichText.Content, { tagName: 'h2', className: 'bkbg-cc-course-title', value: a.courseTitle }),
103 a.showCourseMeta && el('p', { className: 'bkbg-cc-course-meta' }, a.courseMeta || (a.sections.length + ' sections • ' + totalLessons + ' lessons'))
104 ),
105 el('div', { className: 'bkbg-cc-sections-list' },
106 a.sections.map(function (section, idx) {
107 var videoCount = section.lessons.filter(function (l) { return l.type === 'video'; }).length;
108 return el('div', { key: section.id, className: 'bkbg-cc-section', 'data-section-idx': idx },
109 el('button', { type: 'button', className: 'bkbg-cc-sec-hdr', 'aria-expanded': a.expandAll || a.defaultOpen === idx ? 'true' : 'false' },
110 el('span', { className: 'bkbg-cc-sec-arrow', 'aria-hidden': 'true' }, ''),
111 el('span', { className: 'bkbg-cc-sec-title' }, section.title),
112 el('span', { className: 'bkbg-cc-sec-meta' }, section.lessons.length + ' ' + __('lessons', 'blockenberg') + (videoCount ? '' + videoCount + ' ' + __('videos', 'blockenberg') : ''))
113 ),
114 el('div', { className: 'bkbg-cc-lessons', 'aria-hidden': a.expandAll || a.defaultOpen === idx ? 'false' : 'true' },
115 section.lessons.map(function (lesson) {
116 var icon = LESSON_ICONS[lesson.type] || '';
117 return el('div', { key: lesson.id, className: 'bkbg-cc-lesson ' + (lesson.free ? 'is-free' : 'is-locked'), 'data-type': lesson.type },
118 el('div', { className: 'bkbg-cc-lesson-left' },
119 el('span', { className: 'bkbg-cc-lesson-icon', 'aria-hidden': 'true' }, icon),
120 el('span', { className: 'bkbg-cc-lesson-title' }, lesson.title)
121 ),
122 el('div', { className: 'bkbg-cc-lesson-right' },
123 lesson.free
124 ? el('span', { className: 'bkbg-cc-free-badge' }, __('Free', 'blockenberg'))
125 : el('span', { className: 'bkbg-cc-locked-icon', 'aria-label': __('Locked', 'blockenberg') }, '🔒'),
126 lesson.duration && el('span', { className: 'bkbg-cc-lesson-dur' }, lesson.duration)
127 )
128 );
129 })
130 )
131 );
132 })
133 ),
134 a.showCta && el('div', { className: 'bkbg-cc-cta-wrap' },
135 el('a', { href: a.ctaUrl, className: 'bkbg-cc-cta-btn' }, a.ctaLabel)
136 )
137 )
138 );
139 }
140
141 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
142 var isOpen = openKey === key;
143 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
144 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
145 el('div', { style: { position: 'relative', flexShrink: 0 } },
146 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' } }),
147 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
148 el('div', { style: { padding: '8px' } },
149 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
150 )
151 )
152 )
153 );
154 }
155
156 /* ── Lesson row in editor ────────────────────────────────────────── */
157 function LessonRow(props) {
158 var lesson = props.lesson;
159 var sectionId = props.sectionId;
160 var a = props.a;
161 var onUpdate = props.onUpdate;
162 var onRemove = props.onRemove;
163 var showEdit = props.showEdit;
164 var setShowEdit = props.setShowEdit;
165 var icon = LESSON_ICONS[lesson.type] || '';
166
167 return el('div', { className: 'bkbg-cc-lesson-row', style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '7px 12px', borderBottom: '1px solid ' + a.cardBorder, background: a.lessonBg } },
168 el('span', { style: { fontSize: '14px', flexShrink: 0, minWidth: '20px' } }, icon),
169 el('input', { type: 'text', className: 'bkbg-cc-lesson-title', value: lesson.title, onChange: function (e) { onUpdate('title', e.target.value); },
170 placeholder: __('Lesson title…', 'blockenberg'),
171 style: { flex: 1, border: 'none', outline: 'none', color: a.lessonTitleColor, background: 'transparent', padding: 0 }
172 }),
173 el('select', { value: lesson.type, onChange: function (e) { onUpdate('type', e.target.value); }, style: { fontSize: '11px', border: '1px solid #ddd', borderRadius: '4px', background: '#fff', color: '#555', cursor: 'pointer', padding: '1px 4px', flexShrink: 0 } },
174 LESSON_TYPE_OPTIONS.map(function (opt) { return el('option', { key: opt.value, value: opt.value }, opt.label); })
175 ),
176 el('input', { type: 'text', value: lesson.duration, onChange: function (e) { onUpdate('duration', e.target.value); },
177 placeholder: '5:00',
178 style: { width: '46px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '11px', color: '#555', textAlign: 'center', padding: '2px 4px', flexShrink: 0 }
179 }),
180 el('label', { style: { display: 'flex', alignItems: 'center', gap: '3px', fontSize: '11px', color: a.freeColor, cursor: 'pointer', flexShrink: 0 } },
181 el('input', { type: 'checkbox', checked: lesson.free, onChange: function (e) { onUpdate('free', e.target.checked); }, style: { accentColor: a.freeColor, width: '12px', height: '12px' } }),
182 __('Free', 'blockenberg')
183 ),
184 el('button', { type: 'button', onClick: onRemove, style: { border: 'none', background: 'none', cursor: 'pointer', color: '#ef4444', fontSize: '14px', padding: '0 2px', flexShrink: 0 } }, '×')
185 );
186 }
187
188 /* ── Section block in editor ─────────────────────────────────────── */
189 function SectionBlock(props) {
190 var section = props.section;
191 var a = props.a;
192 var isOpen = props.isOpen;
193 var onToggle = props.onToggle;
194 var onUpdateSection = props.onUpdateSection;
195 var onRemoveSection = props.onRemoveSection;
196 var onAddLesson = props.onAddLesson;
197 var onUpdateLesson = props.onUpdateLesson;
198 var onRemoveLesson = props.onRemoveLesson;
199 var editLessonState = useState(null);
200 var setEditLesson = editLessonState[1];
201 var showEditLesson = editLessonState[0];
202
203 return el('div', { className: 'bkbg-cc-section', style: { border: '1px solid ' + a.sectionBorder, borderRadius: a.sectionRadius + 'px', marginBottom: '8px', overflow: 'hidden' } },
204 /* Section header row */
205 el('div', { className: isOpen ? 'bkbg-cc-sec-hdr is-open' : 'bkbg-cc-sec-hdr', style: { display: 'flex', alignItems: 'center', gap: '10px', padding: '12px 14px', background: a.sectionBg, cursor: 'pointer' } },
206 el('span', { style: { fontSize: '14px', transform: isOpen ? 'rotate(90deg)' : 'none', transition: 'transform 0.2s', flexShrink: 0, color: a.accentColor } }, ''),
207 el('input', { type: 'text', className: 'bkbg-cc-sec-title', value: section.title, onChange: function (e) { onUpdateSection('title', e.target.value); },
208 onClick: function (e) { e.stopPropagation(); },
209 placeholder: __('Section title…', 'blockenberg'),
210 style: { flex: 1, border: 'none', outline: 'none', background: 'transparent', color: a.sectionTitleColor }
211 }),
212 el('span', { style: { fontSize: '12px', color: a.metaColor, flexShrink: 0 } }, section.lessons.length + ' ' + (section.lessons.length === 1 ? __('lesson', 'blockenberg') : __('lessons', 'blockenberg'))),
213 el('button', { type: 'button', onClick: function (e) { e.stopPropagation(); onRemoveSection(); }, style: { border: 'none', background: 'none', cursor: 'pointer', color: '#ef4444', fontSize: '16px', padding: '0 2px', flexShrink: 0 } }, '×'),
214 /* toggle icon */
215 el('div', { onClick: function () { onToggle(); }, style: { position: 'absolute', inset: 0 } })
216 ),
217 /* Lessons */
218 isOpen && el('div', { className: 'bkbg-cc-sec-body' },
219 section.lessons.map(function (lesson) {
220 return el(LessonRow, { key: lesson.id, lesson: lesson, sectionId: section.id, a: a,
221 showEdit: showEditLesson === lesson.id,
222 setShowEdit: setEditLesson,
223 onUpdate: function (key, val) { onUpdateLesson(lesson.id, key, val); },
224 onRemove: function () { onRemoveLesson(lesson.id); }
225 });
226 }),
227 el('button', { type: 'button', onClick: onAddLesson, style: { display: 'block', width: '100%', padding: '8px', background: 'none', border: 'none', borderTop: '1px solid ' + a.cardBorder, cursor: 'pointer', fontSize: '12px', color: a.accentColor, fontWeight: 700, textAlign: 'center' } },
228 '+ ' + __('Add Lesson', 'blockenberg')
229 )
230 )
231 );
232 }
233
234 registerBlockType('blockenberg/course-curriculum', {
235 title: __('Course Curriculum', 'blockenberg'),
236 icon: 'welcome-learn-more',
237 category: 'bkbg-blog',
238
239 edit: function (props) {
240 var a = props.attributes;
241 var setAttributes = props.setAttributes;
242
243 var openColorKeyState = useState(null);
244 var openColorKey = openColorKeyState[0];
245 var setOpenColorKey = openColorKeyState[1];
246
247 var openSectionState = useState(0);
248 var openSection = openSectionState[0];
249 var setOpenSection = openSectionState[1];
250
251 var blockProps = useBlockProps({ style: buildWrapStyle(a) });
252
253 function cc(key, label, attrKey) {
254 return renderColorControl(key, label, a[attrKey], function (val) {
255 var upd = {}; upd[attrKey] = val; setAttributes(upd);
256 }, openColorKey, setOpenColorKey);
257 }
258
259 function updateSection(sectionId, key, val) {
260 setAttributes({ sections: a.sections.map(function (s) {
261 if (s.id !== sectionId) return s;
262 var u = Object.assign({}, s); u[key] = val; return u;
263 }) });
264 }
265
266 function addSection() {
267 var newId = makeId();
268 setAttributes({ sections: a.sections.concat([{
269 id: newId,
270 title: __('New Section', 'blockenberg'),
271 lessons: []
272 }]) });
273 setOpenSection(a.sections.length); /* open the new one */
274 }
275
276 function removeSection(sectionId) {
277 setAttributes({ sections: a.sections.filter(function (s) { return s.id !== sectionId; }) });
278 }
279
280 function addLesson(sectionId) {
281 setAttributes({ sections: a.sections.map(function (s) {
282 if (s.id !== sectionId) return s;
283 return Object.assign({}, s, { lessons: s.lessons.concat([{ id: makeId(), title: __('New Lesson', 'blockenberg'), duration: '5:00', type: 'video', free: false }]) });
284 }) });
285 }
286
287 function updateLesson(sectionId, lessonId, key, val) {
288 setAttributes({ sections: a.sections.map(function (s) {
289 if (s.id !== sectionId) return s;
290 return Object.assign({}, s, { lessons: s.lessons.map(function (l) {
291 if (l.id !== lessonId) return l;
292 var u = Object.assign({}, l); u[key] = val; return u;
293 }) });
294 }) });
295 }
296
297 function removeLesson(sectionId, lessonId) {
298 setAttributes({ sections: a.sections.map(function (s) {
299 if (s.id !== sectionId) return s;
300 return Object.assign({}, s, { lessons: s.lessons.filter(function (l) { return l.id !== lessonId; }) });
301 }) });
302 }
303
304 var totalLessons = a.sections.reduce(function (sum, s) { return sum + s.lessons.length; }, 0);
305
306 return el(Fragment, null,
307 el(InspectorControls, null,
308 el(PanelBody, { title: __('Course Info', 'blockenberg'), initialOpen: true },
309 el(TextControl, { label: __('Course meta line', 'blockenberg'), value: a.courseMeta, onChange: function (v) { setAttributes({ courseMeta: v }); }, help: __('e.g. "12 sections • 48 lessons • 24h"', 'blockenberg') }),
310 el(ToggleControl, { label: __('Show course meta', 'blockenberg'), checked: a.showCourseMeta, onChange: function (v) { setAttributes({ showCourseMeta: v }); }, __nextHasNoMarginBottom: true }),
311 el(ToggleControl, { label: __('Show search bar', 'blockenberg'), checked: a.showSearch, onChange: function (v) { setAttributes({ showSearch: v }); }, __nextHasNoMarginBottom: true }),
312 el(ToggleControl, { label: __('Expand all by default', 'blockenberg'), checked: a.expandAll, onChange: function (v) { setAttributes({ expandAll: v }); }, __nextHasNoMarginBottom: true }),
313 !a.expandAll && el(RangeControl, { label: __('Default open section (0 = first)', 'blockenberg'), value: a.defaultOpen, min: 0, max: Math.max(0, a.sections.length - 1), onChange: function (v) { setAttributes({ defaultOpen: v }); } })
314 ),
315 el(PanelBody, { title: __('CTA Button', 'blockenberg'), initialOpen: false },
316 el(ToggleControl, { label: __('Show CTA button', 'blockenberg'), checked: a.showCta, onChange: function (v) { setAttributes({ showCta: v }); }, __nextHasNoMarginBottom: true }),
317 a.showCta && el(TextControl, { label: __('Button label', 'blockenberg'), value: a.ctaLabel, onChange: function (v) { setAttributes({ ctaLabel: v }); } }),
318 a.showCta && el(TextControl, { label: __('Button URL', 'blockenberg'), value: a.ctaUrl, onChange: function (v) { setAttributes({ ctaUrl: v }); } })
319 ),
320 el(PanelBody, { title: __('Sizing', 'blockenberg'), initialOpen: false },
321 el(RangeControl, { label: __('Card radius (px)', 'blockenberg'), value: a.cardRadius, min: 0, max: 32, onChange: function (v) { setAttributes({ cardRadius: v }); } }),
322 el(RangeControl, { label: __('Section radius (px)', 'blockenberg'), value: a.sectionRadius, min: 0, max: 20, onChange: function (v) { setAttributes({ sectionRadius: v }); } }),
323 el(RangeControl, { label: __('Button radius (px)', 'blockenberg'), value: a.btnRadius, min: 0, max: 99, onChange: function (v) { setAttributes({ btnRadius: v }); } }),
324 ),
325
326 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
327 _tc() && el(_tc(), { label: __('Course Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { setAttributes({ typoTitle: v }); } }),
328 _tc() && el(_tc(), { label: __('Section Title', 'blockenberg'), value: a.typoSection, onChange: function (v) { setAttributes({ typoSection: v }); } }),
329 _tc() && el(_tc(), { label: __('Lesson Title', 'blockenberg'), value: a.typoLesson, onChange: function (v) { setAttributes({ typoLesson: v }); } })
330 ),
331 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
332 cc('accentColor', __('Accent', 'blockenberg'), 'accentColor'),
333 cc('titleColor', __('Course title', 'blockenberg'), 'titleColor'),
334 cc('sectionTitleColor', __('Section title', 'blockenberg'), 'sectionTitleColor'),
335 cc('lessonTitleColor', __('Lesson text', 'blockenberg'), 'lessonTitleColor'),
336 cc('metaColor', __('Meta / count', 'blockenberg'), 'metaColor'),
337 cc('cardBg', __('Card bg', 'blockenberg'), 'cardBg'),
338 cc('cardBorder', __('Card border', 'blockenberg'), 'cardBorder'),
339 cc('sectionBg', __('Section header bg','blockenberg'), 'sectionBg'),
340 cc('sectionBorder', __('Section border', 'blockenberg'), 'sectionBorder'),
341 cc('sectionHoverBg', __('Section hover', 'blockenberg'), 'sectionHoverBg'),
342 cc('lessonBg', __('Lesson row bg', 'blockenberg'), 'lessonBg'),
343 cc('lessonHoverBg', __('Lesson hover', 'blockenberg'), 'lessonHoverBg'),
344 cc('freeBg', __('Free badge bg', 'blockenberg'), 'freeBg'),
345 cc('freeColor', __('Free badge text', 'blockenberg'), 'freeColor'),
346 cc('lockedColor', __('Locked icon', 'blockenberg'), 'lockedColor'),
347 cc('btnBg', __('Button bg', 'blockenberg'), 'btnBg'),
348 cc('btnColor', __('Button text', 'blockenberg'), 'btnColor'),
349 cc('bgColor', __('Section bg', 'blockenberg'), 'bgColor')
350 ),
351 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
352 el(RangeControl, { label: __('Padding top', 'blockenberg'), value: a.paddingTop, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingTop: v }); } }),
353 el(RangeControl, { label: __('Padding bottom', 'blockenberg'), value: a.paddingBottom, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingBottom: v }); } })
354 )
355 ),
356
357 el('div', blockProps,
358 /* Card */
359 el('div', { className: 'bkbg-cc-card', style: { background: a.cardBg, border: '1px solid ' + a.cardBorder, borderRadius: a.cardRadius + 'px', overflow: 'hidden' } },
360 /* Card header */
361 el('div', { className: 'bkbg-cc-card-hdr', style: { padding: '24px 28px', borderBottom: '1px solid ' + a.cardBorder } },
362 el(RichText, { tagName: 'h2', className: 'bkbg-cc-course-title', value: a.courseTitle, onChange: function (v) { setAttributes({ courseTitle: v }); }, placeholder: __('Course title…', 'blockenberg'), style: { color: a.titleColor, margin: '0 0 6px' } }),
363 a.showCourseMeta && el('p', { className: 'bkbg-cc-course-meta', style: { fontSize: '13px', color: a.metaColor, margin: 0 } }, a.courseMeta || (a.sections.length + ' sections • ' + totalLessons + ' lessons'))
364 ),
365 /* Sections */
366 el('div', { className: 'bkbg-cc-sections-list', style: { padding: '16px 20px' } },
367 a.sections.map(function (section, idx) {
368 return el(SectionBlock, { key: section.id, section: section, a: a,
369 isOpen: openSection === idx,
370 onToggle: function () { setOpenSection(openSection === idx ? -1 : idx); },
371 onUpdateSection: function (key, val) { updateSection(section.id, key, val); },
372 onRemoveSection: function () { removeSection(section.id); },
373 onAddLesson: function () { addLesson(section.id); },
374 onUpdateLesson: function (lid, key, val) { updateLesson(section.id, lid, key, val); },
375 onRemoveLesson: function (lid) { removeLesson(section.id, lid); }
376 });
377 }),
378 el(Button, { variant: 'secondary', onClick: addSection, style: { width: '100%', justifyContent: 'center', marginTop: '8px' } }, '+ ' + __('Add Section', 'blockenberg'))
379 ),
380 /* CTA */
381 a.showCta && el('div', { className: 'bkbg-cc-cta-wrap', style: { padding: '20px 28px', borderTop: '1px solid ' + a.cardBorder, textAlign: 'center' } },
382 el('a', { href: a.ctaUrl, className: 'bkbg-cc-cta-btn', style: { display: 'inline-block', padding: '14px 36px', borderRadius: a.btnRadius + 'px', background: a.btnBg, color: a.btnColor, fontWeight: 800, fontSize: '16px', textDecoration: 'none' } }, a.ctaLabel)
383 )
384 )
385 )
386 );
387 },
388
389 save: function (props) {
390 return saveCurriculum(props.attributes, buildWrapStyle);
391 },
392
393 deprecated: [{
394 save: function (props) {
395 return saveCurriculum(props.attributes, buildWrapStyleLegacy);
396 },
397 migrate: function (attributes) { return attributes; }
398 }]
399 });
400 }() );
401