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 / faq-schema / index.js

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

468 lines 23.3 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 RichText = wp.blockEditor.RichText;
10 var PanelBody = wp.components.PanelBody;
11 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
12 var Button = wp.components.Button;
13 var ToggleControl = wp.components.ToggleControl;
14 var RangeControl = wp.components.RangeControl;
15 var SelectControl = wp.components.SelectControl;
16 var TextControl = wp.components.TextControl;
17
18 var _fqsTC, _fqsTV;
19 function _tc() { return _fqsTC || (_fqsTC = window.bkbgTypographyControl); }
20 function _tv(o, p) { if (!_fqsTV) _fqsTV = window.bkbgTypoCssVars; return _fqsTV ? _fqsTV(o, p) : {}; }
21 function _tvStr(o, p) { var obj = _tv(o, p); return Object.keys(obj).map(function (k) { return k + ':' + obj[k]; }); }
22
23 // SVG icons
24 var icons = {
25 chevron: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>',
26 plus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',
27 arrow: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>'
28 };
29
30 function getIconEl(type) {
31 var svg = icons[type] || icons.chevron;
32 return el('span', {
33 className: 'bkbg-faq-icon',
34 'aria-hidden': 'true',
35 dangerouslySetInnerHTML: { __html: svg }
36 });
37 }
38
39 var layoutOptions = [
40 { label: __('Default (Separated)', 'blockenberg'), value: 'default' },
41 { label: __('Boxed', 'blockenberg'), value: 'boxed' },
42 { label: __('Minimal', 'blockenberg'), value: 'minimal' },
43 { label: __('Bordered Left', 'blockenberg'), value: 'bordered-left' }
44 ];
45
46 var iconTypeOptions = [
47 { label: __('Chevron', 'blockenberg'), value: 'chevron' },
48 { label: __('Plus / Minus', 'blockenberg'), value: 'plus' },
49 { label: __('Arrow', 'blockenberg'), value: 'arrow' },
50 { label: __('None', 'blockenberg'), value: 'none' }
51 ];
52
53 var iconPositionOptions = [
54 { label: __('Right', 'blockenberg'), value: 'right' },
55 { label: __('Left', 'blockenberg'), value: 'left' }
56 ];
57
58 var fontWeightOptions = [
59 { label: '300', value: 300 },
60 { label: '400', value: 400 },
61 { label: '500', value: 500 },
62 { label: '600', value: 600 },
63 { label: '700', value: 700 },
64 { label: '800', value: 800 }
65 ];
66
67 registerBlockType('blockenberg/faq-schema', {
68 edit: function (props) {
69 var attributes = props.attributes;
70 var setAttributes = props.setAttributes;
71 var a = attributes;
72
73 var activeItems = a.activeItems || [0];
74
75 function setActiveItems(newActive) {
76 setAttributes({ activeItems: newActive });
77 }
78
79 function toggleItem(index) {
80 var isActive = activeItems.indexOf(index) !== -1;
81 var newActive;
82 if (isActive) {
83 newActive = activeItems.filter(function (i) { return i !== index; });
84 } else {
85 if (a.allowMultiple) {
86 newActive = activeItems.concat([index]);
87 } else {
88 newActive = [index];
89 }
90 }
91 setActiveItems(newActive);
92 }
93
94 function updateItem(index, field, value) {
95 var newItems = a.items.map(function (item, i) {
96 if (i !== index) return item;
97 var updated = {};
98 for (var k in item) { updated[k] = item[k]; }
99 updated[field] = value;
100 return updated;
101 });
102 setAttributes({ items: newItems });
103 }
104
105 function addItem() {
106 setAttributes({
107 items: a.items.concat([{ question: __('New Question', 'blockenberg'), answer: __('Answer goes here...', 'blockenberg') }])
108 });
109 setActiveItems(activeItems.concat([a.items.length]));
110 }
111
112 function removeItem(index) {
113 if (a.items.length <= 1) return;
114 setAttributes({ items: a.items.filter(function (_, i) { return i !== index; }) });
115 setActiveItems(activeItems.filter(function (i) { return i !== index; }).map(function (i) { return i > index ? i - 1 : i; }));
116 }
117
118 function moveItem(index, dir) {
119 var newIndex = index + dir;
120 if (newIndex < 0 || newIndex >= a.items.length) return;
121 var newItems = a.items.slice();
122 var tmp = newItems[index];
123 newItems[index] = newItems[newIndex];
124 newItems[newIndex] = tmp;
125 setAttributes({ items: newItems });
126 }
127
128 var wrapStyle = Object.assign({
129 '--bkbg-faq-spacing': a.spacing + 'px',
130 '--bkbg-faq-radius': a.borderRadius + 'px',
131 '--bkbg-faq-q-padding': a.questionPadding + 'px',
132 '--bkbg-faq-a-padding': a.answerPadding + 'px',
133 '--bkbg-faq-speed': a.animationSpeed + 'ms',
134 '--bkbg-faq-q-color': a.questionColor,
135 '--bkbg-faq-q-bg': a.questionBg,
136 '--bkbg-faq-q-hover-bg': a.questionHoverBg,
137 '--bkbg-faq-q-active-bg': a.activeQuestionBg,
138 '--bkbg-faq-q-active-color': a.activeQuestionColor,
139 '--bkbg-faq-a-color': a.answerColor,
140 '--bkbg-faq-a-bg': a.answerBg,
141 '--bkbg-faq-icon-color': a.iconColor,
142 '--bkbg-faq-icon-active-color': a.iconActiveColor,
143 '--bkbg-faq-border-color': a.borderColor,
144 '--bkbg-faq-divider-color': a.dividerColor
145 },
146 _tv(a.typoQuestion, '--bkbg-fqs-qt-'),
147 _tv(a.typoAnswer, '--bkbg-fqs-an-'));
148
149 var blockProps = useBlockProps({
150 className: 'bkbg-faq-wrap bkbg-faq-editor',
151 style: wrapStyle,
152 'data-layout': a.layout,
153 'data-icon': a.iconType,
154 'data-icon-pos': a.iconPosition
155 });
156
157 var inspector = el(InspectorControls, {},
158
159 // Items panel
160 el(PanelBody, { title: __('FAQ Items', 'blockenberg'), initialOpen: true },
161 el('p', { style: { color: '#6b7280', fontSize: '12px', margin: '0 0 12px' } },
162 __('Click a question to expand/collapse it in the editor.', 'blockenberg')
163 ),
164 el(ToggleControl, {
165 label: __('Allow multiple open', 'blockenberg'),
166 checked: a.allowMultiple,
167 __nextHasNoMarginBottom: true,
168 onChange: function (v) { setAttributes({ allowMultiple: v }); }
169 }),
170 el(ToggleControl, {
171 label: __('First item open by default', 'blockenberg'),
172 checked: a.expandFirst,
173 __nextHasNoMarginBottom: true,
174 onChange: function (v) { setAttributes({ expandFirst: v }); }
175 })
176 ),
177
178 // Layout panel
179 el(PanelBody, { title: __('Layout & Style', 'blockenberg'), initialOpen: false },
180 el(SelectControl, {
181 label: __('Layout', 'blockenberg'),
182 value: a.layout,
183 options: layoutOptions,
184 onChange: function (v) { setAttributes({ layout: v }); }
185 }),
186 el(SelectControl, {
187 label: __('Icon Type', 'blockenberg'),
188 value: a.iconType,
189 options: iconTypeOptions,
190 onChange: function (v) { setAttributes({ iconType: v }); }
191 }),
192 el(SelectControl, {
193 label: __('Icon Position', 'blockenberg'),
194 value: a.iconPosition,
195 options: iconPositionOptions,
196 onChange: function (v) { setAttributes({ iconPosition: v }); }
197 }),
198 el(RangeControl, {
199 label: __('Gap Between Items (px)', 'blockenberg'),
200 value: a.spacing,
201 min: 0,
202 max: 40,
203 onChange: function (v) { setAttributes({ spacing: v }); }
204 }),
205 el(RangeControl, {
206 label: __('Border Radius (px)', 'blockenberg'),
207 value: a.borderRadius,
208 min: 0,
209 max: 24,
210 onChange: function (v) { setAttributes({ borderRadius: v }); }
211 }),
212 el(RangeControl, {
213 label: __('Question Padding (px)', 'blockenberg'),
214 value: a.questionPadding,
215 min: 8,
216 max: 40,
217 onChange: function (v) { setAttributes({ questionPadding: v }); }
218 }),
219 el(RangeControl, {
220 label: __('Answer Padding (px)', 'blockenberg'),
221 value: a.answerPadding,
222 min: 8,
223 max: 40,
224 onChange: function (v) { setAttributes({ answerPadding: v }); }
225 }),
226 el(RangeControl, {
227 label: __('Animation Speed (ms)', 'blockenberg'),
228 value: a.animationSpeed,
229 min: 0,
230 max: 800,
231 step: 50,
232 onChange: function (v) { setAttributes({ animationSpeed: v }); }
233 })
234 ),
235
236 // Typography panel
237 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
238 _tc() && _tc()({ label: __('Question', 'blockenberg'), typo: a.typoQuestion, onChange: function (v) { setAttributes({ typoQuestion: v }); } }),
239 _tc() && _tc()({ label: __('Answer', 'blockenberg'), typo: a.typoAnswer, onChange: function (v) { setAttributes({ typoAnswer: v }); } })
240 ),
241
242 // Colors panel
243 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
244 el(PanelColorSettings, {
245 title: __('Question Colors', 'blockenberg'),
246 initialOpen: false,
247 colorSettings: [
248 { value: a.questionColor, onChange: function (c) { setAttributes({ questionColor: c || '#111827' }); }, label: __('Text', 'blockenberg') },
249 { value: a.questionBg, onChange: function (c) { setAttributes({ questionBg: c || '#ffffff' }); }, label: __('Background', 'blockenberg') },
250 { value: a.questionHoverBg, onChange: function (c) { setAttributes({ questionHoverBg: c || '#f9fafb' }); }, label: __('Background (Hover)', 'blockenberg') },
251 { value: a.activeQuestionColor, onChange: function (c) { setAttributes({ activeQuestionColor: c || '#1d4ed8' }); }, label: __('Text (Active)', 'blockenberg') },
252 { value: a.activeQuestionBg, onChange: function (c) { setAttributes({ activeQuestionBg: c || '#eff6ff' }); }, label: __('Background (Active)', 'blockenberg') }
253 ]
254 }),
255 el(PanelColorSettings, {
256 title: __('Answer Colors', 'blockenberg'),
257 initialOpen: false,
258 colorSettings: [
259 { value: a.answerColor, onChange: function (c) { setAttributes({ answerColor: c || '#374151' }); }, label: __('Text', 'blockenberg') },
260 { value: a.answerBg, onChange: function (c) { setAttributes({ answerBg: c || '#ffffff' }); }, label: __('Background', 'blockenberg') }
261 ]
262 }),
263 el(PanelColorSettings, {
264 title: __('Icon & Border Colors', 'blockenberg'),
265 initialOpen: false,
266 colorSettings: [
267 { value: a.iconColor, onChange: function (c) { setAttributes({ iconColor: c || '#6b7280' }); }, label: __('Icon', 'blockenberg') },
268 { value: a.iconActiveColor, onChange: function (c) { setAttributes({ iconActiveColor: c || '#2563eb' }); }, label: __('Icon (Active)', 'blockenberg') },
269 { value: a.borderColor, onChange: function (c) { setAttributes({ borderColor: c || '#e5e7eb' }); }, label: __('Border', 'blockenberg') },
270 { value: a.dividerColor, onChange: function (c) { setAttributes({ dividerColor: c || '#f3f4f6' }); }, label: __('Divider', 'blockenberg') }
271 ]
272 })
273 )
274 );
275
276 // Build item list in editor
277 var itemsEl = a.items.map(function (item, index) {
278 var isActive = activeItems.indexOf(index) !== -1;
279 return el('div', {
280 className: 'bkbg-faq-item' + (isActive ? ' is-active' : ''),
281 key: 'faq-' + index
282 },
283 el('div', { className: 'bkbg-faq-item-actions' },
284 el(Button, {
285 icon: 'arrow-up-alt2',
286 label: __('Move up', 'blockenberg'),
287 onClick: function () { moveItem(index, -1); },
288 disabled: index === 0,
289 isSmall: true
290 }),
291 el(Button, {
292 icon: 'arrow-down-alt2',
293 label: __('Move down', 'blockenberg'),
294 onClick: function () { moveItem(index, 1); },
295 disabled: index === a.items.length - 1,
296 isSmall: true
297 }),
298 el(Button, {
299 icon: 'trash',
300 label: __('Remove', 'blockenberg'),
301 onClick: function () { removeItem(index); },
302 isDestructive: true,
303 isSmall: true,
304 disabled: a.items.length <= 1
305 })
306 ),
307 el('button', {
308 className: 'bkbg-faq-question',
309 'aria-expanded': isActive ? 'true' : 'false',
310 onClick: function (e) {
311 if (e.target.tagName === 'INPUT') return;
312 toggleItem(index);
313 }
314 },
315 a.iconPosition === 'left' && a.iconType !== 'none' && getIconEl(a.iconType),
316 el('input', {
317 className: 'bkbg-faq-q-input',
318 type: 'text',
319 value: item.question,
320 placeholder: __('Enter question...', 'blockenberg'),
321 onChange: function (e) { updateItem(index, 'question', e.target.value); },
322 onClick: function (e) { e.stopPropagation(); }
323 }),
324 a.iconPosition === 'right' && a.iconType !== 'none' && getIconEl(a.iconType)
325 ),
326 el('div', { className: 'bkbg-faq-answer', 'aria-hidden': isActive ? 'false' : 'true' },
327 el('div', { className: 'bkbg-faq-answer-inner' },
328 el(RichText, {
329 tagName: 'div',
330 className: 'bkbg-faq-answer-body',
331 value: item.answer,
332 onChange: function (val) { updateItem(index, 'answer', val); },
333 placeholder: __('Enter answer...', 'blockenberg')
334 })
335 )
336 )
337 );
338 });
339
340 return el(Fragment, {},
341 inspector,
342 el('div', blockProps,
343 el('div', { className: 'bkbg-faq-schema-badge' },
344 el('span', {}, '✓ FAQ Schema (JSON-LD)')
345 ),
346 itemsEl,
347 el('div', { className: 'bkbg-faq-add-wrap' },
348 el(Button, {
349 variant: 'secondary',
350 className: 'bkbg-faq-add-btn',
351 onClick: addItem
352 }, '+ ' + __('Add Question', 'blockenberg'))
353 )
354 )
355 );
356 },
357
358 save: function (props) {
359 var a = props.attributes;
360 var RichTextContent = wp.blockEditor.RichText.Content;
361
362 // Build JSON-LD
363 var schemaItems = a.items.map(function (item) {
364 return {
365 '@type': 'Question',
366 'name': item.question,
367 'acceptedAnswer': {
368 '@type': 'Answer',
369 'text': item.answer.replace(/<[^>]+>/g, '')
370 }
371 };
372 });
373
374 var schema = JSON.stringify({
375 '@context': 'https://schema.org',
376 '@type': 'FAQPage',
377 'mainEntity': schemaItems
378 });
379
380 var wrapStyle = [
381 '--bkbg-faq-spacing:' + a.spacing + 'px',
382 '--bkbg-faq-radius:' + a.borderRadius + 'px',
383 '--bkbg-faq-q-padding:' + a.questionPadding + 'px',
384 '--bkbg-faq-a-padding:' + a.answerPadding + 'px',
385 '--bkbg-faq-speed:' + a.animationSpeed + 'ms',
386 '--bkbg-faq-q-color:' + a.questionColor,
387 '--bkbg-faq-q-bg:' + a.questionBg,
388 '--bkbg-faq-q-hover-bg:' + a.questionHoverBg,
389 '--bkbg-faq-q-active-bg:' + a.activeQuestionBg,
390 '--bkbg-faq-q-active-color:' + a.activeQuestionColor,
391 '--bkbg-faq-a-color:' + a.answerColor,
392 '--bkbg-faq-a-bg:' + a.answerBg,
393 '--bkbg-faq-icon-color:' + a.iconColor,
394 '--bkbg-faq-icon-active-color:' + a.iconActiveColor,
395 '--bkbg-faq-border-color:' + a.borderColor,
396 '--bkbg-faq-divider-color:' + a.dividerColor
397 ].concat(_tvStr(a.typoQuestion, '--bkbg-fqs-qt-'))
398 .concat(_tvStr(a.typoAnswer, '--bkbg-fqs-an-')).join(';');
399
400 var blockProps = wp.blockEditor.useBlockProps.save({
401 className: 'bkbg-faq-wrap',
402 style: wrapStyle,
403 'data-layout': a.layout,
404 'data-icon': a.iconType,
405 'data-icon-pos': a.iconPosition,
406 'data-allow-multiple': a.allowMultiple ? '1' : '0',
407 'data-expand-first': a.expandFirst ? '1' : '0',
408 'data-speed': a.animationSpeed
409 });
410
411 var itemsEl = a.items.map(function (item, index) {
412 var isFirst = index === 0 && a.expandFirst;
413 return el('div', {
414 className: 'bkbg-faq-item' + (isFirst ? ' is-active' : ''),
415 key: 'faq-' + index,
416 itemScope: true,
417 itemProp: 'mainEntity',
418 itemType: 'https://schema.org/Question'
419 },
420 el('button', {
421 className: 'bkbg-faq-question',
422 'aria-expanded': isFirst ? 'true' : 'false',
423 itemProp: 'name'
424 },
425 a.iconPosition === 'left' && a.iconType !== 'none' && el('span', {
426 className: 'bkbg-faq-icon',
427 'aria-hidden': 'true',
428 dangerouslySetInnerHTML: { __html: a.iconType === 'plus' ? icons.plus : a.iconType === 'arrow' ? icons.arrow : icons.chevron }
429 }),
430 el('span', { className: 'bkbg-faq-q-text' }, item.question),
431 a.iconPosition === 'right' && a.iconType !== 'none' && el('span', {
432 className: 'bkbg-faq-icon',
433 'aria-hidden': 'true',
434 dangerouslySetInnerHTML: { __html: a.iconType === 'plus' ? icons.plus : a.iconType === 'arrow' ? icons.arrow : icons.chevron }
435 })
436 ),
437 el('div', {
438 className: 'bkbg-faq-answer',
439 'aria-hidden': isFirst ? 'false' : 'true',
440 itemScope: true,
441 itemProp: 'acceptedAnswer',
442 itemType: 'https://schema.org/Answer'
443 },
444 el('div', { className: 'bkbg-faq-answer-inner', itemProp: 'text' },
445 el(RichTextContent, { tagName: 'div', className: 'bkbg-faq-answer-body', value: item.answer })
446 )
447 )
448 );
449 });
450
451 return el('div', blockProps,
452 itemsEl,
453 el('script', {
454 type: 'application/ld+json',
455 dangerouslySetInnerHTML: { __html: schema }
456 })
457 );
458 }
459 });
460
461 // SVG strings for save
462 var icons = {
463 chevron: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>',
464 plus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',
465 arrow: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>'
466 };
467 }() );
468